2
votes

I have a basic C# UWP app running on the desktop. The app's layout is similar to Word Mobile, i.e. it has a main menu with a command bar below to apply various commands.

The default CommandBar has primary commands (displayed as icons) and secondary commands shown in the overflow menu.

The Word Mobile app uses a special command bar with "groups" of command buttons. Each group has their own overflow menu should the window size be too small to show all commands (see screenshots below).

Is there a way to get these "grouped" commands with their own overflow menu using standard XAML controls? If not, what would be a strategy to implement a custom control like this?

Example:

(1) Wide window: CommandBar shows all command buttons:

CommandBar showing all command buttons

(2) Small window: two separate overflow menu buttons:

enter image description here

2
looks like you would be able to do it using AppBarButtons with a Flyout and hiding and showing certain buttons using VisualStates and AdaptiveTriggers. I'll try and put something together later (if someone hasnt beaten me to it :)SWilko

2 Answers

0
votes

Sorry about the delay but I thought I would post a proof of concept answer. For this example I have created 7 action AppBarButton in a CommandBar including the following

2 Buttons that are independent - AllAppsButton, CalculatorButton

2 Buttons that are part of the CameraGroup - CameraButton, AttachCameraButton

3 Buttons that are part of the FontGroup - BoldButton, FontButton, ItalicButton

The idea is that if the screen size is less than 501 pixels I use VisualStateManager and AdaptiveTriggers to show the Group Chevron Buttons instead of the action Buttons. The action Buttons are then shown by clicking the down chevron which opens up a relevant Popup control. The Popups are hidden again using AdaptiveTriggers if the screen is increased to 501 pixels or above.

MainPage.xaml

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CameraGroupStates">
            <VisualState x:Name="Default">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="501" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="CameraButton.Visibility" Value="Visible"/>
                    <Setter Target="AttachCameraButton.Visibility" Value="Visible" />
                    <Setter Target="CameraGroupButton.Visibility" Value="Collapsed" />
                    <Setter Target="CameraGroupPopup.IsOpen" Value="false" />

                    <Setter Target="FontButton.Visibility" Value="Visible"/>
                    <Setter Target="BoldButton.Visibility" Value="Visible" />
                    <Setter Target="ItalicButton.Visibility" Value="Visible" />
                    <Setter Target="FontGroupButton.Visibility" Value="Collapsed" />
                    <Setter Target="FontGroupPopup.IsOpen" Value="false" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="Minimal">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="CameraButton.Visibility" Value="Collapsed"/>
                    <Setter Target="AttachCameraButton.Visibility" Value="Collapsed" />
                    <Setter Target="CameraGroupButton.Visibility" Value="Visible" />

                    <Setter Target="FontButton.Visibility" Value="Collapsed"/>
                    <Setter Target="BoldButton.Visibility" Value="Collapsed" />
                    <Setter Target="ItalicButton.Visibility" Value="Collapsed" />
                    <Setter Target="FontGroupButton.Visibility" Value="Visible" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <CommandBar x:Name="MainCommandBar" Height="50">            
        <AppBarButton x:Name="AllAppsButton" Icon="AllApps"></AppBarButton>            
        <AppBarButton x:Name="CameraButton" Icon="Camera"></AppBarButton>
        <AppBarButton x:Name="AttachCameraButton" Icon="AttachCamera"></AppBarButton>

        <!--This is the Group Chevron button for Camera-->
        <AppBarButton x:Name="CameraGroupButton" Visibility="Collapsed" Content="&#xE019;"
                      Click="CameraGroupButton_Click">
            <AppBarButton.ContentTemplate>
                <DataTemplate>
                    <Grid>
                        <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}"
                                   FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Center"></TextBlock>
                    </Grid>
                </DataTemplate>
            </AppBarButton.ContentTemplate>
        </AppBarButton>
        <AppBarButton x:Name="CaluclatorButton" Icon="Calculator"></AppBarButton>
        <AppBarButton x:Name="BoldButton" Icon="Bold"></AppBarButton>
        <AppBarButton x:Name="FontButton" Icon="Font"></AppBarButton>
        <AppBarButton x:Name="ItalicButton" Icon="Italic"></AppBarButton>

        <!--This is the Group Chevron button for Fonts-->
        <AppBarButton x:Name="FontGroupButton" Visibility="Collapsed" Content="&#xE019;"
                      Click="FontGroupButton_Click">
            <AppBarButton.ContentTemplate>
                <DataTemplate>
                    <Grid>
                        <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}"
                                   FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Center"></TextBlock>
                    </Grid>
                </DataTemplate>
            </AppBarButton.ContentTemplate>
        </AppBarButton>
    </CommandBar>
    <Popup x:Name="CameraGroupPopup" HorizontalAlignment="Right" HorizontalOffset="-120"
           VerticalOffset="50" IsOpen="False">
        <StackPanel Orientation="Horizontal" Width="120" Height="60" Background="LightGray"
                    HorizontalAlignment="Center">
            <AppBarButton x:Name="CameraGroupCameraButton" Icon="Camera" Width="50" Height="50"
                          Foreground="Black"/>
            <AppBarButton x:Name="CameraGroupAttachCameraButton" Icon="AttachCamera"
                          Foreground="Black" Width="50" Height="50"></AppBarButton>
        </StackPanel>
    </Popup>
    <Popup x:Name="FontGroupPopup" HorizontalAlignment="Right" HorizontalOffset="-170"
           VerticalOffset="50" IsOpen="False">
        <StackPanel Orientation="Horizontal" Width="170" Height="60" Background="LightGray"
                    HorizontalAlignment="Center">
            <AppBarButton x:Name="FontGroupBoldButton" Icon="Bold" Width="50" Height="50"
                          Foreground="Black"/>
            <AppBarButton x:Name="FontGroupFontButton" Icon="Font"
                          Foreground="Black" Width="50" Height="50"></AppBarButton>
            <AppBarButton x:Name="FontGroupItalicButton" Icon="Italic"
                          Foreground="Black" Width="50" Height="50"></AppBarButton>
        </StackPanel>
    </Popup>
