【6月日新计划10】WPF入门-MVVM

794 阅读3分钟

【6月日新计划10】WPF入门-MVVM

官方提供了更全面的依賴,不用自己封裝ICommand和INotifyPropertyChanged

1.MvvmLight

下載依賴,如下操作:

图片.png

图片.png

namespace WpfApp01
{
    public class MainViewModel : ViewModelBase
    {

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                RaisePropertyChanged();
            }
        }

        public RelayCommand ShowCommand { get; set; }

        public MainViewModel()
        {
            Name = "Hello Word";
            this.ShowCommand = new RelayCommand(ShowMsg);
        }

        public void ShowMsg()
        {
            Name = "弹出消息示例";
            MessageBox.Show("弹出消息");
        }
    }
}

在後端程序MainWindow.xaml.cs中引入

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }

2. Microsoft.Toolkit.Mvvm

同理,先下載依賴。輸入Microsoft.Toolkit.Mvvm

图片.png

namespace WpfApp01
{
    public class MainViewModel : ObservableObject
    {

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                OnPropertyChanged();
            }
        }

        public RelayCommand ShowCommand { get; set; }

        public MainViewModel()
        {
            Name = "Hello Word";
            this.ShowCommand = new RelayCommand(ShowMsg);
        }

        public void ShowMsg()
        {
            Name = "弹出消息示例";
            MessageBox.Show("弹出消息");
        }
    }
}

在後端程序MainWindow.xaml.cs中引入

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }

3. Prism

github.com/PrismLibrar…

Prism是一个用于在 WPFXamarin FormUno 平台WinUI 中构建松散耦合、可维护和可测试的 XAML 应用程序框架。

图片.png

Sample-Demo:學習各部分功能的代碼

github.com/PrismLibrar…

3.1 Install

1.安裝擴展

點擊菜單欄的擴展 -> 搜索Prism -> 重新啟動

图片.png

2.創建項目

图片.png

3.我的專案名字FullApp02,設為啟動項。

图片.png

4.啟動

图片.png

3.2 NuGet import Prism

1.在NuGet中搜索Prism DryIoc

图片.png

2.添加名稱空間prism,修改基類

<prism:PrismApplication x:Class="FullApp01.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:FullApp01"
             xmlns:prism="http://prismlibrary.com/" >
    <Application.Resources>
         
    </Application.Resources>
</prism:PrismApplication>

一定要記得:刪除StartupUri,不然會出現兩個窗口

3.修改app.xaml.cs,實現PrismApplication

3.3 Detail

1.安裝了prism template插件以後,直接使用prism創建項目

2.查看項目結構,在app.xaml中創建prism的名稱空間,以及修改基類

<prism:PrismApplication x:Class="FullApp01.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:FullApp01"
             xmlns:prism="http://prismlibrary.com/" >
    <Application.Resources>
         
    </Application.Resources>
</prism:PrismApplication>

3.app.xaml.cs中實現PrismApplication,重寫裡面的接口

    public partial class App
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<IMessageService, MessageService>();
        }

        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            moduleCatalog.AddModule<ModuleNameModule>();
        }
    }

4.兩個文件夾的名字ViewsViewModels是固定格式,以及下面的MainWindow.xamlMainWindowViewModel.cs

图片.png

5.在MainWindows.xaml中帶入prism,以及掃所有的包prism:ViewModelLocator.AutoWireViewModel="True"

<Window x:Class="FullApp01.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:core="clr-namespace:FullApp01.Core;assembly=FullApp01.Core"
        Title="{Binding Title}" Height="350" Width="525" >
    <Grid>
        <ContentControl prism:RegionManager.RegionName="{x:Static core:RegionNames.ContentRegion}" />

    </Grid>
</Window>

3.4 Adapter

<ContentControl prism:RegionManager.RegionName="ContentRegion" />就是在xaml中定義了一個名字的ContentRegion的區域。

上面的代碼能運行,是因為Prism默认提供了几种适配器:
    ContentControlRegionAdapter 内容控制区域适配器
    SelectorRegionAdaptor 选择区域适配器
    ItemsControlRegionAdapter 区域适配器

如果在MainWindow.xaml中不使用ContentControl,而是StackPanel,那麼就無法定義區域,所有只能換條路,自定義一個適配器。

using Prism.Regions;
using System.Windows;
using System.Windows.Controls;

namespace Regions
{
    public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
    {
        public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
            : base(regionBehaviorFactory)
        {

        }

        protected override void Adapt(IRegion region, StackPanel regionTarget)
        {
            region.Views.CollectionChanged += (s, e) =>
            {
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                {
                    foreach (FrameworkElement element in e.NewItems)
                    {
                        regionTarget.Children.Add(element);
                    }
                }

                //handle remove
            };
        }

        protected override IRegion CreateRegion()
        {
            return new AllActiveRegion();
        }
    }
}

最後在app.xaml.cs内注册StackPanelRegionAdapter,即可完成自定义区域适配器

   protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
        {
            base.ConfigureRegionAdapterMappings(regionAdapterMappings);
          
            regionAdapterMappings.RegisterMapping(typeof(StackPanel),Container.Resolve<StackPanelRegionAdapter>() );
        }

