Introduction
I've worked a lot with database applications. Sometimes, providing the capability to search through stored items is a critical requirement. Items may have many fields and, of course, they may be located in different tables. These raise the question of how to design an elegantly effective user interface supporting the capability. I tried some user interface prototypes, but none of them made me feel satisfied. A good solution came up when I used Windows Explorer.
Figure 1: The search text box on the top-right corner of Windows Explorer
The combination of the search button, the text label, and the text box is quite amazing. A lot of space is saved, and the functional design is good. I wanted to use such controls in my applications, but .NET Framework has not provided any standard control like this one. In this article, I'm demonstrating a solution based on WPF. Many thanks fly to David Owens for his wonderful article at http://davidowens.wordpress.com/2009/02/18/wpf-search-text-box/. Technically, his document showed me a practical way to realize my own Search Text Box control which has more functionalities. Now, let's get started!
Designing the control
Figure 2: The control's layout
We're going to create a custom control exposing the following properties:
LabelText
- When the text box is blank, we'll display this text instead. The default value is "Search".
LabelTextColor
- The color of the label text. The default will be "Gray".
ShowSectionButton
- Decides to show the section button or not. This property is very useful if you allow users to search items based on a set of criteria.
SectionsList
- Holds a list of sections which correspond to the data columns.
SectionsStyle
- The style of section items. You can choose between CheckBoxStyle
, RadioBoxStyle
, or NormalStyle
.
OnSearch
- This event is spawn whenever the search button is clicked or the Enter key is pressed.
Here are some images of the control I created:
Figure 3: A sections list styled by CheckBoxStyle
Figure 4: A sections list styled by RadioBoxStyle
Figure 5: List of previous keywords
Creating the layout
The file Generic.xaml contains the XAML code for laying out the control. All that we're doing is modifying the property template.
<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>
Displaying the lists
To customize a standard list box with the above styles, I added another Resource Dictionary file which is named as ListBoxEx.xaml. The file contains definitions of the three styles. We need an adjustment to the way that our lists' items are rendered. For example, if we want our items to have CheckBoxStyle
, we simply alter the template ListBoxItem
like this:
<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>
With RadioBoxStyle
, we must ensure that: at a particular time, there's only one item selected. I don't want to use lengthy C# code to attain this task. Everything seems so easy when every RadioBoxStyle
item shares the same Group name:
<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>
So far so good; the remainder is programmatically showing the lists whenever users click on the section button or the previous button.
private Popup m_listPopup = new Popup();private ListBoxEx m_listSection = null;
private ListBoxEx m_listPreviousItem = null;
private void BuildPopup()
{
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;
if (ShowSectionButton)
{
m_listSection = new ListBoxEx((int)m_itemStyle +
ListBoxEx.ItemStyles.NormalStyle);
}
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);
}
Raising the Search event
Whenever users request to start a new search either by clicking on the search button or entering a keyword, an event OnSearch
will be fired. A class named SearchEventArgs
provides data for the event.
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){
}
}
I've chosen to override the method OnKeyDown
to create the event. This is shown below:
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);
}
How to use the control
That's it; now the control is ready for action. To use it, you can go through these steps:
- Make a reference to the assembly SearchTextBox.dll which contains our control.
- Create a namespace in XAML markup like the following in your application XAML namespace:
<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">
- Create an instance of the control in the markup:
<l:SearchTextBox Height="39" Margin="118,52,116,0"
VerticalAlignment="Top" Name="m_txtTest" Background="AliceBlue" />
- Implement the C# code-behind:
- Create a
using
directive to use the control in the namespace UIControls
.
using UIControls;
- Initialize the control with the appropriate information, for example:
public Window1()
{
InitializeComponent();
List<string> sections = new List<string> {"Author",
"Title", "Comment"};
m_txtTest.SectionsList = sections;
m_txtTest.SectionsStyle = SearchTextBox.SectionsStyles.RadioBoxStyle;
m_txtTest.OnSearch += new RoutedEventHandler(m_txtTest_OnSearch);
}
void m_txtTest_OnSearch(object sender, RoutedEventArgs e)
{
SearchEventArgs searchArgs = e as SearchEventArgs;
string sections = "\r\nSections(s): ";
foreach (string section in searchArgs.Sections)
sections += (section + "; ");
m_txtSearchContent.Text = "Keyword: " + searchArgs.Keyword + sections;
}
Conclusion
I hope you liked this article. Any feedback from you is valuable to me for making the control better. Thanks for reading!