[Silverlight入门系列]用MVVM实现带下拉框的搜索框功能
带下拉框的搜索框,英文就是Searchbox with dropdown options,平时暴一看就是一个搜索框,当你输入东西就有搜索选项出来,很符合简洁的UI用户体验。类似下面的效果:
一个是jQuery的效果,点击获得在线演示地址:
一个是时下热门的新浪微博的:
今天我们在Silverlight下面也实现一个,比较丑陋没有做美工啥的,用MVVM实现的:
主要原理就是用到Popup控件,让它显示隐藏即可,里面的RadioButton要MVVM兼容。来看看XAML代码:
1: <UserControl x:Class="CustomSearch.MainPage"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:YourNamespace="clr-namespace:CustomSearch" mc:Ignorable="d"
6: d:DesignHeight="300" d:DesignWidth="400">
7: <UserControl.Resources>
8: <YourNamespace:EnumBoolConverter x:Key="ConvertEnum" />
9: <Storyboard x:Key="scalePopupIn">
10: <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="filterBorder"
11: Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
12: <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
13: <EasingDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1"/>
14: </DoubleAnimationUsingKeyFrames>
15: </Storyboard>
16: </UserControl.Resources>
17: <Grid x:Name="LayoutRoot" Background="White">
18: <StackPanel>
19: <TextBox Height="36" TextChanged="textBox1_TextChanged" LostFocus="textBox1_LostFocus" GotFocus="textBox1_GotFocus" HorizontalAlignment="Left" Name="textBox1" VerticalAlignment="Top" Width="163" />
20: <Popup x:Name="filterPopup" Visibility="Collapsed">
21: <Border x:Name="filterBorder" BorderBrush="Gray" BorderThickness="1,0,1,1" Width="163" Height="Auto">
22: <Border.RenderTransform>
23: <TransformGroup>
24: <ScaleTransform />
25: </TransformGroup>
26: </Border.RenderTransform>
27: <StackPanel Margin="5,0,0,0" Width="158" Height="Auto">
28: <RadioButton Margin="0,5" ClickMode="Hover" GroupName="filters"
29: IsChecked="{Binding Path=UserFilterOption, Mode=TwoWay, Converter={StaticResource ConvertEnum},
30: ConverterParameter=Person}" Content="Person"/>
31: <RadioButton Margin="0,5" ClickMode="Hover" GroupName="filters"
32: IsChecked="{Binding Path=UserFilterOption, Mode=TwoWay, Converter={StaticResource ConvertEnum},
33: ConverterParameter=Grade}" Content="Grade"/>
34: <RadioButton Margin="0,5" ClickMode="Hover" GroupName="filters"
35: IsChecked="{Binding Path=UserFilterOption, Mode=TwoWay, Converter={StaticResource ConvertEnum},
36: ConverterParameter=Both}" Content="Both"/>
37: </StackPanel>
38: </Border>
39: </Popup>
40: </StackPanel>
41:
42: </Grid>
43: </UserControl>
重点圈出好理解:
Xaml.cs代码:
1: using System;
2: using System.Windows;
3: using System.Windows.Controls;
4: using System.Windows.Media.Animation;
5:
6: namespace CustomSearch
7: {
8: public partial class MainPage : UserControl
9: {
10: public MainPage()
11: {
12: InitializeComponent();
13: this.DataContext = new ViewModel();
14:
15: }
16:
17: private void textBox1_GotFocus(object sender, RoutedEventArgs e)
18: {
19: TogglePopup(true);
20: }
21:
22: private void textBox1_LostFocus(object sender, RoutedEventArgs e)
23: {
24: TogglePopup(false);
25: }
26:
27: private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
28: {
29: TogglePopup(this.textBox1.Text == String.Empty);
30: }
31:
32:
33: private void TogglePopup(bool show)
34: {
35: this.filterPopup.Visibility = show ? Visibility.Visible : Visibility.Collapsed;
36: this.filterPopup.IsOpen = show;
37: if (show)
38: {
39: var story = Resources["scalePopupIn" ] as Storyboard;
40: if (story != null) story.Begin();
41: }
42: }
43: }
44: }
ViewModel代码:
1: #region "Using Namespace"
2:
3: using System;
4: using System.ComponentModel;
5: using System.Windows.Input;
6:
7: #endregion
8:
9: namespace CustomSearch
10: {
11: public enum FilterOption { Person = 0, Grade = 1, Both = 2 }
12:
13: public class ViewModel : INotifyPropertyChanged
14: {
15: public FilterOption UserFilterOption
16: {
17: get { return _userOption; }
18: set
19: {
20: if (_userOption != value)
21: {
22: _userOption = value;
23:
24: if(PropertyChanged != null)
25: PropertyChanged(this, new PropertyChangedEventArgs("UserFilterOption"));
26: }
27: }
28: }
29: private FilterOption _userOption = FilterOption.Person;
30:
31: public event PropertyChangedEventHandler PropertyChanged;
32: }
33:
34: public class DelegateCommand : ICommand
35: {
36:
37: /// <summary>
38:
39: /// Occurs when changes occur that affect whether the command should execute.
40:
41: /// </summary>
42:
43: public event EventHandler CanExecuteChanged;
44:
45:
46:
47: Func<object, bool> canExecute;
48:
49: Action<object> executeAction;
50:
51: bool canExecuteCache;
52:
53:
54:
55: /// <summary>
56:
57: /// Initializes a new instance of the <see cref="DelegateCommand"/> class.
58:
59: /// </summary>
60:
61: /// <param name="executeAction">The execute action.</param>
62:
63: /// <param name="canExecute">The can execute.</param>
64:
65: public DelegateCommand(Action<object> executeAction,
66:
67: Func<object, bool> canExecute)
68: {
69:
70: this.executeAction = executeAction;
71:
72: this.canExecute = canExecute;
73:
74: }
75:
76:
77:
78: #region ICommand Members
79:
80: /// <summary>
81:
82: /// Defines the method that determines whether the command
83:
84: /// can execute in its current state.
85:
86: /// </summary>
87:
88: /// <param name="parameter">
89:
90: /// Data used by the command.
91:
92: /// If the command does not require data to be passed,
93:
94: /// this object can be set to null.
95:
96: /// </param>
97:
98: /// <returns>
99:
100: /// true if this command can be executed; otherwise, false.
101:
102: /// </returns>
103:
104: public bool CanExecute(object parameter)
105: {
106:
107: bool tempCanExecute = canExecute(parameter);
108:
109:
110:
111: if (canExecuteCache != tempCanExecute)
112: {
113:
114: canExecuteCache = tempCanExecute;
115:
116: if (CanExecuteChanged != null)
117: {
118:
119: CanExecuteChanged(this, new EventArgs());
120:
121: }
122:
123: }
124:
125:
126:
127: return canExecuteCache;
128:
129: }
130:
131:
132:
133: /// <summary>
134:
135: /// Defines the method to be called when the command is invoked.
136:
137: /// </summary>
138:
139: /// <param name="parameter">
140:
141: /// Data used by the command.
142:
143: /// If the command does not require data to be passed,
144:
145: /// this object can be set to null.
146:
147: /// </param>
148:
149: public void Execute(object parameter)
150: {
151:
152: executeAction(parameter);
153:
154: }
155:
156: #endregion
157:
158: }
159: }
EnumBoolConverter.cs代码(RadioButton MVVM Binding绑定用到):
1: #region "Using Namespace"
2:
3: using System;
4: using System.Net;
5: using System.Windows;
6: using System.Windows.Controls;
7: using System.Windows.Data;
8: using System.Windows.Documents;
9: using System.Windows.Ink;
10: using System.Windows.Input;
11: using System.Windows.Media;
12: using System.Windows.Media.Animation;
13: using System.Windows.Shapes;
14:
15: #endregion
16:
17: namespace CustomSearch
18: {
19: public class EnumBoolConverter : IValueConverter
20: {
21: #region Methods
22: public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
23: {
24: if (value == null || parameter == null)
25: return value;
26:
27: return value.ToString() == parameter.ToString();
28: }
29:
30: public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
31: {
32: if (value == null || parameter == null)
33: return value;
34:
35: return Enum.Parse(targetType, (String)parameter, true);
36: }
37: #endregion Methods
38: }
39: }
这样的话搜索就是一个Command在ViewModel实现,用户下拉框选择的选项在UserFilterOption也在ViewModel中,非常方便。
源码里面还有一个展开动画,如果你不要可以删除。
我自己用的是套在这个搜索框里面的,组合为一个控件,点击以后自动展开,整体为一个控件。如图:
希望对你有用。

