We’ve seen a lot of good advancement in usability in our operating systems and in our programs. One such advancement is the combining of the text label and the text box that it adorns. Not only does the text box hold the label, it also holds the search and clear button. This frees up a lot of space and allows us to create UI that looks like the following:
Now, I’m not going to argue who started or came up with this design because I frankly don’t care. :p The point is that it is around and it’s very good use of space and functional design. However, there is a problem; there isn’t a standard control to provide users with this functionality.
Today, let’s solve that problem.
First Look
Before we get started, let’s look at the actual control that we’ll be building.
As you can see, there are two basic modes that we can be in. The top mode is what we’ll refer to as instant search and the second is delayed search. The difference between the two is fairly straightforward. In the instant search case we expect to have live filtering or searching of data where as in the second we require the search icon to be pressed or the ENTER key to be pressed in order for the search to take place.
If we were getting this from a designer we’d usually have pixel offsets of where everything should be placed, but I don’t want to spend the time creating those images as well.
Thinking About the Solution
So we have a few options to achieving the goal with WPF.
The first is to simply create this control by laying out the parts of the control directly on the designer. This has the major downfall of reusability problems but it would work.
The second is to create a user control that encapsulates the structure and functionality of the control. However, this has the drawback of not allowing others to control the look and feel of the control. It does have the great benefit of being able to be designed in either the WPF Designer in Visual Studio or with Expression Blend.
The third is to create a custom control that defines the default look through a style for the type and handles all of the functionality of the control as well. This gives us the greatest control and flexibility but comes at the cost of not being able to design the control with either Visual Studio or Expression Blend.
Since we’re trying to create a control that others can consume and can customize the look and feel of they wish, we need to go with the custom control option.
Ok, so we know the strategy that we’re going to take, let’s start looking into the functionality that we want to expose.
We are going to want the following properties on our control:
- LabelText – This will be the text that labels the text box box. The default will be “Search”.
- LabelTextColor – The color of the text for the label. The default will be “Gray”.
- SearchMode – The type of search that will be taking place: instant or delayed. The default will be instant.
- HasText – We’ll need this to make some of our triggers easier in our control template.
We’ll also want to expose an event for when the search icon is clicked in the delayed state.
- SearchClicked – The event to be raised when the search icon is clicked. Note that this will only be raised when the control is in the delayed state.
Getting Started
We now have a pretty good idea of where we are going and we have enough information that we should be able to get there, so let’s get started!
For this project, since there really is no designer support for custom controls in any of the products available and we’ll be needed to write some actual code, I’ll be using Visual Studio’s WPF designer.
Ok, so first things first, we need a new project. I’m actually going to be creating two projects:
- A WPF application that will consume my custom control (I named mine TestUI), and
- The custom control library (I named mine UIControls)
With those created, let’s add a new item to the UIControls project. When the new item dialog comes up, be sure to choose ‘WPF Custom Control’ and name the item “SearchTextBox”.
You should now have a SearchTextBox.cs file in your UIControls project.
Next we’ll want to add a project reference to our TestUI project that points to the UIControls project. After doing that, build the solution so that control library gets built and is available for use within the TestUI project.
Alright, the last little step we need to do is add our xmlns and our control to the XAML.
Open up the Window1.xaml file in the TestUI project. In the Window definition in the XAML editor you should see a few xmlns entries. We need to add another in there. Switch over to the XAML editor and add this:
xmlns:l="clr-namespace:UIControls;assembly=UIControls"
This creates a way to reference our user control using the “l” namespace prefix.
Now we can add an instance of our new custom control. Let’s add the following XAML as a child of the Grid:
<l:SearchTextBox Width="125" Height="21" />
If you build again, hopefully you won’t get any errors! If so, great! Though you might be wondering why you don’t see anything on the Grid – that’s OK, the template that you get defaulted with is a Border with no default properties so it will be transparent.
OK, we’re done! Yeah… not really, but you technically have created a custom control, and maybe your first, so congratulations if it is.
Adding the Properties
We’ll go ahead and get the property creation done first as there is a good portion of our UI work that will depend on them.
Open up the SearchTextBox.cs file. You should currently see something that looks like this (I left out the namespace using statements for space consideration):
namespace UIControls {
public class SearchTextBox : Control {
static SearchTextBox() {
DefaultStyleKeyProperty.OverrideMetadata(
typeof(SearchTextBox),
new FrameworkPropertyMetadata(typeof(SearchTextBox)));
}
}
}
Alright, we want to create dependency properties for all of the properties. If you don’t know much about dependency properties then check here: http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.aspx.
Here’s what the code should look like after you create all of the dependency properties mentioned above. Of course, you can do this in a few different ways, this is just one way to do it.
namespace UIControls {
public enum SearchMode {
Instant,
Delayed,
}
public class SearchTextBox : TextBox {
public static DependencyProperty LabelTextProperty =
DependencyProperty.Register(
"LabelText",
typeof(string),
typeof(SearchTextBox));
public static DependencyProperty LabelTextColorProperty =
DependencyProperty.Register(
"LabelTextColor",
typeof(Brush),
typeof(SearchTextBox));
public static DependencyProperty SearchModeProperty =
DependencyProperty.Register(
"SearchMode",
typeof(SearchMode),
typeof(SearchTextBox),
new PropertyMetadata(SearchMode.Instant));
private static DependencyPropertyKey HasTextPropertyKey =
DependencyProperty.RegisterReadOnly(
"HasText",
typeof(bool),
typeof(SearchTextBox),
new PropertyMetadata());
public static DependencyProperty HasTextProperty = HasTextPropertyKey.DependencyProperty;
static SearchTextBox() {
DefaultStyleKeyProperty.OverrideMetadata(
typeof(SearchTextBox),
new FrameworkPropertyMetadata(typeof(SearchTextBox)));
}
protected override void OnTextChanged(TextChangedEventArgs e) {
base.OnTextChanged(e);
HasText = Text.Length != 0;
}
public string LabelText {
get { return (string)GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public Brush LabelTextColor {
get { return (Brush)GetValue(LabelTextColorProperty); }
set { SetValue(LabelTextColorProperty, value); }
}
public SearchMode SearchMode {
get { return (SearchMode)GetValue(SearchModeProperty); }
set { SetValue(SearchModeProperty, value); }
}
public bool HasText {
get { return (bool)GetValue(HasTextProperty); }
private set { SetValue(HasTextPropertyKey, value); }
}
}
}
So there is definitely a lot of code in there, but the majority of it is just setting up the dependency properties. But here are the highlights:
- SearchTextBox derives from TextBox now instead of Control
- SearchMode enum defined
- OnTextChanged implemented to set our HasText dependency project
Creating the Look and Feel
We now have pretty much all of the functionality defined that we’ll be using so we can focus on getting the control looking like we want it.
The first thing we want to tackle is the look of the control when it has no text it in and does not have keyboard focus nor has the mouse over it. We’ll call this the control’s default state.
Let’s first take a look at the basic idea of the structure and then I’ll provide the XAML that we’ll need to put in the Generic.xaml resource dictionary.
- Border – The outermost border of the control.
- Grid – Provides an easy way to layout the two sections of the control we’ll have.
- ScrollViewPresenter – This provides us the basic functionality of a text box control.
- Label – Used for our overlay text.
- Border – The border around our search icon, used for mouse over effects.
- Image – The image control used for the search and delete icon.
- Grid – Provides an easy way to layout the two sections of the control we’ll have.
That is the basic hierarchy that we’ll be creating. And here is the style that we’ll be using for the template:
<Style x:Key="{x:Type l:SearchTextBox}" TargetType="{x:Type l:SearchTextBox}">
<Setter Property="Background" Value="{StaticResource SearchTextBox_Background}" />
<Setter Property="BorderBrush" Value="{StaticResource SearchTextBox_Border}" />
<Setter Property="Foreground" Value="{StaticResource SearchTextBox_Foreground}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="LabelText" Value="Search" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="LabelTextColor" Value="{StaticResource SearchTextBox_LabelTextColor}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type l:SearchTextBox}">
<Border x:Name="Border"
Padding="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="LayoutGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=ActualHeight}" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" Grid.Column="0" />
<Label x:Name="LabelText"
Grid.Column="0"
Foreground="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=LabelTextColor}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelText}"
Padding="2,0,0,0"
FontStyle="Italic" />
<Border x:Name="PART_SearchIconBorder"
Grid.Column="1"
BorderThickness="1"
Padding="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="{StaticResource SearchTextBox_SearchIconBorder}"
Background="{StaticResource SearchTextBox_SearchIconBackground}">
<Image x:Name="SearchIcon"
Stretch="None"
Width="15"
Height="15"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="pack://application:,,,/UIControls;component/Images/search.png" />
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There we have it.
I’m not really going to explain all of XAML up there – hopefully it’s fairly straight forward, but as always, if you have questions, let me know.
The resources that were defined above are below, you just need to add these to the Generic.xaml file as well. I’m going to go ahead and give you all of the resources that we’ll be using.
<SolidColorBrush x:Key="SearchTextBox_Background" Color="White" />
<SolidColorBrush x:Key="SearchTextBox_Foreground" Color="Black" />
<LinearGradientBrush x:Key="SearchTextBox_Border" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFABADB3" Offset="0.05" />
<GradientStop Color="#FFE2E3EA" Offset="0.07" />
<GradientStop Color="#FFE3E9EF" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="SearchTextBox_BorderMouseOver" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FF5C97C1" Offset="0.05" />
<GradientStop Color="#FFB9D7EB" Offset="0.07" />
<GradientStop Color="#FFC7E2F1" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="SearchTextBox_SearchIconBorder" Color="White" />
<SolidColorBrush x:Key="SearchTextBox_SearchIconBackground" Color="White" />
<LinearGradientBrush x:Key="SearchTextBox_SearchIconBorder_MouseOver" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#FFE5F4FC" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="SearchTextBox_SearchIconBackground_MouseOver" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFE7F5FD" Offset="0" />
<GradientStop Color="#FFD2EDFC" Offset="0.5" />
<GradientStop Color="#FFB6E3FD" Offset="0.51" />
<GradientStop Color="#FF9DD5F3" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="SearchTextBox_SearchIconBorder_MouseDown" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#FFE5F4FC" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="SearchTextBox_SearchIconBackground_MouseDown" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFE7F5FD" Offset="0" />
<GradientStop Color="#FFD2EDFC" Offset="0.5" />
<GradientStop Color="#FFB6E3FD" Offset="0.51" />
<GradientStop Color="#FF9DD5F3" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="SearchTextBox_LabelTextColor" Color="Gray" />
And the images that are used here:
Place those images in an “Images” folder in your project, set them to be a resource and to always copy or copy if newer in the properties for the item.
Voila, if all went well, you should see something like this when you run your application.
Handling the Mouse Over and Keyboard Focus
The next thing we’ll tackle with this is getting the styling right for when the mouse is over the control and when the keyboard has focus. Both of these will have the same look so we’ll handle them together.
Unfortunately we won’t be having the “cool” animation of the mouse over border color transitioning in as there are some issues with the ListBoxChrome type that is used to achieve that effect (basically they have some hard-coded colors in there). We’d have to write our own chrome class and that is just simply out of scope for this article.
In order to handle the mouse over and the keyboard focus, we’re going to have to pay attention to the mode that we are in.
Here are the triggers that we need to add to our control template:
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource SearchTextBox_BorderMouseOver}" />
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource SearchTextBox_BorderMouseOver}" />
</Trigger>
That will give us the nice little border effect when the mouse is over or when the control has keyboard focus.
Handling Having Text and Being In Different Modes
Next we’ll handle the triggers for when the control is in different modes.
<Trigger Property="HasText" Value="True">
<Setter Property="Visibility" TargetName="LabelText" Value="Hidden" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasText" Value="True" />
<Condition Property="SearchMode" Value="Instant" />
</MultiTrigger.Conditions>
<Setter Property="Source"
TargetName="SearchIcon"
Value="pack://application:,,,/UIControls;component/Images/clear.png" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" SourceName="PART_SearchIconBorder" Value="True" />
<Condition Property="HasText" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="BorderBrush"
TargetName="PART_SearchIconBorder"
Value="{StaticResource SearchTextBox_SearchIconBorder_MouseOver}" />
<Setter Property="Background"
TargetName="PART_SearchIconBorder"
Value="{StaticResource SearchTextBox_SearchIconBackground_MouseOver}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" SourceName="PART_SearchIconBorder" Value="True" />
<Condition Property="HasText" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="BorderBrush"
TargetName="PART_SearchIconBorder"
Value="{StaticResource SearchTextBox_SearchIconBorder_MouseOver}" />
<Setter Property="Background"
TargetName="PART_SearchIconBorder"
Value="{StaticResource SearchTextBox_SearchIconBackground_MouseOver}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" SourceName="PART_SearchIconBorder" Value="True" />
<Condition Property="IsMouseLeftButtonDown" Value="True" />
<Condition Property="HasText" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Padding"
TargetName="PART_SearchIconBorder"
Value="2,0,0,0" />
<Setter Property="BorderBrush"
TargetName="PART_SearchIconBorder"
Value="{StaticResource SearchTextBox_SearchIconBorder_MouseDown}" />
<Setter Property="Background"
TargetName="PART_SearchIconBorder"
Value="{StaticResource SearchTextBox_SearchIconBackground_MouseDown}" />
</MultiTrigger>
In here we basically have four different triggers happening:
- Hiding the place holder text (i.e. Search)
- Changing the search icon when in instant mode.
- Changing the border brush and background when the mouse is over the search icon.
- Changing the look of the search icon when it’s depressed.
You’ll notice that there is a property that doesn’t exist on our control: IsMouseLeftButtonDown. Well, we are going to need to add a new property to support this. Here’s the code we need to add to our code file:
private static DependencyPropertyKey IsMouseLeftButtonDownPropertyKey =
DependencyProperty.RegisterReadOnly(
"IsMouseLeftButtonDown",
typeof(bool),
typeof(SearchTextBox),
new PropertyMetadata());
public static DependencyProperty IsMouseLeftButtonDownProperty =
IsMouseLeftButtonDownPropertyKey.DependencyProperty;
public bool IsMouseLeftButtonDown {
get { return (bool)GetValue(IsMouseLeftButtonDownProperty); }
private set { SetValue(IsMouseLeftButtonDownPropertyKey, value); }
}
That’s great and all, but we’re not doing anything to set this property. We’ll need to handle a few mouse events in our code.
public override void OnApplyTemplate() {
base.OnApplyTemplate();
Border iconBorder = GetTemplateChild("PART_SearchIconBorder") as Border;
if (iconBorder != null) {
iconBorder.MouseLeftButtonDown += new MouseButtonEventHandler(IconBorder_MouseLeftButtonDown);
iconBorder.MouseLeftButtonUp += new MouseButtonEventHandler(IconBorder_MouseLeftButtonUp);
iconBorder.MouseLeave += new MouseEventHandler(IconBorder_MouseLeave);
}
}
private void IconBorder_MouseLeftButtonDown(object obj, MouseButtonEventArgs e) {
IsMouseLeftButtonDown = true;
}
private void IconBorder_MouseLeftButtonUp(object obj, MouseButtonEventArgs e) {
if (!IsMouseLeftButtonDown) return;
IsMouseLeftButtonDown = false;
}
private void IconBorder_MouseLeave(object obj, MouseEventArgs e) {
IsMouseLeftButtonDown = false;
}
That should about do it for getting our IsMouseLeftButtonDown property working, nothing to it.
Event Notification For Search
The last thing we need to do is add an event that our control consumers (i.e. the people using our control) can add a handler to in order to know when a search should happen.
There are two strategies that we want here:
- When in instant mode, we want a timer to trigger the event after the last character is entered. We want a timer because we don’t necessarily want every keystroke to raise the event.
- When in delayed mode, we want the event raised on ENTER or when the search icon is clicked.
To go about implementing the first of these strategies, we are going to need another property:
public static DependencyProperty SearchEventTimeDelayProperty =
DependencyProperty.Register(
"SearchEventTimeDelay",
typeof(Duration),
typeof(SearchTextBox),
new FrameworkPropertyMetadata(
new Duration(new TimeSpan(0, 0, 0, 0, 500)),
new PropertyChangedCallback(OnSearchEventTimeDelayChanged)));
public Duration SearchEventTimeDelay {
get { return (Duration)GetValue(SearchEventTimeDelayProperty); }
set { SetValue(SearchEventTimeDelayProperty, value); }
}
static void OnSearchEventTimeDelayChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
SearchTextBox stb = o as SearchTextBox;
if (stb != null) {
stb.searchEventDelayTimer.Interval = ((Duration)e.NewValue).TimeSpan;
stb.searchEventDelayTimer.Stop();
}
}
This property will be used to expose the timer delay before the event is raised. The default is half a second (0.5s).
The next thing we need to do is add the event, we’ll call it Search.
public static readonly RoutedEvent SearchEvent =
EventManager.RegisterRoutedEvent(
"Search",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(SearchTextBox));
public event RoutedEventHandler Search {
add { AddHandler(SearchEvent, value); }
remove { RemoveHandler(SearchEvent, value); }
}
We’ll also want to create a helper function that will raise this event for us.
private void RaiseSearchEvent() {
RoutedEventArgs args = new RoutedEventArgs(SearchEvent);
RaiseEvent(args);
}
The get the timer to work, we’re going to actually need to create a timer object. We’ll also want to add some code to our constructor to perform some default initialization.
private DispatcherTimer searchEventDelayTimer;
public SearchTextBox() : base() {
searchEventDelayTimer = new DispatcherTimer();
searchEventDelayTimer.Interval = SearchEventTimeDelay.TimeSpan;
searchEventDelayTimer.Tick += new EventHandler(OnSeachEventDelayTimerTick);
}
void OnSeachEventDelayTimerTick(object o, EventArgs e) {
searchEventDelayTimer.Stop();
RaiseSearchEvent();
}
The OnTextChanged method will need to be updated so that it stops and starts the timer correctly when in instant mode.
protected override void OnTextChanged(TextChangedEventArgs e) {
base.OnTextChanged(e);
HasText = Text.Length != 0;
if (SearchMode == SearchMode.Instant) {
searchEventDelayTimer.Stop();
searchEventDelayTimer.Start();
}
}
To enable the second scenario, we need to update our mouse and key handling methods.
private void IconBorder_MouseLeftButtonUp(object obj, MouseButtonEventArgs e) {
if (!IsMouseLeftButtonDown) return;
if (HasText && SearchMode == SearchMode.Instant) {
this.Text = "";
}
if (HasText && SearchMode == SearchMode.Delayed) {
RaiseSearchEvent();
}
IsMouseLeftButtonDown = false;
}
protected override void OnKeyDown(KeyEventArgs e) {
if (e.Key == Key.Escape && SearchMode == SearchMode.Instant) {
this.Text = "";
}
else if ((e.Key == Key.Return || e.Key == Key.Enter) && SearchMode == SearchMode.Delayed) {
RaiseSearchEvent();
}
else {
base.OnKeyDown(e);
}
}
Alright, there we have it. I know this was a really long post but I hope that you found it at least helpful. I’ve chosen to let the code do most of the explanation so if anything is confusing or if you have questions about anything, please let me know and I’ll be glad to answer them.
Here’s the sample project so you can look at all of the bits in one place: SearchTextBox.zip
February 19, 2009 at 1:11 pm
Great article. Many thanks for sharing.
February 19, 2009 at 1:57 pm
No problem, glad someone out there liked it.
February 19, 2009 at 5:31 pm
Excellent post David. I really liked your “Thinking About the Solution” section as it’s nice to not only see the solution but why you went with it versus the alternatives.
February 19, 2009 at 10:23 pm
[...] in luck however. David Owens II has a lengthy article describing how to make your own custom SearchTextBox [...]
February 20, 2009 at 12:21 pm
The file is corrupt. Cannot download
February 20, 2009 at 1:05 pm
Hey Benny, not sure what the problem is. I followed the link and it worked fine on my side on two different computers. One computer I used Window’s built-in zip handling, the other I used WinRAR. I also tried from both IE 8 and Chrome.
Can you try it again?
Thanks!
February 24, 2009 at 3:31 am
Nice post David!
Do you mind me using these sources for property grid project (http://www.codeplex.com/wpfpropertygrid)?
Thanks in advance.
February 24, 2009 at 9:06 am
Be my guest. You should not use the icons though as I ripped those from a screenshot of Windows Explorer. Glad you liked the control!
February 26, 2009 at 2:12 am
[...] WPF Search Text Box [...]
February 26, 2009 at 5:03 am
[...] Owens: WPF Search Text Box A WPF Search Text Box control that you may find use for, or learn from. Thanks David Owens for [...]
February 26, 2009 at 7:55 am
Oh my god this post is priceless!
I will be sure using these techniques in my next WPF App, thank you very much for your time!
February 27, 2009 at 12:00 pm
Anyone able to get this? I’m getting a login error.
February 27, 2009 at 12:15 pm
WPF Search Text Box « David’s Playground…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
February 27, 2009 at 12:26 pm
Hmm… I’m not seeing this. You shouldn’t need to log in at all as the files are publicly available.
Can you try again?
Thanks.
March 1, 2009 at 12:30 pm
I think you can also move the [PART_SearchIconBorder] into a Button and set [SearchIcon] as its content. Then override its style. The Button way would simplify handling the keyboard and mouse interaction in a elegant way.
What do you think?
March 1, 2009 at 7:10 pm
Yeah, you could use a button there if you wanted. You get some of the mouse handling for free, though there is nothing about keyboard handling that changes except by using a button you need to make sure that the IsTabStop property is set to false on it otherwise when you TAB through the controls on your, you’ll hit that button. If that is what you want, then don’t worry about; I didn’t want that for this control as there are other keyboard means to clear and search for the items.
Also, by using a button you force people to have a button as their container which is a little heavier of an element. I force people to use border, but I think the trade off is worth it. Also, in the case of a button you have to re-template it as well as provide different styles that get triggered on the mouse over.
I’m not sure using a button is more elegant, it does save you a few lines of code (only on the order of 10) but requires more work in the template and styling department.
March 2, 2009 at 2:38 pm
Very nice article! I had coded the exact same control myself using Windows Forms, and your article gives me great insight on how to go about implementing custom controls using WPF. I haven’t fully grasped the WPF mindset yet but this article goes a long way helping me do that!
April 9, 2009 at 5:32 am
I feel like I must be doing something goofy. Every time I try to make a custom control based on the textbox (based on my own experiments plus following this post to T) I wind up with a textbox that doesn’t use the “I Beam” mouse cursor when I mouse over it. It’s just the regular arrow. Any clue as to what I’m missing?
July 21, 2009 at 3:02 pm
Thanks very much for this article.
I ported it to VB and it works very well.
Related to the post directly above
To get the I beam cursor to appear modify the Trigger Property IsMouseOver section.
From:
To:
July 21, 2009 at 3:05 pm
I can see that worked well.
OK I will try again – ADD
“”
to the Trigger Property IsMouseOver section.
July 21, 2009 at 3:07 pm
Once again – I can see that worked out about the same..
OK I will try again – ADD
Setter Property=”Cursor” Value=”IBeam”
properly enclosed.
July 26, 2009 at 7:18 pm
I’ve been looking all over for this, and this is the best I’ve seen. I’m using it for a personal project of mine, but I had no problem getting it setup and then hosting it inside of an ElementHost in a WinForms application.. works amazing. Love it.
August 20, 2009 at 10:40 am
[...] del control Search TextBox de David Owens, con algunos cambios en los estilos y posiblemente acabe haciendo algún cambio [...]
August 29, 2009 at 6:08 am
I really like the styling of WPF improvement from winforms. And the explanation is very nice
September 23, 2009 at 2:45 am
I’m a beginner in WPF and this tutorial really opened my eyes on custom controls matter. Thank you very much for making it.
October 18, 2009 at 9:24 am
@David – Great job of presenting a slick control with a slick presentation of design concepts and usage (and letting the code speak for itself!).
@All – has anyone made a unit test for this? I’d like to see how you did it if so.
Cheers
November 11, 2009 at 4:42 am
Very interesting post and also very useful, but I was wondering if I could get some help to do the same thing in silverlight as I am new to both silverlight and WPF.
Thanks
November 11, 2009 at 10:37 am
I’ll see if I can’t create a Silverlight version sometime soon.