欢迎来到陆季疵的博客

人生莫作远行客,远行莫戍黄沙碛。黄沙碛下八月时, 霜风裂肤百草衰。尘沙晴天迷道路,河水悠悠向东去。 胡笳听彻双泪流,羁魂惨惨生边愁。原头猎火夜相向, 马蹄蹴蹋层冰上。不似京华侠少年,清歌妙舞落花前。

WPF使用MVVMLight学习入门

参考文档

共四篇入门介绍  MvvmLight框架使用入门(一) - 楼上那个蜀黍 - 博客园 (cnblogs.com)

官方使用文档  Introduction to the MVVM Toolkit - Windows Community Toolkit | Microsoft Docs

 

DLL介绍

  • Microsoft.Toolkit.Mvvm.ComponentModel
    • ObservableObject    该类实现了INotifyPropertyChanged接口,定义了一个可通知的对象基类,供ViewModelBase继承   摘要:  一个对象的基类,其属性必须是可观察的
    • ObservableRecipient   可观察对象的基类,它也充当消息的接收者  这个类是ObservableObject的扩展,它也提供了使用Microsoft.Toolkit.Mvvm.Messaging.IMessenger类型的内置支持。  
    • 是一个基类,实现了INotifyDataErrorInfo接口,为验证向其他应用程序模块公开的属性提供了支持。 它也继承了ObservableObject,所以它也实现了INotifyPropertyChanged和INotifyPropertyChanging。 它可以作为所有需要支持属性更改通知和属性验证的对象的起点
  • Microsoft.Toolkit.Mvvm.DependencyInjection
    • Ioc一种便于系统使用的类型。 IServiceProvider类型。 ioc提供了在单例、线程安全的服务提供者实例中配置服务的能力,然后可以使用该实例来解析服务实例。 使用此特性的第一步是声明一些服务
  • Microsoft.Toolkit.Mvvm.Input
  • Microsoft.Toolkit.Mvvm.Messaging
    • IMessenger  提供在不同对象之间交换消息能力的类型的接口 , 还可以将消息发送到由令牌唯一标识的特定通道,并在应用程序的不同部分中使用不同的信使
    • WeakReferenceMessenger
      • 消息接口的实现  备注:imessenger实现使用弱引用来跟踪已注册的收件人,所以当不再需要收件人时,
      • 不需要手动注销他们该类型将自动执行内部调整时完整GC调用集合,所以手动调用Cleanup没有必要确保平均内部数据结构尽可能的削减和紧凑。 注意:在。net Framework中运行时,由于应用程序域卸载问题,不支持此功能。  
    • StrongReferenceMessenger
      • 消息接口的实现  备注:imessenger实现使用强引用来跟踪已注册的收件人,所以当他们不再需要时,必须手动注销他们。
    • IRecipient<TMessage>  用于声明特定消息类型注册的接收方的接口。
    • MessageHandler<TRecipient, TMessage>
  • Microsoft.Toolkit.Mvvm.Messaging.Messages

 MVVM简单使用方法

 安装包

 

 

 添加视图模型

    public class MainWindowViewModel : ObservableObject
    {
        private string title;

        public string Title
        {
            get { return title; }
            set { SetProperty(ref title, value); }
        }

        public ICommand ChangeTitleCommand { get; set; }



        private void ChangeTitle()
        {
            Title = "Hello MvvmLight";
        }
        public MainWindowViewModel()
        {
            Title = "Hello World";
            ChangeTitleCommand = new RelayCommand(ChangeTitle);
        }
    }

 

添加视图模型Locator

public class ViewModelLocator
{
    /// <summary>
    /// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
    /// </summary>
    internal static  IServiceProvider Services { get; private set; }
    public ViewModelLocator()
    {
        ConfigureServices();
    }
    /// <summary>
    /// Configures the services for the application.
    /// </summary>
    private static IServiceProvider ConfigureServices()
    {
        
        var services = new ServiceCollection();
        services.AddSingleton<MainWindowViewModel>();
        Services = services.BuildServiceProvider();
        Ioc.Default.ConfigureServices(Services);
        return Services;
    }
    public MainWindowViewModel MainVM=> Services.GetService<MainWindowViewModel>();
}

 

