MVVM-Sidekick 之SendToEventRouterAction使用

原文:MVVM-Sidekick 之SendToEventRouterAction使用

在WP开发中点击列表项跳转到详情页是一个很常用的功能,但是有可能项模板中还有其他的区域,比如点击标题跳转到详情页,点击"赞"图标送一个赞,点击"踩"图标踩一下,那如何处理同一个项的不同点击区域呢?

在WP7时代我是这么做的,先在cs代码中处理点击事件,然后判断sender是项模板里的哪个控件,根据不同控件来做不同的处理。感觉弱爆了!

后来使用了MVVM-Sidekick之后,实现这种目的变得方便多了!(韦恩卑鄙快给广告费!)

我会在以下的Demo里演示这种高大上的用法。

首先新建一个WP8.1的MVVM-Sidekick项目,我手头没有Win10的开发机,建Win10的也一样。

1.建立Model

在项目中添加Models文件夹,添加一个UserInfoItem类,继承于BindableBase<UserInfoItem>,代码如下:

public class UserInfoItem : BindableBase<UserInfoItem>

{

public string UserName

{

get { return _UserNameLocator(this).Value; }

set { _UserNameLocator(this).SetValueAndTryNotify(value); }

}

#region Property string UserName Setup

protected Property<string> _UserName = new Property<string> { LocatorFunc = _UserNameLocator };

static Func<BindableBase, ValueContainer<string>> _UserNameLocator = RegisterContainerLocator<string>("UserName", model => model.Initialize("UserName", ref model._UserName, ref _UserNameLocator, _UserNameDefaultValueFactory));

static Func<string> _UserNameDefaultValueFactory = () => { return default(string); };

#endregion

public int Age

{

get { return _AgeLocator(this).Value; }

set { _AgeLocator(this).SetValueAndTryNotify(value); }

}

#region Property int Age Setup

protected Property<int> _Age = new Property<int> { LocatorFunc = _AgeLocator };

static Func<BindableBase, ValueContainer<int>> _AgeLocator = RegisterContainerLocator<int>("Age", model => model.Initialize("Age", ref model._Age, ref _AgeLocator, _AgeDefaultValueFactory));

static Func<int> _AgeDefaultValueFactory = () => { return default(int); };

#endregion

}

 

之前已经说过了,使用propvm代码段可以快速生成以上的属性。

2.初始化数据源

打开MainPage_Model.cs文件,使用propvm代码段添加一个ObservableCollection列表:

public ObservableCollection<UserInfoItem> UserInfoItemList

{

get { return _UserInfoItemListLocator(this).Value; }

set { _UserInfoItemListLocator(this).SetValueAndTryNotify(value); }

}

#region Property ObservableCollection<UserInfoItem> UserInfoItemList Setup

protected Property<ObservableCollection<UserInfoItem>> _UserInfoItemList = new Property<ObservableCollection<UserInfoItem>> { LocatorFunc = _UserInfoItemListLocator };

static Func<BindableBase, ValueContainer<ObservableCollection<UserInfoItem>>> _UserInfoItemListLocator = RegisterContainerLocator<ObservableCollection<UserInfoItem>>("UserInfoItemList", model => model.Initialize("UserInfoItemList", ref model._UserInfoItemList, ref _UserInfoItemListLocator, _UserInfoItemListDefaultValueFactory));

static Func<ObservableCollection<UserInfoItem>> _UserInfoItemListDefaultValueFactory = () => { return new ObservableCollection<UserInfoItem>(); };

#endregion

 

注意在_UserInfoItemListDefaultValueFactory里我改成了返回了一个new出来的ObservableCollection<UserInfoItem>,避免直接使用时因没有初始化而报错。这一步也可以放在MainPage_Model的构造函数里。

然后找到下面被注释掉的OnBindedViewLoad方法,初始化数据源:

///<summary>
/// This will be invoked by view when the view fires Load event and this viewmodel instance is already in view's ViewModel property
///</summary>
///<param name="view">View that firing Load event</param>
///<returns>Task awaiter</returns>
protected override Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
        {

    if (!UserInfoItemList.Any())
            {
                UserInfoItemList.Add(new UserInfoItem { UserName = "Jack", Age = 20 });
                UserInfoItemList.Add(new UserInfoItem { UserName = "Tom", Age = 21 });
                UserInfoItemList.Add(new UserInfoItem { UserName = "Lily", Age = 18 });
                UserInfoItemList.Add(new UserInfoItem { UserName = "Jim", Age = 20 });
                UserInfoItemList.Add(new UserInfoItem { UserName = "Bob", Age = 22 });
            }

        return base.OnBindedViewLoad(view);
        }

 

 

