.Net-Avalonia学习笔记(四)-MVVM

  Avalonia的MVVM实现方式与WPF一样,下面是一个简单示例;WPF的MVVM示例见:WPF-理解与使用MVVM,请勿滥用

  Avalonia与WPF不同的是,DataContext不一样。Avalonia不在后台绑定DataContext时按钮事件不可Binding(使用CommunityToolkit.MVVM的除外)

一、快速创建方式

1、使用模版进行创建

2、模版项目结构如下

3、Model示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AvaloniaUI_MVVM_Simple.Models
{
    /// <summary>
    /// 用户信息
    /// </summary>
    public class UserModel
    {
        /// <summary>
        /// 主键
        /// </summary>
        public string ID { get; set; } = string.Empty;

        /// <summary>
        /// 用户名
        /// </summary>
        public string Name { get; set; } = string.Empty;

        /// <summary>
        /// 用户邮箱
        /// </summary>
        public string Email { get; set; } = string.Empty;
    }
}

4、View示例

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaUI_MVVM_Simple.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaUI_MVVM_Simple.Views.MainWindow"
        x:DataType="vm:MainWindowViewModel"
        Icon="/Assets/avalonia-logo.ico"
        Title="AvaloniaUI_MVVM_Simple">

    <Design.DataContext>
        <vm:MainWindowViewModel/>
    </Design.DataContext>
	<Grid>
        <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Top"/>
		
		<Label Content="名字:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
		<TextBox Text="{Binding Name, Mode=TwoWay}" HorizontalAlignment="Left" Margin="56,14,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="139"/>
		<Label Content="邮箱:" HorizontalAlignment="Left" Margin="10,40,0,0" VerticalAlignment="Top"/>
		<TextBox Text="{Binding Email }" HorizontalAlignment="Left" Margin="56,44,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="139"/>

		<!--Click="Button_Click"-->
		<Button Content="获取作者信息" Command="{Binding ButtonCommand}" HorizontalAlignment="Left" Margin="213,13,0,0" VerticalAlignment="Top" RenderTransformOrigin="0,-0.272" Width="110" />
		<!--Click="Button1_Click"-->
		<Button Content="和作者打招呼" Command="{Binding Button1Command}" HorizontalAlignment="Left" Margin="213,44,0,0" VerticalAlignment="Top" RenderTransformOrigin="0,-0.272" Width="110" />

		<Label Content="{Binding Log }" Margin="28,100,28,13" FontSize="72" Foreground="#FF00EDE8" HorizontalAlignment="Center" VerticalAlignment="Center"/>

	</Grid>
</Window>

5、ViewModel示例

  MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Threading.Tasks;
using System;
using AvaloniaUI_MVVM_Simple.Models;

namespace AvaloniaUI_MVVM_Simple.ViewModels
{
    public partial class MainWindowViewModel : ViewModelBase
    {
        #region 控件 Binding
        // 写法一
#pragma warning disable CA1822 // Mark members as static
        public string Greeting => "Welcome to Avalonia!";
#pragma warning restore CA1822 // Mark members as static

        #region 写法二
        /// <summary>
        /// 名字
        /// </summary>
        [ObservableProperty]
        private string _name = string.Empty;

        /// <summary>
        /// 邮箱
        /// </summary>
        [ObservableProperty]
        private string _email = string.Empty;

        /// <summary>
        /// 打招呼的日志
        /// </summary>
        [ObservableProperty]
        private string _log = string.Empty;
        #endregion 写法二
        #endregion 控件 Binding

        #region
        /// <summary>
        /// 获取作者信息Command
        /// 数据流向:Model-> ViewModel-> View
        /// </summary>
        [RelayCommand]
        //public void ButtonFun(bool bool1)
        public void ButtonFun()
        {
            // Model ->ViewModel
            UserModel userModel = new UserModel()
            {
                ID = Guid.NewGuid().ToString("N"),
                Name = "Bili执笔小白",
                Email = "2806933146@qq.com"
            };

            #region 清空字符串(可省略)
            Name = string.Empty;
            Email = string.Empty;
            Log = string.Empty;
            #endregion 清空字符串(可省略)

            Name = userModel.Name;
            Email = userModel.Email;
        }

        //private RelayCommand<User>? buttonCommand;
        //public IRelayCommand<User> ButtonCommand => buttonCommand ??= new RelayCommand<bool>(ButtonFun);
        private RelayCommand? buttonCommand;  // 可省略
        public IRelayCommand ButtonCommand => buttonCommand ??= new RelayCommand(ButtonFun);  // 可省略


