2010-03-10 11 views
14

Tengo un BindingSource que estoy utilizando 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 que hayan realizado cambios en los datos. Una especie de "¿Estás seguro de que quieres salir sin guardar los cambios?"¿Puede mi fuente de enlace decirme si ha ocurrido un cambio?

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

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

Quiero imitar esta funcionalidad similar de libreta

  • libreta abierta
  • tipo algo
  • eliminar todo (esencialmente deshacer lo que hizo)
  • cerca de bloc de notas, cierra el bloc de notas, sin preguntar a guardar cambios porque conoce el estado final == el estado inicial

Si esto no es posible Entonces, ¿debería ir con el controlador de eventos ItemChanged como se describe anteriormente o hay una forma mejor?

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

que acababa en lugar no tiene que administrar el estado actual y el estado inicial yo mismo, estoy buscando una manera de obtener esa información del BindingSource Si puedo obtener esta funcionalidad del BindingSource es mucho más ideal, ya que podré usar la funcionalidad en muchas fuentes de datos diferentes, sin importar el tipo, etc.

+2

+1 Me parece muy interesante la pregunta – Javier

Respuesta

4

Vas a tener que implementar la interfaz INotifyPropertyChanged desde dentro de sus clases de objetos, luego coger cada vez que un cambio se produce a través de controladores de eventos adecuados para su tipo de clase dentro de su DataSourceBindingSource propiedad.

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

Además del DataSet, quizás considerando un ORM como NHibernate hará el trabajo por usted, además de permitirle usar objetos personalizados, en lugar de un DataSet. Mantener activa la API ISession mientras está en su formulario permitirá a la ISession realizar un seguimiento de sus cambios, cualquiera que sea el objeto que sea, siempre que NHibernate lo conozca.

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

Tengo una pregunta sobre nuestra interesante discusión inicial. Solo quiero asegurarme de una cosa. Llamémosla barrera idiomática si queremos. Pero publicar el evento PropertyChanged a través de la interfaz INotifyPropertyChanged también de alguna manera "revertirá" un objeto a su estado original. El único detalle que debe tener cuidado 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. Sin retrasos en su GUI, su usuario canceló los cambios y restableció el objeto a su estado predeterminado/original.

Bueno, supongo que aquí hay suficientes detalles para hacerte 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! =)

+0

De qué forma es esto diferente a voltear un poco con CurrencyManager. ItemChanged? No compara el estado original con el estado actual –

+0

Su objeto solo debe saber si ha cambiado o no. Implementándolo directamente en su objeto, no se molestará en jugar con CurrencyManager. De todos modos, no entiendo realmente lo que Brett quiere decir con "En lugar de dar un salto", aunque tengo la idea de comparar la instantánea con el estado inicial.De hecho, la verdadera pregunta es, ¿desea poder comparar si hubo un cambio en comparación con su estado inicial, o desea publicar que hubo un cambio, por lo que puede validar si el usuario desea guardar los cambios? o no mientras se cierra el formulario? –

+0

Brett significa que en lugar de hacer 'bool hasChanged() {return this._hasChanged; } ', Haría' bool hasChanged() {return currentValues! = InitialValues; } ' –

1

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

+0

sí sí sí, esto es lo que quiero hacer, pero ¿cómo lo hago? jajaja No quiero hacerlo manualmente, seguramente el 'currencymanager' o el' bindingsource' tiene esto incorporado. Eso o tal vez hace un seguimiento de los estados iniciales y actuales –

1

Al abrir 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 solicitar al usuario.

+0

Tenía la opinión de que CurrencyManager o BindingSource ya lo hacían internamente. –

+0

Aha, sí ..., tiene todo el sentido. También esperaré una respuesta a tu pregunta. – Javier

4

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 Edición, intente utilizar la interfaz IEditableObject.

Las tres interfaces se utilizan por defecto de WinForms y ayudan a que los programadores sean más fáciles.

+0

Me puede estar perdiendo algo, pero como dije, ¿de qué manera es esto diferente al evento que describí en mi pregunta? TODAVÍA me acaba de decir cuando un usuario hizo un cambio, no está comparando el estado inicial con el estado actual. –

+0

+1 Gracias por este BeowulfOF –

+0

@ Allen: Simplemente depende de su implementación si tiene un estado inicial internamente. La implementación de referencia para IEditableObject muestra una forma limpia de hacer que sus objetos sean editables e imposibles de deshacer. –

0

Se podría rodar su propia fuente de unión y ponerlo en práctica para hacer lo que quiere de esa manera que no es necesario INotifyChange manipulación en toda forma - que acaba de dejar la BindingSource le dan el elemento cambiado - esto funciona cuando el BindingSource se actualiza - el control enlazable .UpdateSourceTrigger se establece en UpdateOnPropertyChanged. es instantáneo (bueno casi).

Aquí hay algo para empezar: 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 ` 
Cuestiones relacionadas