Как узнать, изменил ли пользователь данные с помощью источника привязки?

У меня есть DataGridView, привязанный к источнику привязки, который привязан к List<T>. Пользователь щелкает строку, которая переходит в форму с текстовыми полями и т. д. Текстовые поля привязаны к данным следующим образом:

if (txtID.DataBindings.Count == 0)
    txtID.DataBindings.Add("Text", bindingSource, "Title");

Я хочу иметь возможность определить, изменил ли пользователь какие-либо данные в элементах управления, когда они нажимают кнопку закрытия, чтобы я мог предложить им сказать: «У вас есть несохраненная работа. Вы хотите сохранить?»

Как обнаружить это в источнике привязки?

ОБНОВЛЕНИЕ: я понял, что могу сделать bindingSource.EndEdit(), который подтолкнет изменения к моему элементу в списке. В моем элементе я могу сказать, если Dirty выбрасывает окно сообщений, но если они нажимают «Нет» для сохранения информации, CancelEdit не работает.


person Jon    schedule 05.02.2010    source источник


Ответы (12)


Если ваш объект в списке поддерживает событие INotifyPropertyChanged и вы замените List<T> на BindingList<T> вы можете подписаться на ListChanged события BindingList, чтобы получать информацию о любых изменениях, внесенных пользователем.

person Oliver    schedule 28.04.2011
comment
Привет, Оливер, старый пост ... но у тебя есть пример? Я спрашиваю здесь. stackoverflow.com/questions/25820078/ - person Kirsten Greed; 13.09.2014

Если вы привязаны к набору данных, вам повезло: у него есть ссылка Имеет изменения Свойство. Вы можете получить фактические изменения, вызвав GetChanges для набора данных. Это возвращает новый набор данных, содержащий копию всех измененных строк.

person Matt Jacobsen    schedule 05.02.2010
comment
Источником данных привязки является список‹T›. - person Jon; 05.02.2010
comment
Вы не можете смотреть коллекции. Альтернативой может быть набор событий TextChanged, которые обновляют частную переменную Changed, которую вы проверяете перед сохранением. - person Matt Jacobsen; 05.02.2010
comment
у меня была такая мысль, но это противно - person Jon; 05.02.2010
comment
Это! Об использовании DataSet/DataTable не может быть и речи? - person Matt Jacobsen; 05.02.2010
comment
Я так думаю, потому что список содержит List‹MyClass›, который имеет все свойства и т. д. редактируемого/добавляемого элемента. - person Jon; 05.02.2010
comment
Вы все еще можете это сделать: эти свойства в MyClass просто нужно сопоставить с DataColumns в вашей новой DataTable. Зависит от вашей ситуации, насколько это неприглядно. - person Matt Jacobsen; 05.02.2010
comment
Или вы можете следить за изменениями в MyClass. Это немного красивее, чем куча событий TextChanged в форме. - person Matt Jacobsen; 05.02.2010
comment
Как я могу контролировать свой класс, поскольку его свойства не изменятся, пока вы не вызовете метод MyClass.Save. Я думал, что привязка данных обеспечит мне мониторинг, но это не так. - person Jon; 05.02.2010

Я сделал эту функцию сейчас. Вы можете использовать как:

if (changedOrNew(myBindingSource)){
    // Do something!
}

public bool changedOrNew(BindingSource bs){
    EntityObject obj = (EntityObject)bs.Current;
    if (obj==null)
        return false;
    return (obj.EntityState == EntityState.Detached ||
            obj.EntityState == EntityState.Added ||
            obj.EntityState == EntityState.Modified);
}
person Tiago Gouvêa    schedule 21.11.2012
comment
Бесполезно, если DataSource BindingSource не является «EntityObject», который, как я предполагаю, является старой версией базового класса Entity Framework? Кроме того, почему вы передаете параметр «bs», который никогда не используется? - person Christopher King; 20.12.2013
comment
Вы правы, @ChristopherKing, я исправил параметр bs, теперь он будет использоваться в первой строке .. спасибо. EntityObject не является старой версией, все мои сущности наследуют класс EntityObject, даже в .Net 4.5. - person Tiago Gouvêa; 21.12.2013

Попробовав разные вещи, я получил этот фрагмент кода:

private MyClass currentItem = null;
private bool itemDirty = false; // can be used for "do you want to save?"

private void bindingSource_CurrentChanged(object sender, EventArgs e)
{
    var handler = new PropertyChangedEventHandler((s, e2) => itemDirty = true);

    var crnt = currentItem as INotifyPropertyChanged;
    if(crnt != null) crnt.PropertyChanged -= handler;

    currentItem = (MyClass)bindingSource.Current;

    crnt = currentItem as INotifyPropertyChanged;
    if(crnt != null) crnt.PropertyChanged += handler;

    itemDirty = false;
}

