2010-03-10 9 views
14

Ho un BindingSource che sto utilizzando nell'associazione dati winforms e vorrei ricevere una sorta di richiesta quando l'utente tenta di chiudere il modulo dopo aver apportato modifiche ai dati. Una sorta di "Sei sicuro di voler uscire senza salvare le modifiche?"La mia fonte di collegamento può dirmi se si è verificata una modifica?

Sono consapevole che posso farlo tramite l'evento CurrencyManager.ItemChanged di BindingSource semplicemente girando un booleano "è stato modificato".

Tuttavia, voglio una funzionalità più robusta. Mi piacerebbe sapere quando i dati attuali sono diversi dai dati originali. L'evento mi dice solo se qualcosa è cambiato. Un utente può ancora modificare una proprietà, premere annulla e penserei ancora che ci sia un cambiamento nei dati da salvare.

Voglio imitare questo simile funzionalità di notepad

  • aperto notepad
  • tipo qualcosa
  • cancellare tutto (essenzialmente disfare quello che hai fatto)
  • chiudere il Blocco note, chiude notepad, nessuna richiesta di salva le modifiche perché conosce lo stato finale == lo stato iniziale

Se questo non è possibile le, allora dovrei andare con il gestore di eventi ItemChanged come descritto sopra o c'è un modo migliore?

Per la cronaca, io sto cercando qualcosa lungo le linee di

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

non questo

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

avevo appena piuttosto non c'è bisogno di gestire lo stato attuale e lo stato iniziale io stesso, sto cercando un modo per prendere le informazioni dal BindingSource Se riesco a ottenere questa funzionalità dal BindingSource il suo modo più ideale dal momento che sarò in grado di utilizzare la funzionalità su molte diverse origini dati, indipendentemente dal tipo, ecc.

+2

+1 Trovo questa domanda molto interessante – Javier

risposta

4

È necessario implementare l'interfaccia INotifyPropertyChanged all'interno delle classi di oggetti, quindi rilevare ogni volta che si verifica una modifica tramite gestori di eventi appropriati per la classe del tipo all'interno della proprietà DataSourceBindingSource.

L'unico oggetto che offre ciò che si richiede è lo DataSet, che contiene sia lo stato originale che quello corrente (modificato) di un'entità persistente. Quindi, quando si annulla, tutto ciò che è necessario chiamare è il metodo Rollback(). Quando si accettano le modifiche, verrà eseguita una chiamata al metodo AcceptChanges().

Oltre allo DataSet, forse considerando un ORM come NHibernate farà il lavoro per te, oltre a consentire di utilizzare oggetti definiti personalizzati, invece di un DataSet. Mantenere attiva l'API ISession mentre si è nel modulo consente all'ISession di tenere traccia delle proprie modifiche indipendentemente dall'oggetto, purché sia ​​noto a NHibernate.

Un'altra soluzione che implementa l'interfaccia INotifyPropertyChanged, è il setter della proprietà, è possibile memorizzare il valore originale all'interno di un campo privato o per ogni proprietà di un oggetto. Si potrebbe semplicemente avere una classe astratta con il valore di proprietà HasChanges se ogni proprietà è come stato originale, quindi restituire true o false di conseguenza.

Ho una domanda riguardante la nostra interessante discussione iniziale. Voglio solo assicurarmi di una cosa. Chiamiamola barriera linguistica, se vogliamo. Tuttavia, la pubblicazione dell'evento PropertyChanged tramite l'interfaccia INotifyPropertyChanged comporta anche il "rollback" di un oggetto al suo stato originale. L'unico dettaglio che dovevi fare attenzione è che se l'utente dice che non vuole mantenere le modifiche, quindi ricarica questo CurrentItem dal database sottostante tramite la classe BackgroundWorker ed è fatto! Nessun ritardo rispetto alla GUI, l'utente ha annullato le modifiche e reimpostato l'oggetto sul suo stato predefinito/originale!

Bene, immagino ci siano abbastanza dettagli per farsi un'idea, oltre a tutte le altre buone risposte fornite dagli altri. Sono sicuro che troverai il tuo modo di realizzare ciò che desideri.

Meglio di successo! =)

+0

In che modo è diverso da girare un po 'con CurrencyManager. ItemChanged? Non confronta lo stato originale con lo stato corrente –

+0

L'oggetto deve solo sapere se è stato modificato o meno. Implementandolo direttamente nel tuo oggetto, non ti preoccuperai di giocare con il CurrencyManager. Ad ogni modo, non capisco davvero cosa intendesse Brett con "Invece di lanciare un pochino", anche se ho l'idea di confrontare l'istantanea con lo stato iniziale.In effetti, la vera domanda è: vuoi essere in grado di confrontare se c'è stato un cambiamento rispetto al tuo stato iniziale, o se vuoi pubblicare c'è stata una modifica, quindi puoi convalidare se l'utente vuole salvare le modifiche o no durante la chiusura del modulo? –

+0

Brett significa che invece di eseguire 'bo ha hasChanged() {return this._hasChanged; } ', Vorrei fare' bool hasChanged() {return currentValues! = InitialValues; } ' –

1

Invece di capovolgere un po ', è possibile controllare lo stato rispetto a un'istantanea dello stato iniziale.

+0

sì sì sì, questo è quello che voglio fare, ma come faccio a farlo? lol Non voglio farlo manualmente, sicuramente il 'currencymanager' o' bindingsource' ha questo built-in. Questo o forse tiene traccia degli stati iniziali e attuali –

1

Quando si aprono i dettagli, è possibile creare un clone dell'entità che si intende modificare.

Quindi, quando l'utente tenta di chiudere il modulo, è possibile confrontare il clone (l'entità nel suo stato originale) con l'entità modificata (o non). Se il clone e l'entità non sono uguali, è possibile richiedere all'utente.

+0

Ero dell'opinione che il CurrencyManager o il BindingSource lo facessero già, internamente. –

+0

Aha, sì ..., ha tutto il senso. Anch'io aspetterò una risposta anche alla tua domanda. – Javier

4

È giusto, è necessario implementare INotifyPropertyChanged, idealmente in combinazione con IDataInfoError per ottenere informazioni visisble per i vostri utenti.

Per consentire agli oggetti di ottenere uno stato e una notifica sul montaggio, provare a utilizzare l'interfaccia IEditableObject.

Tutte e tre le interfacce sono utilizzate per impostazione predefinita da WinForms e contribuiscono a semplificare la vita dei programmatori.

+0

Forse mi manca qualcosa, ma come ho detto, in che modo è diverso dall'evento che ho descritto nella mia domanda. È ANCORA che mi ha appena detto quando un utente ha apportato una modifica, non è stato confrontato lo stato iniziale con lo stato corrente. –

+0

+1 Grazie per questo BeowulfOF –

+0

@Allen: Dipende solo dall'implementazione se contiene internamente uno stato iniziale. L'implementazione di riferimento per IEditableObject mostra un modo pulito per rendere i tuoi oggetti modificabili e annullabili. –

0

Si potrebbe rotolare il proprio fonte vincolante e la sua attuazione a fare ciò che si vuole in questo modo non è necessario INotifyChange movimentazione in ogni forma - basta lasciare che il BindingSource darvi l'elemento modificato - questo funziona quando il BindingSource è aggiornato - il controllo legabile .UpdateSourceTrigger è impostato su UpdateOnPropertyChanged. è istantaneo (beh quasi).

Ecco qualcosa per iniziare: l'ho trovato sulla rete anni fa, non ricordo il creatore del codice, l'ho leggermente modificato per il mio scopo.

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 `