2010-12-29 4 views
14

WPFS를 처음 사용하는 사람 & MVVM 일부 기본 기능에 어려움을 겪고 있습니다.MVVM - 데이터를 저장하기 위해 ModelView에 'IsDirty'기능을 구현합니다.

나는 사용자의 목록을 보여주는 화면을 가지고 있고, 나는 오른쪽에 선택한 사용자의 세부 정보를 표시 ... 내가 먼저 난 후 나는 다음 몇 가지 예제 코드를 첨부 무엇인지 설명

하자 편집 가능한 텍스트 상자가있는 쪽. 그런 다음 DataBound 인 저장 단추가 있지만 데이터가 실제로 변경되었을 때만이 단추를 표시하는 것이 좋습니다. ie - "더티 데이터"를 확인해야합니다.

나는이 모델이 사용자라고 보유하고있는 완전 MVVM 예를 :

은 "RelayCommand"
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() 
     { 

     } 

    } 
} 

는 단순한 래퍼입니다 :

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

그런 다음 뷰 모델은 다음과 같습니다 클래스와 마찬가지로 "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() 
     { 
     } 
    } 
} 

을 (난 그냥 선명도 불구하고 후자를 첨부 할 것) - 나는 성을 편집 할 때

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

그래서 기본적으로, 저장 버튼이 활성화되어야 XAML을; 편집을 취소하면 잘 변경된 것이므로 아무것도 다시 사용하지 않아야합니다.

나는 많은 예제에서 이것을 보았지만 아직 어떻게하는지 알지 못했습니다.

도움이 될 것입니다. 브렌든

답변

0

UserSave 명령이 ViewModel에 있으므로 "더티"상태를 추적합니다. ListBox에서 선택한 항목에 데이터 바인딩하고 변경하면 선택한 사용자 속성의 현재 값에 대한 스냅 샷을 저장합니다. 그런 다음 명령과 비교하여 명령을 사용 가능/사용 불가능으로 결정해야하는지 판별 할 수 있습니다.

그러나 모델에 직접 바인딩되어 있으므로 변경된 사항이 있는지 알아볼 수있는 방법이 필요합니다. 모델에 INotifyPropertyChanged를 구현하거나 ViewModel에 속성을 래핑하십시오.

명령의 CanExecute가 변경되면 CommandManager.InvalidateRequerySuggested()를 실행해야 할 수도 있습니다.

3

인프라를 직접 작성하는 대신 프레임 워크 방식을 사용하려면 비즈니스 개체 개발을위한 Rocky의 프레임 워크 인 CSLA (http://www.lhotka.net/cslanet/)를 사용할 수 있습니다. 객체 상태는 속성 변경시 관리되며 코드 기반에는 기본 모델, 저장 동사 및 CanSave 속성을 지원하는 ViewModel 유형도 포함됩니다. 프레임 워크를 사용하지 않으려는 경우에도 코드에서 영감을 얻을 수 있습니다.

4

GalaSoft MVVM Light Toolkit을 사용하는 것이 좋습니다. DIY 방식보다 구현하기가 훨씬 쉽습니다.

더티 읽기의 경우 각 필드의 스냅 샷을 유지하고 UserSaveCanExecute() 메서드에서 true 또는 false를 반환해야 명령 버튼을 적절히 활성화/비활성화 할 수 있습니다.

+0

MVVM Light Toolkit도 사용합니다. –

+3

감사합니다. MVVM-Light 툴킷을 설치했지만 "IsDirty"기능을 쉽게 구현할 수있는 방법이 없습니다. 그러나 나는 문제를 해결할 수 있었다. (아마도 최선의 방법은 아니지만 작동한다.) - 내 자신의 질문에 약간의 세부 사항으로 대답 할 것이다. – Brendan

+0

MVVM은 더티 읽기 기능을 지원하지 않는다. MVVM 패턴을 구현하기위한 노력을 최소화하기위한 제안 일뿐입니다. 이미 더러운 읽기를 연습 한 것을 알아두면 좋습니다. 이제는 라이트 툴킷에서 더 많은 제어권을 줄 수있는 추가 메시징 및 이벤트를 탐구해볼 것을 제안합니다. 즐거운 시간 되십시오. – ShahidAzim

7

내 경험에 따르면, 뷰 모델에 IsDirty을 구현하면 뷰 모델이 IEditableObject을 구현하는 것이 좋습니다.

IsDirty을 설정, 뷰 모델은 보통의 일종이라고 가정 PropertyChanged을 구현하고 개인 또는 그것을 제기 OnPropertyChanged 방법을 보호하는 것은 간단하다 : 그것은 이미 사실이 아닌 경우 그냥 OnPropertyChangedIsDirty을 설정합니다.

사용자가 IsDirty 설정자가 false이고 현재 true 인 경우 BeginEdit으로 전화해야합니다.

Save 명령은 데이터 모델을 업데이트하고 IsDirty을 false로 설정하는 EndEdit을 호출해야합니다.

Cancel 명령은 데이터 모델에서 뷰 모델을 새로 고치고 IsDirty을 false로 설정하는 CancelEdit을 호출해야합니다.

CanSaveCanCancel 속성은 IsDirty의 현재 값을 반환 (이러한 명령에 대한 RelayCommand를 사용하는 가정).

이 기능은 뷰 모델의 특정 구현에 의존하지 않으므로이를 추상 기본 클래스에 넣을 수 있습니다. 파생 클래스는 명령 관련 속성이나 IsDirty 속성을 구현할 필요가 없습니다. BeginEdit, EndEditCancelEdit을 무시하면됩니다.

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

있습니다. isPageDirty을 확인 짱을 저장하는 동안 그래서

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

는 경우에 propertychanges의는 isPageDirty는 사실 일 것이다.

+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 나는, 성 &은 FIRSTNAME가 UpdateSourceTrigger=PropertyChanged 을 포함하는 사용자 이름에 바인딩을 변경 그리고 내가 바인딩 목록 상자의 selectedItem가와의 IsEnabled

내가 처음에 말했듯이 - 그것은하지 않을 수 있습니다 최상의 솔루션이 될 것 같습니다 ...

3

내 ViewModel에 래핑 된 모델에 대해 IsDirty를 구현하는 데 몇 가지 작업을 수행했습니다. 당신이 ModelDataStore 클래스를 찾을 수

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 
} 

코드 http://wpfcontrols.codeplex.com/ 어셈블리 패턴에서 확인 및 MVVM 폴더 @ :

결과는 정말 내 ViewModels를 단순화.

P. 본격적인 테스트를 수행하지 않았지만 테스트 어셈블리를 찾을 수있는 정말 간단한 테스트입니다.

관련 문제