1. 程式人生 > >WPF/MVVM 快速入門教程

WPF/MVVM 快速入門教程

簡介

先假設大家對c#已經有一定的瞭解了,並且很容易接受一些關於WPF的知識。因為下面的知識是通過WPF為例的。

我前段時間開始研究了一下WPF,但是找不到什麼有幫助的關於MVVM的教程。因此我希望這篇文章能讓你眼前一亮。

當我們剛開始學一門新技術時,我們得益於前人的積累。但是從我個人的觀點來看,我看過的那些教程什麼的都不能滿足我,原因如下:

例子都是用XAML寫的例子根本不提那些能大大簡化你開發過程的特性和關鍵點那些例子的存在只是為了向你炫耀 WPF/XAML對一些無聊特性的相容性在例子中有一些變數的名字很容易和語言本身的一些關鍵詞和類混淆,這讓我們這些新手們很蛋疼

而我現在寫的這玩意卻沒有這些缺點,它是根據我在谷歌搜到的一篇很火的文章改寫而來。這篇文章可能不是100%正確,也可能只是許多最佳實踐中的一種,但它確實很好地說明了一些我在這幾個月裡發現的重點。

好吧我們節奏緊湊一點,先說一個關鍵點然後看幾個例子。P.S. 這個UI比較醜,但那不是重點!另外這篇教程比較長,我已經省略了許多程式碼,所以請下載原始碼來觀看例子。

一些你要了解的關鍵點

1. 你應該用ObservableCollection<>而不是List或者Dictionary去儲存資料。顧名思義,你的介面必須能Observe(監視)你的資料集。而恰好這個Collection實現了一些能夠讓它被很好地監控的介面。

2. 每一個WPF控制元件(包括Window)都有一個DataContext屬性,而每一個Collection控制元件都有一個ItemsSrouce屬性用於繫結資料。

3. INotifyPropertyChanged介面將被廣泛地使用在你的介面和後臺程式碼中,它用於傳遞資料的變更。

Example 1:一個大部分人都這樣做的錯誤做法

來個栗子先! 我們先建立一個Song類,而不是那個二逼的Person類。我們能把Song組織到Album中,甚至更大的集合中。一個簡單的song類如下:

public class Song
    {
        #region Members
        string _artistName;
        string _songTitle;
        #endregion

        #region Properties
        /// The artist name.
        public string ArtistName
        {
            get { return _artistName; }
            set { _artistName = value; }
        }

        /// The song title.
        public string SongTitle
        {
            get { return _songTitle; }
            set { _songTitle = value; }
        }
        #endregion
    }

在WPF的術語中這叫做Model,而圖形介面就是我們的View。而ViewModel就是將資料繫結到他們上的魔法師,它把一個簡單的Model變成了WPF框架能夠使用的東西。我再重申一下,這個類就是我們的Model。

好的現在我們來建立一個SongViewModel。我應該思考的是,我們要展示什麼東西出來。假設我們只關心一首歌的演唱者,那這個SongViewModel就可以被定義成這樣:

public class SongViewModel
{
    Song _song;

        public Song Song
        {
            get
            {
                return _song;
            }
            set
            {
                _song = value;
            }
        }

        public string ArtistName
        {
            get { return Song.ArtistName; }
            set { Song.ArtistName = value; }
        }
}

差不多就是這樣。我們在ViewModel中暴露了一個屬性,就是想在UI中自動地改變它,反之亦然:

SongViewModel song = ...;
// ... enable the databinding ...
//  change the name
song.ArtistName = "Elvis";
//  the gui should change

還有一點要說的就是,我們在XAML中這樣來建立我們的ViewModel:

<Window x:Class="Example1.MainWindow"
        xmlns:local="clr-namespace:Example1">
    <Window.DataContext>
        <!-- Declaratively create an instance of our SongViewModel -->
        <local:SongViewModel />
    </Window.DataContext>


這相當於我們在後臺程式碼中這樣寫:

    public partial class MainWindow : Window
    {
        SongViewModel _viewModel = new SongViewModel();
        public MainWindow()
        {
            InitializeComponent();
            base.DataContext = _viewModel;
        }
    }

當然如果你想用後臺程式碼實現的話你得把XAML中的Window.DataContext去掉:

<Window x:Class="Example1.MainWindow"
        xmlns:local="clr-namespace:Example1">
    <!--  no data context -->


好了,這就是我們的介面:

點那個Button沒用,因為我們還沒繫結資料呢。

資料繫結


還記得我說過要選一個用來展示的屬性麼?這個屬性就是ArtistName,我選這個屬性是因為WPF的屬性中沒有 和它名字相同的屬性。(之後是作者的一堆吐槽,不表。)

