摘要
要開發一款優秀的application,控件肯定是必不可少的,uwp就為開發者提供了各種各樣的系統控件,AutoSuggestBox就是uwp極具特色的控件之一,也是相對於之前win8.1的uap較新的控件,今天我們就來談談AutoSuggestBox的基本用法及其自定義UI方法。
A Simplest Sample
話不多說,先來個小Demo。
<!-- MainPage.xaml --> <Page x:Class="AutoSuggestBoxSample.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:AutoSuggestBoxSample" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <StackPanel VerticalAlignment="Center" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <AutoSuggestBox x:Name="autoSuggestBox" PlaceholderText="Type a control name" TextChanged="AutoSuggestBox_TextChanged" QueryIcon="Find" QuerySubmitted="AutoSuggestBox_QuerySubmitted" Width="300" HorizontalAlignment="Center"/> <TextBlock x:Name="textBlock" HorizontalAlignment="Center" Margin="10"/> </StackPanel> </Page>
//MainPage.xaml.cs using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; namespace AutoSuggestBoxSample { /// <summary> /// An empty page that can be used on its own or navigated to within a Frame. /// </summary> public sealed partial class MainPage : Page { public MainPage() { suggestions = new ObservableCollection<string>(); this.InitializeComponent(); } private ObservableCollection<String> suggestions; private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { if (args.ChosenSuggestion != null) textBlock.Text = args.ChosenSuggestion.ToString(); else textBlock.Text = sender.Text; } private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { suggestions.Clear(); suggestions.Add(sender.Text + "1"); suggestions.Add(sender.Text + "2"); sender.ItemsSource = suggestions; } } }
運行結果如圖所示:
Understand the code above
上面的代碼定義了一個最簡單的AutoSuggestBox,先看xaml部分,在AutoSuggestBox標簽部分,定義了PlaceHolderText,就是在沒有輸入的時候顯示的Hint。QueryIcon是控件最右邊的搜索按鈕,等號右邊的Find是系統自帶的icon,當然也可以選擇其它icon,后面我們會說,這個屬性可以不設置,這樣的話,AutoSuggestBox則不會顯示最右邊的搜索按鈕。而TextChanged和QuerySubmitted對應的是兩個事件,事件的定義在MainPage.xaml.cs里面。這里注意一下,除了這兩個事件外,還有一個會經常用到並且容易理解錯誤的事件SuggestionChosen,每次在List選擇之后,會自動觸發這個事件,除此之外,QuerySubmitted也會觸發一次,所以在使用過程中,搞清楚兩者之間的關系是必要的。
What if the data set is something else?
細心的讀者可能會發現,在上面的例子中,我們僅僅是顯示了一個字符串列表,可是在實際開發中,誰又能保證suggestions只能是字符串呢?這當然是不可能的。幸運的是,AutoSuggestBox的Suggestion List其實就是一個ListView,我們可以通過定義ItemTemplate來使ListViewItem展示不同的數據,對AutoSuggestBox來說,當然也是可行的。舉例來說,當數據集為SampleSuggest對象:
public class SampleSuggest { public int Index { set; get; } public String Value { set; get; } }
若要正常顯示ListViewItem,則需要在XAML里面定義AutoSuggestBox的ItemTemplate屬性,如下所示:
<AutoSuggestBox x:Name="autoSuggestBox" PlaceholderText="Type a control name" TextChanged="AutoSuggestBox_TextChanged" QueryIcon="Find" QuerySubmitted="AutoSuggestBox_QuerySubmitted" Width="300" HorizontalAlignment="Center"> <AutoSuggestBox.ItemTemplate> <DataTemplate x:DataType="local:SampleSuggest"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{x:Bind Path=Index}"/> <TextBlock Margin="10,0,0,0" Text="{x:Bind Path=Value}"/> </StackPanel> </DataTemplate> </AutoSuggestBox.ItemTemplate> </AutoSuggestBox>
這樣ListViewItem就能按照我們的定義顯示了:
但是,當我們點擊列表時,TextBox里面顯示的卻是AutoSuggestBoxSample.SampleSuggest,這顯然不是我們想要的。因為在選擇ListViewItem的時候,系統會自動調用選中數據的ToString()方法,將得到的字符串顯示在TextBox中。為了放着這種情況發生,我們可以選擇重寫SampleSuggest的ToString()方法,或者為AutoSuggestBox添加 SuggestionChosen事件,在事件中根據Suggestion決定TextBox中顯示的內容,在這里,文章將使用第二種方法。
首先,在AutuSuggestBox的XAML定義中,添加以下屬性:
SuggestionChosen="AutoSuggestBox_SuggestionChosen"
然后,在相應的cs文件中添加如下代碼:
private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { SampleSuggest suggest = args.SelectedItem as SampleSuggest; if (suggest == null) return; sender.Text = suggest.Value; }
這樣,一個AutoSuggestBox的功能就基本完成了!!
[Ignorable]
A story which is not interesting
這里,請允許小編插入一段不是很有趣的故事……不喜歡的讀者可以直接跳過……
小編接到任務,需要開發一個這樣的控件:
小編看到,立刻就開心了,這不是AutoSuggestBox嘛!!簡單!!
於是,小編很興奮地寫下了以下代碼:
<AutoSuggestBox x:Name="autoSuggestBox" PlaceholderText="anything to search?" BorderThickness="0" Background="#232323" Foreground="White" QueryIcon="Find" VerticalContentAlignment="Center" Width="500" Height="50" HorizontalAlignment="Center"/>
結果出來的結果是這個樣子的……
此時,小編的心情是這樣的……
於是乎,小編便下定決心要改改這個AutoSuggestBox,就有了下邊的內容……
Style the AutoSuggestBox
通過以上的程序,我們實現了AutoSuggestBox的基本功能,但是這樣一個控件一般都不會滿足我們程序開發的需求,因為它長得似乎不那么漂亮,或者說,不那么滿足我們的需求。所以,我們要自己定義AutoSuggestBox的style,讓它看上去更有范兒!
在VS designer的Document Outline面板中右鍵點擊定義的AutoSuggestBox -> Edit Template -> Edit a Copy…,則會在XAML里生成AutoSuggestBox的Style。
通過觀察以下由VS生成的Style代碼,我們可以知道,這個神奇的AutoSuggestBox其實就是由一個TextBox和一個Popup構成的:
<Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="AutoSuggestBox"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="Orientation"> <VisualState x:Name="Landscape"/> <VisualState x:Name="Portrait"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <TextBox x:Name="TextBox" ScrollViewer.BringIntoViewOnFocusChange="False" DesiredCandidateWindowAlignment="BottomEdge" Header="{TemplateBinding Header}" Margin="0" PlaceholderText="{TemplateBinding PlaceholderText}" Style="{TemplateBinding TextBoxStyle}" Width="{TemplateBinding Width}" Canvas.ZIndex="0"/> <Popup x:Name="SuggestionsPopup"> <Border x:Name="SuggestionsContainer"> <Border.RenderTransform> <TranslateTransform x:Name="UpwardTransform"/> </Border.RenderTransform> <ListView x:Name="SuggestionsList" BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}" BorderThickness="{ThemeResource AutoSuggestListBorderThemeThickness}" Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" IsItemClickEnabled="True" ItemTemplate="{TemplateBinding ItemTemplate}" ItemContainerStyle="{TemplateBinding ItemContainerStyle}" ItemTemplateSelector="{TemplateBinding ItemTemplateSelector}" MaxHeight="{ThemeResource AutoSuggestListMaxHeight}" Margin="{ThemeResource AutoSuggestListMargin}"> <ListView.ItemContainerTransitions> <TransitionCollection/> </ListView.ItemContainerTransitions> </ListView> </Border> </Popup> </Grid> </ControlTemplate> </Setter.Value> </Setter>
那么AutoSuggestBox的外觀,基本就是由其中的TextBox決定的,其中對應的就是一個AutoSuggestBoxTextBoxStyle:
<Style x:Key="AutoSuggestBoxTextBoxStyle" TargetType="TextBox"> <Setter Property="MinWidth" Value="{ThemeResource TextControlThemeMinWidth}"/> <Setter Property="MinHeight" Value="{ThemeResource TextControlThemeMinHeight}"/> <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}"/> <Setter Property="Background" Value="#ffdddddd"/> <Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundChromeDisabledLowBrush}"/> <Setter Property="SelectionHighlightColor" Value="{ThemeResource SystemControlHighlightAccentBrush}"/> <Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}"/> <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/> <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/> <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False"/> <Setter Property="Padding" Value="{ThemeResource TextControlThemePadding}"/> <Setter Property="Template"> ……… </Style>
有了這個,我們就可以輕松定義AutoSuggestBox的外觀啦!我們再看這個AutoSuggestBoxTextBoxStyle,它主要由以下幾個部分構成:ContentElement,它是一個ScrollViewer,就是我們TextBox中的輸入部分;PlacehoderTextContentPresenter,通過它的名字,我們可以知道它就是顯示PlacehoderText的部分;還有DeleteButton和QueryButton兩個按鈕,分別對應控件中的刪除按鈕和查詢按鈕:
<Border x:Name="BackgroundElement" Background="{TemplateBinding Background}" Grid.ColumnSpan="3" Margin="{TemplateBinding BorderThickness}" Opacity="{ThemeResource TextControlBackgroundRestOpacity}" Grid.Row="1" Grid.RowSpan="1"/> <Border x:Name="BorderElement" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Grid.ColumnSpan="3" Grid.Row="1" Grid.RowSpan="1"/> <ContentPresenter x:Name="HeaderContentPresenter" Grid.ColumnSpan="3" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Foreground="{ThemeResource SystemControlForegroundBaseHighBrush}" FontWeight="Normal" Margin="0,0,0,8" Grid.Row="0" TextWrapping="Wrap" Visibility="Collapsed" x:DeferLoadStrategy="Lazy"/> <ScrollViewer x:Name="ContentElement" AutomationProperties.AccessibilityView="Raw" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled"/> <ContentControl x:Name="PlaceholderTextContentPresenter" Grid.ColumnSpan="3" Content="{TemplateBinding PlaceholderText}" Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" IsHitTestVisible="False" IsTabStop="False" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1"/> <Button x:Name="DeleteButton" BorderThickness="{TemplateBinding BorderThickness}" Grid.Column="1" FontSize="{TemplateBinding FontSize}" IsTabStop="False" Margin="{ThemeResource HelperButtonThemePadding}" MinWidth="34" Grid.Row="1" Style="{StaticResource DeleteButtonStyle}" Visibility="Collapsed" VerticalAlignment="Stretch"/> <Button x:Name="QueryButton" BorderThickness="{TemplateBinding BorderThickness}" Grid.Column="2" FontSize="{TemplateBinding FontSize}" IsTabStop="False" Margin="{ThemeResource HelperButtonThemePadding}" MinWidth="34" Grid.Row="1" Style="{StaticResource QueryButtonStyle}" VerticalAlignment="Stretch"/>
這下就清楚很多了,那么我們就開始改造我們的AutoSuggestBox吧!
首先是Foreground和Background,將AutoSuggestBoxTextBoxStyle里以下代碼替換為:
<Setter Property="Foreground" Value="White"/> <Setter Property="Background" Value="#232323"/>
然后,去掉Border:
<Setter Property="BorderThickness" Value="0"/>
改變DeleteButton的VisualState,主要是PointOver和Pressed兩種狀態:
<VisualState x:Name="PointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BorderElement"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="GlyphElement"> <DiscreteObjectKeyFrame KeyTime="0" Value="Goldenrod"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BorderElement"> <DiscreteObjectKeyFrame KeyTime="0" Value="Goldenrod"/> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="GlyphElement"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltChromeWhiteBrush}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState>
接着去Style我們的QueryButton,首先是QueryIcon:
<AutoSuggestBox x:Name="autoSuggestBox" PlaceholderText="anything to search?" BorderThickness="0" Background="#232323" Foreground="White" VerticalContentAlignment="Center" Width="500" Height="50" HorizontalAlignment="Center" Style="{StaticResource AutoSuggestBoxStyle1}"> <AutoSuggestBox.QueryIcon> <BitmapIcon UriSource="ms-appx:Assets/actionbar_searchicon.png"/> </AutoSuggestBox.QueryIcon> </AutoSuggestBox>
然后是Button的大小,從Style里面去更改:
<Button x:Name="QueryButton" Width="50" Height="50" BorderThickness="{TemplateBinding BorderThickness}" Grid.Column="2" FontSize="{TemplateBinding FontSize}" IsTabStop="False" Margin="{ThemeResource HelperButtonThemePadding}" MinWidth="34" Grid.Row="1" Style="{StaticResource QueryButtonStyle}" VerticalAlignment="Stretch"/>
修改QueryButton的Background,注意這里並不能直接在Button里添加Background屬性,而是在QueryButtonStyle里的ContentPresenter里面修改,同時把Margin設置為0,以便於能填充全部背景:
<ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" Background="Goldenrod" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
因為調整了QueryButton的高度,所以控件整體高度會變高,為了讓TextBox里面的Text能夠垂直居中,需要在AutoSuggestBoxTextBoxStyle中,將顯示正文的ContentElement和顯示hint的PlaceholderTextContentPresenter的VerticalAlignment設為Center,同時設置FontSize:
<ScrollViewer x:Name="ContentElement" AutomationProperties.AccessibilityView="Raw" FontSize="20" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1" VerticalAlignment="Center" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled"/> <ContentControl x:Name="PlaceholderTextContentPresenter" FontSize="20" Grid.ColumnSpan="3" Content="{TemplateBinding PlaceholderText}" Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" IsHitTestVisible="False" IsTabStop="False" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1" VerticalAlignment="Center"/>
程序運行結果如下圖所示:
和目標發現,好像還有什么不一樣的……對!是Background!可是我們已經設置過Background啦……這是為什么呢?通過觀察Style,我們發現,有許多VisualState的Background都有Transparent的屬性,也就是說,AutoSuggestBox顯示出來的Background和它的父控件式相關的。簡單起見,我們直接為AutoSuggestBox添加一個背景色相同的Grid就好:
<Grid Width="500" Background="#232323" VerticalAlignment="Center" HorizontalAlignment="Center"> <AutoSuggestBox x:Name="autoSuggestBox" PlaceholderText="anything to search?" Style="{StaticResource AutoSuggestBoxStyle1}"> <AutoSuggestBox.QueryIcon> <BitmapIcon UriSource="ms-appx:Assets/actionbar_searchicon.png"/> </AutoSuggestBox.QueryIcon> </AutoSuggestBox> </Grid>
再執行程序,發現和目標就相同了!!
Consulting
本文概括地介紹了AutoSuggestBox的使用方法以及簡單自定義Style,希望初次使用該控件的開發者能夠從中得到些許幫助並交流一些開發經驗,謝謝!