WPF Mvvm

了解MVVM

  1. 什么是MVVM:一种设计模式

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

  1. 为什么需要MVVM?解决什么问题?

降低耦合、独立开发、逻辑重用(Xamarin MAUI多平台应用)、可测试

响应式布局

控件交互到MVVM模式的转变

控件交互:基于控件的功能开发

MVVM模式

代码/项目结构

Models:

Views:

ViewModels:

需求:计算器,输入:A B 输出:通过按钮 A+B 的结果

控件交互写法:

XAML代码:

<StackPanel>
  <TextBox Text="" Name="tb_1"/>
  <TextBlock Text="+" />
  <TextBox Text="" Name="tb_2"/>
  <TextBlock Text="=" />
  <TextBox Text="" Name="tb_3"/>
  <Button Content="计算" Click="Button_Click"/>
</StackPanel>

C#代码:

private void Button_Click(object sender, RoutedEventArgs e)
{
    // 控制逻辑
    double.TryParse(this.tb_1.Text, out double value_1);
    double.TryParse(this.tb_2.Text, out double value_2);

    this.tb_3.Text = (value_1 + value_2) + "";
}

效果:

MVVM模式开发:

XAML代码:View代码

在XAML或内部cs文件中,绑定对应的ViewModel

<Window x:Class="XH.MvvmLesson.Mvvm.MvvmWindow"
        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:XH.MvvmLesson.Mvvm"
        mc:Ignorable="d"
        Title="MvvmWindow" Height="450" Width="800">
    <!--绑定ViewModel-->
    <Window.DataContext>
        <local:LogicClass />
    </Window.DataContext>
    <StackPanel>
        <TextBox Text="{Binding _model.Value1}" />
        <TextBlock Text="+" />
        <TextBox Text="{Binding _model.Value2}" />
        <TextBlock Text="=" />
        <TextBox Text="{Binding _model.Value3}" />
        <Button Content="计算" Command="{Binding BtnCommand}" CommandParameter="BtnParameter"/>
        <Button Content="检查状态" Command="{Binding BtnCheckCommand}" />
    </StackPanel>
</Window>

ViewModel代码:

创建实例,绑定View

创建Command对象,绑定事件

namespace XH.MvvmLesson.Mvvm
{
    public class LogicClass
    {
        public DataModel _model { get; set; } = new DataModel();

        public Command BtnCommand { get; set; }
        public Command BtnCheckCommand { get; set; }

        public LogicClass()
        {
            BtnCommand = new Command(DoLogic, CanDoLogic);

            BtnCheckCommand = new Command(DoCheck);

            _model.PropertyChanged += _model_PropertyChanged;
        }

        // 属性变化事件
        private void _model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            BtnCommand.RaiseCanExecuteChanged();
        }

        // 检查按钮
        private void DoCheck(object obj)
        {
            BtnCommand.RaiseCanExecuteChanged();
        }

        // 控制逻辑
        private void DoLogic(object obj)
        {
            _model.Value3 = _model.Value1 + _model.Value2;
        }

        // 是否可以启用按钮
        private bool CanDoLogic(object obj)
        {
            return _model.Value1 > 0 && _model.Value2 > 0;
        }
    }
}

Model代码:

INotifyPropertyChanged:使用页面通知属性,在属性set的时候,调用

using System.ComponentModel;

namespace XH.MvvmLesson.Mvvm
{
    // 针对功能所提供的数据模型
    public class DataModel : INotifyPropertyChanged
    {
        private double _value1;

        public double Value1
        {
            get { return _value1; }
            set
            {
                _value1 = value;
                PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("Value1"));
            }
        }

        private double _value2;

        public double Value2
        {
            get { return _value2; }
            set
            {
                _value2 = value;
                PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("Value2"));
            }
        }

        private double _value3;

        public double Value3
        {
            get { return _value3; }
            set
            {
                _value3 = value;
                // 在系统内广而告之 告诉页面上哪个对象关注了这个实例里的这个属性的,赶紧更新结果
                PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("Value3"));
            }
        }

        // 做信息发布的对象 需要执行这一下这个对象
        public event PropertyChangedEventHandler? PropertyChanged;
    }
}