        /// <summary>
        /// 和作者打招呼Command - 异步
        /// 数据流向:ViewModel-> View
        /// </summary>
        [RelayCommand]
        public async Task Button1Fun()
        {
            await Task.Run(() =>
            {
                Log = "你好!" + Name;
            });
        }

        private AsyncRelayCommand? button1Command;  // 可省略
        public IRelayCommand Button1Command => button1Command ??= new AsyncRelayCommand(Button1Fun);  // 可省略
        #endregion 
    }
}
  ViewModelBase.cs代码
using CommunityToolkit.Mvvm.ComponentModel;

namespace AvaloniaUI_MVVM_Simple.ViewModels
{
    public class ViewModelBase : ObservableObject
    {
    }
}

二、原生MVVM方式

1、MVVM知识点

  想要创建一个完整的MVVM,需要用到以下类型或修饰词:

  • Window.DataContext:View Window强绑定ViewModel;
  • Binding:View绑定ViewModel中的变量、事件;
  • INotifyPropertyChanged:用于ViewModel通知View刷新;ViewModel继承该类,并实现PropertyChanged、OnPropertyChanged;
  • ICommand:用于VIew通知ViewModel响应控件事件;创建ICommand的实现类RelayCommand,并实现CanExecuteChanged,CanExecute,Execute;

2、创建Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AvaloniaUI_Simple.Models
{
    /// <summary>
    /// 用户信息
    /// </summary>
    public class UserModel
    {
        /// <summary>
        /// 主键
        /// </summary>
        public string ID { get; set; } = string.Empty;

        /// <summary>
        /// 用户名
        /// </summary>
        public string Name { get; set; } = string.Empty;

        /// <summary>
        /// 用户邮箱
        /// </summary>
        public string Email { get; set; } = string.Empty;
    }
}

3、创建View

  注意已下代码:

  • Window.DataContext:View Window强绑定ViewModel;
  • Binding:View绑定ViewModel中的变量、事件;
  • Avalonia与WPF不同的是,DataContext不一样。Avalonia不在后台绑定DataContext时按钮事件不可Binding(使用CommunityToolkit.MVVM的除外)。
<Window xmlns="https://github.com/avaloniaui"
        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" d:DesignWidth="800" d:DesignHeight="450"
		xmlns:vm="using:AvaloniaUI_Simple.ViewModels"
        x:Class="AvaloniaUI_Simple.MVVMView"
		x:DataType="vm:MVVMWindowViewModel"
        Title="MVVMView">
	<Design.DataContext>
		<vm:MVVMWindowViewModel/>
	</Design.DataContext>
	<Grid>
		<Label Content="名字:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
		<TextBox Text="{Binding Name, Mode=TwoWay}" HorizontalAlignment="Left" Margin="56,14,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="139"/>
		<Label Content="邮箱:" HorizontalAlignment="Left" Margin="10,40,0,0" VerticalAlignment="Top"/>
		<TextBox Text="{Binding Email }" HorizontalAlignment="Left" Margin="56,44,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="139"/>

		<!--Click="Button_Click" Command="{Binding ButtonCommand}" -->
		<Button Content="获取作者信息" Command="{Binding ButtonCommand}" IsEnabled="True" HorizontalAlignment="Left" Margin="213,13,0,0" VerticalAlignment="Top" RenderTransformOrigin="0,-0.272" Width="110" />
		<!--Click="Button1_Click"-->
		<Button Content="和作者打招呼" Command="{Binding Button1Command}" IsEnabled="True" HorizontalAlignment="Left" Margin="213,44,0,0" VerticalAlignment="Top" RenderTransformOrigin="0,-0.272" Width="110" />

		<Label Content="{Binding Log }" Margin="28,100,28,13" FontSize="72" Foreground="#FF00EDE8" HorizontalAlignment="Center" VerticalAlignment="Center"/>

	</Grid>
</Window>
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using AvaloniaUI_Simple.ViewModels;

namespace AvaloniaUI_Simple;

public partial class MVVMView : Window
{
    /// <summary>
    /// 与WPF不一样的是无参时 事件不可用
    /// </summary>
    public MVVMView()
    {
        InitializeComponent();
    }

    //外部传值
    public MVVMView(MVVMWindowViewModel mVVMWindowViewModel)
    {
        InitializeComponent();

        if (mVVMWindowViewModel != null)
        {
            this.DataContext = mVVMWindowViewModel;
        }
    }
}

4、创建ViewModel

  • ViewModelBase为INotifyPropertyChanged的实现类;
  • RelayCommand为ICommand的实现类;
using System.Threading.Tasks;
using System;
using AvaloniaUI_Simple.Models;
using AvaloniaUI_Simple.Commands;
using System.Windows.Input;

