构建 WPF 搜索文本框控件






4.90/5 (44投票s)
另一个方便的 WPF 应用程序控件。
引言
我经常使用数据库应用程序。有时,提供搜索存储项目的功能至关重要。项目可能有很多字段,当然,它们可能位于不同的表中。这就提出了如何设计一个优雅有效的用户界面来支持该功能的问题。我尝试了一些用户界面原型,但没有一个让我感到满意。当我使用 Windows 资源管理器时,出现了一个很好的解决方案。
搜索按钮、文本标签和文本框的组合非常棒。节省了大量空间,而且功能设计很好。我想在我的应用程序中使用这样的控件,但 .NET Framework 还没有提供任何这样的标准控件。在本文中,我将演示一个基于 WPF 的解决方案。非常感谢 David Owens 在 http://davidowens.wordpress.com/2009/02/18/wpf-search-text-box/ 上的精彩文章。从技术上讲,他的文档向我展示了一种实现我自己的具有更多功能的搜索文本框控件的实用方法。现在,让我们开始吧!
设计控件
我们将创建一个自定义控件,公开以下属性
LabelText
- 当文本框为空时,我们将显示此文本。默认值为“搜索”。LabelTextColor
- 标签文本的颜色。默认为“灰色”。ShowSectionButton
- 决定是否显示部分按钮。如果您允许用户根据一组条件搜索项目,则此属性非常有用。SectionsList
- 包含与数据列对应的部分列表。SectionsStyle
- 部分项目的样式。您可以选择CheckBoxStyle
、RadioBoxStyle
或NormalStyle
。
OnSearch
- 每次单击搜索按钮或按下 Enter 键时都会触发此事件。
以下是我创建的控件的一些图像
创建布局
文件 *Generic.xaml* 包含用于布局控件的 XAML 代码。我们所做的只是修改属性模板。
<ResourceDictionary> <!-- ... -->
<Style x:Key="{x:Type l:SearchTextBox}" TargetType="{x:Type l:SearchTextBox}">
<!-- ... -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type l:SearchTextBox}">
<!- XAML code for creating the layout goes here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
显示列表
为了使用上述样式自定义标准列表框,我添加了另一个资源字典文件,名为 *ListBoxEx.xaml*。该文件包含三个样式的定义。我们需要调整列表项的呈现方式。例如,如果我们希望我们的项目具有 CheckBoxStyle
,我们只需像这样更改模板 ListBoxItem
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox Focusable="False"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent} }">
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
使用 RadioBoxStyle
,我们必须确保:在特定时间,只有一个项目被选中。我不想使用冗长的 C# 代码来完成这项任务。当每个 RadioBoxStyle
项目共享相同的组名时,一切似乎都非常简单
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Focusable="False" GroupName="RadioListBoxItems"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent} }">
<ContentPresenter></ContentPresenter>
</RadioButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
到目前为止一切顺利;剩下的就是以编程方式显示列表,无论何时用户单击部分按钮或上一个按钮。
private Popup m_listPopup = new Popup();private ListBoxEx m_listSection = null;
private ListBoxEx m_listPreviousItem = null;
private void BuildPopup()
{
// initialize the pop up
m_listPopup.PopupAnimation = PopupAnimation.Fade;
m_listPopup.Placement = PlacementMode.Relative;
m_listPopup.PlacementTarget = this;
m_listPopup.PlacementRectangle = new Rect(0, this.ActualHeight, 30, 30);
m_listPopup.Width = this.ActualWidth;
// initialize the sections' list
if (ShowSectionButton)
{
m_listSection = new ListBoxEx((int)m_itemStyle +
ListBoxEx.ItemStyles.NormalStyle);
// ...
}
// initialize the previous items' list
m_listPreviousItem = new ListBoxEx();
// ...
}
private void HidePopup()
{
m_listPopup.IsOpen = false;
}
private void ShowPopup(UIElement item)
{
m_listPopup.StaysOpen = true;
m_listPopup.Child = item;
m_listPopup.IsOpen = true;
}
private void ChooseSection_MouseDown(object sender, MouseButtonEventArgs e)
{
if (SectionsList == null)
return;
if (SectionsList.Count != 0)
ShowPopup(m_listSection);
}
private void PreviousItem_MouseDown(object sender, MouseButtonEventArgs e)
{
if (m_listPreviousItem.Items.Count != 0)
ShowPopup(m_listPreviousItem);
}
引发搜索事件
每当用户请求通过单击搜索按钮或输入关键字来开始新搜索时,将触发事件 OnSearch
。名为 SearchEventArgs
的类为该事件提供数据。
public class SearchEventArgs: RoutedEventArgs{
private string m_keyword="";
public string Keyword
{
get { return m_keyword; }
set { m_keyword = value; }
}
private List<string> m_sections= new List<string>();
public List<string> Sections
{
get { return m_sections; }
set { m_sections = value; }
}
public SearchEventArgs(): base(){
}
public SearchEventArgs(RoutedEvent routedEvent): base(routedEvent){
}
}
我选择覆盖方法 OnKeyDown
来创建事件。 如下所示
protected override void OnKeyDown(KeyEventArgs e) {
if (e.Key == Key.Escape) {
this.Text = "";
}
else if ((e.Key == Key.Return || e.Key == Key.Enter)) {
RaiseSearchEvent();
}
else {
base.OnKeyDown(e);
}
}
private void RaiseSearchEvent() {
if (this.Text == "")
return;
if(!m_listPreviousItem.Items.Contains(this.Text))
m_listPreviousItem.Items.Add(this.Text);
SearchEventArgs args = new SearchEventArgs(SearchEvent);
args.Keyword = this.Text;
if(m_listSection != null){
args.Sections = (List<string>)m_listSection.SelectedItems.Cast<string>().ToList();
}
RaiseEvent(args);
}
如何使用该控件
就是这样;现在控件已准备好运行。要使用它,您可以执行以下步骤
- 引用包含我们控件的程序集 *SearchTextBox.dll*。
- 在 XAML 标记中创建一个命名空间,例如在应用程序 XAML 命名空间中
- 在标记中创建控件的实例
- 实现 C# 代码隐藏
- 创建一个
using
指令以在命名空间UIControls
中使用该控件。 - 使用适当的信息初始化控件,例如
<Window x:Class="TestUI.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:UIControls;assembly=SearchTextBox"
Title="Window1" Height="423" Width="487">
<l:SearchTextBox Height="39" Margin="118,52,116,0"
VerticalAlignment="Top" Name="m_txtTest" Background="AliceBlue" />
using UIControls;
public Window1()
{
InitializeComponent();
// Supply the control with the list of sections
List<string> sections = new List<string> {"Author",
"Title", "Comment"};
m_txtTest.SectionsList = sections;
// Choose a style for displaying sections
m_txtTest.SectionsStyle = SearchTextBox.SectionsStyles.RadioBoxStyle;
// Add a routine handling the event OnSearch
m_txtTest.OnSearch += new RoutedEventHandler(m_txtTest_OnSearch);
}
void m_txtTest_OnSearch(object sender, RoutedEventArgs e)
{
SearchEventArgs searchArgs = e as SearchEventArgs;
// Display search data
string sections = "\r\nSections(s): ";
foreach (string section in searchArgs.Sections)
sections += (section + "; ");
m_txtSearchContent.Text = "Keyword: " + searchArgs.Keyword + sections;
}
结论
希望您喜欢这篇文章。您的任何反馈对我改进控件都非常有价值。感谢阅读!