Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Odyssey RibbonBar

0.00/5 (No votes)
15 Feb 2009 5  
A WPF RibbonBar control.

intro.png

Introduction

The Odyssey RibbonBar is the next WPF control to be added to the Odyssey Control Library, available on CodePlex.

Background

After the BreadcrumbBar, ExplorerBar, and OutlookBar, which I previously introduced at CodeProject, the Odyssey RibbonBar is the next control included in the Odyssey library. I'm aware of Microsoft's WPF RibbonBar which is available as a fully functional preview and will be added to .NET 4.0. So, you may wonder why I wrote my own RibbonBar anyway. The answer is, it's fun and interesting to do this. And, I also plan to build a Silverlight version. Since the RibbonBar is a set of various controls, it would fill a whole book to explain every detail, so I reduced it to the most important issues of the RibbonBar. The demo application contains the XAML for a RibbonBar that demonstrates most of the features. I did not create a demo that does something useful, nor does it have useful button images or labels. The images I use for the demo are the free images available at www.glyfx.com. The RibbonBar contains tabs that contain groups. Each group can contain any possible control. However, dynamic sizing is only applied when it contains IRibbonControls. Currently, there are the following IRibbonControls:

  • RibbonButton
  • RibbonToggleButton
  • RibbonDropDownButton
  • RibbonSplitButton
  • RibbonComboBox
  • RibbonTextBox
  • RibbonButtonGroup
  • RibbonGallery
  • RibbonSeparator

The RibbonBar hosts an ApplicationMenu and a QuickAccessToolbar which can be placed at the top or at the bottom, as well as a collection of RibbonTabItems:

<odc:RibbonTabItem Title="Contextual Tabs">
    <odc:RibbonGroup Title="Select">
        <odc:RibbonButton Content="Context #1" 
            LargeImage="img/paste32.png" 
            odc:RibbonBar.MinSize="Large" 
            Click="Context1Click" />
        <odc:RibbonButton Content="Context #2" 
            LargeImage="img/mail32.png" 
            odc:RibbonBar.MinSize="Large" 
            Click="Context2Click" />
        <odc:RibbonButton Content="Off" 
            LargeImage="img/delete32.png" 
            odc:RibbonBar.MinSize="Large" 
            Click="ContextOffClick" />
    </odc:RibbonGroup>
</odc:RibbonTabItem>

Each of these controls can have up to three possible RibbonSizes: Large, Medium, and Small, except RibbonGallery which can have unlimited sizes. While buttons usually have three possible sizes. The RibbonBar automatically detects the optimal size depending on the width of the RibbonBar, which I call AutoReduction.

The reduction works in the following way:

First, all groups are assumed to have an unlimited width. If the sum of widths of all groups exceeds the available width, each group gets reduced by one level, beginning from the last group up to the first, until the sum finally fits into the width. This is repeated unless no group can get reduced. The number of levels a group can be reduced depends on the controls inside a group. If there is any IRibbonControl inside, the group can have four reduction levels: Large, Medium, Small, and Collapsed.

reduction.png

RibbonGroup

RibbonGroup.png

A RibbonGroup can host any kind of control, but IRibbonControls are preferred to enable different sizing of the groups.

<odc:RibbonButtonGroup>
    <odc:RibbonButton SmallImage="img/home16.png"/>
    <odc:RibbonButton SmallImage="img/history16.png"/>
    <odc:RibbonButton SmallImage="img/favorites16.png"/>
    <odc:RibbonButton SmallImage="img/mail16.png"/>
</odc:RibbonButtonGroup>

A group is reduced in the following way: All controls inside a group are grouped into buckets. Each new bucket begins when there is a control which is not reducible and has a height that is greater than the possible height to fit into three rows. After the buckets are determined, the layout depends on the number of controls inside a bucket. If there are three controls, then all of them are reduced to medium or small, so that they fit into one column. If there are four, the first remains large and fills one complete column, while the other three will fill another column. With five elements, the first two will remain large and each of it will occupy one column, while the remaining three will fit into the last column in three different rows. If there are six, the first three are placed into the first column in three different rows, while the last three are placed into the second column.

