具有动态可编辑列的DataGrid [英] DataGrid with Dynamic Editable Columns

查看:58
本文介绍了具有动态可编辑列的DataGrid的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直试图在WPF MVVM项目中使用动态列制作一个可编辑的 DataGrid 。动态列将是同一类型,即:十进制



目的是收集商店的部门总数部门数量不确定。我试图在下面演示它。

  Day Dept1 Dept2 Dept3 ... TotalOfDepartments CashTotal CreditTotal 
============ ================================================== =======
1 100 200 50 350 50 300
2 75100 0 175 25150

因此,有许多商店的部门不确定,我的目标是收取月份



我想将部门,CashTotal& CreditTotal列可编辑。我尝试过几种方法:






  • 此方法有很多限制。首先,我依靠数据网格自动生成列。如果要在标题文本中添加空格之类的内容,则需要做更多的事情。其次,我指望部门名称是有效的属性名称,并且不要与每日收入类别中的其他属性相冲突。如果没有,那么我将需要做更多的事情。依此类推。


    I have been trying to make a an editable DataGrid with dynamic columns in a WPF MVVM project. The dynamic columns would be the same type, i.e: 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. 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:

    <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.

    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 . 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.

    EDIT 1: More info about scenario added.

    解决方案

    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.

    这篇关于具有动态可编辑列的DataGrid的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