Как создать базовый класс для элементов управления

У меня есть базовая библиотека, которая определяет пользовательский элемент управления

BaseLib.dll:

UC.xaml.cs

<UserControl x:Class = "BaseLib.UC" ...

UC.xaml.cs

public abstract partial class UC : UserControl, IFoo
{
    public UC()
    {
        InitializeComponent();
    }

    protected abstract Foo();
}

Затем разные библиотеки могут наследовать от элемента управления и перегружать методы. Они реализуют только абстрактные методы и ничего не делают с базовым XAML.

lib2.dll

public class NewControl : UC
{
    protected override Foo(){ /* do stuff*/}
}

Базовая библиотека прекрасно компилируется, как и производные классы. Но когда я пытаюсь создать экземпляр производных элементов управления, я получаю эту ошибку в InitializeComponent() в конструкторе UC:

System.Exception: 'The component 'NewControl' does not have a resource identified by the URI '/BaseLib;component/UC.xaml'.'

Могу ли я что-то исправить с URI пакета где-нибудь, чтобы указать на baselib xaml? Я не знаю, куда бы я его положил. Я просмотрел похожие вопросы, но они не совсем соответствуют моим намерениям. Я не пытаюсь наследовать элементы WPF и изменять их внешний вид или макет.

Я думаю, вы хотите вызвать класс напрямую, используя его тег <namespace:NewControl></namespace:NewControl>, который является пользовательским элементом управления.

Felix Castor 16.03.2022 21:17

Что в файле UC.xaml? Абстрактный базовый класс не должен иметь частичный класс XAML.

mm8 17.03.2022 14:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Поэтому я сделал аналогичную структуру проекта для тестирования. Это прекрасно работает. Строка xmlns:local = "clr-namespace:PoopToTest" будет ссылаться на ваше пространство имен BaseLib.dll.

<Window
    x:Class = "PoopToTest.MainWindow"
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local = "clr-namespace:PoopToTest"
    xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title = "MainWindow"
    Width = "800"
    Height = "450"
    mc:Ignorable = "d">
    <Grid>
        <local:ActualControl>
            <TextBlock
                HorizontalAlignment = "Center"
                VerticalAlignment = "Center"
                FontSize = "50"
                Text = "Hello" />
        </local:ActualControl>
    </Grid>
</Window>

Базовый класс.

namespace PoopToTest
{
    public abstract class BaseControl : UserControl
    {
        public BaseControl()
        {
            //InitializeComponent() is not in UserControl.  Needed a place to break.
            int x = int.MaxValue;
        }

        protected abstract void Foo();
    }
}

Реализация.

namespace PoopToTest
{
    public class ActualControl : BaseControl
    {
        /// <inheritdoc />
        protected override void Foo()
        {
            //Do Stuff.
        }
    }
}
Ответ принят как подходящий

Чтобы создать базовый класс, расширяющий UserControl, вы должны следовать нескольким правилам.

  • Базовый тип не должен быть partial class (иметь файл XAML).
  • Поскольку базовому классу не разрешается иметь частичное определение класса XAML, базовый класс не должен вызывать InitializeComponent().
  • Производный неабстрактный тип, расширяющий базовый класс, должен быть partial class, имеющим определение класса XAML.
  • Корневой элемент XAML этого производного элемента управления должен быть базовым классом.

Если вы хотите повторно использовать фактическое представление (определение XAML), вы должны определить его как ресурс, например. как ControlTemplate.

Рекомендуемый подход — расширить ContentControl вместо UserControl (см. второй пример ниже). Этот подход не имеет ограничений по сравнению с расширением UserControl. Это очень просто, особенно если вы хотите расширить функции, не переопределяя внешний вид по умолчанию.

Решение 1. Создание базового класса с использованием UserControl в качестве суперкласса (не рекомендуется)

Базеусерконтрол.cs
Абстрактный базовый класс для использования в библиотеках.

public abstract class BaseUserControl : UserControl, IFoo
{
  protected abstract void Foo();
}

App.xaml
Определение BaseUserControlTemplate.

<Application.Resources>
  <ControlTemplate x:Key = "BaseUserControlTemplate" 
                   TargetType = "BaseUserControl">
    <TextBlock Text = "Reusable view from template" />
  </ControlTemplate>
</Application.Resources>

Library1UserControl.xaml.cs
Реализация пользовательской библиотеки BaseUserControl.

public partial class Library1UserControl : BaseUserControl
{
  public Library1UserControl()
  {
    InitializeComponent();
  }

  protected override void Foo()
  {}
}

Library1UserControl.xaml.cs
Определение представления Library1UserControl, которое повторно использует предопределенное (например, в App.xaml) BaseControlTemplate.

<BaseControl x:Class = "Library1UserControl"
             Template = "{StaticResource BaseUserControlTemplate}" />

Решение 2. Создание базового класса с использованием ContentControl или Control в качестве суперкласса (рекомендуется)

Если вы хотите явно определить представление как часть базового класса, дайте экстенту базового класса ContentControl (или Control) и определите значение по умолчанию Style в ресурсах Универсальный.xaml (файл Универсальный.xaml находится в папке Themes библиотеки управления ).
Таким образом, все производные классы будут автоматически наследовать представление по умолчанию (если только они явно не переопределяют представление по умолчанию Style).
Расширение ContentControl или Control обычно рекомендуется вместо использования UserControl.
Вам следует рассмотреть возможность расширения ContentControl вместо UserControl.

BaseControl.cs

public abstract class BaseControl : Control, IFoo
{
  static BaseControl()
  {    
    DefaultStyleKeyProperty.OverrideMetadata(typeof(BaseControl), new FrameworkPropertyMetadata(typeof(BaseControl)));
  }

  protected abstract void Foo();
}

Универсальный.xaml
Файл Универсальный.xaml находится в папке Темы библиотеки управления.

<ResourceDictionary>
  <Style TargetType = "BaseControl">
    <Setter Property = "Template">
      <Setter.Value>
        <ControlTemplate TargetType = "BaseControl">          
          <TextBlock Text = "Reusable view from template" />
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

Library1Control.cs
Производный тип автоматически наследует значения по умолчанию StyleControlTemplate), которые были определены в базовом типе.

public class Library1Control : BaseControl
{
  public Library1Control()
  {}

  protected override void Foo()
  {}
}

Мне это не нравится, но я полагаю, что нарушал правила :) Я приму этот ответ. Мои изменения пользовательского интерфейса настолько нулевые, что я, вероятно, просто передам реализацию, которую хочу, в качестве аргумента конструктора класса.

stevep 18.03.2022 13:30

Есть много способов расширить функциональность класса. Состав один. Просто обратите внимание, что механизму XAML требуется конструктор по умолчанию на случай, если элемент управления будет добавлен в XAML (например, через DataTemplate). Использование внедрения конструктора для элементов управления пользовательского интерфейса может быть проблематичным, особенно если элементы управления являются частью библиотеки. Создание прикрепленного поведения также может предоставить мощную альтернативу наследованию или композиции.

BionicCode 18.03.2022 13:48

Это то, что вы имеете в виду? wpf-controls.com/behaviors-and-attached-properties

stevep 18.03.2022 14:17

Да. Это прикрепленное свойство, которое обычно реализует функцию (поведение) на основе элемента, к которому оно прикреплено. Вы можете наблюдать за элементом, обрабатывая его события. Если это полезное решение, конечно, зависит от того, какую функцию вы хотите расширить.

BionicCode 18.03.2022 14:21

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

stevep 18.03.2022 14:56

Другие вопросы по теме