Search code examples
c#wpfavalondock

AvalonDock: Binding to LayoutAnchorablePane position?


I am creating a custom theme for my WPF application that uses the AvalonDock docking framework.
I have already opened a GitHub issue for my question on the AvalonDock repo but I'm hoping I can get an answer faster here (and ready to put a bounty on this ASAP).


In my custom theme I have moved the tab items for the LayoutAnchorablePane to stack vertically on the left side, and the pane uses a Grid with column sizes Auto, *, Auto.
I would like to write a Trigger for the style that moves the tabs from the left column to the right column when the LayoutAnchorablePane is attached to the right side of the root layout panel. (So that the tabs are always on the outside)

Here is the relevant section of my theme's XAML that I am trying to put the trigger on. This is almost identical to the LayoutAnchorablePaneControl template from the generic.xaml style in AvalonDock:

<Grid
    ClipToBounds="true"
    KeyboardNavigation.TabNavigation="Local"
    SnapsToDevicePixels="true">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
    <ColumnDefinition Width="*" />
    <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <!--  Following border is required to catch mouse events  -->
    <Border Grid.ColumnSpan="3" Background="Transparent" />
    <StackPanel
    x:Name="HeaderPanel"
    Width="40"
    Grid.Column="0"
    Panel.ZIndex="1"
    IsItemsHost="true"
    KeyboardNavigation.TabIndex="1" />
    <Border
    x:Name="ContentPanel"
    Grid.Column="1"
    Background="Transparent"
    BorderThickness="2"
    BorderBrush="{StaticResource PrimaryBrush}"
    KeyboardNavigation.DirectionalNavigation="Contained"
    KeyboardNavigation.TabIndex="2"
    KeyboardNavigation.TabNavigation="Cycle">
    <ContentPresenter
            x:Name="PART_SelectedContentHost"
            ContentSource="SelectedContent"
            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
    </Border>
</Grid>
<ControlTemplate.Triggers>
    <DataTrigger Binding="{Binding ??? }">
        <Setter TargetName="HeaderPanel" Property="Grid.Column" Value="2"/>
    </DataTrigger>
</ControlTemplate.Triggers>

As far as I can tell, there is no property on LayoutAnchorablePane or any of its interfaces that exposes which side of the layout the pane is on. So I'm lost for what I can put in {Binding ??? } in my DataTrigger.

It seems like I need to implement the property myself and use my own builds of AvalonDock. I would like to avoid this if it's at all possible; So maybe there's some clever MarkupExtension or Converter idea I could implement in my own code? Maybe my assumption that this can be done with DataTrigger can be challenged too. I would be happy to use a completely code-behind solution for this.


Solution

  • Thanks to BionicCode for the really detailed answer, I ended up only needing a few hints from that answer to solve the problem in my own way. So I thought it would be worth sharing my code too.


    LayoutAnchorablePaneControl inherits from TabControl so it already has the TabStripPlacement property that the style can bind to on its templated parent.

    So the new style replaces Grid with DockPanel and looks like this:

    <DockPanel
        ClipToBounds="true"
        KeyboardNavigation.TabNavigation="Local"
        SnapsToDevicePixels="true">
        <!--  Following border is required to catch mouse events  -->
        <Border Background="Transparent" />
        <StackPanel
        x:Name="HeaderPanel"
        Width="40"
        DockPanel.Dock="{TemplateBinding TapStripPlacement}"
        Panel.ZIndex="1"
        IsItemsHost="true"
        KeyboardNavigation.TabIndex="1" />
        <Border
        x:Name="ContentPanel"
        Background="Transparent"
        BorderThickness="2"
        BorderBrush="{StaticResource PrimaryBrush}"
        KeyboardNavigation.DirectionalNavigation="Contained"
        KeyboardNavigation.TabIndex="2"
        KeyboardNavigation.TabNavigation="Cycle">
        <ContentPresenter
                x:Name="PART_SelectedContentHost"
                ContentSource="SelectedContent"
                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
        </Border>
    </DockPanel>
    

    Now this style will move the tabs to any of the sides (left/right/top/bottom) depending on the LayoutAnchorablePaneControl.TabStripPlacement property.

    In code behind (for the Window that has the DockingManager) I attached an event handler to DockingManager.Layout.Updated that runs the following method:

    private void UpdateTabSides()
    {
        foreach (LayoutAnchorablePaneControl apc in DockManager.LayoutRootPanel.FindLogicalChildren<LayoutAnchorablePaneControl>())
        {
            var side = apc.Model.GetSide();
            if (side == AnchorSide.Right)
            {
                apc.TabStripPlacement = Dock.Right;
            }
            else
            {
                apc.TabStripPlacement = Dock.Left;
            }
        }
    }
    

    I find this approach to be a lot simpler than BionicCode's answer but they deserve the bounty for pushing me in the right direction.