3.5 Module

此部分僅支持.Net Core(不支持.net Framework),使代碼盡最大化的解耦,每個部分都是一個module。

图片.png

Reference: github.com/PrismLibrar… 图片.png

1.生成事件 使用其他Model時,需要生成 類庫右鍵 -> 點擊屬性 -> 生成事件

图片.png

2.視圖注入

[Module(ModuleName = "Contact")]
public class ModuleA : IModule
{
    public void OnInitialized(IContainerProvider containerProvider)
    {
        //通过注册RegionManager,添加ContactView
        var regionManager = containerProvider.Resolve<IRegionManager>();
        //通过ContentRegion管理导航默认初始页面ViewA
        var contentRegion = regionManager.Regions["ContentRegion"];
        contentRegion.RequestNavigate(nameof(ViewA));
    }
 
    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterForNavigation<ViewA>();
    }
}

3.6 Mvvm

图片.png

1.設置自動關聯

prism:ViewModelLocator.AutoWireViewModel="True"

2.Code

Reference: github.com/PrismLibrar…

DelegateCommand就是ICommand,操作數據

BindableBase就是通知,RaisePropertyChanged()=>反過來影響UI.

namespace UsingDelegateCommands.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private bool _isEnabled;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                _isEnabled=true;
                RaisePropertyChanged();
            }
        }
        private string _updateText;
        public string UpdateText
        {
            get { return _updateText; }
            set 
            { 
                SetProperty(ref _updateText, value);
                RaisePropertyChanged();
            }
        }
        
        public DelegateCommand ExecuteDelegateCommand { get; private set; }
        public DelegateCommand<string> ExecuteGenericDelegateCommand { get; private set; }
        public DelegateCommand DelegateCommandObservesProperty { get; private set; }
        public DelegateCommand DelegateCommandObservesCanExecute { get; private set; }
        public MainWindowViewModel()
        {
        
            ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
            DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
            DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled);         
            ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
        }
        private void Execute()
        {
            UpdateText = $"Updated: {DateTime.Now}";
        }
        private void ExecuteGeneric(string parameter)
        {
            UpdateText = parameter;
        }
        private bool CanExecute()
        {
            return IsEnabled;
        }
    }
}

3.發佈訂閱和過濾

Reference: github.com/PrismLibrar…

图片.png

3.7 Router

Reference:github.com/PrismLibrar…

图片.png

1.目錄結構

  • 點擊按鈕A,顯示ViewA
  • 點擊按鈕B,顯示ViewB 图片.png

2.App.xaml注入視圖

namespace FullApp01
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<ViewA>();
            containerRegistry.RegisterForNavigation<ViewB>();
        }

        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
        }
    }
}

3.MainWindow.xaml自動導入和按鈕點擊傳參

xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True"

<Window x:Class="FullApp01.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:core="clr-namespace:FullApp01.Core;assembly=FullApp01.Core"
        Title="{Binding Title}" Height="550" Width="850" >
    <Window.Resources>
        <Style TargetType="Button" x:Key="left_btn">
            <Setter Property="Height" Value="45"/>
            <Setter Property="Width" Value="80"/>
            <Setter Property="Background" Value="GreenYellow"/>
            <Setter Property="Margin" Value="5"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition />
        </Grid.RowDefinitions>
        <Border Grid.Row="0">
            <TextBlock Height="60" Width="650" Background="AliceBlue" Text="Prism Template"/>

        </Border>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150"/>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Border Grid.Column="0" Background="Orchid">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <Button Grid.Row="0" Content="Btn01" Style="{StaticResource left_btn}" Command="{Binding NavigateCommand}" CommandParameter="ViewA"></Button>
                    <Button Grid.Row="1" Content="Btn02" Style="{StaticResource left_btn}" Command="{Binding NavigateCommand}" CommandParameter="ViewB"></Button>
                    <Button Grid.Row="2" Content="Btn03" Style="{StaticResource left_btn}"></Button>
                    <Button Grid.Row="3" Content="Btn04" Style="{StaticResource left_btn}"></Button>
                </Grid>
            </Border>
            <Border Grid.Column="1" Margin="2">
                <ContentControl prism:RegionManager.RegionName="{x:Static core:RegionNames.ContentRegion}" />
            </Border>
        </Grid>
       
    </Grid>
</Window>

4.MainVindowViewModel處理方法

namespace FullApp01.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";

        private readonly IRegionManager _regionManager;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        public DelegateCommand<string> NavigateCommand { get; private set; }

        public MainWindowViewModel(IRegionManager regionManager)
        {
            _regionManager = regionManager;
            NavigateCommand = new DelegateCommand<string>(Navigate);
        }
        private void Navigate(string navigatePath) 
        {
            if (NavigateCommand != null) 
            {
                _regionManager.RequestNavigate("ContentRegion", navigatePath);
            }
        }
    }
}

3.8 Dialog

1.創建Dialog

就是在Views目錄下創建UserControl

