数据加载动画实现
概要:
当打开功能页面时,在数据未加载完毕前,希望有一个友好的等待提示。那么,本章通过学习Prism 中事件聚合器(EventAggregator),并通过创建等待提示窗口,同时结合事件订阅发布来实现该功能点。
一.事件聚合器(EventAggregator)实现发布和订阅
1.定义事件消息模型
定义一个 UpdateLoadingEvent (等待加载完成的事件)消息模型,并继承PubSubEvent。PubSubEvent 是一个泛型,可以传入字符串或其他。如下传入一个UpdateModel 实体类。表明了当前的消息模型用来传递一个对象实体类的消息或定义成一个string (字符串) 表示传递一个字符串的消息。
传递对象实体类消息
public class UpdateModel
{
public bool IsOpen { get; set; }
}
public class UpdateLoadingEvent:PubSubEvent<UpdateModel>
{
}
传递字符串消息
public class UpdateLoadingEvent:PubSubEvent<string>
{
}
2.发布事件
如何使用:在构造函数中注入IEventAggregator,并通过IEventAggregator的GetEvent获取定义的UpdateLoadingEvent 消息,再通过Publish 方法进行发布消息。
当前发布事件主要用来,处理某个View 或ViewMode 之间传递消息,则使用 Publish 来进行发布一个消息
/// <summary>
/// 发布事件
/// </summary>
/// <param name="aggregator"></param>
/// <param name="model"></param>
public UpdateLoading(IEventAggregator aggregator,UpdateModel model)
{
aggregator.GetEvent<UpdateLoadingEvent>().Publish(model);
}
3.订阅事件
如何使用:在构造函数中注入IEventAggregator,并通过IEventAggregator的GetEvent获取定义的UpdateLoadingEvent 消息,再通过Subscribe方法注册,Subscribe是一个委托方法
/// <summary>
/// 订阅事件
/// </summary>
/// <param name="aggregator"></param>
/// <param name="model"></param>
public UpdateLoading(IEventAggregator aggregator,Action<UpdateModel> model)
{
aggregator.GetEvent<UpdateLoadingEvent>().Subscribe(model);
}
一种写法
/// <summary>
/// 订阅事件
/// </summary>
/// <param name="aggregator"></param>
/// <param name="model"></param>
public UpdateLoading(IEventAggregator aggregator)
{
aggregator.GetEvent<UpdateLoadingEvent>().Subscribe(arg=>
{
// arg 就是订阅到的消息,进行逻辑处理
});
}
一种写法
/// <summary>
/// 订阅事件
/// </summary>
/// <param name="aggregator"></param>
/// <param name="model"></param>
public UpdateLoading(IEventAggregator aggregator)
{
aggregator.GetEvent<UpdateLoadingEvent>().Subscribe(SubMessage);
}
private void SubMessage(UpdateModel model)
{
//逻辑处理
}
无论是那种写法,订阅事件都是需要传入一个委托。并且只有订阅了相关消息模型后,从其他View 或ViewMode 发布的消息,才能成功接收到。一般,订阅消息,也称注册消息,都是干同一件事件。
4.取消订阅
Unsubscribe 同样也是一个委托方法。事件订阅被取消后,无论再怎么发布该事件,都无法接收到该事件消息了。
/// <summary>
/// 发布事件
/// </summary>
/// <param name="aggregator"></param>
/// <param name="model"></param>
public UpdateLoading(IEventAggregator aggregator)
{
aggregator.GetEvent<UpdateLoadingEvent>().Unsubscribe(SubMessage);
}
这样,一个发布/订阅/取消事件的例子就完成了。上面只是说明Prism 事件聚合器的使用例子。表示怎么定义事件模型,以及使用事件模型。有了上面的基础后,接着实现根据事件聚合器实现数据加载等待窗口功能。
二.实现数据加载等待窗口功能
当打开待办事项或备忘录数据窗口时,在数据未准备完成前,显示一个加载中的提示消息窗口。
首先是要在MainView 中,定义一个名字:DialogHost
1.然后要在Views 文件夹中定义加载中视图窗口 (ProgressView.xaml)
<UserControl x:Class="MyToDo.Views.ProgressView"
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:MyToDo.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<StackPanel Width="80">
<ProgressBar Width="40" Height="40" IsIndeterminate="True" Margin=" 20"
Style="{StaticResource MaterialDesignCircularProgressBar}"
/>
</StackPanel>
</Grid>
</UserControl>
2.定义打开窗口的消息模型
创建一个 Events 文件夹,定义一个 UpdateLoadingEvent (等待加载完成的事件)消息模型。并且定义一个实体类 UpdateModel ,实体类有一个IsOpen属性,表示窗口是否打开状态。把该实体类传入泛型对象PubSubEvent 中。
namespace MyToDo.Common.Events
{
public class UpdateModel
{
public bool IsOpen { get; set; }
}
public class UpdateLoadingEvent:PubSubEvent<UpdateModel>
{
}
}
3.创建一个基类 NavigationViewModel
封装一个基类(共用类) NavigationViewModel,该类的主要作用是可以设置窗口的等待,就是它需要等待我们数据加载完成,才能展示出对应的数据窗口。它同时具备导航功能,需要转到对应的数据页面。且需要继承INavigationAware。该基类的方法,增加 virtual,表示它是一个虚方法,表明其他继承基类的子类也能修改基类方法。
创建一个基类(NavigationViewModel)这里,有点绕。就是例如:待办事项页面,需要等待数据加载完成。那么我们就需要把这个待办事项页面设置成可等待窗口,同时显示加载中的效果。所以就需要这么去创建一个基类,同时其他页面也能沿用该基类。
然后在基类构造函数中通过 IOC 容器(IContainerProvider) ,注册事件聚合器,并拿到IEventAggregator 的实例。再通过IEventAggregator 去发布消息,设置页面要展示的加载中的效果。
NavigationViewModel 类
namespace MyToDo.ViewModels
{
public class NavigationViewModel : BindableBase, INavigationAware
{
private readonly IContainerProvider containerProvider;
public readonly IEventAggregator aggregator;
public NavigationViewModel(IContainerProvider containerProvider)
{
this.containerProvider = containerProvider;
aggregator=containerProvider.Resolve<IEventAggregator>();
}
/// <summary>
/// 重用以前窗口
/// </summary>
/// <param name="navigationContext"></param>
/// <returns></returns>
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public virtual void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public virtual void OnNavigatedTo(NavigationContext navigationContext)
{
}
/// <summary>
/// 发布消息
/// </summary>
/// <param name="IsOpen">是否打开窗口</param>
public void UpdateLoading(bool IsOpen)
{
aggregator.UpdateLoading(new Common.Events.UpdateModel()
{
IsOpen = IsOpen
});
}
}
}
然后接着实现IEventAggregator 事件的发布/订阅 。目前考滤是把发布和订阅,做成一个扩展类,可供NavigationViewModel 或MainView 使用。或其他view中需要使用到发布订阅都可以用到该扩展。
namespace MyToDo.Extensions
{
public static class DialogExtensions
{
/// <summary>
/// 发布事件
/// </summary>
/// <param name="aggregator"></param>
/// <param name="model"></param>
public static void UpdateLoading(this IEventAggregator aggregator,UpdateModel model)
{
aggregator.GetEvent<UpdateLoadingEvent>().Publish(model);
}
/// <summary>
/// 订阅事件
/// </summary>
/// <param name="aggregator"></param>
/// <param name="model"></param>
public static void Resgiter(this IEventAggregator aggregator,Action<UpdateModel> model)
{
aggregator.GetEvent<UpdateLoadingEvent>().Subscribe(model);
}
}
}
4.发布消息
首先:当前待办事项页面,或备忘录页面,当打开页面时,需要加载数据。那么待办事项类就需要在数据未准备好前,发布一个打开加载中的窗口的一个消息。数据渲染完成后,发布一个关闭加载中的窗口的一个消息。
待办事项类 (ToDoViewModel)修改。
namespace MyToDo.ViewModels
{
public class ToDoViewModel: NavigationViewModel
{
//由于NavigationViewModel 类构造中传入了 IOC容器,所以当前类继承的时候,需要把对应的参数传通过Base传过去就不会报错了
public ToDoViewModel(IToDoService toDoService, IContainerProvider provider):base(provider)
{
ToDoDtos = new ObservableCollection<ToDoDto>();
AddCommand = new DelegateCommand(Add);
this.toDoService = toDoService;
}
private bool isRightDrawerOpen;
/// <summary>
/// 右侧编辑窗口是否展开
/// </summary>
public bool IsRightDrawerOpen
{
get { return isRightDrawerOpen; }
set { isRightDrawerOpen = value; RaisePropertyChanged(); }
}
public DelegateCommand AddCommand{ get; private set; }
private ObservableCollection<ToDoDto> toDoDtos;
private readonly IToDoService toDoService;
/// <summary>
/// 创建数据的动态集合
/// </summary>
public ObservableCollection<ToDoDto> ToDoDtos
{
get { return toDoDtos; }
set { toDoDtos = value;RaisePropertyChanged(); }
}
/// <summary>
/// 获取数据
/// </summary>
async void GetDataAsync()
{
UpdateLoading(true); //发布消息,设置加载中的窗口
//添加查询条件
var todoResult=await toDoService.GetAllAsync(new Shared.Parameters.QueryParameter()
{
PageIndex = 0,
PageSize = 100,
});
if (todoResult.Status)
{
toDoDtos.Clear();
foreach (var item in todoResult.Result.Items)
{
toDoDtos.Add(item);
}
}
UpdateLoading(false); //发布消息,关闭加载中的窗口
}
/// <summary>
/// 添加待办
/// </summary>
/// <exception cref="NotImplementedException"></exception>
private void Add()
{
IsRightDrawerOpen=true;
}
//重写导航加载数据的方法
public override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);
GetDataAsync();
}
}
}
5.MainView 消息订阅
最后:当消息发布后,谁要订阅到这个消息?并进行对应的打开或关闭加载中的窗口逻辑处理?那就是要在 MainView中订阅,传递过来的消息是否要打开加载中的窗口(ProgressView.xaml)或关闭加载中的窗口(ProgressView.xaml)。所以需要在MainView中去实现该功能逻辑
namespace MyToDo.Views
{
/// <summary>
/// MainView.xaml 的交互逻辑
/// </summary>
public partial class MainView : Window
{
public MainView(IEventAggregator aggregator)
{
InitializeComponent();
//订阅是否打开或关闭加载中的窗口
aggregator.Resgiter(arg =>
{
DialogHost.IsOpen = arg.IsOpen;//设置打开窗口
if (DialogHost.IsOpen)
{
DialogHost.DialogContent = new ProgressView();
}
});
}
}
}