1

我正在使用 EF 返回带有一对多标志的进程列表。标志是唯一的,它们可能会根据需要增加或减少。数据结构大致翻译为:

public enum FlagTypes
{
    OnlyOnWeekends,
    OnlyOnHolidays
}

public class Process
{
    public DateTime Date { get; set; }
    public String Description { get; set; }
    public Dictionary<FlagTypes, Flag> Flags { get; set; }
}

public class Flag
{
    public FlagTypes Type { get; set; }
    public bool Enabled { get; set; }
}

我想在 DataGridView 中显示它,如下所示:

Date | Description | OnlyOnWeekends | OnlyOnHolidays [|... more flags as needed]

..同时也使它可编辑。

我能够解决 DataGridView 的限制,以使用自定义列和单元格显示表格

public class EnumerationColumn : DataGridViewColumn
{
    public FlagTypes EnumerationType { get; set; }

    public EnumerationColumn(FlagTypes enumerationType)
        : base(new EnumerationCell())
    {
        EnumerationType = enumerationType;
    }
    
    public override DataGridViewCell CellTemplate
    {
        get
        {
            return base.CellTemplate;
        }
        set
        {
            // Ensure that the cell used for the template is a EnumerationCell.
            if (value != null &&
                !value.GetType().IsAssignableFrom(typeof(EnumerationCell)))
            {
                throw new InvalidCastException("Must be a EnumerationCell");
            }
            base.CellTemplate = value;
        }
    }
    
    public class EnumerationCell : DataGridViewCheckBoxCell
    {
        private EnumerationColumn Parent
        {
            get
            {
                var parent = base.OwningColumn as EnumerationColumn;
                if(parent == null)
                {
                    throw new NullReferenceException("EnumerationCell must belong to a EnumerationColumn");
                }
                return parent;
            }
        }
        
        private Dictionary<FlagTypes, Flag> GetFlags(int rowIndex)
        {
            var flags = base.GetValue(rowIndex) as Dictionary<FlagTypes, Flag>;
            return flags ?? new Dictionary<FlagTypes, Flag>();
        }
        
        protected override object GetValue(int rowIndex)
        {
            var flags = GetFlags(rowIndex);
            if (flags.ContainsKey(Parent.EnumerationType))
            {
                return flags[Parent.EnumerationType].Enabled;
            }
            return false;
        }
    }
}

并创建列

grid.AutoGenerateColumns = false;
grid.DataSource = processes; // List<Process>

var dateCol = new CalendarWidgetColumn();
dateCol.DataPropertyName = "Date";
dateCol.HeaderText = "Date";

var descCol = new DataGridViewTextBoxColumn();
descCol.DataPropertyName = "Description";
descCol.HeaderText = "Description";

grid.Columns.Add(dateCol);
grid.Columns.Add(descCol);
        
foreach(string name in Enum.GetNames(typeof(FlagTypes)))
{
    FlagTypes flag;
    if(FlagTypes.TryParse(name, out flag))
    {
        var enumCol = new EnumerationColumn(flag);
        enumCol.DataPropertyName = "Flags";
        enumCol.HeaderText = String.Format("{0}?", name);
        grid.Columns.Add(enumCol);
    }
}

我无法弄清楚如何拦截调用以保存到 DataSource,因此它抛出一个异常,试图将 bool(复选框值)设置为 Dictionary(Flags DataProperty)。我看过 CellValuePushed 事件,但事后会触发。有任何想法吗?

或者也许是一种更简单的方法来解决这一切?

(最终我想用 BindingList 包装进程列表,这样我也可以直接从 DataGridView 创建新行)


解决方案(如下@Erez Robinson 所建议)

第 1 步:修改 DataStructure 以允许轻松访问底层字典

public enum FlagType
{
    OnlyOnWeekends,
    OnlyOnHolidays
}

public class Process
{
    public DateTime Date { get; set; }
    public String Description { get; set; }
    public Dictionary<FlagType, Flag> Flags { get; set; }

    public bool this[FlagType flagType]
    {
        get
        {
            if(!Flags.ContainsKey(flagType))
            {
                Flags.Add(flagType, new Flag(flagType, false));
            }
            return Flags[flagType].Enabled;
        }
        set
        {
            Flags[flagType].Enabled = value;
        }
    }
}

public class Flag
{
    public FlagType Type { get; set; }
    public bool Enabled { get; set; }

    public Flag(FlagType flagType, bool enabled)
    {
        Type = flagType;
        Enabled = enabled;
    }
}

第 2 步:创建一个从 ITypedList 派生的容器

class ProcessCollection : List<Process>, ITypedList
{
    protected IProcessViewBuilder _viewBuilder;

    public ProcessCollection(IProcessViewBuilder viewBuilder)
    {
        _viewBuilder = viewBuilder;
    }

    #region ITypedList Members

    protected PropertyDescriptorCollection _props;

    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        if (_props == null)
        {
            _props = _viewBuilder.GetView();
        }
        return _props;
    }

    public string GetListName(PropertyDescriptor[] listAccessors)
    {
        return ""; // was used by 1.1 datagrid
    }

    #endregion
}

第 3 步:将动态属性创建移至 ViewBuilder

public interface IProcessViewBuilder
{
    PropertyDescriptorCollection GetView();
}

public class ProcessFlagView : IProcessViewBuilder
{
    public PropertyDescriptorCollection GetView()
    {
        List<PropertyDescriptor> props = new List<PropertyDescriptor>();
        
        props.Add(new DynamicPropertyDescriptor(
            typeof(Process),
            "Date",
            typeof(DateTime),
            delegate(object p)
            {
                return ((Process)p).Date;
            },
            delegate(object p, object newPropVal)
                {
                    ((Process)p).Date = (DateTime)newPropVal;
                }
        ));

        props.Add(new DynamicPropertyDescriptor(
            typeof(Process),
            "Description",
            typeof(string),
            delegate(object p)
            {
                return ((Process)p).Description;
            },
            delegate(object p, object newPropVal)
                {
                    ((Process) p).Description = (string) newPropVal;
                }
        ));

        foreach (string name in Enum.GetNames(typeof(FlagType)))
        {
            FlagType flag;
            if (FlagType.TryParse(name, out flag))
            {

                props.Add(new DynamicPropertyDescriptor(
                              typeof (Process),
                              name,
                              typeof (bool),
                              delegate(object p)
                                  {
                                      return ((Process) p)[flag];
                                  },
                              delegate(object p, object newPropVal)
                                  {
                                      ((Process) p)[flag] = (bool) newPropVal;
                                  }
                              ));
            }
        }

        PropertyDescriptor[] propArray = new PropertyDescriptor[props.Count];
        props.CopyTo(propArray);
        
        return new PropertyDescriptorCollection(propArray);
    }
}

第 4 步:将集合绑定到 DataGridView

ProcessCollection processes = new ProcessCollection(new ProcessFlagView());

// Add some dummy data ...
processes.Add( ... );

// If you want a custom DataGridViewColumn, bind it before you bind the DataSource
var dateCol = new CalendarColumn();
dateCol.DataPropertyName = "Date";
dateCol.HeaderText = "Date";
grid.Columns.Add(dateCol);

grid.DataSource = processes;
4

1 回答 1

1

您可以从 DataGridView 派生并使用new关键字隐藏 DataSource 属性。

但我认为你需要改变你的概念。
我不会碰datagridview。

将这些标志公开为单独的属性。将这些类添加到绑定列表。
您可以使用反射动态创建属性。

请阅读这篇文章,它将为您指明正确的方向:ITypedList

于 2012-05-31T18:19:37.827 回答