我正在使用 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;