2010-12-29 16 views
14

WPFの新機能& MVVMいくつかの基本機能に苦しんでいます。MVVM - データを保存するためにModelViewに 'IsDirty'機能を実装する

私はユーザーのリストを示す画面を持っている、と私は右手で選択したユーザーの詳細を表示

...私が最初に私が後だ内容を説明した後、いくつかのサンプルコードを添付してみましょう編集可能なテキストボックスを持つサイド。私はその後、DataBoundである保存ボタンを持っていますが、データが実際に変更されたときに表示するだけです。すなわち、 - 私は "汚いデータ"をチェックする必要があります。

私はユーザーと呼ばれるモデルを持っている完全にMVVM例があります。

namespace Test.Model 
{ 
    class User 
    { 
     public string UserName { get; set; } 
     public string Surname { get; set; } 
     public string Firstname { get; set; } 
    } 
} 

その後、ViewModelには、次のようになります。

using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.Windows.Input; 
using Test.Model; 

namespace Test.ViewModel 
{ 
    class UserViewModel : ViewModelBase 
    { 
     //Private variables 
     private ObservableCollection<User> _users; 
     RelayCommand _userSave; 

     //Properties 
     public ObservableCollection<User> User 
     { 
      get 
      { 
       if (_users == null) 
       { 
        _users = new ObservableCollection<User>(); 
        //I assume I need this Handler, but I am stuggling to implement it successfully 
        //_users.CollectionChanged += HandleChange; 

        //Populate with users 
        _users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"}); 
        _users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"}); 
       } 
       return _users; 
      } 
     } 

     //Not sure what to do with this?!?! 

     //private void HandleChange(object sender, NotifyCollectionChangedEventArgs e) 
     //{ 
     // if (e.Action == NotifyCollectionChangedAction.Remove) 
     // { 
     //  foreach (TestViewModel item in e.NewItems) 
     //  { 
     //   //Removed items 
     //  } 
     // } 
     // else if (e.Action == NotifyCollectionChangedAction.Add) 
     // { 
     //  foreach (TestViewModel item in e.NewItems) 
     //  { 
     //   //Added items 
     //  } 
     // } 
     //} 

     //Commands 
     public ICommand UserSave 
     { 
      get 
      { 
       if (_userSave == null) 
       { 
        _userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute); 
       } 
       return _userSave; 
      } 
     } 

     void UserSaveExecute() 
     { 
      //Here I will call my DataAccess to actually save the data 
     } 

     bool UserSaveCanExecute 
     { 
      get 
      { 
       //This is where I would like to know whether the currently selected item has been edited and is thus "dirty" 
       return false; 
      } 
     } 

     //constructor 
     public UserViewModel() 
     { 

     } 

    } 
} 

「RelayCommandは」単純なラッパーです"ViewModelBase"と同様に、

using System; 
using System.ComponentModel; 

namespace Test.ViewModel 
{ 
    public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable 
    { 
     protected ViewModelBase() 
     { 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     protected virtual void OnPropertyChanged(string propertyName) 
     { 
      PropertyChangedEventHandler handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       var e = new PropertyChangedEventArgs(propertyName); 
       handler(this, e); 
      } 
     } 

     public void Dispose() 
     { 
      this.OnDispose(); 
     } 

     protected virtual void OnDispose() 
     { 
     } 
    } 
} 

が最後に(私はちょうど明確にするためものの、後者を添付します) - 私は姓を編集するときXAML