reduction2.png

reduction3.pngreduction4.png

It is possible to modify this reduction. You can attach a Reduction property to a control which describes the size of the control for each group level. Using the Reduction property also allows to modify the group to have various possible levels:

<odc:RibbonButton Content="Button 4" LargeImage="img/paste32.png" 
   SmallImage="img/paste16.png" 
   odc:RibbonBar.Reduction="Large,Large,Large,Medium"/>

customreduction.png

You can also set the MinSize and MaxSize attached properties to a control to force it to have a size not bigger than MaxSize and/or not smaller than MinSize:

<odc:RibbonButton Content="Windows 7" odc:RibbonBar.MinSize="Large" 
    SmallImage="img/save16.png" LargeImage="img/Save32.png" Click="Win7Click"/>
<odc:RibbonButton Content="Office Blue" odc:RibbonBar.MinSize="Medium" 
    SmallImage="img/home16.png" LargeImage="img/home32.png" Click="OfficeBlueClick"/>
 
<odc:RibbonButton odc:RibbonBar.MaxSize="Medium" Content="Home" 
    SmallImage="img/home16.png" LargeImage="img/home32.png"/>
<odc:RibbonButton odc:RibbonBar.MaxSize="Medium" Content="Paste" 
    SmallImage="img/paste16.png" LargeImage="img/paste32.png"/>

Furthermore, you can also modify the reduction order of the groups, by specifying the ReductionOrder property for a RibbonTabItem. The ReductionOrder is a list of group names. When the TabItem needs to reduce groups, it starts with the first group in the list, instead of the last group in its Group collection:

<odc:RibbonTabItem Title="Tab 1" ReductionOrder="grp2,grp1,grp3,grp4,grp3a">

A RibbonGroup can also contain a Launcher button on the bottom right. As soon as this button is clicked, an ExecuteLauncher RoutedEvent is fired:

<odc:RibbonGroup Title="Skin" Image="img/home16.png" IsDialogLauncherVisible="True" 
    ExecuteLauncher="RibbonGroup_LaunchDialog">

The C# code:

private void RibbonGroup_LaunchDialog(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Launcher");
}

IRibbonButton

RibbonButton, RibbonToggleButton, RibbonDropDownButton, and RibbonSplitButton implement IRibbonButton so they have three different visual states. If set to large, the text for the button is split into two lines if possible, which means the text must contain at least one space. In medium mode, the button's text has only one line, and in small mode, the text of the button is hidden. To each button, a LargeImage and SmallImage property can be attached. The LargeImage is shown when the button is in large state, and the SmallImage, when the button is in medium or small state. These properties are attached from RibbonBar and specify an ImageSource:

<odc:RibbonButton Content="Windows 7" odc:RibbonBar.MinSize="Large" 
   SmallImage="img/save16.png" LargeImage="img/Save32.png" Click="Win7Click"/>

While LargeImage is visualized with 32x32 pixels, SmallImage is visualized with 16x16 pixels. So the ImageSource should have these sizes. However, the image is scaled to the appropriate size. You can modify how the image is scaled with the ImageStretch attached property of RibbonButton:

/// <summary>
/// Gets or sets how to stretch an image inside an IRibbonButton
/// This is an attached dependency property.
/// </summary>
public static Stretch GetImageStretch(DependencyObject obj)
{
    return (Stretch)obj.GetValue(ImageStretchProperty);
}
 
public static void SetImageStretch(DependencyObject obj, Stretch value)
{
    obj.SetValue(ImageStretchProperty, value);
}
 
// Using a DependencyProperty as the backing store for ImageStretch.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImageStretchProperty =
DependencyProperty.RegisterAttached("ImageStretch", 
    typeof(Stretch), typeof(RibbonButton), 
    new UIPropertyMetadata(Stretch.Uniform));

RibbonGallery

RibbonGallery.png

The gallery is derived from ListBox and hosts RibbonThumbnails. A RibbonThumbnail displays images of any size. Depending on the size of the image, the gallery has different rows in the in-ribbon gallery.

