欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-30 10:02:11
...

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的使用会改进这些,待我研究研究。。。

相关标签: wp windows phone