事件Command代码:ICommand

需要继承ICommand接口

CanExecute:判断是否需要启用这个事件,等同于IsEnabled

在界面加载的时候调用一次,在每次执行之前再调用一次

Execute:执行事件方法

parameter:界面的CommandParameter属性来传值

using System.Windows.Input;

namespace XH.MvvmLesson.Mvvm
{
    public class Command : ICommand
    {
        // 判断绑定的当前命令实例的对象 是否可用
        // 比如按钮是否可以执行下面的逻辑
        public event EventHandler? CanExecuteChanged;

        // 外部调用
        public void RaiseCanExecuteChanged()
        {
            // 通知方法使用是否可以触发 通知状态检查
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }

        // 作用,触发一个时机 切换调用方的状态是否可用
        public bool CanExecute(object? parameter)
        {
            return _canExecute?.Invoke(parameter) != false;
        }
        // 参数 返回值
        private Func<object?, bool> _canExecute;


        // 绑定了当前命令实例的对象的执行逻辑
        // 等同于 Button的Click 事件
        // parameter:界面的CommandParameter属性来传值
        public void Execute(object? parameter)
        {
            // 委托 
            DoExeute?.Invoke(parameter);
        }

        public Action<object> DoExeute { get; set; }
        public Command(Action<object> action, Func<object?, bool> func = null)
        {
            DoExeute = action;
            _canExecute = func;
        }
    }
}

MVVM绑定模式下的信息交互

数据类型:INotifyPropertyChanged接口
class Class1 : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler? PropertyChanged;
}
行为动作:ICommand接口
class Class1 : ICommand
{
    // 调用这个方式的时候 又调用 CanExecute 方法
    public event EventHandler? CanExecuteChanged;

    // 外部调用
    public void RaiseCanExecuteChanged()
    {
        // 通知方法使用是否可以触发 通知状态检查
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    // 作用,触发一个时机 切换调用方的状态是否可用
    public bool CanExecute(object? parameter)
    {
        throw new NotImplementedException();
    }

    // 绑定了当前命令实例的对象的执行逻辑
    // 等同于 Button的Click 事件
    // parameter:界面的CommandParameter属性来传值
    public void Execute(object? parameter)
    {
        throw new NotImplementedException();
    }
}

扩展:修改数据格式,把每次的算法记录成一个表格,并且添加删除按钮可以删除

主要是修改表格的数据模板,并且每个数据增加个按钮 进行移除,并且传入当前集合,集合通知属性

部分XAML代码:

 <Grid>
     <Grid.ColumnDefinitions>
         <ColumnDefinition />
         <ColumnDefinition />
     </Grid.ColumnDefinitions>
     <StackPanel>
         <TextBox Text="{Binding _model.Value1,UpdateSourceTrigger=PropertyChanged}" />
         <TextBlock Text="+" />
         <TextBox Text="{Binding _model.Value2,UpdateSourceTrigger=PropertyChanged}" />
         <TextBlock Text="=" />
         <TextBox Text="{Binding _model.Value3}" />
         <Button Content="计算" Command="{Binding BtnCommand}" CommandParameter="BtnParameter"/>
         <Button Content="检查状态" Command="{Binding BtnCheckCommand}" />
     </StackPanel>
     <ListBox Grid.Column="1" ItemsSource="{Binding ResultList}" Name="lb">
         <ListBox.ItemTemplate>
             <DataTemplate>
                 <StackPanel Orientation="Horizontal">
                     <TextBlock Text="{Binding}" />
                     <!--<TextBlock Text="{Binding State}" />-->
                     <!--只写个Binding 是绑定当前数据源-->
                     <Button Content="删除" CommandParameter="{Binding}"
                             Command="{Binding DataContext.BtnDelCommand,RelativeSource={RelativeSource AncestorType=Window}}" />
                 </StackPanel>
             </DataTemplate>
         </ListBox.ItemTemplate>
     </ListBox>
 </Grid>

只写个Binding 是绑定当前数据源

部分C#代码:

ObservableCollection:集合通知属性

// ObservableCollection:集合通知属性,代替List 可以通知界面修改数据
//public ObservableCollection<ResultModel> ResultList { get; set; } = new ObservableCollection<ResultModel>();
public ObservableCollection<string> ResultList { get; set; } = new ObservableCollection<string>();

public Command BtnDelCommand { get; set; }

public MainViewModel()
{
    BtnDelCommand = new Command(DoDel);
}

// 删除按钮
private void DoDel(object obj)
{
    ResultList.Remove((string)obj);
}


// 控制逻辑
private void DoLogic(object obj)
{
    _model.Value3 = _model.Value1 + _model.Value2;

    // 如果希望通知子项的变化(这个集合中的子项的增减)
    // 需要实现INotifyCollectionChanged接口,进行子项变化通知
    // 框架提供了通知集合对象ObservableCollection
    //ResultList.Add(new ResultModel
    //{
    //    Msg = $"第{ResultList.Count + 1}次{_model.Value1} + {_model.Value2} = {_model.Value3}",
    //});
    ResultList.Add($"第{ResultList.Count + 1}次{_model.Value1} + {_model.Value2} = {_model.Value3}");
}

显示效果:

MVVM绑定扩展

无法绑定的对象属性

通过附加属性进行扩展

案例:绑定ScottPlot,实现图标动态刷新

View:

<Window x:Class="XH.MvvmPattern.Views.ScottPlotWindow"
  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:XH.MvvmPattern.Views"
  xmlns:vm="clr-namespace:XH.MvvmPattern.ViewModels"
  xmlns:b="clr-namespace:XH.MvvmPattern.Base"
  mc:Ignorable="d"
  Title="ScottPlotWindow" Height="450" Width="800">
  <Window.DataContext>
    <vm:ScottPlotViewModel />
  </Window.DataContext>
  <Grid>
    <WpfPlot Name="wpf_plot" b:ScottPlotExtension.Values="{Binding Datas}"/>
  </Grid>
</Window>

Model:由于数据很少,数据写在ViewModel 里面

ViewModel:

using ScottPlot;
using System.Collections.ObjectModel;
using System.Windows;

namespace XH.MvvmPattern.ViewModels
{
    public class ScottPlotViewModel
    {
        public ObservableCollection<double> Datas { get; set; } 
        public ScottPlotViewModel()
        {
            Datas = new ObservableCollection<double>(DataGen.RandomWalk(new Random(), 10));

            // 实时监控 持续获取数据 
            Task.Run(async () =>
            {
                while (true)
                {
                    await Task.Delay(1000);

                    Application.Current.Dispatcher.BeginInvoke(() =>
                    {
                        Datas.Add(new Random().NextDouble());
                        Datas.RemoveAt(0);
                    });
                }
            });
        }
    }
}

附加属性:

using ScottPlot;
using System.Collections.ObjectModel;
using System.Windows;

namespace XH.MvvmPattern.Base
{
    public class ScottPlotExtension
    {

        // 依赖附加属性
        // 这个属性里面可以知道绑定的属性 以及被附加的对象
        public static ObservableCollection<double> GetValues(DependencyObject obj)
        {
            return (ObservableCollection<double>)obj.GetValue(ValuesProperty);
        }

        public static void SetValues(DependencyObject obj, int value)
        {
            obj.SetValue(ValuesProperty, value);
        }

        // Using a DependencyProperty as the backing store for Values.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValuesProperty =
            DependencyProperty.RegisterAttached(
                "Values",
                typeof(ObservableCollection<double>),
                typeof(ScottPlotExtension),
                new PropertyMetadata(null, new PropertyChangedCallback(OnValuesChanged)));

        // 当给Value赋值的时候 才会触发
        private static void OnValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var plt = d as WpfPlot;
            var newPlt = plt.Plot;
            var new_list = (ObservableCollection<double>)e.NewValue;

            var signal = newPlt.AddSignal(new_list.ToArray());
            // 对集合实例进行子项增减的时候 进行回调
            new_list.CollectionChanged += (s, e) =>
            {
                // 由于数组长度不能变 所以是ViewModel中,长度变为10的事件中,再触发,加个判断
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
                {
                    signal.Update(new_list.ToArray());
                    plt.Refresh();
                }
            };

