Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building a Search Text Box Control with WPF

0.00/5 (No votes)
3 Sep 2010 1  
Another convenient control for your WPF applications.

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.

clip_image002

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:

clip_image006

Figure 3: A sections list styled by CheckBoxStyle

clip_image008

Figure 4: A sections list styled by RadioBoxStyle

clip_image010

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()
{
    // 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);
}

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:

  1. Make a reference to the assembly SearchTextBox.dll which contains our control.
  2. Create a namespace in XAML markup like the following in your application XAML namespace:
  3. <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">
  4. Create an instance of the control in the markup:
  5. <l:SearchTextBox Height="39" Margin="118,52,116,0" 
          VerticalAlignment="Top" Name="m_txtTest" Background="AliceBlue" />
  6. 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();
       
          // 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;
      }

Conclusion

I hope you liked this article. Any feedback from you is valuable to me for making the control better. Thanks for reading!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here