在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 = });
UserInfoItemList.Add(new UserInfoItem { UserName = "Tom", Age = });
UserInfoItemList.Add(new UserInfoItem { UserName = "Lily", Age = });
UserInfoItemList.Add(new UserInfoItem { UserName = "Jim", Age = });
UserInfoItemList.Add(new UserInfoItem { UserName = "Bob", Age = });
} 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 =  });

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

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

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

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

}

}

然后编译一下,用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

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

  1. Vue.js 和 MVVM 小细节

    MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自 ...

  2. 领域驱动和MVVM应用于UWP开发的一些思考

    领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...

  3. MVVM框架从WPF移植到UWP遇到的问题和解决方法

    MVVM框架从WPF移植到UWP遇到的问题和解决方法 0x00 起因 这几天开始学习UWP了,之前有WPF经验,所以总体感觉还可以,看了一些基础概念和主题,写了几个测试程序,突然想起来了前一段时间在W ...

  4. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  5. MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信

    MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...

  6. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  7. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  8. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  9. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  10. 从Script到Code Blocks、Code Behind到MVC、MVP、MVVM

    刚过去的周五(3-14)例行地主持了技术会议,主题正好是<UI层的设计模式——从Script.Code Behind到MVC.MVP.MVVM>,是前一天晚上才定的,中午花了半小时准备了下 ...

随机推荐

  1. EF6(CodeFirst)+MySql开发遇到的坑

    最近一不小心偷个懒就已经过了好几个月了,真是惭愧惭愧,出来混终究是要还的,我还是把”脱坑指南“写完吧,-_-~~.点我打开上篇博客 0x001.架构名”dbo”の殇 坑之首也,当提架构名,在mssql ...

  2. 团队项目——站立会议DAY14

    第十四次站立会议记录: 参会人员:张靖颜,钟灵毓秀,何玥,赵莹,王梓萱 项目进展: 1.张靖颜:修改页面,查漏补缺.进行需求分析,监督每个组员,把大家的问题都一一梳理. 2.钟灵毓秀:继续修改模块代码 ...

  3. 谈谈javascript语法里一些难点问题(二)

    3)    作用域链相关的问题 作用域链是javascript语言里非常红的概念,很多学习和使用javascript语言的程序员都知道作用域链是理解javascript里很重要的一些概念的关键,这些概 ...

  4. Unity3D逻辑热更新,第二代舒爽解决方案,L#使用简介

    热更新 天下武功,无坚不破,唯快不破 热更新就是为了更快的把内容推到用户手中. 之前,我设计了C#Light,经过半年多的持续修补,勉强可用,磕磕绊绊.感谢那些,试过,骂过,用过的朋友,在你们的陪伴下 ...

  5. CocoSocket开源下载与编写经验分享

    CocoSocket分享 cocos2dx 3.1都出了,但依然没有发现与它原生的SOCKET支持,于是,这几天在家,手工撸了一个. 目前版本对IOS,ANDROID,WINDOWS支持良好.且为异步 ...

  6. Java-继承,多态0922-05

    28.按要求编写一个Java应用程序: (1)定义一个类,描述一个矩形,包含有长.宽两种属性,和计算面积方法. (2)编写一个类,继承自矩形类,同时该类描述长方体,具有长.宽.高属性, 和计算体积的方 ...

  7. Bootstrap~表单Form

    回到目录 在进行自己的后台改版时,大体布局都使用了bootstrap,剩下的表单部分没理由不去使用它,对于表单的美化和布局,bootstrap做的也是很不错的,有大气的边框,多功能的按钮及宏观的表单布 ...

  8. java基础 数组15

    15.找出如下数组中最大的元素和最小的元素, a[][]={{3,2,6},{6,8,2,10},{5},{12,3,23}}

  9. webpack配置详解

    webpack配置详解 先点个赞吧,再挨个点下面的连接,觉得不值这个赞的回来骂我啊. Webpack傻瓜式指南(一) Webpack傻瓜指南(二)开发和部署技巧 Webpack傻瓜式指南 原生的官网详 ...

  10. struts之动态方法调用使用通配符

    一.DMI动态方法调用的其中一种改变form表单中action属性的方式已经讲过了.还有两种,一种是改变struts.xml配置文件中action标签中的method属性,来指定执行不同的方法处理不同 ...