控件的本質是「數據+算法」——用戶輸入原始數據,算法處理原始數據並獲得結果數據。問題就在於程序如何將結果數據展現給用戶。一樣一組數據,你可使用LED陣列顯示出來,或者是以命令行模式藉助各類控制字符(如Tab)對其並輸出,但這些都不如圖形化用戶界面(Graphics User Interface ,GUI)來的友好和方便。GUI是程序界的優勝者,但在Windows上實現圖形化界面有不少中方法。每種方法又擁有本身的一套開發理念和工具。每種GUI開發與它的裏理念和工具共同組成一種方法論。常見的有:程序員
咱們能夠對以上方法論分爲四代:算法
WPF之因此可以稱上最新的一代在於兩點:第一,以前幾代GUI方法論只能使用編程語言進行UI設計,而WPF具備專門的UI設計的XAML;第二,前幾代在UI與數據交互方面是由Windows消息到控件事件一脈相承,始終是把UI控件放在主導地位而把數據放在被動地位,用UI來驅動數據的改變,WPF在事件驅動的基礎上引入了數據驅動界面的理念,讓數據重回核心地位而讓UI迴歸數據表達者的位置編程
在從Winform轉到WPF的學習過程當中,心中必定要樹立起這樣一個概念——WPF中是數據驅動UI,數據是核心、是主動的。UI從屬於數據並表達數據、是被動的。也能夠這麼理解,當咱們想改變控件上的顯示內容時,只須要改變該控件綁定的數據源內容便可。app
UI的功能始讓用戶觀察和操做數據,爲了讓用戶觀察數據,咱們須要用UI元素來顯示數據;爲了讓用戶操做數據,咱們須要用UI元素響應用戶的操做。WPF把那些可以展現數據、響應用戶操做的UI元素稱爲控件(Control)。控件所展現的數據,咱們稱之爲控件的「數據內容」,控件在響應用戶的操做後會執行本身的一些方法以事件(Event)的形式通知應用程序(開發人員就能夠決定如何處理這些事件),咱們稱之爲控件的「行爲」或「算法」內容。可見,WPF中的控件扮演者雙重角色、是個很是抽象的概念——Control是數據和行爲的載體,而無需具備固定的形象。換句話說,Button之因此是Button不是由於它長得方方正正、顯示一串文字而且可以響應用戶單擊,而是應該倒過來想——凡是符合「能顯示一些提示文字(能夠是文字、也能夠是圖片、動畫甚至是視頻)並能響應用戶單擊」這一抽象概念的UI元素均可以是Button,至於Button具體長成什麼樣子(是方是圓、是顯示文字仍是顯示動畫)徹底由它的風格(Style)和模板(Template)來決定。編程語言
在平常的開發工做中咱們打交道的控件大體分爲6類,即:函數
WPF能夠分爲以下幾類工具
名稱 | 註釋 |
ContentControl | 單一內容控件 |
HeaderedContentControl | 帶標題的單一內容控件 |
ItemsControl | 以條目集合爲內容的控件 |
HeaderedItemsControl | 帶標題的以條目集合爲內容的控件 |
Decorator | 控件裝飾的元素 |
Panel | 面板類元素 |
Adorner | 文字點綴元素 |
Flow Text | 流式文本元素 |
TextBox | 文本輸入框 |
TextBlock | 靜態文字 |
Shape | 圖形元素 |
下面咱們逐一剖析這些元素的內部結構,瞭解內容與內容屬性。組件化
咱們能夠把控件想象成一個容器,容器裏面裝的東西就是它的內容,控件的內容能夠直接是數據,也能夠是控件。當控件的內容仍是控件的時候就造成了控件的嵌套,因此WPF的UI會造成一個樹形結構。若是不考慮控件內部的組成結構,只觀察由控件組成的「樹」,那麼這棵樹稱爲「邏輯樹(Logicol Tree)」;WPF控件每每是由更基本的控件構成的。即控件自己就是一棵樹,若是連控件自己的樹也考慮在內,則這顆比邏輯樹更「繁茂」的樹稱爲可視元素樹(Visual Tree)。佈局
控件時內存中的對象,控件的內容也是內存中的對象。控件經過本身的某個屬性引用着做爲其控件的對象,這個屬性稱爲內容屬性(Content Property)。「內容屬性」是個統稱,具體到每種控件上,內容屬性都有本身確切的名字——有的直接叫Content,有的叫Child;有些控件的內容能夠是集合,其內容屬性叫Items或Children的。學習
咱們把符合某類內容模型的UI元素稱爲一個族,每一個族用它們共同的基類來命名
本類元素的特色以下:
怎麼理解「只能由單一元素充當其內容」這句話呢?看下例子。
Button控件屬於ContentControl族,因此下面兩個Button代碼都是正確的——第一個Button的內容是一個靜態文字,第二個Button的內容是一張圖片。
<StackPanel> <Button> <TextBlock>Hello World</TextBlock> </Button> <Button> <Image Source=".\1.jpg" Height="30" Width="30"></Image> </Button> </StackPanel>
但若是你想讓Button的內容即包含文字又包含圖片是不行的:
<StackPanel> <Button> <TextBlock>Hello World</TextBlock> <Image Source=".\1.jpg" Height="30" Width="30"></Image> </Button> </StackPanel>
編譯器會報錯「對象「Button」已經具備子級且沒法添加「Image」。「Button」只能接受一個子級。」但是若是咱們真的須要一個帶圖標的Button那怎麼辦呢?咱們只須要先用一個能夠包含多個元素的佈局控件把圖片和文字包裝起來,再把這個佈局控件做爲Button的內容就行了
ContentControl族包含的控件以下
Button | ButtonBase | CheckBox | ComboboxItem |
ContentControl | Frame | GridViewColumnHeader | GroupItem |
Label | ListBoxItem | ListViewItem | NavigationWindow |
RadioButton | RepeatButton | ScrollViewer | StatusBarItem |
ToggleButton | ToolTip | UserControl | Window |
本族元素的特色以下:
HeaderedContentControl族包含的控件以下
Expander | GroupBox | HeaderedContentControl | TabItem |
下面這個例子是一個以圖標爲Header、以文字爲內容主體的GroupBox
<StackPanel> <GroupBox Margin="10"> <GroupBox.Header> <Image Source=".\1.jpg" Height="30"></Image> </GroupBox.Header> <TextBlock TextWrapping="WrapWithOverflow" Margin="10"> 願你慢慢長大,願你有好運,若是沒有,但願你在不幸中學會慈悲;願你被不少人愛,若是沒有,但願你在寂寞中學會寬容。 </TextBlock> </GroupBox> </StackPanel>
本族元素特色以下:
本族的包含控件以下所示
Menu | MenuBase | ContextMenu | Combobox |
ItemsControl | ListBox | ListView | TabControl |
TreeView | Selector | StatusBar |
本族控件具備特點的一點就是會自動使用條目容器對提交給它的內容進行包裝。合法的ItemsControl內容必定是個集合,當咱們把這個集合做爲內容提交給ItemsControl時,ItemsControl不會把這個集合直接拿來用,而是使用本身對應的條目容器把集合中的條目逐個包裝,而後再把包裝好的條目序列看成本身的內容。這種自動包裝的好處就是容許程序員向ItemsControl提交各類數據類型的集合,程序員在思考問題時會天然而然的感受到ItemsControl控件直接裝載着數據,若是須要進行增長、刪除、更新或者排序,那麼直接去操做數據集合就能夠,UI會自動將改變展示出來,這正體現了在WPF開發時數據直接驅動UI再進行顯示。
ListBox是典型的ItemsControl,下面將以它爲例,研究一下ItemsControl。
首先,咱們看看ListBox的自動包裝。WPF的ListBox在顯示功能上比Windows Form或者ASP.NET的ListBox要強大的多。傳統的ListBox只能將條目以字符串的形式顯示,而WPF的ListBox除了能夠顯示中規中矩的字符串條目還能顯示更多的元素,如CheckBox、RadioButton、TextBox等,這樣一來,咱們就能製做出更加豐富的UI,代碼以下
<ListBox> <CheckBox x:Name="ckBoxTim" Content="Tim"/> <CheckBox x:Name="ckBoxTom" Content="Tom"/> <CheckBox x:Name="ckBoxSimple" Content="Simple"/> <Button x:Name="Mess" Content="Mess"/> <Button x:Name="Ownen" Content="Ownen"/> <Button x:Name="Victor" Content="Victor"/> </ListBox>
運行效果以下
表面看上去是ListBox直接包含了一些CheckBox和Button,實際並不是這樣。咱們爲Ownen這個按鈕添加一個Click事件,看看它的父容器是什麼。
private void Ownen_Click(object sender, RoutedEventArgs e) { var invoker = sender as Button; var parent = VisualTreeHelper.GetParent( VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(invoker))); MessageBox.Show(parent.GetType().ToString()); }
VisualTreeHelper類是幫助咱們在這顆由可視化元素構成的樹上進行導航的輔助類。咱們沿着被單擊的Button一層一層向上找,找到第三層發現它是一個ListBoxItem。ListBoxItem就是ListBox對應的Container,也就是說,不管你把什麼樣的數據集合交給ListBox,他都會以這種方式進行自動包裝。
上面這個例子就是單純的爲了說明ItemsControl可以使用對應的Item Container自動包裝數據。實際工做中,除非列表裏的元素自始至終都是固定的咱們才使用這種直接把UI元素做爲ItemControl內容的方法,好比一年由十二個月、一週有七天等。大多數狀況下,UI上的列表會用於顯示動態的後臺數據,這時候咱們交給ItemsControl的就是程序邏輯中的數據而非控件了。
假設程序中定義有Person類:
public class Person { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } // ... }
而且有一個Person類型的集合:
var lstPerson = new List<Person>() { new Person() { Age = 20, Name = "Simple", ID = 1 }, new Person() { Age = 22, Name = "Tim", ID = 2 }, new Person() { Age = 22, Name = "Tom", ID = 3 }, new Person() { Age = 22, Name = "jrrey", ID = 3 } };
在主程序中有一個名爲lsbPerson的ListBox,咱們只須要這樣寫:
var lstPerson = new List<Person>() { new Person() { Age = 20, Name = "Simple", ID = 1 }, new Person() { Age = 22, Name = "Tim", ID = 2 }, new Person() { Age = 22, Name = "Tom", ID = 3 }, new Person() { Age = 22, Name = "jrrey", ID = 3 } }; lsbPerson.DisplayMemberPath = "Name"; lsbPerson.SelectedValuePath = "ID"; lsbPerson.ItemsSource = lstPerson;
DisplayMemberPath這個屬性告訴ListBox顯示每條數據的哪一個屬性,換句話說,ListBox會去調用這個屬性的ToString()方法,把獲得的字符串放入一個TextBlock(最簡單的文本控件),而後再按前面說的辦法把TextBlock包裝進一個ListBoxItem裏。
ListBox的SelectedValuePath屬性將與其SelectedValue屬性配合使用。當你調用SelectedValue屬性是,ListBox先找到選中的Item所對應的數據對象,而後把SelectedValuePath的值看成數據對象的屬性名稱並把這個屬性的值取出來。
DisplayMemberPath和SelectedValuePath是兩個至關簡化的屬性。DisplayMemberPath只能顯示簡單的字符串,想用更加複雜的形式顯示數據須要使用DataTemplate。SelectedValuePath也只能返回單一的值,若是想進行一些複雜的操做,不妨直接使用ListBox的SelectedItem和SelectedItems屬性,這兩個屬性返回的就是數據集中的對象,獲得原始的數據對象後就職由程序員操做了。
理解了ListBox的自動包裝機制,我把所有ItemsControl對應的Item Container列在下面
Items名稱 | 對應的Item Container |
ComboBox | ComboBoxItem |
ContextMenu | MenuItem |
ListBox | ListBoxItem |
ListView | ListViewItem |
Menu | MenuItem |
StatusBar | StatusBarItem |
TabControl | TabItem |
TreeView | TreeViewItem |
顧名思義,本族控件除了具備ItemsControl的特性外,還具顯示標題的能力。
本族元素的特色以下
在本族中的元素,在UI上是其裝飾做用的。如可使用Border元素爲一些組織在一塊兒的內容加個邊框。若是須要組織在一塊兒的內容可以自由縮放,則可使用ViewBox元素。
本元素的特色以下:
本族元素以下
ButtonChrome | ClassicBorderDecorator | ListBoxChrome | SystemDropShadowChrome |
Border | InkPresenter | BulletDecorator | ViewBox |
AdornerDectorator |
這兩個控件最主要的功能就是顯示文本。TextBlock只能顯示文本,不能編輯,因此又稱靜態文本。TextBox則容許用戶編輯其中的內容。TextBlock雖然不能編輯內容,但可使用豐富的印刷級的格式控制標記顯示專業的排版效果。
TextBox不須要太多的顯示格式,因此它的內容是簡單的字符串,內容屬性爲Text。
TextBlock因爲須要操縱格式,因此內容屬性是InLines(印刷中的「行」),同時TextBlock也保留一個名爲Text的屬性,當簡單的顯示一個字符串時,可使用這個屬性。
友好的界面離不開各類圖形的搭配,Shape族元素(它們只是簡單的視覺元素,不是控件)就是專門用來在UI上繪製圖形的一類元素。這類元素沒有本身的內容,咱們可使用Fill屬性爲它們設置填充效果,還可使用Stroke屬性爲它們設置邊線效果。
本族的元素特色以下:
之因此把Panel元素放在最後是由於這一族控件實在是過重要了——全部用於UI佈局的元素都屬於這一族。
本族元素的特色以下:
對比ItemsControl和Panel元素,雖然內容均可以是多個元素,但ItemsControl強調以列表的形式來展示數據而Panel則強調對包含元素進行佈局。因此ItemsControl的內容屬性是Items和ItemsSource而Panel的內容屬性名爲Children。
本族元素以下所示
Canvas | DockPanel | Grid | TabPanel |
ToolBarOverflowPanel | StackPanel | ToolBarPanel | UniformGrid |
VirtualizingPanel | VirtualizingStackPanel | WrapPanel |