【问题标题】:Can my binding source tell me if a change has occurred?我的绑定源能否告诉我是否发生了变化?
【发布时间】:2010-03-10 19:24:42
【问题描述】:

我有一个BindingSource,我在winforms 数据绑定中使用它,我希望在用户更改数据后尝试关闭表单时得到某种提示。类似于“您确定要退出而不保存更改吗?”

我知道我可以通过BindingSourceCurrencyManager.ItemChanged 事件来做到这一点,只需翻转一个“已更改”布尔值。

但是,我想要更强大的功能。 我想知道当前数据何时与原始数据不同。该事件只是告诉我是否发生了变化。用户仍然可以更改属性,点击撤消,我仍然会认为要保存的数据有更改。

我想模仿记事本的类似功能

  • 打开记事本
  • 输入一些东西
  • 删除所有内容(基本上撤消您所做的)
  • 关闭记事本,记事本关闭,没有提示保存更改,因为它知道结束状态 == 初始状态

如果这不可能,那么我应该使用上述ItemChanged 事件处理程序还是有更好的方法?

为了记录,我正在寻找类似

的东西
bool HasChanged()
{
    return this.currentState != this.initialState;
}

不是这个

bool HasChanged()
{
    // this._hasChanged is set to true via event handlers
    return this._hasChanged;
}

我宁愿不必自己管理当前状态和初始状态,我正在寻找一种从 BindingSource 获取该信息的方法,如果我可以从 BindingSource 获取此功能更理想,因为我将能够在许多不同的数据源上使用该功能,而不管类型等。

【问题讨论】:

  • +1 我觉得这个问题很有趣
  • 如果您将 DataTable/DataSet 视为绑定源,则每个 DataRow 可以有多个版本。使用 HasVersion 进行检查。您可以使用 myDataRow["FieldName", DataRowVersion.Original] 或 myDataRow.Field("FieldName", DataRowVersion.Current] 等访问各种版本中的值。

标签: c# winforms data-binding bindingsource


【解决方案1】:

您必须在您的对象类中实现INotifyPropertyChanged 接口,然后在您的DataSourceBindingSource 属性中通过您的类型类的适当事件处理程序来捕获任何发生更改。

提供您所需的一个对象是DataSet,它包含持久实体的原始和当前(更改)状态。然后,当取消时,您只需要调用Rollback() 方法即可。当接受更改时,调用AcceptChanges() 方法即可。

除了DataSet,也许考虑像NHibernate 这样的ORM 可以为您完成这项工作,并且允许您使用自定义对象,而不是DataSet。在您的表单中保持ISession API 处于活动状态将允许 ISession 跟踪您对任何对象所做的更改,只要 NHibernate 知道它。

实现INotifyPropertyChanged 接口的另一个解决方案是在属性设置器中,您可以将原始值存储在私有字段或对象的每个属性中。您可以简单地拥有一个带有 HasChanges 属性的抽象类,返回每个属性是否为其原始状态,然后相应地返回 true 或 false。

我有一个关于我们有趣的初步讨论的问题。我只想确定一件事。如果我们愿意,我们称之为语言障碍。但是通过INotifyPropertyChanged 接口发布PropertyChanged 事件也会以某种方式将对象“回滚”到其原始状态。您必须注意的唯一细节是,如果用户说他不想保留更改,则通过 BackgroundWorker 类从底层数据库重新加载此 CurrentItem 并完成!您的 GUI 没有滞后,您的用户已取消更改,并且您将对象重置为其默认/原始状态!

好吧,我想这里有足够的细节让你自己产生想法,再加上其他人提供的所有其他好的答案。我相信你会找到自己的方式来完成你想要的。

最好的成功! =)

【讨论】:

  • 这与使用 CurrencyManager.ItemChanged 翻转一点有何不同?它不会将原始状态与当前状态进行比较
  • 你的对象只应该知道它是否改变了。将它直接实现到您的对象中,您就不会费心使用 CurrencyManager。无论如何,我并没有真正理解 Brett 所说的“而不是翻转一点”是什么意思,尽管我想到了将快照与初始状态进行比较的想法。实际上,真正的问题是,您是否希望能够比较与您的初始状态相比是否有变化,或者您是否要发布有变化,因此您可以验证用户是否要保存更改还是在关闭表单时不?
  • Brett 的意思是,我不会做bool hasChanged() { return this._hasChanged; },而是做bool hasChanged() { return currentValues != initialValues; }
  • 感谢您点亮我的灯笼。 =)对于您的情况,这可能是更好的方法,我不能说,只有您知道。尽管如此,我还是经常使用建议的方法并且效果很好。采用 Brett 的方式意味着您必须在内存中有两组对象。但这很好,真的! =)
  • @Will,我在别处读到 BindingSource 已经这样做了,这就是为什么我犹豫要不要自己去实现它。我想知道它在哪里/如何保持当前和初始状态,所以我可以比较它们,而不是自己跟踪它
【解决方案2】:

Will 是对的,您应该实现INotifyPropertyChanged,最好与IDataInfoError 结合使用,以便为您的用户获取可见的信息。

要让您的对象在编辑时获得状态和通知,请尝试使用IEditableObject 接口。

WinForms 中默认使用所有三个接口,这有助于简化程序员的工作。

【讨论】:

  • 我可能遗漏了一些东西,但就像我说的,这与我在问题中概述的事件有什么不同。它仍然只是告诉我用户何时进行了更改,而不是将初始状态与当前状态进行比较。
  • @Allen:它是否在内部保持初始状态仅取决于您的实现。 IEditableObject 的参考实现展示了一种使您的对象可编辑和可撤消的简洁方法。
【解决方案3】:

您可以根据初始状态的快照检查状态,而不是稍微翻转一下。

【讨论】:

  • 是的,是的,这是我想做的,但我该怎么做呢?哈哈,我不想手动操作,currencymanagerbindingsource 肯定有这个内置的。或者它可能会跟踪初始和当前状态
【解决方案4】:

当您打开详细信息时,您可以对要修改的实体进行克隆

然后,当用户尝试关闭表单时,您可以将克隆(处于其原始状态的实体)与修改(或未修改)的实体进行比较。如果克隆和实体不相等,可以提示用户。

【讨论】:

  • 我认为 CurrencyManager 或 BindingSource 在内部已经这样做了。
  • 啊哈,是的......,它有所有的意义。我也会等待你的问题的答案。
【解决方案5】:

你可以滚动你自己的绑定源并实现它来做你想做的事情,这样你就不需要在每个表单上处理INotifyChange - 你只需让BindingSource 给你改变的元素 - 当@ 987654323@ 已更新 - 可绑定控件 .UpdateSourceTrigger 设置为 UpdateOnPropertyChanged。是即时的(几乎)。

这里有一些东西可以帮助您入门 - 几年前我在网上找到了它,我不记得代码的创始人,为了我的目的我稍微修改了它。

Imports System.ComponentModel.Design
Imports System.Windows.Forms
Imports System.ComponentModel

Public Class BindingSourceExIsDirty
    Inherits System.Windows.Forms.BindingSource
    Implements INotifyPropertyChanged

    #Region "DECLARATIONS AND PROPERTIES"

    Private _displayMember As String
    Private _dataTable As DataTable
    Private _dataSet As DataSet
    Private _parentBindingSource As BindingSource
    Private _form As System.Windows.Forms.Form
    Private _usercontrol As System.Windows.Forms.Control

    Private _isCurrentDirtyFlag As Boolean = False

    Public Property IsCurrentDirty() As Boolean
        Get
            Return _isCurrentDirtyFlag
        End Get
        Set(ByVal value As Boolean)
            If _isCurrentDirtyFlag <> value Then
                _isCurrentDirtyFlag = value
                Me.OnPropertyChanged(value.ToString())
                If value = True Then 'call the event when flag is set
                    OnCurrentIsDirty(New EventArgs)

                End If
            End If
        End Set
    End Property

    Private _objectSource As String

    Public Property ObjectSource() As String
        Get
            Return _objectSource
        End Get
        Set(ByVal value As String)
            _objectSource = value
            Me.OnPropertyChanged(value)
        End Set
    End Property   