<UserControl x:Class="FullApp01.Views.ViewMsg"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:FullApp01.Views"
             xmlns:prism="http://prismlibrary.com/"
             mc:Ignorable="d" 
             d:DesignHeight="250" d:DesignWidth="400">
    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="Width" Value="500"/>
            <Setter Property="Height" Value="400"/>
        </Style>
    </prism:Dialog.WindowStyle>
    <UserControl.Resources>
        <Style TargetType="Button">
            <Setter Property="Height" Value="28"/>
            <Setter Property="Width" Value="50"/>
            <Setter Property="Background" Value="Bisque"/>
            <Setter Property="Margin" Value="6"/>
        </Style>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition />
            <RowDefinition Height="45"/>
        </Grid.RowDefinitions>
        <Border Grid.Row="0">
            <TextBlock Text="Dialog Test" FontSize="25" TextAlignment="Center"/>
        </Border>
        <Border Grid.Row="1">
            <TextBox FontSize="25" Text="{Binding Title}">
            </TextBox>
        </Border>
        <Border Grid.Row="2">
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                <Button Content="Save" Command="{Binding SaveCommand}"></Button>
                <Button Content="Cancle" Command="{Binding CancelCommand}"></Button>
            </StackPanel>
        </Border>
    </Grid>
</UserControl>

2.創建對應後台

在ViewModels目錄下創建類

namespace FullApp01.ViewModels
{
    class ViewMsgModel : BindableBase, IDialogAware
    {
        private string title;
        
        public string Title{
            get { return title; }
            set { title = value; RaisePropertyChanged(); }
        }
        public DelegateCommand SaveCommand { get; set; }

        public DelegateCommand CancelCommand { get; set; }

        public event Action<IDialogResult> RequestClose;

        public ViewMsgModel() { 
            Title = "This is Dialog Test";

            SaveCommand = new DelegateCommand(() => 
                {
                    DialogParameters parameters = new DialogParameters();
                    parameters.Add("Value", Title);
                    RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
                }
            );

            CancelCommand = new DelegateCommand(() =>
                {
                    RequestClose?.Invoke(new DialogResult(ButtonResult.No));
                }
            );
            
        }

        bool IDialogAware.CanCloseDialog()
        {
            return true;
        }

        void IDialogAware.OnDialogClosed()
        {
            
        }

        void IDialogAware.OnDialogOpened(IDialogParameters parameters)
        {
            var value = parameters.GetValue<string>("topic");
        }
    }
}

3.View和Model進行綁定

在App.xaml.cs中綁定

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            //containerRegistry.RegisterSingleton<IMessageService, MessageService>();

            containerRegistry.RegisterForNavigation<ViewA>();
            containerRegistry.RegisterForNavigation<ViewB>();

            //前端和後台代碼綁定
            containerRegistry.RegisterDialog<ViewMsg,ViewMsgModel> ();
        }

綁定時可以添加別名。

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            //仅注册视图
            containerRegistry.RegisterDialog<MessageDialog>();
 
            //注册视图时绑定VM
            containerRegistry.RegisterDialog<MessageDialog, MessageDialogViewModel>();
 
            //添加别名
            containerRegistry.RegisterDialog<MessageDialog>("DialogName");
        }

在MainWindow.xaml賦予按鈕彈窗功能

 <Button Grid.Row="3" Content="Btn04" Style="{StaticResource left_btn}" Command="{Binding OpenCommand}"></Button>

MainWindowViewModel.CS詳細的彈窗寫法

namespace FullApp01.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string title = "Prism Template";

        private readonly IRegionManager _regionManager;

        private readonly IDialogService dialog;

        public DelegateCommand<string> NavigateCommand { get; private set; }

        public DelegateCommand OpenCommand { get; private set; }

        public CompositeCommand OpenAll { get; private set; }


        public string Title
        {
            get { return title; }
            set 
            { 
                title = value;
                RaisePropertyChanged(); 
            }
        }

        public MainWindowViewModel(IRegionManager regionManager,IDialogService dialog)
        {

            _regionManager = regionManager;
            NavigateCommand = new DelegateCommand<string>(Navigate);
            OpenCommand = new DelegateCommand(OpenA);

            OpenAll = new CompositeCommand();
            OpenAll.RegisterCommand(NavigateCommand);
            OpenAll.RegisterCommand(OpenCommand);

            this.dialog = dialog;
        }

        private void OpenA()
        {
            DialogParameters parameters = new DialogParameters();
            parameters.Add("topic", "this is first prism demo");

            dialog.ShowDialog("ViewMsg", parameters, arg =>
                {
                    if (arg.Result == ButtonResult.OK)
                    {
                        var input = arg.Parameters.GetValue<string>("Value");
                        MessageBox.Show($"用戶輸入:{input}");
                    }
                    else {
                        MessageBox.Show("用戶取消了彈窗");
                    }
                }
            );
        }

        private void Navigate(string navigatePath) 
        {
            if (NavigateCommand != null) 
            {
                _regionManager.RequestNavigate("ContentRegion", navigatePath);
                Title = navigatePath;
            }
        }
    }
}