2003-03-16 Daniel Morgan <danmorg@sc.rr.com>
[mono.git] / mcs / class / System.Data / System.Data / DataTable.cs
1 //
2 // System.Data.DataTable.cs
3 //
4 // Author:
5 //   Franklin Wise <gracenote@earthlink.net>
6 //   Christopher Podurgiel (cpodurgiel@msn.com)
7 //   Daniel Morgan <danmorg@sc.rr.com>
8 //   Rodrigo Moya <rodrigo@ximian.com>
9 //   Tim Coleman (tim@timcoleman.com)
10 //   Ville Palo <vi64pa@koti.soon.fi>
11 //
12 // (C) Chris Podurgiel
13 // (C) Ximian, Inc 2002
14 // Copyright (C) Tim Coleman, 2002
15 // Copyright (C) Daniel Morgan, 2002-2003
16 //
17
18 using System;
19 using System.Collections;
20 using System.ComponentModel;
21 using System.Globalization;
22 using System.Runtime.Serialization;
23
24 namespace System.Data {
25         //[Designer]
26         [ToolboxItem (false)]
27         [DefaultEvent ("RowChanging")]
28         [DefaultProperty ("TableName")]
29         [DesignTimeVisible (false)]
30         [Serializable]
31         public class DataTable : MarshalByValueComponent, IListSource, ISupportInitialize, ISerializable 
32         {
33                 internal DataSet dataSet;   
34                 
35                 private bool _caseSensitive;
36                 private DataColumnCollection _columnCollection;
37                 private ConstraintCollection _constraintCollection;
38                 private DataView _defaultView;
39
40                 private string _displayExpression;
41                 private PropertyCollection _extendedProperties;
42                 private bool _hasErrors;
43                 private CultureInfo _locale;
44                 private int _minimumCapacity;
45                 private string _nameSpace;
46                 private DataRelationCollection _childRelations; 
47                 private DataRelationCollection _parentRelations;
48                 private string _prefix;
49                 private DataColumn[] _primaryKey;
50                 private DataRowCollection _rows;
51                 private ISite _site;
52                 private string _tableName;
53                 private bool _containsListCollection;
54                 private string _encodedTableName;
55
56                 
57                 // If CaseSensitive property is changed once it does not anymore follow owner DataSet's 
58                 // CaseSensitive property. So when you lost you virginity it's gone for ever
59                 private bool _virginCaseSensitive = true;
60
61                 /// <summary>
62                 /// Initializes a new instance of the DataTable class with no arguments.
63                 /// </summary>
64                 
65                 public DataTable () 
66                 {
67                         dataSet = null;
68                         _columnCollection = new DataColumnCollection(this);
69                         _constraintCollection = new ConstraintCollection(); 
70                         _extendedProperties = new PropertyCollection();
71                         _tableName = "";
72                         _nameSpace = null;
73                         _caseSensitive = false;         //default value
74                         _displayExpression = null;
75                         _primaryKey = null;
76                         _site = null;
77                         _rows = new DataRowCollection (this);
78                         _locale = CultureInfo.CurrentCulture;
79
80                         //LAMESPEC: spec says 25 impl does 50
81                         _minimumCapacity = 50;
82                         
83                         _childRelations = new DataRelationCollection.DataTableRelationCollection (this);
84                         _parentRelations = new DataRelationCollection.DataTableRelationCollection (this);
85
86                 
87                         _defaultView = new DataView(this);
88                 }
89
90                 /// <summary>
91                 /// Intitalizes a new instance of the DataTable class with the specified table name.
92                 /// </summary>
93                 public DataTable (string tableName) : this () 
94                 {
95                         _tableName = tableName;
96                 }
97
98                 /// <summary>
99                 /// Initializes a new instance of the DataTable class with the SerializationInfo and the StreamingContext.
100                 /// </summary>
101                 [MonoTODO]
102                 protected DataTable (SerializationInfo info, StreamingContext context)
103                         : this () 
104                 {
105                         //
106                         // TODO: Add constructor logic here
107                         //
108                 }
109
110                 /// <summary>
111                 /// Indicates whether string comparisons within the table are case-sensitive.
112                 /// </summary>
113                 [DataSysDescription ("Indicates whether comparing strings within the table is case sensitive.")]        
114                 public bool CaseSensitive {
115                         get { return _caseSensitive; }
116                         set { 
117                                 _virginCaseSensitive = false;
118                                 _caseSensitive = value; 
119                         }
120                 }
121
122                 internal bool VirginCaseSensitive {
123                         get { return _virginCaseSensitive; }
124                         set { _virginCaseSensitive = value; }
125                 }
126
127                 internal void ChangedDataColumn (DataRow dr, DataColumn dc, object pv) 
128                 {
129                         DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv);
130                         OnColumnChanged(e);
131                 }
132
133                 internal void ChangingDataColumn (DataRow dr, DataColumn dc, object pv) 
134                 {
135                         DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv);
136                         OnColumnChanging (e);
137                 }
138
139                 internal void DeletedDataRow (DataRow dr, DataRowAction action) 
140                 {
141                         DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);
142                         OnRowDeleted (e);
143                 }
144
145                 internal void ChangedDataRow (DataRow dr, DataRowAction action) 
146                 {
147                         DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);
148                         OnRowChanged (e);
149                 }
150
151                 /// <summary>
152                 /// Gets the collection of child relations for this DataTable.
153                 /// </summary>
154                 [Browsable (false)]
155                 [DataSysDescription ("Returns the child relations for this table.")]
156                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
157                 public DataRelationCollection ChildRelations {
158                         get {
159                                 return _childRelations;
160                         }
161                 }
162
163                 /// <summary>
164                 /// Gets the collection of columns that belong to this table.
165                 /// </summary>
166                 [DataCategory ("Data")]
167                 [DataSysDescription ("The collection that holds the columns for this table.")]
168                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
169                 public DataColumnCollection Columns {
170                         get { return _columnCollection; }
171                 }
172
173                 /// <summary>
174                 /// Gets the collection of constraints maintained by this table.
175                 /// </summary>
176                 [DataCategory ("Data")] 
177                 [DataSysDescription ("The collection that holds the constraints for this table.")]
178                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
179                 public ConstraintCollection Constraints {
180                         get { return _constraintCollection; }
181                 }
182
183                 /// <summary>
184                 /// Gets the DataSet that this table belongs to.
185                 /// </summary>
186                 [Browsable (false)]
187                 [DataSysDescription ("Indicates the DataSet to which this table belongs.")]
188                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
189                 public DataSet DataSet {
190                         get { return dataSet; }
191                 }
192
193                 
194
195                 /// <summary>
196                 /// Gets a customized view of the table which may 
197                 /// include a filtered view, or a cursor position.
198                 /// </summary>
199                 [MonoTODO]      
200                 [Browsable (false)]
201                 [DataSysDescription ("This is the default DataView for the table.")]
202                 public DataView DefaultView {
203                         get { return _defaultView; }
204                 }
205                 
206
207                 /// <summary>
208                 /// Gets or sets the expression that will return 
209                 /// a value used to represent this table in the user interface.
210                 /// </summary>
211                 [DataCategory ("Data")]
212                 [DataSysDescription ("The expression used to compute the data-bound value of this row.")]       
213                 [DefaultValue ("")]
214                 public string DisplayExpression {
215                         get { return "" + _displayExpression; }
216                         set { _displayExpression = value; }
217                 }
218
219                 /// <summary>
220                 /// Gets the collection of customized user information.
221                 /// </summary>
222                 [Browsable (false)]
223                 [DataCategory ("Data")]
224                 [DataSysDescription ("The collection that holds custom user information.")]
225                 public PropertyCollection ExtendedProperties {
226                         get { return _extendedProperties; }
227                 }
228
229                 /// <summary>
230                 /// Gets a value indicating whether there are errors in 
231                 /// any of the_rows in any of the tables of the DataSet to 
232                 /// which the table belongs.
233                 /// </summary>
234                 [Browsable (false)]
235                 [DataSysDescription ("Returns whether the table has errors.")]
236                 public bool HasErrors {
237                         get { return _hasErrors; }
238                 }
239
240                 /// <summary>
241                 /// Gets or sets the locale information used to 
242                 /// compare strings within the table.
243                 /// </summary>
244                 [DataSysDescription ("Indicates a locale under which to compare strings within the table.")]
245                 public CultureInfo Locale {
246                         get { return _locale; }
247                         set { _locale = value; }
248                 }
249
250                 /// <summary>
251                 /// Gets or sets the initial starting size for this table.
252                 /// </summary>
253                 [DataCategory ("Data")]
254                 [DataSysDescription ("Indicates an initial starting size for this table.")]
255                 [DefaultValue (50)]
256                 public int MinimumCapacity {
257                         get { return _minimumCapacity; }
258                         set { _minimumCapacity = value; }
259                 }
260
261                 /// <summary>
262                 /// Gets or sets the namespace for the XML represenation 
263                 /// of the data stored in the DataTable.
264                 /// </summary>
265                 [DataCategory ("Data")]
266                 [DataSysDescription ("Indicates the XML uri namespace for the elements contained in this table.")]
267                 public string Namespace {
268                         get { return "" + _nameSpace; }
269                         set { _nameSpace = value; }
270                 }
271
272                 /// <summary>
273                 /// Gets the collection of parent relations for 
274                 /// this DataTable.
275                 /// </summary>
276                 [Browsable (false)]
277                 [DataSysDescription ("Returns the parent relations for this table.")]
278                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
279                 public DataRelationCollection ParentRelations {
280                         get {   
281                                 return _parentRelations;
282                         }
283                 }
284
285                 /// <summary>
286                 /// Gets or sets the namespace for the XML represenation
287                 ///  of the data stored in the DataTable.
288                 /// </summary>
289                 [DataCategory ("Data")]
290                 [DataSysDescription ("Indicates the Prefix of the namespace used for this table in XML representation.")]
291                 [DefaultValue ("")]
292                 public string Prefix {
293                         get { return "" + _prefix; }
294                         set { _prefix = value; }
295                 }
296
297                 /// <summary>
298                 /// Gets or sets an array of columns that function as 
299                 /// primary keys for the data table.
300                 /// </summary>
301                 [DataCategory ("Data")]
302                 [DataSysDescription ("Indicates the column(s) that represent the primary key for this table.")]
303                 public DataColumn[] PrimaryKey {
304                         get {
305                                 UniqueConstraint uc = UniqueConstraint.GetPrimaryKeyConstraint( Constraints);
306                                 if (null == uc) return new DataColumn[] {};
307                                 return uc.Columns;
308                         }
309                         set {
310
311                                 //YUK: msft removes a previous unique constraint if it is flagged as a pk  
312                                 //when a new pk is set 
313
314                                 //clear Primary Key if value == null
315                                 if (null == value) {
316                                         UniqueConstraint.SetAsPrimaryKey(this.Constraints, null);
317                                         return;
318                                 }
319                         
320
321                                 //Does constraint exist for these columns
322                                 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(
323                                         this.Constraints, (DataColumn[]) value);
324
325                                 //if constraint doesn't exist for columns
326                                 //create new unique primary key constraint
327                                 if (null == uc) {
328                                         uc = new UniqueConstraint( (DataColumn[]) value, true);
329                                 }
330                                 else { //set existing constraint as the new primary key
331                                         UniqueConstraint.SetAsPrimaryKey(this.Constraints, uc);
332                                 }
333                                 
334                         }
335                 }
336
337                 /// <summary>
338                 /// Gets the collection of_rows that belong to this table.
339                 /// </summary>
340                 [Browsable (false)]
341                 [DataSysDescription ("Indicates the collection that holds the rows of data for this table.")]   
342                 public DataRowCollection Rows {
343                         get { return _rows; }
344                 }
345
346                 /// <summary>
347                 /// Gets or sets an System.ComponentModel.ISite 
348                 /// for the DataTable.
349                 /// </summary>
350                 [Browsable (false)]
351                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
352                 public override ISite Site {
353                         get { return _site; }
354                         set { _site = value; }
355                 }
356
357                 /// <summary>
358                 /// Gets or sets the name of the the DataTable.
359                 /// </summary>
360                 [DataCategory ("Data")]
361                 [DataSysDescription ("Indicates the name used to look up this table in the Tables collection of a DataSet.")]
362                 [DefaultValue ("")]     
363                 [RefreshProperties (RefreshProperties.All)]
364                 public string TableName {
365                         get { return "" + _tableName; }
366                         set { _tableName = value; }
367                 }
368                 
369                 bool IListSource.ContainsListCollection {
370                         get {
371                                 // the collection is a DataView
372                                 return false;
373                         }
374                 }
375
376                 /// <summary>
377                 /// Commits all the changes made to this table since the 
378                 /// last time AcceptChanges was called.
379                 /// </summary>
380                 public void AcceptChanges () 
381                 {
382                         //FIXME: Do we need to validate anything here or
383                         //try to catch any errors to deal with them?
384
385                         foreach(DataRow myRow in _rows) {
386                                 myRow.AcceptChanges();
387                         }
388                 }
389
390                 /// <summary>
391                 /// Begins the initialization of a DataTable that is used 
392                 /// on a form or used by another component. The initialization
393                 /// occurs at runtime.
394                 /// </summary>
395                 public void BeginInit () 
396                 {
397                 }
398
399                 /// <summary>
400                 /// Turns off notifications, index maintenance, and 
401                 /// constraints while loading data.
402                 /// </summary>
403                 [MonoTODO]
404                 public void BeginLoadData () 
405                 {
406                 }
407
408                 /// <summary>
409                 /// Clears the DataTable of all data.
410                 /// </summary>
411                 public void Clear () {
412                         _rows.Clear ();
413                 }
414
415                 /// <summary>
416                 /// Clones the structure of the DataTable, including
417                 ///  all DataTable schemas and constraints.
418                 /// </summary>
419                 [MonoTODO]
420                 public virtual DataTable Clone () 
421                 {
422                         DataTable Copy = new DataTable ();                      
423                         CopyProperties (Copy);
424                         return Copy;
425                 }
426
427                 /// <summary>
428                 /// Computes the given expression on the current_rows that 
429                 /// pass the filter criteria.
430                 /// </summary>
431                 [MonoTODO]
432                 public object Compute (string expression, string filter) 
433                 {
434                         //FIXME: //Do a real compute
435                         object obj = "a";
436                         return obj;
437                 }
438
439                 /// <summary>
440                 /// Copies both the structure and data for this DataTable.
441                 /// </summary>
442                 [MonoTODO]      
443                 public DataTable Copy () 
444                 {
445                         DataTable Copy = new DataTable ();
446                         CopyProperties (Copy);
447
448                         foreach (DataRow Row in Rows) {
449                                 DataRow NewRow = Copy.NewRow ();
450                                 NewRow.RowError = Row.RowError;
451                                 foreach (DataColumn C in Copy.Columns) {
452                                         NewRow [C.ColumnName] = Row [C.ColumnName];
453                                 }
454                                 Copy.Rows.Add (NewRow);
455                         }
456                                         
457                         return Copy;
458                 }
459
460                 [MonoTODO]
461                 private void CopyProperties (DataTable Copy) 
462                 {
463                         Copy.CaseSensitive = CaseSensitive;
464                         Copy.VirginCaseSensitive = VirginCaseSensitive;
465
466                         // Copy.ChildRelations
467                         // Copy.Constraints
468                         // Copy.Container
469                         // Copy.DefaultView
470                         // Copy.DesignMode
471                         Copy.DisplayExpression = DisplayExpression;
472                         // Copy.ExtendedProperties
473                         Copy.Locale = Locale;
474                         Copy.MinimumCapacity = MinimumCapacity;
475                         Copy.Namespace = Namespace;
476                         // Copy.ParentRelations
477                         Copy.Prefix = Prefix;
478                         //Copy.PrimaryKey = PrimaryKey;
479                         Copy.Site = Site;
480                         Copy.TableName = TableName;
481
482                         // Copy columns
483                         foreach (DataColumn Column in Columns) {
484                                 Copy.Columns.Add (CopyColumn (Column));                         
485                         }
486
487                 }
488                 /// <summary>
489                 /// Ends the initialization of a DataTable that is used 
490                 /// on a form or used by another component. The 
491                 /// initialization occurs at runtime.
492                 /// </summary>
493                 [MonoTODO]
494                 public void EndInit () 
495                 {
496
497                 }
498
499                 /// <summary>
500                 /// Turns on notifications, index maintenance, and 
501                 /// constraints after loading data.
502                 /// </summary>
503                 [MonoTODO]
504                 public void EndLoadData() 
505                 {
506                 }
507
508                 /// <summary>
509                 /// Gets a copy of the DataTable that contains all
510                 ///  changes made to it since it was loaded or 
511                 ///  AcceptChanges was last called.
512                 /// </summary>
513                 [MonoTODO]
514                 public DataTable GetChanges() 
515                 {
516                         //TODO:
517                         return this;
518                 }
519
520                 /// <summary>
521                 /// Gets a copy of the DataTable containing all 
522                 /// changes made to it since it was last loaded, or 
523                 /// since AcceptChanges was called, filtered by DataRowState.
524                 /// </summary>
525                 [MonoTODO]      
526                 public DataTable GetChanges(DataRowState rowStates) 
527                 {
528                         //TODO:
529                         return this;
530                 }
531
532                 /// <summary>
533                 /// Gets an array of DataRow objects that contain errors.
534                 /// </summary>
535                 [MonoTODO]
536                 public DataRow[] GetErrors () 
537                 {
538                         throw new NotImplementedException ();
539                 }
540         
541                 /// <summary>
542                 /// This member is only meant to support Mono's infrastructure 
543                 /// </summary>
544                 protected virtual DataTable CreateInstance () 
545                 {
546                         return Activator.CreateInstance (this.GetType (), true) as DataTable;
547                 }
548
549                 /// <summary>
550                 /// This member is only meant to support Mono's infrastructure 
551                 /// </summary>
552                 protected virtual Type GetRowType () 
553                 {
554                         return typeof (DataRow);
555                 }
556
557                 /// <summary>
558                 /// This member is only meant to support Mono's infrastructure 
559                 /// 
560                 /// Used for Data Binding between System.Web.UI. controls 
561                 /// like a DataGrid
562                 /// or
563                 /// System.Windows.Forms controls like a DataGrid
564                 /// </summary>
565                 IList IListSource.GetList () 
566                 {
567                         IList list = (IList) _defaultView;
568                         return list;
569                 }
570                                 
571                 /// <summary>
572                 /// Copies a DataRow into a DataTable, preserving any 
573                 /// property settings, as well as original and current values.
574                 /// </summary>
575                 [MonoTODO]
576                 public void ImportRow (DataRow row) 
577                 {
578                 }
579
580                 /// <summary>
581                 /// This member is only meant to support Mono's infrastructure          
582                 /// </summary>
583                 [MonoTODO]
584                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context) 
585                 {
586                 }
587
588                 /// <summary>
589                 /// Finds and updates a specific row. If no matching row
590                 ///  is found, a new row is created using the given values.
591                 /// </summary>
592                 [MonoTODO]
593                 public DataRow LoadDataRow (object[] values, bool fAcceptChanges) 
594                 {
595                         DataRow row = null;
596                         if (PrimaryKey.Length == 0) {
597                                 row = Rows.Add (values);
598                                 if (fAcceptChanges)
599                                         row.AcceptChanges ();
600                         }
601                         else
602                                 throw new NotImplementedException ();
603                         return row;
604                 }
605
606                 /// <summary>
607                 /// Creates a new DataRow with the same schema as the table.
608                 /// </summary>
609                 public DataRow NewRow () 
610                 {
611                         return this.NewRowFromBuilder (new DataRowBuilder (this, 0, 0));
612                 }
613
614                 /// <summary>
615                 /// This member supports the .NET Framework infrastructure
616                 ///  and is not intended to be used directly from your code.
617                 /// </summary>
618                 protected internal DataRow[] NewRowArray (int size) 
619                 {
620                         return (DataRow[]) Array.CreateInstance (GetRowType (), size);
621                 }
622
623                 /// <summary>
624                 /// Creates a new row from an existing row.
625                 /// </summary>
626                 protected virtual DataRow NewRowFromBuilder (DataRowBuilder builder) 
627                 {
628                         return new DataRow (builder);
629                 }
630         
631
632                 /// <summary>
633                 /// Rolls back all changes that have been made to the 
634                 /// table since it was loaded, or the last time AcceptChanges
635                 ///  was called.
636                 /// </summary>
637                 [MonoTODO]
638                 public void RejectChanges () 
639                 {       
640                         //foreach(DataRow myRow in _rows)
641                         //{
642                         for (int i = _rows.Count - 1; i >= 0; i--) {
643                                 DataRow row = _rows [i];
644                                 if (row.RowState != DataRowState.Unchanged)
645                                         _rows [i].RejectChanges ();
646                         }
647                 }
648
649                 /// <summary>
650                 /// Resets the DataTable to its original state.
651                 /// </summary>          
652                 [MonoTODO]
653                 public virtual void Reset () 
654                 {
655                 }
656
657                 /// <summary>
658                 /// Gets an array of all DataRow objects.
659                 /// </summary>
660                 public DataRow[] Select () 
661                 {
662                         DataRow[] dataRows = new DataRow[_rows.Count];
663                         _rows.CopyTo (dataRows, 0);
664                         return dataRows;
665                 }
666
667                 /// <summary>
668                 /// Gets an array of all DataRow objects that match 
669                 /// the filter criteria in order of primary key (or 
670                 /// lacking one, order of addition.)
671                 /// </summary>
672                 public DataRow[] Select (string filterExpression) 
673                 {
674                         ExpressionElement Expression = new ExpressionMainElement (filterExpression);
675
676                         ArrayList List = new ArrayList ();
677                         foreach (DataRow Row in Rows) {
678                                 
679                                 if (Expression.Test (Row))
680                                         List.Add (Row);
681                         }
682                         
683                         return (DataRow [])List.ToArray (typeof (DataRow));
684                 }
685
686                 /// <summary>
687                 /// Gets an array of all DataRow objects that 
688                 /// match the filter criteria, in the the 
689                 /// specified sort order.
690                 /// </summary>
691                 public DataRow[] Select (string filterExpression, string sort) 
692                 {
693                         DataRow[] dataRows = null;
694
695                         if (filterExpression != null && filterExpression.Equals (String.Empty) == false)
696                                 dataRows = Select (filterExpression);
697                         else
698                                 dataRows = Select ();
699
700                         if (sort != null && !sort.Equals (String.Empty)) {
701                                 SortableColumn[] sortableColumns = null;\r
702 \r
703                                 sortableColumns = ParseTheSortString (sort);\r
704                                 if (sortableColumns == null)\r
705                                         throw new Exception ("sort expression result is null");\r
706                                 if (sortableColumns.Length == 0)\r
707                                         throw new Exception("sort expression result is 0");\r
708
709                                 RowSorter rowSorter = new RowSorter (dataRows, sortableColumns);\r
710                                 dataRows = rowSorter.SortRows ();
711                         
712                                 sortableColumns = null;
713                                 rowSorter = null;
714                         }
715
716                         return dataRows;
717                 }
718
719                 /// <summary>
720                 /// Gets an array of all DataRow objects that match
721                 /// the filter in the order of the sort, that match 
722                 /// the specified state.
723                 /// </summary>
724                 [MonoTODO]
725                 public DataRow[] Select(string filterExpression, string sort, DataViewRowState recordStates) 
726                 {
727                         DataRow[] dataRows = null;
728
729                         // TODO: do something with recordStates
730                         dataRows = Select (filterExpression, sort);
731
732                         return dataRows;
733                 }
734
735                 /// <summary>
736                 /// Gets the TableName and DisplayExpression, if 
737                 /// there is one as a concatenated string.
738                 /// </summary>
739                 public override string ToString() 
740                 {
741                         //LAMESPEC: spec says concat the two. impl puts a 
742                         //plus sign infront of DisplayExpression
743                         return TableName + " " + DisplayExpression;
744                 }
745
746                 
747                 #region Events /////////////////
748                 
749                 /// <summary>
750                 /// Raises the ColumnChanged event.
751                 /// </summary>
752                 protected virtual void OnColumnChanged (DataColumnChangeEventArgs e) {
753                         if (null != ColumnChanged) {
754                                 ColumnChanged (this, e);
755                         }
756                 }
757
758                 /// <summary>
759                 /// Raises the ColumnChanging event.
760                 /// </summary>
761                 protected virtual void OnColumnChanging (DataColumnChangeEventArgs e) {
762                         if (null != ColumnChanging) {
763                                 ColumnChanging (this, e);
764                         }
765                 }
766
767                 /// <summary>
768                 /// Raises the PropertyChanging event.
769                 /// </summary>
770                 [MonoTODO]
771                 protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent) {
772                         //      if (null != PropertyChanging)
773                         //      {
774                         //              PropertyChanging (this, e);
775                         //      }
776                 }
777
778                 /// <summary>
779                 /// Notifies the DataTable that a DataColumn is being removed.
780                 /// </summary>
781                 [MonoTODO]
782                 protected internal virtual void OnRemoveColumn (DataColumn column) {
783                         //      if (null != RemoveColumn)
784                         //      {
785                         //              RemoveColumn(this, e);
786                         //      }
787                 }
788
789                 /// <summary>
790                 /// Raises the RowChanged event.
791                 /// </summary>
792                 protected virtual void OnRowChanged (DataRowChangeEventArgs e) {
793                         if (null != RowChanged) {
794                                 RowChanged(this, e);
795                         }
796                 }
797
798
799                 /// <summary>
800                 /// Raises the RowChanging event.
801                 /// </summary>
802                 protected virtual void OnRowChanging (DataRowChangeEventArgs e) {
803                         if (null != RowChanging) {
804                                 RowChanging(this, e);
805                         }
806                 }
807
808                 /// <summary>
809                 /// Raises the RowDeleted event.
810                 /// </summary>
811                 protected virtual void OnRowDeleted (DataRowChangeEventArgs e) {
812                         if (null != RowDeleted) {
813                                 RowDeleted(this, e);
814                         }
815                 }
816
817                 /// <summary>
818                 /// Raises the RowDeleting event.
819                 /// </summary>
820                 protected virtual void OnRowDeleting (DataRowChangeEventArgs e) {
821                         if (null != RowDeleting) {
822                                 RowDeleting(this, e);
823                         }
824                 }
825
826                 [MonoTODO]
827                 private DataColumn CopyColumn (DataColumn Column) {
828                         DataColumn Copy = new DataColumn ();
829
830                         // Copy all the properties of column
831                         Copy.AllowDBNull = Column.AllowDBNull;
832                         Copy.AutoIncrement = Column.AutoIncrement;
833                         Copy.AutoIncrementSeed = Column.AutoIncrementSeed;
834                         Copy.AutoIncrementStep = Column.AutoIncrementStep;
835                         Copy.Caption = Column.Caption;
836                         Copy.ColumnMapping = Column.ColumnMapping;
837                         Copy.ColumnName = Column.ColumnName;
838                         // Copy.Container
839                         // Copy.DataType
840                         // Copy.DefaultValue                    
841                         Copy.Expression = Column.Expression;
842                         //Copy.ExtendedProperties
843                         Copy.MaxLength = Column.MaxLength;
844                         Copy.Namespace = Column.Namespace;
845                         Copy.Prefix = Column.Prefix;
846                         Copy.ReadOnly = Column.ReadOnly;
847                         //Copy.Site
848                         Copy.Unique = Column.Unique;
849                         
850                         return Copy;
851                 }                       
852
853                 /// <summary>
854                 /// Occurs when after a value has been changed for 
855                 /// the specified DataColumn in a DataRow.
856                 /// </summary>
857                 [DataCategory ("Data")] 
858                 [DataSysDescription ("Occurs when a value has been changed for this column.")]
859                 public event DataColumnChangeEventHandler ColumnChanged;
860
861                 /// <summary>
862                 /// Occurs when a value is being changed for the specified 
863                 /// DataColumn in a DataRow.
864                 /// </summary>
865                 [DataCategory ("Data")]
866                 [DataSysDescription ("Occurs when a value has been submitted for this column. The user can modify the proposed value and should throw an exception to cancel the edit.")]
867                 public event DataColumnChangeEventHandler ColumnChanging;
868
869                 /// <summary>
870                 /// Occurs after a DataRow has been changed successfully.
871                 /// </summary>
872                 [DataCategory ("Data")] 
873                 [DataSysDescription ("Occurs after a row in the table has been successfully edited.")]
874                 public event DataRowChangeEventHandler RowChanged;
875
876                 /// <summary>
877                 /// Occurs when a DataRow is changing.
878                 /// </summary>
879                 [DataCategory ("Data")] 
880                 [DataSysDescription ("Occurs when the row is being changed so that the event handler can modify or cancel the change. The user can modify values in the row and should throw an  exception to cancel the edit.")]
881                 public event DataRowChangeEventHandler RowChanging;
882
883                 /// <summary>
884                 /// Occurs after a row in the table has been deleted.
885                 /// </summary>
886                 [DataCategory ("Data")] 
887                 [DataSysDescription ("Occurs after a row in the table has been successfully deleted.")] 
888                 public event DataRowChangeEventHandler RowDeleted;
889
890                 /// <summary>
891                 /// Occurs before a row in the table is about to be deleted.
892                 /// </summary>
893                 [DataCategory ("Data")] 
894                 [DataSysDescription ("Occurs when a row in the table marked for deletion. Throw an exception to cancel the deletion.")]
895                 public event DataRowChangeEventHandler RowDeleting;
896                 
897                 #endregion // Events
898
899                 // to parse the sort string for DataTable:Select(expression,sort)
900                 // into sortable columns (think ORDER BY, 
901                 // such as, "customer ASC, price DESC" )
902                 private SortableColumn[] ParseTheSortString (string sort) \r
903                 {\r
904                         SortableColumn[] sortColumns = null;\r
905                         ArrayList columns = null;\r
906                 \r
907                         if (sort != null && !sort.Equals ("")) {\r
908                                 columns = new ArrayList ();\r
909                                 string[] columnExpression = sort.Trim ().Split (new char[1] {','});\r
910                         \r
911                                 for (int c = 0; c < columnExpression.Length; c++) {\r
912                                         string[] columnSortInfo = columnExpression[c].Trim ().Split (new char[1] {' '});\r
913                                 \r
914                                         string columnName = columnSortInfo[0].Trim ();\r
915                                         string sortOrder = "ASC";\r
916                                         if (columnSortInfo.Length > 1) \r
917                                                 sortOrder = columnSortInfo[1].Trim ().ToUpper ();\r
918                                         \r
919                                         ListSortDirection sortDirection = ListSortDirection.Ascending;\r
920                                         switch (sortOrder) {\r
921                                         case "ASC":\r
922                                                 sortDirection = ListSortDirection.Ascending;\r
923                                                 break;\r
924                                         case "DESC":\r
925                                                 sortDirection = ListSortDirection.Descending;\r
926                                                 break;\r
927                                         default:\r
928                                                 throw new IndexOutOfRangeException ("Could not find column: " + columnExpression[c]);\r
929                                         }\r
930                                         Int32 ord = 0;\r
931                                         try {\r
932                                                 ord = Int32.Parse (columnName);\r
933                                         }\r
934                                         catch (FormatException) {\r
935                                                 ord = -1;\r
936                                         }\r
937                                         DataColumn dc = null;\r
938                                         if (ord == -1)                          \r
939                                                 dc = _columnCollection[columnName];\r
940                                         else\r
941                                                 dc = _columnCollection[ord];\r
942                                         SortableColumn sortCol = new SortableColumn (dc,sortDirection);\r
943                                         columns.Add (sortCol);\r
944                                 }       \r
945                                 sortColumns = (SortableColumn[]) columns.ToArray (typeof (SortableColumn));\r
946                         }               \r
947                         return sortColumns;\r
948                 }
949         
950                 private class SortableColumn \r
951                 {\r
952                         private DataColumn col;\r
953                         private ListSortDirection dir;\r
954 \r
955                         internal SortableColumn (DataColumn column, \r
956                                                 ListSortDirection direction) \r
957                         {\r
958                                 col = column;\r
959                                 dir = direction;\r
960                         }\r
961 \r
962                         public DataColumn Column {\r
963                                 get {\r
964                                         return col;\r
965                                 }\r
966                         }\r
967 \r
968                         public ListSortDirection SortDirection {\r
969                                 get {\r
970                                         return dir;\r
971                                 }\r
972                         }\r
973                 }\r
974 \r
975                 private class RowSorter : IComparer \r
976                 {\r
977                         private SortableColumn[] sortColumns;\r
978                         private DataRow[] rowsToSort;\r
979                         \r
980                         internal RowSorter(DataRow[] unsortedRows, \r
981                                         SortableColumn[] sortColumns) \r
982                         {\r
983                                 this.sortColumns = sortColumns;\r
984                                 this.rowsToSort = unsortedRows;\r
985                         }\r
986 \r
987                         public SortableColumn[] SortColumns {\r
988                                 get {\r
989                                         return sortColumns;\r
990                                 }\r
991                         }\r
992                         \r
993                         public DataRow[] SortRows () \r
994                         {\r
995                                 Array.Sort (rowsToSort, this);\r
996                                 return rowsToSort;\r
997                         }\r
998 \r
999                         int IComparer.Compare (object x, object y) \r
1000                         {\r
1001                                 if(x == null)\r
1002                                         throw new Exception ("Object to compare is null: x");\r
1003                                 if(y == null)\r
1004                                         throw new Exception ("Object to compare is null: y");\r
1005                                 if(!(x is DataRow))\r
1006                                         throw new Exception ("Object to compare is not DataRow: x is " + x.GetType().ToString());\r
1007                                 if(!(y is DataRow))\r
1008                                         throw new Exception ("Object to compare is not DataRow: y is " + x.GetType().ToString());\r
1009 \r
1010                                 DataRow rowx = (DataRow) x;\r
1011                                 DataRow rowy = (DataRow) y;\r
1012 \r
1013                                 for(int i = 0; i < sortColumns.Length; i++) {\r
1014                                         SortableColumn sortColumn = sortColumns[i];\r
1015                                         DataColumn dc = sortColumn.Column;\r
1016 \r
1017                                         IComparable objx = (IComparable) rowx[dc];\r
1018                                         object objy = rowy[dc];\r
1019 \r
1020                                         int result = CompareObjects (objx, objy);\r
1021                                         if (result != 0) {\r
1022                                                 if (sortColumn.SortDirection == ListSortDirection.Ascending) {\r
1023                                                         return result;\r
1024                                                 }\r
1025                                                 else {\r
1026                                                         return -result;\r
1027                                                 }\r
1028                                         }\r
1029                                 }\r
1030                                 return 0;\r
1031                         }\r
1032 \r
1033                         private int CompareObjects (object a, object b) \r
1034                         {\r
1035                                 if (a == b)
1036                                         return 0;
1037                                 else if (a == null)
1038                                         return -1;
1039                                 else if (a == DBNull.Value)
1040                                         return -1;
1041                                 else if (b == null)
1042                                         return 1;
1043                                 else if (b == DBNull.Value)
1044                                         return 1;
1045
1046                                 if((a is string) && (b is string)) {
1047                                         a = ((string) a).ToUpper ();
1048                                         b = ((string) b).ToUpper ();                    
1049                                 }
1050
1051                                 if (a is IComparable)
1052                                         return ((a as IComparable).CompareTo (b));
1053                                 else if (b is IComparable)
1054                                         return -((b as IComparable).CompareTo (a));
1055
1056                                 throw new ArgumentException ("Neither a nor b IComparable");
1057                         }\r
1058                 }
1059         }
1060 }