XAML配置

 

 

 主窗体xaml

    <Window.DataContext>
        <Binding Path="MainVM" Source="{StaticResource Locator}"/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <TextBlock Height="50" Text="{Binding Title}"></TextBlock>
        <Button Height="50" Grid.Row="1" Command="{Binding ChangeTitleCommand}"></Button>
    </Grid>

数据绑定的一个备注     利刃 MVVMLight 4:绑定和绑定的各种使用场景 - Hello-Brand - 博客园 (cnblogs.com)

值验证ObservableValidator

    //参考链接  https://www.cnblogs.com/AtTheMoment/p/15676984.html
    public class NumberAttribute : ValidationAttribute
    {
        public NumberAttribute()
        {

        }
        public override string FormatErrorMessage(string name)
        {
            return base.FormatErrorMessage(name);
        }
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
                return new("内容为空!");
            if (!int.TryParse(value.ToString(), out var number))
                return new("年龄非法");
            else if (number <= 0 || number > 100)
                return new($"{number}是非法年龄");

            return ValidationResult.Success;
        }
    }

    public class ValidatorVM : ObservableValidator
    {

        private string name;

        [Required(AllowEmptyStrings =false,ErrorMessage ="名字不可为空")]
        [MaxLength(6,ErrorMessage ="长度不能大于6")]
        public string Name
        {
            get { return name; }
            set { SetProperty(ref name, value, true); }
        }

        private int age;


        [Number]
        public int Age
        {
            get { return age;}
            set { SetProperty(ref age, value, true); }
        }
    }

 

前台代码

 <TextBox Height="50" Text="{Binding VM.Age}">
                <TextBox.Style>
                    <Style>
                        <Setter Property="Validation.ErrorTemplate">
                            <Setter.Value>
                                <ControlTemplate>
                                    <StackPanel>
                                        <AdornedElementPlaceholder/>
                                        <ItemsControl ItemsSource="{Binding}">
                                            <ItemsControl.ItemTemplate>
                                                <DataTemplate>
                                                    <TextBlock Foreground="Red" Text="{Binding ErrorContent}"/>
                                                </DataTemplate>
                                            </ItemsControl.ItemTemplate>
                                        </ItemsControl>
                                    </StackPanel>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </TextBox.Style>
            </TextBox>

 

 或者参考  https://www.cnblogs.com/wzh2010/p/6518834.html

 命令绑定修改为

ChangeTitleCommand = new RelayCommand(ChangeTitle, CanExecute);
       private bool CanExecute()
        {
            return !VM.HasErrors;
        }

 

完整的模型代码如下

public class MainWindowViewModel : ObservableObject
    {
        private string title;

        public string Title
        {
            get { return title; }
            set { SetProperty(ref title, value); }
        }

        public ICommand ChangeTitleCommand { get; set; }

        private ValidatorVM _VM;

        public ValidatorVM VM
        {
            get { return _VM; }
            set { _VM = value; }
        }



        private void ChangeTitle()
        {
            Title = "Hello MvvmLight";
        }
        public MainWindowViewModel(IServiceProvider service)
        {
            _VM = service.GetRequiredService<ValidatorVM>();
            Title = "Hello World";
            //ChangeTitleCommand = new RelayCommand(ChangeTitle);

            ChangeTitleCommand = new RelayCommand(ChangeTitle, CanExecute);
        }

        private bool CanExecute()
        {
            return !VM.HasErrors;
        }
    }

 

 带参数命令

RelayCommand = new RelayCommand<string>(obj => Title = "带参数的命令" + obj);

 

