【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的使用会改进这些,待我研究研究。。。
上一篇: Category使用实例,格式化时间
下一篇: 微软支招:如何让WP应用脱颖而出