Наследование 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
, поскольку мне нужно, чтобы у него были некоторые общие свойства/методы по другим причинам.
Что вы думаете о моем решении? Имеет ли смысл?
Ответы
Ответ 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>
что невозможно.
Как насчет проблем с внедрением/совместимостью управления?