DCSIMG
October 2009 - Posts - Arik Poznanski's Blog

Arik Poznanski's Blog

It CAN be done with .NET

News

MVP

MCC

CodeProject MVP

MCPD

MCTS

Subscribe to my blog by email

Arik Poznanski LinkedIn Profile

Email: arik.com at gmail dot com
or, use this form

Locations of visitors to this page


Sela Group

Sela Canada

DZone MVB

Links

Official Blogs

WPF / SL Blogs

Developers Blogs

October 2009 - Posts

Windows Ribbon for WinForms, Part 11 – DropDownGallery, SplitButtonGallery and InRibbonGallery

In this post I'll show you how to use the different galleries available with the Windows Ribbon Framework.

The result of this post is a new sample named “09-Galleries” that you can find on the Windows Ribbon for WinForms project page. It looks like this:

image

image

Item Galleries vs. Command Galleries
The galleries that we will soon review comes in two flavors: item galleries and command galleries. In this section will see what’s the difference between these two.

Item Galleries

  • Gallery items are “simple” items, just text and image, similar to items in a ComboBox.
  • Items have an index and there is a concept of "Selected Item”.
  • Item galleries support preview. This means you get a “preview” notification (event) when you move over an item and also a “cancel preview” notification when you cancel the selection.
  • A single item in an item gallery has the following properties: Label, Image and Category ID.

Note: A ComboBox ribbon control is also an item gallery.

Command Galleries

  • Gallery items are actually commands.
  • The Commands are not indexed and thus doesn’t have the concept of “Selected Item”.
  • Command galleries doesn’t support preview.
  • A single item in a command gallery has the following properties: Command ID, Command Type and Category ID.

Note: Both gallery types have the concept of categories, see ComboBox post for more details.

Gallery Types
Now we will review the 3 gallery types we have, each one can be an item gallery or command gallery.
Note that according to MSDN a ComboBox is also considered to be a gallery which is always an item gallery. Since we already review the ComboBox in a previous post, I won’t mention it here.

DropDownGallery
Just a button that a click on it displays a list of items. The button itself has no action.
In the first image above, the leftmost control is a DropDownGallery.

SplitButtonGallery
Two buttons, one that is used as a default action and another one that opens a list of items.
In the second image above, the middle control is a SplitButtonGallery.

Note: We already saw in a previous post that the main difference between a DropDown and a SplitButton is that a SplitButton has a default item.

InRibbonGallery
The items are displayed inside the ribbon, no button is needed.
In the images above, the rightmost control is an InRibbonGallery.

Command Space
One additional feature you should know about, in the sake of completeness, is that all 3 galleries have a command space. This is a section on the bottom of the control that contains statically defined commands.
In the first image above, the DropDownGallery has a button defined in its command space.

Using Galleries
In the next sections I’ll show you how to define galleries in ribbon markup and use the new helper classes for easily operate on them. Note that since galleries have many options I will demonstrate only a representative subset.

Using DropDownGallery – Ribbon Markup
Here I’ll show you how to use the DropDownGallery as an item gallery with a command space.
Commands section:

<Application.Commands>
  <Command Name='cmdTabMain' Id='1000' LabelTitle='Main' />
  <Command Name='cmdGroupDropDownGallery' Id='1001' LabelTitle='Drop Down' />
  <Command Name='cmdDropDownGallery' Id='1002' LabelTitle='Size' >
    <Command.LargeImages>
      <Image>Res/Open32.bmp</Image>
    </Command.LargeImages>
    <Command.SmallImages>
      <Image>Res/Open16.bmp</Image>
    </Command.SmallImages>
  </Command>
  <Command Name='cmdCommandSpace' Id='1003' LabelTitle='Command Space' >
    <Command.LargeImages>
      <Image>Res/Save32.bmp</Image>
    </Command.LargeImages>
    <Command.SmallImages>
      <Image>Res/Save16.bmp</Image>
    </Command.SmallImages>
  </Command>
  …
</Application.Commands>

Views section:

<Application.Views>
  <Ribbon>
    <Ribbon.Tabs>
      <Tab CommandName='cmdTabMain'>
        <Group CommandName='cmdGroupDropDownGallery' SizeDefinition='OneButton'>
          <DropDownGallery CommandName='cmdDropDownGallery'
                           TextPosition='Hide'
                           Type='Items'>
            <DropDownGallery.MenuLayout>
              <FlowMenuLayout Columns='1' Rows='5' Gripper='None' />
            </DropDownGallery.MenuLayout>
            <DropDownGallery.MenuGroups>
              <MenuGroup>
                <Button CommandName='cmdCommandSpace' />
              </MenuGroup>
            </DropDownGallery.MenuGroups>
          </DropDownGallery>
        </Group>
        …
      </Tab>
    </Ribbon.Tabs>
  </Ribbon>
</Application.Views>

In the view part you define the use of a DropDownGallery along with its layout (how many columns and rows you want) and command space.
What makes this gallery an item gallery is setting the Type attribute to ‘Items’.
More details about DropDownGallery attributes can be found on MSDN.

Using DropDownGallery – Code Behind
Create an instance of RibbonLib.DropDownGallery helper class and register some of its events:

private Ribbon _ribbon = new Ribbon();
private RibbonDropDownGallery _dropDownGallery;

public Form1()
{
    InitializeComponent();

    _dropDownGallery = new RibbonDropDownGallery(_ribbon, (uint)RibbonMarkupCommands.cmdDropDownGallery);

    _dropDownGallery.OnExecute += new OnExecuteEventHandler(_dropDownGallery_OnExecute);
    _dropDownGallery.OnPreview += new OnPreviewEventHandler(_dropDownGallery_OnPreview);
    _dropDownGallery.OnCancelPreview += new OnCancelPreviewEventHandler(_dropDownGallery_OnCancelPreview);
}

void _dropDownGallery_OnCancelPreview(PropertyKeyRef key, PropVariantRef currentValue, IUISimplePropertySet commandExecutionProperties)
{
    Console.WriteLine("DropDownGallery::OnCancelPreview");
}

void _dropDownGallery_OnPreview(PropertyKeyRef key, PropVariantRef currentValue, IUISimplePropertySet commandExecutionProperties)
{
    Console.WriteLine("DropDownGallery::OnPreview");
}

void _dropDownGallery_OnExecute(PropertyKeyRef key, PropVariantRef currentValue, IUISimplePropertySet commandExecutionProperties)
{
    Console.WriteLine("DropDownGallery::OnExecute");
}

Add items to the DropDownGallery:

private void Form1_Load(object sender, EventArgs e)
{
    _ribbon.InitFramework(this);

    FillDropDownGallery();
}

