aboutsummaryrefslogtreecommitdiff
path: root/source/ShiftUI/Internal/Binding.cs
diff options
context:
space:
mode:
Diffstat (limited to 'source/ShiftUI/Internal/Binding.cs')
-rw-r--r--source/ShiftUI/Internal/Binding.cs578
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
+ }
+}