随便写上几个就好。

3.绑定数据

为了方便使用Blend,还需要增加设计视图支持。把以上初始化数据的代码,加到MainPage_Model的构造函数里,放在if (IsInDesignMode )里面:

public MainPage_Model()

{

if (IsInDesignMode )

{

Title = "Title is a little different in Design mode";

UserInfoItemList.Add(new UserInfoItem { UserName = "Jack", Age = 20 });

UserInfoItemList.Add(new UserInfoItem { UserName = "Tom", Age = 21 });

UserInfoItemList.Add(new UserInfoItem { UserName = "Lily", Age = 18 });

UserInfoItemList.Add(new UserInfoItem { UserName = "Jim", Age = 20 });

UserInfoItemList.Add(new UserInfoItem { UserName = "Bob", Age = 22 });

}

}

 

然后编译一下,用Blend打开。

在MainPage中添加一个ListView,绑定ItemsSource属性:

刚绑定上是这个样子:

接下来需要设置项模板,具体步骤就不说了,能显示出内容就行:

4.添加详情页

添加一个UserInfoDetailPage,在UserInfoDetailPage_Model文件中添加属性:

public UserInfoItem CurrentUserInfoItem
        {

                    get { return _CurrentUserInfoItemLocator(this).Value; }

                    set { _CurrentUserInfoItemLocator(this).SetValueAndTryNotify(value); }
        }

                    #region Property UserInfoItem CurrentUserInfoItem Setup

                    protected Property<UserInfoItem> _CurrentUserInfoItem = new Property<UserInfoItem> { LocatorFunc = _CurrentUserInfoItemLocator };

                    static Func<BindableBase, ValueContainer<UserInfoItem>> _CurrentUserInfoItemLocator = RegisterContainerLocator<UserInfoItem>("CurrentUserInfoItem", model => model.Initialize("CurrentUserInfoItem", ref model._CurrentUserInfoItem, ref _CurrentUserInfoItemLocator, _CurrentUserInfoItemDefaultValueFactory));

                    static Func<UserInfoItem> _CurrentUserInfoItemDefaultValueFactory = () => { return
                                    default(UserInfoItem); };

                    #endregion

 

然后修改UserInfoDetailPage_Model的构造函数,注意,每个VM必须有一个无参的构造函数,如果要传值的话,要手动把无参的构造函数也加上。同时别忘了把CurrentUserInfoItem绑定到页面上。

public UserInfoDetailPage_Model()
        { }

public UserInfoDetailPage_Model(UserInfoItem item)
        {
            CurrentUserInfoItem = item;
        }

 

 

5. 使用InvokeCommandAction导航到详情页面

接下来就要实现我们的目的,点击User列表的时候,导航到详情页。首先看第一种方式,使用InvokeCommandAction:

在Blend中编辑MainPage,拖一个InvokeCommandAction到ListView上:

Behavior事件选择SelectionChanged:

在MainPage_Model中添加一个 Command,使用propcmd代码段来生成:

