You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:35:25