简体   繁体   English

具有动态可编辑列的DataGrid

[英]DataGrid with Dynamic Editable Columns

I have been trying to make a an editable DataGrid with dynamic columns in a WPF MVVM project. 我一直在尝试在WPF MVVM项目中使用动态列创建一个可编辑的DataGrid The dynamic columns would be the same type, ie: decimal . 动态列的类型相同,即: decimal

The aim is to collect department totals of shops with indefinite number of departments. 目的是收集具有无限数量部门的商店的部门总数。 I tried to demonstrate it below. 我试着在下面演示它。

Day Dept1   Dept2   Dept3... TotalOfDepartments CashTotal CreditTotal
=====================================================================
1    100     200     50            350             50       300
2     75     100      0            175             25       150  

So, there are numerous shops with indefinite departments and my goal is to collect month 因此,有许多商店有无限期的部门,我的目标是收集月份

I want to make Department, CashTotal & CreditTotal Columns editable. 我想让Department,CashTotal和CreditTotal Columns可以编辑。 I've had several approaches that I tried like: 我尝试了几种方法:

This is my last try from the last approach. 这是我最后一次尝试的最后一次尝试。 As follows: 如下:

Model: 模型:

 public class DailyRevenues
    {
        public int ShopId { get; set; }
        public int Day { get; set; }
        public ObservableCollection<Department> DepartmentList { get; set; }

        public DailyRevenues()
        {
            this.DepartmentList = new ObservableCollection<Department>();
        }
    }

    public class Department
    {
        public string Name { get; set; }

        private decimal total;
        public decimal Total
        {
            get { return total; }
            set { total = value; }
        }
    }

ViewModel: 视图模型:

public class DataItemViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public DataItemViewModel()
        {
            this.MonthlyRevenues = new ObservableCollection<DailyRevenues>();

            var d1 = new DailyRevenues() { ShopId = 1, Day = 1 };
            d1.DepartmentList.Add(new Department() { Name = "Deapartment1", Total = 100 });
            d1.DepartmentList.Add(new Department() { Name = "Deapartment2", Total = 200 });

            var d2 = new DailyRevenues() { ShopId = 1, Day = 2 };
            d2.DepartmentList.Add(new Department() { Name = "Deapartment1", Total = 75 });
            d2.DepartmentList.Add(new Department() { Name = "Deapartment2", Total = 150 });
            d2.DepartmentList.Add(new Department() { Name = "Deapartment3", Total = 100 });

            this.MonthlyRevenues.Add(d1);
            this.MonthlyRevenues.Add(d2);
        }

        private ObservableCollection<DailyRevenues> monthlyRevenues;
        public ObservableCollection<DailyRevenues> MonthlyRevenues
        {
            get { return monthlyRevenues; }
            set
            {
                if (monthlyRevenues != value)
                {
                    monthlyRevenues = value;
                    OnPropertyChanged(nameof(MonthlyRevenues));
                }
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

And the XAML: 和XAML:

<DataGrid ItemsSource="{Binding MonthlyRevenues}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Day" Binding="{Binding Path=Day}" />
            <DataGridTextColumn Header="{Binding Path=MonthlyRevenues[0].DepartmentList[0].Name}" Binding="{Binding Path=DepartmentList[0].Total, Mode=TwoWay}" />
            <DataGridTextColumn Header="{Binding Path=DepartmentList[1].Name}" Binding="{Binding Path=DepartmentList[1].Total, Mode=TwoWay}" />
            <DataGridTextColumn Header="Department Total"/>
            <DataGridTextColumn Header="Cash Total" />
            <DataGridTextColumn Header="Credit Total" />
        </DataGrid.Columns>
    </DataGrid>

Unfortunately, on this last try using the indexers on XAML does not help me on dynamic columns and I can not find a way to bind them any other way. 不幸的是,在最后一次尝试使用XAML上的索引器对动态列没有帮助,我找不到以任何其他方式绑定它们的方法。

More info: The datagrid (and the data demonstration) above belongs to shop1 and I want to collect monthly revenues of it`s departments on a window/user control . 更多信息:上面的datagrid(和数据演示)属于shop1,我想在窗口/用户控件上收集它的部门的月收入。 Each shop has the same count of departments throughout the month, but this does not mean that every department should have revenues each day, it can be zero. 每个商店在整个月内都有相同数量的部门,但这并不意味着每个部门每天都应该有收入,它可以是零。 The department may be closed for any day, so does not raise any revenue for the day. 该部门可能会在任何一天关闭,因此当天不会产生任何收入。 Shop2 might have entirely different departments for the same month, so I will not handle all shops in the same screen. Shop2可能在同一个月有完全不同的部门,所以我不会在同一个屏幕上处理所有商店。

EDIT 1: More info about scenario added. 编辑1:有关添加的场景的更多信息。

There are a number of different approaches you could take, each with pluses and minuses. 你可以采取许多不同的方法,每种方法都有优点和缺点。 Based on your more complete description of the problem, I have chosen the custom type descriptor approach. 根据您对问题的更完整描述,我选择了自定义类型描述符方法。

Here we add a custom type descriptor to the daily revenues class... 在这里,我们为每日收入类添加一个自定义类型描述符...

public class DailyRevenues : ICustomTypeDescriptor
{
    public int ShopId { get; set; }
    public int Day { get; set; }
    public ObservableCollection<Department> DepartmentList { get; set; }

    public DailyRevenues()
    {
        this.DepartmentList = new ObservableCollection<Department>();
    }
    public decimal TotalOfDepartments { get;  }
    public decimal CashTotal { get;  }
    public decimal CreditTotal { get; }

    public AttributeCollection GetAttributes()
    {
        return new AttributeCollection();
    }

    public string GetClassName()
    {
        return "DailyRevenues";
    }

    public string GetComponentName()
    {
        return "";
    }

    public TypeConverter GetConverter()
    {
        return null;
    }

    public EventDescriptor GetDefaultEvent()
    {
        return null;
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }

    public object GetEditor(Type editorBaseType)
    {
        return null;
    }

    public EventDescriptorCollection GetEvents()
    {
        return null;
    }

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

    public PropertyDescriptorCollection GetProperties()
    {
        PropertyDescriptorCollection pdc0 = TypeDescriptor.GetProperties(typeof(DailyRevenues));
        List<PropertyDescriptor> pdList = new List<PropertyDescriptor>();
        pdList.Add(pdc0["Day"]);
        for (int i = 0; i < DepartmentList.Count; ++i)
        {
            pdList.Add(new DailyRevenuesProperty(DepartmentList[i].Name, i));
        }
        pdList.Add(pdc0["TotalOfDepartments"]);
        pdList.Add(pdc0["CashTotal"]);
        pdList.Add(pdc0["CreditTotal"]);
        return new PropertyDescriptorCollection(pdList.ToArray());
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        return GetProperties();
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }
}

The custom type descriptor allows us to "flatten" the data structure. 自定义类型描述符允许我们“展平”数据结构。 As the number of departments change, the number of properties on the object changes. 随着部门数量的变化,对象上的属性数量也会发生变化。 This requires a custom property descriptor for the daily revenues class... 这需要每日收入等级的自定义属性描述符...

public class DailyRevenuesProperty : PropertyDescriptor
{
    int _index;
    public DailyRevenuesProperty(string name, int index)
        : base(name, new Attribute[0])
    {
        _index = index;
    }
    public override Type ComponentType
    {
        get
        {
            return typeof(DailyRevenues);
        }
    }

    public override bool IsReadOnly
    {
        get
        {
            return false;
        }
    }

    public override Type PropertyType
    {
        get
        {
            return typeof(decimal);
        }
    }

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

    public override object GetValue(object component)
    {
        DailyRevenues dr = component as DailyRevenues;
        if(dr != null && _index >= 0 && _index < dr.DepartmentList.Count)
        {
            return dr.DepartmentList[_index].Total;
        }
        else
        {
            return (decimal)0;
        }
    }

    public override void ResetValue(object component)
    {
    }

    public override void SetValue(object component, object value)
    {
        DailyRevenues dr = component as DailyRevenues;
        if (dr != null && _index >= 0 && _index < dr.DepartmentList.Count && value is decimal)
        {
            dr.DepartmentList[_index].Total = (decimal)value;
        }
    }

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

Now we need a typed list. 现在我们需要一个打字列表。 This replaces the observable collection. 这取代了可观察的集合。

public class MonthlyRevenues : ObservableCollection<DailyRevenues>, ITypedList
{
    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        if(Count > 0)
        {
            return TypeDescriptor.GetProperties(this[0]);
        }
        else
        {
            return TypeDescriptor.GetProperties(typeof(DailyRevenues));
        }
    }

    public string GetListName(PropertyDescriptor[] listAccessors)
    {
        return "Monthly Revenues";
    }
}

When auto generating columns the data grid checks to see if the items collection is a typed list. 自动生成列时,数据网格会检查项集合是否为类型列表。 If it is, the data grid queries for the properties on the typed list. 如果是,则数据网格查询键入列表上的属性。

Finally to wrap things up, here is the data grid... 最后要总结一下,这里是数据网格......

    <DataGrid ItemsSource="{Binding MonthlyRevenues}" AutoGenerateColumns="true" />

And this is the resulting grid... 这就是结果网格......

在此输入图像描述

There are number of limitations to this approach. 这种方法有许多限制。 First I am relying on the data grid to autogenerate the columns. 首先,我依靠数据网格自动生成列。 If I want to add things like spaces to the header text, I will need to do some more stuff. 如果我想在标题文本中添加空格之类的东西,我需要做更多的事情。 Second I am counting on the department names to be valid property names and to not collide with other properties in the daily revenues class. 其次,我指望部门名称是有效的属性名称,并且不会与日常收入类别中的其他属性发生冲突。 If not, then I will need to do some more stuff. 如果没有,那么我将需要做更多的事情。 And so on. 等等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM