使用WPF和MVVM创建动态行和列

时间:2017-04-21 11:36:27

标签: wpf xaml mvvm data-binding

注意:我正在使用 MVVM Light Toolkit MahApps.Metro

我已经检查了答案,但似乎没有任何一个与我的问题相关。

我有一个Grid,其列和标题应该动态创建。查看时未知列的数量和值,并且查看的行数未知。

行中的列,行和数据表示数据库表。所有数据都存在于ViewModel中。

我的ViewModel中有ObservableCollection<ServerRow> ServerRows;

服务器行对象是一个如下所示的模型:

    public class ServerRow : ObservableObject
    {
         private ObservableCollection<ServerColumn> _columns;

         public ObservableCollection<ServerColumn> Columns
         {
             get { return _columns; }
             set { Set(() => Columns, ref _columns, value); }
         }
     }

这是ServerColumn类:

    public class ServerColumn : ObservableObject
    {
         private string _name;
         private string _type;
         private string _value;

         public string Name
         {
             get { return _name; }
             set { Set(() => Name, ref _name, value); }
         }

         public string Type
         {
             get { return _type; }
             set { Set(() => Type, ref _type, value); }
         }

         public string Value
         {
             get { return _value; }
             set { Set(() => Value, ref _value, value); }
         }
}

想法是将DataGrid绑定到ObservableCollection<ServerRow> ServerRows;,然后生成列,具体取决于ServerRow对象ServerColumns,而Name又有Type(应该是列的标题),Value作为列数据的数据类型,<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding ServerRows}"/> 作为应在每行/列中表示的值。

我的XAML很简单(因为它不完整,当然 - 不工作)

ToString()

如何正确编写XAML以实现我想要做的事情?

这是结果,这是有道理的,因为Grid试图在单个列中显示对象集合并调用其 select u.ID,f.FRIEND_ID,fsong.ARTIST from user u inner join friend f on u.ID=f.USER_ID left join user fsong on fsong.ID= f.FRIENDID 方法。

enter image description here

3 个答案:

答案 0 :(得分:1)

我之前也遇到过这个问题。

如果你看看这里做了什么:

https://github.com/taori/WMPR/blob/0a81bc6a6a4c6fc36edc4cbc99f0cfa8a2b8871c/src/WMPR/WMPR.Client/ViewModels/Sections/ReportEvaluationViewModel.cs#L503

当底层结构实际上是DynamicGridCell类型时,您将可迭代集合提供为ObservableCollection<object>,它使用可在

找到的DynamicGridCellDescriptor

DynamicGridCell:

public class DynamicGridCell : DynamicObject, ICustomTypeDescriptor, IDictionary<string, object>
{
    private readonly Dictionary<string, object> _values = new Dictionary<string, object>();

    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return new AttributeCollection();
    }

    string ICustomTypeDescriptor.GetClassName()
    {
        return nameof(DynamicGridCell);
    }

    string ICustomTypeDescriptor.GetComponentName()
    {
        return null;
    }

    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return null;
    }

    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return null;
    }

    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return null;
    }

    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return null;
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return null;
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return null;
    }

    private PropertyDescriptor[] CreatePropertyDescriptors()
    {
        var result = new List<PropertyDescriptor>();
        foreach (var pair in _values)
        {
            result.Add(new DynamicGridCellDescriptor(pair.Key));
        }

        return result.ToArray();
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        var result = new PropertyDescriptorCollection(CreatePropertyDescriptors());
        return result;
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        var result = new PropertyDescriptorCollection(CreatePropertyDescriptors());
        return result;
    }

    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }

    public IEnumerator GetEnumerator()
    {
        return _values.GetEnumerator();
    }

    IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
    {
        return _values.GetEnumerator();
    }

    void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
    {
        _values.Add(item.Key, item.Value);
    }

    void ICollection<KeyValuePair<string, object>>.Clear()
    {
        _values.Clear();
    }

    bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
    {
        return _values.Contains(item);
    }

    void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
    {
    }

    bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
    {
        if (_values.ContainsKey(item.Key))
        {
            _values.Remove(item.Key);
            return true;
        }

        return false;
    }

    public int Count => _values.Count;

    bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;

    public bool ContainsKey(string key)
    {
        return _values.ContainsKey(key);
    }

    public void Add(string key, object value)
    {
        _values.Add(key, value);
    }

    bool IDictionary<string, object>.Remove(string key)
    {
        return _values.Remove(key);
    }

    public bool TryGetValue(string key, out object value)
    {
        return _values.TryGetValue(key, out value);
    }

    public object this[string key]
    {
        get { return _values[key]; }
        set
        {
            if (_values.ContainsKey(key))
            {
                _values[key] = value;
            }
            else
            {
                _values.Add(key, value);
            }
        }
    }

    public ICollection<string> Keys => _values.Keys;
    public ICollection<object> Values => _values.Values;
}

