最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Dynamic CellTemplate in WPF DataGrid with dynamically bound column - Stack Overflow

programmeradmin1浏览0评论

This question has been asked in some form in some old threads, but having scoured them for hours and not found any satisfactory answers, I thought I would resurrect the question and see if I can get a fresh perspective.

For context - I have a DataGrid which is to display an indeterminate number of properties from an indeterminate object (sometimes of different types) which the view model will supply to it. My view model provides this object by wrapping it in a ElementWrapper class which at runtime will find all the properties of the object using element.GetType().GetProperties(). It will use these properties to construct a PropertyWrapper class like the below:

    public class PropertyWrapper(string name, Type type, object value) : ObservableObject
    {
        public string Name { get; } = name;
        public Type Type { get; } = type;
        public object Value { get; set; } = value;
    }

The ElementWrapper will compile these into a dictionary of Dictionary<string, PropertyWrapper> as shown below:

    public class ElementWrapper
    {
        public static string PropertyNotFound { get; } = "#N/A";
        public Element Element { get; }
        public Dictionary<string, PropertyWrapper> Properties { get; } = [];
    
        public ElementWrapper(Element element)
        {
            Element = element;
            var propInfo = element?.GetType().GetProperties();
            foreach (var p in propInfo)
            {
                object value = p.GetValue(element);
                var pWrap = new PropertyWrapper(p.Name, p.PropertyType, value, true);            
                Properties.Add(p.Name, pWrap);
            }
        }
    }

Now my DataGrid is going to display an ObservableCollection<ElementWrapper>. Each column will be constructed at run-time by the view model (not using AutoGenerateColumns because I need more control in the View Model - I only want properties selected by user to be displayed, as there may be 50+ properties). The view model keeps a list of properties that is exposed to the view to select which properties to display. Whenever one is added, the below method of the View Model is called, which adds a new DataGridColumn to an ObservableCollection<DataGridColumn> called DataColumns which is bound to the DataGrid in xaml using a behavior.

    private void AddColumn(PropertyInfo propInfo)
    {
        DataColumns.Add(new DataGridTextColumn()
        {
            Header = propInfo.Name,
            Binding = new PriorityBinding()
            {
                Bindings =
                {
                        new System.Windows.Data.Binding("Properties[propInfo.Name].Value"),
                        new System.Windows.Data.Binding("PropertyNotFound")) { Mode = BindingMode.OneTime },
                }
            }
        });
    }

As you can see, I'm using a PriorityBinding so that for elements which do not have the property, it will fallback to the "#N/A" string supplied by PropertyNotFound.

That was a lot of context to get to the real question ... now the real question is, when I add columns, if a property is a boolean I want to display a CheckBox type of column. Adding a DataGridCheckBoxColumn in similar fashion to the above works pretty readily OOTB, but for elements which do not have the boolean property, I want to display the fallback "#N/A" text instead of a CheckBox. That forces me to possibly use a TemplateColumn and swap the DataTemplate using a CellTemplateSelector, or create a custom class deriving from DataGridBoundColumn. The latter approach seemed the cleanest, but I have not been able to get it work. In the former approach, I have found it difficult to construct the DataTemplates in my Resources in such a way that I can set their bindings at runtime. Sadly the best I have been able to get work is in my view model I have a block of a xaml code string defining a DataTemplate that contains a Grid with a CheckBox and a TextBlock in it that have DataTriggers that toggle their visibility based on the return value of the binding to the target property.

This is working, but seems very much anti-MVVM. It is also a real pain in my code-behind to access the CheckBoxes when they are added to the DataGrid so that I can subscribe to their Checked and Unchecked events. This too I have gotten to work, but feel like I am having to trick the system. Is there a good way to handle this need?

My current XAML string which my View Model is using:

    string bindingPath = "Properties[propInfo.Name].Value"
    string cellTemplateXaml = $@"
        <DataTemplate xmlns=''
                      xmlns:x=''>
          <Grid>
        
            <CheckBox IsChecked='{{Binding {bindingPath}}}'>
                <CheckBox.Style>
                    <Style TargetType='CheckBox'>
                        <Style.Triggers>
                            <DataTrigger Binding='{{Binding {bindingPath}}}' Value='{{x:Null}}'>
                                <Setter Property='Visibility' Value='Collapsed'/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </CheckBox.Style>
            </CheckBox>
                  
            <TextBlock Text='{{Binding {nameof(ElementWrapper.PropertyNotFound)}, Mode=OneTime}}'>
                <TextBlock.Style>
                    <Style TargetType='TextBlock'>
                        <Style.Triggers>
                            <DataTrigger Binding='{{Binding {bindingPath}}}' Value='True'>
                                <Setter Property='Visibility' Value='Collapsed'/>
                            </DataTrigger>
                            <DataTrigger Binding='{{Binding {bindingPath}}}' Value='False'>
                                <Setter Property='Visibility' Value='Collapsed'/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
    
          </Grid>
        </DataTemplate>";
发布评论

评论列表(0)

  1. 暂无评论