Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

578 lines
15 KiB
C#

// 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 pbartok@novell.com
// Jackson Harper jackson@ximian.com
//
using System.ComponentModel;
namespace System.Windows.Forms {
[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 Control Control {
get {
return control as Control;
}
}
[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 SetControl (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
Control ctrl = control as Control;
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 Control && !((Control)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
}
}