WPF之Binding的使用(一)
一、 前言
初學WPF經常被Binding搞得苦不堪言,Binding的重用性就不做介紹了,在WPF應用程序開發中Binding是一個非常重要的部分。WPF也是近期才接觸,學習WPF也是在網上查資料與微軟的MSDN進行學習,寫本博客的目為了溫故而知新把學習過程記錄下來,以備后查。
二、 WPF之Binding
1.) Binding的一般步驟:准備數據源,數據源需要實現INotifyPropertyChanged接口.如下實例
class Student: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string name; public string Name { get { return name; } set{name = value;} //觸發事件 if (PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } }
2.) Binding對象
Student p = new Student ("曾小賢"); Binding binding = new Binding(); binding.Source = p; binding.Path = new PropertyPath("Name");
3.) 用Binding對象將數據源和目標連結 假如在XAML處添加了一個TextBlock目標
<TextBlock x:Name="txtName"></TextBlock>
使用BindingOperations.SetBinding()方法將其進行binding了
BindingOperations.SetBinding(txtName, TextBlock.TextProperty, binding);
也可以使用UI元素的基類FramworkElement封裝的SetBinding函數
txtName.SetBinding(TextBlock.TextProperty, binding);
兩步結合在一起可以這樣寫
txtName.SetBinding(TextBlock.TextProperty, new Binding("Name") { Source=p});
三、 WPF對比WinForm實例
做這個例子也是上家公司同事想了解WPF,之前公司項目是用WinForm做His系統的,這個例子主要展示同一個需求用WinForm和WPF分別進行實現,通過這個例子,我們可以看到兩者之間的區別和聯系,同時也可以對我們的項目選型帶來一定的參考作用(原型來自於Josh Smith與Knights Warrior的文章,個人覺得講得非常不錯,所以對原有例子進行了改造,進而有了這個案例)。
下面是WinForms版本與WPF版本的截圖:
程序簡介
這個例子非常簡單,需求就是展示三大社區的基本信息,同時你可以在輸入框對其進行修改,當焦點切換的時候,你就會看到它會自動進行修改,你把鼠標放在圖片上面會提示社區的ID等等。我在這里沒有用復雜的邏輯和高深的架構,只是想通過這個例子展示WinForm的WPF的差異和聯系,所以在程序處理上可能會有很多漏洞,比如沒有對輸入進行驗證,你可以輸入空格和任意字符等。如果你編輯了某個社區的中文名稱或者英文名稱,然后把焦點移到另外一個地方,這些更改就會通過右上角的全名體現出來,因為他們都是通過綁定到公用字段來實現這些操作的。
- 公用代碼部分(BusinessObjects)
這兩個應用程序都是使用的BusinessObjects作為邏輯類庫,BusinessObjects中的Company對UI所使用的數據進行了Mock。所以他們在需求方面都是一樣的,由於比較簡單,所以請看下面代碼:

