In WinForms it was possible to make a window with elements. Then create another window inherited from the first one and add more elements to it. How does WPF do this? Something doesn’t work out with me.
3 answers
This can be done, but rather difficult, and requires well-known technical skills.
Let's look at an example where you need to make a window with an additional text message below.
First, you need to inherit from the Window
class:
[TemplatePart(Name = "PART_MessageCountHost", Type = typeof(Panel))] // будет объяснено позже class WindowWithMessage : Window {
and define the metadata override so that the styles defined in the ResourceDictionary
without a key are picked up:
static WindowWithMessage() { DefaultStyleKeyProperty.OverrideMetadata( typeof(WindowWithMessage), new FrameworkPropertyMetadata(typeof(WindowWithMessage))); }
Then, you need the text itself as a window property:
#region dp string MessageText public string MessageText { get { return (string)GetValue(MessageTextProperty); } set { SetValue(MessageTextProperty, value); } } public static readonly DependencyProperty MessageTextProperty = DependencyProperty.Register( "MessageText", typeof(string), typeof(WindowWithMessage), new UIPropertyMetadata(OnMessageChanged)); #endregion
When the MessageText
is OnMessageChanged
will be called.
In principle, we don’t really need to know where exactly it will be shown - this is, after all, a matter of style / template. But suppose we need custom logic: we want to count how many times the message has changed.
int messageCount = 0; static void OnMessageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { WindowWithMessage self = (WindowWithMessage)d; self.messageCount++; self.UpdateMessageCount(); }
To do this, we need to know where to display it. To do this, we need to ask the templates to tell us the necessary information. To do this, use the TemplatePart
declaration before the class (see above).
Panel messageCountHost;
The visual element for the message counter can change only when the template is applied.
public override void OnApplyTemplate() { base.OnApplyTemplate(); messageCountHost = GetTemplateChild("PART_MessageCountHost") as Panel; UpdateMessageCount(); } void UpdateButtons() { if (messageCountHost == null) return; messageCountHost.Children.Clear(); var textControl = new TextBlock() { Text = messageCount.ToString() }; messageCountHost.Children.Add(textControl); } }
Okay, that was a lookless control. Now you need to write the visual part.
To do this, you must create a Themes subdirectory in the root of your project, and in it ResourceDictionary
Generic.xaml
. Names are important, do not change them. If you already have such a file, just add a style to it.
<Style TargetType="{x:Type code:WindowWithMessage}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type code:WindowWithMessage}"> <Grid Background="{TemplateBinding Background}"> <AdornerDecorator> <Grid Margin="{TemplateBinding Padding}"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ContentPresenter Grid.Row="0" Content="{TemplateBinding Content}"/> <StackPanel Grid.Row="1" Orientation="Vertical"> <TextBlock Text="{Binding MessageText}"/> <Panel x:Name="PART_MessageCountHost"/> </StackPanel> </Grid> </AdornerDecorator> <ResizeGrip x:Name="WindowResizeGrip" HorizontalAlignment="Right" VerticalAlignment="Bottom" Visibility="Collapsed" IsTabStop="false" /> </Grid> <ControlTemplate.Triggers> <Trigger Property="ResizeMode" Value="CanResizeWithGrip"> <Setter TargetName="WindowResizeGrip" Property="Visibility" Value="Visible" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Background" Value="White"/> <Setter Property="Padding" Value="12"/> </Style>
Now you can use the new window as usual:
<code:WindowWithMessage x:Class="Namespace.MyMainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:code="clr-namespace:YourLibrary" Width="400" Height="600" SizeToContent="WidthAndHeight" MessageText="Test"> <Grid Background="Blue"> <!-- content --> </Grid> </code:WindowWithMessage>
- @Nodon: Finished. - VladD
- oneIt is a pity that I just can not understand it, because the skills are not enough. But thanks for the answer, be sure to figure it out and play around with it. - Nodon
- @Nodon: UserControls are a good idea too. They do not replace the window, but allow you to collect UI from the pieces. They are likely to be more useful to you than the technique of writing your window. - VladD
- But UserControls do not allow to simply put content inside themselves. With a window such a trick passes without problems. - VladD
- Well, I already have the experience to write my controls, I even managed to write my own panel with 0 (oh, how much rake I collected). I have now made a sample window in which there are a bunch of elements, some even overlap each other. The visibility of elements is tied to the properties of the template ViewModel - Nodon
There is no display inheritance in WPF. Instead, you can use UserControls. example:
<Window xmlns:local="clr-namespace:MyNamespace.MyUserControl" > <StackPanel> <local:MyUserControl /> <Button Content="OK" Click="OkClickHandler" /> <Button Content="Cancel" Click="CancelClickHandler" /> </StackPanel> </Window>
- I have already done a lot of controls. The main thing for me is how they will be located on the screen. This location is always identical, the only difference is that somewhere there are additional buttons or some other elements - Nodon
Hi, yes, the inheritance of UI in WPF is done so-so ... In order to be able to inherit the UI, you have to write code in C # and not in xaml
public class BaseWindow : Window { protected override void OnContentChanged(object oldContent, object newContent) { created = false; var content = (IAddChild)newContent; // Создаем табличку для примера var g = new Grid(); var groupsColumns = new ColumnDefinition(); var b = new Binding("GroupWidth"); groupsColumns.SetBinding(ColumnDefinition.WidthProperty, b); g.ColumnDefinitions.Add(groupsColumns); g.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); g.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) }); content.AddChild(g); } bool created; // пригодится в событии Loaded }
After that we can redefine Window
<base:BaseWindow xmlns:local="clr-namespace:MyNamespace.MyUserControl" xmlns:base="clr-namespace:MyNamespace.BaseWindow" > <StackPanel> <local:MyUserControl /> <Button Content="OK" Click="OkClickHandler" /> <Button Content="Cancel" Click="CancelClickHandler" /> </StackPanel> </base:BaseWindow>