Infragistics Undo/Redo Framework はエンドユーザーによる操作の履歴を保存し、元に戻す/やり直し機能を提供します。フレームワークの機能を実装することによって XamDataGrid におけるセルの編集、新規行追加、行の削除などの一般的なCRUD操作を元に戻すことができます。

手順1. 以下の NuGet パッケージ参照を追加します。

  • Infragistics.WPF.Undo
  • Infragistics.WPF.DataGrids

手順2. 元に戻す/やり直し操作をサポートするデータモデルの RowData クラスを作成します。RowData クラスでは INotifyPropertyChanged を実装し、プロパティの変更ごとに UndoUnit インスタンスが作成されるようにします。

public class RowData : INotifyPropertyChanged
{
	private object _owner;
	internal object Owner
	{
		get { return _owner; }
		set { _owner = value; }
	}

	private int m_id;
	public int Id
	{
		get { return m_id; }
		set
		{
			this.SetField(ref m_id, value, "Id");
		}
	}

	private String m_test1;
	public String Test1
	{
		get { return m_test1; }
		set
		{
			this.SetField(ref m_test1, value, "Test1");
		}
	}

	private String m_test2;
	public String Test2
	{
		get { return m_test2; }
		set
		{
			this.SetField(ref m_test2, value, "Test2");
		}
	}

	protected bool SetField<T>(ref T member, T newValue, string propertyName)
	{
		if (EqualityComparer<T>.Default.Equals(member, newValue))
			return false;
		if (_owner != null)
			UndoManager.FromReference(_owner).AddPropertyChange(this, propertyName, member, newValue);
		member = newValue;
		this.NotifyPropertyChanged(propertyName);
		return true;
	}

	public event PropertyChangedEventHandler PropertyChanged;

	private void NotifyPropertyChanged(String info)
	{
		if (PropertyChanged != null)
		{
			PropertyChanged(this, new PropertyChangedEventArgs(info));
		}
	}
}

手順3. ObservableCollectionExtendedWithUndo クラスから派生するコレクションクラスRowDataCollection を作成します。このクラスでは、InsertItem および RemoveItem メソッドのオーバーライドを行います。コレクションは UndoManager インスタンスに関連付けられます。

public class RowDataCollection : ObservableCollectionExtendedWithUndo<RowData>
{
	public RowDataCollection(UndoManager undoManager)
		: base(undoManager)
	{
		undoManager.RegisterReference(this);
	}
	protected override void InsertItem(int index, RowData item)
	{
		item.Owner = this;
		base.InsertItem(index, item);
	}
	protected override void RemoveItem(int index)
	{
		RowData item = this[index];
		item.Owner = null;
		base.RemoveItem(index);
	}
}

手順4. MainViewModel クラスを作成します。

public class MainViewModel : INotifyPropertyChanged
{
	private UndoManager _undoManager;
	public UndoManager UndoManager
	{
		get { return _undoManager; }
	}

	private RowDataCollection _gridData;
	public RowDataCollection GridData
	{
		get { return _gridData; }
	}