            plt.Refresh();
        }
    }
}

注意:附加属性中的属性改变事件,只有在Value赋值的时候才会触发,但是List中间值改变不会触发,

所以需要再此事件中写集合的值改变事件:CollectionChanged

无法绑定的动作事件

单击鼠标左键 :LeftClick

双击鼠标左键:LeftDoubleClick

单击鼠标中键 :MiddleClick

双击鼠标中键:MiddleDoubleClick

单击鼠标右键:RightClick

双击鼠标右键:RightDoubleClick

不执行任何操作:None

旋转鼠标滚轮:WheelClick

键盘和鼠标常用方法:InputBindings

<Border.InputBindings>
  <MouseBinding Command="{Binding BtnCommand}" MouseAction="LeftClick" />
  <KeyBinding Command="{Binding BtnCommand}" Key="Enter" />
</Border.InputBindings>
事件转命令

Behavior

第一步:下载NuGet 包

第二步:引入命名空间

xmlns:b="http://schemas.microsoft.com/xaml/behaviors"

第三步:使用:

EventName:绑定的事件名称

Command:绑定

 <Border Height="40" Background="Orange">
     <b:Interaction.Triggers>
         <b:EventTrigger EventName="MouseLeftButtonDown">
             <b:InvokeCommandAction Command="{Binding BtnCommand}" />
         </b:EventTrigger>
     </b:Interaction.Triggers>
 </Border>

自定义实现

思路:就是写一个附加属性,然后同过一个类,传入事件名称和事件Command,然后同过反射反射到对应的控件事件上,进行调用即可。

C#代码:

using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;

namespace XH.MvvmPattern.Base
{
    // 任意事件绑定
    public class EventExtension
    {

        // 解决:页面至少需要传递两个信息:事件名陈、命令,可以将这两个信息进行类的打包 EventCommand
        // 通过EventCommand的实例进行相关信息的获取(事件名称、命令)
        // 然后进行反射事件的委托挂载,在执行命令过程

        public static EventCommand GetEventTarget(DependencyObject obj)
        {
            return (EventCommand)obj.GetValue(EventTargetProperty);
        }

        public static void SetEventTarget(DependencyObject obj, EventCommand value)
        {
            obj.SetValue(EventTargetProperty, value);
        }

        // Using a DependencyProperty as the backing store for EventTarget.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EventTargetProperty =
            DependencyProperty.RegisterAttached(
            "EventTarget", 
            typeof(EventCommand), 
            typeof(EventExtension), 
            new PropertyMetadata(
                null,
                new PropertyChangedCallback(OnEventTargetChanged)));

        private static void OnEventTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is UIElement control)
            {
                var eventCommand = e.NewValue as EventCommand;
                if (eventCommand == null) return;

                string eventName = eventCommand.EventName;
                // 获取控件类型	
                Type controlType = control.GetType();

                bool isEvent = IsEvent(eventName, controlType);

                if (isEvent)
                {
                    EventInfo eventInfo = controlType.GetEvent(eventName);
                    if (eventInfo != null)
                    {
                        // 动态挂载事件
                        MethodInfo methodInfo = typeof(EventExtension).GetMethod("OnEventRaised", BindingFlags.NonPublic | BindingFlags.Static);
                        Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, methodInfo);
                        eventInfo.AddEventHandler(control, handler);

                        control.SetValue(EventTargetProperty, eventCommand);
                    }
                }
            }
        }
        // 挂载事件 事件转命令 
        private static void OnEventRaised(object sender, EventArgs e)
        {
            DependencyObject control = sender as DependencyObject;
            if (control == null) return;

            EventCommand eventCommand = GetEventTarget(control);
            if (eventCommand == null) return;

            ICommand command = eventCommand.Command;
            // 如果Command 获取为null,有可能是因为EventCommand对象的继承有问题,需要继承Animatable
            if (command != null && command.CanExecute(eventCommand.CommandParameter))
            {
                command.Execute(eventCommand.CommandParameter);
            }
        }
        // 判断是不是事件
        private static bool IsEvent(string eventName, Type controlType)
        {
            // 获取控件类型的所有事件
            EventInfo[] events = controlType.GetEvents();

            // 检查是否存在与给定名称匹配的事件
            foreach (EventInfo eventInfo in events)
            {
                if (eventInfo.Name.Equals(eventName, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

    }

    public class EventCommand : Animatable
    {
        public string EventName { get; set; }

        // 事件绑定
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null));

        // 参数
        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CommandParameter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommand), new PropertyMetadata(0));


        protected override Freezable CreateInstanceCore()
        {
            return (Freezable)Activator.CreateInstance(GetType());
        }
    }
}