namespace AvaloniaUI_Simple.ViewModels
{
    /// <summary>
    /// ViewModel
    /// 集合 请使用 ObservableCollection<MainViewModel>
    /// </summary>
    public partial class MVVMWindowViewModel : ViewModelBase
    {
        #region 控件 Binding
        private string _name = string.Empty;
        /// <summary>
        /// 名字
        /// </summary>
        public string Name
        {
            get => _name;
            set
            {
                _name = value;
                OnPropertyChanged("Name");  // 通知UI
            }
        }

        private string _email = string.Empty;
        /// <summary>
        /// 邮箱
        /// </summary>
        public string Email
        {
            get => _email;
            set
            {
                _email = value;
                OnPropertyChanged("Email");  // 通知UI
            }
        }

        private string _log = string.Empty;
        /// <summary>
        /// 打招呼的日志
        /// </summary>
        public string Log
        {
            get => _log;
            set
            {
                _log = value;
                OnPropertyChanged("Log");  // 通知UI
            }
        }
        #endregion 控件 Binding

        #region 控件事件
        /// <summary>
        /// 获取作者信息Command
        /// 数据流向:Model-> ViewModel-> View
        /// </summary>
        public ICommand ButtonCommand => new RelayCommand(
                    excute =>
                    {
                        // Model ->ViewModel
                        UserModel userModel = new UserModel()
                        {
                            ID = Guid.NewGuid().ToString("N"),
                            Name = "Bili执笔小白",
                            Email = "2806933146@qq.com"
                        };

                        #region 清空字符串(可省略)
                        Name = string.Empty;
                        Email = string.Empty;
                        Log = string.Empty;
                        #endregion 清空字符串(可省略)

                        Name = userModel.Name;
                        Email = userModel.Email;
                    },
                    canExcute => { return true; }
                );

        /// <summary>
        /// 和作者打招呼Command
        /// 数据流向:ViewModel-> View
        /// </summary>
        public ICommand Button1Command => new RelayCommand(
                    excute =>
                    {
                        Log = "你好!" + Name;
                    },
                    canExcute => { return true; }
                );
        #endregion 控件事件
    }
}

5、实现INotifyPropertyChanged

  INotifyPropertyChanged:用于ViewModel通知View刷新;ViewModel继承该类,并实现PropertyChanged、OnPropertyChanged;

using System.ComponentModel;

namespace AvaloniaUI_Simple.ViewModels
{
    /// <summary>
    /// ViewModel基类,主要为实现INotifyPropertyChanged接口
    /// </summary>
    public class ViewModelBase : INotifyPropertyChanged
    {
        /// <summary>
        /// MVVM 绑定事件
        /// </summary>
        public event PropertyChangedEventHandler? PropertyChanged;

        /// <summary>
        /// 数据变更时通知UI
        /// </summary>
        /// <param name="propName">绑定的值</param>
        protected virtual void OnPropertyChanged(string propName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }
}

6、实现ICommand

  ICommand:用于VIew通知ViewModel响应控件事件;创建ICommand的实现类RelayCommand,并实现CanExecuteChanged,CanExecute,Execute;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace AvaloniaUI_Simple.Commands
{
    /// <summary>
    /// 用ICommand解耦‘用户界面’与‘事件’,即控件事件不写在页面的默认.cs代码中
    /// 这是实现MVVM(Model-View-ViewModel)设计模式的关键部分
    /// </summary>
    public class RelayCommand: ICommand
    {
        #region 变量
        /// <summary>
        /// 执行操作
        /// </summary>
        private Action<object?> _Excute;
        /// <summary>
        /// 如果可以执行Execute方法,则返回true;否则返回false
        /// </summary>
        private Func<object?, bool> _CanExcute;

        /// <summary>
        /// 当CanExecute的返回值可能发生更改时,会触发该事件
        /// </summary>
        public event EventHandler? CanExecuteChanged;
        #endregion 变量

        /// <summary>
        /// 如果可以执行Execute方法,则返回true;否则返回false
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object? parameter)
        {
            return _CanExcute == null ? true : _CanExcute(parameter);
        }

        /// <summary>
        /// 执行操作
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object? parameter)
        {
            if (_Excute != null)
                _Excute(parameter);
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="ExcuteMethod"></param>
        /// <param name="CanExcuteMethod"></param>
        public RelayCommand(Action<object?> ExcuteMethod, Func<object?, bool> CanExcuteMethod)
        {
            _Excute = ExcuteMethod;
            _CanExcute = CanExcuteMethod;
        }
    }
}

7、外部调用

        /// <summary>
        /// 打开 MVVM页
        /// </summary>
        /// <param name="source"></param>
        /// <param name="args"></param>
        public void BtnMVVM_Click(object source, RoutedEventArgs args)
        {
            MVVMWindowViewModel mVVMWindowViewModel = new MVVMWindowViewModel();
            MVVMView mVVMWindow = new MVVMView(mVVMWindowViewModel);
            mVVMWindow.Show();
        }

