2010-12-29 14 views
14

Mới sử dụng WPF & MVVM Tôi đang gặp khó khăn với một số chức năng cơ bản.MVVM - thực hiện chức năng 'IsDirty' đối với ModelView để lưu dữ liệu

Hãy đầu tiên để tôi giải thích những gì tôi sau đó, và sau đó đính kèm một số mã ví dụ ...

Tôi có một màn hình hiển thị danh sách người dùng, và tôi hiển thị chi tiết của người sử dụng lựa chọn trên cánh tay phải bên cạnh hộp văn bản có thể chỉnh sửa. Sau đó tôi có một nút Save là DataBound, nhưng tôi chỉ muốn nút này hiển thị khi dữ liệu đã thực sự thay đổi. tức là - tôi cần kiểm tra "dữ liệu bẩn".

Tôi có một ví dụ đầy đủ MVVM trong đó tôi có một mô hình gọi là User:

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

Sau đó, ViewModel trông như thế này:

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() 
     { 

     } 

    } 
} 

Các "RelayCommand" chỉ là một wrapper đơn giản lớp, như là "ViewModelBase". (Tôi sẽ đính kèm sau này mặc dù chỉ cho rõ ràng)

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() 
     { 
     } 
    } 
} 

Cuối cùng - 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> 

Vì vậy, về cơ bản, khi tôi chỉnh sửa một họ, nút Save nên được kích hoạt; và nếu tôi hoàn tác chỉnh sửa của mình - thì sau đó nó sẽ bị vô hiệu hóa một lần nữa vì không có gì thay đổi.

Tôi đã thấy điều này trong nhiều ví dụ, nhưng chưa tìm ra cách thực hiện.

Mọi trợ giúp sẽ được đánh giá cao! Brendan

Trả lời

0

Vì lệnh UserSave của bạn nằm trong ViewModel, tôi sẽ thực hiện theo dõi trạng thái "bẩn" ở đó. Tôi sẽ databind mục đã chọn trong ListBox, và khi nó thay đổi, lưu trữ ảnh chụp nhanh của các giá trị hiện tại của thuộc tính của người dùng đã chọn. Sau đó, bạn có thể so sánh với điều này để xác định xem lệnh có nên được bật/tắt hay không.

Tuy nhiên, vì bạn đang ràng buộc trực tiếp với mô hình, bạn cần một số cách để tìm hiểu xem có điều gì đó đã thay đổi hay không. Hoặc bạn cũng thực hiện INotifyPropertyChanged trong mô hình, hoặc bọc các thuộc tính trong một ViewModel.

Lưu ý rằng khi lệnh CanExecute của lệnh thay đổi, bạn có thể cần phải chạy CommandManager.InvalidateRequerySuggested().

3