将参数包装成依赖属性,这种方式不推荐,太麻烦

    /// <summary>
    /// 一种方式就是将 UserParam类 改成 支持具有依赖属性的对象
    /// </summary>
    public class UserParam:FrameworkElement
    {
        public int Age
        {
            get { return (int)GetValue(AgeProperty); }
            set { SetValue(AgeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Age.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AgeProperty =
            DependencyProperty.Register("Age", typeof(int), typeof(UserParam), new PropertyMetadata(0, CustomProperty, CustomValidateValueCallback));

        /// <summary>
        /// 属性值验证回调方法
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static object CustomValidateValueCallback(DependencyObject d, object baseValue)
        {
            return baseValue;
        }
        /// <summary>
        /// 属性值更改回调方法
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void CustomProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
          
        }
    }

 

绑定示例 https://www.cnblogs.com/wzh2010/p/6607702.html

<StackPanel DockPanel.Dock="Left" Width="240">
                            <Button Command="{Binding PassArgObjCmd}"  Content="传递多个参数" Height="23" HorizontalAlignment="Left" Width="100">
                                <Button.CommandParameter>
                                    <model:UserParam UserName="{Binding ElementName=ArgStrFrom,Path=Text}" UserPhone="88888888" UserAdd="地址" UserSex="男" ></model:UserParam>
                                </Button.CommandParameter>
                            </Button>
  </StackPanel>

 

转换器传递参数

模型定义如下

    /// <summary>
    /// 一种方式就是将 UserParam类 改成 支持具有依赖属性的对象
    /// </summary>
    public class UserParam
    {
        public string UserName { get; internal set; }
        public string UserSex { get; internal set; }
        public string UserPhone { get; internal set; }
        public string UserAdd { get; internal set; }
    }

    public class UserInfoConvert : IMultiValueConverter
    {
        /// <summary>
        /// 对象转换
        /// </summary>
        /// <param name="values">所绑定的源的值</param>
        /// <param name="targetType">目标的类型</param>
        /// <param name="parameter">绑定时所传递的参数</param>
        /// <param name="culture"><系统语言等信息</param>
        /// <returns></returns>
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text)) && values.Count() == 4)
            {
                UserParam up = new UserParam() { UserName = values[0].ToString(), UserSex = values[1].ToString(), UserPhone = values[2].ToString(), UserAdd = values[3].ToString() };
                return up;
            }

            return null;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

 

ViewModel中添加属性

        private void ExcuteParam(UserParam obj)
        {
            userParam = obj;
        }

        public IRelayCommand<UserParam> RelayCommand { get;  }

        private UserParam userParam;

        public UserParam UserParam
        {
            get { return userParam; }
            set { SetProperty(ref userParam, value); }
        }
RelayCommand = new RelayCommand<UserParam>(ExcuteParam);

 

前台页面代码

名称空间引入

