Статьи

Текстовый ящик с закругленными углами через WPF XAML

Какая первая идея приходит на ум, когда кто-то упоминает закругленные углы и WPF? Возможно Граница . Об этом стоит подумать, но как применить его к элементу управления TextBox ?

Есть два способа достичь того, что вы хотите.

Способ А:

Самым очевидным было бы создание границы вокруг самого элемента управления. Что-то вроде этого:

<Border CornerRadius="5" BorderThickness="1" BorderBrush="Black" Margin="91,192,150,79">
<TextBox Background=”Transparent” BorderThickness="0" Height="35" Name="txtContents" Width="254" />
</Border>

Это должно сделать первоначальный трюк — элемент управления TextBox не имеет границы вокруг него ( свойство BorderThickness установлено в 0), а содержащая его граница устанавливает правильное округление, цвет и толщину.

Выглядит хорошо, но самое интересное происходит, когда вы решаете, что этот конкретный TextBox не должен быть включен, поэтому вы устанавливаете для свойства IsEnabled значение False.  

Что случилось с остальной частью белого пространства между границей и областью письма? Не похоже, что мы хотим, чтобы он вел себя так. И вот где второй способ создания закругленных углов спасает день.

Способ Б:

Это немного сложнее, но дает лучший результат. Он переопределяет шаблон элемента управления по умолчанию для TextBox . Но как мне узнать, как выглядит шаблон по умолчанию?

Поскольку невозможно изменить только одну часть шаблона, весь шаблон должен быть переопределен. Чтобы избежать потери функциональности, я собираюсь получить шаблон по умолчанию и использовать его с небольшими изменениями.

Чтобы получить стиль по умолчанию (который встраивает шаблон элемента управления) для элемента управления TextBox, я использую метод GetStyle :

string GetStyle(Type t)
{
FrameworkElement element = (FrameworkElement)Activator.CreateInstance(t);
object styleName = element.GetValue(FrameworkElement.DefaultStyleKeyProperty);
Style style = Application.Current.TryFindResource(styleName) as Style;
StringWriter stringContainer = new StringWriter();
XmlTextWriter xmlWriter = new XmlTextWriter(stringContainer);
xmlWriter.Formatting = Formatting.Indented;
System.Windows.Markup.XamlWriter.Save(style, xmlWriter);
return stringContainer.ToString();
}

Поскольку я использую элемент управления по умолчанию, без каких-либо пользовательских стилей, я могу просто создать экземпляр TextBox и использовать его тип в качестве параметра для метода GetStyle :

TextBox t = new TextBox();
Debug.Print(GetStyle(t.GetType()));

Вывод должен выглядеть так:

<Style TargetType="TextBox" xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation"">http://schemas.microsoft.com/winfx/2006/xaml/presentation"</a> xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml"">http://schemas.microsoft.com/winfx/2006/xaml"</a> xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:mwt="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
<Style.BasedOn>
<Style TargetType="TextBoxBase">
<Style.Resources>
<ResourceDictionary />
</Style.Resources>
<Setter Property="TextElement.Foreground">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.ControlTextBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="Panel.Background">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.WindowBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="Border.BorderBrush">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,20" MappingMode="Absolute">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#FFABADB3" Offset="0.05" />
<GradientStop Color="#FFE2E3EA" Offset="0.07" />
<GradientStop Color="#FFE3E9EF" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Border.BorderThickness">
<Setter.Value>
<Thickness>1,1,1,1</Thickness>
</Setter.Value>
</Setter>
<Setter Property="Control.Padding">
<Setter.Value>
<Thickness>1,1,1,1</Thickness>
</Setter.Value>
</Setter>
<Setter Property="UIElement.AllowDrop">
<Setter.Value>
<s:Boolean>True</s:Boolean>
</Setter.Value>
</Setter>
<Setter Property="FrameworkElement.FocusVisualStyle">
<Setter.Value>
<x:Null />
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.PanningMode">
<Setter.Value>
<x:Static Member="PanningMode.VerticalFirst" />
</Setter.Value>
</Setter>
<Setter Property="Stylus.IsFlicksEnabled">
<Setter.Value>
<s:Boolean>False</s:Boolean>
</Setter.Value>
</Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="TextBoxBase">
<mwt:ListBoxChrome Background="{TemplateBinding Panel.Background}" BorderBrush="{TemplateBinding Border.BorderBrush}" BorderThickness="{TemplateBinding Border.BorderThickness}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" RenderFocused="{TemplateBinding UIElement.IsKeyboardFocusWithin}" Name="Bd" SnapsToDevicePixels="True">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</mwt:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled">
<Setter Property="Panel.Background" TargetName="Bd">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.ControlBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="TextElement.Foreground">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.GrayTextBrushKey}" />
</Setter.Value>
</Setter>
<Trigger.Value>
<s:Boolean>False</s:Boolean>
</Trigger.Value>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.BasedOn>
<Style.Resources>
<ResourceDictionary />
</Style.Resources>
</Style>

Здесь много разметки XAML, но все, что нам нужно, это шаблон элемента управления:

<ControlTemplate TargetType="TextBoxBase">
<mwt:ListBoxChrome Background="{TemplateBinding Panel.Background}" BorderBrush="{TemplateBinding Border.BorderBrush}" BorderThickness="{TemplateBinding Border.BorderThickness}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" RenderFocused="{TemplateBinding UIElement.IsKeyboardFocusWithin}" Name="Bd" SnapsToDevicePixels="True">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</mwt:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled">
<Setter Property="Panel.Background" TargetName="Bd">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.ControlBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="TextElement.Foreground">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.GrayTextBrushKey}" />
</Setter.Value>
</Setter>
<Trigger.Value>
<s:Boolean>False</s:Boolean>
</Trigger.Value>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Теперь оболочку ListBoxChrome следует удалить и вместо нее использовать Border с назначенным свойством CornerRadius . Модифицированный шаблон выглядит так:

<ControlTemplate TargetType="TextBoxBase" x:Key="txt">
<Border CornerRadius="5" BorderThickness="1" BorderBrush="Black" x:Name="Bd" Background="{TemplateBinding Panel.Background}">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled">
<Setter Property="Panel.Background" TargetName="Bd">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.ControlBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="TextElement.Foreground">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.GrayTextBrushKey}" />
</Setter.Value>
</Setter>
<Trigger.Value>
<s:Boolean>False</s:Boolean>
</Trigger.Value>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Кроме того, я добавил x: Key к заголовку шаблона, чтобы я мог идентифицировать его в своем приложении. Теперь этот шаблон можно вставить в раздел Ресурсы для приложения WPF. Поскольку я тестирую это в оконном приложении WPF, я вставлю этот шаблон в Windows.Resources .

Также следует добавить ссылку на пространство имен s (используется для триггера IsEnabled ):

xmlns:s="clr-namespace:System;assembly=mscorlib"

Теперь я могу ссылаться на шаблон для TextBox внутри окна:

<TextBox Template="{StaticResource txt}" Background="Transparent" BorderThickness="0" Height="35" Name="textBox1" Width="254" />

Так и должно быть. Если вам не нужно изменять внешний вид элемента управления, когда он отключен, вы можете просто удалить триггер IsEnabled из шаблона, чтобы все, что у вас будет, это:

<ControlTemplate TargetType="TextBoxBase" x:Key="txt">

<Border CornerRadius="5" BorderThickness="1" BorderBrush="Black" x:Name="Bd" Background="{TemplateBinding Panel.Background}">

<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />

</Border>

</ControlTemplate>

Он будет правильно отображать границу, но не будет серого фона и измененного переднего плана, когда элемент управления явно установлен как неактивный.