上一篇高仿QQ即時聊天軟件開發系列之二登陸窗口界面寫了一個大概的佈局和原理html
這一篇詳細說下拉框的實現原理ide
先上最終效果圖佈局
一開始其實只是想給下拉框加一個placeholder效果,讓下拉框在未選擇未輸入時顯示一個提示字符串。因爲Background對ComboBox無效,因此直接經過Background來實現是不行了。須要從新寫ComboBox的模板,也就是Template,自定義一個模板來實現這個結果。又看了一下QQ的下拉框,這玩意不自定義也難以實現,因此就乾脆自定義了。post
先上代碼,先是ComboBox,再是ComboBoxToggleButton,最後是ComboBoxItem動畫
1 <Style TargetType="{x:Type ComboBox}"> 2 <Setter Property="Template"> 3 <Setter.Value> 4 <ControlTemplate TargetType="ComboBox"> 5 <Grid Margin="0"> 6 <Border 7 BorderThickness="1" 8 BorderBrush="{TemplateBinding BorderBrush}" 9 CornerRadius="4,4,0,0" > 10 <Grid> 11 <Grid.ColumnDefinitions> 12 <ColumnDefinition></ColumnDefinition> 13 <ColumnDefinition Width="20" x:Name="colArrow"></ColumnDefinition> 14 </Grid.ColumnDefinitions> 15 <ToggleButton 16 Name="ToggleButton" 17 Grid.Column="1" 18 Focusable="false" 19 Style="{StaticResource ComboBoxToggleButton}" 20 IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}" 21 ClickMode="Press"> 22 </ToggleButton> 23 <TextBox x:Name="PART_EditableTextBox" 24 BorderThickness="0" 25 VerticalAlignment="Center"> 26 <TextBox.Resources> 27 <VisualBrush x:Key="tbPlaceHolder" Stretch="None" AlignmentX="Left"> 28 <VisualBrush.Visual> 29 <Label Content="CC號碼/用戶名/郵箱" Foreground="Gray" Padding="5,0,0,0"></Label> 30 </VisualBrush.Visual> 31 </VisualBrush> 32 </TextBox.Resources> 33 <TextBox.Style> 34 <Style TargetType="TextBox"> 35 <Style.Triggers> 36 <Trigger Property="Text" Value="{x:Null}"> 37 <Setter Property="Background" Value="{DynamicResource tbPlaceHolder}"> 38 </Setter> 39 </Trigger> 40 <Trigger Property="Text" Value=""> 41 <Setter Property="Background" Value="{DynamicResource tbPlaceHolder}"> 42 </Setter> 43 </Trigger> 44 </Style.Triggers> 45 </Style> 46 </TextBox.Style> 47 </TextBox> 48 </Grid> 49 </Border> 50 <Popup 51 Placement="Bottom" 52 IsOpen="{TemplateBinding IsDropDownOpen}" 53 AllowsTransparency="True" 54 Focusable="False" 55 PopupAnimation="Slide"> 56 <Grid 57 Name="DropDown" 58 Width="{TemplateBinding ActualWidth}" 59 MaxHeight="{TemplateBinding MaxDropDownHeight}"> 60 <Border 61 x:Name="DropDownBorder" 62 BorderThickness="1" 63 BorderBrush="{TemplateBinding BorderBrush}"> 64 <ScrollViewer> 65 <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" /> 66 </ScrollViewer> 67 </Border> 68 </Grid> 69 </Popup> 70 </Grid> 83 </ControlTemplate> 84 </Setter.Value> 85 </Setter> 86 </Style>
有沒有以爲頭暈?反正我是感受有點暈吧。我也不是憑空重寫出來的,是找的別人寫好的示例代碼改的,別人的示例代碼那可叫真看着暈。如今改爲這樣已經簡單不少了spa
從上到下,一點點來.net
第4行,給ComboBox從新定義控件模板,只在當前頁面生效,而後下面是模板的內容雙向綁定
第6行,Border用於實現圓角code
第10行,好戲開始。這個Grid定義了兩列,第一列用於存放下拉框選擇的文本,第二列用於存放下拉的箭頭orm
第15行,開始定義下拉的箭頭。ToggleButton是用來做爲點擊後彈出下拉框的按鈕,但具體是什麼東東?其實本身建一個wpf項目拉一個ToggleButton出來看看就明白了。它跟複選框相似,通常狀況有兩個值,一個是Checked,一個是UnChecked,但跟複選框不一樣的是,它只是一個按鈕,沒有對勾,而且你點擊它以後,若是它的狀態是Checked,那麼它就不會彈起來了,再點一下才能彈起來,想一想這個是否是跟下拉框的功能有點類似?下面的Style暫時不說,由於那個是屬於ComboBoxToggleButton那塊的
第20行,IsChecked,關鍵的地方。這個值綁定到了一個叫IsDropDownOpen的屬性,是雙向綁定,綁定的對象是使用這個控件的控件。這什麼意思呢?誰會使用這個控件呢?那其實就是ComboBox(TemplatedParent這個屬性其實我沒太理解透,可能一時間也難以理解透),而若是下拉框是打開的,那麼也就意味着isChecked會爲True,那麼ToggleButton就會是選中的狀態。反過來,若是下拉框未打開,那麼IsChecked就是未選中狀態,這個時候點擊ToggleButton,那麼IsChecked會爲True,因爲是雙向綁定,因此IsDropDownOpen也會爲True,下拉框就天然打開了。不得不說WPF真心強大。
第23行,這個TextBox是用於存放選擇後的結果的,也是用於實現可編輯下拉框的。而後嘛,placeholder效果就在這兒實現了。
第50行,Popup,用於彈出一個控件,這裏的{TemplateBinding IsDropDownOpen}其實跟上面說的TemplatedParent是一個意思,把IsOpen綁定到了應用了該控件的控件的IsDropDownOpen屬性
第65行,StackPanel用於存放下拉框中的每一項,IsItemsHost表示這個控件是否用於Item的容器(好吧其實這個地方我短期理解不了,只能這麼去理解)
OK,這個完畢,下一個,ComboBoxToggleButton
1 <Style x:Key="ComboBoxToggleButton" TargetType="ToggleButton"> 2 <Setter Property="Template"> 3 <Setter.Value> 4 <ControlTemplate TargetType="ToggleButton"> 5 <Grid> 6 <Grid.ColumnDefinitions> 7 <ColumnDefinition /> 8 </Grid.ColumnDefinitions> 9 <Path 10 Panel.ZIndex="1" 11 x:Name="Arrow" 12 Grid.Column="1" 13 Fill="#B1B1B1" 14 Stroke="#B1B1B1" 15 HorizontalAlignment="Center" 16 VerticalAlignment="Center" 17 Data="M0,0L3,3 6,0z"> 18 <Path.RenderTransform> 19 <ScaleTransform x:Name="stfArrow" CenterY="2"></ScaleTransform> 20 </Path.RenderTransform> 21 </Path> 22 <TextBlock Panel.ZIndex="0"></TextBlock> 23 </Grid> 37 <!--</Grid>--> 38 <ControlTemplate.Triggers> 45 <Trigger Property="IsMouseOver" Value="True"> 46 <Setter TargetName="Arrow" Property="Fill" Value="Black"></Setter> 47 <Setter TargetName="Arrow" Property="Stroke" Value="Black"></Setter> 48 </Trigger> 49 <EventTrigger RoutedEvent="Checked"> 50 <EventTrigger.Actions> 51 <BeginStoryboard> 52 <Storyboard> 53 <DoubleAnimation 54 Duration="0:0:0.2" 55 Storyboard.TargetName="stfArrow" 56 Storyboard.TargetProperty="ScaleY" From="1" 57 To="-1" /> 58 </Storyboard> 59 </BeginStoryboard> 60 </EventTrigger.Actions> 61 </EventTrigger> 62 <EventTrigger RoutedEvent="Unchecked"> 63 <EventTrigger.Actions> 64 <BeginStoryboard> 65 <Storyboard> 66 <DoubleAnimation 67 Duration="0:0:0.2" 68 Storyboard.TargetName="stfArrow" 69 Storyboard.TargetProperty="ScaleY" From="-1" 70 To="1" /> 71 <DoubleAnimation 72 Duration="0:0:0.2" 73 Storyboard.TargetName="stfArrow" 74 Storyboard.TargetProperty="ScaleY" From="-1" 75 To="1" /> 76 </Storyboard> 77 </BeginStoryboard> 78 </EventTrigger.Actions> 79 </EventTrigger> 80 </ControlTemplate.Triggers> 81 </ControlTemplate> 82 </Setter.Value> 83 </Setter> 84 </Style>
不少重複的技術點就跳過
第9行,Path控件用於畫出點擊下拉框時的那個向下的箭頭,其中的Data可能比較難以理解,推薦一篇文章:http://blog.csdn.net/johnsuna/article/details/1885597,我也是在這兒看懂的。
第18行,RenderTransform中的ScaleTransform用於旋轉這個Path控件也就是箭頭,ScaleTransform有一個屬性ScaleY,改成-1時就會180度翻轉,能夠實現當下拉框被打開時箭頭朝上的效果
第22行,呃··怎麼不記得有這一行,可能沒什麼用吧,忽略掉
第45行,註冊鼠標通過觸發器,通過時把箭頭變成黑色
第49行,註冊Checked事件觸發器,BeginStoryboard開始一段動畫,Storyboard建立一個動畫,DoubleAnimation建立兩個值之間的過渡,TargetName指定給哪一個控件應用這個過渡,TargetProperty指定給哪一個屬性應用,From和To就不用說了。下面的UnChecked跟上面相似。這個主要用於實現點擊箭頭時的翻轉動畫。
OK,下一部分
1 <Style TargetType="{x:Type ComboBoxItem}"> 2 <Setter Property="RenderTransform"> 3 <Setter.Value> 4 <ScaleTransform x:Name="stItem"></ScaleTransform> 5 </Setter.Value> 6 </Setter> 7 <Setter Property="Template"> 8 <Setter.Value> 9 <ControlTemplate TargetType="ComboBoxItem"> 10 <Grid Background="White" x:Name="spItem" Height="23"> 11 <Grid.ColumnDefinitions> 12 <ColumnDefinition x:Name="colImage" Width="20"></ColumnDefinition> 13 <ColumnDefinition></ColumnDefinition> 14 <ColumnDefinition Width="30"></ColumnDefinition> 15 </Grid.ColumnDefinitions> 16 <Image Grid.Column="0" Margin="0,1" Source="{Binding Image}"></Image> 17 <Label Grid.Column="1" HorizontalAlignment="Stretch" Content="{Binding CC}" Name="lblItem" VerticalContentAlignment="Center" FontSize="11" HorizontalContentAlignment="Left" Margin="0,1"> 18 </Label> 19 <Image Cursor="Hand" ToolTip="刪除該帳號" x:Name="delImage" Visibility="Collapsed" Margin="10,0" Grid.Column="2" Width="9" HorizontalAlignment="Right" Source="Resources/images/deleteAccountNormal.png"></Image> 20 </Grid> 21 <ControlTemplate.Triggers> 22 <EventTrigger RoutedEvent="ComboBoxItem.MouseEnter" SourceName="spItem"> 23 <EventTrigger.Actions> 24 <BeginStoryboard> 25 <Storyboard> 26 <DoubleAnimation Storyboard.TargetName="spItem" Storyboard.TargetProperty="(Height)" From="23" To="40" Duration="0:0:0.20"></DoubleAnimation> 27 <DoubleAnimation Storyboard.TargetName="lblItem" Storyboard.TargetProperty="FontSize" From="11" To="14" Duration="0:0:0.20"></DoubleAnimation> 28 </Storyboard> 29 </BeginStoryboard> 30 </EventTrigger.Actions> 31 </EventTrigger> 32 <EventTrigger RoutedEvent="ComboBoxItem.MouseLeave" SourceName="spItem"> 33 <EventTrigger.Actions> 34 <BeginStoryboard> 35 <Storyboard> 36 <DoubleAnimation Storyboard.TargetName="spItem" Storyboard.TargetProperty="(Height)" From="40" To="23" Duration="0:0:0.20"></DoubleAnimation> 37 <DoubleAnimation Storyboard.TargetName="lblItem" Storyboard.TargetProperty="FontSize" From="14" To="11" Duration="0:0:0.20"></DoubleAnimation> 38 </Storyboard> 39 </BeginStoryboard> 40 </EventTrigger.Actions> 41 </EventTrigger> 42 <Trigger Property="IsMouseOver" Value="true"> 44 <Setter TargetName="spItem" Property="Background" Value="#378FCF"></Setter> 45 <Setter TargetName="lblItem" Property="Foreground" Value="White"></Setter> 46 <Setter TargetName="colImage" Property="Width" Value="40"></Setter> 47 <Setter TargetName="delImage" Property="Visibility" Value="Visible"></Setter> 48 </Trigger> 49 </ControlTemplate.Triggers> 50 </ControlTemplate> 51 </Setter.Value> 52 </Setter> 53 </Style>
終於到最後一個地方了,上面的代碼定義了下拉框中的每一項的模板
第10行,定義了一個Grid,三列,第一列是圖片,也就是用戶頭像,第二列是用戶名,第三列是刪除用戶的圖標
而後其實下面的就是一堆動畫,這個跟上一部分的動畫原理相似的。
這些模板定義好以後,直接拖個ComboBox出來就好了,不須要任何設置
這篇文章確定會有技術錯誤,畢竟我對WPF並非太熟,若是有的話請指正