I am doing a draft of the C # WPF project, a universal one so to speak so that later you can just copy the entire folder for a new project, did this: - created folders and classes, moved MainWindow.xaml, it turned out like this enter image description here

corrected App.xaml

<Application x:Class="ШаблонMVVM.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ШаблонMVVM" StartupUri="View\MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application> 

added two lines in MainWindow.xaml

 <Window x:Class="ШаблонMVVM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ШаблонMVVM" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" xmlns:viewModel="clr-namespace:ШаблонMVVM.ViewModel" DataContext="{Binding ViewModel, ElementName=MainWindowInstance}"> <Grid> </Grid> </Window> 

in the code window did not touch anything

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace ШаблонMVVM { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } } 

ClassModel.cs

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel; namespace ШаблонMVVM.Model { class ClassModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; } } 

ClassViewModel.cs

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ШаблонMVVM.ViewModel { class ClassViewModel { } } 

Tell me 1) is it enough to call it a C # WPFMVVM project framework? 2) if not, what should be added and in which module?

frame I want to cook as if universal

  • I would immediately add a merge point View and ViewModel. Now they are not connected with you anywhere. I usually do this in App.cs, where I delete StartupURI and in the Startup event I create a window with a DataContext. - John
  • ICommand implementation is not enough) - Gardes
  • DataContext="{Binding ViewModel, ElementName=MainWindowInstance}" - what is this? It will not work. Essentially, in the most minimal version, you need: a) implementation of INPC, b) implementation of ICommand, c) the root of the place where the VM is created, the window and the link to that VM in the DataContext - Andrew NOP
  • DataContext = "{Binding ViewModel, ElementName = MainWindowInstance} - I thought this was the unification point of View and ViewModel - vova-forum
  • ICommand implementation is not enough) - Gardes - and how to add, this is a class? create a separate file? what to write to it - vova-forum

2 answers 2

To begin with my advice to you, forget about the Russian names of variables, projects, etc. This is not correct ( namespace ШаблонMVVM.Model )!

Regarding MVVM, what do we need to know the main thing? Well, for a start, what is it used for? And it is used to separate everything and everything into layers:

  • Model - your data (base, something loadable from the site or other data source).
  • View - What the user sees (the interface, all sorts of alerts and other things).
  • ViewModel - A certain connecting layer that helps to “collect” all one whole, here we describe the logic for the View, take data from the Model and other utility.

Here you should remember a couple of things:

  1. From the Model and ViewModel, you don’t have to interact with the data in the View layer (that is, MyTextBox1.Text = "Hello!" not appropriate in MVVM!). View should know only that in some ViewModel there can be some Text property from which it should take the text for Control. This property may not be at all (deleted, renamed), View on it will be roughly speaking anyway, nothing will fall and everything will work correctly (except of course this value). In other words, our interface is not independent of the code, which allows for example to develop the interface and code separately with two different commands, or to change the interface by removing unnecessary elements from it (without touching the code). This is the essence of the division of all the layers.
  2. Each Model / ViewModel should be responsible for its own one or the other. A rough example: For example, we have a student who has some books, a student is a kind of ViewModel with its own data set (name, surname, list of books, commands (read, give away) and other information associated only with it), the book is already another object, which must also have everything that is connected with it (well, for example, the name, author, release date, where it is located). That is, you should not have such that the ViewModel of the student is responsible for the functionality of the book, or vice versa!