У меня это работает нормально, хотя я сохраняю много информации о состоянии в полях экземпляра формы Windows. Однако возня с CurrentChanged и CurrentItemChanged мне не помогла.

person Matthias Meid    schedule 28.04.2011

Более простой подход — подписаться на событие ListChanged BindingSource и установить флаг IsDirty на основе типа события.

categoryBindingSource.ListChanged += 
new System.ComponentModel.ListChangedEventHandler(categoryBindingSource_ListChanged);

и установите IsDirty = true в методе события...

void customerAccountBindingSource_ListChanged(object sender, system.ComponentModel.ListChangedEventArgs e)
{
    if (e.ListChangedType == System.ComponentModel.ListChangedType.ItemChanged)
        _isDirty = true;
}

Предостережение здесь: он не сможет определить, когда измененное значение остается таким же, как исходное значение. Memberwise.Clone можно использовать дополнительно, если требуется такой уровень точности.

person prabhats.net    schedule 17.07.2015

Я установил довольно простой механизм, а именно:

  1. После привязки элементов управления я запускаю метод, который находит все связанные элементы управления и сохраняет их текущие значения (я выполняю ReadValue(), чтобы убедиться, что я получил значения из DataSource). ) в Dictionary, который сопоставляет элемент управления с его значением (есть небольшой метод, который получает соответствующее значение для каждого типа элемента управления, который у меня есть).
  2. Я также добавляю обработчик события изменения для каждого из них (опять же, конкретное событие определяется типом элемента управления, но все они указывают на один и тот же обработчик).
  3. Обработчик изменений сравнивает текущее значение со значением Dictionary. Если он отличается, он действует соответствующим образом (в моем случае он переключает кнопку Закрыть на кнопку Отмена). Если это то же самое, он проверяет все другие связанные элементы управления, так что, если ничего не отличается, он может переключить Отмена обратно на Закрыть; хорошая особенность этого метода заключается в том, что он также распознает, когда изменение было отменено, даже если это произошло путем повторного ввода исходного значения.
  4. Перед уходом, если есть изменения, которые нужно сохранить, я снова перебираю связанные элементы управления, чтобы выполнить WriteValue(), на случай, если WinForms не успеет распространить какое-то изменение. .

Могу поделиться источником, если кому интересно.

person Michael    schedule 16.01.2014

Если ваш источник привязки использует таблицу данных, вы можете сделать это:

    public bool HasChanges()
    {
        bool Result = false;

        myBindingSource.EndEdit();
        Result = ((DataTable)myBindingSource.DataSource).GetChanges(DataRowState.Modified) != null;


        return Result;
    }
person GuidoG    schedule 10.04.2015

Я знаю, что это старый пост, но вот расширенный BindingSource с IsDirtyFlag — вы можете адаптировать его по своему усмотрению — я вытащил этот код из другого поста где-то в сети несколько лет назад — сделал несколько очень незначительных изменений, я думаю, что он был изначально в VB - я перешел на C#..

using System.ComponentModel.Design;
using System.Windows.Forms;
using System.ComponentModel;
using System.Data;
using System;


public class BindingSourceExIsDirty : System.Windows.Forms.BindingSource, INotifyPropertyChanged
{

    #region "DECLARATIONS AND PROPERTIES"

    private string _displayMember;
    private DataTable _dataTable;
    private DataSet _dataSet;
    private BindingSource _parentBindingSource;
    private System.Windows.Forms.Form _form;
    private System.Windows.Forms.Control _usercontrol;




    private bool _isCurrentDirtyFlag = false;
    public bool IsCurrentDirty {
        get { return _isCurrentDirtyFlag; }
        set {
            if (_isCurrentDirtyFlag != value) {
                _isCurrentDirtyFlag = value;
                this.OnPropertyChanged(value.ToString());
                //call the event when flag is set
                if (value == true) {
                    OnCurrentIsDirty(new EventArgs());

                }
            }
        }
    }


    private string _objectSource;
    public string ObjectSource {
        get { return _objectSource; }
        set {
            _objectSource = value;
            this.OnPropertyChanged(value);
        }
    }


private bool _autoSaveFlag;

public bool AutoSave {
    get { return _autoSaveFlag; }
    set {
        _autoSaveFlag = value;
        this.OnPropertyChanged(value.ToString());
    }
} 

    #endregion

    #region "EVENTS"


    //Current Is Dirty Event
    public event CurrentIsDirtyEventHandler CurrentIsDirty;

