Понимание WPF, выводящего класс WIndow

Я уверен, что это легко, но новичок для WPF с использованием С#. Я знаю о наследовании от классов и делал так много раз, например, в проектах С# WinForms...

public class MyClass : DerivedFromClass
{}

Однако, тупик в WPF и вот проблема. Я хочу создать собственный набор элементов управления, которые будут использоваться в качестве базовой линии для нового учебного проекта... предустановить мои собственные стили, цвета, фоны и другие функции. Нет проблем. Начните сначала с окна WPF и создайте "MyWindow".

Теперь я хочу использовать этот базовый "MyWindow" и подкласс THAT для еще одного класса MySubClassedWindow. Итак, я создаю новый класс Window, и по умолчанию VS2010 создает как конструкторскую, так и кодовую части формы. Я просматриваю код в MySubClassedWindow и нахожу

partial class MySubclassedWindow : Window
{}

В С# с использованием WinForms я просто изменил бы (и я включил ссылку на библиотеку классов, которая включает объявление MyWindow.

partial class MySubclassedWindow : MyWindow
{}

Когда я это сделаю, я получаю ошибку компиляции

Partial declarations of 'MyNameSpace.MySubclassedWindow' must not specify different base classes

Ответ 1

Ваш базовый класс должен быть просто файлом класса (не Window).

Итак, создайте WindowBase.cs

public class WindowBase : Window
{
    // ...
}

В MainWindow (например) измените файл xaml.cs, чтобы наследовать от WindowBase вместо

public partial class MainWindow : WindowBase
{
    public MainWindow()
    {
        InitializeComponent();
    }
    // ...
}

В MainWindow.xaml включите пространство имен для WindowBase и измените Окно на базу: WindowBase, подобный этому

<base:WindowBase x:Class="SubclassWindow.MainWindow"
                  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  xmlns:base="clr-namespace:NamespaceForWindowBase"
                  Title="MainWindow" Height="350" Width="525">
    <!--...-->
</base:WindowBase>

Ответ 2

Наличие базового класса Window приводит к критическому недостатку, а именно, что привязка к свойствам в вашем базовом классе гораздо труднее сделать (и принятый в настоящее время ответ не решает эту проблему). Какова точка наследования, если вы не можете ссылаться на базовые свойства? Я выяснил, как установить это после долгих часов, и хотел поделиться с тем, что другие будут избавлены от этой боли.

Возможно, вам придется использовать такие вещи, как преобразователи значений, на которые можно ссылаться только через статическое связывание, что в моем случае имело смысл иметь в классе WindowBase. Я включил пример, потому что мне было трудно использовать эти преобразователи последовательно в режиме проектирования и запуска.

Вы не можете установить свойство x: Name этого унаследованного окна через XAML, но вам может и не понадобиться делать это при использовании подхода ниже. Я включил пример того, как установить имя, потому что наследование из окна не позволит вам указать имя во время разработки в подклассе. Я не рекомендую полагаться на имя окна во время разработки, но установка d: DataContext должен заботиться о любых обязательных требованиях для вас.

Обратите внимание, что в режиме разработки, но не в режиме запуска, копия WindowBase (или класса, указанного в d: DataContext) будет создана в режиме разработки и использована как контекст привязки. Поэтому в очень специфических случаях вы можете видеть расхождения данных, но в подавляющем большинстве случаев такого подхода должно быть достаточно.

WindowBase.cs

`` ``

public class WindowBase : Window
{
    //User-Defined UI Configuration class containing System.Drawing.Color 
    //and Brush properties (platform-agnostic styling in your Project.Core.dll assembly)
    public UIStyle UIStyle => Core.UIStyle.Current;

    //IValueConverter that converts System.Drawing.Color properties 
    //into WPF-equivalent Colors and Brushes 
    //You can skip this if you do not need or did not implement your own ValueConverter
    public static IValueConverter UniversalValueConverter { get; } = new UniversalValueConverter();

    public WindowBase()
    {
        //Add window name to scope so that runtime properties can be referenced from XAML
        //(Name setting must be done here and not in xaml because this is a base class)
        //You probably won't need to, but working example is here in case you do.
        var ns = new NameScope();
        NameScope.SetNameScope(this, ns);
        ns["window"] = this;

        //Call Initialize Component via Reflection, so you do not need 
        //to call InitializeComponent() every time in your base class
        this.GetType()
            .GetMethod("InitializeComponent", 
                System.Reflection.BindingFlags.Public | 
                System.Reflection.BindingFlags.NonPublic | 
                System.Reflection.BindingFlags.Instance)
            .Invoke(this, null);

        //Set runtime DataContext - Designer mode will not run this code
        this.DataContext = this;
    }

    //Stub method here so that the above code can find it via reflection
    void InitializeComponent() { }
}  

SubClassWindow.xaml

<local:WindowBase
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:YourProjectNamespace"
        x:Class="YourProjectNamespace.SubClassWindow"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance Type= {x:Type local:WindowBase}, IsDesignTimeCreatable=True}"
        Title="SubClassWindow" Height="100" Width="300">
    <!--Design-time DataContext is set in d:DataContext. That option does not affect runtime data binding
        Replace local:WindowBase with local:SubClassWindow if you need to access properties in SubClassWindow-->
    <Grid Background="{Binding UIStyle.BackgroundColor, Converter={x:Static local:WindowBase.UniversalValueConverter}}"></Grid>
</local:WindowBase>

Ничего не нужно в коде SubClassWindow (даже не в конструкторе).