<odc:RibbonGallery x:Name="gallery" odc:RibbonGallery.Stretch="None" DropDownColumns="5">
    <odc:RibbonGallery.ThumbnailSize>
        <Size Width="36" Height="36"/>
    </odc:RibbonGallery.ThumbnailSize>
    <odc:RibbonThumbnail x:Name="thumb1" 
       ImageSource="img/cut16.png" odc:RibbonGallery.Stretch="None"/>
    <odc:RibbonThumbnail x:Name="thumb2" ImageSource="img/delete16.png"/>
    <odc:RibbonThumbnail ImageSource="img/favorites16.png"/>
    <odc:RibbonThumbnail ImageSource="img/folder16.png"/>
    <odc:RibbonThumbnail ImageSource="img/history16.png"/>
    <odc:RibbonThumbnail ImageSource="img/home16.png"/>
    <odc:RibbonThumbnail ImageSource="img/mail16.png"/>
    <odc:RibbonThumbnail ImageSource="img/paste16.png"/>
    <odc:RibbonThumbnail ImageSource="img/props16.png"/>
    <odc:RibbonThumbnail ImageSource="img/save16.png"/>
    <odc:RibbonThumbnail ImageSource="img/search16.png"/>
    <odc:RibbonThumbnail ImageSource="img/undo16.png"/>
</odc:RibbonGallery>

The RibbonGallery also has the HotItem property which is a dependency property which specifies the thumbnail that is currently under the mouse pointer, so you can implement a preview together with the HotThumbnailChanged RoutedEvent. The demo application demonstrates the usage of HotItem using a binding in the XAML:

<TextBlock Text="{Binding HotItem, ElementName=gallery, 
                 Converter={StaticResource ThumbnailConverter}}"/>

The RibbonGallery can have various reduction levels. You can specify the number of columns for every level:

<odc:RibbonGallery x:Name="gallery2" odc:RibbonGallery.Stretch="None" 
    odc:RibbonGallery.ReductionColumns="12,11,10,9,8,7,6,5,4,3">

Since the RibbonGallery is derived from ListBox, it allows grouping for the drop down list using GroupStyle. I won't go into the details since this is a standard feature, but together with ItemsPanel, DropDownHeader, and DropDownFooter, you can modify the dropdown listbox in unlimited ways.

RibbonDropDownButton and RibbonSplitButton

RibbonDropDownButton.pngRibbonSplitButton.png

Both controls offer a drop down list to expose additional items. They are derived from Items HeaderedItemsControl so it is possible to use a data source for the items. An item can be any type, to enable a rich variety to build the drop down list, but usually it would contain RibbonMenuItems:

<odc:RibbonDropDownButton odc:RibbonBar.MinSize="Medium" Content="Drop Down" 
        SmallImage="img/folder16.png" LargeImage="img/folder32.png">
    <odc:RibbonMenuItem Header="Enable Glass" Image="img/search16.png" IsCheckable="True" 
        IsChecked="{Binding IsGlassEnabled,ElementName=window}"/>
    <odc:RibbonMenuItem Header="Item 2" Image="img/cut16.png"/>
    <odc:RibbonMenuItem Header="Item 3" Image="img/cut16.png"/>
</odc:RibbonDropDownButton>

It's also possible to specify a DropDownHeader and DropDownFooter for the drop down list, together with DropDownHeaderTemplate and DropDownFooterTemplate:

<odc:RibbonDropDownButton.DropDownHeader>
    <TextBlock Text="Custom Header" Background="Orange"/>
    </odc:RibbonDropDownButton.DropDownHeader>
    <odc:RibbonDropDownButton.DropDownFooter>
        <TextBlock Text="Custom Footer" Background="Lime"/>
    </odc:RibbonDropDownButton.DropDownFooter>
</odc:RibbonDropDownButton>

RibbonComboBox and RibbonTextBox

RibbonComoBox.png

They are derived from ComboBox and TextBox, and have additional Image and Title properties to specify a 16x16 image and a text for the control. These controls can have two different sizes. A medium size with Image, Title, and the control itself visible, and a small size with only Image and the control. Like RibbonDropDownButton and RibbonSplitButton, RibbonCombobox also has a DropDownHeader and DropDownFooter property together with their template properties.