<Window x:Class="Test.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:vm="clr-namespace:Test.ViewModel" 
     Title="MainWindow" Height="350" Width="525"> 
    <Window.DataContext> 
     <vm:UserViewModel/> 
    </Window.DataContext> 
    <Grid> 
     <ListBox Height="238" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1" VerticalAlignment="Top" 
       Width="197" ItemsSource="{Binding Path=User}" IsSynchronizedWithCurrentItem="True"> 
      <ListBox.ItemTemplate> 
      <DataTemplate> 
       <StackPanel> 
         <TextBlock Text="{Binding Path=Firstname}"/> 
         <TextBlock Text="{Binding Path=Surname}"/> 
       </StackPanel> 
      </DataTemplate> 
      </ListBox.ItemTemplate> 
     </ListBox> 
     <Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="232,16,0,0" Name="label1" VerticalAlignment="Top" /> 
     <TextBox Height="23" HorizontalAlignment="Left" Margin="323,21,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/UserName}" /> 
     <Label Content="Surname" Height="28" HorizontalAlignment="Left" Margin="232,50,0,0" Name="label2" VerticalAlignment="Top" /> 
     <TextBox Height="23" HorizontalAlignment="Left" Margin="323,52,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Surname}" /> 
     <Label Content="Firstname" Height="28" HorizontalAlignment="Left" Margin="232,84,0,0" Name="label3" VerticalAlignment="Top" /> 
     <TextBox Height="23" HorizontalAlignment="Left" Margin="323,86,0,0" Name="textBox3" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Firstname}" /> 
     <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="368,159,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Path=UserSave}" /> 
    </Grid> 
</Window> 

だから基本的には、[保存]ボタンを有効にする必要があります。編集を元に戻すと、何も変わっていないので、もう一度無効にする必要があります。

私はこれを多くの例で見てきましたが、まだそれを行う方法を見つけていません。

ご協力いただければ幸いです。 ブレンダン

答えて

0

UserSaveコマンドはViewModelにあるので、そこに「ダーティ」状態のトラッキングを行います。 ListBoxの選択した項目にデータバインドし、変更されたときに、選択したユーザーのプロパティの現在の値のスナップショットを保存します。次に、これを比較して、コマンドを有効/無効にする必要があるかどうかを判断できます。

ただし、モデルに直接バインドするため、何か変更があるかどうかを調べる方法が必要です。また、モデルにINotifyPropertyChangedを実装するか、ViewModelにプロパティをラップします。

コマンドのCanExecuteが変更されたときに、CommandManager.InvalidateRequerySuggested()を起動する必要があることに注意してください。

3