private void FillDropDownGallery()
{
    // set label
    _dropDownGallery.Label = "Size";

    // set _dropDownGallery items
    IUICollection itemsSource = _dropDownGallery.ItemsSource;
    itemsSource.Clear();
    foreach (Image image in imageListLines.Images)
    {
        itemsSource.Add(new GalleryItemPropertySet()
                        {
                            ItemImage = _ribbon.ConvertToUIImage((Bitmap)image)
                        });
    
    }
}

Note that I’m using here a standard ImageList control to supply the bitmaps for the DropDownGallery.
GalleryItemPropertySet is a helper class that represents a single item in an item gallery.

Update (18.11.2009): The updated version of the Ribbon class provides an implementation for IUICommandHandler, so the user doesn't need to implement Execute and UpdateProperty methods anymore.

Finally, Connect IUICommandHandler.Execute and IUICommandHandler.UpdateProperty methods with the implementation of these methods in the DropDownGallery class:

public HRESULT Execute(uint commandId, UI_ExecutionVerb verb, ref PropertyKey key,
                       ref PropVariant currentValue,
                       IUISimplePropertySet commandExecutionProperties)
{
    if (commandId == (uint)RibbonMarkupCommands.cmdDropDownGallery)
    {
        return _dropDownGallery.Execute(verb, ref key, ref currentValue,
                                        commandExecutionProperties);
    }
    return HRESULT.S_OK;
}

public HRESULT UpdateProperty(uint commandId, ref PropertyKey key,
                              ref PropVariant currentValue, ref PropVariant newValue)
{
    if (commandId == (uint)RibbonMarkupCommands.cmdDropDownGallery)
    {
        return _dropDownGallery.UpdateProperty(ref key, ref currentValue, ref newValue);
    }
    return HRESULT.S_OK;
}

Using SplitButtonGallery – Ribbon Markup
Here I’ll show you how to use the SplitButtonGallery as a command gallery.
Commands section:

<Command Name='cmdGroupSplitButtonGallery' Id='1004' LabelTitle='Split Button' />
<Command Name='cmdSplitButtonGallery' Id='1005' LabelTitle='Brushes' >
  <Command.LargeImages>
    <Image>Res/Brush1.bmp</Image>
  </Command.LargeImages>
</Command>

Views section:


<Group CommandName="cmdGroupSplitButtonGallery" SizeDefinition="OneButton">
  <SplitButtonGallery CommandName="cmdSplitButtonGallery"
                      TextPosition="Hide" Type="Commands" HasLargeItems="true">
    <SplitButtonGallery.MenuLayout>
      <FlowMenuLayout Columns="4" Rows="3" Gripper="None"/>
    </SplitButtonGallery.MenuLayout>
  </SplitButtonGallery>
</Group>

In the view part you define the use of a SplitButtonGallery along with its layout (how many columns and rows you want), and optionally, a command space.
What makes this gallery a command gallery is setting the Type attribute to ‘Commands’.
More details about SplitButtonGallery attributes can be found on MSDN.

Using SplitButtonGallery – Code Behind
Create an instance of RibbonLib.SplitButtonGallery helper class.

private Ribbon _ribbon = new Ribbon();
private RibbonSplitButtonGallery _splitButtonGallery;
private RibbonButton[] _buttons;

public Form1()
{
    InitializeComponent();

    _splitButtonGallery = new RibbonSplitButtonGallery(_ribbon, (uint)RibbonMarkupCommands.cmdSplitButtonGallery);
}

Add items to the SplitButtonGallery:

private void Form1_Load(object sender, EventArgs e)
{
    _ribbon.InitFramework(this);

    FillSplitButtonGallery();
}

private void FillSplitButtonGallery()
{
    // set label
    _splitButtonGallery.Label = "Brushes";

    // prepare helper classes for commands
    _buttons = new RibbonButton[imageListBrushes.Images.Count];
    uint i;
    for (i = 0; i < _buttons.Length; ++i)
    {
        _buttons[i] = new RibbonButton(_ribbon, 2000 + i)
                          {
                              Label = "Label " + i.ToString(),
                              LargeImage = _ribbon.ConvertToUIImage((Bitmap) imageListBrushes.Images[(int) i])
                          };
    }

    // set _splitButtonGallery categories
    IUICollection categories = _splitButtonGallery.Categories;
    categories.Clear();
    categories.Add(new GalleryItemPropertySet() { Label = "Category 1", CategoryID = 1 });

    // set _splitButtonGallery items
    IUICollection itemsSource = _splitButtonGallery.ItemsSource;
    itemsSource.Clear();
    i = 0;
    foreach (Image image in imageListBrushes.Images)
    {
        itemsSource.Add(new GalleryCommandPropertySet()
                        {
                            CommandID = 2000 + i++,
                            CommandType = CommandType.Action,
                            CategoryID = 1
                        });
    }

    // add default item to items collection
    itemsSource.Add(new GalleryCommandPropertySet()
                    {
                        CommandID = (uint)RibbonMarkupCommands.cmdSplitButtonGallery,
                        CommandType = CommandType.Action,
                        CategoryID = 1
                    });
}

GalleryCommandPropertySet is a helper class that represents a single item in a command gallery.

Important: If you don’t add the default item to the items list of a SplitButtonGallery, the items will appears twice! This is probably a bug.

Update (18.11.2009): The updated version of the Ribbon class provides an implementation for IUICommandHandler, so the user doesn't need to implement Execute and UpdateProperty methods anymore.

Finally, Connect IUICommandHandler.Execute and IUICommandHandler.UpdateProperty methods with the implementation of these methods in the SplitButtonGallery and Button classes:

public HRESULT Execute(uint commandId, UI_ExecutionVerb verb, ref PropertyKey key,
                       ref PropVariant currentValue,
                       IUISimplePropertySet commandExecutionProperties)
{
    if (commandId == (uint)RibbonMarkupCommands.cmdSplitButtonGallery)
    {
        return _splitButtonGallery.Execute(verb, ref key, ref currentValue,
                                           commandExecutionProperties);
    }
    else if (commandId >= 2000)
    {
        return _buttons[commandId - 2000].Execute(verb, ref key, ref currentValue,
                                                  commandExecutionProperties);
    }
    return HRESULT.S_OK;
}

public HRESULT UpdateProperty(uint commandId, ref PropertyKey key,
                              ref PropVariant currentValue, ref PropVariant newValue)
{
    if (commandId == (uint)RibbonMarkupCommands.cmdSplitButtonGallery)
    {
        return _splitButtonGallery.UpdateProperty(ref key, ref currentValue,
                                                  ref newValue);
    }
    else if (commandId >= 2000)
    {
        return _buttons[commandId - 2000].UpdateProperty(ref key, ref currentValue,
                                                         ref newValue);
    }
    return HRESULT.S_OK;
}

 

Note the handling of dynamically added commands.

Using InRibbonGallery – Ribbon Markup
Here I’ll show you how to use the InRibbonGallery as an item gallery.
Commands section:

<Command Name='cmdGroupInRibbonGallery' Id='1006' LabelTitle='In Ribbon' />
<Command Name='cmdInRibbonGallery' Id='1007' />

Views section:


<Group CommandName="cmdGroupInRibbonGallery" SizeDefinition="OneInRibbonGallery">
  <InRibbonGallery CommandName="cmdInRibbonGallery" Type="Items"
                   MaxRows="3" MaxColumns="7">
  </InRibbonGallery>
</Group>

In the view part you define the use of a InRibbonGallery along with its layout. Note that InRibbonGallery has more control on how to layout its items.
More details about InRibbonGallery attributes can be found on MSDN.

Using InRibbonGallery – Code Behind
Similar to DropDownGallery code. This post is long enough.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it

Windows Ribbon for WinForms, Part 10 – Working With Images

In this post we'll review the ribbon framework images terminology and see how to set images both statically and dynamically in your WinForms application.

More details can be found at Specifying Ribbon Image Resources on MSDN.

Large Images vs. Small Images
Many ribbon controls allow you to specify an image. For example: Button, ComboBox, and Spinner.
Most of these controls have two properties, one for a large image and one for small. The Ribbon framework will choose one of these sizes according to the available screen space and your definitions for group scaling.

Large image is usually of size 32x32 pixels and Small image is usually of size 16x16 pixels.
I say usually, because this can change. The actual image size should be dependent on your chosen resolution and DPI settings. Microsoft recommended sizes for images are as follows:

DPI

  Small Image

  Large Image

96 dpi

  16x16 pixels

  32x32 pixels

120 dpi

  20x20 pixels

  40x40 pixels

144 dpi

  24x24 pixels

  48x48 pixels

192 dpi

  32x32 pixels

  64x64 pixels

The images for a ribbon control are exposed via the LargeImage and SmallImage properties.

High Contrast Mode
High Contrast is a windows accessibility feature designed for people who have vision impairment. It can be turned on/off by pressing: Left ALT + Left SHIFT + PRINT SCREEN.

The mode’s main affect is changing the system colors, so that near colors have high contrast.
Now, in order to support high contrast mode in your application, the ribbon framework exposes two extra properties: LargeHighContrastImage and SmallHighContrastImage which allows you to set images specifically for this mode. Here is an example of how an applications usually looks in high contrast mode:

image

Setting Images Statically
So we’ve mentioned that we have 4 image properties: LargeImage, SmallImage, LargeHighContrastImage and SmallHighContrastImage. And that the size of the images depends on the current system settings.
So we need a way to supply the application different images for these scenarios. Here it is:

<Command Name="cmdCut" Id="1008" LabelTitle="Cut">
  <Command.LargeImages>
    <Image Source="res/CutLargeImage32.bmp" MinDPI="96" />
    <Image Source="res/CutLargeImage40.bmp" MinDPI="120" />
    <Image Source="res/CutLargeImage48.bmp" MinDPI="144" />
    <Image Source="res/CutLargeImage64.bmp" MinDPI="192" />
  </Command.LargeImages>
  <Command.SmallImages>
    <Image Source="res/CutSmallImage16.bmp" MinDPI="96" />
    <Image Source="res/CutSmallImage20.bmp" MinDPI="120" />
    <Image Source="res/CutSmallImage24.bmp" MinDPI="144" />
    <Image Source="res/CutSmallImage32.bmp" MinDPI="192" />
  </Command.SmallImages>
  <Command.LargeHighContrastImages>
    <Image Source="res/CutLargeImage32HC.bmp" MinDPI="96" />
    <Image Source="res/CutLargeImage40HC.bmp" MinDPI="120" />
    <Image Source="res/CutLargeImage48HC.bmp" MinDPI="144" />
    <Image Source="res/CutLargeImage64HC.bmp" MinDPI="192" />
  </Command.LargeHighContrastImages>
  <Command.SmallHighContrastImages>
    <Image Source="res/CutSmallImage16HC.bmp" MinDPI="96" />
    <Image Source="res/CutSmallImage20HC.bmp" MinDPI="120" />
    <Image Source="res/CutSmallImage24HC.bmp" MinDPI="144" />
    <Image Source="res/CutSmallImage32HC.bmp" MinDPI="192" />
  </Command.SmallHighContrastImages>
</Command>

If you don’t specify all theses images, the ribbon framework will use the available images and resize them according to his needs. Of course, providing the images yourself is the way to get the best results.

Setting Images Dynamically
In this section we’ll see how to dynamically set the images for a button. The end result will look like this:

image

This time the image doesn’t help, you need to run it yourself to see the code at work.
The “Swap Once” button demonstrates the simplest way to set the LargeImage property programmatically.
The “Swap Image” button demonstrates how to set the image according to the recommended size.

I’ve added a new function to the RibbonLib.Ribbon class, named ConvertToUIImage. Here is how you use it:

void _buttonDropA_OnExecute(PropertyKeyRef key, PropVariantRef currentValue, IUISimplePropertySet commandExecutionProperties)
{
    // load bitmap from file
    Bitmap bitmap = new System.Drawing.Bitmap(@"..\..\Res\Drop32.bmp");
    bitmap.MakeTransparent();

    // set large image property
    _buttonDropA.LargeImage = _ribbon.ConvertToUIImage(bitmap);
}

If you want to set an image which has the correct size according to the current DPI settings, in order to avoid the ribbon framework from resizing your image, you should check the value of SystemInformation.IconSize.Width.
Large images size should be
(SystemInformation.IconSize.Width x SystemInformation.IconSize.Width) and small images size should be (SystemInformation.IconSize.Width/2) x (SystemInformation.IconSize.Width/2).

Here is an example for setting an image according to windows settings:

void _buttonDropB_OnExecute(PropertyKeyRef key, PropVariantRef currentValue, IUISimplePropertySet commandExecutionProperties)
 {
     List<int> supportedImageSizes = new List<int>() { 32, 48, 64 };

     Bitmap bitmap;
     StringBuilder bitmapFileName = new StringBuilder();

     int selectedImageSize;
     if (supportedImageSizes.Contains(SystemInformation.IconSize.Width))
     {
         selectedImageSize = SystemInformation.IconSize.Width;
     }
     else
     {
         selectedImageSize = 32;
     }

     exitOn = !exitOn;
     string exitStatus = exitOn ? "on" : "off";

     bitmapFileName.AppendFormat(@"..\..\Res\Exit{0}{1}.bmp", exitStatus, selectedImageSize);

     bitmap = new System.Drawing.Bitmap(bitmapFileName.ToString());
     bitmap.MakeTransparent();

     _buttonDropB.LargeImage = _ribbon.ConvertToUIImage(bitmap);
 }

