我的绑定源可以告诉我是否发生了更改吗?

发布于 2024-08-24 19:48:15 字数 1203 浏览 7 评论 0原文

我有一个 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 获取此功能,则更加理想,因为我将能够在许多不同的数据源上使用该功能,无论类型如何,等等。

I have a BindingSource that I'm using in winforms data binding and I'd like to have some sort of prompt for when the user attempts to close the form after they've made changes to the data. A sort of "Are you sure you want to exit without saving changes?"

I'm aware that I can do this via the BindingSource's CurrencyManager.ItemChanged event by just flipping a "has changed" boolean.

However, I want a more robust functionality. I'd like to know when the current data is different from the original data. The event just tells me if somethings changed. A user could still change a property, hit undo, and I would still think that there is a change in the data to save.

I want to mimic this similar functionality of notepad

  • open notepad
  • type something
  • delete everything (essentially undoing what you did)
  • close notepad, notepad closes, no prompt to save changes because it knows the end state == the initial state

If this is not possible, then should I go with the ItemChanged event handler as outlined above or is there a better way?

For the record, I'm looking for something along the lines of

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

not this

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

I'd just rather not have to manage the current state and the initial state myself, I'm looking for a way to grab that info from the BindingSource If I can get this functionality from the BindingSource its way more ideal since I will be able to use the functionality on many different data sources, regardless of type, etc.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

仅此而已 2024-08-31 19:48:15

您必须从对象类中实现 INotifyPropertyChanged 接口,然后通过 DataSource BindingSource 中的类型类的正确事件处理程序捕获发生的更改 属性。

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

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

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

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

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

祝您成功! =)

You'll have to implement the INotifyPropertyChanged interface from within your object classes, then catch whenever a change occurs through proper event handlers for your type class within your DataSource BindingSource property.

The one object offering what you require is the DataSet, containing both the Original and Current (changed) state of an persistent entity. Then, when one cancels, all you need to call is the Rollback() method. When one accepts the changes, then a call to the AcceptChanges() method will do.

Besides the DataSet, perhaps considering an ORM like NHibernate will do the job for you, plus allowing you to use custom defined objects, instead of a DataSet. Keeping the ISession API alive while in your form will allow the ISession to keep track of your changes whatever it may be to whatever object it is, as long as it is know by NHibernate.

Another solution implementing the INotifyPropertyChanged interface, is at the property setter, you could stock the Original value within a private field or for every property of an object. You could simple have an abstract class with the HasChanges property return whether each property is as its Original state, then return true or false accordingly.

I have a question regarding our interesting initial discussion. I just want to make sure of one thing. Let's call it language barrier if we like. But publishing the PropertyChanged event through the INotifyPropertyChanged interface will also somehow "rollback" an object to its original state. The only detail you had to take care is that if the user says he doesn't want to keep the changes, then reload this CurrentItem from the underlying database via the BackgroundWorker class and its done! No lagging from your GUI, your user has canceled the changes, and you resetted the object to its default/original state!

Well, I guess here's enough details to make yourself an idea, plus all of the other good answers provided by the others. I am confident you will find your way to accomplish what you want.

Best of success! =)

颜漓半夏 2024-08-31 19:48:15

威尔是对的,您应该实施 < code>INotifyPropertyChanged,最好与 IDataInfoError 为您的用户获取可见信息。

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

WinForms 默认使用所有三个接口,有助于使程序员的工作更轻松。

Will is right, you should implement INotifyPropertyChanged, ideally in conjunction with IDataInfoError to get visisble information for your users.

For your Objects to get a state and a notification on Editing, try using the IEditableObject interface.

All three interfaces are used by default from WinForms and help make the programmers life easier.

九局 2024-08-31 19:48:15

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

Instead of flipping a bit, you could check the state against a snapshot of your initial state.

回忆躺在深渊里 2024-08-31 19:48:15

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

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

When you open your detail, you could make a clone of the entity that you are going to modify.

Then, when the user attempts to close the form, you could compare the clone (the entity in its original state) with the modified (or not) entity. If the clone and the entity are not equals, you could prompt the user.

浮生面具三千个 2024-08-31 19:48:15

您可以推出自己的绑定源并实现它来执行您想要的操作,这样您就不需要在每个表单上处理 INotifyChange - 您只需让 BindingSource 为您提供更改后的内容元素 - 当 BindingSource 更新时,此功能有效 - 可绑定控件 .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

You could roll your own binding source and implement it to do what you want that way you do not need INotifyChange handling on every form - you just let the BindingSource give you the changed element - this works when the BindingSource is updated - bindable control .UpdateSourceTrigger is set to UpdateOnPropertyChanged. is instant(well almost).

Here is something to get you started - I found it on the net years ago I do not remember the originator of the code , I have modified it slightly for my purpose.

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
戏剧牡丹亭 2024-08-31 19:48:15

是的,但是需要一些工作。我知道,这是一个迟到的答案,但我最近问了自己同样的问题,并提出了以下解决方案,我将其包装到类 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 状态发生变化时,都会在输出窗口中打印“Dirty”或“Saved”。

Yes, but there is some work involved. I know, it's a late answer, but I asked myself the same question resecently and came up with the following soltion that I wrapped up into the class UpdateManager. I only accounted for binding to a single object so far.

This works with plain POCO objects. Implementing INotifyPropertyChanged is not required; however, it works only if the changes are made through the UI. Changes made through code in the business object are not detected. But this enough in most cases to detect whether the object is dirty or in a saved state.

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;
    }
}

In the form I used it like this:

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.
}

Whenever the Dirty status changes, this prints either "Dirty" or "Saved" in the Output window.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文