    // Delegate declaration.
    public delegate void CurrentIsDirtyEventHandler(object sender, EventArgs e);

    protected virtual void OnCurrentIsDirty(EventArgs e)
    {
        if (CurrentIsDirty != null) {
            CurrentIsDirty(this, e);
        }
    }

    //PropertyChanged Event 
//  public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string info)
    {

        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(info));
        }       

    }



    #endregion

    #region "METHODS"



    private void _BindingComplete(System.Object sender, System.Windows.Forms.BindingCompleteEventArgs e)
    {

        if (e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate) {

            if (e.BindingCompleteState == BindingCompleteState.Success & !e.Binding.Control.BindingContext.IsReadOnly) {
                //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 (!e.Binding.Control.Focused)
                    return;

                //check for the lookup type of combobox that changes position instead of value
                if (e.Binding.Control as ComboBox != null) {
                    //if the combo box has the same data member table as the binding source, ignore it
                    if (((ComboBox)e.Binding.Control).DataSource != null) {
                        if (((ComboBox)e.Binding.Control).DataSource as BindingSource != null) {
                            if (((BindingSource)((ComboBox)e.Binding.Control).DataSource).DataMember == (this.DataMember)) {
                                return;
                            }

                        }

                    }
                }
                IsCurrentDirty = true;
                //set the dirty flag because data was changed
            }
        }



    }

    private void _DataSourceChanged(System.Object sender, System.EventArgs e)
    {
        _parentBindingSource = null;
        if (this.DataSource == null) {
            _dataSet = null;
        } else {
            //get a reference to the dataset
            BindingSource bsTest = this;
            Type dsType = bsTest.DataSource.GetType();
            //try to cast the data source as a binding source
            while ((bsTest.DataSource as BindingSource != null)) {
                //set the parent binding source reference
                if (_parentBindingSource == null)
                    _parentBindingSource = bsTest;
                //if cast was successful, walk up the chain until dataset is reached
                bsTest = (BindingSource)bsTest.DataSource;
            }
            //since it is no longer a binding source, it must be a dataset or something else
            if (bsTest.DataSource as DataSet == null) {
                //Cast as dataset did not work

                if (dsType.IsClass == false) {
                    throw new ApplicationException("Invalid Binding Source ");
                } else {
                    _dataSet = null;

                }

            } else {
                _dataSet = (DataSet)bsTest.DataSource;
            }


            //is there a data member - find the datatable
            if (!string.IsNullOrEmpty(this.DataMember)) {
                _DataMemberChanged(sender, e);
            }
            //CType(value.GetService(GetType(IDesignerHost)), IDesignerHost)
            if (_form == null)
                GetFormInstance();
            if (_usercontrol == null)
                GetUserControlInstance();
        }
    }

    private void _DataMemberChanged(System.Object sender, System.EventArgs e)
    {
        if (string.IsNullOrEmpty(this.DataMember) | _dataSet == null) {
            _dataTable = null;
        } else {
            //check to see if the Data Member is the name of a table in the dataset
            if (_dataSet.Tables(this.DataMember) == null) {
                //it must be a relationship instead of a table
                System.Data.DataRelation rel = _dataSet.Relations(this.DataMember);
                if ((rel != null)) {
                    _dataTable = rel.ChildTable;
                } else {
                    throw new ApplicationException("Invalid Data Member");
                }
            } else {
                _dataTable = _dataSet.Tables(this.DataMember);
            }
        }
    }

    public override System.ComponentModel.ISite Site {
        get { return base.Site; }
        set {
            //runs at design time to initiate ContainerControl
            base.Site = value;
            if (value == null)
                return;
            // Requests an IDesignerHost service using Component.Site.GetService()
            IDesignerHost service = (IDesignerHost)value.GetService(typeof(IDesignerHost));
            if (service == null)
                return;
            if ((service.RootComponent as Form != null)) {
                _form = (Form)service.RootComponent;
            } else if ((service.RootComponent as UserControl != null)) {
                _usercontrol = (UserControl)service.RootComponent;
            }

        }
    }

    public System.Windows.Forms.Form GetFormInstance()
    {
        if (_form == null & this.CurrencyManager.Bindings.Count > 0) {
            _form = this.CurrencyManager.Bindings[0].Control.FindForm();

        }
        return _form;
    }

    /// <summary>
    /// Returns the First Instance of the specified User Control
    /// </summary>
    /// <returns>System.Windows.Forms.Control</returns>
    public System.Windows.Forms.Control GetUserControlInstance()
    {
        if (_usercontrol == null & this.CurrencyManager.Bindings.Count > 0) {
            System.Windows.Forms.Control[] _uControls = null;
            _uControls = this.CurrencyManager.Bindings[0].Control.FindForm().Controls.Find(this.Site.Name.ToString(), true);
            _usercontrol = _uControls[0];

        }
        return _usercontrol;
    }
    public BindingSourceExIsDirty()
    {
        DataMemberChanged += _DataMemberChanged;
        DataSourceChanged += _DataSourceChanged;
        BindingComplete += _BindingComplete;
    }

