2010-12-29 15 views
14

MVVM मैं WPF & करने के लिए कुछ बुनियादी कार्यशीलता के साथ संघर्ष कर नया होने के नाते को बचाने के लिए एक ModelView करने के लिए 'IsDirty' कार्यक्षमता को लागू करने।MVVM - आदेश डेटा

मुझे पहले की व्याख्या क्या मैं के बाद कर रहा हूँ, और फिर कुछ उदाहरण कोड देते हैं ...

मैं उपयोगकर्ताओं की सूची दिखा एक स्क्रीन है, और मुझे दाएँ हाथ पर चयनित उपयोगकर्ता के विवरण प्रदर्शित संपादन योग्य टेक्स्टबॉक्स के साथ पक्ष। मेरे पास एक बचत बटन है जो डेटाबाउंड है, लेकिन डेटा को वास्तव में बदलते समय मुझे यह बटन प्रदर्शित करना होगा। यानी - मुझे "गंदे डेटा" की जांच करने की आवश्यकता है।

मैं एक पूरी तरह से MVVM उदाहरण है, जिसमें मैं एक मॉडल उपयोगकर्ता कहा जाता है है:

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" सिर्फ एक सरल आवरण है:

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

फिर, ViewModel इस तरह दिखता है कक्षा, जैसा कि "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 में चयनित आइटम से डाटाबेस करता हूं, और जब यह बदलता है, तो चयनित उपयोगकर्ता के गुणों के वर्तमान मानों का स्नैपशॉट स्टोर करें। फिर आप यह निर्धारित करने के लिए तुलना कर सकते हैं कि कमांड सक्षम/अक्षम होना चाहिए या नहीं।

हालांकि, बाद से आप मॉडल के लिए सीधे बाध्यकारी हैं, तो आप किसी तरह से अगर कुछ बदल गया पता लगाने के लिए की जरूरत है। या तो आप मॉडल में शामिल IotifyProperty को भी लागू करते हैं, या व्यूमोडेल में गुणों को लपेटते हैं।

ध्यान दें कि जब CanExecute आदेश परिवर्तन की, आप CommandManager.InvalidateRequerySuggested सक्रिय होने की आवश्यकता हो सकती है()।

3