The RibbonComboBox Items usually are RibbonComboBoxItems:

<odc:RibbonComboBox Title="ComboBox" Image="img/history16.png" ContentWidth="100">
    <odc:RibbonComboBoxItem Content="Item 1" Image="img/mail16.png" />
    <odc:RibbonComboBoxItem Content="Item 2" Image="img/props16.png"/>
    <odc:RibbonComboBoxItem Content="Item 3"/>
    <odc:RibbonComboBoxItem Content="Item 4"/>
</odc:RibbonComboBox>

RibbonSeparator

The purpose of RibbonSeparator is to attach RibbonBar.Reduction on to it, so it might be possible to hide the Separator if its size is determined to be RibbonSize.Small.

Skins

The RibbonBar currently has four different skins:

  • Office Blue (standard)
  • Office Silver
  • Office Black
  • Window 7
Window 7 / Vista Skin

VistaTheme.png

Black Skin

BlackSkin2.png

You can set the skin for the application at any time using SkinManager:

private void Win7Click(object sender, RoutedEventArgs e)
{
    SkinManager.SkinId = SkinId.Windows7;
}
 
private void OfficeBlueClick(object sender, RoutedEventArgs e)
{
    SkinManager.SkinId = SkinId.OfficeBlue;
}

However, you can also create your own ResourceDictionary that overrides the ComponentResourceKeys that enables skinning. This is described later.

ApplicationMenu

The RibbonApplicationMenu is a separate control. It is derived from ItemsControl to host RibbonApplicationMenuItems. The difference between RibbonApplicationMenuItem and RibbonMenuItem is only the size. As soon as a RibbonApplicationMenuItem has sub items, they are opened in a popup container that fills the space of the RecentList content, like shown below:

ApplicationMenu.png

The RecentList property of the ApplicationMenu is of type object so it can contain any control to build the list of recent items.

QuickAccessToolbar

QAToolbar.png

The RibbonQAToolbar is derived from ItemsControl and usually contains IRibbonButtons and/or RibbonMenuItems. Depending on these two groups, the item is either visible in the toolbar if it is of type IRibbonButton, or in the drop down menu that appears when you click the drop down button on the right:

<odc:RibbonQAToolBar>
    <odc:RibbonButton SmallImage="img/save16.png"/>
    <odc:RibbonButton SmallImage="img/undo16.png"/>
    <odc:RibbonButton SmallImage="img/delete16.png"/>
    <odc:RibbonButton SmallImage="img/folder16.png"/>
    <odc:RibbonToggleButton Content="Enable Glass" 
        odc:RibbonBar.MinSize="Large" SmallImage="img/search32.png" 
        IsChecked="{Binding IsGlassEnabled, ElementName=window}"/> 
    <odc:RibbonButton SmallImage="img/props16.png"/>
    <odc:RibbonMenuItem Image="img/props16.png" 
       Header="Show Below the Ribbon" Click="ShowBelowClick"/>
    <odc:RibbonMenuItem Image="img/props16.png" 
       Header="Show Above the Ribbon" Click="ShowAboveClick"/>
    <Separator/>
    <odc:RibbonMenuItem Header="Minimize Ribbon" 
        IsCheckable="True" 
        IsChecked="{Binding CanMinimize, ElementName=ribbonBar, Mode=TwoWay}"/> 
</odc:RibbonQAToolBar>

You can place the toolbar either at the top or at the bottom:

PlacementBtm.png

You can set the RibbonBar.ToolbarPlacement property to specify the placement:

private void ShowBelowClick(object sender, RoutedEventArgs e)
{
    ribbonBar.ToolbarPlacement = QAPlacement.Bottom;
}

Contextual Tab Sets

ContextualTabs2.png

Contextual Tab Sets contain additional RibbonTabItems that only appear on demand by setting the ContextualTabSet property of the RibbonBar:

private void Context2Click(object sender, RoutedEventArgs e)
{
    ribbonBar.ContextualTabSet = ribbonBar.ContextualTabSets[1];
}

A RibbonContextualTabSet can contain various RibbonTabItems:

<odc:RibbonBar.ContextualTabSets>
    <odc:RibbonContextualTabSet Title="Context #1" Color="Red">
        <odc:RibbonTabItem Title="Gallery">
            <odc:RibbonGroup Title="Large Gallery" Image="img/undo16.png" >
                <odc:RibbonGallery x:Name="gallery2" odc:RibbonGallery.Stretch="None" 
                    odc:RibbonGallery.ReductionColumns="12,11,10,9,8,7,6,5,4,3">
                <odc:RibbonGallery.ThumbnailSize>
                    <Size Width="68" Height="68"/>
                </odc:RibbonGallery.ThumbnailSize>
                <odc:RibbonThumbnail ImageSource="img/cut32.png" />
                <odc:RibbonThumbnail ImageSource="img/delete32.png"/>
                <odc:RibbonThumbnail ImageSource="img/favorites32.png"/>
                <odc:RibbonThumbnail ImageSource="img/folder32.png"/>
                <odc:RibbonThumbnail ImageSource="img/history32.png"/>
                <odc:RibbonThumbnail ImageSource="img/home32.png"/>
                <odc:RibbonThumbnail ImageSource="img/mail32.png"/>
                <odc:RibbonThumbnail ImageSource="img/paste32.png"/>
                <odc:RibbonThumbnail ImageSource="img/props32.png"/>
                <odc:RibbonThumbnail ImageSource="img/save32.png"/>
                <odc:RibbonThumbnail ImageSource="img/search32.png"/>
                <odc:RibbonThumbnail ImageSource="img/undo32.png"/>
            </odc:RibbonGallery>
        </odc:RibbonGroup>
 
    </odc:RibbonTabItem>
    <odc:RibbonTabItem Title="Tab 1b">
 
    </odc:RibbonTabItem>
</odc:RibbonContextualTabSet>
    <odc:RibbonContextualTabSet Title="Context #2" Color="Lime">
        <odc:RibbonTabItem Title="Tab 2">
 
        </odc:RibbonTabItem>
 
    </odc:RibbonContextualTabSet>
</odc:RibbonBar.ContextualTabSets>

RibbonButtonGroup

RibbonButtonGroup.png

A RibbonButtonGroup is meant to group IRibbonButtons together which is visualized with a group border. If placed in a RibbonButtonGroup, the only allowed size of the button is RibbonSize.Small which means that only the SmallImage is displayed.

<odc:RibbonGroup Title="Button Groups" Image="img/favorites16.png" 
    odc:RibbonBar.Reduction="Large,Large,Minimized" >
    <odc:RibbonFlowGroup>
        <odc:RibbonButtonGroup>
            <odc:RibbonToggleButton SmallImage="img/cut16.png"/>
            <odc:RibbonToggleButton SmallImage="img/delete16.png"/>
            <odc:RibbonToggleButton SmallImage="img/paste16.png"/>
        </odc:RibbonButtonGroup>
        <odc:RibbonButtonGroup>
            <odc:RibbonButton SmallImage="img/home16.png"/>
            <odc:RibbonButton SmallImage="img/history16.png"/>
            <odc:RibbonButton SmallImage="img/favorites16.png"/>
            <odc:RibbonButton SmallImage="img/mail16.png"/>
            </odc:RibbonButtonGroup>
         
        <odc:RibbonButtonGroup>
            <odc:RibbonButton SmallImage="img/search16.png"/>
            <odc:RibbonDropDownButton SmallImage="img/undo16.png"/>
            <odc:RibbonButton SmallImage="img/folder16.png"/>
            <odc:RibbonSplitButton SmallImage="img/props16.png"/>
            <odc:RibbonButton SmallImage="img/save16.png"/>
        </odc:RibbonButtonGroup>
        <odc:RibbonButtonGroup>
            <odc:RibbonButton SmallImage="img/search16.png"/>
        </odc:RibbonButtonGroup>
    </odc:RibbonFlowGroup>
</odc:RibbonGroup>

RibbonFlowGroup

This is is used as a container for RibbonButtonGroups. It can have two possible states: 2rows and 3rows:

RibbonFlowGroup1.pngRibbonFlowGroup2.png

In 2rows mode, the order of the RibbonButtonGroups is the order in the collection. In 3rows mode, however, the order is determined depending on the width of a group. The algorithm optimizes the order in that way, that the RibbonFlowGroup has the smallest possible width. This is solved by first determining the three largest groups and placing them in column one where the first row has the largest group, and the third the least. Now, for the second column, the order of the row is the opposite to the former column.

RibbonWindow

Finally, some words about RibbonWindow which is derived from the Window class. You must have noticed that the QuickAccessToolbar (QAT) and the contextual tab sets appear inside the window's title bar. This is possible using the RibbonWindow instead of the Window class. RibbonWindow hijacks the Windows messages to allow painting inside the non-client area of the window. It also uses DwmExtendFrameIntoClientArea to set the glass effect to the title bar if Aero is enabled and IsGlassEnabled is set to true.

RibbonWindow1.png

In this case, the Windows buttons on the right are the original Windows buttons. However, if Glass is turned off, the window must paint its own buttons:

RibbonWnd1.png

Internals

If a RibbonButton has a large size, its text is separated into two lines if it contains a space. This is implemented by using a ValueConverter and a ContentControl/TextBlock pair for each line. If you're asking why not two TextBlocks, the answer is, so it is possible to use any type for the button's content, and not just string. The ValueConverter recognizes this and returns the original value for the first line if the value to convert is not a string. The code part looks like this:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="12"/>
        <RowDefinition Height="12"/>
    </Grid.RowDefinitions>
    <TextBlock x:Name="content2" Grid.Row="0"
        Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent},
              Converter={StaticResource twoLineTextConverter},ConverterParameter=1}"
        VerticalAlignment="Center" 
        HorizontalAlignment="Center" SnapsToDevicePixels="True" />
    <StackPanel Orientation="Horizontal" Grid.Row="2" 
        HorizontalAlignment="Center" VerticalAlignment="Bottom">
         <TextBlock Grid.Row="1"
            Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent},
                Converter={StaticResource twoLineTextConverter},ConverterParameter=2}"
                VerticalAlignment="Bottom" HorizontalAlignment="Center" 
                SnapsToDevicePixels="True"/>
 
        <Image 
            Source="{DynamicResource {ComponentResourceKey odc:Skins, DownArrowImage}}" 
                Margin="2,0,2,0" Stretch="None" 
                SnapsToDevicePixels="True"/>
    </StackPanel>
</Grid>

The C# code:

public class TwoLineTextConverter : IValueConverter
{
#region IValueConverter Members
 
/// <summary>
/// Splits a string into two lines which have almost the same length if possible.
/// </summary>
/// <param name="value">The string to split. If this type
/// is not a string, the value is returned directly.</param>
/// <param name="parameter">Specifies the line number to return.
/// The value must be either (int)1 or (int)2.</param>
/// <returns>The first or second line of the string, otherwise value.</returns>
public object Convert(object value, Type targetType, object parameter, 
                      System.Globalization.CultureInfo culture)
{
    int line;
    try
    {
        line = int.Parse(parameter as string);
    }
    catch
    {
        throw new ArgumentException("parameter must be either 1 or 2.");
    }
    string s = value as string;
    if (s == null) return line == 1 ? value : null;
 
    int l = SplitIn2Lines(s);
    if (l == 0) return line == 1 ? s : null;
 
    switch (line)
    {
        case 1: return s.Substring(0, l).Trim();
        case 2: return s.Substring(l + 1).Trim();
        default: throw new ArgumentException("parameter must be either 1 or 2.");
    }
}

The Skins

For skinning, I use ComponentResourceKeys to change the appearance of a control. A ComponentResourceKey is similar to a ResourceKey except that it contains a class type and an identifier. Defining a component resource key looks like this:

<SolidColorBrush 
  x:Key="{ComponentResourceKey odc:Skins, PopupContainerBgBrush}" Color="White"/>

And using the key in a XAML looks like this:

<Border 
    Background="{DynamicResource {ComponentResourceKey odc:Skins, PopupContainerBgBrush}}" 
    BorderBrush="{DynamicResource {ComponentResourceKey odc:Skins, 
        {ComponentResourceKey odc:Skins, RibbonBorderBrush}}}" 
    Padding="1" CornerRadius="2" BorderThickness="1" >
    <DockPanel>
        <Border x:Name="title" DockPanel.Dock="Top" Height="24" Background="#FFDDE7EE" 
            BorderBrush="{DynamicResource {ComponentResourceKey odc:Skins, 
                {ComponentResourceKey odc:Skins, RibbonBorderBrush}}}" 
            BorderThickness="0,0,0,1" Padding="4">
            <ContentControl Content="{TemplateBinding SubMenuTitle}" 
                VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </Border>
        <ScrollViewer CanContentScroll="True" VerticalScrollBarVisibility="Auto" 
            HorizontalScrollBarVisibility="Disabled">
            <ItemsPresenter ScrollViewer.VerticalScrollBarVisibility="Visible" 
                ScrollViewer.CanContentScroll="True"/>
        </ScrollViewer>
    </DockPanel>