注意:如果在挂载的时候,发现Command是null,或者绑定的时候目标源在FrameworkElement 或 FrameworkContentElement上的时候,需要继承Animatable类,也不是继承接口:ICommand

主要是仿照微软提供的behaviors进行编写,这个方法也是继承Animatable才能实现Command 传递

ContextMenu绑定事件处理

XAML代码:原因:ContextMenu在窗体之上,不在窗体之内,所以找不到窗体的数据源,需要明确指定数据源

<Window.Resources>
    <vm:MainViewModel x:Key="mvm" />
</Window.Resources>
<Window.DataContext>
    <!--<vm:MainViewModel />-->
    <StaticResource ResourceKey="mvm" />
</Window.DataContext>
………………
<StackPanel.ContextMenu>
  <ContextMenu>
    <!--第一种方式 Source={x:Reference Name=lb}-->
    <!--<MenuItem Header="删除" CommandParameter="{Binding}" Command="{Binding DataContext.BtnDelCommand,Source={x:Reference Name=lb}}"/>-->
    <!--第二种方式 把数据源当做资源调用 Source 即可-->
    <!--原因:ContextMenu在窗体之上,不在窗体之内,所以找不到窗体的数据源,需要明确指定数据源
    注意:命令的数据源的指定-->
    <MenuItem Header="删除" CommandParameter="{Binding}" 
              Command="{Binding BtnDelCommand,Source={StaticResource mvm}}"/>
  </ContextMenu>
</StackPanel.ContextMenu>
自定义控件的事件绑定:

C#代码:直接在代码中写依赖绑定属性即可,在有需要的地方调用Execute

