2

Есть абстрактный класс, который наследуется от RadioButton.

public abstract class TestClass: RadioButton
{
}

Также есть шаблон для отображения экземпляров этого класса:

<DataTemplate DataType="{x:Type local:TestClass}">
    <StackPanel>
        <Button Content="Кнопка" Width="100" />
    </StackPanel>
</DataTemplate>

В MainWindow должен отображаться один из выбранных экземпляров TestClass.

<ContentControl  Content="{Binding SelectedObject}"/>

При создании экземпляра TestClass у меня отображается обычный RadioButton вместо нужного шаблона данных. Если же я убираю наследование класса TestClass от RadioButton, то все отображается нормально. Также если по ключу подставляю шаблон, то все работает. Получается, что шаблон данных RadioButton'a перекрывает мой шаблон из ресурсов.

Как это исправить?

trydex
  • 3,742

1 Answers1

3

Вы создаёте custom control неправильно. Проще всего в Visual Studio пойти через Add → New Item → WPF → Custom Control, при этом произойдёт следующее (вы можете сделать то же самое вручную):

  1. Создастся класс такого вот вида:

    public class TestClass : Control
    {
        static TestClass()
        {
            // объявляем, что у нашего класса может быть собственный стиль,
            // а не тупо наследуем стиль родительского объекта
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(TestClass), new FrameworkPropertyMetadata(typeof(TestClass)));
        }
    }
    

    Вам нужно будет поменять Control на RadioButton.

  2. Создастся в корне проекта каталог Themes, и в нём файл Generic.xaml (название важно!), в котором будет расположен стиль и шаблон для вашего контрола. Он будет выглядеть как-то так:

    <Style TargetType="{x:Type local:TestClass}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TestClass}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

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


Почему текущий код не работает? DataTemplate используется не для визуальных элементов, а для отображения невизуальных данных, таких как строка или VM-объект. Если вы не наследуетесь от UI-класса, ваш объект расценивается как объект невизуального класса, и для него работает DataTemplate. Но это неправильно, т. к. вы-то хотите создать UI-элемент!

А когда вы наследуетесь от RadioButton, то ваш объект становится UI-объектом, и для его отображения используется стиль по умолчанию. Поскольку вы такой стиль не определили, используется, как вы сами заметили, стиль родительского объекта.


На самом деле, использование custom control'а — довольно редкая штука. Если вам нужно просто отобразить данные, вы идёте слишком сложным путём: вам не нужно наследоваться от RadioButton'а, а просто использовать его в шаблоне и привязаться к данным через Binding.

VladD
  • 206,799
  • У меня несколько другая задача.

    На форме есть N-е количество RadioButton'ов (и по мере выполнения программы они могут добавляться пользователем) и если какой-то из них выбирается, то нужно отобразить информацию из его свойств на отдельной панели в определенном виде. К примеру, если выбрана одна радиокнопка, то на панели отображаются 2 textbox'a, если другая, то 3 кнопки и т.д. Таких вариантов достаточно много. Поэтому я наследую от RadioButton, т.к. мне нужно хранить дополнительную информацию в свойствах.

    Подскажите, как такое лучше сделать?

    – trydex Nov 04 '16 at 11:09
  • RadioButton'ы должны находиться в Canvas. – trydex Nov 04 '16 at 11:10
  • 1
    @maxwell: Например, так: вы кладёте всю нужную информацию в VM-класс, привязываете к его свойствам RadioButton, а при отображении свойств привязываете к той же VM панель со свойствами. Вы ведь пользуетесь паттерном MVVM? – VladD Nov 04 '16 at 11:56
  • Именно на этом приложении решил изучить MVVM, поэтому пока не совсем понятен ваш вариант реализации. Если сделать стиль для ContentControl'a и через DataTrigger отслеживать тип выбранного объекта и в зависимости от этого возвращать нужный DataTemplate? Так делать правильно? – trydex Nov 04 '16 at 12:21
  • 1
    @maxwell: Не, чё-то слишком сложно. У вас должна быть коллекция VM, её вы выкладываете на Canvas через ItemsControl (как здесь), прибиндить SelectedItem к выбранному элементу, прибиндить это всё к полю SelectedVM в вашей охватывающей VM. А панель со свойствами тоже прибиндить к SelectedVM. – VladD Nov 04 '16 at 12:48
  • А как панель будет определять для какого элемента ей вывести 3 кнопки, а для какого только textbox? Не могу понять в каком виде и где нужно хранить эти характеристики. Ну т.е. сейчас я ведь тоже биндю панель к выбранном объекту. – trydex Nov 04 '16 at 13:32
  • @maxwell: А почему для некоторых нужно выводить одно, а для некоторых — другое? Может быть, это сущности разного типа? Тогда пусть это будут FirstVM и SecondVM, наследуемые от общего VM. – VladD Nov 04 '16 at 13:34
  • Ну это как, к примеру, в графическом редакторе: есть карандаш, у которого есть свойства толщина, цвет. А есть кисть, у которой помимо этого добавляется возможность выбора формы кисти: круглая, квадратная и т.п.. Примерно то же самое и у меня. – trydex Nov 04 '16 at 13:41
  • изначально я наследовал классы от Radiobatton,т.к. одновременно может быть выбран только один инструмент. Т.е. у меня есть абстрактный класс от Radiobatton, описывающий общие характеристики для всех инструментов. Затем Карандаш наследуемый от абстрактного класса, затем Кисть, которая наследуется от Карандаша. И таких инструментов множество. Плюс по мере выполнения программы пользователь может выбрать Карандаш, отредактировать его свойства, и сохранить как новый инструмент. – trydex Nov 04 '16 at 13:52
  • Поэтому у меня в MainVM было свойство SelectedObject типа абстрактного класса, в котором хранился выбранный инструмент на данный момент. К которому я и биндил панель. И коллекция всех инструментов. – trydex Nov 04 '16 at 13:54
  • 1
    Ну, карандаш и кисть — это разные объекты, так что пусть будет PencilVM и BrushVM. Только их наследовать нужно не от RadioButton (потому что кисть — это не RadioButton!), а от какой-нибудь DrawingToolVM. – VladD Nov 04 '16 at 14:25
  • Подскажите, а при такой реализации, где нужно реализовать события (или команды?) рисования: MainVM, PencilVM или же в изначальном классе Pencil ? – trydex Nov 06 '16 at 12:50
  • 1
    Реализация рисования карандашом должна быть, по идее, в PencilVM. Возможно, при этом будут использоваться внутренние вспомогательные классы, или работа будет делегироваться модели (но это уже внутренние подробности, с точки зрения VM команда выполняется внутри PencilVM). – VladD Nov 06 '16 at 13:16