DynamicGridCellDescriptor

public class DynamicGridCellDescriptor : PropertyDescriptor
    {
        public DynamicGridCellDescriptor(string name) : base(name, null)
        {
        }

        public override bool CanResetValue(object component)
        {
            return true;
        }

        public override object GetValue(object component)
        {
            return ((DynamicGridCell) component)[Name];
        }

        public override void ResetValue(object component)
        {
            ((DynamicGridCell) component)[Name] = null;
        }

        public override void SetValue(object component, object value)
        {
            ((DynamicGridCell) component)[Name] = value;
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        public override Type ComponentType => typeof(DynamicGridCell);
        public override bool IsReadOnly => false;
        public override Type PropertyType => typeof(object);
    }

确保绑定的属性无论如何都是ObservableCollection<object>类型 - 否则对我来说自动生成网格列不起作用。

答案 1 :(得分:0)

你有一些逻辑问题。

当您设置ItemsSource的{​​{1}}时,绑定的集合将用于创建行,如果您不进行更改,则属性DataGrid将设置为{{ 1}}。在这种情况下,AutoGenerateColumns将为绑定集合中的每个属性生成一列,这正是您的示例中发生的情况。 您将实例与属性&#39; Columns&#39;绑定在一起。并获取一个名为&#39; Columns&#39;的DataGrid列。并且您获得的行数与此属性中的条目一样多,显示为&#39;(收集)&#39;因为true继承自DataGrid

您可以将ServerColumn设置为ObservableObject,并且必须自己创建列;通常在xaml =&gt;硬编码。

如果你真的想动态生成列,你必须编写自己的逻辑来创建和绑定列。如果你想让它变得通用的话,我曾经做过一次这样的事情并且很痛苦。 如果你想要一个AutoGenerateColumns动态列,用户可以更改值,那么它就比只读一个更棘手。

一种方法可能是列名为false,另一种DataGrid存储每行的ViewModel。

答案 2 :(得分:0)

如果行列确实需要是动态的,那么最好的选择是使用两个嵌套的ItemControl,外部的一个代表行,内部的一个列:

<ItemsControl ItemsSource="{Binding Rows}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ItemsControl ItemsSource="{Binding Columns}" ItemTemplateSelector="{StaticResource ColumnTemplateSelector}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

这允许您通过定义可能看起来有点类似于以下内容的模板选择器来显示不同类型的列:

public class ColumnTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var column = item as ServerColumn;
        switch (column.Type)
        {
            case "Text":
                return TextTemplate;
            case "Number":
                return NumberTemplate;
            case "Image":
                return ImageTemplate;
        }
        // Fallback
        return TextTemplate;
    }

    public DataTemplate TextTemplate { get; set; }
    public DataTemplate NumberTemplate { get; set; }
    public DataTemplate ImageTemplate { get; set; }
}

...基于每个列的类型,将引用不同的模板(所有这些模板显然需要在某处定义并引用为StaticResource。(这甚至可以轻松创建可更改的(不读取 - 只有)网格。)

请注意,您可以使用ItemsControl或从ListView派生的任何其他控件,而不是外部ItemsControl。例如,如果您需要自动滚动,则使用ListView可能很有用。

相关问题