[Silverlight入门系列]用MVVM实现带下拉框的搜索框功能

带下拉框的搜索框,英文就是Searchbox with dropdown options,平时暴一看就是一个搜索框,当你输入东西就有搜索选项出来,很符合简洁的UI用户体验。类似下面的效果:

一个是jQuery的效果,点击获得在线演示地址:

uielements_search

一个是时下热门的新浪微博的:

10.113

 

今天我们在Silverlight下面也实现一个,比较丑陋没有做美工啥的,用MVVM实现的:

10.112

 

主要原理就是用到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>

重点圈出好理解:

10.115

 

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中,非常方便。

源码里面还有一个展开动画,如果你不要可以删除。

我自己用的是套在这个搜索框里面的,组合为一个控件,点击以后自动展开,整体为一个控件。如图:

10.116

希望对你有用。

posted on 2011-10-11 16:23 Mainz 阅读(816) 评论(2) 编辑 收藏

导航

公告

统计