Behind the scenes
What ConvertToUIImage method actually does is creating an instance of a ribbon framework COM object named UIRibbonImageFromBitmapFactory, which implements IUIImageFromBitmap. This interface supplies a function for wrapping a given HBITMAP (handle for bitmap) as an IUIImage interface.
The ribbon image properties work with these instances of IUIImage. Note that the actual creation of UIRibbonImageFromBitmapFactory is done in the RibbonLib.Ribbon InitFramework method.

public IUIImage ConvertToUIImage(Bitmap bitmap)
{
    if (_imageFromBitmap == null)
    {
        return null;
    }

    IUIImage uiImage;
    _imageFromBitmap.CreateImage(bitmap.GetHbitmap(), Ownership.Transfer, out uiImage);

    return uiImage;
}

Bonus
Similar to my implementation of helper classes for Spinner and ComboBox ribbon controls, I’ve added helper classes for Tab, Group and Button controls. These helpers let you change properties of tabs, groups and buttons easily. The button class also exposes an OnExecute event, which facilitate the way you respond to button clicks.

As always, the result of this post is yet another example of using ribbon features in WinForms applications. Find it at Windows Ribbon for WinForms.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it

Windows Ribbon for WinForms, Part 9 – Changing Ribbon Colors

Introduction to the feature
The feature I want to talk about today is how to change the ribbon general colors. Note that you can’t change the colors of a specific ribbon item, only the entire ribbon.

There are 3 colors we can change:

  • Background Color
  • Highlight Color
  • Text Color

Here is an example of a colored ribbon:

image

How to do it?
I’ve added a new method to the RibbonLib.Ribbon class in my Windows Ribbon for WinForms library.
Following is an example of how to use it:

private void Form1_Load(object sender, EventArgs e)
{
    // init ribbon framework
    _ribbon.InitFramework(this);

    // set ribbon colors
    _ribbon.SetColors(Color.Wheat, Color.IndianRed, Color.BlueViolet);
}

Behind the scenes
What the SetColors method actually does is:

  • Get IPropertyStore interface from the IUIFramework (which represents the ribbon framework)
  • Create 3 PropVariant variables that will hold the 3 colors we want to set
  • Convert the colors: RGB –> HSL –> HSB –> uint, see next section.
  • Set the relevant properties with the converted colors values
public void SetColors(Color background, Color highlight, Color text)
{
    if (_framework == null)
    {
        return;
    }
    
    IPropertyStore propertyStore = (IPropertyStore)_framework;
    PropVariant backgroundColorProp = new PropVariant();
    PropVariant highlightColorProp = new PropVariant();
    PropVariant textColorProp = new PropVariant();

    uint backgroundColor = ColorHelper.HSB2uint(
                             ColorHelper.HSL2HSB(
                               ColorHelper.RGB2HSL(background)));
    
    uint highlightColor = ColorHelper.HSB2uint(
                            ColorHelper.HSL2HSB(
                              ColorHelper.RGB2HSL(highlight)));
    
    uint textColor = ColorHelper.HSB2uint(
                       ColorHelper.HSL2HSB(
                         ColorHelper.RGB2HSL(text)));
    
    backgroundColorProp.SetUInt(backgroundColor);
    highlightColorProp.SetUInt(highlightColor);
    textColorProp.SetUInt(textColor);
    
    propertyStore.SetValue(ref RibbonProperties.UI_PKEY_GlobalBackgroundColor,
                           ref backgroundColorProp);
    propertyStore.SetValue(ref RibbonProperties.UI_PKEY_GlobalHighlightColor,
                           ref highlightColorProp);
    propertyStore.SetValue(ref RibbonProperties.UI_PKEY_GlobalTextColor,
                           ref textColorProp);
    propertyStore.Commit();
}

Color Format
I didn’t dive into the colors format world, so I’ll just give a quick reference:

RGB is the well known color format that us, developers, like and understand.

RGB can be converted to HSL. This should be a common transformation. or as Microsoft wrote here, “easily accomplished with most photo editing software”. In this page Microsoft also gives the formulas for converting HSL to HSB.

You can find here C# code for transforming RGB to HSL and vice versa.

Finally HSB is converted to uint by just OR-ing the values, much like RGB to uint conversion.
I’ve encapsulated all these details in a helper class named RibbonLib.ColorHelper.

A new sample, named 07-RibbonColor, summarize the details of this post. Find it on the project site.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it

Windows Ribbon for WinForms, Part 8 – ComboBox

ComboBox Control
A ribbon ComboBox control is basically the normal ComboBox control that we all love, but with the additional feature of dividing the items into categories. A category is not an item and cannot be selected from the ComboBox. It is only used to organized the items.

image 

ComboBox Properties
Every ribbon control has properties that defines the way it looks and behaves. Here is a quick review of ComboBox properties, divided into logical groups:

ComboBox Value Related Properties

  • Items Source – The list of ComboBox items. It is exposed as an IUICollection where every element in the collection is of type: IUISimplePropertySet. More on this later.
    Property Identifier: UI_PKEY_ItemsSource
  • Categories – The list of categories. Also exposed as an IUICollection of IUISimplePropertySet elements.
    Property Identifier: UI_PKEY_Categories
  • Selected Item – The index of the selected item in the ComboBox. If nothing is selected returns UI_Collection_InvalidIndex, which is a fancy way to say -1.
    Property Identifier: UI_PKEY_SelectedItem
  • String Value – The current string in the ComboBox. This can be a string that isn’t one of the possible items in the ComboBox, in case the ComboBox has IsEditable set to true.
    Property Identifier: UI_PKEY_StringValue

ComboBox Appearance Related Properties

  • Representative String – A string that represents the common value for the ComboBox. This is used to calculate the width of the ComboBox, so you should set here the longest string you forecast. Note that it doesn’t have to be an actual value, it can be also: “XXXXXXXX”.
    Property Identifier: UI_PKEY_RepresentativeString

Common Appearance & Image Properties

See these sections at Windows Ribbon for WinForms, Part 7 – Spinner

Using ComboBox – Ribbon Markup
As always, a command should be defined:

<Command Name="cmdComboBox2" Id="1019" />

The views section:

<Application.Views>
  <Ribbon>
    <Ribbon.Tabs>
      <Tab>
        <Group>
          <ComboBox CommandName="cmdComboBox2"
                    IsAutoCompleteEnabled="true"
                    IsEditable="true"
                    ResizeType="VerticalResize" />
        </Group>
      </Tab>
    </Ribbon.Tabs>
  </Ribbon>
</Application.Views>

ComboBox Attributes:

  • CommandName – Name of the command attached to this ComboBox.
  • IsAutoCompleteEnabled – Flag that indicated whether to complete the words as you write.
  • IsEditable – Flag that indicates whether to allow free writing in the ComboBox.
  • ResizeType – Allow resize of the ComboBox. Can be NoResize or VerticalResize.

Using ComboBox – Code Behind
In a similar way to the spinner control, I’ve created a helper classes that encapsulates the interaction between the ComboBox and ribbon framework. To use the ComboBox, create a RibbonComboBox instance, passing to the constructor the Ribbon instance and command ID of the ComboBox:

private Ribbon _ribbon;
private RibbonComboBox _comboBox2;

public Form1()
{
    InitializeComponent();

    _ribbon = new Ribbon();
    _comboBox2 = new RibbonComboBox(_ribbon, (uint)RibbonMarkupCommands.cmdComboBox2);
    _comboBox2.RepresentativeString = "XXXXXXXXXXX";
}

Note: We set the RepresentativeString property BEFORE the initializing the ribbon framework. This is because for some reason the framework reads this property only once, when the ribbon is initialized. This means that if you change it after initialization it will have no affect since the framework doesn’t reads this property anymore. By the way, according to the current documentation of the ribbon framework, this property is not part of the ComboBox, but as mentioned earlier, this property controls the width of the ComboBox.

In the next code snippet you can see how to use another helper class, named GalleryItemPropertySet. This class represents a container for properties of a single element in an IUICollection.

Adding categories and items to the ComboBox is done like this:

private void Form1_Load(object sender, EventArgs e)
{
    _ribbon.InitFramework(this);

    // set combobox2 label
    _comboBox2.Label = "Advanced Combo";
    
    // set _comboBox2 categories
    IUICollection categories2 = _comboBox2.Categories;
    categories2.Clear();
    categories2.Add(new GalleryItemPropertySet() { Label="Category 1", CategoryID=1 });
    categories2.Add(new GalleryItemPropertySet() { Label="Category 2", CategoryID=2 });

    // set _comboBox2 items
    IUICollection itemsSource2 = _comboBox2.ItemsSource;
    itemsSource2.Clear();
    itemsSource2.Add(new GalleryItemPropertySet() { Label="Label 1", CategoryID=1 });
    itemsSource2.Add(new GalleryItemPropertySet() { Label="Label 2", CategoryID=1 });
    itemsSource2.Add(new GalleryItemPropertySet() { Label="Label 3", CategoryID=2 });
}

Note: Adding items and categories can be done only AFTER the Ribbon Framework has been initialized.

Finally, you must connect the IUICommandHandler.UpdateProperty method with the implementation of this method in our RibbonComboBox class:

public HRESULT UpdateProperty(uint commandId, ref PropertyKey key, PropVariantRef currentValue, ref PropVariant newValue)
{
    if (commandId == (uint)RibbonMarkupCommands.cmdComboBox2)
    {
        _comboBox2.UpdateProperty(ref key, currentValue, ref newValue);
    }
    return HRESULT.S_OK;
}

Update (18.11.2009): The updated version of the Ribbon class provides an implementation for IUICommandHandler, so the user doesn't need to implement Execute and UpdateProperty methods anymore.

IUICollection Events
Objects that implements IUICollection interface usually expose an OnChanged event that is called when the collection has changed due to: Insert item, Remove item, Replace item, Reset collection.

This event is exposed using the standard COM events mechanism, namely: IConnectionPointContainer, IConnectionPoint and Advise().

To help the user to avoid these issue altogether, I’ve created the UICollectionChangedEvent class, which attaches to a given IUICollection and exposes the OnChanged event as a normal .NET event.

Following is an example of using it:

private RegisterEvent()
{
    _uiCollectionChangedEvent = new UICollectionChangedEvent();
    _uiCollectionChangedEvent.Attach(_comboBox1.ItemsSource);
    _uiCollectionChangedEvent.OnChanged +=
        new UICollectionChangedEvent.OnChangedEventHandler(_ChangedEvent_OnChanged);
}

 

void _ChangedEvent_OnChanged(UI_CollectionChange action,
                             uint oldIndex, object oldItem,
                             uint newIndex, object newItem)
{
    MessageBox.Show("Got OnChanged event. Action = " + action.ToString());
}

Note: There is no OnChanged event for the ComboBox. Only for IUICollection, which is completely different.

Update (27.10.2009): The ComboBox itself has 3 events, Execute, Preview and CancelPreview. The Execute event can be used as a "Selected Change" event. See this future post.

Summary
You can find a working sample that demonstrates using a ComboBox control at Windows Ribbon for WinForms under sample “06-ComboBox”.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it

Windows Ribbon for WinForms, Part 7 – Spinner

Spinner Control
A Spinner control is a control that represents a decimal value (like double, only with higher resolution). The control is composed of an edit box and two buttons, for increment and decrement. Let’s try the thing with the 1000 words:

image


Spinner Properties
Every ribbon control has properties that defines the way it looks and behaves. Here is a quick review of spinner properties, divided into logical groups:

Spinner Value Related Properties

  • Decimal Value – The actual decimal value of the spinner.
    Property Identifier: UI_PKEY_DecimalValue
  • Increment – The size of the step when pressing on increment / decrement buttons.
    Property Identifier: UI_PKEY_Increment
  • Max Value – Maximum value that can be set using the spinner control.
    Property Identifier: UI_PKEY_MaxValue
  • Min Value - Minimum value that can be set using the spinner control.
    Property Identifier: UI_PKEY_MinValue

Note: When using decimal values with the Ribbon Framework you must use a PropVariant implementation that support decimal values. More on this at: Setting Decimal value on PropVariant.

Spinner Appearance Related Properties

  • Decimal Places – The number of digits to show after the point.
    Property Identifier: UI_PKEY_DecimalPlaces
  • Format String – The units of the value. In the previous image, it is “m”, for meters.
    Property Identifier: UI_PKEY_FormatString
  • Representative String – A string that represents the common value for the spinner. This is used to calculate the width of the spinner, so you should set here the longest string you forecast. Note that it doesn’t have to be an actual value, it can be also: “XXXXXXXX”.
    Property Identifier: UI_PKEY_RepresentativeString

