Search code examples
wpfxamlborderscrollviewcornerradius

Scrolling Content Inside A Border With CornerRadius


I'm looking for help with a WPF problem I've been wrestling with for a while. I've styled a tab view, moving the tabs onto the left and displaying them vertically. These tabs sit inside a border with rounded corners at the top and bottom left.

Normal Tab http://gallery.me.com/theplatz/100006/TabGood.png?derivative=medium&source=web.png&type=medium&ver=12464623560001

I'm running into a problem when the tabs are scrolled. Instead of the rounded corners clipping the scrolled content, the content actually rides up on top of the corners, as seen here:

Overlapping Tab http://gallery.me.com/theplatz/100006/TabBad/web.png?ver=12464623500001

Here's the XAML:

<Style x:Key="SidebarTabControl" TargetType="TabControl">
    <Setter Property="Background" Value="#FFC6D3DE" />
    <Setter Property="Padding" Value="0,20,0,0" />
    <Setter Property="TabStripPlacement" Value="Left" />
    <Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabControl}">
                <Grid KeyboardNavigation.TabNavigation="Local">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="200" MinWidth="150" MaxWidth="400" />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>

                <Border
                    CornerRadius="10,0,0,10"
                    Background="{TemplateBinding Background}">
                    <ScrollViewer Grid.Column="0"
                        VerticalScrollBarVisibility="Auto"
                        HorizontalScrollBarVisibility="Disabled"
                        ClipToBounds="True">
                        <Border Padding="{TemplateBinding Padding}">        
                        <TabPanel
                            IsItemsHost="True"
                            KeyboardNavigation.TabIndex="1"
                            Background="Transparent">
                        </TabPanel>
                        </Border>
                    </ScrollViewer>
                </Border>

                <ContentPresenter
                    Grid.Column="1"
                    Margin="0"
                    ContentSource="SelectedContent" />

                <GridSplitter Grid.Column="0"
                  HorizontalAlignment="Right"
                  VerticalAlignment="Stretch"
                  Background="{StaticResource SplitterBrush}" 
                  ShowsPreview="True"
                  Width="1" />
              </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="SidebarTab" TargetType="TabItem">
    <Setter Property="Padding" Value="10,12,2,12" />
    <Setter Property="BorderThickness" Value="0,1,0,1" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Border Padding="{TemplateBinding Padding}" 
                    Name="tab" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    BorderBrush="{StaticResource SidebarTabBorderBrush}">
                    <ContentPresenter Style="{StaticResource SidebarTabForegroundStyle}" Name="content" ContentSource="Header" />
                </Border>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="tab" Property="Background" Value="{StaticResource SidebarTabBackgroundBrushSelected}" />
                        <Setter TargetName="tab" Property="BorderBrush" Value="{StaticResource SidebarTabBorderBrushSelected}" />
                        <Setter TargetName="content" Property="Style" Value="{StaticResource SidebarTabForegroundStyleSelected}" />
                    </Trigger>
                    <Trigger Property="IsSelected" Value="False">
                        <Setter TargetName="tab" Property="Background" Value="{StaticResource SidebarTabBackgroundBrush}" />
                        <Setter TargetName="tab" Property="BorderBrush" Value="{StaticResource SidebarTabBorderBrush}" />
                        <Setter TargetName="content" Property="Style" Value="{StaticResource SidebarTabForegroundStyle}" />
                    </Trigger>
                </ControlTemplate.Triggers>

            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Any ideas on a way I could accomplish what I'm looking for? I've tried some ZIndex tricks, but that didn't seem to work.


Solution

  • To accomplish what I was looking for, I used the solution found here, and modified it to fit my needs. Here's what I ended up with:

    <Style x:Key="SidebarTabControl" TargetType="TabControl">
        <Setter Property="Background" Value="#FFC6D3DE" />
        <Setter Property="Padding" Value="0,20,0,20" />
        <Setter Property="TabStripPlacement" Value="Left" />
        <Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TabControl}">
                    <Grid KeyboardNavigation.TabNavigation="Local">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="200" MinWidth="150" MaxWidth="400" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
    
                <!-- Background of the sidebar and our clipping bounds -->
                <Border Grid.Column="0"
                    CornerRadius="10,0,0,10"
                        Background="{TemplateBinding Background}"
                    Name="mask" />
    
                <!-- Border necessary so that the top tab does not get clipped prematurely -->
                <Border Grid.Column="0" Background="Transparent">
                    <!-- Add opacity mask to clip contents as they're scrolled -->
                    <Border.OpacityMask>
                            <VisualBrush Visual="{Binding ElementName=mask}"/>
                    </Border.OpacityMask>
                    <ScrollViewer
                    VerticalScrollBarVisibility="Visible"
                    HorizontalScrollBarVisibility="Disabled">
                    <Border Padding="{TemplateBinding Padding}">        
                            <TabPanel
                            IsItemsHost="True"
                            KeyboardNavigation.TabIndex="1"
                            Background="Transparent">
                        </TabPanel>
                    </Border>
                     </ScrollViewer>
                </Border>
    
                <ContentPresenter
                    Grid.Column="1"
                            Margin="0"
                            ContentSource="SelectedContent" />
    
                <GridSplitter Grid.Column="0"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Stretch"
                            Background="{StaticResource SplitterBrush}" 
                            ShowsPreview="True"
                            Width="1" />
                  </Grid>
            </ControlTemplate>
        </Setter.Value>
       </Setter>
    </Style>
    

    Edit: I found the solution to the clipping problem with the first TabItem. Nesting the ScrollView inside a second border and applying the OpacityMask to this border, and not the ScrollView fixed the problem. Additionally, I had to explicitly set Background="Transparent" to the Border where the OpacityMask was being applied in order for the clip to not happen prematurely.