MenuItems with fixed length - wpf

I've got a Menu which receives a dynamic ItemsSource. The number of items and their length varies from time to time.
<Menu ItemsSource="{loc:CustomBinding CurrentMenuItems}" />
The menu's orientation is Horizontal, at times this causes the menu to overflow.
The Menu Items Template :
<ControlTemplate TargetType="{x:Type MenuItem}">
<TextBlock Text="{loc:CustomBinding Title}"
MaxWidth="200"
TextTrimming="CharacterEllipsis"/>
</ControlTemplate>
When there are more than 4 items, the menu overflows outside the left side of the screen
(sorry can't upload pictures from the work place).
Are there any built in overflow options to WPF menu?
Is there an option to control overflow of menu items from the menu itself? Something along the lines of ItemsMaxLength which I could of bound to a calculated field?

I'm not clear what you want to happen when the rightmost menu items cannot fit on the screen. Is it?:
wrap them to the next line
crop them fully
crop them partially
put them in an overflow dropdown?
show an ellipse or other "more" indicator?
The default ItemsPanel template for a Menu uses a WrapPanel...that should wrap them to the next line (unless you have set a height for the menu or your menu is in a container that is isn't allowing it to have its desired height).
We can change the ItemsPanel used to change how items are layed out.
In the example XAML below I show 4 ways to display a menu. You can use this within Kaxaml to see the different behaviours...resize the Window to a small width to see what happens when there isn't enough horizontal space.
the original menu style...wraps menu items that don't fit
a menu that fully crops the menu item that doesn't fit
a menu that partially crops the menu item that doesn't fit
a menu that uses a Toolbar...any items that don't fit go into an overflow panel (note this example isn't perfect...it doesn't exactly adhere to the standard menu
selection behaviour with the mouse/keyboard because its using separate menus for each item...and the style is a little off...and it shows a toolbar gripper....more work would be needed).
If 4 is closest to what you want, then you somehow need to combine the behaviour or a ToolBar with it's overflow panel...with the behaviour of a menu and it's tracking/navigation between menu items.
You might be able to achieve that by creatively re-templating the menu, or the toolbar, or creating your own custom control.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<StackPanel Orientation="Vertical">
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_Exit"/>
</MenuItem>
<MenuItem Header="_Edit"/>
<MenuItem Header="_Window"/>
</Menu>
<Menu>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<ToolBarPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem Header="_File">
<MenuItem Header="_Exit"/>
</MenuItem>
<MenuItem Header="_Edit"/>
<MenuItem Header="_Window"/>
</Menu>
<Menu>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem Header="_File">
<MenuItem Header="_Exit"/>
</MenuItem>
<MenuItem Header="_Edit"/>
<MenuItem Header="_Window"/>
</Menu>
<ToolBar>
<ToolBar.Resources>
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}">
<Style.Triggers>
<Trigger Property="Role" Value="TopLevelHeader">
<Setter Property="Template"
Value="{StaticResource {x:Static MenuItem.SubmenuHeaderTemplateKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ToolBar.Resources>
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_Exit"/>
</MenuItem>
</Menu>
<Menu>
<MenuItem Header="_Edit"/>
</Menu>
<Menu>
<MenuItem Header="_Window"/>
</Menu>
</ToolBar>
</StackPanel>
</Window>

Related

Xceed splitbutton with menu items and sub menuitems