// PositionChanged
private override void _PositionChanged(object sender, EventArgs e)
{
    if (IsCurrentDirty) {
        // IsAutoSavingEvent
        if (AutoSave | MessageBox.Show(_msg, "Confirm Save", MessageBoxButtons.YesNo) == DialogResult.Yes) {
            try {
                //cast table as ITableUpdate to get the Update method
                //  CType(_dataTable, ITableUpdate).Update()
            } catch (Exception ex) {
                MessageBox.Show(ex, "Position Changed Error");
                // - needs to raise an event 
            }
        } else {
            this.CancelEdit();
            _dataTable.RejectChanges();
        }
        IsCurrentDirty = false;

    }
 base(e);
}


    #endregion

}
person Ken    schedule 10.08.2016

Из моего обновленного вопроса я обнаружил, что мне нужно сохранить текущую версию объекта в BeginEdit с помощью Memberwise.Clone, а затем в CancelEdit я восстановил ее до текущей.

person Jon    schedule 12.02.2010
comment
Что именно вы имеете в виду под BeginEdit? У BindingSource нет событий BeginEdit и EndEdit, не так ли? Я также пытаюсь получить копию своего объекта в нужное время, но я борюсь. :( - person Matthias Meid; 28.04.2011
comment
@Jon Я знаю, что твой пост устарел, но посмотри мой пост - он может быть тебе полезен. - person Ken; 10.08.2016

Что я всегда делаю, так это фиксирую отдельные «измененные» события элементов управления. В приведенном ниже примере я использовал tabcontrol в этом примере. Try/Catch - это грязное решение, чтобы не иметь дело со всеми видами исключений ;-)

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    '
    ' some code        
    '
    BindingNavigatorSaveItem.Enabled = False
    For Each tabctl As Control In Me.TabControl1.Controls
        For Each ctl As Control In tabctl.Controls
            Try
                If ctl.GetType Is GetType(TextBox) Then
                    AddHandler DirectCast(ctl, TextBox).TextChanged, AddressOf GenDataChanged
                ElseIf ctl.GetType Is GetType(NumericUpDown) Then
                    AddHandler DirectCast(ctl, NumericUpDown).ValueChanged, AddressOf GenDataChanged
                ElseIf ctl.GetType Is GetType(ComboBox) Then
                    AddHandler DirectCast(ctl, ComboBox).SelectedValueChanged, AddressOf GenDataChanged
                ElseIf ctl.GetType Is GetType(CheckBox) Then
                    AddHandler DirectCast(ctl, CheckBox).CheckStateChanged, AddressOf GenDataChanged
                End If
            Catch ex As Exception
            End Try
        Next
    Next
End Sub

Private Sub GenDataChanged(sender As System.Object, e As System.EventArgs)
    BindingNavigatorSaveItem.Enabled = True
End Sub
person Jeroen Bom    schedule 13.06.2013

Я не уверен, был ли он доступен, когда был задан вопрос, но я использую grid_CurrentCellDirtyStateChanged; событие

person Kirsten Greed    schedule 13.09.2014

сначала убедитесь, что вы установили DataSourceUpdateMode.OnPropertyChanged

txrFirstName.DataBindings.Add("Text", bindingSource1, "FirstName", false,DataSourceUpdateMode.OnPropertyChanged);

затем добавьте этот код в событие movenext click

 if (((DataRowView)bindingSource1.Current).IsNew)
                {
                MessageBox.Show("Current Row IsNew");
                }
            if (((DataRowView)bindingSource1.CurrencyManager.Current).Row.HasVersion(DataRowVersion.Proposed))
                {
                MessageBox.Show("Current Row Modified");
                DialogResult dialogResult = MessageBox.Show("Current Row Modified", "Some Title", MessageBoxButtons.YesNo);
                if (dialogResult == DialogResult.Yes)
                    {
                    //do something
                    ((DataRowView)bindingSource1.CurrencyManager.Current).Row.AcceptChanges();
                    }
                else if (dialogResult == DialogResult.No)
                    {
                    //do something else
                    ((DataRowView)bindingSource1.CurrencyManager.Current).Row.RejectChanges();
                    }


                }
            else { 
                bindingSource1.MoveNext();
                }
person Wasim Foad    schedule 28.07.2019