【WP开发】再谈View与ViewModel之间的解耦实现方式

MVVM模式的View与ViewModel的三大通讯方式:Binding Data(实现数据的传递)、Command(实现操作的调用)和Attached Behavior(实现控件加载过程中的操作)。

这里再谈一下textbox双向绑定的问题以及绑定行为的一些东西。

 

一、textbox双向绑定取值异常问题:

就比如说这个textbox:

 

<TextBox Text="{Binding Mobile,  Mode=TwoWay}"
                         BorderBrush="{StaticResource PhoneAccentBrush}" 
                         Background="{StaticResource PhoneBackgroundBrush}"/>

 

 很明显是个双向绑定的数据模式,我们在看后端vm代码

 

public void SubmitAction()
        {
            if (!this.ValidateData())
                return;
            App.LwApi.GetInternalService().BindMobile(this.Mobile, this.Code, (o, ev) =>
            {
                if (ev.Error != null)
                {
                    MessageHandleHelper.HandleError(ev.Error);
                    return;
                }
                MessageBox.Show("手机号绑定成功!");
                NavigationController.GoBack();
            });
        }

 

上述代码在提交命令触发时调用,这里有一个很奇怪的现象:当我们直接触发点击事件时,是无法同步的享有Mobile属性的值的,只有在用户再对textbox失焦后才能正常的。

 

这里的原因在于wp系统只在当前textbox触发LostFocus事件后才会对绑定的数据赋值。现在我们就需要将事件触发提前到每一次的TextChanged事件里进行触发。

 

将代码改成:

 

<TextBox Text="{Binding Mobile,  Mode=TwoWay, UpdateSourceTrigger=Explicit}" TextChanged="OnPhoneTextBoxTextChanged"
                         BorderBrush="{StaticResource PhoneAccentBrush}" 
                         Background="{StaticResource PhoneBackgroundBrush}"/>

 

 注意上面的 UpdateSourceTrigger 属性:TwoWay是由绑定目标到绑定源方向,若实现绑定目标的值更改影响绑定源的值方式,只需要设置相应控件绑定时的UpdateSourceTrigger的值,其值有三种:1、PropertyChanged:当绑定目标属性更改时,立即更新绑定源。2、 LostFocus:当绑定目标元素失去焦点时,更新绑定源。3、 Explicit:仅在调用 UpdateSource 方法时更新绑定源。 多数依赖项属性的UpdateSourceTrigger 值的默认值为 PropertyChanged,而 TextBox 属性的默认值为 LostFocus。

 

我们现在把它设置为Explicit的意思就是要在cs文件里手动调用UpdateSource 方法才会更新绑定源数据。cs代码如下:

 

private void OnPhoneTextBoxTextChanged(object sender, TextChangedEventArgs e)
 {
            TextBox ptb = sender as TextBox;
            BindingExpression be = ptb.GetBindingExpression(TextBox.TextProperty);
            be.UpdateSource();
 }

 

 上面的代码就是手动调用更新绑定源的操作。

现在我们可以看到viewmodel层已经可以正确的操作属性了。

 

下面我们再看一下附加行为方式的解耦:

 

二、LongListSelector的回到顶部功能的Attached Behavior方式实现

回到顶部的功能其实在codebehind代码里写起来异常的简单:只需一句ScrollTo就能轻松搞定,但我们的业务代码全部写在Viewmodel中,比如我们执行了refreshData的操作,要求list回到顶部,如果ScrollTo写在cb代码里,我们就必须要用notification方式去实现,然而这种方式既会搞得代码很复杂而且后期维护的成本也很大。(不要问我那为什么要把业务代码写vm里。。。vm里写业务代码能使得前端UI修改方便,而且移植起来也方便,甚至我们的win8 app就直接能套用wp的业务vm代码)

 

为了使这里我们要用到第三种解耦模式:Attached Behavior方式也就是行为依赖的方式。我们会写一个ToTop的Behavior类。这里先看它的调用方式:

 

 

<toolkit:LongListSelector x:Name="StoryListBox" Background="Transparent"  ShowListHeader="False" ShowListFooter="False" IsFlatList="True" BufferSize="5.0"
                              lwcontrols:ToTopBehavior.GoTop="{Binding GoTopFlag, Mode=TwoWay}"  ItemsSource="{Binding StoryList}" ItemTemplate="{StaticResource StoryTemplate}"  />

 看到这一句: lwcontrols:ToTopBehavior.GoTop="{Binding GoTopFlag, Mode=TwoWay}"这个就是Behavior的xml绑定方式;

再来看vm:

 

 

private bool goTopFlag;
public bool GoTopFlag
 {
            get { return goTopFlag; }
            set
            {
                goTopFlag = value;
                this.RaisePropertyChanged("GoTopFlag");
            }
}   
    
 public override void RefreshData(Action<object> callback = null)
{
            base.RefreshData((o) =>
            {
                GoTopFlag = true;
                callback.Invoke(o);
            });
 }
 

是的,我们需要一个双向绑定的属性,在vm中直接调用 GoTopFlag = true;就能使list直接回到顶部;看是如何做到的(ToTopBehavior.cs):

 

namespace Laiwang.Controls
{
    static public class ToTopBehavior
    {
        public static readonly DependencyProperty GoTopProperty = DependencyProperty.RegisterAttached(
            "GoTop",
            typeof(bool),
            typeof(ToTopBehavior),
            new PropertyMetadata(new PropertyChangedCallback(OnGoTopChanged)));


        public static bool GetGoTop(DependencyObject obj)
        {
            return (bool)obj.GetValue(GoTopProperty);
        }

        public static void SetGoTop(DependencyObject obj, bool value)
        {
            obj.SetValue(GoTopProperty, value);
        }

        private static void OnGoTopChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            LongListSelector control = obj as LongListSelector;
            if (control != null)
            {
                if ((bool)args.NewValue)
                {
                    ScrollViewer scrollViewer = CommonHelper.FindChildOfType<ScrollViewer>(control);
                    if (scrollViewer.VerticalOffset > 1)
                    {
                        object o = null;
                        foreach (var item in control.ItemsSource)
                        {
                            if (item == null)
                                return;
                            o = item;
                            break;
                        }
                        control.ScrollTo(o);
                    }
                }
            }
            SetGoTop(control, false);

        }

    }
}

  像给依赖对象附加依赖属性一样,我们这里只是对一个已有的依赖对象附加一个新的依赖属性:GoTop,我们所有的操作都是在OnGoTopChanged事件做的,事实上我们这个方法是对双向模式中的PropertyChangedCallback,还记得一般的双向的UpdateSourceTrigger都是PropertyChanged吗,对于依赖属性也是这样的。我们只是简单的判断了 if ((bool)args.NewValue)并作出回滚的实现,注意后面我们又调用了SetGoTop(control, false),这样做很明显就是让它下一次的赋值为true时能执行刚才的代理事件。

 

vm和view的解耦虽说感觉是简单的事情复杂化了,但不管怎么说,这样做的好处也显而易见的,维护的成本降低了,wp和win8的移植也变的如此的简单美妙,只需对view层更改及简单的vm更改就能轻松搞定,ms这一套wpf的东西还是非常值得借鉴的。嗯,wp8我还是非常期待啊。。。

 

另外,近些天一直在做的ios开发对于事件的绑定及回调都是用delegate去做,感觉非常的麻烦。试图改变这种情况,貌似ios中KVO以及block的使用会改进这些,待我研究研究。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值