| File: System\Windows\Forms\DataBinding\Binding.BindToObject.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; namespace System.Windows.Forms; public partial class Binding { private class BindToObject { private BindingManagerBase? _bindingManager; private readonly Binding _owner; private bool _dataSourceInitialized; private bool _waitingOnDataSource; private void PropValueChanged(object? sender, EventArgs e) { _bindingManager?.OnCurrentChanged(EventArgs.Empty); } private bool IsDataSourceInitialized { get { Debug.Assert(_owner.DataSource is not null, "how can we determine if DataSource is initialized or not if we have no data source?"); if (_dataSourceInitialized) { return true; } if (_owner.DataSource is not ISupportInitializeNotification ds || ds.IsInitialized) { _dataSourceInitialized = true; return true; } // We have an ISupportInitializeNotification which was not initialized yet. // We already hooked up the Initialized event and the data source is not initialized yet. if (_waitingOnDataSource) { return false; } // Hook up the Initialized event. ds.Initialized += DataSource_Initialized; _waitingOnDataSource = true; return false; } } internal BindToObject(Binding owner) { Debug.Assert(owner is not null); _owner = owner; CheckBinding(); } private void DataSource_Initialized(object? sender, EventArgs e) { Debug.Assert(sender == _owner.DataSource, "data source should not change"); Debug.Assert(_owner.DataSource is ISupportInitializeNotification, "data source should not change on the BindToObject"); Debug.Assert(_waitingOnDataSource); // Unhook the Initialized event. if (_owner.DataSource is ISupportInitializeNotification ds) { ds.Initialized -= DataSource_Initialized; } // The wait is over: DataSource is initialized. _waitingOnDataSource = false; _dataSourceInitialized = true; // Rebind. CheckBinding(); } internal void SetBindingManagerBase(BindingManagerBase? lManager) { if (_bindingManager == lManager) { return; } // remove notification from the backEnd if (_bindingManager is not null && FieldInfo is not null && _bindingManager.IsBinding && _bindingManager is not CurrencyManager) { FieldInfo.RemoveValueChanged(_bindingManager.Current!, PropValueChanged); FieldInfo = null; } _bindingManager = lManager; CheckBinding(); } internal string DataErrorText { get; private set; } = string.Empty; /// <summary> /// Returns any data error info on the data source for the bound data field /// in the current row /// </summary> private string GetErrorText(object? value) { if (value is not IDataErrorInfo errorInfo) { return string.Empty; } // Get the row error if there is no DataMember if (FieldInfo is null) { return errorInfo.Error ?? string.Empty; } // Get the column error if there is a DataMember. // The DataTable uses its own Locale to lookup column names <sigh>. // So passing the DataMember from the BindingField could cause problems. // Pass the name from the PropertyDescriptor that the DataTable gave us. // (If there is no fieldInfo, data binding would have failed already ) return errorInfo[FieldInfo.Name] ?? string.Empty; } internal object? GetValue() { object? obj = _bindingManager?.Current; // Update IDataErrorInfo text: it's ok to get this now because we're going to need // this as part of the BindingCompleteEventArgs anyway. DataErrorText = GetErrorText(obj); if (FieldInfo is not null) { obj = FieldInfo.GetValue(obj); } return obj; } internal Type? BindToType { get { if (_owner.BindingMemberInfo.BindingField.Length == 0) { // if we are bound to a list w/o any properties, then // take the type from the BindingManager Type? type = _bindingManager?.BindType; if (typeof(Array).IsAssignableFrom(type)) { type = type.GetElementType(); } return type; } return FieldInfo?.PropertyType; } } internal void SetValue(object? value) { object? obj = null; if (FieldInfo is not null) { obj = _bindingManager?.Current; if (obj is IEditableObject editableObject) { editableObject.BeginEdit(); } if (!FieldInfo.IsReadOnly) { FieldInfo.SetValue(obj, value); } } else { if (_bindingManager is CurrencyManager cm) { cm[cm.Position] = value; obj = value; } } // Update IDataErrorInfo text. DataErrorText = GetErrorText(obj); } internal PropertyDescriptor? FieldInfo { get; private set; } internal void CheckBinding() { // At design time, don't check anything. if (_owner.BindableComponent is not null && _owner.ControlAtDesignTime()) { return; } // Remove propertyChangedNotification when this binding is deleted if (_bindingManager is not null && FieldInfo is not null && _bindingManager.IsBinding && _bindingManager is not CurrencyManager) { FieldInfo.RemoveValueChanged(_bindingManager.Current!, PropValueChanged); } if (_bindingManager is not null && _owner.BindableComponent is not null && _owner.ComponentCreated && IsDataSourceInitialized) { string dataField = _owner.BindingMemberInfo.BindingField; FieldInfo = _bindingManager.GetItemProperties().Find(dataField, true); if (_bindingManager.DataSource is not null && FieldInfo is null && dataField.Length > 0) { throw new ArgumentException(string.Format(SR.ListBindingBindField, dataField), "dataMember"); } // Do not add propertyChange notification if the fieldInfo is null // // We add an event handler to the dataSource in the BindingManagerBase because // if the binding is of the form (Control, ControlProperty, DataSource, Property1.Property2.Property3) // then we want to get notification from Current.Property1.Property2 and not from DataSource // when we get the backEnd notification we push the new value into the Control's property if (FieldInfo is not null && _bindingManager.IsBinding && _bindingManager is not CurrencyManager) { FieldInfo.AddValueChanged(_bindingManager.Current!, PropValueChanged); } } else { FieldInfo = null; } } } }