xmlns:convert="clr-namespace:MvvmSample.Core.ViewModels"
     <StackPanel Margin="10,0,0,50">
                <TextBlock Text="动态参数传递" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
                <StackPanel Orientation="Horizontal" >
                    <StackPanel Orientation="Vertical" Margin="0,0,10,0" >
                        <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >
                            <TextBlock Text="姓名" Width="80" ></TextBlock>
                            <TextBox x:Name="txtUName" Width="200" />
                        </StackPanel>
                        <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >
                            <TextBlock Text="电话" Width="80" ></TextBlock>
                            <TextBox x:Name="txtUPhone" Width="200" />
                        </StackPanel>
                        <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >
                            <TextBlock Text="地址" Width="80"></TextBlock>
                            <TextBox x:Name="txtUAdd" Width="200"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >
                            <TextBlock Text="性别" Width="80" ></TextBlock>
                            <TextBox x:Name="txtUSex" Width="200" />
                        </StackPanel>
                    </StackPanel>

                    <StackPanel>
                        <StackPanel.Resources>
                            <convert:UserInfoConvert x:Key="uic"/>
                        </StackPanel.Resources>
                        <Button Content="点击传递" Command="{Binding RelayCommand}">
                            <Button.CommandParameter>
                                <MultiBinding Converter="{StaticResource uic}">
                                    <Binding ElementName="txtUName" Path="Text"/>
                                    <Binding ElementName="txtUSex" Path="Text"/>
                                    <Binding ElementName="txtUPhone" Path="Text"/>
                                    <Binding ElementName="txtUAdd" Path="Text"/>
                                </MultiBinding>
                            </Button.CommandParameter>
                        </Button>
                    </StackPanel>

                    <StackPanel Width="240" Orientation="Vertical" Margin="10,0,0,0" >
                        <TextBlock Text="{Binding UserParam.UserName,StringFormat='姓名:\{0\}'}" ></TextBlock>
                        <TextBlock Text="{Binding UserParam.UserPhone,StringFormat='电话:\{0\}'}" ></TextBlock>
                        <TextBlock Text="{Binding UserParam.UserAdd,StringFormat='地址:\{0\}'}" ></TextBlock>
                        <TextBlock Text="{Binding UserParam.UserSex,StringFormat='性别:\{0\}'}" ></TextBlock>
                    </StackPanel>
                </StackPanel>
            </StackPanel>

 

传递事件参数--拖拽上传

添加包

 

 名称空间引用

xmlns:behav="http://schemas.microsoft.com/xaml/behaviors"
            <StackPanel Margin="10,0,0,50">
                <TextBlock Text="传递原事件参数" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
                <DockPanel x:Name="PassEventArg" >
                    <StackPanel DockPanel.Dock="Left" Width="240" Orientation="Horizontal" >
                        <Border BorderBrush="Red" BorderThickness="1" >
                            <TextBlock Width="100" Height="50"  Text="拖拽上传" TextAlignment="Center" FontSize="18" AllowDrop="True" >
                            <behav:Interaction.Triggers>
                                <behav:EventTrigger EventName="Drop">
                                    <behav:InvokeCommandAction Command="{Binding DragCommand}" PassEventArgsToCommand="True"/>
                                </behav:EventTrigger>
                            </behav:Interaction.Triggers>
                            </TextBlock>
                        </Border>
                    </StackPanel>
                    <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal">
                        <TextBlock Text="{Binding FileAdd,StringFormat='获取地址:\{0\}'}" ></TextBlock>
                    </StackPanel>
                </DockPanel>
            </StackPanel>

 

后台代码

        public IRelayCommand<DragEventArgs> DragCommand { get; set; }
        private string _FileAdd;

        public string FileAdd
        {
            get { return _FileAdd; }
            set { SetProperty(ref _FileAdd, value); }
        }

        private void ExecuteDrop(DragEventArgs obj)
        {

            FileAdd = ((System.Array)obj.Data.GetData(System.Windows.DataFormats.FileDrop)).GetValue(0).ToString();
        }

DragCommand = new RelayCommand<DragEventArgs>(ExecuteDrop);

 

控件的事件转换为命令

  <StackPanel Margin="10,0,0,50">
                <TextBlock Text="事件转命令执行" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock>
                <DockPanel x:Name="EventToCommand" >
                    <StackPanel DockPanel.Dock="Left" Width="240" Orientation="Horizontal" >
                        <ComboBox Name="combox" Width="130" ItemsSource="{Binding UserParamList}" DisplayMemberPath="UserName" SelectedValuePath="UserSex">
                            <behav:Interaction.Triggers>
                                <behav:EventTrigger EventName="SelectionChanged">
                                    <behav:InvokeCommandAction Command="{Binding SelectCommand}"/>
                                </behav:EventTrigger>
                            </behav:Interaction.Triggers>
                        </ComboBox>
                    </StackPanel>
                    <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal">
                        <TextBlock Text="{Binding ElementName=combox, Path=SelectedIndex}" ></TextBlock>
                    </StackPanel>
                </DockPanel>
            </StackPanel>

 

