EFを使用して、1対多のフラグを含むプロセスのリストを返しています。フラグは一意であり、要件に応じて増減する場合があります。データ構造は大まかに次のように変換されます。
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(チェックボックス値)をディクショナリ(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;