インフラストラクチャを自分で作成するのではなく、フレームワークアプローチを採用する場合は、ビジネスオブジェクトを開発するためのCSLA(http://www.lhotka.net/cslanet/) - Rockyのフレームワークを使用できます。オブジェクトの状態はプロパティの変更で管理され、コードベースには、基になるモデル、保存動詞、およびCanSaveプロパティをサポートするViewModelのタイプも含まれています。フレームワークを使用したくない場合でも、コードからインスピレーションを得ることができます。

4

GalaSoft MVVM Light Toolkitを使用することをお勧めします。DIYアプローチよりも実装がはるかに簡単です。

汚れた読み取りの場合は、各フィールドのスナップショットを保持し、UserSaveCanExecute()メソッドからtrueまたはfalseを戻す必要があります。これにより、コマンドボタンが有効/無効になります。

+0

私も –

+3

感謝をMVVMライトツールキットを使用することになり、私はMVVM-ライトツールキットをインストールしたが、私は簡単に「IsDirty」機能を実装する任意の方法を見ていません。しかし私は私の問題を解決するために管理している( - しかし、それは動作しますおそらく最良の方法はありません) - – Brendan

+0

MVVMは、ダーティリード機能をサポートしていません詳細は少し後に自分の質問にお答えします。 MVVMパターンを実装するための努力を最小限に抑えることが唯一の提案でした。あなたがすでに汚い読書を済ませていることを知っておいてよかったです。今、私はあなたより多くの制御を与える光のツールキットに命令するために、さらに、メッセージングおよびイベントを探求することをお勧め。楽しい時間をお過ごしください。 – ShahidAzim

7

私の経験では、ビューモデルにIsDirtyを実装すると、ビューモデルにIEditableObjectを実装したいと思うかもしれません。

IsDirtyを設定し、あなたのビューモデルは、通常の一種であると仮定するとPropertyChangedを実装し、プライベートまたは、それを提起OnPropertyChanged方法を保護するだけでは十分に簡単です:それはまだ本当ではない場合、あなただけのOnPropertyChangedIsDirtyを設定します。

IsDirtyセッターは、プロパティがfalseで現在trueの場合は、BeginEditと呼びます。

Saveコマンドは、EndEditを呼び出してデータモデルを更新し、IsDirtyをfalseに設定する必要があります。

Cancelコマンドは、CancelEditを呼び出して、データモデルからビューモデルを更新し、IsDirtyをfalseに設定する必要があります。 (あなたがこれらのコマンドのRelayCommandを使用していると仮定)

CanSaveCanCancelプロパティは、ちょうどIsDirtyの現在の値を返します。

この機能はビューモデルの特定の実装に依存しないため、抽象基底クラスに入れることができます。派生クラスは、コマンド関連のプロパティまたはIsDirtyプロパティを実装する必要はありません。 BeginEditEndEdit、およびCancelEditを上書きするだけです。

0

これは、私がIsDirtyを実装した方法です。 ViewModalでUserクラスのすべてのプロパティ(IPropertyChangedでUserクラスを継承し、Userクラスではonpropertychangedを実装する)のラッパーを作成します。バインドをUserNameからWrapUserNameに変更する必要があります。あなたのviewmodalはbaseviewmodalとbaseviewmodal実装がOnPropertyChangedを継承しているので

public string WrapUserName 
    { 
     get 
     { 
      return User.UserName   
     } 
     set 
     { 
      User.UserName = value; 
      OnPropertyChanged("WrapUserName"); 
     } 
    } 

は今プロパティ

public bool isPageDirty 
    { 
     get; 
     set; 
    }  

を持っています。するPropertyChangesのいずれの場合においても

UserViewModel.PropertyChanged += (s, e) => { isPageDirty = true; };  

、isPageDirtyあなたはちゃんisPageDirtyをチェック節約しながらだから、trueになります。

+0

ありがとうございます。これは面白そうに見えます - 今夜それで遊びます。しかし私はもう少し後で説明する別の解決策を思いつきました – Brendan

2

私は実際の解決策を考え出しました。これはもちろん最善の方法ではないかもしれませんが、私はもっと学ぶように私が仕事をすることができると確信しています...

私はプロジェクトを実行すると、私はすべての項目を缶詰め、リストボックスが無効になり、保存ボタンが有効になります。編集内容を元に戻すと、リストボックスが再び有効になり、保存ボタンが無効になります。

私はINotifyPropertyChangedのを実装するために私のユーザモデルを変更している、と私はまた、「IsDirty」を確認するために、「元の値」といくつかのロジックを格納するプライベート変数のセットを作成している

using System.ComponentModel; 
namespace Test.Model 
{ 
    public class User : INotifyPropertyChanged 
    { 
    //Private variables 
    private string _username; 
    private string _surname; 
    private string _firstname; 

    //Private - original holders 
    private string _username_Orig; 
    private string _surname_Orig; 
    private string _firstname_Orig; 
    private bool _isDirty; 

    //Properties 
    public string UserName 
    { 
     get 
     { 
      return _username; 
     } 
     set 
     { 
      if (_username_Orig == null) 
      { 
       _username_Orig = value; 
      } 
      _username = value; 
      SetDirty(); 
     } 
    } 
    public string Surname 
    { 
     get { return _surname; } 
     set 
     { 
      if (_surname_Orig == null) 
      { 
       _surname_Orig = value; 
      } 
      _surname = value; 
      SetDirty(); 
     } 
    } 
    public string Firstname 
    { 
     get { return _firstname; } 
     set 
     { 
      if (_firstname_Orig == null) 
      { 
       _firstname_Orig = value; 
      } 
      _firstname = value; 
      SetDirty(); 
     } 
    } 

    public bool IsDirty 
    { 
     get 
     { 
      return _isDirty; 
     } 
    } 

    public void SetToClean() 
    { 
     _username_Orig = _username; 
     _surname_Orig = _surname; 
     _firstname_Orig = _firstname; 
     _isDirty = false; 
     OnPropertyChanged("IsDirty"); 
    } 

    private void SetDirty() 
    { 
     if (_username == _username_Orig && _surname == _surname_Orig && _firstname == _firstname_Orig) 
     { 
      if (_isDirty) 
      { 
       _isDirty = false; 
       OnPropertyChanged("IsDirty"); 
      } 
     } 
     else 
     { 
      if (!_isDirty) 
      { 
       _isDirty = true; 
       OnPropertyChanged("IsDirty"); 
      } 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 

     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

その後、私のViewModelもちょっと変わった....

using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.Windows.Input; 
using Test.Model; 
using System.ComponentModel; 

namespace Test.ViewModel 
{ 
    class UserViewModel : ViewModelBase 
    { 
     //Private variables 

    private ObservableCollection<User> _users; 
    RelayCommand _userSave; 
    private User _selectedUser = new User(); 

    //Properties 
    public ObservableCollection<User> User 
    { 
     get 
     { 
      if (_users == null) 
      { 
       _users = new ObservableCollection<User>(); 
       _users.CollectionChanged += (s, e) => 
       { 
        if (e.Action == NotifyCollectionChangedAction.Add) 
        { 
         // handle property changing 
         foreach (User item in e.NewItems) 
         { 
          ((INotifyPropertyChanged)item).PropertyChanged += (s1, e1) => 
           { 
            OnPropertyChanged("EnableListBox"); 
           }; 
         } 
        } 
       }; 
       //Populate with users 
       _users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"}); 
       _users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"}); 
      } 
      return _users; 
     } 
    } 

    public User SelectedUser 
    { 
     get { return _selectedUser; } 
     set { _selectedUser = value; } 
    } 

    public bool EnableListBox 
    { 
     get { return !_selectedUser.IsDirty; } 
    } 

    //Commands 
    public ICommand UserSave 
    { 
     get 
     { 
      if (_userSave == null) 
      { 
       _userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute); 
      } 
      return _userSave; 
     } 
    } 

    void UserSaveExecute() 
    { 
     //Here I will call my DataAccess to actually save the data 
     //Save code... 
     _selectedUser.SetToClean(); 
     OnPropertyChanged("EnableListBox"); 
    } 

    bool UserSaveCanExecute 
    { 
     get 
     { 
      return _selectedUser.IsDirty; 
     } 
    } 

    //constructor 
    public UserViewModel() 
    { 

    } 

} 

最後に、XAML 私は、姓&はファーストネームがUpdateSourceTrigger=PropertyChanged が含まれるようにユーザー名にバインドを変更そして私はバインドリストボックスのSelectedItemのとでIsEnabled

私が最初に言ったように - それはないかもしれません最善の解決策になるが、動作しているようです...

3

私は自分のViewModelに包まれたモデルのためにIsDirtyを実装する上でいくつかの作業を行ってきました。結果は本当に私のviewmodels簡素化

:パターンアセンブリとMVVMフォルダの下http://wpfcontrols.codeplex.com/ チェック@

public class PersonViewModel : ViewModelBase 
{ 
    private readonly ModelDataStore<Person> data; 
    public PersonViewModel() 
    { 
     data = new ModelDataStore<Person>(new Person()); 
    } 

    public PersonViewModel(Person person) 
    { 
     data = new ModelDataStore<Person>(person); 
    } 

    #region Properties 

    #region Name 
    public string Name 
    { 
     get { return data.Model.Name; } 
     set { data.SetPropertyAndRaisePropertyChanged("Name", value, this); } 
    } 
    #endregion 

    #region Age 
    public int Age 
    { 
     get { return data.Model.Age; } 
     set { data.SetPropertyAndRaisePropertyChanged("Age", value, this); } 
    } 
    #endregion 

    #endregion 
} 

はコードを、あなたはModelDataStoreクラスを見つけることができます。

P.S. 私は、それをあなたがテストアセンブリを見つけることができます本当に簡単なテストをフルスケールのテストを行っていません。

関連する問題