Nếu bạn muốn thực hiện một cách tiếp cận khuôn khổ thay vì tự viết cơ sở hạ tầng, bạn có thể sử dụng CSLA (http://www.lhotka.net/cslanet/) - Khuôn khổ của Rocky để phát triển các đối tượng kinh doanh. Trạng thái đối tượng được quản lý cho bạn về các thay đổi thuộc tính và cơ sở mã cũng bao gồm một loại ViewModel mẫu hỗ trợ mô hình cơ bản, một động từ Lưu và thuộc tính CanSave. Bạn có thể lấy cảm hứng từ mã, thậm chí bạn không muốn sử dụng khung công tác.

4

Tôi khuyên bạn nên sử dụng GalaSoft MVVM Light Toolkit vì việc triển khai DIY dễ dàng hơn nhiều so với phương pháp tiếp cận DIY.

Đối với các lần đọc bẩn, bạn cần giữ lại ảnh chụp nhanh của từng trường và trả về true hoặc false từ phương thức UserSaveCanExecute(), cho phép/tắt nút lệnh tương ứng.

+0

Tôi cũng sẽ sử dụng Bộ công cụ ánh sáng MVVM –

+3

Cảm ơn, tôi đã cài đặt bộ công cụ MVVM-Light, nhưng tôi chưa thấy bất kỳ cách nào dễ dàng thực hiện chức năng "IsDirty". Tuy nhiên, tôi đã giải quyết được vấn đề của mình (có lẽ không phải là cách tốt nhất - nhưng nó hoạt động) - sẽ trả lời câu hỏi của riêng tôi một chút sau đó với các chi tiết – Brendan

+0

MVVM không hỗ trợ chức năng đọc bẩn. Đó chỉ là một gợi ý để giảm thiểu nỗ lực thực hiện mô hình MVVM. Nó là tốt để biết rằng bạn đã làm việc ra đọc bẩn. Bây giờ tôi sẽ đề nghị để khám phá thêm tin nhắn và sự kiện để chỉ huy trong bộ công cụ ánh sáng mà sẽ cung cấp cho bạn kiểm soát nhiều hơn nữa. Có một thời gian tốt. – ShahidAzim

7

Theo kinh nghiệm của tôi, nếu bạn triển khai IsDirty trong mô hình chế độ xem của mình, bạn có thể cũng muốn mô hình chế độ xem triển khai IEditableObject.

Giả sử rằng mô hình điểm của bạn là loại thông thường, thực hiện PropertyChanged and a private hoặc protected OnPropertyChanged phương pháp làm tăng nó, thiết lập IsDirty là đủ đơn giản: bạn chỉ cần đặt IsDirty trong OnPropertyChanged nếu nó không phải là đã thành sự thật.

Thiết lập IsDirty của bạn, nếu thuộc tính là sai và bây giờ là đúng, hãy gọi BeginEdit.

Lệnh Save của bạn nên gọi EndEdit, cập nhật mô hình dữ liệu và đặt IsDirty thành sai.

Lệnh Cancel của bạn nên gọi CancelEdit, làm mới mô hình chế độ xem từ mô hình dữ liệu và đặt IsDirty thành sai.

Các CanSaveCanCancel tài sản (giả sử bạn đang sử dụng một RelayCommand cho các lệnh này) chỉ trả về giá trị hiện tại của IsDirty.

Lưu ý rằng vì không có chức năng này phụ thuộc vào việc triển khai cụ thể mô hình khung nhìn, bạn có thể đặt nó trong một lớp cơ sở trừu tượng. Các lớp có nguồn gốc không phải thực hiện bất kỳ thuộc tính nào liên quan đến lệnh hoặc thuộc tính IsDirty; họ chỉ cần ghi đè BeginEdit, EndEditCancelEdit.

0

Đây là cách tôi đã triển khai IsDirty. Tạo một trình bao bọc cho mọi thuộc tính của lớp User (kế thừa lớp User với IPropertyChanged và thực hiện onpropertychanged trong lớp User wont help) trong ViewModal của bạn. Bạn cần phải thay đổi ràng buộc của bạn từ UserName thành WrapUserName.

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

Bây giờ có một tài sản

public bool isPageDirty 
    { 
     get; 
     set; 
    }  

Kể từ viewmodal của bạn được thừa hưởng từ baseviewmodal và dụng cụ baseviewmodal onPropertyChanged.

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

Trong trường hợp bất kỳ thuộc tính nào, làPageDirty sẽ đúng, Vì vậy, trong khi bạn tiết kiệm, hãy kiểm tra làPageDirty.

+0

Cảm ơn. Điều này có vẻ thú vị - sẽ chơi với nó tối nay. Tuy nhiên tôi đã đưa ra một giải pháp khác mà tôi sẽ giải thích sau một chút sau – Brendan

2

Tôi đã đưa ra giải pháp làm việc. Điều này có thể tất nhiên không phải là cách tốt nhất, nhưng tôi chắc chắn tôi có thể làm việc trên nó khi tôi tìm hiểu thêm ...

Khi tôi chạy dự án, nếu tôi cange bất kỳ mục nào, hộp danh sách bị vô hiệu hóa và đã bật nút lưu. Nếu tôi hoàn tác các chỉnh sửa của mình, khi đó hộp danh sách được bật lại và nút lưu đã bị tắt.

Tôi đã thay đổi tài khoản mẫu của tôi để thực hiện INotifyPropertyChanged, và tôi cũng đã tạo ra một tập hợp các biến riêng để lưu trữ các "giá trị ban đầu" và một số logic để kiểm tra "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)); 
     } 
    } 
} 

Sau đó, , ViewModel của tôi cũng đã thay đổi một chút ....

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() 
    { 

    } 

} 

Cuối cùng, XAML Tôi đã thay đổi các ràng buộc trên Username, Surname & FirstName để bao gồm UpdateSourceTrigger=PropertyChanged Và sau đó tôi bị ràng buộc của listbox SelectedItem và IsEnabled

Như tôi đã nói ở phần đầu - nó có thể không là giải pháp tốt nhất, nhưng có vẻ như hoạt động ...

3

Tôi đã thực hiện một số công việc về triển khai IsDirty cho các mô hình được gói trong ViewModel của tôi.

Kết quả thực sự đơn giản hóa ViewModels của tôi:

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 
} 

Mã @http://wpfcontrols.codeplex.com/ Kiểm tra dưới Patterns lắp ráp và thư mục MVVM, bạn sẽ tìm thấy một lớp ModelDataStore.

P.S. Tôi đã không thực hiện một thử nghiệm toàn diện về nó, chỉ cần thử nghiệm thực sự đơn giản, bạn sẽ tìm thấy hội đồng kiểm tra.

Các vấn đề liên quan