zoukankan      html  css  js  c++  java
  • 【我们一起写框架】MVVM的WPF框架(二)—绑定

    MVVM的特点之一是实现数据同步,即,前台页面修改了数据,后台的数据会同步更新。

    上一篇我们已经一起编写了框架的基础结构,并且实现了ViewModel反向控制Xaml窗体。

    那么现在就要开始实现数据同步了。

    DataContext—数据上下文

    在实现数据同步前,我们要了解一个知识点——DataContext。

    WPF中每个UI都有一个Content和一个DataContext,那么Content和DataContext是什么呢?

    Content:Content是指页面内容,即我们编写的代码,或者认为它是展示的UI。

    打个比方,Content就是HTML页面中的标签,如【<html></html】;那么,在WPF中Content是指的就是Xaml页面的标签了。

    DataContext:DataContext是指页面中的数据内容,这部分内容只有运行了才存在,用过ASP.NET MVC的同学可以把它理解为MVC中的Model。(每个页面都有一个唯一的指定Model)

    既然在WPF里DataContext就是MVC中的Model。那么,自然的,DataContext就要存储页面的ViewModel了,所以,我们为它赋值它自身对应的ViewModel。

    现在,找到我们的BaseViewModel的构造函数,加入这行代码[UIElement.DataContext = this;],代码如下:

    public BaseViewModel()
    {
        WindowMain = Application.Current.MainWindow; 
        SetUIElement();
        UIElement.DataContext = this;
    }
    

    这样用ViewModel创建的页面的DataContext就被自动赋值了。

    页面与ViewModel的基础关系就建立完成了。

    Binding—绑定

    在我们编写的框架中,绑定分两种,一种是属性绑定,一种是命令绑定。

    属性绑定:属性绑定很好理解,就是将Xaml页面的控件属性和ViewModel中的自定义属性捆绑到一起,让他们的数据值同步。

    命令绑定:命令绑定是Xaml页面触发命令,然后由ViewModel来处理命令。

    这里的命令(Command)有点不太好理解,不过大家都做过面向事件的开发,我们可以把命令想象成事件,就是Xaml页面触发事件,ViewModel来执行事件内容。

    接下来,我们一起做一些简单的绑定。

    Property—属性绑定

    首先,在程序框架中找到VM_WindowMain页面,然后在里面创建属性HeaderName,代码如下:

    public string _HeaderName = "HeaderName_KibaFramework";
    public string HeaderName { get { return _HeaderName; } set { _HeaderName = value; OnPropertyChanged(); } }
    

    然后,我们再找到VM对应的Xam页面—WindowMain.xaml,修改Header代码如下:

    <StackPanel  DockPanel.Dock="Top" Background="Gainsboro">
        <TextBlock TextAlignment="Left" Text="{Binding HeaderName}" Margin="20,20,0,0" Height="70" FontSize="36"></TextBlock>
    </StackPanel>
    

    界面效果如下:

    通过图片,我们可以看到,属性已经绑定成功了,并且成功输出了我们的HeaderName。

    然后,我们重点看一下这段代码{Binding HeaderName}。

    这句话的意思就是让TextBlock的Text属性绑定HeaderName属性,其中Binding就是绑定的意思。【注意,这里只能是属性绑定属性】

    HeaderName是我们在VM中刚刚定义的属性,那么Text是怎么绑定到了HeaderName上的呢?

    很简单,因为上面我们已经把ViewModel赋值到了DataContext中了,所以在Xaml中,我们就可以使用{Binding 属性名}这样的语句,来绑定VM中所有的属性。

    在Xaml中,TextBlock默认的绑定是单向绑定,就是说,VM中的属性值改变会同步Xaml页面的属性值,让其改变;但,当Xaml页面的属性值改变了,VM中的属性值却不会改变。

    那么如何让他们同步呢?

    很简单,只需要在绑定的时候多加一个属性Mode=TwoWay即可,代码如下:

    {Binding HeaderName,Mode=TwoWay}
    

    Command—命令绑定

    在MVVM中,事件被极大的程度的弱化了,因为Command在ViewModel中替代了事件来处理业务逻辑,所以,事件在框架中就只负责处理UI变化这么一件事了。 

    BaseCommand

    在WPF中,系统为我们提供一些Command,但为了能处理更多细节,自定义Command的效果会更好,所以,我们需要编写属于我们框架自己的自定义BaseCommand。

    代码如下:

    public class BaseCommand : ICommand
    {
        public Action<object> ExecuteAction;
        public BaseCommand(Action<object> action)
        {
            ExecuteAction = action;
        }
        public bool CanExecute(object parameter)
        {
            return true;
        } 
        public event EventHandler CanExecuteChanged;
        public void Execute(object parameter)
        { 
            ExecuteAction(parameter); 
        } 
    }
    

    如上代码所示,我们自定义了BaseCommand,并且继承了ICommand接口,实现了接口方法。

    Command的应用

    下面我们开始Command的基础应用,使用Command实现页面切换;页面切换我们采用最简单的模式Window—Frame—Page的控制模式。

    首先我们找到VM_WindowMain,创建切换Page的Command和存储页面实例的属性FrameSource。

    代码如下:

    public Page _FrameSource;
    public Page FrameSource { get { return _FrameSource; } set { _FrameSource = value; OnPropertyChanged(); } } 
    public BaseCommand ChangeFrameSourceCommand
    {
        get
        {
            return new BaseCommand(ChangeFrameSourceCommand_Executed);
        }
    }
    public void ChangeFrameSourceCommand_Executed(object obj)
    {
        string pageName = obj.ToString();
       switch(pageName)
       {
           case "PageMain":
               FrameSource = new VM_PageMain().UIElement as Page;
               break;
           case "PageUser":
               FrameSource = new VM_PageUser().UIElement as Page;
               break;
       }
    }
    

    接下来在页面实现按钮事件绑定和Frame显示页面绑定。

    代码如下:

    <TreeViewItem>
        <TreeViewItem.Template>
            <ControlTemplate>
                <Button HorizontalAlignment="Left" Content="PageMain" Command="{Binding ChangeFrameSourceCommand}" CommandParameter="PageMain"  Style="{StaticResource NullButton}"></Button>
            </ControlTemplate>
        </TreeViewItem.Template>
    </TreeViewItem>
    <TreeViewItem>
        <TreeViewItem.Template>
            <ControlTemplate>
                <Button HorizontalAlignment="Left" Content="PageUser" Command="{Binding ChangeFrameSourceCommand}" CommandParameter="PageUser"  Style="{StaticResource NullButton}"></Button>
            </ControlTemplate>
        </TreeViewItem.Template>
    </TreeViewItem>
    
    /* 省略了框架其他元素代码 */
    
    <Frame x:Name="frameMain" Content="{Binding FrameSource,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"  NavigationUIVisibility="Hidden" ScrollViewer.CanContentScroll="True"  ></Frame>
    

    从代码中我们可以看到,VM中的属性FrameSource绑定到了页面Frame的Content属性上。

    由于TreeViewItem没有Command的依赖属性,所以我们修改了他的模板,然后用模板内的Button的Command属性绑定了VM中的ChangeFrameSourceCommand属性。

    因为ChangeFrameSourceCommand是BaseCommand类型,所以,当按钮被按下时,就会触发ChangeFrameSourceCommand定义的执行命令——ChangeFrameSourceCommand_Executed。

    这样我们就实现了框架内的页面切换了。

    ----------------------------------------------------------------------------------------------------

    到此,我们框架的基础功能就已经实现了。

    但如果框架只写到这里,那ViewModel对页面的掌控力度就显的太弱了。

    而且项目框架不能仅仅考虑结构分离和业务独立,我们还要降低使用难度和提高使用者的开发效率。

    所以为了更好的掌控UI,降低开发者的门槛,我们还需要编写数据控件,让开发者在不能熟练掌握Xaml样式的情况下,依然可以顺利完成开发。

    那么,本篇文章就先讲到这了,下一篇文章我们将一起为框架编写数据控件,敬请期待。

    框架代码已经传到Github上了,并且会持续更新。

    相关文章:

    【我们一起写框架】MVVM的WPF框架(一)—序篇

    To be continued

    Github地址:https://github.com/kiba518/KibaFramework

    ----------------------------------------------------------------------------------------------------

    注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
    若您觉得这篇文章还不错,请点击下方的推荐】,非常感谢!

     

  • 相关阅读:
    Codeforces 651 A. Joysticks
    Codeforces 538 C. Tourist's Notes
    Codeforces 538 B. Quasi Binary
    Codeforces 538 A. Cutting Banner-substr()函数字符串拼接
    Codeforces 626 C. Block Towers-二分 (8VC Venture Cup 2016-Elimination Round)
    Codeforces 626 B. Cards (8VC Venture Cup 2016-Elimination Round)
    hdu 4825 Xor Sum trie树
    Codeforces Round #358 (Div. 2) C. Alyona and the Tree dfs
    Codeforces Round #357 (Div. 2) 优先队列+模拟
    2016 湘潭邀请赛
  • 原文地址:https://www.cnblogs.com/kiba/p/9610364.html
Copyright © 2011-2022 走看看