public CommandModel<ReactiveCommand, String> CommandNavToDetailByInvokeCommand
        {

                    get { return _CommandNavToDetailByInvokeCommandLocator(this).Value; }

                    set { _CommandNavToDetailByInvokeCommandLocator(this).SetValueAndTryNotify(value); }
        }

                    #region Property CommandModel<ReactiveCommand, String> CommandNavToDetailByInvokeCommand Setup

                    protected Property<CommandModel<ReactiveCommand, String>> _CommandNavToDetailByInvokeCommand = new Property<CommandModel<ReactiveCommand, String>> { LocatorFunc = _CommandNavToDetailByInvokeCommandLocator };

                    static Func<BindableBase, ValueContainer<CommandModel<ReactiveCommand, String>>> _CommandNavToDetailByInvokeCommandLocator = RegisterContainerLocator<CommandModel<ReactiveCommand, String>>("CommandNavToDetailByInvokeCommand", model => model.Initialize("CommandNavToDetailByInvokeCommand", ref model._CommandNavToDetailByInvokeCommand, ref _CommandNavToDetailByInvokeCommandLocator, _CommandNavToDetailByInvokeCommandDefaultValueFactory));

                    static Func<BindableBase, CommandModel<ReactiveCommand, String>> _CommandNavToDetailByInvokeCommandDefaultValueFactory =
            model =>
            {

                    var resource = "NavToDetailByInvokeCommand";           // Command resource  

                    var commandId = "NavToDetailByInvokeCommand";

                    var vm = CastToCurrentType(model);

                    var cmd = new ReactiveCommand(canExecute: true) { ViewModel = model }; //New Command Core

                cmd.DoExecuteUIBusyTask(
                        vm,

                    async e =>
                        {

                    //Todo: Add NavToDetailByInvokeCommand logic here, or

                    await MVVMSidekick.Utilities.TaskExHelper.Yield();

                    var item = e.EventArgs.Parameter as UserInfoItem;
                            if(item != null)
                            {
                                await vm.StageManager.DefaultStage.Show(new UserInfoDetailPage_Model(item));
                            }
                        })
                    .DoNotifyDefaultEventRouter(vm, commandId)
                    .Subscribe()
                    .DisposeWith(vm);

                    var cmdmdl = cmd.CreateCommandModel(resource);

                cmdmdl.ListenToIsUIBusy(
                    model: vm,
                    canExecuteWhenBusy: false);

                    return cmdmdl;
            };

                    #endregion

 

 

注意看红色的部分,首先获取Command的参数,然后使用StageManager去导航到详情页,并在详情页的构造函数里传递一个参数。

然后编译一下,在Blend里把Command绑定到Action上:

Command的参数绑定到ListView的SelectedItem上:

这样在Command里面就可以获取到点击的是哪个item了。

运行一下看看,可以根据点击项来导航了。

6.使用SendToEventRouterAction来导航到详情页面

看了上面的大家觉得也太简单了,下面给大家介绍一个好东西,MVVM-Sidekick里的SendToEventRouterAction。

这个Action顾名思义就是将Event发送到一个 Router里来处理,Router可以是当前VM的,也可以是全局的,我一般喜欢使用全局的方式,比如在好多页面可能都有文章列表,这些列表点击后的动作都是相同的,都是导航到文章详情页面,使用全局的Router,只需要处理一次就可以了。

在MainPage里再添加一个ListView,也绑定到相同的数据源UserInfoItemList上,项模板复制一下之前的,命名为SendToRouterUserInfoItemDataTemplate,两个ListView用的是不同的项模板,不要弄混了。

这次我们不用InvokeCommandAction了,在项模板里做文章。编辑SendToRouterUserInfoItemDataTemplate项模板,拖一个SendToEventRouterAction到根Grid上 :

Behavior的事件选择Tapped:

EventRoutingName设置为NavToDetailByEventRouter,选中IsEventFiringToAllBaseClassesChannels,EventData自定义表达式输入{Binding}:

在XAML里看起来是这样的:

<Interactivity:Interaction.Behaviors>
                    <Core:EventTriggerBehavior EventName="Tapped">
                        <Behaviors:SendToEventRouterAction EventRoutingName="NavToDetailByEventRouter" IsEventFiringToAllBaseClassesChannels="True" EventData="{Binding}"/>
                    </Core:EventTriggerBehavior>
                </Interactivity:Interaction.Behaviors>

 

然后来处理Router,在MainPage_Model里添加一个订阅命令的方法:

private
                        void SubscribeCommand()
        {

                        //一般列表项点击事件
            MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()
                .Where(x => x.EventName == "NavToDetailByEventRouter")
                     .Subscribe(

                    async e =>
                         {

                    var item = e.EventData as UserInfoItem;
                             if (item != null)
                             {
                                 await StageManager.DefaultStage.Show(new UserInfoDetailPage_Model(item));
                             }
                         }
                     ).DisposeWith(this);
        }

 

注意看红色的部分,通过EventData来获取绑定的Model,进行下一步操作。

别忘了在Load事件中调用此方法。调用的时候注意最好加个flag避免重复订阅。

现在运行一下看看,下面的ListView也可以导航到详情页了。

需要说明的是,当前VM也有个EventRouter,如果要绑定到当前VM的EventRouter,XAML里要写明绑定到当前的EventRouter:

<Behaviors:SendToEventRouterAction EventRoutingName="NavToArticle" EventData="{Binding}" EventRouter="{Binding ElementName=LayoutRoot, Path=DataContext.EventRouter}" />

订阅的时候这样写:

this.LocalEventRouter.GetEventChannel<Object>()。。。。后面的一样

和全局的相比就是如果离开当前的VM此订阅就无效了。

7.处理不同区域的点击事件

下面更进一步,实现点击项的不同区域分别进行处理。

在项模板里添加两个按钮,想实现这样的功能,点击一个按钮可以增加Age,点击另一个减少Age。

项模板改成这样:

分别拖两个Action到按钮上,XAML变成这样:

<StackPanel Grid.Row="2" Orientation="Horizontal">
                    <AppBarButton HorizontalAlignment="Stretch" Icon="Like" Label="Good" VerticalAlignment="Stretch">
                        <Interactivity:Interaction.Behaviors>
                            <Core:EventTriggerBehavior EventName="Click">
                                <Behaviors:SendToEventRouterAction EventData="{Binding}" IsEventFiringToAllBaseClassesChannels="True" EventRoutingName="AddAge"/>
                            </Core:EventTriggerBehavior>
                        </Interactivity:Interaction.Behaviors>
                    </AppBarButton>
                    <AppBarButton HorizontalAlignment="Stretch" Icon="Dislike" Label="boo" VerticalAlignment="Stretch">
                        <Interactivity:Interaction.Behaviors>
                            <Core:EventTriggerBehavior EventName="Click">
                                <Behaviors:SendToEventRouterAction EventData="{Binding}" EventRoutingName="RemoveAge" IsEventFiringToAllBaseClassesChannels="True"/>
                            </Core:EventTriggerBehavior>
                        </Interactivity:Interaction.Behaviors>
                    </AppBarButton>
                </StackPanel>

 

订阅事件:

MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()
                .Where(x => x.EventName == "AddAge")
                     .Subscribe(
                         e =>
                         {

                    var item = e.EventData as UserInfoItem;

                    if (item != null)
                             {
                                 item.Age++;
                             }
                         }
                     ).DisposeWith(this);
            MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()
                .Where(x => x.EventName == "RemoveAge")
                     .Subscribe(
                         e =>
                         {

                    var item = e.EventData as UserInfoItem;

                    if (item != null)
                             {
                                 item.Age--;
                             }
                         }
                     ).DisposeWith(this);

 

跑一下试试,竟然点击按钮的时候也触发了导航事件,那需要把导航的Action从根Grid转移一下,和按钮的Action分开即可。

打完收工!

你们过节,我写博客……

 最后附上demo下载:链接:http://pan.baidu.com/s/1dD51Fax 密码:ymdn

时间: 2024-11-01 04:04:42

MVVM-Sidekick 之SendToEventRouterAction使用的相关文章

Windows 8开发入门(二十一) Windows 8 下进行MVVM开发

在本文中将演示如何在Windows 8进行MVVM开发,首先我们准备两个辅助类如下: ViewModeBase类 : public class ViewModeBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// 属性变化时触发事件 /// </summary> /// <param name="prope

使用MVVM编写可测试的表示层

在使用 Windows 窗体时代的传统应用程序的情况下,标准测试做法是布局一个视图,在该视图的代 码隐藏文件中编写代码,然后运行该应用程序以进行测试.幸运的是,在那以后,相关做法有了一些变 化. Windows Presentation Foundation (WPF) 的出现将数据绑定概念提升到了一个全新的水平.它使得 一种称为"模型-视图-视图模型"(MVVM) 的新设计模式得到发展.通过 MVVM,您可以将表 示逻辑与实际表示分离开.基本上,这意味着您可以在极大程度上避免在视图的

利用 Windows 8 功能和 MVVM

Windows 8 引入了许多新功能,开发人员可利用这些功能创建引人注目的应用程 序和形式丰富的 UX.遗憾的是,这些功能并非总是易于进行单元测试.共享和辅助磁贴等功 能可提高应用程序的互动性和趣味,但也会变得不太易于测试. 在本文中,我将介绍 让应用程序可使用共享.设置.辅助磁贴.应用程序设置和应用程序存储等功能的多种不同 方式.通过使用模型-视图-视图模型 (MVVM) 模式.依赖注入和某些抽象,我将向您演示如 何利用这些功能,同时将表示层保持易于进行单元测试. 关于示例应用程序 为了说明将

