ПРОГРАММИРОВАНИЕ16 августа

Паттерн MVVM - просто о сложном. Пример на C#.

MVVM расшифровывается как Model-View-ViewModel, известный шаблон проектирования приложений, созданный Microsoft. Это позволяет разработчикам отделить логику пользовательского интерфейса от бизнес-правил. Например, пользовательская анимация или элементы рендеринга на экране будут вашей страницей. Тогда вызовы API для извлечения данных и загрузки этих данных в коллекции будут кодом ViewModel.

 

(Изображение предоставлено документацией Microsoft Xamarin)

Если вы не знакомы с шаблоном, вам следует ознакомиться с документацией Xamarin для MVVM, так как он подробно объясняет шаблон.

Простая привязка ViewModel

Рассмотрим этот простой пример ViewModel, который хранит a Message для отображения на экране для приложения Hello World.

1
2
3
4
public class MainViewModel
{
    public string Message => "Hello World from MainViewModel!";
}
 


После ViewModel создания нам не нужно делать с ним ничего особенного. Мы просто создаем его экземпляр или создаем новый в коде представления позади и назначаем его DataContext.

1
2
3
4
5
6
7
8
public sealed partial class MainPage
{
    public MainPage()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}
 

Теперь вы можете обновить код представления для привязки к Messageсвойству вместо жесткого кодирования строки в XAML.

1
2
3
4
5
6
7
8
9
10
11
12
13
<Page
    x:Class="ViewModelLocator.Views.MainPage"
    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"
    mc:Ignorable="d">
 
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{Binding Message}" Margin="20" FontSize="30" />
    </Grid>
     
</Page>
 

 

Локатор модели представления

Локатор моделей представлений — это метод в приложениях MVVM для автоматического назначения представлению ViewModelбез DataContextдополнительного кода, необходимого в представлении или коде позади. Этот метод становится все более полезным, когда вы хотите использовать внедрение зависимостей с вашим приложением MVVM. Это автоматически разрешит любые зависимости при создании ViewModelэкземпляра.

Как это работает?

Мы не добавляем BasePage и не переписываем основной код для решения этой проблемы. Мы собираемся использовать концепцию, известную как присоединенные свойства. Присоединенное свойство позволяет выполнять логику над открытыми членами объекта, которого нет в классе. Этот метод можно применять в конструкторе или в xaml. Если вы работали в приложениях Uno Platform, UWP или Xamarin.Forms, вы, скорее всего, использовали прикрепленное свойство и даже не знали об этом. Например, определение Grid и определение строки или столбца с Grid.Row="0" помощью прикрепленного свойства.

1
2
3
4
5
6
7
8
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
 
    <TextBlock Grid.Row="0" Text="This is using an attached property!" />
 
</Grid>
 
 

Если вы хотите узнать больше о прикрепленных свойствах, вам следует прочитать документацию Microsoft UWP по этому методу.

 

Использование ViewModelLocator

 

Мы собираемся сделать вещи немного в обратном направлении от большинства моих статей. Будет полезно увидеть окончательный API, прежде чем мы начнем.

Цель состоит в том, чтобы добавить 2 строки кода в наш XAML для регистрации файла ViewModelLocator. Давайте запланируем определить ViewModelLocatorв пространстве имен Mvvm. Нам нужно будет добавить следующие строки в наше представление

  1. Зарегистрируйте ViewModelLocator.Mvvmxmlns (пространство имен xml) в коде xaml. В нашем примере кода корневое пространство имен ViewModelLocator

  2. Используя новую ссылку xmlns, получите доступ к ней, ViewModelLocatorчтобы включить ее.
1
2
xmlns:mvvm="using:ViewModelLocator.Mvvm"
mvvm:ViewModelLocator.AutoWireViewModel="True"
 
 
Это должен быть весь код, необходимый для автоматической привязки ViewModel к View. Теперь, когда вы создаете все больше и больше отношений View/ViewModel, он автоматически создаст их экземпляр и назначит его представлению. Вот наш обновленный код всего представления.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Page
    x:Class="ViewModelLocator.Views.MainPage"
    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:mvvm="using:ViewModelLocator.Mvvm"
    mvvm:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d">
 
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{Binding Message}" Margin="20" FontSize="30" />
    </Grid>
     
</Page>
 

 

Создайте ViewModelLocator

Теперь, когда мы определили использование нашего API, мы можем создать, ViewModelLocator который будет реализовывать прикрепленное свойство, чтобы назначить правильное ViewModel значение DataContext

Давайте начнем с создания новой папки в вашем общем коде с именем Mvvm, а затем создадим ViewModelLocator класс.

 

Начните с создания базового прикрепленного свойства, которое включает в себя методы доступа, установки и измененного свойства.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class ViewModelLocator
{
    public static DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool),
        typeof(ViewModelLocator), new PropertyMetadata(false, AutoWireViewModelChanged));
 
    public static bool GetAutoWireViewModel(UIElement element)
    {
        return (bool)element.GetValue(AutoWireViewModelProperty);
    }
 
    public static void SetAutoWireViewModel(UIElement element, bool value)
    {
        element.SetValue(AutoWireViewModelProperty, value);
    }
 
    private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // TODO - Implement binding
    }
}
 

Это самый простой код, который вам понадобится для реализации прикрепленного свойства. При AutoWireViewModelChanged вызове он будет передан на страницу в качестве параметра. Любой объект в платформе Uno, наследуемый от DependencyObject которого является переданным базовым объектом. Это позволяет использовать более сложные сценарии, но мы сосредоточимся на простой привязке страницы.

