diff options
Diffstat (limited to 'source/ShiftUI/Internal/Binding.cs')
| -rw-r--r-- | source/ShiftUI/Internal/Binding.cs | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/source/ShiftUI/Internal/Binding.cs b/source/ShiftUI/Internal/Binding.cs new file mode 100644 index 0000000..7b6ccc7 --- /dev/null +++ b/source/ShiftUI/Internal/Binding.cs @@ -0,0 +1,578 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// Jackson Harper [email protected] +// + + +using System.ComponentModel; +using System; + +namespace ShiftUI { + + [TypeConverter (typeof (ListBindingConverter))] + public class Binding { + + private string property_name; + private object data_source; + private string data_member; + + private bool is_binding; + private bool checked_isnull; + + private BindingMemberInfo binding_member_info; + private IBindableComponent control; + + private BindingManagerBase manager; + private PropertyDescriptor control_property; + private PropertyDescriptor is_null_desc; + + private object data; + private Type data_type; + + private DataSourceUpdateMode datasource_update_mode; + private ControlUpdateMode control_update_mode; + private object datasource_null_value = Convert.DBNull; + private object null_value; + private IFormatProvider format_info; + private string format_string; + private bool formatting_enabled; + #region Public Constructors + public Binding (string propertyName, object dataSource, string dataMember) + : this (propertyName, dataSource, dataMember, false, DataSourceUpdateMode.OnValidation, null, string.Empty, null) + { + } + + public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled) + : this (propertyName, dataSource, dataMember, formattingEnabled, DataSourceUpdateMode.OnValidation, null, string.Empty, null) + { + } + + public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode) + : this (propertyName, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, null, string.Empty, null) + { + } + + public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue) + : this (propertyName, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, nullValue, string.Empty, null) + { + } + + public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString) + : this (propertyName, dataSource, dataMember, formattingEnabled, dataSourceUpdateMode, nullValue, formatString, null) + { + } + + public Binding (string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString, IFormatProvider formatInfo) + { + property_name = propertyName; + data_source = dataSource; + data_member = dataMember; + binding_member_info = new BindingMemberInfo (dataMember); + datasource_update_mode = dataSourceUpdateMode; + null_value = nullValue; + format_string = formatString; + format_info = formatInfo; + } + #endregion // Public Constructors + + #region Public Instance Properties + [DefaultValue (null)] + public IBindableComponent BindableComponent { + get { + return control; + } + } + + public BindingManagerBase BindingManagerBase { + get { + return manager; + } + } + + public BindingMemberInfo BindingMemberInfo { + get { + return binding_member_info; + } + } + + [DefaultValue (null)] + public Widget Widget { + get { + return control as Widget; + } + } + + [DefaultValue (ControlUpdateMode.OnPropertyChanged)] + public ControlUpdateMode ControlUpdateMode { + get { + return control_update_mode; + } + set { + control_update_mode = value; + } + } + + public object DataSource { + get { + return data_source; + } + } + + [DefaultValue (DataSourceUpdateMode.OnValidation)] + public DataSourceUpdateMode DataSourceUpdateMode { + get { + return datasource_update_mode; + } + set { + datasource_update_mode = value; + } + } + + public object DataSourceNullValue { + get { + return datasource_null_value; + } + set { + datasource_null_value = value; + } + } + + [DefaultValue (false)] + public bool FormattingEnabled { + get { + return formatting_enabled; + } + set { + if (formatting_enabled == value) + return; + + formatting_enabled = value; + PushData (); + } + } + + [DefaultValue (null)] + public IFormatProvider FormatInfo { + get { + return format_info; + } + set { + if (value == format_info) + return; + + format_info = value; + if (formatting_enabled) + PushData (); + } + } + + public string FormatString { + get { + return format_string; + } + set { + if (value == null) + value = String.Empty; + if (value == format_string) + return; + + format_string = value; + if (formatting_enabled) + PushData (); + } + } + + public bool IsBinding { + get { + if (manager == null || manager.IsSuspended) + return false; + + return is_binding; + } + } + + public object NullValue { + get { + return null_value; + } + set { + if (value == null_value) + return; + + null_value = value; + if (formatting_enabled) + PushData (); + } + } + + [DefaultValue ("")] + public string PropertyName { + get { + return property_name; + } + } + #endregion // Public Instance Properties + + public void ReadValue () + { + PushData (true); + } + + public void WriteValue () + { + PullData (true); + } + + #region Protected Instance Methods + protected virtual void OnBindingComplete (BindingCompleteEventArgs e) + { + if (BindingComplete != null) + BindingComplete (this, e); + } + + protected virtual void OnFormat (ConvertEventArgs cevent) + { + if (Format!=null) + Format (this, cevent); + } + + protected virtual void OnParse (ConvertEventArgs cevent) + { + if (Parse!=null) + Parse (this, cevent); + } + #endregion // Protected Instance Methods + + internal string DataMember { + get { return data_member; } + } + + internal void SetWidget (IBindableComponent control) + { + if (control == this.control) + return; + + control_property = TypeDescriptor.GetProperties (control).Find (property_name, true); + + if (control_property == null) + throw new ArgumentException (String.Concat ("Cannot bind to property '", property_name, "' on target control.")); + if (control_property.IsReadOnly) + throw new ArgumentException (String.Concat ("Cannot bind to property '", property_name, "' because it is read only.")); + + data_type = control_property.PropertyType; // Getting the PropertyType is kinda slow and it should never change, so it is cached + + Widget ctrl = control as Widget; + if (ctrl != null) { + ctrl.Validating += new CancelEventHandler (ControlValidatingHandler); + if (!ctrl.IsHandleCreated) + ctrl.HandleCreated += new EventHandler (ControlCreatedHandler); + } + + EventDescriptor prop_changed_event = GetPropertyChangedEvent (control, property_name); + if (prop_changed_event != null) + prop_changed_event.AddEventHandler (control, new EventHandler (ControlPropertyChangedHandler)); + this.control = control; + UpdateIsBinding (); + } + + internal void Check () + { + if (control == null || control.BindingContext == null) + return; + + if (manager == null) { + manager = control.BindingContext [data_source, binding_member_info.BindingPath]; + + if (manager.Position > -1 && binding_member_info.BindingField != String.Empty && + TypeDescriptor.GetProperties (manager.Current).Find (binding_member_info.BindingField, true) == null) + throw new ArgumentException ("Cannot bind to property '" + binding_member_info.BindingField + "' on DataSource.", + "dataMember"); + + manager.AddBinding (this); + manager.PositionChanged += new EventHandler (PositionChangedHandler); + + if (manager is PropertyManager) { // Match .net, which only watchs simple objects + EventDescriptor prop_changed_event = GetPropertyChangedEvent (manager.Current, binding_member_info.BindingField); + if (prop_changed_event != null) + prop_changed_event.AddEventHandler (manager.Current, new EventHandler (SourcePropertyChangedHandler)); + } + } + + if (manager.Position == -1) + return; + + if (!checked_isnull) { + is_null_desc = TypeDescriptor.GetProperties (manager.Current).Find (property_name + "IsNull", false); + checked_isnull = true; + } + + PushData (); + } + + internal bool PullData () + { + return PullData (false); + } + + // Return false ONLY in case of error - and return true even in cases + // where no update was possible + bool PullData (bool force) + { + if (IsBinding == false || manager.Current == null) + return true; + if (!force && datasource_update_mode == DataSourceUpdateMode.Never) + return true; + + data = control_property.GetValue (control); + if (data == null) + data = datasource_null_value; + + try { + SetPropertyValue (data); + } catch (Exception e) { + if (formatting_enabled) { + FireBindingComplete (BindingCompleteContext.DataSourceUpdate, e, e.Message); + return false; + } + throw e; + } + + if (formatting_enabled) + FireBindingComplete (BindingCompleteContext.DataSourceUpdate, null, null); + return true; + } + + internal void PushData () + { + PushData (false); + } + + void PushData (bool force) + { + if (manager == null || manager.IsSuspended || manager.Count == 0 || manager.Position == -1) + return; + if (!force && control_update_mode == ControlUpdateMode.Never) + return; + + if (is_null_desc != null) { + bool is_null = (bool) is_null_desc.GetValue (manager.Current); + if (is_null) { + data = Convert.DBNull; + return; + } + } + + PropertyDescriptor pd = TypeDescriptor.GetProperties (manager.Current).Find (binding_member_info.BindingField, true); + if (pd == null) { + data = manager.Current; + } else { + data = pd.GetValue (manager.Current); + } + + if ((data == null || data == DBNull.Value) && null_value != null) + data = null_value; + + try { + data = FormatData (data); + SetControlValue (data); + } catch (Exception e) { + if (formatting_enabled) { + FireBindingComplete (BindingCompleteContext.ControlUpdate, e, e.Message); + return; + } + throw e; + } + + if (formatting_enabled) + FireBindingComplete (BindingCompleteContext.ControlUpdate, null, null); + } + + internal void UpdateIsBinding () + { + is_binding = false; + if (control == null || (control is Widget && !((Widget)control).IsHandleCreated)) + return; + + is_binding = true; + PushData (); + } + + private void SetControlValue (object data) + { + control_property.SetValue (control, data); + } + + private void SetPropertyValue (object data) + { + PropertyDescriptor pd = TypeDescriptor.GetProperties (manager.Current).Find (binding_member_info.BindingField, true); + if (pd.IsReadOnly) + return; + data = ParseData (data, pd.PropertyType); + pd.SetValue (manager.Current, data); + } + + private void ControlValidatingHandler (object sender, CancelEventArgs e) + { + if (datasource_update_mode != DataSourceUpdateMode.OnValidation) + return; + + bool ok = true; + // If the data doesn't seem to be valid (it can't be converted, + // is the wrong type, etc, we reset to the old data value. + // If Formatting is enabled, no exception is fired, but we get a false value + try { + ok = PullData (); + } catch { + ok = false; + } + + e.Cancel = !ok; + } + + private void ControlCreatedHandler (object o, EventArgs args) + { + UpdateIsBinding (); + } + + private void PositionChangedHandler (object sender, EventArgs e) + { + Check (); + PushData (); + } + + EventDescriptor GetPropertyChangedEvent (object o, string property_name) + { + if (o == null || property_name == null || property_name.Length == 0) + return null; + + string event_name = property_name + "Changed"; + Type event_handler_type = typeof (EventHandler); + + EventDescriptor prop_changed_event = null; + foreach (EventDescriptor event_desc in TypeDescriptor.GetEvents (o)) { + if (event_desc.Name == event_name && event_desc.EventType == event_handler_type) { + prop_changed_event = event_desc; + break; + } + } + + return prop_changed_event; + } + + void SourcePropertyChangedHandler (object o, EventArgs args) + { + PushData (); + } + + void ControlPropertyChangedHandler (object o, EventArgs args) + { + if (datasource_update_mode != DataSourceUpdateMode.OnPropertyChanged) + return; + + PullData (); + } + + private object ParseData (object data, Type data_type) + { + ConvertEventArgs e = new ConvertEventArgs (data, data_type); + + OnParse (e); + if (data_type.IsInstanceOfType (e.Value)) + return e.Value; + if (e.Value == Convert.DBNull) + return e.Value; + if (e.Value == null) { + bool nullable = data_type.IsGenericType && !data_type.ContainsGenericParameters && + data_type.GetGenericTypeDefinition () == typeof (Nullable<>); + return data_type.IsValueType && !nullable ? Convert.DBNull : null; + } + + return ConvertData (e.Value, data_type); + } + + private object FormatData (object data) + { + ConvertEventArgs e = new ConvertEventArgs (data, data_type); + + OnFormat (e); + if (data_type.IsInstanceOfType (e.Value)) + return e.Value; + + if (formatting_enabled) { + if ((e.Value == null || e.Value == Convert.DBNull) && null_value != null) + return null_value; + + if (e.Value is IFormattable && data_type == typeof (string)) { + IFormattable formattable = (IFormattable) e.Value; + return formattable.ToString (format_string, format_info); + } + } + + if (e.Value == null && data_type == typeof (object)) + return Convert.DBNull; + + return ConvertData (data, data_type); + } + + private object ConvertData (object data, Type data_type) + { + if (data == null) + return null; + + TypeConverter converter = TypeDescriptor.GetConverter (data.GetType ()); + if (converter != null && converter.CanConvertTo (data_type)) + return converter.ConvertTo (data, data_type); + + converter = TypeDescriptor.GetConverter (data_type); + if (converter != null && converter.CanConvertFrom (data.GetType())) + return converter.ConvertFrom (data); + + if (data is IConvertible) { + object res = Convert.ChangeType (data, data_type); + if (data_type.IsInstanceOfType (res)) + return res; + } + + return null; + } + void FireBindingComplete (BindingCompleteContext context, Exception exc, string error_message) + { + BindingCompleteEventArgs args = new BindingCompleteEventArgs (this, + exc == null ? BindingCompleteState.Success : BindingCompleteState.Exception, + context); + if (exc != null) { + args.SetException (exc); + args.SetErrorText (error_message); + } + + OnBindingComplete (args); + } + + #region Events + public event ConvertEventHandler Format; + public event ConvertEventHandler Parse; + public event BindingCompleteEventHandler BindingComplete; + #endregion // Events + } +} |
