WinUI 3:继承基页时DataGrid始终为空的技术问题咨询
问题分析与优化方案
问题描述
需求是实现包含Windows Community Toolkit DataGrid及通用后台功能的基页,并创建继承该基页的子页面。实际遇到的问题:继承后的页面中myDataGrid控件始终为null,直接实例化基页时DataGrid可正常初始化,导致MasterDataEditPage后台代码无法正常操作该控件。
当前实现的错误点
- 控件初始化时机错误:在
MasterDataEditPage的SetViewModel方法中直接访问myDataGrid,但此时页面可视化树尚未完成初始化。继承页面的构造函数执行顺序为BasePage→MasterDataEditPage→ClerkMasterDataPage,InitializeComponent仅加载XAML结构,控件真正实例化并加入可视化树要等到页面Loaded事件触发后,提前访问必然返回null。 - ViewModel与UI强耦合:
MasterDataEditViewModel直接持有DataGrid实例并在内部创建列,违反MVVM分离原则——ViewModel不应依赖具体UI控件,这会导致ViewModel无法独立测试,也限制了UI的灵活性和可扩展性。 - 基类设计未合理利用:
BasePage定义的SetViewModel抽象方法被MasterDataEditPage空实现,子类自行定义泛型SetViewModel<T>,导致基类设计失去意义,代码结构混乱。
优化方案
方案1:修正控件初始化时机(快速修复)
将操作myDataGrid的逻辑移到页面Loaded事件中,确保控件已完成初始化:
public partial class MasterDataEditPage : BasePage { private IMasterDataEditViewModel _viewModel; public MasterDataEditPage() : base() { InitializeComponent(); Loaded += MasterDataEditPage_Loaded; } private void MasterDataEditPage_Loaded(object sender, RoutedEventArgs e) { if (_viewModel != null) { _viewModel.SetDataGrid(myDataGrid); _viewModel.InitDataGridColumns(); } } public void SetViewModel<T>(T viewModel) where T : IMasterDataEditViewModel { _viewModel = viewModel; DataContext = viewModel; } public override void SetViewModel() { // 可在此整合基类统一初始化逻辑 } }
方案2:解耦ViewModel与UI控件(推荐,符合MVVM)
让ViewModel只提供列配置数据,UI层负责生成列,彻底分离视图与业务逻辑:
步骤1:修改ViewModel,移除UI依赖
public partial class MasterDataEditViewModel<ModelType, DatabaseType> : BaseViewModel, INavigationAware, IMasterDataEditViewModel { // 暴露列配置,移除DataGrid相关字段和方法 public List<DataGridSelectionType> ColumnConfigs { get; } public MasterDataEditViewModel(string name, Symbol symbol, List<DataGridSelectionType> dataGridSelectionTypes, IMasterDataService<DatabaseType> masterDataRepository, IMessageService messageService, IMapper mapper, string controllerName) : base(name, symbol) { ColumnConfigs = dataGridSelectionTypes; // 保留原有业务逻辑,移除InitDataGridColumns方法 } }
步骤2:在View层生成DataGrid列
public partial class MasterDataEditPage : BasePage { public MasterDataEditPage() : base() { InitializeComponent(); Loaded += MasterDataEditPage_Loaded; } private void MasterDataEditPage_Loaded(object sender, RoutedEventArgs e) { if (DataContext is IMasterDataEditViewModel viewModel) { InitDataGridColumns(viewModel.ColumnConfigs); } } private void InitDataGridColumns(List<DataGridSelectionType> columnConfigs) { myDataGrid.Columns.Clear(); foreach (var item in columnConfigs) { switch (item.DataGridType) { case Enums.DataGridType.DataGridText: var textColumn = new DataGridTextColumn { Header = item.HeaderName, Binding = new Microsoft.UI.Xaml.Data.Binding { Path = new Microsoft.UI.Xaml.PropertyPath(item.BindingName) } }; myDataGrid.Columns.Add(textColumn); break; case Enums.DataGridType.DataGridCheckBox: var checkboxColumn = new DataGridCheckBoxColumn { Header = item.HeaderName, Binding = new Microsoft.UI.Xaml.Data.Binding { Path = new Microsoft.UI.Xaml.PropertyPath(item.BindingName) } }; myDataGrid.Columns.Add(checkboxColumn); break; // 其他列类型处理逻辑 } } } public void SetViewModel<T>(T viewModel) where T : IMasterDataEditViewModel { DataContext = viewModel; } public override void SetViewModel() { // 按需实现基类逻辑 } }
步骤3:优化基类设计
将基页改为泛型,统一ViewModel初始化逻辑:
public abstract partial class BasePage<TViewModel> : Page where TViewModel : BaseViewModel { public TViewModel ViewModel { get; protected set; } public BasePage() : base() { InitializeComponent(); Loaded += BasePage_Loaded; } private void BasePage_Loaded(object sender, RoutedEventArgs e) { OnPageLoaded(); } protected abstract void OnPageLoaded(); public void SetViewModel(TViewModel viewModel) { ViewModel = viewModel; DataContext = viewModel; } }
子类继承时指定ViewModel类型:
public partial class MasterDataEditPage : BasePage<IMasterDataEditViewModel> { public MasterDataEditPage() : base() { InitializeComponent(); } protected override void OnPageLoaded() { if (ViewModel != null) { InitDataGridColumns(ViewModel.ColumnConfigs); } } // InitDataGridColumns方法同上 }
方案3:用附加属性实现声明式列生成(进阶)
创建附加属性,让DataGrid自动绑定ViewModel的列配置生成列,完全无需后台代码:
public static class DataGridColumnGenerator { public static readonly DependencyProperty ColumnConfigsProperty = DependencyProperty.RegisterAttached("ColumnConfigs", typeof(List<DataGridSelectionType>), typeof(DataGridColumnGenerator), new PropertyMetadata(null, OnColumnConfigsChanged)); public static void SetColumnConfigs(DependencyObject obj, List<DataGridSelectionType> value) { obj.SetValue(ColumnConfigsProperty, value); } public static List<DataGridSelectionType> GetColumnConfigs(DependencyObject obj) { return (List<DataGridSelectionType>)obj.GetValue(ColumnConfigsProperty); } private static void OnColumnConfigsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is DataGrid dataGrid && e.NewValue is List<DataGridSelectionType> configs) { dataGrid.Columns.Clear(); foreach (var item in configs) { // 列生成逻辑同方案2 } } } }
XAML中绑定附加属性:
<controls:DataGrid x:Name="myDataGrid" AutoGenerateColumns="False" Grid.Row="1" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" GridLinesVisibility="Horizontal" ItemsSource="{Binding Source, Mode=OneWay}" local:DataGridColumnGenerator.ColumnConfigs="{Binding ColumnConfigs}"> <controls:DataGrid.Resources> <SolidColorBrush x:Key="DataGridColumnHeaderBackgroundColor" Color="Transparent" /> </controls:DataGrid.Resources> </controls:DataGrid>
内容的提问来源于stack exchange,提问作者Mirofire
相关产品推荐
相关产品推荐

