Click here to Skip to main content
15,891,253 members
Articles / Programming Languages / C#

DataTable Transaction Logger

Rate me:
Please Sign up or sign in to vote.
4.58/5 (31 votes)
28 Feb 2006CPOL12 min read 154.2K   3.2K   107  
Undo/Redo DataTable transactions by logging row changes (insert/delete) and field changes.
using System;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;

using System.Reflection;

using Clifton.Tools.Data;
using MyXaml.Core;

namespace DataTableUndoRedo
{
	public class Program
	{
		protected DataTable dt;
		protected DataTableTransactionLog tlog;

		[MyXamlAutoInitialize]
		DataGrid dgTransactions = null;

		[MyXamlAutoInitialize]
		Button btnUndo = null;

		[MyXamlAutoInitialize]
		Button btnRedo = null;

		protected bool columnErrorOnChange;
		protected bool rowErrorOnChange;
		protected bool revertFieldValue;
		
		/// <summary>
		/// Gets/sets revertFieldValue
		/// </summary>
		public bool RevertFieldValue
		{
			get { return revertFieldValue; }
			set { revertFieldValue = value; }
		}
		
		/// <summary>
		/// Gets/sets rowErrorOnDelete
		/// </summary>
		public bool RowErrorOnChange
		{
			get { return rowErrorOnChange; }
			set { rowErrorOnChange = value; }
		}
		
		/// <summary>
		/// Gets/sets columnErrorOnChange
		/// </summary>
		public bool ColumnErrorOnChange
		{
			get { return columnErrorOnChange; }
			set { columnErrorOnChange = value; }
		}

		[STAThread]
		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(false);
			new Program();
		}

		int undoRow = 0;

		public Program()
		{
			tlog = new DataTableTransactionLog();
			tlog.TransactionAdding += new DataTableTransactionLog.TransactionDlgt(OnTransactionAdding);
			tlog.TransactionAdded += new DataTableTransactionLog.TransactionDlgt(OnTransactionAdded);
			string names="Marc.Clifton,Karen.Linder,Ian.Clifton,Chris.Maunder,Anders.Molin,Tom.Archer,Justin.Dunlap,Bill.Sergio";
			dt = new DataTable();
			dt.Columns.Add(new DataColumn("LastName", typeof(string)));
			dt.Columns.Add(new DataColumn("FirstName", typeof(string)));

			string[] name = names.Split(',');
			int i = 0;

			foreach (string s in name)
			{
				string[] fl = s.Split('.');
				DataRow dr = dt.NewRow();
				dr["FirstName"] = fl[0];
				dr["LastName"] = fl[1];
				dt.Rows.Add(dr);
				++i;
			}

			// sync up.
			dt.AcceptChanges();

			DataView dv = new DataView(dt);
			dv.Sort = "LastName, FirstName";

			tlog.SourceTable = dt;

			// Add our validation event handlers after the transaction log event handlers, just for fun.
			dt.ColumnChanging += new DataColumnChangeEventHandler(OnColumnChanging);
			dt.RowChanging += new DataRowChangeEventHandler(OnRowChanging);

			Parser.AddExtender("MyXaml.WinForms", "MyXaml.WinForms", "WinFormExtender"); 
			Parser p = new Parser();
			p.AddReference("app", this);
			p.AddReference("view", dv);
			p.AddReference("transactions", tlog.Log);
			Form form = (Form)p.Instantiate("app.myxaml", "*");
			p.InitializeFields(this);

			btnUndo.Enabled = false;
			btnRedo.Enabled = false;

			// Test collecting uncommitted rows.
			//DataRow dr1 = dt.NewRow();
			//dr1["LastName"] = "Foo";
			//DataRow dr2 = dt.NewRow();
			//dr2["FirstName"] = "Bar";
			//tlog.CollectUncommittedRows();

			Application.Run(form);
		}

		void OnColumnChanging(object sender, DataColumnChangeEventArgs e)
		{
			if (columnErrorOnChange)
			{
				e.Row.SetColumnError(e.Column, "Column Error");

				if (revertFieldValue)
				{
					e.ProposedValue = e.Row[e.Column];
				}
			}
			else
			{
				e.Row.SetColumnError(e.Column, null);
			}
		}

		void OnRowChanging(object sender, DataRowChangeEventArgs e)
		{
			if (rowErrorOnChange)
			{
				e.Row.RowError = "Row Error";
			}
			else
			{
				e.Row.RowError = null;
			}
		}

		void OnTransactionAdding(object sender, TransactionEventArgs e)
		{
			btnRedo.Enabled = false;
			
			if (undoRow < tlog.Log.Count - 1)
			{
				tlog.Log.RemoveRange(undoRow + 1, tlog.Log.Count - (undoRow + 1));
				tlog.AcceptChanges();  // We have to accept changes at this point to sync the table.
			}
		}

		void OnTransactionAdded(object sender, TransactionEventArgs e)
		{
			dgTransactions.DataSource = null;
			dgTransactions.DataSource = tlog.Log;
			undoRow = tlog.Log.Count - 1;
			dgTransactions.CurrentRowIndex = undoRow;
			dgTransactions.Invalidate();
			btnUndo.Enabled = true;
		}

		void OnUndo(object sender, EventArgs e)
		{
			tlog.Revert(undoRow);
			--undoRow;

			if (undoRow < 0)
			{
				btnUndo.Enabled = false;
			}

			dgTransactions.CurrentRowIndex = undoRow;
			dgTransactions.Invalidate();
			btnRedo.Enabled = true;
		}

		void OnRedo(object sender, EventArgs e)
		{
			++undoRow;
			tlog.Apply(undoRow);

			if (undoRow == tlog.Log.Count - 1)
			{
				btnRedo.Enabled = false;
			}

			dgTransactions.CurrentRowIndex = undoRow;
			dgTransactions.Invalidate();
			btnUndo.Enabled = true;
		}

		void OnAcceptChanges(object sender, EventArgs e)
		{
			tlog.AcceptChanges();
		}

		void OnRejectChanges(object sender, EventArgs e)
		{
			// Redo any "undone" changes to sync the table with
			// the transaction log.
			while (undoRow < tlog.Log.Count - 1)
			{
				OnRedo(sender, e);
			}

			tlog.RejectChanges();
			dgTransactions.DataSource = null;
			dgTransactions.DataSource = tlog.Log;
			undoRow = tlog.Log.Count - 1;
			dgTransactions.CurrentRowIndex = undoRow;
			dgTransactions.Invalidate();
		}

		void OnClear(object sender, EventArgs e)
		{
			dt.Clear();
			dgTransactions.DataSource = null;
			dgTransactions.DataSource = tlog.Log;
		}

		void OnCollect(object sender, EventArgs e)
		{
			tlog.CollectUncommittedRows();
			dgTransactions.DataSource = null;
			dgTransactions.DataSource = tlog.Log;
			dgTransactions.CurrentRowIndex = tlog.Log.Count - 1;
			dgTransactions.Invalidate();
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions