Мауи MVVM: самый простой шаблон не работает

Я работаю над небольшим дополнением к проекту, используя шаблон MVVM.

Проект немного больше, но я сократил его до небольшого примера, чтобы упростить задачу.

Код

Бусивиевмодел.cs

using CommunityToolkit.Mvvm.ComponentModel;

namespace MauiApp6.ViewModels
{
    public partial class BusyViewModel : ObservableObject
    {
        [ObservableProperty]
        private bool isBusy = false;
    }
}

Коммандвиевмодел.cs

using CommunityToolkit.Mvvm.Input;

namespace MauiApp6.ViewModels
{
    public partial class CommandViewModel
    {
        private BusyViewModel busyViewModel;

        public CommandViewModel()
        {
            // Initialize BusyViewModel
            busyViewModel = new BusyViewModel();
        }

        [RelayCommand()]
        internal void Click()
        {
            // Set IsBusy to true
            busyViewModel.IsBusy = true;
        }
    }
}

MainPage.xaml

<?xml version = "1.0" encoding = "utf-8" ?>
<ContentPage xmlns = "http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm = "clr-namespace:MauiApp6.ViewModels"
             x:Class = "MauiApp6.MainPage" >

    <StackLayout>
        <Button Text = "Click Me" 
                BindingContext = "{Binding Source = {vm:CommandViewModel}}"
                Command = "{Binding ClickCommand}"/>

        <Label BindingContext = "{Binding Source = {vm:BusyViewModel}}"
               Text = "{Binding IsBusy}"/>

        <ActivityIndicator BindingContext = "{Binding Source = {vm:BusyViewModel}}"
                           IsRunning = "{Binding IsBusy}"/>
    </StackLayout>
</ContentPage>

ожидаемый результат

After clicking on the button
  • Команда ClickCommand срабатывает
  • Текст метки изменится на «истина».
  • Индикатор активности начинает вращаться

Фактический результат

After clicking the button
  • Команда ClickCommand срабатывает
  • Никаких видимых изменений не происходит

Вопрос

  • Какую часть мне не хватает? Я не могу найти - скорее всего, очень небольшую - разницу с бесчисленными примерами в сети.

Команда busyViewModel.IsBusy = true; изменяет значение busyViewModel CommandViewModel. Привязали ли вы busyViewModel CommandViewModel к метке и ActivityIndicator?

Liyun Zhang - MSFT 25.06.2024 13:36

Другими словами, являются ли BusyViewModel CommandViewModel и BindingContext = "{Binding Source = {vm:BusyViewModel} одним и тем же экземпляром BusyViewModel?

Liyun Zhang - MSFT 25.06.2024 13:37

Аннотируйте «частную BusyViewModel busyViewModel;» с [ObservableProperty]. Привяжите свойство BindingViewModel с помощью {Binding BindingViewModel.IsBusy}. stackoverflow.com/questions/76094858/… Здесь, в вопросе, вы можете увидеть, как настроена структура.

H.A.H. 25.06.2024 14:59

@LiyunZhang-MSFT вы имеете в виду что-то вроде CominedViewModel, который содержит экземпляр BusyVM и CommandVM?

Evolyzer 25.06.2024 15:29

У вас есть три экземпляра виртуальной машины — Command, Busy и экземпляр Busy, встроенный в команду. Изменения в одном экземпляре Busy не влияют на другой экземпляр Busy.

Jason 25.06.2024 15:55

Я опубликовал ответ, чтобы объяснить ваш код и решение. Кроме того, вы можете прочитать официальный документ о привязке данных, чтобы узнать.

Liyun Zhang - MSFT 25.06.2024 16:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
94
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы неправильно используете BindingContext, вам нужно следующее:

Во-первых, вам нужно назначить BindingContext вашей странице. Как только вы это сделаете, вы назначите его как тип данных, чтобы он был скомпилирован и привязан к вашему XAML, все остальное сделает само.

public MainPage()
{
  InitializeComponent();
  BindingContext= new CommandViewModel();
}

В вашей главной странице XAML

<?xml version = "1.0" encoding = "utf-8" ?>
<ContentPage xmlns = "http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm = "clr-namespace:MauiApp6.ViewModels"
             x:DataType = "vm:CommandViewModel"
             x:Class = "MauiApp6.MainPage" >
 <StackLayout>
        <Button Text = "Click Me" 
                Command = "{Binding ClickCommand}"/>

        <Label Text = "{Binding IsBusy}"/>

        <ActivityIndicator IsRunning = "{Binding IsBusy}"/>
 </StackLayout>
</ContentPage>

Кроме того, я не понимаю, в чем цель BusyViewModel.

Он использует вложенные наблюдаемые объекты. Его внешний наблюдаемый объект — CommandViewModel — имеет вложенное поле наблюдаемого объекта BusyViewModel, и это поле имеет собственное поле IsBusy. Он очень хорошо знает, как привязываться к внешним ViewModels, как вы можете прочитать из его вопроса - привязка команды РАБОТАЕТ. Если вы собираетесь написать ответ, уважайте его организацию классов или направьте его к одному из существующих ответов для вложенных моделей представления.

