Search code examples
c#wpfxamldata-bindingwindow

Content sections for custom style window


Greeting, i have a window with custom style (that includes custom template), but now i want to define some sections in my window template layout, for example - window title bar, window content (the default place for items) and window footer, so i can then use it like this (or similar):

<Window Style="{StaticResource CustomWindow}">
    <Window.Header>
        <Grid x:Name="HeaderGrid">
        </Grid>
    </Window.Header>
    <Grid x:Name="ContentGrid">
    </Grid>
    <Window.Footer>
        <Grid x:Name="FooterGrid">
        </Grid>
    </Window.Footer>
</Window>

i thought i have to add corresponding dependency properties to .xaml.cs file of the style dictionary that contains my window style, but it's class is not inherited from anything, so it is not a DependencyObject, i tried adding dependency properties to the window that uses the style, so i can just bind ContentPresenters from template to it, but it does not allow to set the content of those properties from window's xaml, so, is there a way to achieve this?


Solution

  • to enable the usage of your example you should create a custom base class from which all your windows can inherit.

    Define the default layout of the base class SectionWindow. Simply define the related default Style in the /Themes/Generic.xaml file:

    Generic.xaml

    <Style TargetType="{x:Type local:SectionWindow}">
      <Setter Property="HorizontalContentAlignment"
              Value="Stretch" />
      <Setter Property="VerticalContentAlignment"
              Value="Stretch" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="local:SectionWindow">
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}">
              <Grid>
                <Grid.RowDefinitions>
                  <!-- Dynamic content header row -->
                  <RowDefinition Height="Auto" />
                  <!-- Dynamic content row -->
                  <RowDefinition />
                  <!-- Dynamic content footer row -->
                  <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
    
                <ContentPresenter Grid.Row="0"
                                  ContentSource="Header"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
    
                <ContentPresenter Grid.Row="1"
                                  ContentSource="Content"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
    
                <ContentPresenter Grid.Row="2"
                                  ContentSource="Footer"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
              </Grid>
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
    

    Then define the base class. This is a basic example. It's recommended to define corresponding template properties for each section property. For example, the Header property should have an associated HeaderTemplate dependency property of type DataTemplate. Because the ContentPresenter in the default Style use the ContentPresenter.ContentSource property, the template will be automatically mapped.

    For the Content property, we can reuse the inherited Window.Content property.

    SectionWindow.cs

    public class SectionWindow : Window
    {
      public object Header
      {
        get => (object)GetValue(HeaderProperty);
        set => SetValue(HeaderProperty, value);
      }
    
      public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
        "Header", 
        typeof(object), 
        typeof(SectionWindow), 
        new PropertyMetadata(default));
    
      public object Footer
      {
        get => (object)GetValue(FooterProperty);
        set => SetValue(FooterProperty, value);
      }
    
      public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(
        "Footer",
        typeof(object),
        typeof(SectionWindow),
        new PropertyMetadata(default));
    
      static SectionWindow() => DefaultStyleKeyProperty.OverrideMetadata(typeof(SectionWindow), new FrameworkPropertyMetadata(typeof(SectionWindow)));
    }
    

    Then you can use the new window SectionWindow as follows:

    MainWindow.xaml.cs

    public partial class MainWindow : SectionWindow
    {
      public ExampleWindow() 
      {
        InitializeComponent();
      }
    }
    

    MainWindow.xaml

    <local:SectionWindow x:Class="Net.Wpf.MainWindow" ...>
      <local:SectionWindow.Header>
        <Border Background="Red">
          <TextBlock Text="Header content" />
        </Border>
      </local:SectionWindow.Header>
    
      <local:SectionWindow.Content>
        <Border Background="Green">
          <TextBlock Text="Window content" />
        </Border>
      </local:SectionWindow.Content>
    
      <local:SectionWindow.Footer>
        <Border Background="Blue">
          <TextBlock Text="Footer content" />
        </Border>
      </local:SectionWindow.Footer>
    </local:SectionWindow>