1 using System; 2 using System.ComponentModel; 3 using System.IO; 4 using System.Reflection; 5 6 namespace BusinessObjects 7 { 8 public class Company : INotifyPropertyChanged 9 { 10 #region 創建數據方法 11 12 public static Company[] GetCompanys() 13 { 14 // 在真正的應用程序中,這可能會調用數據訪問層,從數據庫中獲取記錄。 15 return new Company[] 16 { 17 new Company(1, "博客園", "CNBlogs", GetPictureFile(1), new DateTime(2004, 1, 12)), 18 new Company(2, "51CTO", "51CTO", GetPictureFile(2), new DateTime(2005, 3, 1)), 19 new Company(3, "CSDN", "CSDN", GetPictureFile(3), new DateTime(2000, 1, 20)), 20 new Company(4, "開源中國", "開源中國", GetPictureFile(4), new DateTime(2008, 8, 1)), 21 new Company(5, "ITEYE", "ItEye", GetPictureFile(5), new DateTime(2011, 4, 1)), 22 }; 23 } 24 25 private static string GetPictureFile(int CompanyID) 26 { 27 string fileName = String.Format("emp{0}.jpg", CompanyID); 28 string folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 29 folder = Path.Combine(folder, "Images"); 30 return Path.Combine(folder, fileName); 31 } 32 33 private Company(int id, string chineseName, string EnglishName, string pictureFile, DateTime startDate) 34 { 35 this.ID = id; 36 this.chineseName = chineseName; 37 this.EnglishName = EnglishName; 38 this.PictureFile = pictureFile; 39 this.StartDate = startDate; 40 } 41 42 #endregion // 創建數據方法 43 44 #region 實體屬性 45 46 public int ID { get; private set; } 47 48 string _chineseName; 49 public string chineseName//中文名稱 50 { 51 get { return _chineseName; } 52 set 53 { 54 if (value == _chineseName) 55 return; 56 57 _chineseName = value; 58 59 this.OnPropertyChanged("chineseName"); 60 this.OnPropertyChanged("FullName"); 61 } 62 } 63 64 string _EnglishName; 65 public string EnglishName//英文名稱 66 { 67 get { return _EnglishName; } 68 set 69 { 70 if (value == _EnglishName) 71 return; 72 73 _EnglishName = value; 74 75 this.OnPropertyChanged("EnglishName"); 76 this.OnPropertyChanged("FullName"); 77 } 78 } 79 80 public string FullName 81 { 82 get { return String.Format("{0}, {1}", this.EnglishName, this.chineseName); } 83 } 84 85 public string PictureFile { get; private set; }//圖片文件地址 86 public DateTime StartDate { get; private set; }//開始時間 87 88 #endregion // 實體屬性 89 90 #region INotifyPropertyChanged 接口 91 92 public event PropertyChangedEventHandler PropertyChanged; 93 94 protected virtual void OnPropertyChanged(string propertyName)//屬性變更通知 95 { 96 PropertyChangedEventHandler handler = this.PropertyChanged; 97 if (handler != null) 98 handler(this, new PropertyChangedEventArgs(propertyName)); 99 } 100 101 #endregion 102 } 103 }
上面這段代碼沒有什么不尋常的地方,大家寫WinForm和Asp.Net也會寫這樣的邏輯類,只是要注意Company 實現了INotifyPropertyChanged 接口,大家看到這個接口只有一個OnPropertyChanged的方法,這個方法就是我們要說的屬性變更通知方法,就是說當一個屬性改變了,我們需要做些什么來響應這些改變。
- WPF實現介紹
WPF版本我這里就做得很簡單了,由於開發WPF程序提供了很多模板和工具,所以我這里基本沒寫什么代碼,全部的代碼都是通過XAML實現,並且大部分都是自動生成的,只是我們要根據項目具體情況做一些修改就行。
這個WPF項目同樣有一個Window
和一個customUserControl
, 和 WinForms 版本基本一樣. 只是WinForms中用FlowLayoutPanel
來承載EmployeeControl
控件, 而WPF 用的是ItemsControl
來承載這個用戶控件.更加可喜的是,WPF通過模板來進行定制,所以我們就不需要像WinForms那樣寫循環加載控件的代碼,下面就是WPF用XAML實現的窗體代碼:

1 <Window 2 x:Class="WpfApp.Window1" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="clr-namespace:WpfApp" 6 xmlns:model="clr-namespace:BusinessObjects;assembly=BusinessObjects" 7 Title="WPF App" Height="500" Width="560" 8 WindowStartupLocation="CenterScreen" 9 > 10 <Window.DataContext> 11 <ObjectDataProvider ObjectType="{x:Type model:Company}" MethodName="GetCompanys" /> 12 </Window.DataContext> 13 <Grid Width="500"> 14 <Label Name="label1" HorizontalContentAlignment="Center" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" Height="36.6" Margin="0,16,0,0"> 15 .NET 中文社區大比拼 16 </Label> 17 <WrapPanel Margin="0,57.6,0,0"> 18 <ScrollViewer VerticalScrollBarVisibility="Auto" Height="536" Width="500" > 19 <ItemsControl ItemsSource="{Binding}" HorizontalContentAlignment="Center" Focusable="False" Height="783"> 20 <ItemsControl.ItemTemplate> 21 <DataTemplate> 22 <local:CompanyControl /> 23 </DataTemplate> 24 </ItemsControl.ItemTemplate> 25 </ItemsControl> 26 </ScrollViewer> 27 </WrapPanel> 28 </Grid> 29 </Window>
在如下的XAML代碼中,這里有幾點需要注意,。Window的DataContext賦予了一個ObjectDataProvider的對象,而ObjectDataProvider又會調用GetEmployees這個方法。所以一旦把DataContext設置到Company 對象,並且把ItemsControl的ItemsSource設置為“{Binding}” 就意味着該控件里面會自動顯示Company 對象的所有數據。
這里我們並不需要像WinForm一樣用循環的方式創建CompanyControl的實例。這是因為ItemsControl中的ItemTemplate屬性設置為了一個DataTemplate,同時ItemsControl中的ItemsSource綁定到了Company 的對象數組,那么ItemTemplate就會知道如何創建一個CompanyControl,所以大家看到這里寫的代碼就相對變少了,這也是XAML的一個優點之一。
該CompanyControl的后台CS文件也是空的(除了必須的InitializeComponent),所以它不像的WinForms應用程序那么累贅,界面和邏輯緊密的耦合在了一起。下面就是CompanyControl的XAML代碼, 這個代碼相對來說就比較簡單了。

1 <UserControl x:Class="WpfApp.CompanyControl" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Height="137" Width="481"> 5 <Border BorderBrush="Black" BorderThickness="1" Margin="2" SnapsToDevicePixels="True" Width="469"> 6 <Grid Height="129" Width="451"> 7 <Image Source="{Binding PictureFile}" Margin="10" Name="image1" Stretch="Fill" Width="150" Height="80" HorizontalAlignment="Left" > 8 <Image.ToolTip> 9 <TextBlock> 10 <Run TextBlock.FontWeight="Bold">Company ID:</Run> 11 <TextBlock Margin="4,0,0,0" Text="{Binding ID}" /> 12 </TextBlock> 13 </Image.ToolTip> 14 </Image> 15 <Label Content="{Binding FullName}" Height="34" Margin="99,2,0,0" Name="中英文名稱" VerticalAlignment="Top" HorizontalContentAlignment="Right" FontSize="16" FontWeight="Bold" /> 16 <Label Margin="190,34,0,0" Name="chineseNameLabel" FontWeight="Bold" Height="28" VerticalAlignment="Top" HorizontalAlignment="Left" Width="73">中文名稱:</Label> 17 <TextBox Text="{Binding chineseName}" HorizontalAlignment="Right" Margin="0,39,10,0" Name="textBox1" Width="172" Height="23" VerticalAlignment="Top" TextDecorations="None" /> 18 19 <Label FontWeight="Bold" Height="28" Margin="190,0,0,34" Name="EnglishNameLabel" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="73">英文名稱:</Label> 20 <TextBox Text="{Binding EnglishName}" Height="23" Margin="0,0,10,34" Name="textBox2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="172" /> 21 22 <Label Height="28" Margin="190,0,185,2" Name="startDateLabel" VerticalAlignment="Bottom" FontWeight="Bold">創建日期:</Label> 23 <Label Content="{Binding StartDate}" Height="28" HorizontalAlignment="Right" Margin="0,0,10,2" Name="startDateValueLabel" VerticalAlignment="Bottom" Width="172" /> 24 </Grid> 25 </Border> 26 </UserControl>
如上面的代碼所示,UI上的很多元素我們都可以通過拖控件進行實現,有個功能需要自己簡單的寫一寫代碼,UI上面有一個功能就是你把鼠標放在圖片上的時候會提示CompanyID,這個功能通過ToolTip屬性進行實現的。ToolTip屬性是WPF所有元素的基類FrameworkElement的一個屬性,所以我們可以在這些子元素當中直接使用。
那么大家注意到,這個小功能在WinForm中我們要寫一些代碼,而在WPF就可以直接通過屬性定制,所以在很多方面WPF對這些方面都做了封裝和簡化,也提高了我們的開發效率。
- WinForms實現介紹
WinForms版本就包含一個Form
和一個展示社區信息的custom UserControl
, 這個Form
包含了一個FlowLayoutPanel控件
, 它主要的作用就是用來承載每個社區的實例. 那么代碼就如下所示:

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using BusinessObjects; 10 11 namespace WinFormsApp 12 { 13 public partial class Form1 : Form 14 { 15 public Form1() 16 { 17 InitializeComponent(); 18 19 // 創建並初始化用戶控件 20 foreach (Company com in Company.GetCompanys()) 21 { 22 CompanyControl comCtrl = new CompanyControl(); 23 comCtrl.Company = com; 24 this.flowLayoutPanel.Controls.Add(comCtrl); 25 } 26 } 27 } 28 }
CompanyControl是我們創建的一個UserControl,由於每個CompanyControl都要顯示一個Company對象的屬性值,我在這里使用了BindingSource控件來進行綁定,這樣做也是為了和WPF更接近考慮(增強對比性,呵呵)。具體如下截圖:
如上圖所示,我們用了BindingSource來獲取數據,但有一個屬性除外,那就是Company ID,請看下面代碼:

1 using System.Drawing; 2 using System.Windows.Forms; 3 using BusinessObjects; 4 5 namespace WinFormsApp 6 { 7 /// <summary> 8 /// 顯示社區對象的WinForms控件 9 /// </summary> 10 public partial class CompanyControl : UserControl 11 { 12 public CompanyControl() 13 { 14 InitializeComponent(); 15 16 // 將圖片文件路徑轉換為位圖。 17 Binding binding = this.CompanyPicture.DataBindings[0]; 18 binding.Format += this.ConvertFilePathToBitmap; 19 } 20 21 void ConvertFilePathToBitmap(object sender, ConvertEventArgs e) 22 { 23 e.Value = Bitmap.FromFile(e.Value as string); 24 } 25 26 public Company Company 27 { 28 get { return this.CompanyBindingSource.DataSource as Company; } 29 set 30 { 31 this.CompanyBindingSource.DataSource = value; 32 33 // 該社區的圖片顯示ID提示。 34 if (value != null) 35 { 36 string msg = "Company ID: " + value.ID; 37 this.toolTip.SetToolTip(this.CompanyPicture, msg); 38 } 39 } 40 } 41 } 42 }
這里有幾點需要注意.在綁定的時候,我們對PictureFile 字段進行了轉換,這個是必須做的. 如果不那樣做, 這個圖片會綁定失敗,因為在綁定的時候它不能自動把string類型
直接轉化為Image類型
.
現在我們已經把Company綁定到了我們的控件上, 這里我需要給PictureBox一個
tooltip的效果. 這個tooltip將顯示 Company ID
, 前綴顯示為 "Company ID:". 現在這個是在代碼里面寫的,沒有在窗體中發現有WPF ToolTip等類似的工具,不知道大家用到過沒有?
總的來說, 這是一個很簡單的例子,我們的大部分功能也是用代碼沒有寫代碼,是通過visual designer進行實現的.然后通過一部分代碼把它銜接起來, 我們看到Windows Forms是一個非常快速和實用的開發平台.
例子是用Visual Studio 2017編寫的,所以大家可以下載下來進行查看.
Demo下載:WinFormsAndWPFCompare.rar