</Border>

Since the template use DynamicResource instead of StaticResource, the resource can be modified at any time. The RibbonManager loads a ResourceDictionary and adds it to the MergedDictionaries of the application:

private static void ApplySkin(SkinId skin)
{
    var dict = Application.Current.Resources.MergedDictionaries;
    dict.Remove(OfficeBlack);
    dict.Remove(OfficeSilver);
    dict.Remove(WindowsSeven);
    switch (skin)
    {
        case SkinId.OfficeBlack:
            dict.Add(OfficeBlack);
            break;
 
        case SkinId.OfficeSilver:
            dict.Add(OfficeSilver);
            break;
 
        case SkinId.Windows7:
            dict.Add(WindowsSeven);
            break;
    } 
}

The overlapping of the ApplicationMenu button: when you open up the application menu, you'll notice that the menu button is still at the top of the popup control, and you may wonder how this is possible. Well, the trick is to use two buttons: one at the Ribbon and one at the Popup. When the Popup opens, the button on it needs to be placed to have the same screen location as the button on the Ribbon:

protected virtual void OnPopupOpened(object sender, EventArgs e)
{
    AdjustApplicationButtons();
    IsOpen = true;
}
 
/// <summary>
/// Ensures that both ApplicationMenu buttons are at the same screen location:
/// </summary>
private void AdjustApplicationButtons()
{
    if (appButtonClone != null && appButton != null)
    { 
        Point p = appButton.PointToScreen(new Point());
        Point p2 = appButtonClone.PointToScreen(new Point());
 
        double dx = p2.X - p.X;
        double dy = p2.Y - p.Y;
        appButtonClone.Visibility = 
          dy >= -20 ? Visibility.Visible : Visibility.Hidden;
        Thickness t = appButtonClone.Margin;
        appButtonClone.Margin = new Thickness(t.Left-dx, t.Top-dy, 0.0, 0.0);
    }
}

Hosting items from ItemsControl in a separate panel: If you try to change the parent of an Item from a derived ItemsControl, there would be an exception, that the control already has a logical parent. The trick is to add it to a custom panel that overrides CreateUIElementCollection:

/// <summary>
/// Overriding this to enable a templated parent to add or remove children to 
/// this panel within OnMeasureOverride or somewhere else.
/// </summary>
protected override UIElementCollection 
  CreateUIElementCollection(System.Windows.FrameworkElement logicalParent)
{
    return new UIElementCollection(this, 
              (base.TemplatedParent == null) ? logicalParent : null);
}

When the panel is used in a template, which means TemplatedParent != null, it tells the element collection to have no logical parent. Thus, adding a control to this panel is possible, since the logical parent will not change.

Not Implemented/ To be Available on Future Releases

  • Extended tooltips and tooltips for the Launcher button.
  • Alt-Key short cuts.
  • Up-down textbox
  • IsChecked and IsCheckable properties for RibbonSplitButton.

The Odyssey RibbonBar will be migrated to the available Odyssey Control Library available at www.codeplex.com/odyssey.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here