zoukankan      html  css  js  c++  java
  • MVVM-Sidekick 之SendToEventRouterAction使用

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

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

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

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

    首先新建一个WP8.1MVVM-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,选中IsEventFiringToAllBaseClassesChannelsEventData自定义表达式输入{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

  • 相关阅读:
    django-带参数路由
    django-获取购物车商品数量-redis
    django-配置静态页面-celery/redis/nginx
    django-自定义文件上传存储类
    django-安装nginx及fastdfs-nginx-module
    linux压缩和解压缩命令
    django-文件上传和下载--fastDFS安装和配置
    django-缓存django-redis
    django-改写manage类-objects
    django-登录后得个人信息
  • 原文地址:https://www.cnblogs.com/yanxiaodi/p/4593073.html
Copyright © 2011-2022 走看看