यदि आप स्वयं को आधारभूत संरचना लिखने के बजाय एक ढांचा दृष्टिकोण लेना चाहते हैं, तो आप सीएसएलए (http://www.lhotka.net/cslanet/) का उपयोग कर सकते हैं - व्यावसायिक वस्तुओं के विकास के लिए रॉकी का ढांचा। ऑब्जेक्ट स्टेटस आपके लिए संपत्ति परिवर्तनों पर प्रबंधित की जाती है, और कोड बेस में एक उदाहरण शामिल है जो व्यूमोडेल प्रकार है जो अंतर्निहित मॉडल, एक सेव वर्ब और कैनसेव प्रॉपर्टी का समर्थन करता है। आप कोड से प्रेरणा ले सकते हैं, यहां तक ​​कि आप ढांचे का उपयोग नहीं करना चाहते थे।

4

मैं तुम्हें GalaSoft MVVM Light Toolkit उपयोग करने के लिए के रूप में यह और अधिक आसान है DIY दृष्टिकोण से लागू करने के लिए सुझाव है।

गंदे पढ़ने के लिए, आपको प्रत्येक फ़ील्ड का स्नैपशॉट रखना होगा, और UserSaveCanExecute() विधि से सत्य या गलत वापस लौटना होगा, जो तदनुसार कमांड बटन को सक्षम/अक्षम करेगा।

+0

मैं एमवीवीएम लाइट टूलकिट के साथ-साथ –

+3

का उपयोग करता हूं धन्यवाद, मैंने एमवीवीएम-लाइट टूलकिट स्थापित किया है, लेकिन मैंने "IsDirty" कार्यक्षमता को आसानी से कार्यान्वित करने का कोई तरीका नहीं देखा है। हालांकि मैंने अपनी समस्या को हल करने में कामयाब रहा है (शायद सबसे अच्छा तरीका नहीं - लेकिन यह काम करता है) - – Brendan

+0

विवरण के साथ थोड़ी देर बाद मेरे अपने प्रश्न का उत्तर देगा एमवीवीएम गंदे पढ़ने की कार्यक्षमता का समर्थन नहीं करता है। एमवीवीएम पैटर्न को लागू करने के प्रयास को कम करने के लिए यह केवल एक सुझाव था। यह जानना अच्छा है कि आपने पहले ही गंदा पढ़ा है। अब मैं हल्के टूलकिट में कमांडिंग के लिए आगे संदेश और घटना का पता लगाने का सुझाव दूंगा जो आपको अधिक नियंत्रण देगा। आपका समय अच्छा गुजरे। – ShahidAzim

7

मेरे अनुभव में, यदि आप अपने दृश्य मॉडल में IsDirty लागू करते हैं, तो आप शायद दृश्य मॉडल को IEditableObject लागू करना चाहते हैं। ,

यह मानते हुए कि आपके विचार मॉडल सामान्य प्रकार है PropertyChanged लागू करने और, IsDirty स्थापित करने के लिए एक निजी या OnPropertyChanged विधि है कि यह उठता है की रक्षा की पर्याप्त सरल है: तुम सिर्फ OnPropertyChanged में IsDirty सेट करता है, तो यह पहले से ही सच नहीं है।

आपका IsDirty सेटटर, यदि संपत्ति गलत थी और अब सच है, तो BeginEdit पर कॉल करें।

आपके Save कमांड को EndEdit पर कॉल करना चाहिए, जो डेटा मॉडल को अद्यतन करता है और IsDirty को गलत पर सेट करता है।

आपका Cancel कमांड CancelEdit पर कॉल करना चाहिए, जो डेटा मॉडल से दृश्य मॉडल को रीफ्रेश करता है और IsDirty को गलत पर सेट करता है।

CanSave और CanCancel गुण सिर्फ IsDirty के वर्तमान मूल्य वापसी (यदि आप इन आदेशों के लिए एक RelayCommand उपयोग कर रहे हैं यह सोचते हैं)।

ध्यान दें कि चूंकि इनमें से कोई भी कार्यक्षमता दृश्य मॉडल के विशिष्ट कार्यान्वयन पर निर्भर नहीं है, तो आप इसे एक सार आधार श्रेणी में डाल सकते हैं। व्युत्पन्न कक्षाओं में से किसी भी कमांड से संबंधित गुणों या IsDirty संपत्ति को लागू करने की आवश्यकता नहीं है; उन्हें सिर्फ BeginEdit, EndEdit, और CancelEdit ओवरराइड करना होगा।

0

इस प्रकार मैंने IsDirty को लागू किया है। उपयोगकर्ता व्यू की प्रत्येक प्रॉपर्टी के लिए एक रैपर बनाएं (आईपॉर्पर्टी के साथ उपयोगकर्ता वर्ग को विरासत में मिलाकर और उपयोगकर्ता क्लाउड सहायता में ऑनप्रॉपिचेंज लागू करने पर लागू) अपने व्यूमोडल में। आपको उपयोगकर्ता नाम से WrapUserName में अपना बाध्यकारी बदलने की आवश्यकता है।

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

अब चूंकि आपका viewmodal baseviewmodal से विरासत और baseviewmodal औजार onPropertyChanged एक संपत्ति

public bool isPageDirty 
    { 
     get; 
     set; 
    }  

है।

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

मामले में propertychanges के किसी भी, isPageDirty सच हो जाएगा, इसलिए जब आप चान बचत जाँच 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)); 
     } 
    } 
} 

के लिए जाँच करने के लिए बनाया है , मेरा व्यू मॉडेल थोड़ा बदल गया है ....

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

मैंने अपने व्यू मॉडेल में लिपटे मॉडल के लिए IsDirty को लागू करने पर कुछ काम किया है।

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 फ़ोल्डर, आप एक ModelDataStore वर्ग मिल जाएगा:

परिणाम वास्तव में मेरे ViewModels सरलीकृत।

पीएस मैंने इस पर पूर्ण पैमाने पर परीक्षण नहीं किया है, केवल वास्तव में सरल परीक्षण आपको टेस्ट असेंबली मिलेगा।