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 中, 默认的绑定是单向绑定, 就是说, 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 上了, 并且会持续更新.
来源: https://www.cnblogs.com/kiba/p/9610364.html