简体   繁体   中英

Visual Studio WPF UI Designer with Custom Controls

This is a bit of a weird question:

I have a custom control that inherits from TextBox , and provides "ghost" text - eg it says "Username" in a box until you click inside it, whereupon the "ghost" text disappears, and the user can type in their, in this case, Username.

The "Ghost text" for a control is simply a property in a subclass of TextBox. I then set TextBox.Text to it whenever relevant.

In the Visual Studio WPF XAML preview window (the standard UI design one), I would like to be able to "preview" the "Ghost text" - like when you set the actual text of a textbox, you can see it in the preview, not just when you run the application.

I have tried setting the Text property to the relevant Ghost text in the OnInitialised function, but it doesn't have any effect on the preview.

Where should I be putting code that affects the preview of a control in the designer?

Bonus question: Is there an actual name for what I call "ghost" textboxes? Would be good to know for the future!

Is there an actual name for what I call "ghost" textboxes? Would be god to know for the future!

I have seen this referred to as a "hint" when describing its purpose, or as a "watermark" when describing its appearance. I tend to employ the former, as it describes the function , which is more in line with the WPF design philosophy: the actual presentation is determined by the template, and the conceptual "hint" could be presented differently simply by applying a custom style/template. Why imply that it should be a watermark when someone could choose to present it in another way?

Design-wise, I think you're approaching this the wrong way. I would implement this such a way that controls other than a TextBox could more easily opt in: use attached properties.

I would create a static class, say HintProperties , which declares a couple of attached dependency properties:

  • Hint - declares the hint content; typically a string, but it need not be. It could simply be an object , akin to the Content property of a ContentControl .

  • HasHint - a computed, read-only bool property that gets reevaluated when Hint changes, and simply indicates whether a control has a Hint specified. Useful as a Trigger condition to toggle the visibility of a hint presenter in your control template.

Then, provide a custom style for your TextBox (or other control) which overlays a Hint presenter atop the regular content, hidden by default. Add a trigger to reduce the opacity of the hint when the control has keyboard focus, and another to make the hint Visible when Text is an empty string.

If you really want to go all-out, you can throw in HintTemplate and HintTemplateSelector properties.


However, if this seems like overkill, you can simply declare a Hint or Watermark property directly on your derived TextBox class. I would not try to implement this by conditionally changing the Text property, as that would interfere with data binding and, potentially, value precedence.

You can do this in a reusable way using a style which you would typically declare in your App.xaml . In this style you replace the control template with your own implementation and wrap together some controls. Basically you make up the WatermarkTextBox from a normal TextBox with a transparent background and place a TextBlock control with standard text behind the TextBox . The Visibility of this TextBlock is bound to the TextBox using a specific TextInputToVisibilityConverter so it will disappear when the TextBox has text or just has the focus.

While this maybe looks like a lot of code, you define this once and you can reuse this whereever you need, just by setting style of the TextBox

Declaration of some resources

xmlns:c="clr-namespace:YourNameSpace.Converters"
<SolidColorBrush x:Key="brushWatermarkBackground" Color="White" />
<SolidColorBrush x:Key="brushWatermarkForeground" Color="LightSteelBlue" />
<c:TextInputToVisibilityConverter x:Key="TextInputToVisibilityConverter" />

Declaration of the style:

<Style x:Key="SearchTextBox" TargetType="{x:Type TextBox}">
    <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="AllowDrop" Value="true"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBox}">
                <Grid Background="{StaticResource brushWatermarkBackground}">
                    <TextBlock Margin="5,5" Text="Search..." 
                        Foreground="{StaticResource brushWatermarkForeground}" >
                        <TextBlock.Visibility>
                          <MultiBinding 
                          Converter="{StaticResource TextInputToVisibilityConverter}">
                          <Binding RelativeSource="{RelativeSource 
                                Mode=FindAncestor, AncestorType=TextBox}" 
                                Path="Text.IsEmpty" />
                          <Binding RelativeSource="{RelativeSource 
                              Mode=FindAncestor, AncestorType=TextBox}" 
                              Path="IsFocused" />
                            </MultiBinding>
                        </TextBlock.Visibility>
                    </TextBlock>
                    <Border x:Name="Border" Background="Transparent" 
                       BorderBrush="{DynamicResource SolidBorderBrush}" 
                       BorderThickness="1" Padding="2" CornerRadius="2">

                        <!-- The implementation places the Content into the 
                             ScrollViewer. It must be named PART_ContentHost 
                             for the control to function -->
                        <ScrollViewer Margin="0" x:Name="PART_ContentHost" 
                          Style="{DynamicResource SimpleScrollViewer}" 
                          Background="Transparent"/>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The implementation of the TextInputToVisibilityConverter , which just takes text input, converts to bool and converts this to Visibility . Also keeps Focus into account.

namespace YourNameSpace
{
    public class TextInputToVisibilityConverter : IMultiValueConverter
    {
        public object Convert(object[] values, 
              Type targetType, object parameter, 
              System.Globalization.CultureInfo culture)
        {
            if (values[0] is bool && values[1] is bool)
            {
                bool hasText = !(bool)values[0];
                bool hasFocus = (bool)values[1];

                if (hasFocus || hasText)
                    return Visibility.Collapsed;
            }

            return Visibility.Visible;
        }

        public object[] ConvertBack(object value, 
            Type[] targetTypes, object parameter, 
            System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Now all infrastructure is into place. In your view/usercontrol/window just alter the style of the Textbox and there it is, your watermark textbox..

<TextBox Style="{DynamicResource SearchTextBox}" />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM