18

winforms のデータ バインディングで使用してBindingSourceいる があり、データに変更を加えた後にユーザーがフォームを閉じようとしたときに何らかのプロンプトが表示されるようにしたいと考えています。一種の「変更を保存せずに終了してもよろしいですか?」

「has changed」ブール値を反転するだけで、 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;
}

現在の状態と初期状態を自分で管理する必要はなく、その情報を取得する方法を探してBindingSourceBindingSourceますタイプなどに関係なく、さまざまなデータ ソースの機能。

4

6 に答える 6

5

INotifyPropertyChangedオブジェクト クラス内からインターフェイスを実装する必要があります。次に、DataSource BindingSourceプロパティ内の型クラスの適切なイベント ハンドラーを介して変更が発生するたびにキャッチします。

必要なものを提供する 1 つのオブジェクトDataSetは、永続エンティティの元の状態と現在の (変更された) 状態の両方を含む です。次に、キャンセルするときに呼び出す必要があるのはRollback()メソッドだけです。変更を受け入れると、AcceptChanges()メソッドが呼び出されます。

に加えてDataSet、おそらく NHibernate のような ORM があなたのために仕事をしてくれることを考えると、DataSet. ISessionフォーム内で API を有効にしておくと、NHibernate が認識している限り、ISession はオブジェクトが何であれ、変更​​を追跡できます。

インターフェイスを実装する別のソリューションINotifyPropertyChangedは、プロパティ セッターにあります。プライベート フィールド内またはオブジェクトのすべてのプロパティに対して、元の値をストックできます。HasChanges各プロパティが元の状態であるかどうかを返すプロパティを持つ抽象クラスを単純に作成し、それに応じて true または false を返すことができます。

興味深い最初の話し合いについて質問があります。一つだけ確認したいことがあります。よろしければ、それを言語の壁と呼びましょう。PropertyChangedただし、インターフェイスを介してイベントを公開するINotifyPropertyChangedと、何らかの形でオブジェクトが元の状態に「ロールバック」されます。あなたが注意しなければならなかった唯一の詳細は、ユーザーが変更を保持したくないと言った場合、BackgroundWorkerクラスを介して基礎となるデータベースからこの CurrentItem をリロードすれば完了です! GUI から遅れることはありません。ユーザーが変更をキャンセルし、オブジェクトをデフォルト/元の状態にリセットしました!

まあ、ここに自分自身のアイデアを作るのに十分な詳細と、他の人から提供された他のすべての良い答えがあると思います. あなたが望むものを達成するためのあなたの道を見つけると確信しています。

最高の成功!=)

于 2010-03-10T19:41:55.537 に答える
4

意志は正しいですINotifyPropertyChanged。理想的には と組み合わせて を実装IDataInfoErrorして、ユーザーにとって目に見える情報を取得する必要があります。

オブジェクトが編集時に状態と通知を取得するには、IEditableObjectインターフェースを使用してみてください。

3 つのインターフェイスはすべて WinForms からデフォルトで使用され、プログラマーの作業を容易にします。

于 2010-03-10T19:50:12.953 に答える
1

詳細を開くと、変更するエンティティのクローンを作成できます。

次に、ユーザーがフォームを閉じようとしたときに、クローン(元の状態のエンティティ)を変更された(またはされていない)エンティティと比較できます。クローンとエンティティが等しくない場合は、ユーザーにプロンプ​​トを表示できます。

于 2010-03-10T19:46:00.013 に答える
1

少し反転する代わりに、初期状態のスナップショットに対して状態を確認できます。

于 2010-03-10T19:28:25.783 に答える
0

INotifyChange独自のバインディング ソースをロールし、それを実装して、すべてのフォームで処理する必要がないように、必要なことを行うことができBindingSourceます。変更された要素を与えるだけです。これBindingSourceは、更新されたときに機能.UpdateSourceTriggerUpdateOnPropertyChangedます。インスタントです(ほぼ)。

ここから始めましょう - 何年も前にネットで見つけました。コードの作成者を覚えていません。目的に合わせて少し変更しました。

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
于 2015-03-03T17:38:38.433 に答える
0

はい。ただし、作業が必要です。答えが遅いことはわかっていますが、最近同じ質問を自問し、クラスにまとめた次の解決策を思いつきましたUpdateManager。ここまでは、1 つのオブジェクトへのバインドのみを説明しました。

これはプレーンな POCO オブジェクトで機能します。実装する必要INotifyPropertyChangedはありません。ただし、UI を介して変更が行われた場合にのみ機能します。ビジネス オブジェクトのコードによる変更は検出されません。しかし、ほとんどの場合、オブジェクトがダーティか保存状態かを検出するにはこれで十分です。

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に、出力ウィンドウに「Dirty」または「Saved」が表示されます。

于 2018-11-07T18:28:36.783 に答える