Подтвердить что ты не робот

Наследование XAML UserControl

Начиная с Java, я действительно привык к обычной практике, когда речь заходит о создании компонентов GUI: обычно я делаю какой-то базовый класс, который содержит все общие объекты для моих компонентов графического интерфейса, а затем я расширяю его.

Итак, в основном, это то, что я хотел бы достичь с помощью С# и XAML.

Чтобы сделать вопрос понятным, вот пример (который не работает!) того, что я делаю:

У нас есть базовый класс со своим собственным XAML

<UserControl x:Class="BaseClass"
    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"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">

    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
        <Border BorderBrush="Aqua" BorderThickness="10" CornerRadius="10" x:Name="Border" HorizontalAlignment="Left" Height="480" VerticalAlignment="Top" Width="480"/>

    </Grid>
</UserControl>

а затем у нас есть класс, который расширяет первый

<base:BaseClass x:Class="DerivedClass"
    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:base="clr-namespace:BaseClass"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="60" d:DesignWidth="200">

    <Grid x:Name="LayoutRoot" Margin="0" Width="200" Height="60" MaxWidth="200" MaxHeight="60" Background="{StaticResource PhoneAccentBrush}">        
        <TextBlock x:Name="dummyText" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="Dummy Plugin" VerticalAlignment="Top" Height="40" Width="180" Foreground="White" TextAlignment="Center"/>
    </Grid>
</base:BaseClass>

Начиная с 2 кодов XAML, я бы хотел, чтобы DerivedClass был в контейнере BaseClass. Это позволит мне обмениваться компонентами между различными производными классами без необходимости писать код каждый раз, когда он мне нужен.

Например, если я хочу, чтобы все мои компоненты имели эту закругленную границу, я хотел бы просто поместить ее в класс басов, а затем использовать ее во всех производных, не переписывая ее.

Конечно, каждый класс С# имеет свой собственный метод InitializeComponent(), и это, вероятно, означает, что производный компонент будет создавать свой собственный контент, удалив базовый класс "один".

Удаление метода из конструктора DerivedClass дает базовое содержимое даже в производном классе, но, конечно, я теряю все, что я сделал в окне дизайна XAML DerivedClass.

Вызов базового конструктора из DerivedClass не имеет никакого эффекта, так как он вызывается перед производным InitializeComponent().

Итак, возникает вопрос: как я могу использовать дизайн XAML из базового класса в производный, не нарушая конструкцию XAML производного класса? Есть ли способ просто добавить контент в базовый класс при работе с самим дизайнером?

(Я знаю, что я могу удалить XAML для производного класса и делать то, что хочу делать по коду, но я хочу знать, могу ли я сделать это только с дизайнером, поскольку я не хочу писать свой графический интерфейс когда у меня есть дизайнер)

EDIT:

После ответа HighCore я сделал что-то, что работает на Windows Phone, но я не уверен, что я поступаю правильно (да, это работает, но, возможно, это просто неправильно!).

Вот что я сделал:

BaseControl.xaml

<UserControl x:Class="TestInheritance.BaseControl"
    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"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">


     <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">        
        <TextBlock HorizontalAlignment="Center">BASE</TextBlock>        
        <ContentPresenter Name="Presenter" Content="{Binding PresenterContent}"/>
    </Grid>
</UserControl>

BaseControl.xaml.cs

namespace TestInheritance
{
    public partial class BaseControl : UserControl
    {

        public Grid PresenterContent { get; set; }        

        public BaseControl()
        {
            DataContext = this;
            InitializeComponent();            
        }
    }
}

DerivedControl.xaml

<local:BaseControl x:Class="TestInheritance.DerivedControl"
    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:TestInheritance"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480">

    <local:BaseControl.PresenterContent>
        <Grid>
            <TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Center">DERIVED</TextBlock>
        </Grid>
    </local:BaseControl.PresenterContent>
</local:BaseControl>

Обратите внимание, что DerivedClass является экземпляром BaseClass, поскольку мне нужно, чтобы у него были некоторые общие свойства/методы по другим причинам.

Что вы думаете о моем решении? Имеет ли смысл?

4b9b3361

Ответ 1

Хорошо, позвольте мне разбить это на части:

Переход с Java

Забудьте java. Это действительно устаревший язык, который не развился с 90-х годов. С# в миллион раз лучше, и WPF - лучшая структура пользовательского интерфейса до настоящего времени.

из того, что я видел, java-интерфейсы пользовательского интерфейса, такие как swing, концептуально похожи на .Net winforms, который также был заменен WPF.

WPF (и его братья на основе XAML) принципиально отличаются от любых других фреймворков из-за их расширенных возможностей для настройки через Стили и шаблоны и поддержка DataBinding.

Из-за этого при запуске в WPF требуется Значимый Mindshift.


Я обычно делаю какой-то базовый класс, который содержит все общие объекты для моих компонентов графического интерфейса, а затем я расширяю его.

В WPF существует Модель контента, которая устраняет необходимость в наследовании и других раздутых ненужных практиках, введя возможность поставить "что-нибудь" внутри ничего ".

Например, a Button можно определить следующим образом:

<Button>
    <Button.Content>
        <StackPanel Orientation="Horizontal">
            <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
            <TextBlock Text="Click Me"/>
        </StackPanel>
    <Button.Content>
 </Button>

что приводит к

enter image description here

Нет необходимости наследовать от Button, чтобы определить его содержимое.

Существует дополнительное преимущество, которое WPF предоставляет и действительно удобно, ContentProperty Атрибут, определяющий, что содержимое тегов XAML <Button> </Button> представляет. Button получен из ContentControl, который объявляется следующим образом:

//Declaration of the System.Windows.Control.ContentControl class,
//inside the PresentationFramework.dll assembly
//...  
[ContentProperty("Content")]
public class ContentControl: Control //...
{
   //...
}

Это означает, что следующий XAML функционально идентичен следующему:

<Button>
   <StackPanel Orientation="Horizontal">
       <Ellipse Fill="Red" Height="10" Width="10" Margin="2"/>
       <TextBlock Text="Click Me"/>
    </StackPanel>
</Button>
  • Обратите внимание, что мы удалили тег <Button.Content>, потому что этот атрибут ContentProperty позаботится об этом.

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


то, что я хотел бы сделать, - это заставить DerivedClass в BaseClass контейнер.

Существует несколько способов добиться этого, одним из них является использование ControlTemplates и определение определенного контейнера внутри XAML, который будет размещать контент:

<UserControl x:Class="BaseClass">
    <UserControl.Template>
        <ControlTemplate TargetType="UserControl">
            <DockPanel>
                <TextBlock DockPanel.Dock="Top" Text="I'm the Container"/>

                <!-- This is where the Hosted Content will be placed -->
                <ContentPresenter ContentSource="Content"/>
            </DockPanel>
        </ControlTemplate>
     <UserControl.Template>
</UserControl>

Затем вы можете повторно использовать этот шаблон следующим образом:

<Window>
   <my:BaseClass>
       <Border Background="Gray" BorderBrush="Blue" BorderThickness="2"
               VerticalAlignment="Center" HorizontalAlignment="Center">
           <TextBlock Text="Im the Hosted Content" Foreground="AliceBlue"/>
       </Border>
   </my:BaseClass>
</Window>

что приводит к:

enter image description here

Нет необходимости в наследовании или какой-либо процедурной обработке кода.


Еще один очень важный аспект при запуске в WPF, который подробно объясняется в ссылке "Значительный Mindshift" выше, - вот что я говорю всем здесь:

Изучите MVVM, прежде чем писать одну строку кода в WPF

  • В большинстве случаев вы не помещаете никакого кода в элементы интерфейса WPF, потому что большинство вещей может быть достигнуто с помощью DataBinding (см. ссылку "DataBinding" выше) или путем применения Многоразового Приложенные действия или Прикрепленные свойства. Только код VIEW-Specific должен быть помещен в код позади, который не относится к Data или Business Logic

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

    txtLastName.Text = person.LastName;
    txtFirstName.Text = person.FirstName;
    btnSubmit.IsEnabled = person.IsActive;
    

    и тому подобное, полностью не используется в WPF, опять же из-за DataBinding.


Другая концепция, которая обеспечивает высокую гибкость при отображении данных в пользовательском интерфейсе, - это WPF DataTemplates, который позволяет вам определить конкретный UI, который будет использоваться, когда некоторый тип данных "отображается" на экране.


Из-за всего вышеизложенного WPF принципиально отличается от большинства базовых интерфейсов пользовательского интерфейса и тем самым устраняет необходимость в всех ужасных шаблонах и хаках, которые являются общими в других рамках,

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

Сообщите мне, нужна ли вам дополнительная помощь.

Ответ 2

Насколько я знаю, вывод UI как есть, недоступен, если только для ресурсов XAML (стили для ex). Может быть, причина в том, что менеджер пользовательского интерфейса не может догадаться, куда положить расширенный код XAML: Представьте базовый интерфейс следующим образом:

<UserControl > 
 <Grid>
  <Border/>
 </Grid>
</UserControl>

и полученный UI:

<UserControl> 
 <DockPanel>
  <Button/>
 </DockPanel>
</UserControl>

Каким будет результат после микса?

прямой ответ может быть:

  <UserControl > 
    <Grid>
     <Border/>
    </Grid>
    <DockPanel>
     <Button/>
    </DockPanel>
  </UserControl>

что невозможно.

Как насчет проблем с внедрением/совместимостью управления?