MVVM的特色之一是实现数据同步,即,前台页面修改了数据,后台的数据会同步更新。html
上一篇咱们已经一块儿编写了框架的基础结构,而且实现了ViewModel反向控制Xaml窗体。git
那么如今就要开始实现数据同步了。github
DataContext—数据上下文框架
在实现数据同步前,咱们要了解一个知识点——DataContext。函数
WPF中每一个UI都有一个Content和一个DataContext,那么Content和DataContext是什么呢?this
Content:Content是指页面内容,即咱们编写的代码,或者认为它是展现的UI。spa
打个比方,Content就是HTML页面中的标签,如【<html></html】;那么,在WPF中Content是指的就是Xaml页面的标签了。htm
DataContext:DataContext是指页面中的数据内容,这部份内容只有运行了才存在,用过ASP.NET MVC的同窗能够把它理解为MVC中的Model。(每一个页面都有一个惟一的指定Model)blog
既然在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上了,而且会持续更新。
相关文章:
To be continued
Github地址:https://github.com/kiba518/KibaFramework
----------------------------------------------------------------------------------------------------
注:此文章为原创,任何形式的转载都请联系做者得到受权并注明出处!
若您以为这篇文章还不错,请点击下方的【推荐】,很是感谢!