【6月日新计划10】WPF入门-MVVM
官方提供了更全面的依賴,不用自己封裝ICommand和INotifyPropertyChanged
1.MvvmLight
下載依賴,如下操作:
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
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
Prism是一个用于在 WPF、Xamarin Form、Uno 平台和 WinUI 中构建松散耦合、可维护和可测试的 XAML 应用程序框架。
Sample-Demo:學習各部分功能的代碼
3.1 Install
1.安裝擴展
點擊菜單欄的擴展 -> 搜索Prism -> 重新啟動
2.創建項目
3.我的專案名字FullApp02,設為啟動項。
4.啟動
3.2 NuGet import Prism
1.在NuGet中搜索Prism DryIoc
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.兩個文件夾的名字Views和ViewModels是固定格式,以及下面的MainWindow.xaml和MainWindowViewModel.cs
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。
Reference: github.com/PrismLibrar…
1.生成事件 使用其他Model時,需要生成 類庫右鍵 -> 點擊屬性 -> 生成事件
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
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…
3.7 Router
Reference:github.com/PrismLibrar…
1.目錄結構
- 點擊按鈕A,顯示ViewA
- 點擊按鈕B,顯示ViewB
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;
}
}
}
}