Fix bug #395
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Binding.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2004-2005 Novell, Inc.
21 //
22 // Authors:
23 //      Peter Bartok    pbartok@novell.com
24 //      Jackson Harper  jackson@ximian.com
25 //
26
27
28 using System.ComponentModel;
29
30 namespace System.Windows.Forms {
31
32         [TypeConverter (typeof (ListBindingConverter))]
33         public class Binding {
34
35                 private string property_name;
36                 private object data_source;
37                 private string data_member;
38
39                 private bool is_binding;
40                 private bool checked_isnull;
41
42                 private BindingMemberInfo binding_member_info;
43                 private IBindableComponent control;
44
45                 private BindingManagerBase manager;
46                 private PropertyDescriptor control_property;
47                 private PropertyDescriptor is_null_desc;
48
49                 private object data;
50                 private Type data_type;
51
52                 private DataSourceUpdateMode datasource_update_mode;
53                 private ControlUpdateMode control_update_mode;
54                 private object datasource_null_value = Convert.DBNull;
55                 private object null_value;
56                 private IFormatProvider format_info;
57                 private string format_string;
58                 private bool formatting_enabled;
59                 #region Public Constructors
60                 public Binding (string propertyName, object dataSource, string dataMember) 
61                         : this (propertyName, dataSource, dataMember, false, DataSourceUpdateMode.OnValidation, null, string.Empty, null)
62                 {
63                 }
64                 
65                 public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled)
66                         : this (propertyName, dataSource, dataMember, formattingEnabled, DataSourceUpdateMode.OnValidation, null, string.Empty, null)
67                 {
68                 }
69                 
70                 public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode)
71                         : this (propertyName, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, null, string.Empty, null)
72                 {
73                 }
74                 
75                 public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue)
76                         : this (propertyName, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, nullValue, string.Empty, null)
77                 {
78                 }
79
80                 public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString)
81                         : this (propertyName, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, nullValue, formatString, null)
82                 {
83                 }
84
85                 public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString, IFormatProvider formatInfo)
86                 {
87                         property_name = propertyName;
88                         data_source = dataSource;
89                         data_member = dataMember;
90                         binding_member_info = new BindingMemberInfo (dataMember);
91                         datasource_update_mode = dataSourceUpdateMode;
92                         null_value = nullValue;
93                         format_string = formatString;
94                         format_info = formatInfo;
95                 }
96                 #endregion      // Public Constructors
97
98                 #region Public Instance Properties
99                 [DefaultValue (null)]
100                 public IBindableComponent BindableComponent {
101                         get {
102                                 return control;
103                         }
104                 }
105
106                 public BindingManagerBase BindingManagerBase {
107                         get {
108                                 return manager;
109                         }
110                 }
111
112                 public BindingMemberInfo BindingMemberInfo {
113                         get {
114                                 return binding_member_info;
115                         }
116                 }
117
118                 [DefaultValue (null)]
119                 public Control Control {
120                         get {
121                                 return control as Control;
122                         }
123                 }
124
125                 [DefaultValue (ControlUpdateMode.OnPropertyChanged)]
126                 public ControlUpdateMode ControlUpdateMode {
127                         get {
128                                 return control_update_mode;
129                         }
130                         set {
131                                 control_update_mode = value;
132                         }
133                 }
134
135                 public object DataSource {
136                         get {
137                                 return data_source;
138                         }
139                 }
140
141                 [DefaultValue (DataSourceUpdateMode.OnValidation)]
142                 public DataSourceUpdateMode DataSourceUpdateMode {
143                         get {
144                                 return datasource_update_mode;
145                         }
146                         set {
147                                 datasource_update_mode = value;
148                         }
149                 }
150
151                 public object DataSourceNullValue {
152                         get {
153                                 return datasource_null_value;
154                         }
155                         set {
156                                 datasource_null_value = value;
157                         }
158                 }
159
160                 [DefaultValue (false)]
161                 public bool FormattingEnabled {
162                         get {
163                                 return formatting_enabled;
164                         }
165                         set {
166                                 if (formatting_enabled == value)
167                                         return;
168
169                                 formatting_enabled = value;
170                                 PushData ();
171                         }
172                 }
173
174                 [DefaultValue (null)]
175                 public IFormatProvider FormatInfo {
176                         get {
177                                 return format_info;
178                         }
179                         set {
180                                 if (value == format_info)
181                                         return;
182
183                                 format_info = value;
184                                 if (formatting_enabled)
185                                         PushData ();
186                         }
187                 }
188
189                 public string FormatString {
190                         get {
191                                 return format_string;
192                         }
193                         set {
194                                 if (value == null)
195                                         value = String.Empty;
196                                 if (value == format_string)
197                                         return;
198
199                                 format_string = value;
200                                 if (formatting_enabled)
201                                         PushData ();
202                         }
203                 }
204
205                 public bool IsBinding {
206                         get {
207                                 if (manager == null || manager.IsSuspended)
208                                         return false;
209
210                                 return is_binding;
211                         }
212                 }
213
214                 public object NullValue {
215                         get {
216                                 return null_value;
217                         }
218                         set {
219                                 if (value == null_value)
220                                         return;
221
222                                 null_value = value;
223                                 if (formatting_enabled)
224                                         PushData ();
225                         }
226                 }
227
228                 [DefaultValue ("")]
229                 public string PropertyName {
230                         get {
231                                 return property_name;
232                         }
233                 }
234                 #endregion      // Public Instance Properties
235
236                 public void ReadValue ()
237                 {
238                         PushData (true);
239                 }
240
241                 public void WriteValue ()
242                 {
243                         PullData (true);
244                 }
245
246                 #region Protected Instance Methods
247                 protected virtual void OnBindingComplete (BindingCompleteEventArgs e)
248                 {
249                         if (BindingComplete != null)
250                                 BindingComplete (this, e);
251                 }
252
253                 protected virtual void OnFormat (ConvertEventArgs cevent)
254                 {
255                         if (Format!=null)
256                                 Format (this, cevent);
257                 }
258
259                 protected virtual void OnParse (ConvertEventArgs cevent)
260                 {
261                         if (Parse!=null)
262                                 Parse (this, cevent);
263                 }
264                 #endregion      // Protected Instance Methods
265
266                 internal string DataMember {
267                         get { return data_member; }
268                 }
269                 
270                 internal void SetControl (IBindableComponent control)
271                 {
272                         if (control == this.control)
273                                 return;
274
275                         control_property = TypeDescriptor.GetProperties (control).Find (property_name, true);                   
276
277                         if (control_property == null)
278                                 throw new ArgumentException (String.Concat ("Cannot bind to property '", property_name, "' on target control."));
279                         if (control_property.IsReadOnly)
280                                 throw new ArgumentException (String.Concat ("Cannot bind to property '", property_name, "' because it is read only."));
281                                 
282                         data_type = control_property.PropertyType; // Getting the PropertyType is kinda slow and it should never change, so it is cached
283
284                         Control ctrl = control as Control;
285                         if (ctrl != null) {
286                                 ctrl.Validating += new CancelEventHandler (ControlValidatingHandler);
287                                 if (!ctrl.IsHandleCreated)
288                                         ctrl.HandleCreated += new EventHandler (ControlCreatedHandler);
289                         }
290
291                         EventDescriptor prop_changed_event = GetPropertyChangedEvent (control, property_name);
292                         if (prop_changed_event != null)
293                                 prop_changed_event.AddEventHandler (control, new EventHandler (ControlPropertyChangedHandler));
294                         this.control = control;
295                         UpdateIsBinding ();
296                 }
297
298                 internal void Check ()
299                 {
300                         if (control == null || control.BindingContext == null)
301                                 return;
302
303                         if (manager == null) {
304                                 manager = control.BindingContext [data_source, binding_member_info.BindingPath];
305
306                                 if (manager.Position > -1 && binding_member_info.BindingField != String.Empty &&
307                                         TypeDescriptor.GetProperties (manager.Current).Find (binding_member_info.BindingField, true) == null)
308                                         throw new ArgumentException ("Cannot bind to property '" + binding_member_info.BindingField + "' on DataSource.", 
309                                                         "dataMember");
310
311                                 manager.AddBinding (this);
312                                 manager.PositionChanged += new EventHandler (PositionChangedHandler);
313
314                                 if (manager is PropertyManager) { // Match .net, which only watchs simple objects
315                                         EventDescriptor prop_changed_event = GetPropertyChangedEvent (manager.Current, binding_member_info.BindingField);
316                                         if (prop_changed_event != null)
317                                                 prop_changed_event.AddEventHandler (manager.Current, new EventHandler (SourcePropertyChangedHandler));
318                                 }
319                         }
320
321                         if (manager.Position == -1)
322                                 return;
323
324                         if (!checked_isnull) {
325                                 is_null_desc = TypeDescriptor.GetProperties (manager.Current).Find (property_name + "IsNull", false);
326                                 checked_isnull = true;
327                         }
328
329                         PushData ();
330                 }
331
332                 internal bool PullData ()
333                 {
334                         return PullData (false);
335                 }
336
337                 // Return false ONLY in case of error - and return true even in cases
338                 // where no update was possible
339                 bool PullData (bool force)
340                 {
341                         if (IsBinding == false || manager.Current == null)
342                                 return true;
343                         if (!force && datasource_update_mode == DataSourceUpdateMode.Never)
344                                 return true;
345
346                         data = control_property.GetValue (control);
347                         if (data == null)
348                                 data = datasource_null_value;
349
350                         try {
351                                 SetPropertyValue (data);
352                         } catch (Exception e) {
353                                 if (formatting_enabled) {
354                                         FireBindingComplete (BindingCompleteContext.DataSourceUpdate, e, e.Message);
355                                         return false;
356                                 }
357                                 throw e;
358                         }
359
360                         if (formatting_enabled)
361                                 FireBindingComplete (BindingCompleteContext.DataSourceUpdate, null, null);
362                         return true;
363                 }
364
365                 internal void PushData ()
366                 {
367                         PushData (false);
368                 }
369
370                 void PushData (bool force)
371                 {
372                         if (manager == null || manager.IsSuspended || manager.Count == 0 || manager.Position == -1)
373                                 return;
374                         if (!force && control_update_mode == ControlUpdateMode.Never)
375                                 return;
376
377                         if (is_null_desc != null) {
378                                 bool is_null = (bool) is_null_desc.GetValue (manager.Current);
379                                 if (is_null) {
380                                         data = Convert.DBNull;
381                                         return;
382                                 }
383                         }
384
385                         PropertyDescriptor pd = TypeDescriptor.GetProperties (manager.Current).Find (binding_member_info.BindingField, true);
386                         if (pd == null) {
387                                 data = manager.Current;
388                         } else {
389                                 data = pd.GetValue (manager.Current);
390                         }
391
392                         if ((data == null || data == DBNull.Value) && null_value != null)
393                                 data = null_value;
394
395                         try {
396                                 data = FormatData (data);
397                                 SetControlValue (data);
398                         } catch (Exception e) {
399                                 if (formatting_enabled) {
400                                         FireBindingComplete (BindingCompleteContext.ControlUpdate, e, e.Message);
401                                         return;
402                                 }
403                                 throw e;
404                         }
405
406                         if (formatting_enabled)
407                                 FireBindingComplete (BindingCompleteContext.ControlUpdate, null, null);
408                 }
409
410                 internal void UpdateIsBinding ()
411                 {
412                         is_binding = false;
413                         if (control == null || (control is Control && !((Control)control).IsHandleCreated))
414                                 return;
415
416                         is_binding = true;
417                         PushData ();
418                 }
419
420                 private void SetControlValue (object data)
421                 {
422                         control_property.SetValue (control, data);
423                 }
424
425                 private void SetPropertyValue (object data)
426                 {
427                         PropertyDescriptor pd = TypeDescriptor.GetProperties (manager.Current).Find (binding_member_info.BindingField, true);
428                         if (pd.IsReadOnly)
429                                 return;
430                         data = ParseData (data, pd.PropertyType);
431                         pd.SetValue (manager.Current, data);
432                 }
433
434                 private void ControlValidatingHandler (object sender, CancelEventArgs e)
435                 {
436                         if (datasource_update_mode != DataSourceUpdateMode.OnValidation)
437                                 return;
438
439                         bool ok = true;
440                         // If the data doesn't seem to be valid (it can't be converted,
441                         // is the wrong type, etc, we reset to the old data value.
442                         // If Formatting is enabled, no exception is fired, but we get a false value
443                         try {
444                                 ok = PullData ();
445                         } catch {
446                                 ok = false;
447                         }
448
449                         e.Cancel = !ok;
450                 }
451
452                 private void ControlCreatedHandler (object o, EventArgs args)
453                 {
454                         UpdateIsBinding ();
455                 }
456
457                 private void PositionChangedHandler (object sender, EventArgs e)
458                 {
459                         Check ();
460                         PushData ();
461                 }
462
463                 EventDescriptor GetPropertyChangedEvent (object o, string property_name)
464                 {
465                         if (o == null || property_name == null || property_name.Length == 0)
466                                 return null;
467
468                         string event_name = property_name + "Changed";
469                         Type event_handler_type = typeof (EventHandler);
470
471                         EventDescriptor prop_changed_event = null;
472                         foreach (EventDescriptor event_desc in TypeDescriptor.GetEvents (o)) {
473                                 if (event_desc.Name == event_name && event_desc.EventType == event_handler_type) {
474                                         prop_changed_event = event_desc;
475                                         break;
476                                 }
477                         }
478
479                         return prop_changed_event;
480                 }
481
482                 void SourcePropertyChangedHandler (object o, EventArgs args)
483                 {
484                         PushData ();
485                 }
486
487                 void ControlPropertyChangedHandler (object o, EventArgs args)
488                 {
489                         if (datasource_update_mode != DataSourceUpdateMode.OnPropertyChanged)
490                                 return;
491
492                         PullData ();
493                 }
494
495                 private object ParseData (object data, Type data_type)
496                 {
497                         ConvertEventArgs e = new ConvertEventArgs (data, data_type);
498
499                         OnParse (e);
500                         if (data_type.IsInstanceOfType (e.Value))
501                                 return e.Value;
502                         if (e.Value == Convert.DBNull)
503                                 return e.Value;
504                         if (e.Value == null) {
505                                 bool nullable = data_type.IsGenericType && !data_type.ContainsGenericParameters &&
506                                         data_type.GetGenericTypeDefinition () == typeof (Nullable<>);
507                                 return data_type.IsValueType && !nullable ? Convert.DBNull : null;
508                         }
509
510                         return ConvertData (e.Value, data_type);
511                 }
512
513                 private object FormatData (object data)
514                 {
515                         ConvertEventArgs e = new ConvertEventArgs (data, data_type);
516
517                         OnFormat (e);
518                         if (data_type.IsInstanceOfType (e.Value))
519                                 return e.Value;
520
521                         if (formatting_enabled) {
522                                 if ((e.Value == null || e.Value == Convert.DBNull) && null_value != null)
523                                         return null_value;
524                                 
525                                 if (e.Value is IFormattable && data_type == typeof (string)) {
526                                         IFormattable formattable = (IFormattable) e.Value;
527                                         return formattable.ToString (format_string, format_info);
528                                 }
529                         }
530
531                         if (e.Value == null && data_type == typeof (object))
532                                 return Convert.DBNull;
533
534                         return ConvertData (data, data_type);
535                 }
536
537                 private object ConvertData (object data, Type data_type)
538                 {
539                         if (data == null)
540                                 return null;
541
542                         TypeConverter converter = TypeDescriptor.GetConverter (data.GetType ());
543                         if (converter != null && converter.CanConvertTo (data_type))
544                                 return converter.ConvertTo (data, data_type);
545
546                         converter = TypeDescriptor.GetConverter (data_type);
547                         if (converter != null && converter.CanConvertFrom (data.GetType()))
548                                 return converter.ConvertFrom (data);
549
550                         if (data is IConvertible) {
551                                 object res = Convert.ChangeType (data, data_type);
552                                 if (data_type.IsInstanceOfType (res))
553                                         return res;
554                         }
555
556                         return null;
557                 }
558                 void FireBindingComplete (BindingCompleteContext context, Exception exc, string error_message)
559                 {
560                         BindingCompleteEventArgs args = new BindingCompleteEventArgs (this, 
561                                         exc == null ? BindingCompleteState.Success : BindingCompleteState.Exception,
562                                         context);
563                         if (exc != null) {
564                                 args.SetException (exc);
565                                 args.SetErrorText (error_message);
566                         }
567
568                         OnBindingComplete (args);
569                 }
570
571                 #region Events
572                 public event ConvertEventHandler Format;
573                 public event ConvertEventHandler Parse;
574                 public event BindingCompleteEventHandler BindingComplete;
575                 #endregion      // Events
576         }
577 }