Создание TextBox с наименованием

У меня имеется много TextBox полей, к каждому из которых имеется свой TextBlock с названием поля. Из-за того, что XAML разрастается до невероятных размеров, а все что указывается в TextBlock - это поле Text (остальное забил общим стилем). Назрел вопрос: как бы сделать элемент управления состоящий из TextBox и TextBlock с форматированием, имеющий в себе все свойства TextBox + поле Text для TextBlock. Пробовал создать его через UserControl, но в таком случае нужно прописывать очень много. Поэтому хотелось бы как-то унаследовать от TextBox. Если кто знает как - пожалуйста кодом. Спасибо.


Ответы (1 шт):

Автор решения: EvgeniyZ

Пример

Предположим, у нас есть такой вид

UI

Написан он следующим образом

XAML

<StackPanel>
    <StackPanel Margin="5">
        <TextBlock x:Name="name1" FontWeight="Medium" />
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Лет: " />
            <TextBox x:Name="age1" />
        </StackPanel>
    </StackPanel>

    <StackPanel Margin="5">
        <TextBlock x:Name="name2" FontWeight="Medium" />
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Лет: " />
            <TextBox x:Name="age2" />
        </StackPanel>
    </StackPanel>

    <StackPanel Margin="5">
        <TextBlock x:Name="name3" FontWeight="Medium" />
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Лет: " />
            <TextBox x:Name="age3" />
        </StackPanel>
    </StackPanel>

    <StackPanel Margin="5">
        <TextBlock x:Name="name4" FontWeight="Medium" />
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Лет: " />
            <TextBox x:Name="age4" />
        </StackPanel>
    </StackPanel>
</StackPanel>

C#

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        name1.Text = "Вася";
        age1.Text = "13";

        name2.Text = "Петя";
        age2.Text = "18";

        name3.Text = "Маша";
        age3.Text = "7";

        name4.Text = "Оля";
        age4.Text = "24";
    }
}

Многие новички именно так и пишут свой проект, не задумываясь о правильности того, что они делают. Ну а делают они все в корне неверно, ибо самое ключевое в WPF проекте, это XAML и Binding (привязки), если вы ими пренебрегаете, вы очень многого лишаетесь, как в плане удобства, так и в плане производительности.

Как поступить

Первым делом давайте взглянем на вид и подумаем, а не коллекция случаем это? Ведь если что-то имеет повторяющиеся "паттерны", то значит это можно описать одним классом и создать его множество раз, поместив в коллекцию, верно? Сделаем это, получив уже такой C# код:

public record User(string Name, int Age);

public partial class MainWindow : Window
{
    public List<User> Users { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        Users = [new("Вася", 13), new("Петя", 18), new("Маша", 7), new("Оля", 24)];
    }
}

Смотрите какой простой и логичный код, где есть конкретный класс User, имеющее в себе только те данные, которые относятся к конкретному человеку. А также есть простая коллекция Users, которая хранит в себе всех этих людей.

Имея коллекцию, давайте теперь адаптируем под нее XAML. За вывод коллекции отвечает ListBox (если нужно выделение) и ItemsControl (если выделение нам не нужно), также есть всякие DataGrid и пр., которые имеют разный вид отображения коллекции. В данном случае нам нужно просто продублировать определенный вид столько раз, сколько объектов в коллекции, а значит подойдет простой ItemsControl. XAML превратится тогда в такое:

<ItemsControl ItemsSource="{Binding Users}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="5">
                <TextBlock FontWeight="Medium" Text="{Binding Name}" />
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Лет: " />
                    <TextBox Text="{Binding Age}" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

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

Чтобы это все заработало, нам надо указать DataContext окну, это некий источник данных, в котором будут искаться все свойства для привязки. В коде выше, коллекцию я создал прям в классе окна, это плохо, лучше вынести в отдельный класс, но для примера сойдет, ну а раз данные у нас в окне, то и указываем окну в конструкторе DataContext = this;.

Как все сделали, запускаем проект и видим все тот-же вид, который и был, с тем-же набором данных, но уже написанный правильно, с использованием привязок. Обратите внимание, в XAML у меня нет ни одного x:Name.

UserControl или как сделать TextBox с наименованием

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

  1. Кликаем ПКМ на название проекта и выбираем "Добавить" - "Пользовательский элемент управления.

  2. Задаем ему нужное имя, в моем случае это будет просто UserView.

  3. Открываем .xaml.cs файл и пишем там после конструктора propdp и жмем TAB, вам студия вставит заготовку для свойства зависимости.

  4. Указываем этому свойству тип, имя, название UserControl, значение по умолчанию.

  5. В моем примере, я сделаю 2 свойство, заголовок и значение, получится следующее

    public string Title
    {
        get => (string)GetValue(TitleProperty);
        set => SetValue(TitleProperty, value);
    }
    
    public object Value
    {
        get => GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }
    
    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(UserView), new PropertyMetadata(default));
    
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(object), typeof(UserView), new PropertyMetadata(default));
    
  6. Открываем теперь .xaml файл и переносим туда вид.

  7. В UserControl так просто не привязать данные, вот тут имя и может пригодиться. Задаем самому UserControl значение x:Name, а привязки меняем на {Binding Title, ElementName=uc} (uc - имя, заданное в x:Name).

  8. Все, с контролом закончили. Возвращаемся в основной XAML и от куда забирали вид, меняем на <local:UserView Title="{Binding Name}" Value="{Binding Age}"/>? запускаем и радуемся результатом.

В итоге, весь наш изначальный XAML, с кучей дубликатов и тесной связью с C# кодом, превратился в простой и лаконичный:

<ItemsControl ItemsSource="{Binding Users}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:UserView Title="{Binding Name}" Value="{Binding Age}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Вот примерно такое у вас и должно быть.

→ Ссылка