Ok, now let's write a simple application:

  1. Create 3 folders Model, ViewModel and View.
  2. In View we transfer MainWindow (with all its giblets, we do not forget to change all ways).
  3. Create 3 classes in the ViewModel directory:

    1. BaseVM is a class that will implement INotifyPropertyChanged . What is it and what is it for? For example, we tied a certain property, say, to a TextBox, when this property changes from the code, the interface will not know about it and will not update the data. For this particular alert, and there is an INPC. There are a lot of implementations of this class on the Internet, I personally use this:

       public class BaseVM : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; NotifyPropertyChanged(propertyName); return true; } protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } 
    2. RelayCommand - Since our View should be independent of the ViewModel, we cannot just use Click and other events (although in some cases this is considered acceptable). Here commands come to help us, we create the property of this command and become attached, and even if it is not there - our program will not fall and will work as it should. Like VM, there are lots of implementations on the Internet, the most common one I think is described on this site , but again I will take another option:

       public class RelayCommand<T> : ICommand { private Action<T> action; public RelayCommand(Action<T> action) => this.action = action; public bool CanExecute(object parameter) => true; #pragma warning disable CS0067 public event EventHandler CanExecuteChanged; #pragma warning restore CS0067 public void Execute(object parameter) => action((T)parameter); } public class RelayCommand : ICommand { private Action action; public RelayCommand(Action action) => this.action = action; public bool CanExecute(object parameter) => true; #pragma warning disable CS0067 public event EventHandler CanExecuteChanged; #pragma warning restore CS0067 public void Execute(object parameter) => action(); } 
    3. MainViewModel - This is our main VM. I will not describe the code until we just create an empty class.

  4. Now let's set the DataContext. Honestly, I didn’t fully understand how to do it correctly, I remember one good person who beat me for specifying the DataContext in the XAML markup, and more recently I have read that it’s better to set it a higher level (in the App). By this, I will use the App here (who does not agree, let me know).

    1. We delete StartupUri="MainWindow.xaml" in App.xaml and subscribe to the Startup event there.
    2. We take the variable of our window outside the limits of the method (then suddenly we will need it), we write private MainWindow MainWindow; .
    3. In the method created by the studio (signature on the Startup event), we need to initialize our MainViewModel and set it to the window, and of course show it:

       private void App_OnStartup(object sender, StartupEventArgs e) { var mainViewModel = new MainViewModel(); MainWindow = new MainWindow { DataContext = mainViewModel }; MainWindow.Show(); } 

At this we can say that the "starting" template was created with us, it remains to write only logic.


The simplest example of how to interact with it is:

  1. Let's create in Model some model that will give data. Let's call her Student.

     public class Student { public string FirstName { get; set; } public string LastName { get; set; } public Student[] GetStudents() { var result = new[] { new Student{FirstName = "Маша", LastName = "Иванова"}, new Student{FirstName = "Витя", LastName = "Егоров"}, new Student{FirstName = "Женя", LastName = "Петров"} }; return result; } } 
  2. Next, we need our student's ViewModel, we will create, we will implement the necessary logic for the student in it:

     public class StudentViewModel : BaseVM { public StudentViewModel(string firstName, string lastName) { this.firstName = firstName; this.lastName = lastName; RenameCommand = new RelayCommand<string>(Rename); } private string firstName; public string FirstName { get => firstName; set => Set(ref firstName, value); } private string lastName; public string LastName { get => lastName; set => Set(ref lastName, value); } public ICommand RenameCommand { get; set; } private void Rename(string name) { FirstName = name; } } 
  3. Combine all this into MainViewModel:

     public class MainViewModel { public ObservableCollection<StudentViewModel> Students { get; set; } private Student StudentData; public MainViewModel() { StudentData = new Student(); Students = new ObservableCollection<StudentViewModel>(); LoadData(); } private void LoadData() { foreach (var student in StudentData.GetStudents()) Students.Add(new StudentViewModel(student.FirstName, student.LastName)); } } 
  4. It remains to write the simplest View:

     <ItemsControl ItemsSource="{Binding Students}"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="3*"/> <ColumnDefinition Width="1*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding FirstName}" Margin="0,0,5,0"/> <TextBlock Grid.Column="1" Text="{Binding LastName}" Margin="0,0,5,0"/> <TextBox Grid.Column="2" Name="changeText" Margin="0,0,5,0"/> <Button Grid.Column="3" Content="Изменить" Command="{Binding RenameCommand}" CommandParameter="{Binding ElementName=changeText, Path=Text}"/> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> 

That's all, we run and check:

Result

There are of course some nuances, I may not be accurate, but I think you will understand the main point.
Also (as correctly noted by @ tym32167), you still can’t do a certain universal template, each project has its own nuances and subtleties. In some projects, this approach will not be appropriate (where it may be better to use third-party frameworks ( MVVM Light ; Caliburn . Micro ; Prism ) to facilitate all of this, and somewhere else this will be enough), everything depends on the goals and the project, but for understanding how it all works, I think my answer will be quite enough for you.
Good luck in learning!

  • Comments are not intended for extended discussion; conversation moved to chat . - YurySPb

Enough of that. In your View template, it knows nothing about the Model, nor does Model know anything about the View. At the same time, View knows something about ViewModel, ViewModel knows something about Model, but not vice versa. In fact - all according to the canons of MVVM.

The only thing is that you can select a separate class of the "BaseViewModel" type, which all the ViewModel classes would inherit. And in it to implement the interface INPC. But this is not a strict requirement, so, recommendation