खोज…
टिप्पणियों
मॉडल और दृश्य-मॉडल
एक मॉडल की परिभाषा पर अक्सर गर्म बहस की जाती है, और एक मॉडल और एक दृश्य-मॉडल के बीच की रेखा धुंधली हो सकती है। कुछ INotifyPropertyChanged
इंटरफ़ेस के साथ अपने मॉडल को "प्रदूषित" नहीं करना पसंद करते हैं, और इसके बजाय दृश्य-मॉडल में मॉडल गुणों की नकल करते हैं, जो इस इंटरफ़ेस को लागू करता है। सॉफ्टवेयर विकास में कई चीजों की तरह, कोई सही या गलत उत्तर नहीं है। व्यावहारिक रहें और जो सही लगे उसे करें।
पृथक्करण देखें
MVVM का इरादा उन तीन अलग-अलग क्षेत्रों - मॉडल, व्यू-मॉडल और व्यू को अलग करना है। यद्यपि यह दृश्य-मॉडल (VM) और (अप्रत्यक्ष रूप से) मॉडल तक पहुँचने के लिए स्वीकार्य है, लेकिन MVVM के साथ सबसे महत्वपूर्ण नियम यह है कि VM के पास व्यू या इसके नियंत्रण की कोई पहुँच नहीं होनी चाहिए। सार्वजनिक संपत्तियों के माध्यम से वीएम को वह सब कुछ उजागर करना चाहिए जो देखने की जरूरत है। VM को UI नियंत्रण जैसे TextBox
, Button
आदि को सीधे उजागर या हेरफेर नहीं करना चाहिए।
कुछ मामलों में, इस सख्त पृथक्करण के साथ काम करना मुश्किल हो सकता है, खासकर यदि आपको कुछ जटिल यूआई कार्यक्षमता प्राप्त करने और चलाने की आवश्यकता है। यहां, यह "कोड-पीछे" फ़ाइल के दृश्य में ईवेंट और इवेंट हैंडलर का उपयोग करने के लिए पूरी तरह से स्वीकार्य है। यदि यह विशुद्ध रूप से यूआई कार्यक्षमता है, तो हर तरह से दृश्य में घटनाओं का उपयोग करें। इन ईवेंट हैंडलर्स के लिए वीएम उदाहरण पर सार्वजनिक तरीकों को कॉल करना भी स्वीकार्य है - बस इसे यूआई नियंत्रण या इस तरह के किसी भी संदर्भ से गुजरना नहीं है।
RelayCommand
दुर्भाग्य से RelayCommand
वर्ग इस उदाहरण में इस्तेमाल WPF ढांचे का हिस्सा नहीं है (यह होना चाहिए था!), लेकिन आप इसे लगभग हर WPF डेवलपर टूल बॉक्स में पाएंगे। एक त्वरित खोज ऑनलाइन बहुत सारे कोड स्निपेट्स को प्रकट करेगी जो आप उठा सकते हैं, अपना खुद का बनाने के लिए।
के लिए एक उपयोगी विकल्प RelayCommand
है ActionCommand
जो के हिस्से के रूप प्रदान की जाती है Microsoft.Expression.Interactivity.Core
जिसमें तुलनात्मक कार्यक्षमता प्रदान करता है।
WPF और C # का उपयोग करके बेसिक MVVM उदाहरण
WPF और C # का उपयोग करके, विंडोज़ डेस्कटॉप एप्लिकेशन में MVVM मॉडल का उपयोग करने के लिए यह एक बेसिक उदाहरण है। उदाहरण कोड एक सरल "उपयोगकर्ता जानकारी" संवाद को लागू करता है।
देखें
XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="4" Text="{Binding FullName}" HorizontalAlignment="Center" FontWeight="Bold"/>
<Label Grid.Column="0" Grid.Row="1" Margin="4" Content="First Name:" HorizontalAlignment="Right"/>
<!-- UpdateSourceTrigger=PropertyChanged makes sure that changes in the TextBoxes are immediately applied to the model. -->
<TextBox Grid.Column="1" Grid.Row="1" Margin="4" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="2" Margin="4" Content="Last Name:" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Grid.Row="2" Margin="4" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="3" Margin="4" Content="Age:" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="1" Grid.Row="3" Margin="4" Text="{Binding Age}" HorizontalAlignment="Left"/>
</Grid>
और पीछे कोड
public partial class MainWindow : Window
{
private readonly MyViewModel _viewModel;
public MainWindow() {
InitializeComponent();
_viewModel = new MyViewModel();
// The DataContext serves as the starting point of Binding Paths
DataContext = _viewModel;
}
}
देखें मॉडल
// INotifyPropertyChanged notifies the View of property changes, so that Bindings are updated.
sealed class MyViewModel : INotifyPropertyChanged
{
private User user;
public string FirstName {
get {return user.FirstName;}
set {
if(user.FirstName != value) {
user.FirstName = value;
OnPropertyChange("FirstName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
public string LastName {
get { return user.LastName; }
set {
if (user.LastName != value) {
user.LastName = value;
OnPropertyChange("LastName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
// This property is an example of how model properties can be presented differently to the View.
// In this case, we transform the birth date to the user's age, which is read only.
public int Age {
get {
DateTime today = DateTime.Today;
int age = today.Year - user.BirthDate.Year;
if (user.BirthDate > today.AddYears(-age)) age--;
return age;
}
}
// This property is just for display purposes and is a composition of existing data.
public string FullName {
get { return FirstName + " " + LastName; }
}
public MyViewModel() {
user = new User {
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Now.AddYears(-30)
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName) {
if(PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
आदर्श
sealed class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
दृश्य-मॉडल
एमवी वीएम में व्यू-मॉडल "वीएम" है। यह एक वर्ग है जो एक गो-के बीच काम करता है, उपयोगकर्ता इंटरफ़ेस (दृश्य) के लिए मॉडल (एस) को उजागर करता है, और दृश्य से अनुरोधों को संभालता है, जैसे कि बटन क्लिक द्वारा उठाए गए आदेश। यहाँ एक बुनियादी दृश्य-मॉडल है:
public class CustomerEditViewModel
{
/// <summary>
/// The customer to edit.
/// </summary>
public Customer CustomerToEdit { get; set; }
/// <summary>
/// The "apply changes" command
/// </summary>
public ICommand ApplyChangesCommand { get; private set; }
/// <summary>
/// Constructor
/// </summary>
public CustomerEditViewModel()
{
CustomerToEdit = new Customer
{
Forename = "John",
Surname = "Smith"
};
ApplyChangesCommand = new RelayCommand(
o => ExecuteApplyChangesCommand(),
o => CustomerToEdit.IsValid);
}
/// <summary>
/// Executes the "apply changes" command.
/// </summary>
private void ExecuteApplyChangesCommand()
{
// E.g. save your customer to database
}
}
कंस्ट्रक्टर एक Customer
मॉडल ऑब्जेक्ट बनाता है और उसे CustomerToEdit
प्रॉपर्टी में असाइन करता है, ताकि यह दृश्य में दिखाई दे।
कंस्ट्रक्टर एक RelayCommand
ऑब्जेक्ट भी बनाता है और इसे ApplyChangesCommand
प्रॉपर्टी को असाइन करता है, फिर से दृश्य को दृश्यमान बनाता है। WPF कमांड का उपयोग दृश्य से अनुरोधों को संभालने के लिए किया जाता है, जैसे बटन या मेनू आइटम क्लिक।
RelayCommand
दो पैरामीटर लेता है - पहला वह प्रतिनिधि होता है जिसे कमांड निष्पादित होने पर बुलाया जाता है (उदाहरण के लिए एक बटन क्लिक के जवाब में)। दूसरा पैरामीटर एक प्रतिनिधि है जो एक बूलियन मान लौटाता है जो यह दर्शाता है कि कमांड निष्पादित कर सकता है; इस उदाहरण में यह ग्राहक की IsValid
संपत्ति के लिए IsValid
है। जब यह गलत हो जाता है, तो यह बटन या मेनू आइटम को निष्क्रिय कर देता है जो इस कमांड से जुड़ा होता है (अन्य नियंत्रण अलग तरीके से व्यवहार कर सकते हैं)। यह एक सरल लेकिन प्रभावी सुविधा है, जो विभिन्न स्थितियों के आधार पर नियंत्रण को सक्षम या अक्षम करने के लिए कोड लिखने की आवश्यकता से बचती है।
यदि आपको यह उदाहरण मिलता है और चल रहा है, तो TextBox
es में से एक को खाली करने का प्रयास करें ( Customer
मॉडल को अमान्य स्थिति में रखने के लिए)। जब आप TextBox
से दूर जाते हैं, तो आपको यह पता लगाना चाहिए कि "लागू करें" बटन अक्षम हो गया है।
ग्राहक निर्माण पर टिप्पणी
दृश्य-मॉडल INotifyPropertyChanged
(INPC) को लागू नहीं करता है। इसका मतलब यह है कि अगर एक अलग Customer
वस्तु को CustomerToEdit
संपत्ति को सौंपा जाना था, तो नए ऑब्जेक्ट को प्रतिबिंबित करने के लिए दृश्य के नियंत्रण नहीं बदलेंगे - TextBox
अभी भी पिछले ग्राहक का नाम और उपनाम होगा।
उदाहरण कोड काम करता है क्योंकि Customer
को व्यू-मॉडल के कंस्ट्रक्टर में बनाया जाता है, इससे पहले कि वह व्यू के DataContext
को सौंपा जाए (जिस बिंदु पर बाइंडिंग को वायर्ड किया जाता है)। एक वास्तविक दुनिया के अनुप्रयोग में आप एक डेटाबेस से कंस्ट्रक्टर के अलावा अन्य तरीकों से ग्राहकों को पुनः प्राप्त कर सकते हैं। इसका समर्थन करने के लिए, VM को INPC को लागू करना चाहिए, और CustomerToEdit
संपत्ति को "विस्तारित" गेट्टर और सेटर पैटर्न का उपयोग करने के लिए बदला जाना चाहिए, जिसे आप उदाहरण मॉडल कोड में देखते हैं, सेट्टर में PropertyChanged
घटना को बढ़ाते हैं।
दृश्य-मॉडल के ApplyChangesCommand
को INPC को लागू करने की आवश्यकता नहीं है क्योंकि कमांड को बदलने की बहुत संभावना नहीं है। आपको इस पैटर्न को लागू करने की आवश्यकता होगी यदि आप कमांडर को कंस्ट्रक्टर के अलावा कहीं और बना रहे हैं, उदाहरण के लिए किसी तरह का Initialize()
तरीका।
सामान्य नियम यह है: यदि संपत्ति किसी भी दृश्य नियंत्रण के लिए बाध्य है तो INPC को लागू करें और संपत्ति का मूल्य निर्माणकर्ता के अलावा कहीं भी बदलने में सक्षम है। यदि संपत्ति का मूल्य केवल कंस्ट्रक्टर में सौंपा गया है (और आप इस प्रक्रिया में कुछ टाइपिंग से खुद को बचा लेंगे) तो आपको INPC को लागू करने की आवश्यकता नहीं है।
आदर्श
मॉडल एम वीवीएम में पहला "एम" है। मॉडल आमतौर पर एक वर्ग होता है जिसमें डेटा होता है जिसे आप किसी प्रकार के उपयोगकर्ता इंटरफ़ेस के माध्यम से उजागर करना चाहते हैं।
यहाँ कुछ गुणों का खुलासा करने वाला एक बहुत ही सरल मॉडल वर्ग है: -
public class Customer : INotifyPropertyChanged
{
private string _forename;
private string _surname;
private bool _isValid;
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Customer forename.
/// </summary>
public string Forename
{
get
{
return _forename;
}
set
{
if (_forename != value)
{
_forename = value;
OnPropertyChanged();
SetIsValid();
}
}
}
/// <summary>
/// Customer surname.
/// </summary>
public string Surname
{
get
{
return _surname;
}
set
{
if (_surname != value)
{
_surname = value;
OnPropertyChanged();
SetIsValid();
}
}
}
/// <summary>
/// Indicates whether the model is in a valid state or not.
/// </summary>
public bool IsValid
{
get
{
return _isValid;
}
set
{
if (_isValid != value)
{
_isValid = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// Sets the value of the IsValid property.
/// </summary>
private void SetIsValid()
{
IsValid = !string.IsNullOrEmpty(Forename) && !string.IsNullOrEmpty(Surname);
}
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
यह वर्ग INotifyPropertyChanged
इंटरफ़ेस को लागू करता है जो एक PropertyChanged
घटना को उजागर करता है। जब भी कोई संपत्ति मान बदलता है तो इस घटना को उठाया जाना चाहिए - आप इसे उपरोक्त कोड में कार्रवाई में देख सकते हैं। PropertyChanged
घटना WPF डेटा-बाइंडिंग तंत्रों में एक महत्वपूर्ण टुकड़ा है, क्योंकि इसके बिना, उपयोगकर्ता इंटरफ़ेस किसी संपत्ति के मूल्य में किए गए परिवर्तनों को प्रतिबिंबित करने में सक्षम नहीं होगा।
मॉडल में एक बहुत ही सरल सत्यापन रूटीन भी शामिल है जो संपत्ति बसने वालों से कहा जाता है। यह सार्वजनिक संपत्ति को दर्शाता है कि मॉडल एक वैध स्थिति में है या नहीं। मैंने WPF कमांड के "विशेष" फीचर को प्रदर्शित करने के लिए इस कार्यक्षमता को शामिल किया है, जिसे आप शीघ्र ही देखेंगे। WPF फ्रेमवर्क सत्यापन के लिए कई अधिक परिष्कृत दृष्टिकोण प्रदान करता है, लेकिन ये इस लेख के दायरे से बाहर हैं ।
देखें
M V VM में व्यू "V" है। यह आपका यूजर इंटरफेस है। आप विज़ुअल स्टूडियो ड्रैग-एंड-ड्रॉप डिजाइनर का उपयोग कर सकते हैं, लेकिन अधिकांश डेवलपर्स अंततः कच्चे एक्सएएमएल कोडिंग को समाप्त करते हैं - HTML लिखने के समान अनुभव।
Customer
मॉडल के संपादन की अनुमति देने के लिए यहां एक सरल दृश्य का XAML है। एक नया दृश्य बनाने के बजाय, इसे केवल WPF प्रोजेक्ट के MainWindow.xaml
फ़ाइल में चिपकाया जा सकता है, बीच में <Window ...>
और </Window>
टैग: -
<StackPanel Orientation="Vertical"
VerticalAlignment="Top"
Margin="20">
<Label Content="Forename"/>
<TextBox Text="{Binding CustomerToEdit.Forename}"/>
<Label Content="Surname"/>
<TextBox Text="{Binding CustomerToEdit.Surname}"/>
<Button Content="Apply Changes"
Command="{Binding ApplyChangesCommand}" />
</StackPanel>
यह कोड एक साधारण डेटा एंट्री फॉर्म बनाता है जिसमें दो TextBox
शामिल हैं - एक ग्राहक के नाम के लिए, और एक उपनाम के लिए। प्रत्येक TextBox
ऊपर एक Label
है, और फ़ॉर्म के नीचे एक "लागू करें" Button
होता है।
पहला Text
TextBox
ढूंढें और इसे Text
प्रॉपर्टी देखें:
Text="{Binding CustomerToEdit.Forename}"
TextBox
के पाठ को एक निश्चित मान पर सेट करने के बजाय, यह विशेष घुंघराले ब्रेस सिंटैक्स पाठ को "पथ" CustomerToEdit.Forename
बजाय बाइंड करने के लिए है। इस पथ के सापेक्ष क्या है? यह दृश्य का "डेटा संदर्भ" है - इस मामले में, हमारा दृश्य-मॉडल। बाध्यकारी पथ, जैसा कि आप समझ सकते हैं, दृश्य-मॉडल का CustomerToEdit
गुण है, जो कि प्रकार का Customer
है जो बदले में Forename
नामक एक संपत्ति को उजागर करता है - इसलिए "बिंदीदार" पथ संकेतन।
इसी तरह, यदि आप Button
के XAML को देखते हैं, तो इसमें एक Command
है जो व्यू-मॉडल के ApplyChangesCommand
संपत्ति के लिए बाध्य है। वीएम की कमांड के लिए एक बटन को वायर करने की जरूरत है।
DataContext
तो आप व्यू-मॉडल को दृश्य के डेटा संदर्भ के रूप में कैसे सेट करते हैं? एक तरीका इसे दृश्य के "कोड-पीछे" में सेट करना है। इस कोड फ़ाइल को देखने के लिए F7 दबाएं, और व्यू-मॉडल का एक उदाहरण बनाने के लिए मौजूदा कंस्ट्रक्टर में एक लाइन जोड़ें और इसे विंडो के DataContext
प्रॉपर्टी में असाइन करें। इसे इस तरह देखना चाहिए:
public MainWindow()
{
InitializeComponent();
// Our new line:-
DataContext = new CustomerEditViewModel();
}
वास्तविक विश्व प्रणालियों में, दृश्य मॉडल बनाने के लिए अक्सर अन्य दृष्टिकोणों का उपयोग किया जाता है, जैसे कि निर्भरता इंजेक्शन या एमवीवीएम फ्रेमवर्क।
एमवीवीएम में कमांडिंग
MVVM- पैटर्न का सम्मान करते हुए WPF में Events
को संभालने के लिए कमांड का उपयोग किया जाता है।
एक सामान्य EventHandler
इस तरह दिखेगा ( Code-Behind
में स्थित):
public MainWindow()
{
_dataGrid.CollectionChanged += DataGrid_CollectionChanged;
}
private void DataGrid_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Do what ever
}
कोई MVVM में भी ऐसा ही करने का उपयोग हम Commands
:
<Button Command="{Binding Path=CmdStartExecution}" Content="Start" />
मैं आपके आदेश गुणों के लिए किसी प्रकार के उपसर्ग (
Cmd
) का उपयोग करने की सलाह देता हूं, क्योंकि आपको मुख्य रूप से xaml में उनकी आवश्यकता होगी - इस तरह से उन्हें पहचानना आसान है।
चूंकि यह MVVM है, आप अपने ViewModel
में उस Command (For Button
"eq" Button_Click
) को संभालना चाहते हैं।
इसके लिए हमें मूल रूप से दो चीजों की जरूरत है:
एक साधारण उदाहरण इस तरह दिख सकता है:
private RelayCommand _commandStart;
public ICommand CmdStartExecution
{
get
{
if(_commandStart == null)
{
_commandStart = new RelayCommand(param => Start(), param => CanStart());
}
return _commandStart;
}
}
public void Start()
{
//Do what ever
}
public bool CanStart()
{
return (DateTime.Now.DayOfWeek == DayOfWeek.Monday); //Can only click that button on mondays.
}
तो यह विस्तार से क्या कर रहा है:
ICommand
है जो xaml में Control
लिए बाध्य है। RelayCommand
आपकी कमांड को एक Action
(यानी कॉल ए Method
) में रूट करेगा। नल-चेक केवल यह सुनिश्चित करता है कि प्रत्येक Command
केवल एक बार (प्रदर्शन मुद्दों के कारण) आरंभ हो जाएगा। यदि आपने ऊपर दिए गए RelayCommand
के लिंक को पढ़ा है तो आपने देखा होगा कि RelayCommand
पास इसके निर्माण के लिए दो अधिभार हैं। (Action<object> execute)
और (Action<object> execute, Predicate<object> canExecute)
।
इसका मतलब है कि आप (वस्तुतः) एक दूसरी Method
जोड़कर एक bool
लौटा सकते हैं, यह बताने के लिए कि "घटना" को Control
कर सकते हैं कि आग लग सकती है या नहीं।
कि के लिए एक अच्छी बात यह है कि Button
रों उदाहरण के लिए किया जाएगा Enabled="false"
यदि Method
वापस आ जाएगी false
CommandParameters
<DataGrid x:Name="TicketsDataGrid">
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding CmdTicketClick}"
CommandParameter="{Binding ElementName=TicketsDataGrid,
Path=SelectedItem}" />
</DataGrid.InputBindings>
<DataGrid />
इस उदाहरण में मैं अपने ViewModel में Click_Command के लिए DataGrid.SelectedItem
पास करना चाहता हूं।
आपकी विधि इस तरह दिखनी चाहिए जबकि ICommand कार्यान्वयन स्वयं ऊपर के रूप में रहता है।
private RelayCommand _commandTicketClick;
public ICommand CmdTicketClick
{
get
{
if(_commandTicketClick == null)
{
_commandTicketClick = new RelayCommand(param => HandleUserClick(param));
}
return _commandTicketClick;
}
}
private void HandleUserClick(object item)
{
MyModelClass selectedItem = item as MyModelClass;
if (selectedItem != null)
{
//Do sth. with that item
}
}