XAML Ribbon Bar – Part 2

When designing the XAML Ribbon Bar I really got stuck figuring out how to get the curved effect on the Tab Item. At one stage I used a Path instead of the Border in the markup, so instead of:

<Border CornerRadius="5,5,0,0" 
="Red" BorderThickness="1,1,1,0" Background="Yellow"
="Left" Padding="4,2"> <TextBlock>Header Text</TextBlock> </Border>

XAML Tab Header using Border

I used:

<Canvas Height="25">
    <Path Stroke="Red" StrokeThickness="1" Fill="Yellow"
="M 5,22 Q 10,22 10,17 V 5 Q 10,0 15,0 H 75 Q 80,0 80,5 V 20 Q 80,22 85,22"/> <TextBlock Margin="12,4">Header Text</TextBlock> </Canvas>
XAML Tab Header using Path 

The problem with this was I just couldn’t figure out how to get it to scale nicely to the tab header content (names on the tabs). You can’t bind to the X and Y properties on a point, and as it turns out there is a bug that means you can’t bind to the StartPoint either.

After spending too long trying to figure this out I resigned myself to the fact that I was going to have to write some C# code to get the job done. This turned out to be trivial…

public class TabHeaderBorder : Border
protected override void OnRender(System.Windows.Media.DrawingContext dc)
PathSegmentCollection segments = new PathSegmentCollection();
segments.Add(new QuadraticBezierSegment(
new Point(CornerRadius.BottomLeft, ActualHeight),
new Point(CornerRadius.BottomLeft, ActualHeight - CornerRadius.BottomLeft), true)); segments.Add(new LineSegment(
new Point(CornerRadius.BottomLeft, CornerRadius.TopLeft), true)); segments.Add(new QuadraticBezierSegment(
new Point(CornerRadius.BottomLeft, 0),
new Point(CornerRadius.BottomLeft + CornerRadius.TopLeft, 0), true)); segments.Add(new LineSegment(
new Point(ActualWidth - CornerRadius.TopRight - CornerRadius.BottomRight, 0), true)); segments.Add(new QuadraticBezierSegment(
new Point(ActualWidth - CornerRadius.BottomRight, 0),
new Point(ActualWidth - CornerRadius.BottomRight, CornerRadius.TopRight), true)); segments.Add(new LineSegment(
new Point(ActualWidth - CornerRadius.BottomRight, ActualHeight - CornerRadius.BottomRight), true)); segments.Add(new QuadraticBezierSegment(
new Point(ActualWidth - CornerRadius.BottomRight, ActualHeight),
new Point(ActualWidth, ActualHeight), true)); segments.Add(new LineSegment(
new Point(0, ActualHeight), false)); PathFigure borderPath = new PathFigure(new Point(0, ActualHeight), segments, true); PathFigureCollection figures = new PathFigureCollection(); figures.Add(borderPath); PathGeometry borderGeometry = new PathGeometry(figures); dc.DrawGeometry(Background, new Pen(BorderBrush, BorderThickness.Left), borderGeometry); } }

Which gave the visual effect I was after and the same content capabilties as the Border. This meant that by default it would automatically size to its content. I did cheat a little bit in that the BorderThickness is only treated as a single value – but I did allow separate corner radius values.

<controls:TabHeaderBorder CornerRadius="5, 5, 10, 10" 
="Red" BorderThickness="1" Background="Yellow"
="Left" Padding="12,4"> <TextBlock>Header Text</TextBlock> </controls:TabHeaderBorder>

XAML Tab Header 1

XAML Tab Header With LineNow – how do I get rid of the horizontal line between the selected Tab Header and the Tab Content? Of course the easiest solutions was to simply not paint the content Border’s top edge but I decided that I cared enough.

XAML Tab Header Without LineAgain – I couldn’t figure out how to approach this in XAML. So given how easy it was to create the TabHeaderBorder class I created a TabControlBorder. This control gets “linked” to the actual TabControl to check for and subscribes to its SelectedIndex changed event. The event handler then invalidates the controls visuals so that the OnRender method can draw the border and then “paste” a line directly underneath where the TabItem’s would be drawn. It certainly isn’t pretty but its the most adaptable solution I’ve come up with so far. The results look Ok – unless you really zoom in using a tool like Windows Magnifier which allows the WPF vectors so scale correctly. Once zoomed you can see that the lines are slightly disjointed – not surprisingly because they are drawn on two different UI elements.

    public class TabContentBorder : Border
... Dependency property stuff
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) { InvalidateVisual(); } protected override void OnRender(System.Windows.Media.DrawingContext dc) { if (TabControl != null && TabControl.SelectedIndex >= 0) { TabItem selectedItem = (TabItem) TabControl.SelectedItem; double x = selectedItem.TransformToAncestor(TabControl).Transform(new Point(0,0)).X; // Use the base paint - we'll paint "over" it with the background colour base.OnRender(dc); // Erase the border line segment - this is making a rash assumption that a linear gradient brush is used! dc.DrawRectangle(
new SolidColorBrush(((LinearGradientBrush) Background).GradientStops[0].Color), null,
new Rect(x + 1, -1, selectedItem.ActualWidth - 1, BorderThickness.Top + 2)); } else { base.OnRender(dc); } }

Yuerk – that isn’t pretty – will have to do for now though

I’ve changed the ItemsPanelTemplate for the Toolbar to be a fixed height WrapPanel and its pretty much giving me what I want now. Although – I have a very limited set of requirements

 XAML Ribbon Bar - Part 2

And just for laughs here the same content but without Style=”{StaticResource RibbonBarStyle}” applied.

 XAML Ribbon Bar Default Style

Interestingly what got me started down this whole Ribbon path was the fact that the only way I could find to switch off the display of the Overflow chrome on the ToolBar was to override its default template.

One thought on “XAML Ribbon Bar – Part 2”

Comments are closed.