概述 之前聽說很多大神的成長之路,幾乎都有個習慣——寫博文,可以有效的對項目進行總結、從而提高開發的經驗。所以初學WPF的我想試試,順便提高一下小學作文的能力。O(∩_∩)O哈哈~ 讀萬卷書不如行萬里路,實踐是最好的導師!最近在學習WPF,也嘗試著做了一些小Demo,但並沒有真正的使用WPF的開發模 ...
概述
之前聽說很多大神的成長之路,幾乎都有個習慣——寫博文,可以有效的對項目進行總結、從而提高開發的經驗。所以初學WPF的我想試試,順便提高一下小學作文的能力。O(∩_∩)O哈哈~
讀萬卷書不如行萬里路,實踐是最好的導師!最近在學習WPF,也嘗試著做了一些小Demo,但並沒有真正的使用WPF的開發模式——數據推動UI,最近偶然的機會也是工作需求,就嘗試著寫了一個簡易的手風琴控制項,
因為初學的原因,可能在邏輯上,代碼上有些欠缺,還請大神們多多指點,在這裡先感謝各位!(下麵是效果圖)
思路
剖析效果,拆分控制項,我當時的想法是Grid容器+基本控制項進行手繪、最後在添加一些展開收起的動畫,想法很美好,現實很殘酷!後來詢問了一下大神,給出的建議是ListBox+Expander實現,就這樣在大神們的指點下開始了。
數據結構
ExpanderClass類:標題圖片、標題名稱、按鈕圖片集合 ; ImgUrlClass類:按鈕圖片。
public class DataSourceClass { /// <summary> /// 數據源 /// </summary> public static List<ExpanderClass> GetDateSource() { List<ExpanderClass> exLst = new List<ExpanderClass>(); List<ImgUrlClass> lst = new List<ImgUrlClass>(); for (int i = 1; i < 10; i++) { lst.Add(new ImgUrlClass() { ImageUrl = string.Format("Images/h{0}.png", i) }); } exLst.Add(new ExpanderClass() { Title = "我是第一行哦!", ImgUrl = "Images/Left.png", ImgLst = lst }); lst = new List<ImgUrlClass>(); for (int i = 1; i < 10; i++) { lst.Add(new ImgUrlClass() { ImageUrl = string.Format("Images/h1{0}.png", i) }); } exLst.Add(new ExpanderClass() { Title = "我是第二行哦!!", ImgUrl = "Images/Right.png", ImgLst = lst }); lst = new List<ImgUrlClass>(); for (int i = 1; i < 10; i++) { lst.Add(new ImgUrlClass() { ImageUrl = "Images/h10.png" }); } exLst.Add(new ExpanderClass() { Title = "我是第三行哦!!!", ImgUrl = "Images/Up.png", ImgLst = lst }); return exLst; } } public class ExpanderClass { /// <summary> /// 標題 /// </summary> public string Title { get; set; } /// <summary> /// 標題圖片 /// </summary> public string ImgUrl { get; set; } /// <summary> /// 按鈕圖片集合 /// </summary> public List<ImgUrlClass> ImgLst { get; set; } public ExpanderClass() { Title = string.Empty; ImgUrl = string.Empty; } } public class ImgUrlClass { public ImgUrlClass() { ImageUrl = string.Empty; } /// <summary> /// 按鈕圖片 /// </summary> public string ImageUrl { get; set; } }
Expander
首先添加一個WPF用戶控制項AccordionControl 然後添加一個Expander控制項,然後在裡面添加個Image後運行看一下效果
<Expander x:Name="expander" Header="Expander" > <Grid Background="#FFE5E5E5"> <Image Source="Image/Button/h1.png" /> </Grid> </Expander>
這樣一個展開收起的Expander 就OK了,接下里根據設計需求分析,我們需要修改Expander的 Header、Content 兩部分。
Header(頭部):修改背景色、添加標題圖片、標題。
Content(內容):多個圖片按鈕組成(這裡需要思考一下,結果集是多張圖片,所以需要迴圈載入圖片,所以這裡使用了ListBox控制項)。
<Expander> <Expander.Header> <StackPanel Orientation="Horizontal" Background="#3399ff" > <Image Source="Image/Button/h1.png" Height="16"/> <TextBlock Text="我是標題哦" VerticalAlignment="Center"/> </StackPanel> </Expander.Header> <Expander.Content > <ListBox ItemsSource={binding}> <ListBox.ItemTemplate> <DataTemplate> <Image Source="Image/Button/h1.png" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Expander.Content> </Expander>
綁定數據源運行後
距離我們的設計是不是進了一步,我們需要思考一下 設計圖給出的內容中按鈕是 橫向 展示,並且根據寬度自動換行? WPF中我知道的可以實現此功能的控制項 WrapPanel、TextBlock,WrapPanel更好用,我們來修改下ListBox中ItemsPanel模板,並且將ListBox水平滾動條取消。
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel Orientation="Horizontal" Background="Transparent" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Image Source="{Binding ImageUrl}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
運行,效果不錯吧。
這樣一個簡單的Expander完成了,實際工作中我們可能需要多個這樣的Expander組合使用,很簡單,ListBox嵌套Expander 進去就可以了。
完善功能
運行Demo,可能會發現一個問題,點擊第一行的Expander時候 其它的並沒有收起,怎麼實現點擊其中一個讓其他自動收起呢?
方案一:RadioButton; 單選的效果是RadioButton被分配到相同的組中 GroupName ,結合Demo 我們可以將Expander封裝到RadionButton中,然後給GroupName賦值 這樣就可以實現效果。
方案二:ListBox的ListBoxItem.IsSelected 是否選中; Expander中IsExpanded屬性的意思是內容視窗是否可見,當我們選擇一個ListBoxItem時將IsDelected綁定到IsExpanded中就可以實現。
最初,我是使用RadionButton實現的,後大神提出建議為何不試試ListBoxItem呢?所以我修改了源碼。
<ListBox x:Name="ItemBox" ItemsSource="{Binding}" Width="400" > <ListBox.ItemTemplate> <DataTemplate> <Expander Width="{Binding Path=Width, ElementName=ItemBox}" Tag="{Binding}" IsExpanded="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}, Path=IsSelected}" > <Expander.Header> <StackPanel Orientation="Horizontal" Background="#3399ff" Width="{Binding Path=Width, ElementName=ItemBox}" > <Image Source="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=Expander}, Path=Tag.ImgUrl}" Height="16" Width="16" VerticalAlignment="Center"/> <TextBlock VerticalAlignment="Center" Text="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=Expander}, Path=Tag.Title}" /> </StackPanel> </Expander.Header> <Expander.Content > <ListBox ItemsSource="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=Expander}, Path=Tag.ImgLst}" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel Orientation="Horizontal" HorizontalAlignment="Center" /> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Image Source="{Binding ImageUrl}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Expander.Content> </Expander> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
解析下數據綁定:
WPF基礎Binding(綁定)這裡用到兩種方式(概念上的可以參考百度這裡不再熬述):
1、ElementName指定Source:在C#代碼中可以直接把對象作為Source賦值給Binding,但是XAML無法訪問對象,只能使用Name屬性來找到對象。
Width="{Binding Path=Width, ElementName=ItemBox}"
2、RelataveSource:通過Binding的RelataveSource屬性相對的指定Source:當控制項需要關註自己的、自己容器的或者自己內部某個屬性值就需要使用這種辦法。
IsExpanded="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}, Path=IsSelected}"
* 這裡有個註意點,Expander.Header中並不能直接得到數據源 我這裡是將數據源綁定到Expander.Tag中 然後通過 RelataveSource 向上查找的方式獲取數據源
<TextBlock VerticalAlignment="Center" Text="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=Expander}, Path=Tag.Title}" />
這樣整體的數據綁定就完成了。
現在思考一下這樣個需求:當我們點擊按鈕後更改背景圖片,點擊其他按鈕後,點擊過的按鈕還原?
分析:根據需求分解兩個動作效果.
1、按鈕之間的切換並且還原樣式:這裡的效果類似於之前的Expander 之間的展開與閉合,所以這裡依然使用 RadioButton 來實現,RadionButton改變樣式成為圖片按鈕 。
2、點擊按鈕更改背景圖片:根據RadionButton的IsChecked屬性來實現,將Image 的Source 與IsChecked 關聯起來,通過轉換器根據相應的值返回不同的圖片。
<RadioButton x:Name="rdoImg" GroupName="rdoImgGroup" Cursor="Hand" Width="80" Height="80" > <RadioButton.Template> <ControlTemplate> <Image> <Image.Source> <MultiBinding Converter="{StaticResource IsDataConverter}" > <Binding Path="IsChecked" ElementName="rdoImg" /> <Binding Path="ImageUrl" /> </MultiBinding> </Image.Source> </Image> </ControlTemplate> </RadioButton.Template> </RadioButton>
MultiBinding:多番綁定
在開發的過程中遇到了一個很有意思的事情,我為了方便直接將整個實體綁定進去,然後在轉換器中進行判斷可以得到圖片進行處理,運行後但圖片沒變化。難道轉換器無效,IsChecked沒變?
帶著疑問我修改了一下,只綁定IsChecked 看看到底是否發生變化,運行,結果發生變化,圖片也變了(這裡修改了轉換器 返回一個固定圖片)。
我個人的理解 Binding 應該只針對一個屬性才有效,可是我想把圖片路徑也傳過去,這樣更好的處理,那麼MultiBinding就用到了 。
這裡需要註意一點:MultiBinding 轉換器 繼承 IMultiValueConverter 介面,Binding 繼承 IValueConverter 介面。
MultiBinding : IMultiValueConverter
Binding : IValueConverter
這樣需求就實現了,運行程式
當我們切換Expander時,發現個問題按鈕並沒有還原?
思路:在點擊Expander時,將RadioButton的IsChecked改為False就可以了,這是因為我們已經將Image的Source與RadioButton的IsChecked關聯。
所以只要將RadioButton的IsChecked 與 Expander的IsExpanded 關聯就可以。
<RadioButton x:Name="rdoImg" GroupName="rdoImgGroup" Cursor="Hand" Width="80" Height="80" IsChecked="{Binding Path=IsExpanded,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Expander},Converter={StaticResoce IsCheckedConvert},Mode=OneWay}" >
這裡需要註意一點 IsChecked 與 IsExpanded 的綁定關係是單向的 Mode=OneWay,這樣保證每次點擊Expander 返回的都是False,不然會報錯,因為RadionButton 同一組中不可能同時選中!
好了除了樣式外功能實現完畢,當然還可以繼續拓展O(∩_∩)O哈哈~!
樣式
Expande樣式模板分為三個部分:
Expander-Header:一個寫好樣式的 ToggleButton。
Expander-Content: Border是內容部分 <ContentPresenter /> 這句話千萬不要遺忘,不然什麼都不會顯示!
Expander-Triggers: 動畫效果。
*這裡有個註意點,Header 與 Content 一定要放在 StackPanel 內,不然運行後Expander會重疊。
樣式模板具體可以百度查詢,或者用Blend查看樣式源碼,這裡不再熬述。
<!--ToggleButton樣式代碼:--> <Style x:Key="ToggleButtonStyle" TargetType="{x:Type ToggleButton}"> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="Width" Value="{Binding Path=Width, ElementName=ItemBox}"/> <Setter Property="Height" Value="35" /> <Setter Property="Background" Value="{StaticResource OrangeG}" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontSize" Value="16" /> <Setter Property="Foreground" Value="White" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ToggleButton}"> <Canvas Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"> <Image Source="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=Expander}, Path=Tag.ImgUrl}" Height="16" Canvas.Left="5" Canvas.Top="8" /> <TextBlock Text="{Binding RelativeSource ={ RelativeSource Mode=FindAncestor, AncestorType=Expander}, Path=Tag.Title}" Canvas.Left="25" Canvas.Top="8" Foreground="{TemplateBinding Foreground}"/> <TextBlock Text="+" Foreground="White" Canvas.Top="6" Canvas.Right="10" FontSize="{TemplateBinding FontSize}" x:Name="txtSymbol"/> <ContentPresenter /> </Canvas> <ControlTemplate.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter Property="Background" Value="#3399ff"/> <Setter Property="Text" TargetName="txtSymbol" Value="-"/> </Trigger> <Trigger Property="IsChecked" Value="False"> <Setter Property="Background" Value="{StaticResource OrangeG}"/> <Setter Property="Text" TargetName="txtSymbol" Value="+"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--Expander樣式代碼:--> <Style x:Key="ExpanderStyle" TargetType="{x:Type Expander}"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Expander}"> <StackPanel Background="{TemplateBinding Background}" > <ToggleButton x:Name="HeaderSite" Style="{DynamicResource ToggleButtonStyle}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" /> <Border x:Name="ExpandSite" Visibility="Collapsed" Canvas.Top="40" Focusable="false" BorderThickness="1" Width="{Binding ElementName=HeaderSite,Path=Width}" HorizontalAlignment="Center" > <ContentPresenter /> </Border> </StackPanel> <ControlTemplate.Triggers> <Trigger Property="IsExpanded" Value="true"> <Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
最後調用樣式
<Expander Style="{DynamicResource ExpanderStyle}"
這樣 簡易的手風琴就完成了,但是這裡還是有個小問題,就是內容模板的高度沒有辦法拉伸,以及WrapPanel內會出現左右邊距沒有辦法居中,如果有知道解決辦法的大神們請告知謝謝!!!
結束語
第一次寫博文,終於體會其中的奧妙之處,重新梳理下思路,對自己進行一次總結,進而增加深刻的印象,這種感覺棒棒噠,在這裡感謝為我指點的大神們,同時也希望大家為我指點其中的不足之處,進而改之,讓我可以快速的成長,再次感謝各位,謝謝!!!
(PS:話說怎麼樣可以讓博文的樣式更好看一些?)
這裡是源碼:https://git.oschina.net/smile0905/AccordionClient.git