Custom Control c# WPF

239
17 февраля 2019, 16:00

Как сделать Custom Control как PasswordBox где слева от passwordboxa будет textblock(значок замка или другой символ). Тоесть объеденить passwordbox и textblock в 1 control. У которого я смогу получать значения Password. Была идея сделать у PasswordBoxa ControlTemplate из textblocka и passwordbox`a Но мне также нужно чтоб в MainWindow.xaml я мог изменять стиль

cs:CustomPassBox Style={StaticResource DefaultStyle}>

Но когда я так определяю стиль у контрола то controlTemplate исчезает.

Answer 1

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

Использование стилей:

Тут смысл в чем, мы делаем некий базовый стиль, в котором переопределяем то, что нам нужно и добавляем новое.

К примеру имеем такой стиль для PasswordBox:

<Style x:Key="MainStyle" TargetType="PasswordBox">
    <Setter Property="Height" Value="30"/>
    <Setter Property="Width" Value="150"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <Viewbox>
                        <Canvas Width="24" Height="24">
                            <Path Data="M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" Fill="Black" />
                        </Canvas>
                    </Viewbox>
                    <Border Grid.Column="1" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                        <ScrollViewer x:Name="PART_ContentHost" VerticalAlignment="Center" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

При применении его мы получим следующий результат:

<PasswordBox Style="{StaticResource MainStyle}" Password="1234" />

Теперь мы допустим хотим задать какой нибудь стиль к примеру для ошибок, тогда создаем другой стиль и задаем ему BaseOn, что унаследует все от базового стиля:

<Style x:Key="ErrorStyle" TargetType="PasswordBox" BasedOn="{StaticResource MainStyle}">
    <Setter Property="Background" Value="#99DE0000"/>
    <Setter Property="Foreground" Value="White"/>
</Style>

Применяем и смотрим результат:

<PasswordBox Style="{StaticResource ErrorStyle}" Password="1234" />

Использование UserControl:

Тут все примерно тоже самое, но только есть еще такая вещь, как DependencyProperty. С ее помощью мы можем передавать в наш Control не стандартные свойства (как примеру у PasswordBox есть свойство Password, мы можем сделать тоже самое но допустим для переопределения иконки), UserControl также поддерживает стили. Давайте создадим что то простенькое:

Добавим UserControl с таким стилем:

<UserControl x:Class="WpfApp1.CustomPasswordBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             x:Name="uc"
             Height="30" Width="150">
    <UserControl.Resources>
        <StreamGeometry x:Key="DefaultData">
            M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z
        </StreamGeometry>
    </UserControl.Resources>
    <UserControl.Template>
        <ControlTemplate TargetType="UserControl">
            <ContentPresenter/>
        </ControlTemplate>
    </UserControl.Template>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Viewbox>
            <Canvas Width="24" Height="24">
                <Path Data="{Binding ElementName=uc, Path=Icon, FallbackValue={StaticResource DefaultData}, TargetNullValue={StaticResource DefaultData}}" Fill="Black" />
            </Canvas>
        </Viewbox>
        <PasswordBox x:Name="PBox" Grid.Column="1" Foreground="{Binding ElementName=uc, Path=Foreground}" Background="{Binding ElementName=uc, Path=Background}" VerticalContentAlignment="Center" />
    </Grid>
</UserControl>

И определим пару DP:

public partial class CustomPasswordBox : UserControl
{
    public CustomPasswordBox()
    {
        InitializeComponent();
        PBox.PasswordChanged += (sender, args) => {
            Password = ((PasswordBox) sender).SecurePassword;
        };
    }
    public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register(
        "Password", typeof(SecureString), typeof(CustomPasswordBox), new PropertyMetadata(default(SecureString)));
    public SecureString Password
    {
        get => (SecureString)GetValue(PasswordProperty);
        set => SetValue(PasswordProperty, value);
    }
    public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
        "Icon", typeof(StreamGeometry), typeof(CustomPasswordBox), new PropertyMetadata(default(StreamGeometry)));
    public StreamGeometry Icon
    {
        get => (StreamGeometry) GetValue(IconProperty);
        set => SetValue(IconProperty, value);
    }
}

Тут я для примера задал два DependencyProperty:

  1. Password - тут есть некие трудности, а если быть точнее, то в WPF пароль не должен храниться в памяти в незащищенном виде, по этому просто так привязать пароль не удастся, приходится передавать его через PasswordChanged событие. В интернете много примеров как это обойти, будет интересно, думаю найдете...
  2. Icon - Простая геометрия нашей векторной иконки.

Теперь давайте вызовем это и посмотрим на результат:

<local:CustomPasswordBox />

Создадим стиль под наш новый UserControl, переопределим цвета и иконку:

<StreamGeometry x:Key="KeyIcon">
    M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21M12,6A3,3 0 0,1 15,9C15,10.31 14.17,11.42 13,11.83V14H15V16H13V18H11V11.83C9.83,11.42 9,10.31 9,9A3,3 0 0,1 12,6M12,8A1,1 0 0,0 11,9A1,1 0 0,0 12,10A1,1 0 0,0 13,9A1,1 0 0,0 12,8Z
</StreamGeometry>
<Style x:Key="ErrorStyle" TargetType="local:CustomPasswordBox">
    <Setter Property="Background" Value="#99DE0000"/>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="Icon" Value="{StaticResource KeyIcon}"/>
</Style>

Результат:

<local:CustomPasswordBox Style="{StaticResource ErrorStyle}" />

Собственно вот так довольно просто мы можем работать с элементами.
Удачи в изучении WPF!

READ ALSO
Как конвертировать строку из парсера в число C#

Как конвертировать строку из парсера в число C#

Вопрос 1: Через парсер получаем строку "10д 5ч"(10 дней и 5 часов) Надо ее конвертировать и получить время в часах (10*24+5) и получить число 245

101
Ввод без перехода на следующую строку C#

Ввод без перехода на следующую строку C#

Я хочу сделать, чтоб при вводе данных в C# не выполнялся переход на новую строку в консоли, это возможно? Например, чтоб консоль выглядела не так:

148
Как программно в dataGridView сделать поле ComboBox?

Как программно в dataGridView сделать поле ComboBox?

Как программно в dataGridView сделать поле ComboBox?

162
Необработанное исключение типа &ldquo;System.InvalidOperationException&rdquo; в EntityFramework.dll

Необработанное исключение типа “System.InvalidOperationException” в EntityFramework.dll

пишу приложение WPF с веб сервисом wfc, в веб сервесе модель базы данных entity framework, когда пытаюсь передать запрос из клиента на добавление записи...

119