Common Appearance Properties

  • Keytip – The keyboard accelerator for this command in the ribbon framework (view it by pressing ALT in a ribbon application).
    Property Identifier: UI_PKEY_Keytip
  • Label – The label for this command. Usually appears next to the attached control.
    Property Identifier: UI_PKEY_Label
  • Tooltip Title – The title of the tooltip for this command.
    Property Identifier: UI_PKEY_TooltipTitle
  • Tooltip Description – The description of the tooltip for this command.
    Property Identifier: UI_PKEY_TooltipDescription
  • Enabled – Flag that indicates whether this control is enabled or not.
    Property Identifier: UI_PKEY_Enabled

Image Properties

  • Large Image – The large image for this command.
    Property Identifier: UI_PKEY_LargeImage
  • Small Image – The small image for this command.
    Property Identifier: UI_PKEY_SmallImage
  • Large High Contrast Image – The large high contrast image for this command.
    Property Identifier: UI_PKEY_LargeHighContrastImage
  • Small High Contrast Image – The small high contrast image for this command.
    Property Identifier: UI_PKEY_SmallHighContrastImage

Using Spinner – Ribbon Markup
As always, a command should be defined:

<Command Name="cmdSpinner"
         Id="1018"
         LabelTitle="My Spinner">
</Command>

The views section is also simple:

<Application.Views>
  <Ribbon>
    <Ribbon.Tabs>
      <Tab>
        <Group>
          <Spinner CommandName="cmdSpinner" />
        </Group>
      </Tab>
    </Ribbon.Tabs>
  </Ribbon>
</Application.Views>

Using Spinner – Code Behind
To help us manipulate the spinner I’ve created a helper class that encapsulates all the work regarding the ribbon framework. To use it, create a RibbonSpinner instance, passing to the constructor the Ribbon instance and command ID of the spinner:

private Ribbon _ribbon;
private RibbonSpinner _spinner;
                
public Form1()
{
    InitializeComponent();

    _ribbon = new Ribbon();
    _spinner = new RibbonSpinner(_ribbon, (uint)RibbonMarkupCommands.cmdSpinner);
}

Also we need to connect the IUICommandHandler.UpdateProperty method with the implementation of this method in our spinner class:

public HRESULT UpdateProperty(uint commandId, ref PropertyKey key,
                              ref PropVariant currentValue, ref PropVariant newValue)
{
    if (commandId == (uint)RibbonMarkupCommands.cmdSpinner)
    {
        _spinner.UpdateProperty(ref key, ref currentValue, ref newValue);
    }
    return HRESULT.S_OK;
}

Update (18.11.2009): The updated version of the Ribbon class provides an implementation for IUICommandHandler, so the user doesn't need to implement Execute and UpdateProperty methods anymore.

Now you can manipulate the spinner properties easily, by setting them on the _spinner instance, for example:

private void InitSpinner()
{
    _spinner.DecimalPlaces = 2;
    _spinner.DecimalValue = 1.8M;
    _spinner.TooltipTitle = "Height";
    _spinner.TooltipDescription = "Enter height in meters.";
    _spinner.MaxValue = 2.5M;
    _spinner.MinValue = 0;
    _spinner.Increment = 0.01M;
    _spinner.FormatString = " m";
    _spinner.RepresentativeString = "2.50 m";
    _spinner.Label = "Height:";
}

Where can we get it?
You can find a working sample that demonstrates using a spinner control at Windows Ribbon for WinForms under sample “05-Spinner”.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it

Setting Decimal value on PropVariant

PROPVARIANT is Important
PROPVARIANT is an important COM structure that is used in many windows shell features like: Shell Namespace Extensions, Taskbar Jump List, Sensors and Windows Ribbon Framework, just to name a few.

In order to use these shell features in managed code, one must provide a .NET wrapper for this struct.
Now, you might have expected that if this struct is so important, there would be good .NET wrappers for it..

Unfortunately, this is not the case. The problem with this struct is that it is pretty complicated. It’s constructed from some simple fields and a union of ALL COM types. This includes 73 (!) types, from simple native types till SAFEARRAYs (COM arrays) of COM object types. So in order to marshal this type properly between native-managed boundaries, one must handle marshalling according to the actual union type (marshalling an integer is different than marshalling a string and different than marshalling a pointer to an array of objects).

Enter VT_DECIMAL
One more thing that makes it hard to implement a managed version is that PROPVARIANT itself has a special treatment for one type, namely DECIMAL.

PROPVARIANT structure has a size of 20 bits (on 32 bits systems), its definition is something like:

struct PROPVARIANT
{
    VARTYPE vt;
    PROPVAR_PAD1 wReserved1;
    PROPVAR_PAD2 wReserved2;
    PROPVAR_PAD3 wReserved3;
    union
    {
        CHAR cVal;
        UCHAR bVal;
        INT intVal;
        UINT uintVal;
        // ... you got the point ...
        BSTR *pbstrVal;
        IUnknown **ppunkVal;
        IDispatch **ppdispVal;
        LPSAFEARRAY *pparray;
    }
}

The VARTYPE member specify the type of value the struct currently hold.
Now, If the VARTYPE equals VT_DECIMAL then the entire structure (including the VARTYPE part itself!) is being read as if it was a DECIMAL structure. Let me stress that again: On VT_DECIMAL the VARTYPE-part of the struct overlaps the DECIMAL structure.

The reason why this can work is that VARTYPE has a size of 2 bytes and it turns out that the first 2 bytes of the DECIMAL structure are reserved. So the values of the DECIMAL type and VARTYPE member can coexist.

struct DECIMAL
{
    USHORT wReserved;
    BYTE scale;
    BYTE sign;
    ULONG Hi32;
    ULONGLONG Lo64;
}

Current Implementations
The main point you should have understand until this point is that implementing a managed wrapper for PROPVARIANT is hard. Specifically if you want to support many types.

Google PROPVARIANT finds several implementations, which supports only a limited set of types.

The best implementation I came across is the one in Windows API Code Pack, which is itself based on a previous work by Adam Root.

In Adam’s implementation the VT_DECIMAL case is not handled. In contrast, Windows API Code Pack v1.0 has an implementation but unfortunately it is quite buggy. Let’s check it together:

public void SetDecimal(decimal value)
{
    valueType = (ushort)VarEnum.VT_DECIMAL;
    valueData = Marshal.AllocCoTaskMem(Marshal.SizeOf(VarEnum.VT_DECIMAL));
    Marshal.StructureToPtr(value, valueData, false);
}

The first problem is the call for Marshal.SizeOf on an enum value. If this was possible, it would have return the size of the enum, which is 4 bytes. Not the size of a DECIMAL struct (20).

The second problem is that Marshal.SizeOf can’t be called on an Enum, it immediately throws an ArgumentException with: “Type 'System.Runtime.InteropServices.VarEnum' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed. “.