H.A.H. 25.06.2024 13:56

@Х.А.Х. Я хочу сказать, что поле не может быть привязано, поэтому я не понимаю цели BusyViewModel: либо это должно быть свойство, на которое можно ссылаться, либо базовый класс...

FreakyAli 25.06.2024 14:34

@FreakyAli Насколько мне известно, [ObservableProperty] private bool isBusy = false; равно ``` Private bool isBus = false; public bool IsBusy {get => isBusy; set => SetProperty(ref isBusy, value); } ``` Итак, isBusy — это поле, а IsBusy — свойство. Я ошибаюсь?

Evolyzer 25.06.2024 15:01

@Evolyzer, конечно, можно привязать. Но в каком контексте? Ваш BindingContext — CommandViewModel. Если вы видите этот ответ, Фрики ставит "x:DataType = "vm:CommandViewModel". Это очень важно. По нескольким причинам. Тогда он может просто выполнить {Binding ClickCommand}. И intellisense сообщит ему, если в этой модели представления нет ClickCommand. Если вы не знаете свой тип BindingContext до запуска программы, это к вам не относится, но вам все равно нужно использовать вложенное свойство для доступа к вашим вложенным моделям представления. дайте ссылку на комментарии Прочтите.

H.A.H. 25.06.2024 15:11

@Evolyzer Нет, я об этом private BusyViewModel busyViewModel;

FreakyAli 25.06.2024 17:22

Прежде всего, код, который вы использовали:

       <Button Text = "Click Me" 
                BindingContext = "{Binding Source = {vm:CommandViewModel}}"
                Command = "{Binding ClickCommand}"/>

        <Label BindingContext = "{Binding Source = {vm:BusyViewModel}}"
               Text = "{Binding IsBusy}"/>

        <ActivityIndicator BindingContext = "{Binding Source = {vm:BusyViewModel}}"
                           IsRunning = "{Binding IsBusy}"/>

Каждый из BindingContext = "{Binding Source = {vm:xxx} в xaml создаст новый экземпляр класса xxx. Таким образом, контекст привязки ActivityIndicator и Label относятся к типу BusyViewModel, но являются разными экземплярами.

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

Кроме того, вам не нужно задавать контекст привязки для каждого элемента управления на странице. Вы можете просто установить BindingContext = "{Binding Source = {vm:xxx} для ContentPage. И тогда все дочерние элементы управления на странице содержимого будут иметь класс привязкиcontext: xxx.

Вы можете прочитать официальный документ о Наследование контекста привязки.

И в вашем случае вы можете просто установить BindingContext MainPage как CommandViewModel.

public partial class CommandViewModel
    {
        [ObservableProperty]
        private BusyViewModel busyVM;

        public CommandViewModel()
        {
            // Initialize BusyViewModel
            busyVM = new BusyViewModel();
        }

        [RelayCommand()]
        internal void Click()
        {
            // Set IsBusy to true
            busyVM.IsBusy = true;
        }
    }

И в xaml:

<ContentPage xmlns = "http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm = "clr-namespace:MauiApp6.ViewModels"
             BindingContext = "{Binding Source = {vm:CommandViewModel}}"
             x:Class = "MauiApp6.MainPage" >

    <StackLayout>
        <Button Text = "Click Me" 
                Command = "{Binding ClickCommand}"/>

        <Label Text = "{Binding BusyVM.IsBusy}"/>

        <ActivityIndicator IsRunning = "{Binding BusyVM.IsBusy}"/>
    </StackLayout>
</ContentPage>
Ответ принят как подходящий

Проблема в том, что у вас есть два экземпляра BusyViewModel: модель представления, созданная вами для метки, и модель представления, встроенная в CommandViewModel. ClickCommand изменяется в модели, встроенной в CommandViewModel, и не меняется в BusyViewModel метки, поэтому видимых изменений не произойдет.

решение такое, как предложил @Liyun Zang - MSFT, вам следует создать один экземпляр CommandViewModel и установить команду кнопки для свойства ClickCommand этого экземпляра. и установите метку BindingContext для Label и ActivityIndicator для этого свойства экземпляра BusyViewModel`

public MainPage()
{
  InitializeComponent();
  // instance will be set to all the elements inside MainPage
  BindingContext= new CommandViewModel();
}
<ContentPage xmlns = "http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm = "clr-namespace:MauiApp6.ViewModels"
             x:Class = "MauiApp6.MainPage" >

    <StackLayout>
       <!--because BindingContext is set for the MainPage no need set it here again and setting it again here will lead to unexpected behaviour-->
        <Button Text = "Click Me"
                Command = "{Binding ClickCommand}"/>
        <!--here BindingContext will be set to CommandViewModel.BusyViewModel property-->
        <Label BindingContext = "{Binding BusyViewModel}"
               Text = "{Binding IsBusy}"/>
        <!--same thing-->
        <ActivityIndicator BindingContext = "{Binding Source = {vm:BusyViewModel}}"
                           IsRunning = "{Binding IsBusy}"/>
    </StackLayout>
</ContentPage>

Другие вопросы по теме