'    Private _autoSaveFlag As Boolean
'
'    Public Property AutoSave() As Boolean
'        Get
'            Return _autoSaveFlag
'        End Get
'        Set(ByVal value As Boolean)
'           _autoSaveFlag = value
'           Me.OnPropertyChanged(value.ToString())
'        End Set
'    End Property  

    #End Region

    #Region "EVENTS"

    'Current Is Dirty Event
    Public Event CurrentIsDirty As CurrentIsDirtyEventHandler

    ' Delegate declaration.
    Public Delegate Sub CurrentIsDirtyEventHandler(ByVal sender As Object, ByVal e As EventArgs)

    Protected Overridable Sub OnCurrentIsDirty(ByVal e As EventArgs)
        RaiseEvent CurrentIsDirty(Me, e)
    End Sub

     'PropertyChanged Event 
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Overridable Sub OnPropertyChanged(ByVal info As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
    End Sub 

    #End Region

    #Region "METHODS"

    Private Sub _BindingComplete(ByVal sender As System.Object, ByVal e As System.Windows.Forms.BindingCompleteEventArgs) Handles Me.BindingComplete
        If e.BindingCompleteContext = BindingCompleteContext.DataSourceUpdate Then
            If e.BindingCompleteState = BindingCompleteState.Success And Not e.Binding.Control.BindingContext.IsReadOnly Then

                'Make sure the data source value is refreshed (fixes problem mousing off control)
                e.Binding.ReadValue()
                'if not focused then not a user edit.
                If Not e.Binding.Control.Focused Then Exit Sub

                'check for the lookup type of combobox that changes position instead of value
                If TryCast(e.Binding.Control, ComboBox) IsNot Nothing Then
                    'if the combo box has the same data member table as the binding source, ignore it
                    If CType(e.Binding.Control, ComboBox).DataSource IsNot Nothing Then
                        If TryCast(CType(e.Binding.Control, ComboBox).DataSource, BindingSource) IsNot Nothing Then
                            If CType(CType(e.Binding.Control, ComboBox).DataSource, BindingSource).DataMember = (Me.DataMember) Then
                                Exit Sub
                            End If

                        End If

                    End If
                End If
                IsCurrentDirty = True 'set the dirty flag because data was changed
            End If
        End If
    End Sub

    Private Sub _DataSourceChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.DataSourceChanged
        _parentBindingSource = Nothing
        If Me.DataSource Is Nothing Then
            _dataSet = Nothing
        Else
            'get a reference to the dataset
            Dim bsTest As BindingSource = Me
            Dim dsType As Type = bsTest.DataSource.GetType
            'try to cast the data source as a binding source
            Do While Not TryCast(bsTest.DataSource, BindingSource) Is Nothing
                'set the parent binding source reference
                If _parentBindingSource Is Nothing Then _parentBindingSource = bsTest
                'if cast was successful, walk up the chain until dataset is reached
                bsTest = CType(bsTest.DataSource, BindingSource)
            Loop
            'since it is no longer a binding source, it must be a dataset or something else
            If TryCast(bsTest.DataSource, DataSet) Is Nothing Then
                'Cast as dataset did not work

                If dsType.IsClass = False Then
                    Throw New ApplicationException("Invalid Binding Source ")
                Else
                    _dataSet = Nothing

                End If
            Else

                _dataSet = CType(bsTest.DataSource, DataSet)
            End If


            'is there a data member - find the datatable
            If Me.DataMember <> "" Then
                _DataMemberChanged(sender, e)
            End If 'CType(value.GetService(GetType(IDesignerHost)), IDesignerHost)
            If _form Is Nothing Then GetFormInstance()
            If _usercontrol Is Nothing Then GetUserControlInstance()
        End If
    End Sub

    Private Sub _DataMemberChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.DataMemberChanged
        If Me.DataMember = "" Or _dataSet Is Nothing Then
            _dataTable = Nothing
        Else
            'check to see if the Data Member is the name of a table in the dataset
            If _dataSet.Tables(Me.DataMember) Is Nothing Then
                'it must be a relationship instead of a table
                Dim rel As System.Data.DataRelation = _dataSet.Relations(Me.DataMember)
                If Not rel Is Nothing Then
                    _dataTable = rel.ChildTable
                Else
                    Throw New ApplicationException("Invalid Data Member")
                End If
            Else
                _dataTable = _dataSet.Tables(Me.DataMember)
            End If
        End If
    End Sub

    Public Overrides Property Site() As System.ComponentModel.ISite
        Get
            Return MyBase.Site
        End Get
        Set(ByVal value As System.ComponentModel.ISite)
            'runs at design time to initiate ContainerControl
            MyBase.Site = value
            If value Is Nothing Then Return
            ' Requests an IDesignerHost service using Component.Site.GetService()
            Dim service As IDesignerHost = CType(value.GetService(GetType(IDesignerHost)), IDesignerHost)
            If service Is Nothing Then Return
            If Not TryCast(service.RootComponent, Form) Is Nothing Then
                _form = CType(service.RootComponent, Form)
            ElseIf Not TryCast(service.RootComponent, UserControl) Is Nothing Then
                _usercontrol = CType(service.RootComponent, UserControl)
            End If

        End Set
    End Property

    Public Function GetFormInstance() As System.Windows.Forms.Form
        If _form Is Nothing And Me.CurrencyManager.Bindings.Count > 0 Then
            _form = Me.CurrencyManager.Bindings(0).Control.FindForm()

        End If
        Return _form
    End Function

    ''' <summary>
    ''' Returns the First Instance of the specified User Control
    ''' </summary>
    ''' <returns>System.Windows.Forms.Control</returns>
    Public Function GetUserControlInstance() As System.Windows.Forms.Control
        If _usercontrol Is Nothing And Me.CurrencyManager.Bindings.Count > 0 Then
            Dim _uControls() As System.Windows.Forms.Control
            _uControls = Me.CurrencyManager.Bindings(0).Control.FindForm.Controls.Find(Me.Site.Name.ToString(), True)
            _usercontrol = _uControls(0)

        End If
        Return _usercontrol
    End Function

    '============================================================================

    'Private Sub _PositionChanged(ByVal sender As Object, ByVal e As EventArgs) Handles Me.PositionChanged

    '    If IsCurrentDirty Then
    '        If AutoSave Then  ' IsAutoSavingEvent
    '            Try
    '                'cast table as ITableUpdate to get the Update method
    '                '  CType(_dataTable, ITableUpdate).Update()
    '            Catch ex As Exception
    '               ' - needs to raise an event 
    '            End Try
    '        Else
    '            Me.CancelEdit()
    '            _dataTable.RejectChanges()
    '        End If
    '        IsCurrentDirty = False
    '    End If
    'End Sub

    #End Region

End Class

【讨论】:

    【解决方案6】:

    是的,但涉及一些工作。我知道,这是一个迟到的答案,但我最近问了自己同样的问题,并想出了以下解决方案,我将其纳入课堂 UpdateManager。到目前为止,我只考虑绑定到单个对象。

    这适用于普通 POCO 对象。 不需要实现INotifyPropertyChanged;但是,它仅在通过 UI 进行更改时才有效。未检测到通过业务对象中的代码所做的更改。但这在大多数情况下足以检测对象是脏还是处于保存状态。

    public class UpdateManager
    {
        public event EventHandler DirtyChanged;
    
        private readonly BindingSource _bindingSource;
    
        // Stores original and current values of all bindings.
        private readonly Dictionary<string, (object original, object current)> _values =
            new Dictionary<string, (object original, object current)>();
    
        public UpdateManager(BindingSource bindingSource)
        {
            _bindingSource = bindingSource;
            bindingSource.CurrencyManager.Bindings.CollectionChanged += Bindings_CollectionChanged;
            bindingSource.BindingComplete += BindingSource_BindingComplete;
        }
    
        private bool _dirty;
        public bool Dirty
        {
            get {
                return _dirty;
            }
            set {
                if (value != _dirty) {
                    _dirty = value;
                    DirtyChanged?.Invoke(this, EventArgs.Empty);
                }
            }
        }
    
        private void Bindings_CollectionChanged(object sender, CollectionChangeEventArgs e)
        {
            // Initialize the values information for the binding.
            if (e.Element is Binding binding && GetCurrentValue(binding, out object value)) {
                _values[binding.BindingMemberInfo.BindingField] = (value, value);
            }
        }
    
        private void BindingSource_BindingComplete(object sender, BindingCompleteEventArgs e)
        {
            if (e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate &&
                e.BindingCompleteState == BindingCompleteState.Success) {
    
                UpdateDirty(e.Binding);
            }
        }
    
        private void UpdateDirty(Binding binding)
        {
            if (GetCurrentValue(binding, out object currentValue)) {
                string propertyName = binding.BindingMemberInfo.BindingField;
                var valueInfo = _values[propertyName];
                _values[propertyName] = (valueInfo.original, currentValue);
                if (Object.Equals(valueInfo.original, currentValue)) {
                    Dirty = _values.Any(kvp => !Object.Equals(kvp.Value.original, kvp.Value.current));
                } else {
                    Dirty = true;
                }
            }
        }
    
        private bool GetCurrentValue(Binding binding, out object value)
        {
            object model = binding.BindingManagerBase?.Current;
            if (model != null) {
                // Get current value in business object (model) with Reflection.
                Type modelType = model.GetType();
                string propertyName = binding.BindingMemberInfo.BindingField;
                PropertyInfo modelProp = modelType.GetProperty(propertyName);
                value = modelProp.GetValue(model);
                return true;
            }
            value = null;
            return false;
        }
    }
    

    我使用的形式是这样的:

    private UpdateManager _updateManager;
    private Person _person = new Person();
    
    public frmBindingNotification()
    {
        InitializeComponent();
        _updateManager = new UpdateManager(personBindingSource);
        _updateManager.DirtyChanged += UpdateManager_DirtyChanged;
        personBindingSource.DataSource = _person; // Assign the current business object.
    }
    
    private void UpdateManager_DirtyChanged(object sender, EventArgs e)
    {
        Console.WriteLine(_updateManager.Dirty ? "Dirty" : "Saved"); // Testing only.
    }
    

    每当Dirty 状态发生变化时,都会在“输出”窗口中打印“脏”或“已保存”。

    【讨论】:

      猜你喜欢
      • 2021-04-10
      • 2023-03-12
      • 1970-01-01
      • 2013-12-23
      • 1970-01-01
      • 1970-01-01
      • 2011-03-19
      • 2013-11-05
      • 1970-01-01
      相关资源
      最近更新 更多