tipos son representa quimicos quimico los exclusivamente evaporacion ejemplos ebullicion cuales condensacion como color cocina caracteristicas cambios cambio c# winforms data-binding bindingsource

c# - son - ¿Puede mi fuente de enlace decirme si ha ocurrido un cambio?



la evaporacion condensacion y ebullicion son exclusivamente cambios quimicos (6)

Tengo un BindingSource que estoy usando en el enlace de datos de winforms y me gustaría tener algún tipo de aviso para cuando el usuario intente cerrar el formulario después de haber realizado cambios en los datos. Una especie de "¿Está seguro de que desea salir sin guardar los cambios?"

Soy consciente de que puedo hacer esto a través del evento CurrencyManager.ItemChanged BindingSource simplemente cambiando un booleano "ha cambiado".

Sin embargo, quiero una funcionalidad más robusta. Me gustaría saber cuándo los datos actuales son diferentes de los datos originales. El evento solo me dice si algo ha cambiado. Un usuario todavía podría cambiar una propiedad, hacer clic en deshacer, y todavía pensaría que hay un cambio en los datos para guardar.

Quiero imitar esta funcionalidad similar de bloc de notas

  • abrir bloc de notas
  • escribe algo
  • borra todo (esencialmente deshaciendo lo que hiciste)
  • cierre el bloc de notas, el bloc de notas se cierra, no hay mensajes para guardar los cambios porque conoce el estado final == el estado inicial

Si esto no es posible, ¿debería ItemChanged controlador de eventos ItemChanged como se describe anteriormente o hay una mejor manera?

Para el registro, estoy buscando algo en la línea de

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

no esta

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

Preferiría no tener que administrar el estado actual y el estado inicial, estoy buscando una forma de obtener esa información de BindingSource Si puedo obtener esta funcionalidad de BindingSource su forma más ideal, ya que podré para utilizar la funcionalidad en muchos orígenes de datos diferentes, independientemente del tipo, etc.


Cuando abre sus detalles, puede hacer un clon de la entidad que va a modificar.

Luego, cuando el usuario intenta cerrar el formulario, puede comparar el clon (la entidad en su estado original) con la entidad modificada (o no). Si el clon y la entidad no son iguales, puede preguntar al usuario.


En lugar de voltear un poco, puede comparar el estado con una instantánea de su estado inicial.


Podría rodar su propio origen de enlace e implementarlo para hacer lo que quiera de esa manera, ya que no necesita el manejo de INotifyChange en todos los formularios; simplemente deje que BindingSource brinde el elemento modificado; esto funciona cuando se actualiza .UpdateSourceTrigger control .UpdateSourceTrigger se establece en UpdateOnPropertyChanged . Es instantáneo (bueno, casi).

Aquí hay algo para que comience: lo encontré en la red hace años. No recuerdo el creador del código, lo he modificado ligeramente para mi propósito.

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


Tendrá que implementar la interfaz INotifyPropertyChanged desde sus clases de objetos y luego detectar cada vez que se produzca un cambio a través de los controladores de eventos adecuados para su clase de tipo dentro de su propiedad DataSource BindingSource .

El único objeto que ofrece lo que necesita es el DataSet , que contiene el estado original y actual (modificado) de una entidad persistente. Luego, cuando uno cancela, todo lo que necesita llamar es el método Rollback() . Cuando uno acepta los cambios, se realizará una llamada al método AcceptChanges() .

Además del DataSet , tal vez considerando que un ORM como NHibernate hará el trabajo por usted, además de permitirle usar objetos personalizados definidos, en lugar de un DataSet . Mantener viva la API de ISession mientras está en su formulario le permitirá a ISession realizar un seguimiento de sus cambios, sea cual sea el objeto, siempre que sea conocido por NHibernate.

Otra solución que implementa la interfaz INotifyPropertyChanged está en el establecedor de propiedades, puede almacenar el valor original dentro de un campo privado o para cada propiedad de un objeto. HasChanges podría tener una clase abstracta con la propiedad HasChanges devolver si cada propiedad es como su estado Original, y luego devolver verdadero o falso en consecuencia.

Tengo una pregunta sobre nuestra interesante discusión inicial. Solo quiero asegurarme de una cosa. Llamémoslo barrera del lenguaje si nos gusta. Pero la publicación del evento PropertyChanged través de la interfaz INotifyPropertyChanged también "revertirá" un objeto a su estado original. El único detalle que tuvo que tener en cuenta es que si el usuario dice que no desea mantener los cambios, vuelva a cargar este CurrentItem desde la base de datos subyacente a través de la clase BackgroundWorker y listo! ¡No se ha retrasado su GUI, su usuario ha cancelado los cambios y ha restablecido el objeto a su estado predeterminado / original!

Bueno, supongo que aquí hay suficientes detalles para hacerse una idea, además de todas las otras buenas respuestas proporcionadas por los demás. Estoy seguro de que encontrará la manera de lograr lo que quiere.

¡Lo mejor del éxito! =)


Will tiene razón, debe implementar INotifyPropertyChanged , idealmente junto con IDataInfoError para obtener información visible para sus usuarios.

Para que sus objetos obtengan un estado y una notificación sobre la edición, intente utilizar la interfaz IEditableObject .

Las tres interfaces se utilizan de forma predeterminada desde WinForms y ayudan a facilitar la vida de los programadores.


, pero hay algún trabajo involucrado. Lo sé, es una respuesta tardía, pero me hice la misma pregunta resecentemente y se me ocurrió la siguiente solución que envolví en la clase UpdateManager . Solo he tenido en cuenta el enlace a un solo objeto hasta ahora.

Esto funciona con objetos planos de POCO. Implementar INotifyPropertyChanged no es obligatorio; sin embargo, funciona solo si los cambios se realizan a través de la interfaz de usuario. Los cambios realizados a través del código en el objeto de negocio no se detectan. Pero esto es suficiente en la mayoría de los casos para detectar si el objeto está sucio o en un estado guardado.

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

En la forma lo usé así:

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

Siempre que cambie el estado Dirty , esto imprime "Sucio" o "Guardado" en la ventana de Salida.