WPF之prism的基本使用
项目创建
先创建一个wpf项目,再引入包,可以直接引入Prism.Unity包,这个包会包含基础的包,并使用Unity的容器管理
在App.xaml中修改为prismApp并去掉starturi
<prism:PrismApplication
x:Class="wpf.prism.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf.prism"
xmlns:prism="http://prismlibrary.com/">
<Application.Resources />
</prism:PrismApplication>
在App.xaml.cs中实现PrismApplication接口,该接口包含CreateShell和RegisterTypes两个方法需要实现。
CreateShell是返回打开的主窗口,一般通过容器获取。
RegisterTypes是用于注册需要的各种类,包括view和viewModel。
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterInstance<MyService>(new MyService());
containerRegistry.RegisterSingleton<MainViewModel>();
containerRegistry.RegisterDialog<LoginDialog, LoginDialogViewModel>("loginDialog");
containerRegistry.RegisterSingleton<ViewAViewModel>();
containerRegistry.RegisterForNavigation<ViewA>();
}
}
1.mvvm
该框架的mvvm相关功能模块主要在Prism.Core这个包中,该包实现了一有关INotifyPropertyChanged和ICommand的相关功能。
1.1.通知
通过实现BindableBase接口来实现对界面的通知功能。需要写完整的字段和属性,并调用SetProperty方法来通知.
还有RaisePropertyChanged方法也可以实现通知效果,但是因为没有值校验,会导致所有的数据都会通知前台,不建议使用。
public class MainViewModel : BindableBase
{
//这样定义一个可以通知view的属性
private string? _showText = "this is original value";
public string? ShowText
{
get { return _showText; }
set { SetProperty(ref _showText, value); }
}
}
页面绑定到这个属性,这样后台的值发生变化时,前台页面也会修改
<Grid>
<TextBox Text="{Binding ShowText}" />
</Grid>
1.2.命令(Command)
使用此工具包可以快捷创建一个新的命令,实例化时,可以传入两个参数,分别为触发执行函数,判断是否可执行的函数。
可以安装prism的扩展,然后就可以输入cmd快捷生成这一段代码
private DelegateCommand _updateCommand;
public DelegateCommand UpdateCommand =>
_updateCommand ?? (_updateCommand = new DelegateCommand(ExecuteCommandName, canExecuteMethod: CanUpdated));
//是否可以执行,比如当ShowText属性不为空时才可以执行
bool CanUpdated() => !string.IsNullOrEmpty(ShowText);
//执行的命令
void ExecuteCommandName()
{
//do sth.
}
使用ObservableProperty方法可以根据传入的属性变化从而执行CanExecute方法更新
//这样定义一个可以通知view的属性
private string? _showText = "this is original value";
public string? ShowText
{
get { return _showText; }
set { SetProperty(ref _showText, value); }
}
private DelegateCommand _updateCommand;
public DelegateCommand UpdateCommand =>
_updateCommand ?? (_updateCommand = new DelegateCommand(ExecuteCommandName, canExecuteMethod: CanUpdated).ObservesProperty(() => ShowText));
//执行的命令
void ExecuteCommandName()
{
//...
}
另外,是否可执行可以与前台页面相互联动,比如当命令不能执行时,按钮为灰色。
为实现这个效果,可以让button的IsEnabled方法绑定一个属性,在执行canExeCute方法时更新ui
private bool _isEnabled;
//这个属性绑定到Button的isEnabled
public bool IsEnabled
{
get { return _isEnabled; }
set { SetProperty(ref _isEnabled, value); }
}
public DelegateCommand UpdateCommand =>
_updateCommand ?? (_updateCommand = new DelegateCommand(ExecuteCommandName, canExecuteMethod: CanUpdated).ObservesProperty(() => ShowText));
//是否可以执行
bool CanUpdated()
{
var able = !string.IsNullOrEmpty(ShowText);
执行时更改这个属性
IsEnabled = able;
return able;
}
1.3.viewModel间的通信
prism使用注册和订阅来进行viewModel之间的通信,首先需要通过实现Pub接口定义一个订阅类,规定传递的数据类型
//创建一个订阅类,根据泛型传递string类型的数据,订阅双方使用这个类来传递数据
public class MyMessager : PubSubEvent<string>
{
}
然后再实例在需要通信的两个类中引入订阅类,通常是使用依赖注入的方式。
//发送方
public class MainViewModel : BindableBase
{
public MainViewModel(IEventAggregator ea)
{
this.ea = ea;
}
void SendMsg()
{
//发送消息
ea.GetEvent<MyMessager>().Publish("send a msg");
}
}
//接收方
public class SonViewModel : BindableBase
{
private string? _reveivedMsg;
private readonly IEventAggregator ea;
public string? ReceivedMsg
{
get { return _reveivedMsg; }
set { SetProperty(ref _reveivedMsg, value); }
}
public SonViewModel(IEventAggregator ea)
{
this.ea = ea;
//订阅这个类
ea.GetEvent<MyMessager>().Subscribe(Reseived);
}
//处理接收数据的方法
private void Reseived(string obj)
{
this.ReceivedMsg = obj;
}
}
2.依赖注入
在依赖注入方面,可以选择不同的容器进行,这里选择的是Unity容器。
在App.xaml.cs中,注册需要的类
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
//管理注册的类
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册单例,可以手动new一个
containerRegistry.RegisterInstance<MyService>(new MyService());
//以单例模式注册主窗口
containerRegistry.RegisterSingleton<MainViewModel>();
//注册一个Dialog,后面会讲到
containerRegistry.RegisterDialog<LoginDialog, LoginDialogViewModel>("loginDialog");
containerRegistry.RegisterSingleton<ViewAViewModel>();
//注册一个导航页面,可以使用导航系统获取这个页面
containerRegistry.RegisterForNavigation<ViewA>();
}
}
3.region+navigation
3.1.注册Region
prism将页面划分为不同的区域,使用该功能可以动态修改ui的界面显示的内容和控件。
我们可以先创建一个注册一个区域,这在xaml和后台代码中都可以实现。
<StackPanel>
<Button Command="{Binding NavigateToCommand}" Content="navigate to viewA" />
<!-- 这里注册了两个区域,分别为RegionA和RegionB -->
<ContentControl Name="contentRegion" prism:RegionManager.RegionName="RegionA" />
<ContentControl prism:RegionManager.RegionName="RegionB" />
</StackPanel>
//使用后台代码注册
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
RegionManager.SetRegionName(contentRegion, "RegionA");
}
}
3.2.导航
我这里定义了一个叫ViewA的控件,可以使用RegionManager导航显示这个控件
在viewModel中使用命令显示控件
private DelegateCommand _navigateTo;
public DelegateCommand NavigateToCommand =>
_navigateTo ?? (_navigateTo = new DelegateCommand(ExecuteNavigateToCommand));
void ExecuteNavigateToCommand()
{
//在RegionA上显示控件ViewA
regionManager.RequestNavigate("RegionA", "ViewA");
//这个方法同样的效果
regionManager.RegisterViewWithRegion<ViewA>("RegionA");
}
导航的同时,还可以有回调参数,传递数据等。。
4.DialogService
首先创建一个用户控件叫做LoginDialog和它的viewModel,然后在app.xaml.cs中注册,起个别名ligonDialog
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册起别名
containerRegistry.RegisterDialog<LoginDialog, LoginDialogViewModel>("loginDialog");
}
}
另外,loginDialogViewModel需要实现IDialogAware接口
public class LoginDialogViewModel : BindableBase, IDialogAware
{
private string? _userName;
public string? UserName
{
get { return _userName; }
set { SetProperty(ref _userName, value); }
}
private string? _passwd;
public string? Passwd
{
get { return _passwd; }
set { SetProperty(ref _passwd, value); }
}
//返回消息时触发
public DialogCloseListener RequestClose { get; }
private DelegateCommand _save;
public DelegateCommand SaveCommand =>
_save ?? (_save = new DelegateCommand(ExecuteSaveCommand));
//点击保存的按钮执行这个
//这个方法向调用者发送了两个数据,并将返回值设为OK
void ExecuteSaveCommand()
{
var param = new DialogParameters()
{
{ "userName",UserName},
{ "passwd",Passwd}
};
RequestClose.Invoke(new DialogResult()
{
Result = ButtonResult.OK,
Parameters = param
});
}
private DelegateCommand _cancel;
public DelegateCommand CancelCommand =>
_cancel ?? (_cancel = new DelegateCommand(ExecuteCancelCommand));
//点击取消的按钮执行这个
void ExecuteCancelCommand()
{
RequestClose.Invoke(ButtonResult.Cancel);
}
//控制是否可以关闭
public bool CanCloseDialog()
{
return true;
}
//关闭时执行这个
public void OnDialogClosed()
{
}
//dialog打开时会执行这个方法
public void OnDialogOpened(IDialogParameters parameters)
{
//接收到的参数
UserName = parameters.GetValue<string>("name");
Passwd = parameters.GetValue<string>("pass");
}
}
使用,调用这个dialog
//定义要传递的参数
var param = new DialogParameters()
{
{ "name","张三"},
{"pass","12121" }
};
//显示dialog,输入dialog的别名,传递的参数,dailog关闭时的回调
dialogService.ShowDialog("loginDialog", param, (s) =>
{
ShowText = s.Parameters.GetValue<string>("userName");
});