public partial class DateTimePicker :UserControl, INotifyPropertyChanged
 {
    // 命令属性
    public ICommand SelectedCommand
    {
        get { return (ICommand)GetValue(SelectedCommandProperty); }
        set { SetValue(SelectedCommandProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectedCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedCommandProperty =
        DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(DateTimePicker), new PropertyMetadata(null));




    public object SelectedCommandParameter
    {
        get { return (object)GetValue(SelectedCommandParameterProperty); }
        set { SetValue(SelectedCommandParameterProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SelectedCommandParameter.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedCommandParameterProperty =
        DependencyProperty.Register("SelectedCommandParameter", typeof(object), typeof(DateTimePicker), new PropertyMetadata(null));

    …………………………
    // 在需要调用的地方 调用此事件即可
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // 命令的执行
        SelectedCommand?.Execute(SelectedCommandParameter);
    }
        
}

XAML代码:

<c:DateTimePicker VerticalAlignment="Top" HorizontalAlignment="Center" 
                  SelectedCommand="{Binding SeletedCommand}" 
                  SelectedCommandParameter="{Binding}"/>

MVVM分层逻辑相关问题

VM逻辑中弹窗操作

需求:应用中逻辑处理过程中需要打开一个子窗口,作为Dialog窗口进行信息显示

利用一个第三方对象WindowProvider,允许View层进行窗口对象的注册 ,允许ViewModel层进行窗口对象的调用请求

过程中涉及两方的数据传递

第三方对象WindowProvider:

using System.Windows;

namespace XH.Mvvm.Base
{
    public class WindowProvider
    {
        static Dictionary<string, WindowInfo> types = new Dictionary<string, WindowInfo>();
        // 收购窗口
        // 允许自定义名字 key,不自定义就是窗体名
        public static void Register<T>(string key = "",Window owner = null)
        {
            if (string.IsNullOrEmpty(key))
                key = typeof(T).Name;

            if (!types.ContainsKey(key))
                types.Add(key, new WindowInfo { WinType = typeof(T),OwnerType = owner });
        }

        // 出售
        public static bool ShowDialog(string key,object dataContext)
        {
            Type type = null;
            if (types.ContainsKey(key))
            {
                type = types[key].WinType;
            }
            if (type != null)
            {
                // 同过反射创建新的对象
                Window win = (Window)Activator.CreateInstance(type);

                // 设置窗口所有者 当任务栏打开大窗口的时候,小窗口带出
                win.Owner = types[key].OwnerType;
                win.DataContext = dataContext;
                bool state = (bool)win.ShowDialog();
                return state;
            }
            else
                throw new Exception("没有找到对应的弹窗对象");
        }
    }

    class WindowInfo
    {
        public Type WinType { get; set; }
        public Window OwnerType { get; set; }
    }
}

View地方调用:

 public MainWindow()
 {
     InitializeComponent();

     WindowProvider.Register<SubWindow>(owner: this);
 }

ViewModel地方调用:

public MainViewModel()
{
    BtnCommand = new Command(obj =>
    {
        // Btn的执行逻辑 打开子窗口
        // 因为不能View-->ViewModel-->View 来回调用,此方法不可取
        //new SubWindow().ShowDialog();

        SubViewModel subViewModel = new SubViewModel();
        subViewModel.Value = Value;

        if (WindowProvider.ShowDialog("SubWindow", subViewModel))
        {
            // 获取到子窗口返回的Value属性
            this.Value = subViewModel.Value;
        }
        else { }

        // 如何从子窗口返回到主窗口:SUbViewModel --> MainViewModel
        // 1、子窗口打开后,编辑完成,保存按钮、取消按钮
        // 2、最终数据不在子窗口处理,返回到主VM再处,保存、回退

    });
}

显示:能够顺利显示和传值

逻辑:

  1. 收购
    1. 定义一个字典,然后记录需要弹出的窗口信息(窗口名称,父窗口)和key,用key和信息进行绑定,然后调用的时候用key调用窗口信息。
  1. 出售
    1. 如果存在这个key,然后同过反射把字典中的窗口信息反射出来给Window,然后进行设置Window的属性,并且弹窗,同过DialogResult属性来判断是否需要写入信息。
    2. 出售(调用)的时候,把需要传入的Model通过参数传进来。
  1. 调用
    1. 通过出售的时候返回的bool信息,来判断是否需要保存下来返回的值
任意对象间逻辑调用

委托对象的管理,被动方进行委托方法的注册,主动方进行委托方法的请求调用

核心 :委托对象的使用

ActionManager类:


namespace XH.Mvvm.Base
{
    public class ActionManager
    {
        static Dictionary<string, Delegate> types = new Dictionary<string, Delegate>();
        
        public static void Register<T>(Action<T> action, string key)
        {
            if (!types.ContainsKey(key))
                types.Add(key, action);
        }  
        
        public static void Register<T>(Func<T> action, string key)
        {
            if (!types.ContainsKey(key))
                types.Add(key, action);
        }

        public static void Invoke<T>(string key, T arg)
        {
            if (types.ContainsKey(key))
                ((Action<T>)types[key]).Invoke(arg);
        }

        public static T InvokeWithResult<T>(string key)
        {
            if (types.ContainsKey(key))
               return (types[key] as Func<T>).Invoke();

            return default(T);
        }
    }
}

A窗体进行订阅:

public MainViewModel()
{
    // 订阅的过程,
    ActionManager.Register<object>(new Action<object>(DoAction), "AAA");
    ActionManager.Register<object>(new Func<object>(DoFunc), "BBB");

}
 private object DoFunc()
 {
     return this.Value;
 }

 private void DoAction(object obj)
 {
     
 }

B窗体进行调用:

public SubViewModel()
{
    SubmitCommand = new Command(obj =>
    {
        // 发布的动作
        // B窗口传递给A窗口值
        ActionManager.Invoke<object>("AAA",Value);
        
        // A窗口传递给B窗口值
        var v = ActionManager.InvokeWithResult<object>("BBB");

    });
}

逻辑:

  1. 注册
    1. 注册一个委托key字典,然后同过key和方法进行注册,发布的时候同过key进行搜索委托事件执行。
    2. 可以注册Action 和 Func 方法,Action是调用的时候需要参数,Func调用的时候是返回参数
    3. 注册Func方法的时候,需要返回一个Object类型的参数,给调用方
  1. 调用/发布
    1. 同过Key进行调用,但是需要传入参数,如果是Action的话,需要传给注册方一个object参数,在注册方的方法中。
    2. 如果是Func的话,返回一个参数,是注册方返回给掉用方的参数

注意:Func 和 Action 的区别

Func
  • 定义:Func是一个泛型委托,用于表示可以带有参数并且返回值的方法。Func委托可以有多个输入参数,但只能有一个返回值。
  • 用途:通常用于需要执行一个操作并返回结果的情况。例如,你可能需要一个方法来检查某个字符串是否符合特定的格式,并返回一个布尔值表示检查结果。
  • 语法示例Func<T, TResult> 表示一个接受一个类型为T的参数并返回一个类型为TResult的结果的委托。Func<int, string, bool> 表示一个接受一个int类型和一个string类型参数,并返回一个bool类型结果的委托。
Action
  • 定义:Action是另一个泛型委托,但它不返回任何值(即返回类型为void)。Action委托可以有多个输入参数,但没有返回值。
  • 用途:适用于那些执行操作但不需要返回结果的情况。例如,你可能需要一个方法来打印日志信息,或者更新某个对象的状态,这些操作都不需要返回值。
  • 语法示例Action<T> 表示一个接受一个类型为T的参数但不返回任何结果的委托。Action<int, string> 表示一个接受一个int类型和一个string类型参数,但不返回任何结果的委托。
Func和Action的主要区别归纳

特性

Func

Action

返回值

有返回值

无返回值(void)

参数数量

可以有0到多个参数

可以有0到多个参数

用途

执行操作并返回结果

执行操作但不返回结果

语法示例

Func<T, TResult>

Action<T>

总结

Func和Action是编程中用于表示不同类型方法的泛型委托。Func用于表示需要返回值的方法,而Action用于表示不需要返回值的方法。它们的使用取决于你的具体需求,即在执行某个操作时是否需要返回结果。通过合理使用Func和Action,可以使代码更加清晰、灵活和易于维护。

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF(Microsoft Windows Presentation Foundation)是一种用于创建图形化用户界面的技术。MVVM(Model-View-ViewModel)是一种软件设计模式,用于将用户界面的逻辑与数据分离。WPF MVVM示例是一种将WPFMVVM结合起来的示例项目,以演示如何使用这两个技术来构建用户界面。 在WPF MVVM示例中,通常会有三个主要的组成部分:Model、View和ViewModel。Model用于表示数据模型,即应用程序中的业务逻辑和数据。View用于表示用户界面,即显示数据和与用户交互的部分。ViewModel则是连接Model和View的桥梁,负责处理数据和业务逻辑,并将其绑定到View上。 在WPF MVVM示例中,首先需要创建一个Model,该Model包含应用程序需要使用的数据和方法。然后,创建一个View,该View负责展示数据和与用户交互,通常是通过XAML来构建用户界面。接下来,创建一个ViewModel,该ViewModel将负责处理数据和业务逻辑,并将其绑定到View上。 ViewModel通常会包含一些属性,用于存储数据,并通过数据绑定将这些数据展示在View上。ViewModel还会包含一些命令(Command),用于处理用户的操作,并根据需要更新数据。ViewModel还可以使用一种叫做INotifyPropertyChanged的接口,以实现数据的双向绑定,即当数据发生变化时,自动更新View上的数据。 WPF MVVM示例还可以包含一些其他的功能,比如使用容器控件(如ListBox、TreeView等)来展示数据列表或树状结构,使用验证机制来确保用户输入的有效性,使用消息机制来实现模块间的通信等。 总之,WPF MVVM示例是一种通过使用WPF技术和MVVM设计模式来构建用户界面的示例项目。它可以帮助开发人员更好地组织和管理代码,提高代码的可维护性和可扩展性,并提供更好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值