views:

336

answers:

3

Hey there.

I'm writing an application in WPF using the MVVM-pattern and will really often use TextBoxes. I don't want to use labels for the user to know user what the text box is for, i.e. I don't want something like this:

<TextBlock> Name: </TextBlock>
<TextBox />

Instead, I would like the TextBox to contain its own label. Statically, you would express it like this:

<TextBox>Name</TextBox>

If the cursor is displayed in the textbox, i.e. the TextBox gains focus, I want the description text to disappear. If the TextBox is left empty and it loses the focus, the description text should be shown again. It's similar to the search textbox of StackOverflow or the one of Firefox. (please tell me if your not sure what I mean).

One TextBox's label may change at runtime, dependending on e.g. a ComboBox's selected element or a value in my ViewModel. (It's like in Firefox's search TextBox, if you select google from the search engins' menu, the TextBox's label changes to "Google", if you select "Yahoo" its set to "Yahoo"). Thus I want to be able to bind the label's content.

Consider that I may already have a Binding on the Text-Property of the TextBox.

How can implement such a behaviour and make it reusable for any of my TextBox's? Code is welcome but not needed; a description of what to do is enough.

Thank you in advance.

+1  A: 

You could derive from TextBox and implement your behaviour. The TextBox offers the events GotFocus/LostFocus (or the methods OnGotFocus/OnLostFocus respectively) which should help. You also should consider offering a new DepedencyProperty, so you can define the default text in xaml and bind it to other controls/resources etc.

Femaref
Additionally, it seems like it would be worth investigating whether or not this should be implemented as an adorner, so that you aren't futzing around with the bound `Text` property.
Robert Rossney
@Robert: Can you please explain what an Adorner is, does and how it suits to this situation? What problems could occur with the Text property? Maybe you could write an answer... I would be grateful.
Simon
+2  A: 

Here is a style I think is exactly what you are looking for, and it's pure XAML.

<Style x:Key="WatermarkTextBox" TargetType="{x:Type TextBox}">
     <Setter Property="Template">
          <Setter.Value>
               <ControlTemplate TargetType="{x:Type TextBox}">
                    <Grid>
                         <Border x:Name="BorderBase" Background="White" BorderThickness="1.4,1.4,1,1" BorderBrush="Silver"> 
                             <Label x:Name="TextPrompt" 
                                Content="{Binding RelativeSource={RelativeSource  Mode=TemplatedParent}, Path=Tag}" 
                                Background="{TemplateBinding Background}" Visibility="Collapsed" 
                                Focusable="False" Foreground="Silver"/>
                         </Border>
                         <ScrollViewer Margin="0" x:Name="PART_ContentHost" Foreground="Black"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                         <MultiTrigger>
                              <MultiTrigger.Conditions>
                                   <Condition Property="IsFocused" Value="False"/>
                                   <Condition Property="Text" Value=""/>
                              </MultiTrigger.Conditions>
                              <Setter Property="Visibility" TargetName="TextPrompt" Value="Visible"/>
                         </MultiTrigger>
                         <Trigger Property="IsFocused" Value="True">
                              <Setter Property="BorderBrush" TargetName="BorderBase" Value="Black"/>
                         </Trigger>
                         <Trigger Property="IsEnabled" Value="False">
                              <Setter Property="Foreground" Value="DimGray" />
                         </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
           </Setter.Value>
      </Setter>
 </Style>

Usage is:

<TextBox Style="{StaticResource WatermarkTextBox}" Tag="Full Name"/>

where Tag is the help message you want to show.

You could clean up this style for your own use, but the most important part is the which controls hiding/showing the helper text.

It's worth noting as well, there is already a DependencyObject available for storing the helper text, so you don't need to create your own with this method.

FrameworkElement.Tag is available for holding arbitrary information about this element. That's why we set the Tag property:

http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.tag.aspx

mattjf
Thank you! This helped a lot. I didn't know this kind of TextBox were called Watermark TextBoxes. After typing this in google I found further information. Just make the Label child of Border and your example works just fine.
Simon
Glad it helped. I spent quite a bit of time looking for something similar.
mattjf
One problem left: If I set a binding on the Tag property of the TextBox, the value of the label won't be updated as soon as the bound tag property changes. Any ideas?
Simon
@mattjf: After a few tests I noticed that binding Tag changes the DataContext for TemplateBinding and Binding. You have to explicitly tell where to search the Tag property. I modified your answer so that now it will work in every case.
Simon
+1  A: 

To amplify on my suggestion about using an adorner.

An Adorner is basically an element, rendered on its own layer, that appears over/around another element. For instance, if you implement validation in a binding, the red box that decorates an invalid control is an adorner - it's not part of the control, and it can be (and is) applied to all kinds of controls. See the Adorners section of the WPF docs for a simple but clear example.

I thought of an Adorner for a couple of reasons. The principal one is that the behavior you're describing might not necessarily be confined to a TextBox. You might, for instance, want to have a ComboBox exhibit the same behavior. Implementing an Adorner would give you a consistent way to implement this functionality across multiple controls (though it doesn't make sense in, say, a CheckBox or a ProgressBar). A second is that you wouldn't have to do anything to the underlying control more elaborate than implementing triggers to display and hide the Adorner in response to focus events. Adorners are a bit of a pain in the butt to implement, but it's worth knowing how to.

All that said, I like mattjf's answer a lot more than I like mine. The only disadvantages I see with that approach are 1) It only works with the TextBox; you need to implemnent a new version of the style every time you want to use the approach on another control, 2) I may just be engaging in magical thinking, but every time I ever used the Tag property in WinForms it told me (once I learned to listen) that I was building something fragile. I don't know for sure that this is also true in WPF, but I bet it is.

My comment on using the bound Text property probably needs amplification. If you use the Text property to store the field label, then you've got a number of hard-to-solve problems. First, since it's a bound property, changing its value in the TextBox will change it in the source. So now your source needs to know a lot of information about the state of the UI - does the control currently have the focus? If the value of the Text property is Foo, does that mean that the label is Foo, or the user typed in Foo? There are probably ways that you can manage this, but the best way to manage it is to not have to.

(One other problem with this paradigm: What should be the behavior be if the user wants the value of the TextBox to be the empty string?)

Robert Rossney