.NET MAUI开发:从视图类访问ViewModel是否为不良实践?
.NET MAUI视图直接调用ViewModel方法的优化建议
我正在用.NET MAUI开发桌面应用,未采用Prism框架,仅使用内置功能实现。目前经常需要从视图类中直接调用ViewModel的方法,不确定这是否属于不良实践,也想不到其他替代方案,希望能得到优化建议。
示例代码1:删除按钮点击事件
场景:删除按钮点击后,调用ViewModel的数据库删除方法,同时操作UI布局,最后调用父ViewModel的Populate方法。
private async void DeleteButtonTapped(object sender, TappedEventArgs e) { if (answer) { var bindingContext = BindingContext as AgentDetailsPageViewModel; await bindingContext.DeleteFromDatabase(); var agentsViews = this.Parent as Grid; agentsViews.Remove(this); var parentBorder = agentsViews.Parent as Border; parentBorder.IsVisible = false; var parentGrid = parentBorder.Parent as Grid; var agentsBorder = parentGrid.Children[0] as Border; Grid.SetColumnSpan(agentsBorder, 2); var parentBindingContext = parentGrid.BindingContext as AgentsPageViewModel; parentBindingContext.Populate(); } }
示例代码2:动态添加信用卡输入框
场景:用户可动态添加信用卡输入框,无法实现动态绑定ViewModel属性,因此从UI中遍历获取输入值,整理后传给ViewModel并调用数据库添加方法。
ObservableCollection<string> creditCardEntries = new ObservableCollection<string>(); foreach (var creditCardborderStack in creditCardbordersStack) { var grid = creditCardborderStack.GetChildren<Grid>().FirstOrDefault(); var entry = grid.GetChildren<Entry>().FirstOrDefault(); if (entry.Text is not null) { if (entry.Text.ToCharArray()[4] != '-') { creditCardEntries.Add(Regex.Replace(entry.Text, @"(\w{4})(\w{4})(\w{4})(\w{4})", @"$1-$2-$3-$4")); } else { creditCardEntries.Add(entry.Text); } } } var bindingContext = (AgentAddOrEditPageViewModel)BindingContext; if (bindingContext.AgentCreditCardNumbers is not null) { bindingContext.AgentCreditCardNumbers.Clear(); } bindingContext.AgentCreditCardNumbers = creditCardEntries; if (await bindingContext.AddToDatabase()) { CloseViewAfterInsertion(); }
优化建议
1. 用ICommand替代视图事件处理
.NET MAUI内置支持Command和AsyncCommand,将按钮点击逻辑完全移到ViewModel中,避免视图直接操作ViewModel:
- 在
AgentDetailsPageViewModel中定义删除命令:
public ICommand DeleteCommand { get; } public bool IsDeleted { get; set; } // 用于绑定控制UI可见性 public AgentDetailsPageViewModel() { DeleteCommand = new AsyncCommand(ExecuteDelete); } private async Task ExecuteDelete() { if (!answer) return; await DeleteFromDatabase(); IsDeleted = true; // 发送消息通知父ViewModel更新 MessagingCenter.Send(this, "AgentDeleted"); }
- 视图中绑定命令,移除事件处理代码:
<Button Text="删除" Command="{Binding DeleteCommand}" /> <Border IsVisible="{Binding IsDeleted, Converter={StaticResource InverseBooleanConverter}}"> <!-- 待删除的详情控件 --> </Border>
- 父ViewModel的布局调整逻辑,通过绑定属性实现,比如新增
AgentsGridColumnSpan、DetailsPanelVisible属性,视图直接绑定这些属性即可。
2. 动态控件通过数据绑定实现
针对动态添加信用卡输入框的场景,在ViewModel中维护可观察集合,每个元素对应一个输入项的ViewModel,自动同步数据:
- 定义信用卡项ViewModel:
public class CreditCardItemViewModel : ObservableObject { private string _cardNumber; public string CardNumber { get => _cardNumber; set { // 自动格式化卡号 var formatted = string.IsNullOrEmpty(value) ? value : Regex.Replace(value, @"(\w{4})(\w{4})(\w{4})(\w{4})", @"$1-$2-$3-$4"); SetProperty(ref _cardNumber, formatted); } } }
- 主ViewModel中维护集合并提供添加命令:
public ObservableCollection<CreditCardItemViewModel> CreditCards { get; } = new(); public ICommand AddCreditCardCommand { get; } public ICommand SaveCommand { get; } public AgentAddOrEditPageViewModel() { AddCreditCardCommand = new Command(() => CreditCards.Add(new CreditCardItemViewModel())); SaveCommand = new AsyncCommand(ExecuteSave); } private async Task ExecuteSave() { var cardNumbers = CreditCards.Select(x => x.CardNumber).Where(x => !string.IsNullOrEmpty(x)).ToList(); if (await AddToDatabase(cardNumbers)) { MessagingCenter.Send(this, "CloseAfterInsert"); } }
- 视图用
CollectionView动态生成输入框:
<CollectionView ItemsSource="{Binding CreditCards}"> <CollectionView.ItemTemplate> <DataTemplate> <Entry Text="{Binding CardNumber, Mode=TwoWay}" Placeholder="输入信用卡号" /> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <Button Text="添加信用卡" Command="{Binding AddCreditCardCommand}" /> <Button Text="保存" Command="{Binding SaveCommand}" />
3. 跨ViewModel通信用消息传递
避免直接获取父ViewModel实例,用.NET MAUI内置的MessagingCenter(或CommunityToolkit.Mvvm的IMessenger)传递消息:
- 父ViewModel注册消息监听:
public AgentsPageViewModel() { MessagingCenter.Subscribe<AgentDetailsPageViewModel>(this, "AgentDeleted", (sender) => { Populate(); DetailsPanelVisible = false; AgentsGridColumnSpan = 2; }); }
- 视图监听关闭消息:
public AgentAddOrEditPage() { InitializeComponent(); MessagingCenter.Subscribe<AgentAddOrEditPageViewModel>(this, "CloseAfterInsert", (sender) => { CloseViewAfterInsertion(); }); }
4. 明确视图与ViewModel的职责边界
- 视图仅负责数据绑定和UI布局,不处理业务逻辑、不直接操作其他控件的状态
- ViewModel负责所有业务逻辑、状态管理、数据处理,通过绑定属性将状态同步到视图
- 禁止在视图中遍历UI控件获取数据,禁止直接获取其他ViewModel实例
内容的提问来源于stack exchange,提问作者Fardin Farnezhad
相关产品推荐
相关产品推荐