	public MainViewModel()
	{
		_undoManager = new UndoManager();
		_undoManager.RegisterReference(this);
		_gridData = new RowDataCollection(_undoManager);
		// データの生成中は履歴で UndoUnits の記録を中断します
		UndoManager.Suspend();
		try
		{
			for (int i = 0; i < 10; i++)
			{
				_gridData.Add(new RowData { Id = i, Test1 = "TEXT" + (i % 3), Test2 = "Test" + i });
			}
		}
		finally
		{
			// 履歴で記録を再開します
			UndoManager.Resume();
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;

	private void NotifyPropertyChanged(String info)
	{
		if (PropertyChanged != null)
		{
			PropertyChanged(this, new PropertyChangedEventArgs(info));
		}
	}
}

手順5. MainViewModel を MainWindow の DataContext プロパティに設定します。

public MainWindow()
{
	InitializeComponent();

	this.DataContext = new MainViewModel();
}

手順6. 元に戻す/やり直し履歴項目を表示する XamMenu コントロールを追加して、コマンドをバインドします。

<ig:XamMenu Grid.Row="0">
	<ig:XamMenu.Resources>
		<DataTemplate x:Key="historyItemTemplate">
			<TextBlock Text="{Binding LongDescription}" />
		</DataTemplate>
		<DataTemplate x:Key="undoRedoMenuItem">
			<ig:XamMenuItem>
				<ig:Commanding.Command>
					<ig:UndoManagerCommandSource CommandType="UndoRedoHistoryItem"
							   ParameterBinding="{Binding}"
							   EventName="Click" />
				</ig:Commanding.Command>
			</ig:XamMenuItem>
		</DataTemplate>
	</ig:XamMenu.Resources>
	<ig:XamMenuItem Header="元に戻す"
		  IsEnabled="{Binding UndoManager.CanUndo}"
		  ItemsSource="{Binding UndoManager.UndoHistory}"
		  DefaultItemsContainer="{StaticResource undoRedoMenuItem}"
		  ItemTemplate="{StaticResource historyItemTemplate}">
		<ig:Commanding.Command>
			<ig:UndoManagerCommandSource EventName="SubmenuOpened"
						   CommandType="PreventMerge"
						   ParameterBinding="{Binding UndoManager}" />
		</ig:Commanding.Command>
	</ig:XamMenuItem>
	<ig:XamMenuItem Header="やり直し"
		  IsEnabled="{Binding UndoManager.CanRedo}"
		  ItemsSource="{Binding UndoManager.RedoHistory}"
		  DefaultItemsContainer="{StaticResource undoRedoMenuItem}"
		  ItemTemplate="{StaticResource historyItemTemplate}" >
	</ig:XamMenuItem>
</ig:XamMenu>

手順7. XamDataGrid の RecordAdding および RecordUpdating イベントを実装します。新規行の追加の操作は複数のアクションを伴いますが、これらを一つのトランザクションにまとめて登録するようにします。

private UndoManager _undoManager
{
	get { return ((MainViewModel)this.DataContext).UndoManager; }
}

UndoTransaction transaction;

private void xamDataGrid1_RecordAdding(object sender, Infragistics.Windows.DataPresenter.Events.RecordAddingEventArgs e)
{
	string description = "";
	string detailedDescription = "Add New Row";
	// 複数の undo 単位を 1 つの項目にグループ化します
	transaction = this._undoManager.StartTransaction(description, detailedDescription);
	e.Record.Tag = "Added";
}

private void xamDataGrid1_RecordUpdating(object sender, Infragistics.Windows.DataPresenter.Events.RecordUpdatingEventArgs e)
{
	if (e.Record.Tag != null && e.Record.Tag.ToString() == "Added")
	{
		e.Record.Tag = null;
		new DispatcherSynchronizationContext().Post(new SendOrPostCallback(CommitTransaction), transaction);
	}
}

private void CommitTransaction(object obj)
{
	if (obj != null)
	{
		UndoTransaction transaction = obj as UndoTransaction;
		if (!transaction.IsClosed)
		{
             // 一連のアクションをひとつのトランザクションとしてコミットします
			transaction.Commit();
		}
	}
}

手順8. XamDataGrid コントロールを追加します。

<igWPF:XamDataGrid 
	Grid.Row="1" 
	x:Name="xamDataGrid1" 
	DataSource="{Binding GridData}"
	RecordAdding="xamDataGrid1_RecordAdding"
	RecordUpdating="xamDataGrid1_RecordUpdating" >
	<igWPF:XamDataGrid.FieldLayoutSettings>
		<igWPF:FieldLayoutSettings
		  AllowAddNew="True"
		  AddNewRecordLocation="OnBottomFixed"/>
	</igWPF:XamDataGrid.FieldLayoutSettings>
</igWPF:XamDataGrid>

製品について

Ultimate UI for WPF