XamDataGrid では IsUndoEnabled プロパティを True に設定することによって Undo(元に戻す)/ Redo(やり直し) 操作を有効にすることができます。
履歴に保存する最大操作数は UndoLimit プロパティから指定することができます。
<igWPF:XamDataGrid ..... IsUndoEnabled="True" UndoLimit="10"> ..... </igWPF:XamDataGrid>
上記設定により、XamDataGrid のセルの値の変更や新規行追加といった編集操作に加え、列の並べ替えやグループ化、行の展開などのユーザー操作を「元に戻す/やり直す」ことが可能になります。
これらのユーザー操作に加えて行の削除操作を「元に戻す/やり直す」には、 UndeleteRecordsStrategy クラスを実装します。UndeleteRecordsStrategy クラスでは CanUndelete および Undelete メソッドをオーバーライドする必要があります。特に Undelete メソッドでは、渡されたRecordInfoインスタンスから実際のデータ項目にマップしたディクショナリを返す点に注意してください。
以下は、XamDataGrid で使用するデータが ObsevableCollection の場合の UndeleteRecordsStrategy クラスの実装例です。
public abstract class UnboundCellRecordUndeleteStrategy : UndeleteRecordsStrategy
{
private List<Field> _unboundFields;
private Dictionary<object, IList<object>> _unboundValues;
private Dictionary<object, object> _newToOldMapping;
protected UnboundCellRecordUndeleteStrategy(IList<Record> records)
{
if (records == null)
throw new ArgumentNullException("records");
if (records.Count > 0)
{
List<Field> unboundFields = new List<Field>();
FieldLayout fl = records[0].FieldLayout;
foreach (Field field in fl.Fields)
{
if (field.BindingType == BindingType.UseNameBinding || field.IsPlaceholderForTreeView)
continue;
if (field.AlternateBinding != null)
{
BindingMode? bindingMode = null;
if (field.AlternateBinding is Binding)
bindingMode = ((Binding)field.AlternateBinding).Mode;
else if (field.AlternateBinding is MultiBinding)
bindingMode = ((MultiBinding)field.AlternateBinding).Mode;
if (bindingMode != null)
{
switch (bindingMode)
{
case BindingMode.OneWay:
case BindingMode.Default:
case BindingMode.OneTime:
continue;
}
}
}
unboundFields.Add(field);
}
int unboundFieldCount = unboundFields.Count;
if (unboundFieldCount > 0)
{
_unboundFields = unboundFields;
_unboundValues = new Dictionary<object, IList<object>>();
foreach (Record record in records)
{
DataRecord dataRecord = record as DataRecord;
if (null == dataRecord)
continue;
object dataItem = dataRecord.DataItem;
if (dataItem == null)
continue;
object[] unboundValues = new object[unboundFieldCount];
for (int i = 0; i < unboundFieldCount; i++)
unboundValues[i] = dataRecord.Cells[unboundFields[i]].Value;
_unboundValues[dataItem] = unboundValues;
}
}
}
}
public override IDictionary<RecordInfo, object> Undelete(IList<RecordInfo> oldRecords)
{
IDictionary<RecordInfo, object> oldToNewMapping = this.UndeleteOverride(oldRecords);
if (null != oldToNewMapping && oldToNewMapping.Count > 0)
{
_newToOldMapping = new Dictionary<object, object>();
foreach (KeyValuePair<RecordInfo, object> pair in oldToNewMapping)
_newToOldMapping[pair.Value] = pair.Key.DataItem;
}
return oldToNewMapping;
}
public override void ProcessUndeletedRecords(IList<DataRecord> recordsCreated)
{
if (_newToOldMapping == null || _newToOldMapping.Count == 0)
return;
// we're only using this to reset the unbound cell values
if (_unboundFields == null || _unboundFields.Count == 0)
return;
foreach (DataRecord newRecord in recordsCreated)
{
object oldDataItem;
if (!_newToOldMapping.TryGetValue(newRecord.DataItem, out oldDataItem))
continue;
IList<object> unboundValues;
if (!_unboundValues.TryGetValue(oldDataItem, out unboundValues))
continue;
for (int i = 0; i < unboundValues.Count; i++)
{
Field fld = _unboundFields[i];
Debug.Assert(fld.Owner == newRecord.FieldLayout, "Record is associated with a different field layout!");
// if somehow the field was removed then skip it
if (fld.Owner == newRecord.FieldLayout && fld.Index >= 0)
newRecord.Cells[fld].Value = unboundValues[i];
}
}
}
protected abstract IDictionary<RecordInfo, object> UndeleteOverride(IList<RecordInfo> oldRecords);
}
public class ListUndeleteStrategy : UnboundCellRecordUndeleteStrategy
{
public ListUndeleteStrategy(IList<Record> records)
: base(records)
{
// since this class is set up to reuse the same old items, we can only
// support a subset of list types. also since we want to ensure the
// grid knows when the records are undeleted we're limiting this to
// known classes that send collection change notifications as well.
foreach (Record record in records)
{
IEnumerable dataSource = record.RecordManager.SourceItems;
if (dataSource is DataView || dataSource is DataViewManager)
throw new InvalidOperationException("This class cannot be used with datatables/dataviews since it just reinserts the same item back into the collection.");
if (dataSource is IBindingList)
continue;
if (dataSource is IList && dataSource is INotifyCollectionChanged)
continue;
throw new ArgumentException("The ListUndeleteStrategy is only supported with IBindingList and INotifyCollectionChanged sources");
}
}
public override bool CanUndelete(IList<UndeleteRecordsStrategy.RecordInfo> oldRecords)
{
return true;
}
protected override IDictionary<RecordInfo, object> UndeleteOverride(IList<RecordInfo> oldRecords)
{
Dictionary<RecordInfo, object> oldToNewMapping = new Dictionary<RecordInfo, object>();
RecordInfo[] records = oldRecords.ToArray();
Comparison<RecordInfo> comparison = delegate (RecordInfo item1, RecordInfo item2)
{
return item1.DataItemIndex.CompareTo(item2.DataItemIndex);
};
// sort by the original index to ensure we get them in the original order
Utilities.SortMergeGeneric(records, Utilities.CreateComparer(comparison));
foreach (RecordInfo record in records)
{
RecordManager rm = record.RecordManager;
IList list = rm.SourceItems as IList;
if (list == null)
continue;
int newIndex = Math.Min(list.Count, record.DataItemIndex);
object newDataItem = record.DataItem;
list.Insert(newIndex, newDataItem);
// since we're reusing the same object the old and new will be the same
oldToNewMapping[record] = newDataItem;
}
return oldToNewMapping;
}
}
以上のようにして実装した UndeleteRecordsStrategy の派生クラスである ListUndeleteStrategy のインスタンスを、XamDataGrid の RecordsDeleting イベントで以下のように引数の UndeleteStrategy プロパティに対して割り当てます。
private void xamDataGrid1_RecordsDeleting(object sender, Infragistics.Windows.DataPresenter.Events.RecordsDeletingEventArgs e)
{
e.UndeleteStrategy = new ListUndeleteStrategy(e.Records);
}
以上によって行の削除を含めたユーザー操作の「元に戻す/やり直し」が可能になりました。
Undo/Redo 操作実行用に Button を二つ用意し、Command プロパティをバインドします。
(ここではコマンドを使用してプログラムから Undo/Redo を実行していますが、通常のキーボードショートカットである Ctrl + Z および Ctrl + Y によっても同じ動作が得られます。)
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Stretch">
<Button
Command="{x:Static igWPF:DataPresenterCommands.Undo}"
CommandTarget="{Binding ElementName=xamDataGrid1}">
<StackPanel>
<Path
HorizontalAlignment="Center"
Data="{StaticResource UndoIcon}"
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
<TextBlock Text="元に戻す"/>
</StackPanel>
</Button>
<Button
Command="{x:Static igWPF:DataPresenterCommands.Redo}"
CommandTarget="{Binding ElementName=xamDataGrid1}">
<StackPanel>
<Path
HorizontalAlignment="Center"
Data="{StaticResource RedoIcon}"
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
<TextBlock Text="やり直し"/>
</StackPanel>
</Button>
</StackPanel>
