StayOnTarget
StayOnTarget

Reputation: 12998

Create a dropdown 'panel' area from a WPF ribbon?

The WPF ribbon (System.Windows.Controls.Ribbon) includes a number of controls which you can add to your ribbon having "dropdown" style behavior where clicking the control's main button shows you a new area.

Examples: RibbonMenuButton, RibbonSplitButton, RibbonMenuButton, RibbonGallery, etc.

However, as far as I can see all of them are designed to show you a list of things from which the user makes a selection. But instead, is there any way to display a 'panel' area that is non-selectable, upon which other controls could be placed?

As an example, here is a screenshot from MS Outlook:

enter image description here

The upper red area is NOT itself a selection in a list. Instead it has a custom control (the table size-picker thing).

But the blue items ARE selectable items which function like a traditional menu.

Its the red area I'm interested in understanding.

(I don't know if Outlook was coded using the WPF Ribbon, and that doesn't matter at all - I'm just using it as an illustration of what I'm looking for.)


Note - I'm not trying to replicate this Outlook table-picker specifically, its just an example of the ways in which you might use a non-selectable 'panel' area within the dropdown region.

Upvotes: 2

Views: 1487

Answers (4)

user20568
user20568

Reputation: 71

I had been looking at similar documentation and eventually developed this solution through trial and error, on Visual Studio 2022.

I got the RibbonSplitButton to produce an area where I could dynamically load buttons, by using the following XAML code...

<RibbonSplitButton x:Name="btnColor" Height="21" Width="40" Margin="-139,-9,139,9" SmallImageSource="images/font-color.png" ToolTip="Font Color" >
    <RibbonMenuItem x:Name="itmDefaultColor">
        <RibbonMenuItem.Header>
            <StackPanel Orientation="Horizontal">
                <Rectangle x:Name="rctDefaultColor" Width="13" Height="13" Fill="Transparent" Stroke="Black" Margin="-25,0,15,0"/>
                <TextBlock Text="Default Color"/>
            </StackPanel>
        </RibbonMenuItem.Header>
    </RibbonMenuItem>
    <RibbonGallery x:Name="galAllColorSwatches" ScrollViewer.VerticalScrollBarVisibility="Disabled">
        <RibbonGallery.Resources>
            <Style TargetType="Button">
                <EventSetter Event="UIElement.PreviewMouseLeftButtonDown" Handler="Button_PreviewMouseLeftButtonDown" />
                <EventSetter Event="PreviewKeyDown" Handler="Button_PreviewKeyDown" />
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="BorderBrush" Value="White"/>
                    </Trigger>
                    <Trigger Property="IsFocused" Value="True">
                        <Setter Property="BorderBrush" Value="White"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
            <Style TargetType="Rectangle">
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Stroke" Value="White"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </RibbonGallery.Resources>
        <RibbonGalleryCategory Header="Custom Colors" FontWeight="Bold" />
        <RibbonGallery x:Name="galCustomColors" MinWidth="130" Width="130" MaxWidth="130" ScrollViewer.VerticalScrollBarVisibility="Disabled">
            <RibbonGallery.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Horizontal"></WrapPanel>
                </ItemsPanelTemplate>
            </RibbonGallery.ItemsPanel>
        </RibbonGallery>
        <RibbonGalleryCategory Header="Basic Colors" FontWeight="Bold" />
        <RibbonGallery x:Name="galBasicColors" MinWidth="130" Width="130" MaxWidth="130" ScrollViewer.VerticalScrollBarVisibility="Disabled">
            <RibbonGallery.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Horizontal"></WrapPanel>
                </ItemsPanelTemplate>
            </RibbonGallery.ItemsPanel>
        </RibbonGallery>
    </RibbonGallery>
    <RibbonMenuItem x:Name="itmColor" ImageSource="images/font-color.png" Header="Pick Color..."/>
</RibbonSplitButton>

The end result resembles the following image, but requires code to insert the color swatches...

Image of the menu that appears for the example RibbonSplitButton

Note there is an outer wrapper RibbonGallery named galAllColorSwatches, and that this element has resources set up to respond to specific events when the mouse rolls over buttons that get added dynamically by code, and also to respond to button click events. You could also manually construct all your buttons, and you could potentially add other types of controls in a RibbonGallery.

Creating and inserting a button by code can be accomplished like this in Visual Basic...

    ' Prepares a button for the color swatch...
    Dim [YOUR_BUTTON] As New Button
    ' Works with the button...
    With [YOUR_BUTTON]
        .Name = "btnSwatch"
        .Content = [INSERT_YOUR_CONTENT_HERE]
        .Margin = New Thickness(0, 0, 0, 0)
        .Padding = New Thickness(0, 0, 0, 0)
        .ToolTip = strColor
        .Tag = strColor
    End With
    ' Adds the item to the custom colors...
    [YOUR_RIBBONGALLERY_NAME].Items.Add([YOUR_BUTTON])

...but you could also use binding.

If the above code is nested in a For..Next or similar loop, it can produce a large number of generic buttons like the grid in the image you supplied.

All of the RibbonGalleryCategory elements are siblings of the RibbonGallery elements inside the outer wrapper. The inner RibbonGallery elements each contain RibbonGallery.ItemsPanel and ItemsPanelTemplate that helps define the layout for their contents. My example uses WrapPanel elements to achieve the grid-like layout, but you could potentially add any other compatible element there.

The layout of the content in the WrapPanel is controlled by the maximum and minimum width settings of the RibbonGallery elements, and the width settings of the buttons that are dynamically inserted, so you would need to customize those dimensions based on what you intend to achieve.

The top and bottom of the XAML code each have a standard RibbonMenuItem element. If you nest menu items inside each other, they should produce the standard nested menu with the > indicator. The top item uses a dynamic color swatch that can be loaded with a specific color.

If you construct any RibbonSplitButton like this, and it has an event handler, you may need to use e.Handled = True in the event handler to prevent the menu items and menu buttons from triggering the events in the RibbonSplitButton and vice-versa.

Upvotes: 1

Matthew Anderson
Matthew Anderson

Reputation: 11

For posterity, it's not currently possible. It appears that the RibbonMenuButton uses a private Popup to display the dropdown content.

Source: https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/System.Windows.Controls.Ribbon/Microsoft/Windows/Controls/Ribbon/RibbonMenuButton.cs Lines 1309 and 1335.

Upvotes: 0

Ramakrishnan R
Ramakrishnan R

Reputation: 11

Microsoft RibbonMenuButton / RibbonSplitButton wont support custom controls in dropdown.

Even if we manage to do that by changing the Content of RibbonMenuItem / RibbonGalleryItem, we will still get selection decoration around those controls on mouse hover

Best way is to use the combination of RibbonToggleButton and Popup Control as below

Then you can place whatever custom control you want inside that popup Control

<StackPanel Orientation="Vertical">
<RibbonToggleButton
            x:Name="yAxis"
            Label="Y Axis"
            SmallImageSource="..\Images\ChartYAxis16.png"
            LargeImageSource="..\Images\ChartYAxis32.png"
            RibbonTwoLineText.HasTwoLines="True"
            RibbonTwoLineText.PathData="M 0 0 L 2.5 3 L 5 0 Z">
        </RibbonToggleButton>
        <Popup
            IsOpen="{Binding IsChecked, ElementName=yAxis}">
            <mycontrols:AnyControl/>
        </Popup>
    </StackPanel>

enter image description here

Of course you may need to handle the dropdown closing programmatically by unchecking the togglebutton whenever user clicks outside the togglebutton or dropdown popup

Upvotes: 1

Andy
Andy

Reputation: 12276

You can put anything you like inside a ribbonmenubutton.

For example:

    <Ribbon>
        <RibbonMenuButton Label="Button One">
            <Grid Height="100" Width="200">
                <TextBlock VerticalAlignment="Top" Text="AAAA"/>
                <TextBlock VerticalAlignment="Bottom" Text="ZZZZ"/>
            </Grid>
        </RibbonMenuButton>
    </Ribbon>

You'd want to extract and change the ribbonmenubutton template to avoid the gap at the left.

Upvotes: 0

Related Questions