要把ArtistName屬性繫結到SongViewModel,我們只要在Xaml中寫:

  <Label Content="{Binding ArtistName}" />

關鍵字 “Binding”會把Label的Content和在DataContext中存在的ArtistName的值繫結起來。如你所見,我們把DataContext設為SongViewModel的一個例項,然後就可以在Label中成功地展示_songViewModel.ArtistName了。

但是,你現在點button還是沒反應,因為介面接受不到任何關於資料改變的通知。

Example 2:INotifyPropertyChanged介面

我們現在得實現這個INotifyPropertyChanged(簡稱INPC)介面了。任何實現這個介面的類,都會在值改變的時候發通知給相應監聽者。所以我們要改動一下SongViewModel類:

public class SongViewModel : INotifyPropertyChanged
    {
        #region Construction
        /// Constructs the default instance of a SongViewModel
        public SongViewModel()
        {
            _song = new Song { ArtistName = "Unknown", SongTitle = "Unknown" };
        }
        #endregion

        #region Members
        Song _song;
        #endregion

        #region Properties
        public Song Song
        {
            get
            {
                return _song;
            }
            set
            {
                _song = value;
            }
        }

        public string ArtistName
        {
            get { return Song.ArtistName; }
            set
            {
                if (Song.ArtistName != value)
                {
                    Song.ArtistName = value;
                    RaisePropertyChanged("ArtistName");
                }
            }
        }
        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Methods

        private void RaisePropertyChanged(string propertyName)
        {
            // take a copy to prevent thread issues
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }


有幾點要提一下。首先,我們有檢查屬性的值是否真的改變了,這樣能使專案的效能在資料很複雜時稍稍提升。其次,當值真的改變的時候,我們會發出PropertyChanged時間的訊號給所有監聽者。

現在我們有了Model和ViewModel,只要再定義一下View就行。這就是我們的MainWindow:

<Window x:Class="Example2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Example2"
        Title="Example 2"  SizeToContent="WidthAndHeight" ResizeMode="NoResize"
        Height="350" Width="525">
    <Window.DataContext>
        <!-- Declaratively create an instance of our SongViewModel -->
        <local:SongViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" Grid.Row="0" Content="Example 2 - this works!" />
        <Label Grid.Column="0" Grid.Row="1" Content="Artist:  " />
        <Label Grid.Column="1" Grid.Row="1" Content="{Binding ArtistName}" />
        <Button Grid.Column="1" Grid.Row="2" Name="ButtonUpdateArtist"
        Content="Update Artist Name" Click="ButtonUpdateArtist_Click" />
    </Grid>
</Window>

要測試資料綁定了,我們先沿用傳統方法:實現button的Onclick函式:

    public partial class MainWindow : Window
    {
        #region Members
        SongViewModel _viewModel;
        int _count = 0;
        #endregion

        public MainWindow()
        {
            InitializeComponent();

            //  We have declared the view model instance declaratively in the xaml.
            //  Get the reference to it here, so we can use it in the button click event.
            _viewModel = (SongViewModel)base.DataContext;
        }

        private void ButtonUpdateArtist_Click(object sender, RoutedEventArgs e)
        {
            ++_count;
            _viewModel.ArtistName = string.Format("Elvis ({0})", _count);
        }
    }    

這行得通,但我們不該這樣寫。首先,我們把更新歌手的邏輯放到了介面的後臺程式碼中,但它不應該寫在這!這些程式碼應該只和Window這個介面有關。第二個問題是,如果我們想把onclick中的程式碼放到別的地方,例如從一個Menu中選擇,這就意味著我們得複製貼上好多次。

這是我們點選按鈕後的介面:




Example 3:Commands
在UI中繫結事件有點麻煩。但是WPF提供了一種好方法:ICommand。許多控制元件都有Command屬性,它和Content,ItemsSource的繫結規則一樣,除非你需要繫結到一個能夠返回一個ICommand的屬性。

在我們這個碉堡的例子中,我僅僅實現了一個碉堡的類叫RelayCommand,它實現了ICommand介面。

ICommand要求使用者定義兩個方法:bool CanExecute 和 void Execute。前者告訴使用者是否能夠執行,它能夠用來控制空間是否可用。在我們的例子中,我們不關心這個,所以我們只返回true,表示我們一直能呼叫Execute成功。

因為我們想重用ICommand的程式碼,所以就把重複程式碼放到RelayCommand中。具體的程式碼可以看壓縮包。這是介面:


Example 4:Frameworks

現在你可能覺得許多程式碼都是重複的,實現INPC介面,構造Command。其實這些都是些模版程式碼,例如我們可以把實現INPC的程式碼放到一個ObservableObject基類中。而對於RelayCommand類,我們把它放入我們的.Net類庫中。這就是你在網上找到的那些MVVM框架做的事(例如Prism Caliburn等等)。

ObservableObject和RelayCommand這兩個類是重構之後必然能得到的比較基本的類。所以我把這些類放到一個小的類庫中希望能夠在以後重用。現在介面是這樣:

Example 5: 歌曲的集合,但是這種做法是錯的

我之前說過,要想在View(就是你的XAML)中顯示資料集,必須把資料放到ObservableCollection中。本例我們建立一個AlbumViewModel類,它把許多個Song聚集起來。我們還構建了一個SongDatabase用於歌曲資訊的生成。

這是我們的AlbumViewModel原型:

    class AlbumViewModel
    {
        #region Members
        ObservableCollection<Song> _songs = new ObservableCollection<Song>();
        #endregion
    }

你應該會想“這次我的View Model不一樣了,我想把songs用一個AlbumViewModel來展示,而不是一個SongViewModel”。

我們還要建立一些ICommand並且把他們繫結到button上。

public ICommand AddAlbumArtist {}

public ICommand UpdateAlbumArtists {}

這個例子中,當你點選Add Artist時一切ok,但是點選Update的時候卻沒反應。你可以去讀一下MSDN上的這一頁,高亮的黃字說:

為了完全支援資料繫結,你的集合眾的每一個屬性都應該實現一個合適的變值提醒機制,例如INotifyPropertyChanged介面。

現在我們的介面是這樣:

Example 6: 歌曲的集合,正確做法!

在這個最終的例子中,我們修復了AlbumViewModel,讓ObservableCollection中包含的變數變成了SongViewModels:

class AlbumViewModel
{
    #region Members
    ObservableCollection<SongViewModel> _songs = new ObservableCollection<SongViewModel>();
    #endregion
    //  code elided for brevity
}

現在我們所有的button都繫結到相應的command上了,後臺程式碼真乾淨!

最終介面是這樣:


結論

例項化你的ViewModel

最後一點要說的是當我們在XAML中宣告ViewModel時,你沒法去傳遞引數。換句話說,你的ViewModel必須沒有,或者說有一個預設的無參建構函式。

當然你也可以在cs程式碼中例項化ViewModel並且傳參給他。

其他框架

還有許多不同的MVVM框架,他們有不同的複雜度,不同的功能,不同的目標技術例如WPF Wp7 Silverlight或三者共有。

最後

希望這6個例子能夠讓你輕鬆寫出MVVM框架的WPF應用。我已經嘗試把我認為的重點都在這篇文章中覆蓋了。

引用

高階閱讀

Hello World in MVVM Light in 10 MinutesMVVMLight Using Two Views

相關推薦

WPF/MVVM 快速入門教程

簡介 先假設大家對c#已經有一定的瞭解了,並且很容易接受一些關於WPF的知識。因為下面的知識是通過WPF為例的。 我前段時間開始研究了一下WPF,但是找不到什麼有幫助的關於MVVM的教程。因此我希望這篇文章能讓你眼前一亮。 當我們剛開始學一門新技術時,我們得益於前人的積

WPF快速入門系列(8)——MVVM快速入門

http://www.cnblogs.com/zhili/p/MVVMDemo.html 一、引言   在前面介紹了WPF一些核心的內容,其中包括WPF佈局、依賴屬性、路由事件、繫結、命令、資源樣式和模板。然而,在WPF還衍生出了一種很好的程式設計框架,即WVV

Django REST framework 的快速入門教程

ret turn ads 使用 blog 所有 定義 想去 cti CRM-API項目搭建 序列器(Serializers) 首先,我們來定義一些序列器。我們來創建一個新的模塊(module)叫做 crm/rest_searializer.py ,這是我們用來描述數據是如何

ThinkPHP3.1快速入門教程

pan html manual thinkphp 教程 font 入門教程 入門 href ThinkPHP3.1快速入門教程 http://www.thinkphp.cn/info/155.html -----------------------

AngularJS [ 快速入門教程 ]

空數組 spa put bold [ ] 地址 替換 個人 傳遞 前 序 S N AngularJS是什麽? 我想既然大家查找AngularJS就證明大家多多少少對AngularJS都會有了解。 AngularJS就是,使用JavaScript編寫的客戶端

小程序開發快速入門教程(附源碼)

五分鐘上手-微信小程序 1:用沒有註冊過微信公眾平臺的郵箱註冊一個微信公眾號, 申請帳號 ,點擊 https://mp.weixin.qq.com/wxopen/waregister?action=step1 根據指引填寫信息和提交相應的資料,就可以擁有自己的小程序帳號。註冊完成之後開始登

Linux快速入門教程-進程管理ipcs命令學習

Linux Linux入門 Linux運維 Linux命令 使用Linux系統必備的技能之一就是Linux進程管理,系統運行的過程正是無數進程在運行的過程。這些進程的運行需要占用系統的內存等資源,做好系統進程的管理,對於我們合理分配、使用系統資源有非常大的意義。今天我們來看進程管理命令中的ip

ubuntu快速入門教程:初次見面

切換 優化 第三方軟件 技術分享 計算 shu 做的 基本 移動設備 1 什麽是ubuntu? Ubuntu(友幫拓、優般圖、烏班圖)是一個以桌面應用為主的開源GNU/Linux操作系統,Ubuntu 是基於Debian GNU/Linux,支持x86、amd64(即x6

帶領新手快速開發Android App開發視頻課程 安卓快速入門教程

Android 第1章 課程介紹與項目準備如果你從事Android開發,那你一定要學習一些開發技巧和掌握一些開發邏輯,而很不巧,我這裏全部都有,最開始我們介紹一下我們這個應用的整體組織架構,通過對各個平臺的api進行一個簡單的分析,以及說明一下我們的RxVolley的網絡框架使用教程,在本章中,我們將分析整個

npm 與 package.json 快速入門教程

span out variable toc 管理器 gen 生產環境 格式 加載 npm 與 package.json 快速入門教程 2017年08月02日 19:16:20 閱讀數:33887 npm 是前端開發廣泛使用的包管理工具,之前使用 Weex 時看

hadoop快速入門教程:hadoop安裝包下載與監控參數說明

分布式 height tex region 集群 RoCE 發行版 store serve 前階段用了差不多兩周的時間把DKhadoop的運行環境搭建以及安裝的各個操作都介紹了一遍。關於DKhadoop安裝包下載也只是順帶說了一下,但好像大快搜索的服務器在更新,新的下載頁面

doodoo.js快速入門教程?

node Doodoo.js -- 中文最佳實踐Node.js Web快速開發框架。支持Koa.js, Express.js中間件,支持模塊化,插件,鉤子機制,可以直接在項目裏使用 ES6/7(Generator Function, Class, Async & Await)等特性 htt

doodoo.js快速入門教程

touch es6 odoo node 框架 創建 init fun http Doodoo.js -- 中文最佳實踐Node.js Web快速開發框架。支持Koa.js, Express.js中間件,支持模塊化,插件,鉤子機制,可以直接在項目裏使用 ES6/7(Gener

Koa快速入門教程(一)

Koa 是由 Express 原班人馬打造的,致力於成為一個更小、更富有表現力、更健壯的 Web 框架,採用了async和await的方式執行非同步操作。 Koa有v1.0與v2.0兩個版本,隨著node對async和await的支援,Koa2的正式釋出,本文Koa均

springBoot快速入門教程(第一篇)

Springboot是什麼? 1、springboot是在spring的基礎上進一步封裝,讓以前繁雜的配置全部簡化,從而快速構建專案。 2、對主流框架無配置整合,提高開發效率。 3、內建tomcat無需先部署在執行 4、與雲端計算天然整合 快速搭建一個簡單的springboot

Redis快速入門-教程

目錄: Redis是什麼? Redis 安裝 Redis 啟動 Redis支援的資料型別 Redis伺服器操作命令 伺服器客戶端操作命令 Redis string操作命令 Sorted set (Zset)型別資料的操作命令: Redis l

Spring MVC快速入門教程

必看!!! 這篇文章講的很好很實用,應對Spring MVC基礎知識足夠了 文章轉載自:https://www.tianmaying.com/tutorial/spring-mvc-quickstart ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Apache Shiro 快速入門教程

第一部分 什麼是Apache Shiro 1、什麼是 apache shiro : Apache Shiro是一個功能強大且易於使用的Java安全框架,提供了認證,授權,加密,和會話管理 如同 spring security 一樣

【Android】Android快速入門教程(五——2)——logcat控制檯

文章目錄 一、logcat是什麼? 二、logcat有什麼用? 三、我該怎麼列印自己要的日誌內容 一、logcat是什麼? 一般情況可以在下圖所示位置可以找到logcat控制檯,如果找不到的,可以在Android studi

thymeleaf快速入門教程

thymeleaf教程 本教程涵蓋了常見的前端操作,比如,判斷,迴圈,引入模板,常用函式(日期格式化,字串操作)下拉,js和css中使用,基本可以應對一般場景。 怎麼使用? 前端html頁面標籤中引入如下: <html xmlns:th="http://www.