Windows 8 Store Apps学习(55) 绑定: MVVM 模式

介绍 重新想象 Windows 8 Store Apps 之 绑定 通过 MVVM 模式实现数据的添 加.删除.修改和查询 示例 1.Model 层 Binding/MVVM/Model/ProductDatabase.cs /* * Model 层的数据持久化操作(本地或远程) * * 本例只是一个演示 */ using System; using System.Collections.Generic; using System.Linq; namespace XamlDemo.Binding

基于WPF系统框架设计(6) 整合MVVM框架(Prism)

我们基础的框架已经搭建起来了,现在整合MVVM框架Prism,在ViewModel做一些逻辑处理,真正把界面设 计分离出来. 这样方便我们系统开发分工合作,同时提高系统可维护性和灵活性. 具体的 Prism安装和Microsoft.Practices.Prism.dll获取,在这个网址:http://compositewpf.codeplex.com/ 跟Winform一样原始的模式: (1)现在看一下之前的设计的View: MainWindow.XAML源码: (2)MainWindow.xa

迷你MVVM框架avalonjs 实现上的难点有哪些

经过两个星期的性能优化,avalon终于实现在一个页面绑定达到上万个的时候不卡顿的目标(angular的限制是2000).现在稍作休息,总结一下avalon遇到的一些难题. 首先是如何监控的问题.所有MVVM要将VM中的属性与视图中的绑定属性关联起来大抵有如下三种方式:angular是对函数体取toString进行预编译,将里面的赋值语句,取值语句替换为set,get方法,然后通过特定方法进行脏检测触发,或手动触发:ko是对VM的属性用监控函数外包一层,全事件驱动触发:avalon是通过Obje

简述迷你MVVM框架avalon在兼容旧式IE所做的努力

很多时候,写代码就像砌砖头,只要我们不关心盖楼的原因.建筑的原理.土木工程基础和工程经验,就算我们砌了100栋高楼,我们也就只是一个砌砖工人,永远也成为不了一个工程师,更别说建筑师了.而那些包工头也只会把我们当成劳动力罢了.--左耳朵耗子 avalon在兼容旧式IE上做了大量工作,从而让它更接地气,完美地运行于国内的各种奇葩浏览器中. 首先是Object.defineProperties的模拟,正因为有这东西,才能让avalon是纯事件驱动地同步视图,而不用脏检测,从而获得更高的性能. //IE

Silverlight中使用MVVM(3)—进阶

这篇主要引申出Command结合MVVM模式在应用程序中的使用 我们要做出的效果是这样的 就是提供了一个简单的查询功能将结果绑定到DataGrid中,在前面的基础上 ,这个部分相对比较容易实现了 我们在PageViewModel中添加两个属性 private string _searchText; //查询关键字 public string SearchText { get { return _searchText; } set { _searchText = value; if (Proper

Silverlight中使用MVVM(2)—提高

在第一篇文章中的示例中,我们已经简单的了解了应用MVVM模式的流程,我 的本意是你已经了解了一点MVVM的概念,然后又没有一个较好的例子学习,可以 跟着我一起学习MVVM模式,所以这个部分,都是没有理论知识的,当然整个例子 学完后,我们会回过头探讨一下,将其总结出来. 现在我们主要在前面的示例上进行扩展,前面的示例中我们主要是将一个源 对象绑定到DataGrid中的,接下来我们继续使用MVVM模式,将 DataGrid选择行 的变化体现界面中,其实通过这个需求变化,你会发现UI与逻辑分离带来的优

Silverlight中使用MVVM(1)--基础

这是我第一篇关于设计模式方面的文章,以前除了对单例模式等几个常用的 模式有所研究之外,对设计模式不是太重视,总觉得要到一定的程度才需要接触 ,最近的项目中使用了MVVM模式,所以这段时间查阅了大量这方面模式的文章, 理论上的东西大家都说的比较好,这里我也不大谈MVVM模式的优势了,只是美中 不足的是大部分给出的示例中,对于一个没有用过MVVM模式的人而言,这些例子 总是给人一种摸不着头绪的感觉,所以我想将我学习MVVM的过程一步步写下来, 希望对于和我一样,刚刚接触MVVM这个模式的人有一点点帮