Для этого нам нужно реализовать 2 шага

  1. FindViewModel — этот метод определяет, где находится ViewModel, и возвращает тип
  2. Создание экземпляра . После того, как ViewModel найден, его необходимо установить в DataContext.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static Type FindViewModel(Type viewType)
{
    string viewName = string.Empty;
 
    if (viewType.FullName.EndsWith("Page"))
    {
        viewName = viewType.FullName
            .Replace("Page", string.Empty)
            .Replace("Views", "ViewModels");
    }
 
    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
    var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);
 
    return Type.GetType(viewModelName);
}
 

 

Наша логика здесь смотрит на текущую страницу или DependencyObjectпытается найти соответствующую ViewModel на основе имени. Например, наш код смотрит MainPageи понимает наши конвенционные карты MainViewModel. Оба этих класса содержат ключевое слово Main, которое генерирует сопоставление. Затем метод использует отражение, чтобы получить тип объекта и вернуть его. Ваша FindViewModelлогика может отличаться, если у вас разные пространства имен, в которых существуют ваши ViewModels.

После того, как вы определили свой FindViewModelметод, мы можем реализовать Bindметод. Этот метод примет возвращаемый тип и создаст его экземпляр, который будет применен к DataContext.

  1. Проверьте, DependencyObject является ли файл FrameworkElement. Единственный DataContext существует на, FrameworkElement поэтому нам нужно убедиться, что у нас есть правильный тип.

  2. Используйте FineViewModel метод и получите правильный тип ViewModel.

  3. Создайте новый экземпляр модели представления и сохраните его в файле DataContext.
1
2
3
4
5
6
7
8
private static void Bind(DependencyObject view)
{
    if (view is FrameworkElement frameworkElement)
    {
        var viewModelType = FindViewModel(frameworkElement.GetType());
        frameworkElement.DataContext = Activator.CreateInstance(viewModelType);
    }
}
 

 

В этом примере используется Activator для создания нового экземпляра модели представления и является основным примером локатора модели представления. Чтобы расширить это, я бы обновил Activator вызов, используя контейнер внедрения зависимостей, чтобы разрешить модель представления. Это гарантирует, что все зависимости будут внедрены без какой-либо дополнительной работы.

Если вы используете библиотеку Microsoft.Extensions.DependencyInjection, вы можете заменить Activator вызов на ActivatorUtilities. Мы не собираемся рассматривать внедрение зависимостей в этой статье, но если вы хотите еще больше расширить эту технику, вы можете найти этот фрагмент полезным.

1
frameworkElement.DataContext = ActivatorUtilities.GetServiceOrCreateInstance(((App)App.Current).Container, viewModelType);
 
 

После того, как вы создали Bindметод, вы можете обновить AutoWireViewModelChangedреализацию, чтобы использовать этот Bindметод. В качестве проверки безопасности я всегда гарантирую, что это новая переменная перед применением привязки, вы не хотите постоянно переустанавливать модель представления.

1
2
3
4
5
private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if ((bool)e.NewValue)
        Bind(d);
}
 

 

Теперь вы создали свой ViewModelLocator код, чтобы убедиться, что ваш код правильный. Здесь я собрал полный код в одном фрагменте.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public static class ViewModelLocator
{
    public static DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool),
        typeof(ViewModelLocator), new PropertyMetadata(false, AutoWireViewModelChanged));
 
    public static bool GetAutoWireViewModel(UIElement element)
    {
        return (bool)element.GetValue(AutoWireViewModelProperty);
    }
 
    public static void SetAutoWireViewModel(UIElement element, bool value)
    {
        element.SetValue(AutoWireViewModelProperty, value);
    }
 
    private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
            Bind(d);
    }
 
    private static void Bind(DependencyObject view)
    {
        if (view is FrameworkElement frameworkElement)
        {
            var viewModelType = FindViewModel(frameworkElement.GetType());
            frameworkElement.DataContext = Activator.CreateInstance(viewModelType);
        }
    }
 
    private static Type FindViewModel(Type viewType)
    {
        string viewName = string.Empty;
 
        if (viewType.FullName.EndsWith("Page"))
        {
            viewName = viewType.FullName
                .Replace("Page", string.Empty)
                .Replace("Views", "ViewModels");
        }
 
        var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
        var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName);
 
        return Type.GetType(viewModelName);
    }
}
 
 

На этом этапе вы можете запустить свое приложение, и ваша модель представления будет автоматически подключена к представлению. Любой новый созданный вами объект View/ViewModel будет связан простым добавлением двух операторов xaml, упомянутых ранее.

Заключение о MVVM 

Это все, что вам нужно для настройки View Model Locator в вашем приложении MVVM. Целью локатора модели представления является автоматическое подключение вашего представления к модели представления. Прогрессом в методах здесь будет использование внедрения зависимостей в вашем локаторе модели представления для автоматического внедрения зависимостей в вашу модель представления.

Если у вас возникли проблемы с этой настройкой, обязательно ознакомьтесь с моим примером кода.

Источник What is MVVM

 

Ссылки на статьи

Делегат в Си Шарп

Диалог в WPF C#. Работа с файловой системой. Библиотека Оokii Dialogs WPF

Работа с файловой системой

Библиотека Оokii Dialogs WPF

MVVM - просто о сложном. Пример на C#

MVVM и внедрение зависимостей Часть 1

MVVM и внедрение зависимостей Часть 2

Комментарии

Добавить комментарий:

Отметьте что вы не робот

Вернуться наверх