// DataTableStore.cs // // Copyright (C) 2008 Christian Hoff // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // namespace Bestandsverwaltung.DataWidgets.DataTable { public class DataTableStore : GLib.Object, Gtk.TreeModelImplementor { // A custom TreeModel to display DataTables private Gtk.TreeModelAdapter pAdapter; protected System.Data.DataTable table; public readonly System.Int32 Stamp; public DataTableStore (System.Data.DataTable tbl) : base() { if(tbl == null) throw (new System.ArgumentNullException ("tbl")); table = tbl; table.RowChanged += this.Row_Changed; table.RowDeleted += this.Row_Changed; table.RowDeleting += this.Row_Changing; table.TableCleared += new System.Data.DataTableClearEventHandler (this.Table_Cleared); // Create a random stamp for the iters System.Random RandomStampGen = new System.Random (); Stamp = RandomStampGen.Next (System.Int32.MinValue, System.Int32.MaxValue); pAdapter = new Gtk.TreeModelAdapter (this); } public Gtk.TreeModelAdapter Adapter { get { return pAdapter; } } public System.Data.DataRow GetRowAtPath (Gtk.TreePath path) { switch (path.Indices.Length) { case 0: return null; case 1: return table.Rows [path.Indices [0]]; default: // Model is list-only; there are no child iters throw (new System.ArgumentOutOfRangeException ("path")); } } public Gtk.TreeModelFlags Flags { get { return Gtk.TreeModelFlags.ListOnly; // return Gtk.TreeModelFlags.ItersPersist; } } public System.Int32 NColumns { get { return table.Columns.Count; } } // Problem: the conversion to a GType will fail with any non-value types derived from System.Object(except string) // Therefore, I added another function which returns a System.Type // TODO: Maybe file a bug report and provide a patch public GLib.GType GetColumnType (System.Int32 col) { // System.Console.WriteLine("Column index: {0}, type: {1}", col.ToString(), type.ToString()); return (GLib.GType) table.Columns[col].DataType; } public System.Type GetColumnSystemType (System.Int32 col) { return table.Columns[col].DataType; } public System.String GetColumnTitle(System.Int32 col) { if(col < table.Columns.Count) { return table.Columns[col].Caption; } else { throw(new System.ArgumentOutOfRangeException("col")); } } public Gtk.TreeIter IterFromRow (System.Data.DataRow row) { System.Runtime.InteropServices.GCHandle gch; gch = System.Runtime.InteropServices.GCHandle.Alloc (row); Gtk.TreeIter result = Gtk.TreeIter.Zero; result.UserData = (System.IntPtr) gch; result.Stamp = this.Stamp; return result; } public System.Data.DataRow RowFromIter (Gtk.TreeIter iter) { if (iter.Stamp != this.Stamp) throw (new System.InvalidOperationException (System.String.Format ("iter belongs to a different model; it's stamp is not equal to the stamp of this model({0})", this.Stamp.ToString ()))); if (iter.UserData == System.IntPtr.Zero) throw (new System.Exception ("iter is Gtk.TreeIter.Zero")); System.Runtime.InteropServices.GCHandle gch = (System.Runtime.InteropServices.GCHandle) iter.UserData; return gch.Target as System.Data.DataRow; } private Gtk.TreePath PathFromRow (System.Data.DataRow row) { if (row == null) { return null; } else { Gtk.TreePath path = new Gtk.TreePath (); path.AppendIndex (table.Rows.IndexOf (row)); return path; } } public System.Boolean GetIter (out Gtk.TreeIter iter, Gtk.TreePath path) { if (path == null) throw new System.ArgumentNullException ("path"); iter = Gtk.TreeIter.Zero; System.Data.DataRow row; try { row = GetRowAtPath (path); } catch { return false; } if (row == null) return false; iter = IterFromRow (row); return true; } public Gtk.TreePath GetPath (Gtk.TreeIter iter) { System.Data.DataRow row = RowFromIter (iter); return PathFromRow (row); } public System.Data.DataColumn GetColumn(System.Int32 ColumnIndex) { return table.Columns [ColumnIndex]; } // col: zero-based index of the column public void GetValue (Gtk.TreeIter iter, System.Int32 col, ref GLib.Value val) { val = new GLib.Value (GetValue (iter, col)); } public System.Object GetValue(Gtk.TreeIter iter, System.Int32 col) { System.Data.DataRow row = RowFromIter (iter); if (row == null) { throw(new System.ArgumentException ("The iter is pointing to a row that is NULL")); } else { return row [col]; } } public void SetValue (Gtk.TreeIter iter, System.Int32 column, System.Object val) { RowFromIter (iter) [column] = val; } public System.Boolean GetIterFirst (out Gtk.TreeIter iter) { if(table.Rows.Count == 0) { iter = Gtk.TreeIter.Zero; return false; } else { iter = IterFromRow (table.Rows[0]); return true; } } public System.Boolean IterNext (ref Gtk.TreeIter iter) { System.Data.DataRow row = RowFromIter (iter); if (row == null) { return false; } else { System.Int32 index = table.Rows.IndexOf (row); if(index + 1 < table.Rows.Count - 1) { // Return next row iter = IterFromRow (table.Rows [index + 1]); return true; } else { // No rows remaining return false; } } } // DataTableStore is list-only public System.Int32 ChildCount (System.Data.DataRow row) { return 0; } // Child: first child iter of parent // @return: failure: false, otherwise; true public System.Boolean IterChildren (out Gtk.TreeIter child, Gtk.TreeIter parent) { child = Gtk.TreeIter.Zero; if (parent.UserData == System.IntPtr.Zero) { if(table.Rows.Count == 0) { return false; } else { child = IterFromRow (table.Rows[0]); return true; } } else { // List-only model return false; } } public System.Boolean IterHasChild (Gtk.TreeIter iter) { // List-only model if (IterNChildren(iter) == 0) { return false; } else { return true; } } public System.Int32 IterNChildren (Gtk.TreeIter iter) { // List-only model if (iter.UserData == System.IntPtr.Zero) { return table.Rows.Count; } else { return 0; } } // According to the Mono docs, index should be zero-based public System.Boolean IterNthChild (out Gtk.TreeIter child, Gtk.TreeIter parent, System.Int32 index) { if (parent.UserData == System.IntPtr.Zero) { child = IterFromRow (table.Rows [index]); return true; } else { // List-only model child = Gtk.TreeIter.Zero; return false; } } public System.Boolean IterParent (out Gtk.TreeIter parent, Gtk.TreeIter child) { // List-only model parent = Gtk.TreeIter.Zero; return false; } public void RefNode (Gtk.TreeIter iter) { } public void UnrefNode (Gtk.TreeIter iter) { } public System.Data.DataTable Table { get { return table; } } public System.Data.DataColumn[] GetRelatedColumns(System.Int32[] ColIndices) { System.Data.DataColumn[] SrcCols = new System.Data.DataColumn[ColIndices.GetLength(0)]; for(System.Int32 ColumnIndex = 0; ColumnIndex <= ColIndices.GetUpperBound(0); ColumnIndex++) { SrcCols[ColumnIndex] = table.Columns[ColIndices[ColumnIndex]]; } return GetRelatedColumns(SrcCols); } public System.Data.DataColumn[] GetRelatedColumns(System.Data.DataColumn[] SrcCols) { System.Data.DataSet ds = table.DataSet; System.Collections.Generic.List DestColumns = new System.Collections.Generic.List(); foreach(System.Data.DataRelation rel in ds.Relations) { foreach(System.Data.DataColumn col1 in rel.ChildColumns) { foreach(System.Data.DataColumn col2 in SrcCols) { if(col1 == col2) { foreach(System.Data.DataColumn col in rel.ParentColumns) { DestColumns.Add(col); } } } } foreach(System.Data.DataColumn col1 in rel.ParentColumns) { foreach(System.Data.DataColumn col2 in SrcCols) { if(col1 == col2) { foreach(System.Data.DataColumn col in rel.ChildColumns) { DestColumns.Add(col); } } } } } return DestColumns.ToArray(); } // Event handlers private System.Collections.Generic.Dictionary RowsToBeDeleted = new System.Collections.Generic.Dictionary (); private void Row_Changing (System.Object o, System.Data.DataRowChangeEventArgs e) { if ((e.Action & System.Data.DataRowAction.Delete) == System.Data.DataRowAction.Delete) RowsToBeDeleted.Add (e.Row, PathFromRow (e.Row)); System.Console.WriteLine ("Note: Action {0} is performed in DataTable", e.Action.ToString ()); } private void Row_Changed(System.Object obj, System.Data.DataRowChangeEventArgs e) { if ((e.Action & System.Data.DataRowAction.Add) == System.Data.DataRowAction.Add) pAdapter.EmitRowInserted (PathFromRow (e.Row), IterFromRow (e.Row)); else if ((e.Action & System.Data.DataRowAction.Change) == System.Data.DataRowAction.Change) pAdapter.EmitRowChanged (PathFromRow (e.Row), IterFromRow (e.Row)); else if ((e.Action & System.Data.DataRowAction.Delete) == System.Data.DataRowAction.Delete) { Gtk.TreePath delPath = RowsToBeDeleted [e.Row]; System.Console.WriteLine ("Removed row, path: {0}", delPath.Indices [0]); RowsToBeDeleted.Remove (e.Row); pAdapter.EmitRowDeleted (delPath); foreach (Gtk.TreePath currPath in RowsToBeDeleted.Values) { System.Console.WriteLine ("Looking for a TreePath to be changed"); if (currPath.Indices [0] > delPath.Indices [0]) { if (!currPath.Prev ()) throw (new System.ApplicationException ("Moving TreePath to previous element failed")); } } delPath.Dispose (); } else System.Console.WriteLine ("Note: Unhandled action {0} performed in DataTable", e.Action.ToString ()); } private void Table_Cleared(System.Object sender, System.Data.DataTableClearEventArgs e) { pAdapter.EmitRowDeleted (new Gtk.TreePath ()); } } }