MVVM的特點之一是實現數據同步,即,前臺頁面修改了數據,後臺的數據會同步更新。 上一篇我們已經一起編寫了框架的基礎結構,並且實現了ViewModel反向控制Xaml窗體。 那麼現在就要開始實現數據同步了。 DataContext—數據上下文 在實現數據同步前,我們要瞭解一個知識點——DataCon ...
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上了,並且會持續更新。
相關文章:
To be continued
Github地址:https://github.com/kiba518/KibaFramework
----------------------------------------------------------------------------------------------------
註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接!
若您覺得這篇文章還不錯,請點擊下右下角的【推薦】,非常感謝!
如果您覺得這篇文章對您有所幫助,那就不妨支付寶小小打賞一下吧。