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 IRibbonControl
s. Currently, there are the following IRibbonControl
s:
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 RibbonTabItem
s:
<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 RibbonSize
s: 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
.
RibbonGroup
A RibbonGroup
can host any kind of control, but IRibbonControl
s 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.
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"/>
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
:
public static Stretch GetImageStretch(DependencyObject obj)
{
return (Stretch)obj.GetValue(ImageStretchProperty);
}
public static void SetImageStretch(DependencyObject obj, Stretch value)
{
obj.SetValue(ImageStretchProperty, value);
}
public static readonly DependencyProperty ImageStretchProperty =
DependencyProperty.RegisterAttached("ImageStretch",
typeof(Stretch), typeof(RibbonButton),
new UIPropertyMetadata(Stretch.Uniform));
RibbonGallery
The gallery is derived from ListBox
and hosts RibbonThumbnail
s. 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
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 RibbonMenuItem
s:
<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
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
Black Skin
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 RibbonApplicationMenuItem
s. 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:
The RecentList
property of the ApplicationMenu
is of type object
so it can contain any control to build the list of recent items.
QuickAccessToolbar
The RibbonQAToolbar
is derived from ItemsControl
and usually contains IRibbonButton
s and/or RibbonMenuItem
s. 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:
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
Contextual Tab Sets contain additional RibbonTabItem
s 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 RibbonTabItem
s:
<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
A RibbonButtonGroup
is meant to group IRibbonButton
s 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 RibbonButtonGroup
s. It can have two possible states: 2rows
and 3rows
:
In 2rows
mode, the order of the RibbonButtonGroup
s 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
.
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:
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 TextBlock
s, 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
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;
}
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
:
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.