I`m trying to have a dropdown menu under a split button. Some of these menuItems should have sub-menu items. If you want an example, click on the bookmark button in Firefox (top right).
I can't use Menu, because that is always oriented horizontally. So I went with a stackpanel:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="350" Width="525">
<Grid>
<xctk:SplitButton Content="SplitButton" BorderThickness="1" BorderBrush="Black" Margin="0,0,408,290">
<xctk:SplitButton.DropDownContent>
<StackPanel Width="161" HorizontalAlignment="Left">
<MenuItem Header="MenuItem1" HorizontalAlignment="Left" Width="517">
<MenuItem.Items>
<MenuItem Header="submenuItem1"/>
<MenuItem Header="submenuItem2"/>
</MenuItem.Items>
</MenuItem>
<MenuItem Header="MenuItem2"/>
<MenuItem Header="MenuItem3"/>
</StackPanel>
</xctk:SplitButton.DropDownContent>
</xctk:SplitButton>
</Grid>
Problem here is that the sub menu items don't show up. They don't even have the little arrows next to them. You can do this without the SplitButton, just leaving the stackpanel and everything in it, you'll have the same problem. I've tried putting the parent Menu item in its own tag, but I want the sub-menu items to appear to the right of their parent (just like the firefox example: Assuming your firefox window is not maximized, and you've allowed enough screen-space for the item to appear).
the way you're using the MenuItem control is wrong, it is meant to be hosted inside the Menu control or another MenuItem.
As for the solution to your problem, there are two. the first one is to write a custom control that reuses the ContextMenu to host the menu items, you will write something like this:
<m:SplitButton Content="Split Button" Placement="Bottom">
<MenuItem Header="MenuItem 1"/>
<MenuItem Header="MenuItem 2">
<MenuItem Header="MenuItem 1"/>
<MenuItem Header="MenuItem 2"/>
</MenuItem>
</m:SplitButton
http://www.codeproject.com/Articles/20612/A-WPF-SplitButton
the second approach is to host the Menu control inside the DropDownContent and re-style everything, there will be a lot of xaml markup.
the Menu control will be easy to re-style, the only thing you need to do is make sure that menu items are displayed vertically instead of horizontally, using the following markup:
<Style TargetType="Menu">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
the hardest part is styling the MenuItems, they are styled based on their Role property. It can have four possible values:
TopLevelHeader : direct child of Menu with sub-menu items.
TopLevelItem : direct child of Menu without sub-menu items.
SubmenuHeader : direct child of MenuItem with sub-menu items.
SubmenuItem : direct child of MenuItem without sub-menu items.
Regards

Dynamically binding to ViewModel commands on Window's MenuItem

Working on a WPF application using the MVVM structure.
My Window displays a menu and the current ViewModel. On one of the Menu's MenuItems, I want to list some Commands found in the current ViewModel. The commands listed in the Menu will change depending on the ViewModel.
I got this to work just fine, but the style is messed up - the Command MenuItems are inside another menu box or something. I'll attach a screenshot.
I wrapped the ViewModel's ICommand objects (RelayCommands, in this instance) in CommandViewModel, which expose the Command and the Display string I want on the menu. These CommandViewModels are in a list: CurrentWorkspace.AdditionalOptionsCommands.
Here is the XAML for the Menu. Like I said, it works, it shows the right items and the commands are executed. The display is just incorrect - can anybody tell me why and how to fix it? See the screenshot.
<Menu>
<MenuItem Header="_Additional Options..." ItemsSource="{Binding Path=CurrentWorkspace.AdditionalOptionsCommands}">
<MenuItem.ItemTemplate>
<DataTemplate DataType="{x:Type vm:CommandViewModel}">
<MenuItem Header="{Binding Path=DisplayText}" Command="{Binding Path=Command}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<MenuItem Header="_Testing">
<MenuItem Header="This looks right" />
<MenuItem Header="This looks right" />
</MenuItem>
</Menu>
Current Appearance:
Desired Appearance:
This is because when you specify menu items via ItemsSource each item gets automatically wrapped into a MenuItem object. This way the content defined in the DataTemplate (MenuItem element) gets wrapped into one more MenuItem.
What you need to do instead of defining a DataTemplate is to define a style for the MenuItem where you setup bindings to the view model's properties and use this style as ItemContainerStyle on the parent MenuItem:
<Window.Resources>
<Style x:Key="CommandMenuItemStyle"
TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding Path=DisplayText}"/>
<Setter Property="Command"
Value="{Binding Path=Command}"/>
</Style>
</Window.Resources>
...
<Menu>
<MenuItem Header="_Additional Options..."
ItemsSource="{Binding Path=CurrentWorkspace.AdditionalOptionsCommands}"
ItemContainerStyle="{StaticResource CommandMenuItemStyle}"/>
<MenuItem Header="_Testing">
<MenuItem Header="This looks right" />
<MenuItem Header="This looks right" />
</MenuItem>
</Menu>
See http://drwpf.com/blog/2008/03/25/itemscontrol-i-is-for-item-container/ for an in-depth explanation on how item containers work with ItemsControl controls.

WPF Menu: Wrap items

I'm working on a WPF control that gets placed inside a TabControl in another window, and I have a menu that stretches across the top of my custom control with the letters of the alphabet as such (for indexing purposes):
<UserControl x:Class="thispageclass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<Grid>
<StackPanel Orientation="Vertical">
<Menu Name="mnu">
<MenuItem Header="A" />
<MenuItem Header="B" />
<MenuItem Header="C" />
...
<MenuItem Header="Y" />
<MenuItem Header="Z" />
</Menu>
[other elements]
</StackPanel>
</Grid>
</UserControl>
If all the letters are present, the menu gets wider than the window so you can't see the last few items without making the window wider. It seems like it should be trivial to make the menu wrap around if the window is too small but I can't seem to figure out how.
Just override the ItemsPanel of your Menu to a WrapPanel
Sample
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
I created my own minimal sample which worked, leading me to figuring out it was my custom styles that were messing up my menu.

Property Binding to Static Properties in WPF: Width binding doesn't work

Update: I've corrected the post, so the question is closed.
Expected result:
Menu width must be equal to the main window width.
Here is full code:
<Menu Name="menu1" DockPanel.Dock="Top">
<Menu.Width>
<Binding Path="MainWindow.Width"
Source="{x:Static Application.Current}"
UpdateSourceTrigger="PropertyChanged"/>
</Menu.Width>
<MenuItem Header="File">
<MenuItem Header="Open" />
<MenuItem Header="Close" />
</MenuItem>
</Menu>
The result:
Menu width is equal to content width
Doh,
Dmitry, Application.Current doesn't have a property Width...
Use Grid instead of DockPanel:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Menu Name="menu1" VerticalAlignment="Top">
<MenuItem Header="File">
<MenuItem Header="Open"/>
<MenuItem Header="Close"/>
</MenuItem>
</Menu>
</Grid>
</Page>
Don't overcomplicate things. And I really encourage you to read wonderful Dr. WPF articles: ItemsControl from A to Z
Cheers, Anvaka

WPF ToolBar Separator shrinks to nothing when inside a StackPanel

Given the very simple wpf app
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="800">
<Grid>
<ToolBar Height="50" >
<MenuItem Header="Test1" />
<MenuItem Header="Test2" />
<StackPanel Orientation="Horizontal">
<Separator />
<MenuItem Header="Test3" />
<MenuItem Header="Test4" />
<MenuItem Header="Test5" />
</StackPanel>
</ToolBar>
</Grid>
</Window>
The Separator element shrinks to nothing. If I put the Separator just before the StackPanel begins, it will show up. Why does this happen? Is there a style setting that can be applied somewhere to avoid this?
The StackPanel is changing the orientation of the Separator somehow. Note that if you explicitly tell the Separator to be 20 units wide, the Separator will be a horizontal line instead of a vertical line. That's part of what's going on.
If you apply a LayoutTransform to the Separator, it undoes whatever the StackPanel is doing.
<Separator>
<Separator.LayoutTransform>
<RotateTransform
Angle="90" />
</Separator.LayoutTransform>
</Separator>
I don't understand the need for a StackPanel, though.
Separators default to Horizontal orientation.
Separators placed directly inside a ToolBar have their styles changed, because Toolbar overrides the default styles of its items. Separators placed elsewhere get the default style of a separator. So you will need to style the separator yourself if you vwant to keep it inside the StackPanel.
This CodeProject discussion includes sample code for accomplishing this.
Reference: WPF Unleashed by Adam Nathan, page 117.
ToolBars are funny about what you put inside. They get funny when all the elements aren't direct children of the ToolBar. The grouping elements are ToolBarTray (group of toolbars), ToolBar, and ToolBarPanel (logical, for collapsing overflow). This is what WPF wants to see:
<Grid>
<ToolBarTray>
<ToolBar Height="Auto">
<ToolBarPanel Orientation="Horizontal" ToolBar.OverflowMode="AsNeeded"/>
<MenuItem Header="Test1" />
<Separator/>
<MenuItem Header="Test2" />
</ToolBar>
<ToolBar Height="Auto">
<ToolBarPanel ToolBar.OverflowMode="Never"/>
<MenuItem Header="Test3" />
<MenuItem Header="Test4" />
<Separator/>
<MenuItem Header="Test5" />
<ToolBarPanel ToolBar.OverflowMode="AsNeeded"/>
<MenuItem Header="Test6" />
<MenuItem Header="Test7" />
</ToolBar>
</ToolBarTray>
</Grid>

Resources