Может ли мой источник привязки сообщить мне, произошло ли изменение? - PullRequest
16 голосов
/ 10 марта 2010

У меня есть BindingSource, который я использую в привязке данных winforms, и я хотел бы получить какую-то подсказку, когда пользователь пытается закрыть форму после внесения изменений к данным. Этакий «Вы уверены, что хотите выйти без сохранения изменений?»

Я знаю, что я могу сделать это с помощью события BindingSource s CurrencyManager.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 его способом более идеальным, так как я смогу использовать эту функциональность на разных источниках данных независимо от их типа и т. д.

Ответы [ 6 ]

4 голосов
/ 10 марта 2010

Воля верна, вы должны реализовать INotifyPropertyChanged, в идеале в сочетании с IDataInfoError, чтобы получить визуальную информацию для ваших пользователей.

Чтобы ваши Объекты могли получить состояние и уведомление об изменении, попробуйте использовать интерфейс IEditableObject.

Все три интерфейса по умолчанию используются в WinForms и помогают облегчить жизнь программистам.

4 голосов
/ 10 марта 2010

Вам придется реализовать интерфейс INotifyPropertyChanged из ваших классов объектов, а затем перехватывать всякий раз, когда происходит изменение, через соответствующие обработчики событий для вашего класса типов в вашем свойстве DataSource BindingSource.

Единственный объект, предлагающий то, что вам требуется, это DataSet, содержащий как исходное, так и текущее (измененное) состояние постоянного объекта.Затем, когда кто-то отменяет, все, что вам нужно для вызова, - это метод Rollback().Когда кто-то принимает изменения, тогда вызовет метод AcceptChanges().

Помимо DataSet, возможно, стоит рассмотреть возможность использования ORM, такого как NHibernate, плюс возможность использовать пользовательскиеобъекты, вместо DataSet.Поддержание в рабочем состоянии API ISession в вашей форме позволит ISession отслеживать ваши изменения, какими бы они ни были к любому объекту, если это известно NHibernate.

Другое решение, реализующееИнтерфейс INotifyPropertyChanged находится в установщике свойств, вы можете хранить оригинальное значение в закрытом поле или для каждого свойства объекта.Вы можете просто иметь абстрактный класс со свойством HasChanges, возвращающим, является ли каждое свойство его исходным состоянием, а затем возвращать true или false соответственно.

У меня есть вопрос относительно нашего интересного начального обсуждения.Я просто хочу убедиться в одном.Давайте назовем это языковым барьером, если захотим.Но публикация события PropertyChanged через интерфейс INotifyPropertyChanged также каким-то образом «откатит» объект до его исходного состояния.Единственная деталь, о которой вам нужно было позаботиться, это то, что если пользователь говорит, что не хочет сохранять изменения, перезагрузите этот CurrentItem из базовой базы данных через класс BackgroundWorker и все готово!Не отставая от вашего графического интерфейса, ваш пользователь отменил изменения, и вы вернули объект в исходное / исходное состояние!

Что ж, я думаю, здесь достаточно деталей, чтобы составить идею, а также все остальные хорошиеответы, предоставленные другими.Я уверен, что вы найдете способ добиться того, чего хотите.

Лучший успех!=) * * Тысяча двадцать-семь

1 голос
/ 10 марта 2010

Когда вы откроете свою деталь, вы можете сделать клон сущности, которую вы собираетесь изменить.

Затем, когда пользователь пытается закрыть форму, вы можете сравнить клон (сущность в ее исходном состоянии) с измененной (или нет) сущностью. Если клон и сущность не равны, вы можете предложить пользователю.

1 голос
/ 10 марта 2010

Вместо того, чтобы слегка перевернуть изображение, вы можете проверить состояние по снимку вашего исходного состояния.

0 голосов
/ 07 ноября 2018

Да , но есть работа. Я знаю, это поздний ответ, но я задал себе тот же вопрос недавно и придумал следующее решение, которое я включил в класс UpdateManager. До сих пор я учитывал только привязку к одному объекту.

Это работает с простыми объектами POCO. Реализация INotifyPropertyChanged - это , а не ; однако, это работает, только если изменения сделаны через пользовательский интерфейс. Изменения, внесенные с помощью кода в бизнес-объект, не обнаруживаются. Но этого в большинстве случаев достаточно, чтобы определить, является ли объект грязным или в сохраненном состоянии.

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, в окне «Вывод» печатается «Грязный» или «Сохраненный».

0 голосов
/ 03 марта 2015

Вы можете свернуть свой собственный источник привязки и реализовать его так, чтобы делать то, что вам нужно, без необходимости обрабатывать 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
...