The third problem is in the concept. Setting a DECIMAL into a PROPVARIANT should not allocate any memory. The memory is already there, just copy the DECIMAL value onto the PROPVARIANT place.
The valueData element should not hold a pointer to the decimal. It should contain part of the decimal itself!

Our Implementation
Our solution for setting a decimal into the PROPVARIANT structure is quite simple. It uses our knowledge on the exact structure of both DOUBLE and PROPVARIANT structures. The following code should be part of the PropVariant.cs file in Windows API Code Pack:

public void SetDecimal(decimal value)

{

    int[] bits = Decimal.GetBits(value);

    valueData = (IntPtr)bits[0];

    valueDataExt = bits[1];

    wReserved3 = (ushort)(bits[2] >> 16);

    wReserved2 = (ushort)(bits[2] & 0x0000FFFF);

    wReserved1 = (ushort)(bits[3] >> 16);

    valueType = (ushort)VarEnum.VT_DECIMAL;

}

 

decimal decVal // DECIMAL decVal

{

    get

    {

        int[] bits = new int[4];

        bits[0] = (int)valueData;

        bits[1] = valueDataExt;

        bits[2] = (wReserved3 << 16) | wReserved2;

        bits[3] = (wReserved1 << 16);

        return new decimal(bits);

    }

}

What we do is taking the decimal apart, and setting the PropVariant fields according to the correct layout.

Now, since I don’t want to create my own version of Windows API Code Pack, I wrote a similar implementation that changes the private PropVariant fields from outside using reflection. Not the best piece of code but has the advantage of working on top of Windows API Code Pack v1.0. Hopefully they will fix it in a future version.

By the way, extension methods won’t work here since the PropVariant is defined as a struct and so is immutable, so making the extension method change its internals won’t have an affect on the original PropVariant. (Also, no “ref this PropVariant propVariant” is possible).

Here is the less intrusive version:

public static PropVariant GetPropVariant(decimal decimalValue)
{
    // get decimal binary representation
    int[] bits = Decimal.GetBits(decimalValue);
    FieldInfo field;
    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
    
    // setting private members using FieldInfo.SetValue works on class, but not on struct
    object objPropVariant = new PropVariant();
    
    // set PropVariant internal private members using reflection,
    // according to decimal binary representation
    field = typeof(PropVariant).GetField("valueData", bindingFlags);
    field.SetValue(objPropVariant, (IntPtr)bits[0]);
    field = typeof(PropVariant).GetField("valueDataExt", bindingFlags);
    field.SetValue(objPropVariant, bits[1]);
    field = typeof(PropVariant).GetField("wReserved3", bindingFlags);
    field.SetValue(objPropVariant, (ushort)(bits[2] >> 16));
    field = typeof(PropVariant).GetField("wReserved2", bindingFlags);
    field.SetValue(objPropVariant, (ushort)(bits[2] & 0x0000FFFF));
    field = typeof(PropVariant).GetField("wReserved1", bindingFlags);
    field.SetValue(objPropVariant, (ushort)(bits[3] >> 16));
    field = typeof(PropVariant).GetField("valueType", bindingFlags);
    field.SetValue(objPropVariant, (ushort)VarEnum.VT_DECIMAL);
    return (PropVariant)objPropVariant;
}

public static decimal GetDecimal(PropVariant propVariant)
{
    FieldInfo field;
    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
    object objPropVariant = propVariant;
    
    // get PropVariant internal private members using reflection,
    // according to decimal binary representation
    IntPtr valueData;
    Int32 valueDataExt;
    UInt16 wReserved3;
    UInt16 wReserved2;
    UInt16 wReserved1;
    field = typeof(PropVariant).GetField("valueData", bindingFlags);
    valueData = (IntPtr)field.GetValue(objPropVariant);
    field = typeof(PropVariant).GetField("valueDataExt", bindingFlags);
    valueDataExt = (Int32)field.GetValue(objPropVariant);
    field = typeof(PropVariant).GetField("wReserved3", bindingFlags);
    wReserved3 = (UInt16)field.GetValue(objPropVariant);
    field = typeof(PropVariant).GetField("wReserved2", bindingFlags);
    wReserved2 = (UInt16)field.GetValue(objPropVariant);
    field = typeof(PropVariant).GetField("wReserved1", bindingFlags);
    wReserved1 = (UInt16)field.GetValue(objPropVariant);
    int[] bits = new int[4];
    bits[0] = (int)valueData;
    bits[1] = valueDataExt;
    bits[2] = (wReserved3 << 16) | wReserved2;
    bits[3] = (wReserved1 << 16);
    return new decimal(bits);
}

For the sake of completeness I provide PropVariant.cs, which is based on Windows API Code Pack v1.0 version with my fix for proper support for decimal values. Note that it was tested only on a 32bit machine.

Update: As I’ve mentioned at the end of the post Windows Ribbon for WinForms, Part 13 – DropDownColorPicker, I’ve created a newer version of PropVariant class which is a combination of both Windows API Code Pack version and PreviewRibbon version.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it

Windows Ribbon for WinForms, Part 6 – Tabs, Groups and HelpButton

First, your should know that I changed my ribbon library such that Ribbon class is no longer a singleton.
The reason is that I wanted to make an application with two forms, each form has its own ribbon, so needed two ribbon objects.. So this had to change.

Second, in this post I’ll review some more common ribbon features, namely:

  • Tabs
  • Groups
  • Help Button

The result of this post is yet another sample application, named 04-TabGroupHelp. You can find on the project page and it looks like this:

image

Buttons, Buttons, Buttons
So what are tabs? Just containers for other controls. In this sample will use only buttons. I’ll elaborate on the other control types on future posts.

Every tab can contain several groups, which are just a logical division of the controls in the tab, in this post – buttons..

What is the help button? just another button. For some reason it got its own special place and predefined icon (look on the right side of the image).

Using Tabs and Groups
The commands markup is always the same, just a list of items that attach a mnemonic used by the programmer with an ID used by the ribbon framework. And some string and bitmap resources.

As always the interesting part lies in the views markup:

<Application.Views>
  <Ribbon>
    <Ribbon.Tabs>
      <Tab CommandName="cmdTabMain">
        <!-- scary part -->
        <Tab.ScalingPolicy>
          <ScalingPolicy>
            <ScalingPolicy.IdealSizes>
              <Scale Group="cmdGroupFileActions" Size="Large" />
              <Scale Group="cmdGroupExit" Size="Large" />
            </ScalingPolicy.IdealSizes>
            <Scale Group="cmdGroupFileActions" Size="Medium" />
          </ScalingPolicy>
        </Tab.ScalingPolicy>
        <!-- useful part -->
        <Group CommandName="cmdGroupFileActions" SizeDefinition="ThreeButtons">
          <Button CommandName="cmdButtonNew" />
          <Button CommandName="cmdButtonOpen" />
          <Button CommandName="cmdButtonSave" />
        </Group>
        <Group CommandName="cmdGroupExit" SizeDefinition="OneButton">
          <Button CommandName="cmdButtonExit" />
        </Group>
      </Tab>
      <Tab CommandName ="cmdTabDrop">
        <Group CommandName="cmdGroupDrop" SizeDefinition="ThreeButtons">
          <Button CommandName="cmdButtonDropA" />
          <Button CommandName="cmdButtonDropB" />
          <Button CommandName="cmdButtonDropC" />
        </Group>
      </Tab>
    </Ribbon.Tabs>
  </Ribbon>
</Application.Views>

What I’ve defined in this markup is two tabs. The first tab has two groups and the second tab has one group.
I’ve marked to important parts of the markup in the first tab. Unfortunately, due to the ribbon schema definition the scary part must come before the useful part.

The useful part:
just a simple definition of the groups in the tab, and the controls in the group. one interesting thing to note is the SizeDefinition attribute on the Group tag. This is a definition of the layout for the controls in the group. You can define your own layouts or use a predefined list which usually is quite enough. see the full list (with convenient images) at “Customizing a Ribbon Through Size Definitions and Scaling Policies” on MSDN.

The scary part:
In order to understand this part you should know that one of the features of the ribbon framework is the ability to re-layout your ribbon controls according to the amount of space it has. It pretty much handle this automatically but it does requires you to define hints on how you want your layout to scale when the application form gets smaller and smaller. So in the scary part we first define the ideal size of each group. The size can be one of four values: Large, Medium, Small and Popup. Popup means that the whole group was shrunk to a single icon that upon clicking popups the original group.

After defining the ideal size for the group you can define the order of scaling down, meaning which group should scale down first. In this way you can make your application most important controls more visible then less important ones.

The code-behind for acting on these buttons is the same as in the previous posts, just handle Execute function of the correct command ID.

Update (18.11.2009): Just register to the OnExecute event of the correct button.

Using Help Button
To use the help button just add the following to the views markup:

<Application.Views>
  <Ribbon>
    <Ribbon.HelpButton>
      <HelpButton CommandName="cmdHelp" />
    </Ribbon.HelpButton>
    <Ribbon.Tabs>
      …
    </Ribbon.Tabs>
  </Ribbon>
</Application.Views>

That’s it for now,
Arik Poznanski

kick it on DotNetKicks.com Shout it

How to convert an image to 32 bit BMP

BMP image format can be saved in several different pixel format. Some programs and APIs requires their resources to be in 32bit BMP. Unfortunately, Microsoft Paint support saving only 24bit BMPs.

Since this issue is raised a lot when dealing with Windows Ribbon Framework I’ve decided to solve it once and for all. I’ve written a small console application that converts images into 32bit BMPs.

you can get convert2bmp.exe here, under “Other Available Downloads”.

Using the program

convert2bmp <source> <target>

for example: convert2bmp my24bit.BMP my32bit.BMP

What the program does

  • Load the image
  • Set the white color as the transparent color, thus making use of the alpha channel and forcing the image to be 32bit
  • Save the image

Source Code (C#)
Basically it looks like this:

Bitmap bmp = Image.FromFile(sourceFilename) as Bitmap;
bmp.MakeTransparent(Color.White);
bmp.Save(targetFilename, ImageFormat.Bmp);

Of course the actual version is 10 times longer due to error handling code.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it

Windows Ribbon for WinForms, Part 5 - Application Menu with SplitButton and DropButton

Today I'll show you how to use the following ribbon features inside a Winforms application:

  • Menu Group
  • Split Button in an Application Menu
  • Drop Down Button in an Application Menu

The result of this post looks like this:

image

SplitButton VS DropDownButton
What is actually the difference between those two?

DropDownButton is NOT a button, meaning clicking it does nothing. Hovering over it will open a list of buttons.
On the other hand, SplitButton is itself a button, which you can respond to. Hovering over it will also open a list of buttons.

The common use for a DropDownButton is when you want to expose a set of items which doesn't have an obvious default option. Think of “Rotate” feature in Paint. you have Rotate90, Rotate180 and Rotate270 but none of them is an obvious default.

The common use for a SplitButton is when you want to expose a set of items which has an obvious default option. Think of “Save As” button, where there is a default save format.

Using SplitButton and DropDownButton in Ribbon Application Menu
The commands markup is the same as before, just define some command to be listed later in the views markup. For example:

<Command Name="cmdButtonDropA"
    Id="1008"
    LabelTitle="Drop A"
    LabelDescription="Sub button A"
    TooltipTitle="Drop A">
  <Command.LargeImages>
    <Image>Res/DropA32.bmp</Image>
  </Command.LargeImages>
</Command>

<Command Name="cmdButtonDropB"
    Id="1009"
    LabelTitle="Drop B"
    LabelDescription="Sub button B"
    TooltipTitle="Drop B">
  <Command.LargeImages>
    <Image>Res/DropB32.bmp</Image>
  </Command.LargeImages>
</Command>

The relevant views markup is defined as follows:

<DropDownButton CommandName='cmdDropDownButton'>
  <MenuGroup Class='MajorItems'>
    <Button CommandName='cmdButtonDropA' />
    <Button CommandName='cmdButtonDropB' />
    <Button CommandName='cmdButtonDropC' />
  </MenuGroup>
</DropDownButton>

<SplitButton>
  <SplitButton.ButtonItem>
    <Button CommandName='cmdButtonDropB' />
  </SplitButton.ButtonItem>
  <SplitButton.MenuGroups>
    <MenuGroup Class='MajorItems'>
      <Button CommandName='cmdButtonDropA' />
      <Button CommandName='cmdButtonDropB' />
      <Button CommandName='cmdButtonDropC' />
    </MenuGroup>
  </SplitButton.MenuGroups>
</SplitButton>

The code behind section is also the same as in the previous post, just handle IUICommandHandler.Execute with the corresponding ID.
Update (18.11.2009): Just register to the OnExecute event of the corresponding button.

MenuGroup
A menu group is a collection of menu items inside the application menu. the most useful feature it provides is giving a title to a group of items, like “File Menu” in the last image.

If you just want a simple separator between menu items you use a MenuGroup that is not attached to any command.

<MenuGroup>
  …
</MenuGroup>

As always, the most updated version of “Windows Ribbon for WinForms” along with sample applications can be found here.

That’s it for now,
Arik Poznanski.

kick it on DotNetKicks.com Shout it