Я знаю слишком поздно, но для будущей аудитории:
В этом решении используется оригинальный шаблон элемента управления TextBox и добавлена функциональность, которая заменяет значение свойства Text на свойство Tag TextBox, когда TextBox Text - Пустой . Это довольно сложный стиль, но он работает! (без кода!):
<Style x:Key="TextBoxPlaceHolder" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Microsoft_Windows_Themes:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="IsFocused" Value="False">
<Setter Property="Text">
<Setter.Value>
<MultiBinding Converter="{StaticResource TextBoxPlaceHolderConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Text" />
<Binding RelativeSource="{RelativeSource Self}" Path="Tag" />
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Text">
<Setter.Value>
<MultiBinding Converter="{StaticResource TextBoxPlaceHolderConverter}" ConverterParameter="True">
<Binding RelativeSource="{RelativeSource Self}" Path="Text" />
<Binding RelativeSource="{RelativeSource Self}" Path="Tag" />
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource StringsEqualMultiConverter}" Mode="OneWay">
<Binding RelativeSource="{RelativeSource Self}" Path="Text" />
<Binding RelativeSource="{RelativeSource Self}" Path="Tag" />
</MultiBinding>
</Condition.Binding>
</Condition>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsFocused}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="#FF7C7C80"/>
<Setter Property="FontStyle" Value="Italic"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
И два предоставленных преобразователя:
public class TextBoxPlaceHolderConverter : IMultiValueConverter
{
private static object s_OriginalTag;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
s_OriginalTag = values[1];
var actualText = values[0] as string;
var textToReturn = actualText ?? string.Empty;
var tagText = values[1] is string ? values[1] as string : null;
if (!(parameter is string && parameter.ToString() == "True"))
{
if (actualText != null && tagText != null)
{
if (actualText.Length == 0) // no text
{
textToReturn = tagText;
}
else
{
textToReturn = actualText;
}
}
}
else
{
if (actualText == tagText)
{
textToReturn = string.Empty;
}
}
return textToReturn;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
object[] toReturn = new object[2];
toReturn[0] = value;
toReturn[1] = s_OriginalTag;
return toReturn;
}
}
public class StringsEqualMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var stringToCompare = string.Empty;
if (values != null)
{
if (values.Length > 0 && values[0] is string)
{
stringToCompare = values[0] as string;
}
}
var boolToReturn = values.Aggregate(true, (current, value) => current && (value is string && value.ToString().Equals(stringToCompare)));
return boolToReturn;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}