后台代码

        public ObservableCollection<UserParam> UserParamList { get; set; }
        public IRelayCommand SelectCommand { get; }
        private  void OnSelectCommand()
        {
          
        }

 

 

数据初始化

            UserParamList = new()
            {
                new() { UserName = "张三", UserSex = "1" },
                new() { UserName = "李四", UserSex = "2" },
                new() { UserName = "王五", UserSex = "3" },
                new() { UserName = "赵六", UserSex = "4" },
            };
            SelectCommand=new RelayCommand(OnSelectCommand);

 

在多线程和调度中使用

异步调用不卡UI界面

public IAsyncRelayCommand AsyncRelayCommand { get; }
AsyncRelayCommand = new AsyncRelayCommand(() => Task.Run(() => { Thread.Sleep(8000); Title = "我执行完了"; }));

 

Messenger

 

 

 注册消息的简单模式

参数说明:

<string, string>  消息类型,令牌类型

this为消息的接收者实例,“AAA”为消息令牌,

MessageHandler:(sender,message)==>sender为接收消息的收件人,message接收到的消息

            WeakReferenceMessenger.Default.Register<string, string>(this, "AAA", (recipient, message) =>
            {
                MessageBox.Show($"MessagePage 接收到了消息: {message};\n消息的收件人类型为{recipient.GetType()};");
            });

不带令牌的消息注册

            WeakReferenceMessenger.Default.Register<string>(this, (recipient, message) =>
            {
                if (recipient is MessagePage)//如果是发送给我的
                {
                    MessageBox.Show($"MessagePage 接收到了消息: {message};\n消息的收件人类型为{recipient.GetType()};");
                }
            });

 

备注:同一收件人只能注册一种消息类型

 

发送字符串消息的两种形式

            WeakReferenceMessenger.Default.Send( DateTime.Now.ToString("HH:mm:ss")+"发送了字符串消息");
            WeakReferenceMessenger.Default.Send( DateTime.Now.ToString("HH:mm:ss") +"发送了令牌消息,标识符为AAA", "AAA");

 

备注:不带令牌的形式的消息不会发送给注册了令牌的接收者,带令牌的形式,不会发送给没有注册令牌的接收者

自动注册接收消息

备注:需要将isactive设置为true;以下实例将接收到所有字符串类型的消息

        public class MessagePageViewModel : ObservableRecipient, IRecipient<string>
        {
            public MessagePageViewModel()
            {
                this.IsActive = true;
            }
            public void Receive(string message)
            {
                MessageBox.Show($"MessagePage 接收到了消息: {message};消息令牌为:AAA;\n消息的收件人类型为{this.GetType()};");
            }
        } 
ObservableRecipient的方法说明:发送属性改变的消息,如果想要使用自定义的令牌,可以重写该方法
protected virtual void Broadcast<T>(T oldValue, T newValue, string? propertyName)

 

注册接收属性改变的消息

            WeakReferenceMessenger.Default.Register<PropertyChangedMessage<string>>(this, (recipient, message) =>
            {
                if (recipient is MessagePage)
                {
                    MessageBox.Show($"发件人: {message.Sender};属性名:{message.PropertyName}");
                }
               
            });

 

 

请求消息

注册请求消息

            WeakReferenceMessenger.Default.Register<RequestMessage<string>>(this, (recipient, message) =>
            {
                if (recipient is MessagePage)
                {
                    message.Reply("我已经接收到了你的请求消息,这是我的答复");
                }
            });

 

发送请求,并接收答复

var name = WeakReferenceMessenger.Default.Send<RequestMessage<string>>();

 

posted @ 2022-03-24 14:21  陆季疵  阅读(1624)  评论(0编辑  收藏  举报
//《!--看板娘--> //https://www.cnblogs.com/ZTianming/p/14618913.html