Skip to content

Commit c1a2f46

Browse files
authored
[API Spec] SplitMenuFlyoutItem control (#10862)
* Added specs for SplitMenuFlyoutItem control * Updating the SplitMenuFlyoutItem spec * Maing some content changes to the API Spec * Fixed spelling erros and other minor issues * Updating samples to show actual use cases * Updating the control behaviour when Items is empty * Updating images in the spec language example table * Updated some samples
1 parent 1066282 commit c1a2f46

14 files changed

+388
-0
lines changed
Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
SplitMenuFlyoutItem
2+
===
3+
4+
# Background
5+
6+
The `SplitMenuFlyoutItem` control is a new addition to the WinUI library, designed to provide
7+
a split button experience within a menu flyout. This control derives from `MenuFlyoutItem` and
8+
introduces a dual-button interface consisting of a primary button and a flyout button.
9+
10+
![General look of SplitMenuFlyoutItem - Dark](./splitmenuflyoutitem-normal-dark.png)
11+
12+
The primary button behaves like a standard `MenuFlyoutItem`, raising a click event and
13+
executing a command when clicked. The flyout button operates similarly to `MenuFlyoutSubItem`
14+
and opens a submenu when hovered over. The flyout button does not support click operations.
15+
16+
This control addresses scenarios where you need to provide both a default action and
17+
additional options in a submenu, offering a more efficient use of menu space while
18+
maintaining discoverability of related actions.
19+
20+
![Flyout open state - Dark Mode](./splitmenuflyoutitem-flyoutopen-dark.png)
21+
22+
![Flyout open state - Light Mode](./splitmenuflyoutitem-flyoutopen-normal.png)
23+
24+
# Conceptual pages (How To)
25+
26+
## How to use SplitMenuFlyoutItem
27+
28+
The `SplitMenuFlyoutItem` control enables you to create menu items that combine both immediate
29+
actions and additional options. The control consists of two distinct interactive areas:
30+
a primary button for the main action and a flyout button for accessing additional options.
31+
32+
### Standard Usage (Default behaviour)
33+
34+
You can use `SplitMenuFlyoutItem` in any `MenuFlyout` to provide both a default action and
35+
additional choices:
36+
37+
```xaml
38+
<Button Content="File">
39+
<Button.Flyout>
40+
<MenuFlyout>
41+
<SplitMenuFlyoutItem Text="Save" Command="{Binding SaveCommand}">
42+
<SplitMenuFlyoutItem.Items>
43+
<MenuFlyoutItem Text="Save As..." Command="{Binding SaveAsCommand}" />
44+
<MenuFlyoutItem Text="Export..." Command="{Binding ExportCommand}" />
45+
</SplitMenuFlyoutItem.Items>
46+
</SplitMenuFlyoutItem>
47+
</MenuFlyout>
48+
</Button.Flyout>
49+
</Button>
50+
```
51+
52+
In this example, clicking the primary button executes the `SaveCommand`, while hovering over
53+
the flyout button reveals additional save-related options.
54+
55+
![Save Scenario](./save-scenario.png)
56+
57+
### Advanced Usage
58+
59+
#### Styling the Submenu
60+
61+
You can customize the appearance of the submenu using the styling properties:
62+
63+
```xaml
64+
<SplitMenuFlyoutItem Text="Rewrite with CoPilot">
65+
<SplitMenuFlyoutItem.SubMenuPresenterStyle>
66+
<Style BasedOn="{StaticResource DefaultMenuFlyoutPresenterStyle}" TargetType="MenuFlyoutPresenter">
67+
<Setter Property="Template">
68+
<Setter.Value>
69+
<ControlTemplate TargetType="MenuFlyoutPresenter">
70+
<Border>
71+
<ScrollViewer x:Name="MenuFlyoutPresenterScrollViewer">
72+
<GridView ItemsSource="{TemplateBinding ItemsSource}">
73+
<GridView.ItemsPanel>
74+
<ItemsPanelTemplate>
75+
<ItemsWrapGrid MaximumRowsOrColumns="3" Orientation="Horizontal" />
76+
</ItemsPanelTemplate>
77+
</GridView.ItemsPanel>
78+
</GridView>
79+
</ScrollViewer>
80+
</Border>
81+
</ControlTemplate>
82+
</Setter.Value>
83+
</Setter>
84+
</Style>
85+
</SplitMenuFlyoutItem.SubMenuPresenterStyle>
86+
87+
<SplitMenuFlyoutItem.SubMenuItemStyle>
88+
<Style BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}"
89+
TargetType="MenuFlyoutItem">
90+
<Setter Property="Template">
91+
<Setter.Value>
92+
<ControlTemplate TargetType="MenuFlyoutItem">
93+
<StackPanel Orientation="Vertical" Width="70" Margin="5">
94+
<FontIcon Glyph="{TemplateBinding Tag}" HorizontalAlignment="Center"/>
95+
<TextBlock Text="{TemplateBinding Text}" HorizontalAlignment="Center" />
96+
</StackPanel>
97+
</ControlTemplate>
98+
</Setter.Value>
99+
</Setter>
100+
</Style>
101+
</SplitMenuFlyoutItem.SubMenuItemStyle>
102+
103+
<SplitMenuFlyoutItem.Items>
104+
<MenuFlyoutItem Text="Formal" Tag="&#xE8A5;" />
105+
<MenuFlyoutItem Text="Friendly" Tag="&#xE899;" />
106+
<MenuFlyoutItem Text="Compact" Tag="&#xE8F3;" />
107+
<MenuFlyoutItem Text="Elaborate" Tag="&#xE8F2;" />
108+
</SplitMenuFlyoutItem.Items>
109+
</SplitMenuFlyoutItem>
110+
```
111+
112+
Here is one example of a customized submenu (this is a reference from the SplitButton control).
113+
114+
![Customizable Sub Menu Scenario](./customizable-submenu-sample.png)
115+
116+
#### Nesting of SplitMenuFlyoutItem
117+
118+
We can also support nesting of menu items in this control:
119+
120+
```xaml
121+
<SplitMenuFlyoutItem Text="Edit with Photos">
122+
<MenuFlyoutItem Text="Rotate Left" />
123+
<MenuFlyoutItem Text="Rotate Right" />
124+
<MenuFlyoutItem Text="Crop" />
125+
<SplitMenuFlyoutItem Text="Resize" >
126+
<MenuFlyoutItem Text="Small" />
127+
<MenuFlyoutItem Text="Medium" />
128+
<MenuFlyoutItem Text="Large" />
129+
<MenuFlyoutItem Text="Phone" />
130+
</SplitMenuFlyoutItem>
131+
</SplitMenuFlyoutItem>
132+
```
133+
134+
![Nested Submenu Scenario](./nested-submenu.png)
135+
136+
### Using SplitMenuFlyoutItem in XAML, C#, and C++
137+
138+
As any control can be instantiated using either XANL, C# or C++, here is a table showing how to achieve the same UI with either of the options.
139+
140+
<table>
141+
<tr>
142+
<th>Language</th>
143+
<th>Code Sample</th>
144+
<th>Rendered Output</th>
145+
</tr>
146+
<tr>
147+
<td><b>XAML</b></td>
148+
<td>
149+
<pre lang="xml">&lt;MenuFlyout&gt;
150+
&lt;SplitMenuFlyoutItem Text="Open With Photos"&gt;
151+
&lt;SplitMenuFlyoutItem.Items&gt;
152+
&lt;MenuFlyoutItem Text="Paint" /&gt;
153+
&lt;MenuFlyoutItem Text="Paint 3D" /&gt;
154+
&lt;MenuFlyoutItem Text="Snipping Tool" /&gt;
155+
&lt;/SplitMenuFlyoutItem.Items&gt;
156+
&lt;/SplitMenuFlyoutItem&gt;
157+
&lt;/MenuFlyout&gt;</pre>
158+
</td>
159+
<td><img src="./openwith-xaml-example.png" alt="Rendered control" width="250"/></td>
160+
</tr>
161+
<tr>
162+
<td><b>C#</b></td>
163+
<td>
164+
<pre lang="csharp">var splitItem = new SplitMenuFlyoutItem {
165+
Text = "Open With Photos"
166+
};
167+
splitItem.Items.Add(
168+
new MenuFlyoutItem { Text = "Paint" });
169+
splitItem.Items.Add(
170+
new MenuFlyoutItem { Text = "Paint 3D" });
171+
splitItem.Items.Add(
172+
new MenuFlyoutItem { Text = "Snipping Tool" });
173+
174+
var menuFlyout = new MenuFlyout();
175+
menuFlyout.Items.Add(splitItem);</pre>
176+
</td>
177+
<td><img src="./openwith-csharp-example.png" alt="Rendered control" width="250"/></td>
178+
</tr>
179+
<tr>
180+
<td><b>C++/WinRT</b></td>
181+
<td>
182+
<pre lang="cpp">
183+
#include &lt;winrt/Microsoft.UI.Xaml.Controls.h&gt;
184+
using namespace Microsoft::UI::Xaml::Controls;<br>
185+
186+
auto splitItem = SplitMenuFlyoutItem();
187+
splitItem.Text(L"Open With Photos");
188+
189+
auto notepadItem = MenuFlyoutItem();
190+
notepadItem.Text(L"Paint");
191+
splitItem.Items().Append(notepadItem);
192+
193+
auto vscodeItem = MenuFlyoutItem();
194+
vscodeItem.Text(L"Paint 3D");
195+
splitItem.Items().Append(vscodeItem);
196+
197+
auto vsItem = MenuFlyoutItem();
198+
vsItem.Text(L"Snipping Tool");
199+
splitItem.Items().Append(vsItem);
200+
201+
auto menuFlyout = MenuFlyout();
202+
menuFlyout.Items().Append(splitItem);</pre>
203+
</td>
204+
<td><img src="./openwith-cpp-example.png" alt="Rendered control" width="250"/></td>
205+
</tr>
206+
</table>
207+
208+
This table demonstrates that regardless of whether you use XAML, C#, or C++/WinRT, the SplitMenuFlyoutItem control is rendered identically in the UI.
209+
210+
# API Pages
211+
212+
## SplitMenuFlyoutItem class
213+
214+
A menu flyout item that provides both a primary action and additional options through a
215+
split button interface.
216+
217+
The `SplitMenuFlyoutItem` derives from `MenuFlyoutItem` and extends it with a dual-button design:
218+
219+
```c#
220+
public class SplitMenuFlyoutItem : MenuFlyoutItem
221+
{
222+
public IList<MenuFlyoutItemBase> Items { get; set; }
223+
public Style SubMenuPresenterStyle { get; set; }
224+
public Style SubMenuItemStyle { get; set; }
225+
}
226+
```
227+
228+
### Example Usage
229+
230+
```xaml
231+
<MenuFlyout>
232+
<SplitMenuFlyoutItem Text="New Document" Command="{Binding NewDocumentCommand}">
233+
<SplitMenuFlyoutItem.Items>
234+
<MenuFlyoutItem Text="Blank Document" Command="{Binding NewBlankCommand}" />
235+
<MenuFlyoutItem Text="From Template..." Command="{Binding NewFromTemplateCommand}" />
236+
</SplitMenuFlyoutItem.Items>
237+
</SplitMenuFlyoutItem>
238+
</MenuFlyout>
239+
```
240+
241+
## SplitMenuFlyoutItem.Items property
242+
243+
Gets or sets the collection of menu items to display in the submenu.
244+
245+
The `Items` property contains the collection of `MenuFlyoutItemBase` objects that appear
246+
in the submenu when the flyout button is hovered over. This collection can contain any
247+
type of menu flyout item, including separators and sub-items.
248+
249+
```xaml
250+
<SplitMenuFlyoutItem Text="Compress to zip">
251+
<SplitMenuFlyoutItem.Items>
252+
<MenuFlyoutItem Text="7z file" />
253+
<MenuFlyoutItem Text="TAR file" />
254+
<MenuFlyoutSeparator />
255+
<MenuFlyoutItem Text="Additional Options" />
256+
</SplitMenuFlyoutItem.Items>
257+
</SplitMenuFlyoutItem>
258+
```
259+
260+
> [!NOTE]
261+
> When the Items is empty, the flyout\submenu is disabled.
262+
263+
## SplitMenuFlyoutItem.SubMenuPresenterStyle property
264+
265+
Gets or sets the style applied to the submenu's flyout presenter.
266+
267+
This property allows you to customize the appearance of the submenu container.
268+
itself, such as background colour, border, and padding.
269+
270+
```xaml
271+
<SplitMenuFlyoutItem.SubMenuPresenterStyle>
272+
<Style TargetType="FlyoutPresenter">
273+
<Setter Property="Background" Value="LightBlue" />
274+
<Setter Property="BorderBrush" Value="DarkBlue" />
275+
<Setter Property="BorderThickness" Value="2" />
276+
</Style>
277+
</SplitMenuFlyoutItem.SubMenuPresenterStyle>
278+
```
279+
280+
## SplitMenuFlyoutItem.SubMenuItemStyle property
281+
282+
Gets or sets the style applied to individual items within the submenu.
283+
284+
This property allows you to customize the appearance of all menu items within the submenu.
285+
286+
```xaml
287+
<SplitMenuFlyoutItem.SubMenuItemStyle>
288+
<Style TargetType="MenuFlyoutItem">
289+
<Setter Property="FontSize" Value="14" />
290+
<Setter Property="Foreground" Value="DarkRed" />
291+
</Style>
292+
</SplitMenuFlyoutItem.SubMenuItemStyle>
293+
```
294+
295+
## SplitMenuFlyoutItemAutomationPeer class
296+
297+
Provides automation peer support for the `SplitMenuFlyoutItem` control, enabling assistive technologies like screen readers and Narrator to interact with the control.
298+
299+
The `SplitMenuFlyoutItemAutomationPeer` class derives from `FrameworkElementAutomationPeer` and implements `IInvokeProvider` and `IExpandCollapseProvider` interfaces to provide comprehensive automation support.
300+
301+
- **IInvokeProvider**: Enables assistive technologies to invoke the primary action of the `SplitMenuFlyoutItem`
302+
- **IExpandCollapseProvider**: Provides information about the expand/collapse state of the submenu and allows assistive technologies to expand or collapse it
303+
304+
This class is used internally by the automation framework and is not instantiable from XAML. It comes into play when assistive technologies query the control for its state, properties, and available actions.
305+
306+
# API Details
307+
308+
```c# (but really MIDL3)
309+
namespace Microsoft.UI.Xaml.Controls
310+
{
311+
runtimeclass SplitMenuFlyoutItem : MenuFlyoutItem
312+
{
313+
IVector<MenuFlyoutItemBase> Items { get; };
314+
Style SubMenuPresenterStyle;
315+
Style SubMenuItemStyle;
316+
317+
/// Gets or sets the collection of menu items to display in the submenu.
318+
static DependencyProperty ItemsProperty { get; };
319+
/// Gets or sets the style applied to the submenu's flyout presenter.
320+
static DependencyProperty SubMenuPresenterStyleProperty { get; };
321+
/// Gets or sets the style applied to individual items within the submenu.
322+
static DependencyProperty SubMenuItemStyleProperty { get; };
323+
}
324+
}
325+
326+
namespace Microsoft.UI.Xaml.Automation.Peers
327+
{
328+
runtimeclass SplitMenuFlyoutItemAutomationPeer : Microsoft.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer,
329+
Microsoft.UI.Xaml.Automation.Provider.IInvokeProvider,
330+
Microsoft.UI.Xaml.Automation.Provider.IExpandCollapseProvider
331+
{
332+
SplitMenuFlyoutItemAutomationPeer(Microsoft.UI.Xaml.Controls.SplitMenuFlyoutItem owner);
333+
}
334+
}
335+
```
336+
337+
## Appendix
338+
339+
### Keyboard Behaviour
340+
341+
The `SplitMenuFlyoutItem` provides comprehensive keyboard navigation support to ensure
342+
accessibility and ease of use:
343+
344+
#### Navigation Flow
345+
- **Tab Navigation**: Tab navigation is not supported within the MenuFlyout by default. The same behaviour will continue.
346+
- **Arrow Key Navigation**:
347+
- Within a menu, the Up/Down arrow keys can navigate between menu items,
348+
including the `SplitMenuFlyoutItem`.
349+
- When focus is coming from a menu item above the current menu item i.e. focus is coming because of Down Arrow Key,
350+
the focus will move to primary button. Similarly, when the focus is coming from below,
351+
i.e. focus is coming to this control because of Up Arrow Key, focus first moves to secondary button.
352+
353+
#### Primary Button Interaction
354+
*When focus is on primary button*
355+
- **Right or Down Arrow Key**: Moves the focus to secondary button.
356+
- **Enter or Space Key**: When the focus is on the primary button, pressing `Enter` executes the
357+
primary action.
358+
- **Up Arrow Key**: Moves the focus to previous menu item.
359+
360+
#### Secondary Button Interaction
361+
- **Enter Key**: When the focus is on the flyout button, pressing `Enter` opens the submenu
362+
and shifts the focus to the first item in the submenu.
363+
- **Right Arrow Key**: Pressing the right arrow key when focused on the flyout button also
364+
opens the submenu and moves focus to the first submenu item.
365+
- **Up/Left Arrow Key**: Moves the focus to primary button.
366+
- **Down Arrow Key**: Moves the focus to next menu item.
367+
368+
#### Submenu Navigation
369+
- **Up/Down Arrow Keys**: Navigate between items within the opened submenu.
370+
- **Escape/Left Key**: Closes the submenu and returns focus to the flyout/secondary button.
371+
372+
This keyboard behaviour ensures that users can efficiently navigate and interact with the
373+
`SplitMenuFlyoutItem` using only the keyboard, maintaining accessibility standards and providing
374+
a consistent user experience across the application.
375+
376+
### Automation Behaviour
377+
378+
Accessibility tools like screen readers (Narrator, NVDA) use the UI Automation Framework. These UI automation clients, communicate with applications through the automation peer classes. In this case the defined behaviour is as follows:
379+
1. **Focus on primary button**: When the UI Automation clients focus moves to the primary button, the screen reader announces information similar other menu items in the menu flyout: ***<menu-item-name> menu item, 3 of 5, collapsed***
380+
2. **Focus on secondary button**: When the UI Automation clients focus moves to the secondary button, the screen reader announces the following information: ***More options for <menu-item-name> menu item, button***
381+
382+
#### Differences in behaviour when compared to MenuFlyoutItem and MenuFlyoutSubItem
383+
1. In case of SplitMenuFlyoutItem, the focus of the automation clients will move to individual parts instead of the whole control.
384+
2. Depending on the focus, the bounding box for the control will be around the primary or secondary button and not over the whole control.
385+
386+
### Other Behaviour
387+
388+
1. When `Items` property is an empty collection, the secondary button will be disabled.
10 KB
Loading
31.5 KB
Loading
10.2 KB
Loading
6.48 KB
Loading
10.1 KB
Loading
9.74 KB
Loading
12.3 KB
Loading
7.94 KB
Loading
97.4 KB
Loading

0 commit comments

Comments
 (0)