8、效果

9、补充:

 (1)若ViewModel中有List<>或Array[]类型的变量,请使用ObservableCollection<>代替;ObservableCollection知识见:WPF-双向绑定通知机制之ObservableCollection

三、窗体事件如何使用MVVM

1、引言

  当我们参考上面的代码对窗体的Loaded与Closed进行MVVM时会发现失效,失效代码如下:

(1)XAML文件
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaUI_MVVM_Simple"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaUI_MVVM_Simple.Views.ToDoItemsWindow"
        x:DataType="vm:ToDoItemsViewModel"
        Title="待办事项应用" Loaded ="{Binding LoadedCommand}" Closed ="{Binding ClosedCommand}" >
    <!-- Closed="Window_Closed" -->
    <Design.DataContext>
        <vm:ToDoItemsViewModel />
    </Design.DataContext>
    <Grid>
    </Grid>
</Window>
(2).cs文件
    /// <summary>
    /// ViewModel
    /// </summary>
    public partial class ToDoItemsViewModel : ViewModelBase
    {
        #region 操作
        /// <summary>
        /// 加载
        /// </summary>
        //private void Window_Loaded(object sender, RoutedEventArgs e)
        [RelayCommand]
        //private void Loaded(RoutedEventArgs e)
        private void Loaded()
        {
            Debug.WriteLine("Loaded");
        }

        /// <summary>
        /// 关闭
        /// </summary>
        [RelayCommand]
        private void Closed()
        {
            Debug.WriteLine("Closed");
        }
        #endregion 操作
    }

2、进行MVVM的正确姿势:

(1)引用Negut包 Avalonia.Xaml.Behaviors

(2)XAML添加引用

  添加引用 xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
 xmlns:iac="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"

(3)事件Binding写法如下

  Behavior:行为,主要有鼠标、键盘、数据、位置等变化行为;

  Triggers:触发器;可用来给事件绑定触发器;

    <i:Interaction.Behaviors>
        <iac:EventTriggerBehavior EventName="Loaded">
            <iac:InvokeCommandAction Command="{Binding LoadedCommand}"/>
        </iac:EventTriggerBehavior>
        <iac:EventTriggerBehavior EventName="Closing">
            <iac:InvokeCommandAction Command="{Binding ClosingCommand}"/>
        </iac:EventTriggerBehavior>
        <iac:EventTriggerBehavior EventName="Closed">
            <iac:InvokeCommandAction Command="{Binding ClosedCommand}"/>
        </iac:EventTriggerBehavior>
    </i:Interaction.Behaviors>
(4)完整代码如下:
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaUI_MVVM_Simple"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
        xmlns:iac="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaUI_MVVM_Simple.Views.ToDoItemsWindow"
        x:DataType="vm:ToDoItemsViewModel"
        Title="待办事项应用">
    <!-- Loaded ="{Binding LoadedCommand}" Closed ="{Binding SaveCommand}" Closed="Window_Closed"-->
    <i:Interaction.Behaviors>
        <iac:EventTriggerBehavior EventName="Loaded">
            <iac:InvokeCommandAction Command="{Binding LoadedCommand}"/>
        </iac:EventTriggerBehavior>
        <iac:EventTriggerBehavior EventName="Closing">
            <iac:InvokeCommandAction Command="{Binding ClosingCommand}"/>
        </iac:EventTriggerBehavior>
        <iac:EventTriggerBehavior EventName="Closed">
            <iac:InvokeCommandAction Command="{Binding ClosedCommand}"/>
        </iac:EventTriggerBehavior>
    </i:Interaction.Behaviors>
    <Design.DataContext>
        <vm:ToDoItemsViewModel />
    </Design.DataContext>

    <Grid> </Grid>
</Window>
    /// <summary>
    /// ViewModel
    /// </summary>
    public partial class ToDoItemsViewModel : ViewModelBase
    {
        #region 操作
        /// <summary>
        /// 加载
        /// </summary>
        //private void Window_Loaded(object sender, RoutedEventArgs e)
        [RelayCommand]
        //private void Loaded(RoutedEventArgs e)
        private void Loaded()
        {
            Debug.WriteLine("Loaded");
        }

        /// <summary>
        /// 关闭
        /// </summary>
        [RelayCommand]
        private void Closed()
        {
            Debug.WriteLine("Closed");
        }
        #endregion 操作
    }
posted @ 2024-07-24 14:34  ꧁执笔小白꧂  阅读(520)  评论(0编辑  收藏  举报