</Grid>

Code behind contains 2 fields and 2 events used to hide/show relevant Popups, change chevrons to up/down arrow.

public sealed partial class MainPage : Page
{
    public bool CameraGroupIsOpen { get; set; } = false;
    public bool FontGroupIsOpen { get; set; } = false;

    public MainPage()
    {
        this.InitializeComponent();
    }

    private void CameraGroupButton_Click(object sender, RoutedEventArgs e)
    {
        if (CameraGroupIsOpen)
        {
            CameraGroupPopup.IsOpen = false;
            CameraGroupButton.Content = "\uE019";
            CameraGroupIsOpen = false;
        }
        else
        {
            CameraGroupPopup.IsOpen = true;
            CameraGroupButton.Content = "\uE018";
            CameraGroupIsOpen = true;
        }
    }

    private void FontGroupButton_Click(object sender, RoutedEventArgs e)
    {
        if (FontGroupIsOpen)
        {
            FontGroupPopup.IsOpen = false;
            FontGroupButton.Content = "\uE019";
            FontGroupIsOpen = false;
        }
        else
        {
            FontGroupPopup.IsOpen = true;
            FontGroupButton.Content = "\uE018";
            FontGroupIsOpen = true;
        }
    }
}

Quite a bit of code that could no doubt be vastly improved as a CustomControl but wanted to show the idea. Hope it helps

0
votes

It's probably not super complicated, but it can be a lot of work nevertheless. It probably derives from the the ribbon where there are multiple groups with item collapse and drop priorities (I think) and items get dropped in priority order.

You would need to have a container panel that would read priorities from the groups included in it and in MeasureOverride - it would drive collapsing items in the groups when space pressure happens.

The ToolStrip control in my toolkit has code to move items between the toolstrip and the overflow dropdown (check sample here) and could be a good first step of implementing support for ribbon-like collapsing.

IIRC the official sample for CommandBar also has logic implemented to move items between PrimaryCommands and SecondaryCommands which you could look into.