WPF Popup MenuItem stays highlighted when using arrow keys - wpf

After opening a Popup menu programatically, if the user uses up and down arrow keys to move through the menu, menu items get highlighted and they never get unhighlighted. What can I do so that after the user presses the down arrow, the previously highlighted menuitem becomes unhighlighted?
This happens with a very simple Popup menu:
<Grid>
<Button x:Name="Button1" Content="Open Menu"
Click="OnPopupMenuButton_Click"
Height="23" HorizontalAlignment="Left" Margin="69,12,0,0" VerticalAlignment="Top" Width="75" />
<Popup x:Name="MyPopupMenu" StaysOpen="False" >
<StackPanel Orientation="Vertical" Background="White" Margin="0">
<MenuItem x:Name="xAimee" Header="Aimee" Margin="0,2,0,0" />
<MenuItem x:Name="xBarbara" Header="Barbara" />
<MenuItem x:Name="xCarol" Header="Carol" />
<Separator x:Name="xSeparator1" Margin="0,2,2,2"/>
<MenuItem x:Name="xDana" Header="Dana" />
<MenuItem x:Name="xElizabeth" Header="Elizabeth" />
</StackPanel>
</Popup>
</Grid>
Here is how the Popup gets opened:
private void OnPopupMenuButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
Button button = sender as Button;
MyPopupMenu.PlacementTarget = button;
MyPopupMenu.Placement = PlacementMode.Mouse;
MyPopupMenu.IsOpen = true;
MyPopupMenu.StaysOpen = false;
}

I have been following up on archer's suggestion, but I had a few issues. First, I did not want the menu to open on a right-click, partly because I just didn't want it to open on a right-click and partly because I actually need to use PlacementMode.Top, and the context menu kept opening in the standard context-menu place (to the side and down).
So in the end, I did end up using a Context Menu, but I did a couple of special things. First, in the Window constructor, I set the button's ContextMenu to null, to prevent it from opening when right-clicked. Then when the user left-clicks, I programmatically set the ContextMenu to the one that I created in the xaml file. When the menu closes, I set the button's ContextMenu back to null. I tried manipulating the ContextMenu visibility instead, but that did not seem to work as well as setting it to null and back to an object.
Here is the final xaml, not too different from the question exception that I am handling the Closed event for the ContextMenu.
<Button x:Name="xOpenContextMenuButton" Content = "Open Menu"
Click="OnContextMenuButton_Click"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Width="80" Margin="0,0,36,8" Height="23">
<Button.ContextMenu>
<ContextMenu x:Name="xContextMenu" Closed="OnContextMenu_Closed">
<MenuItem x:Name="xAimee" Header="Aimee" />
<MenuItem x:Name="xBarbara" Header="Barbara" />
<MenuItem x:Name="xCarol" Header="Carol" />
<Separator x:Name="xSeparator1" Margin="0,2,2,2" />
<MenuItem x:Name="xDana" Header="Dana" />
<MenuItem x:Name="xElizabeth" Header="Elizabeth" />
</ContextMenu>
</Button.ContextMenu>
</Button>
Here is the code-behind, which changed a lot:
public MainWindow()
{
InitializeComponent();
xOpenContextMenuButton.ContextMenu = null;
}
private void OnContextMenuButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
xOpenContextMenuButton.ContextMenu = xContextMenu;
xContextMenu.PlacementTarget = xOpenContextMenuButton;
xContextMenu.Placement = PlacementMode.Top;
xContextMenu.IsOpen = true;
xContextMenu.StaysOpen = false;
}
private void OnContextMenu_Closed(object sender, RoutedEventArgs e)
{
xOpenContextMenuButton.ContextMenu = null;
}
Once again, thanks to archer, because I didn't realize that using Popup was not the normal way to create a popup menu in WPF. I think the root cause of the problem is, a Popup can contain anything -- a label, another button, etc. Popup isn't necessarily expecting embedded MenuItems, so it isn't smart enough to understand that it should switch between my menu items when using the arrow keys. But a ContextMenu expects to have MenuItems in it so it knows how to switch between them.

Related

Button inside a MenuItem won't open Sub-MenuItems

I have a Button inside a MenuItem.Header like this:
<Menu>
<MenuItem>
<MenuItem.Header>
<Button>Hello</Button>
</MenuItem.Header>
<MenuItem Header="SubItem1"/>
<MenuItem Header="SubItem2"/>
</MenuItem>
</Menu>
if I click the MenuItem outside the Button, the sub-menu opens. But if I click the Button the sub-menu will not open. I believe that's because the event of the clicked is not passed to the MenuItem. How do I fix it?
In short - I want the sub-menu to open when clicking the Button.
(The use is mainly for styling purposes, I have a button style and I want to use it as a MenuItem)
A Button doesn't know how to expand a MenuItem unless you tell it how to by writing some code:
<Menu>
<MenuItem>
<MenuItem.Header>
<Button Click="Button_Click">Hello</Button>
</MenuItem.Header>
<MenuItem Header="SubItem1"/>
<MenuItem Header="SubItem2"/>
</MenuItem>
</Menu>
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
MenuItem mi = btn.Parent as MenuItem;
if (mi != null)
mi.IsSubmenuOpen = !mi.IsSubmenuOpen;
}

Why is Canvas covering peer controls in Dockpanel?

Why is the Canvas covering the other Children of the Dock Panel?
I'm setting up a menu bar at the top of the client area and a status bar at the bottom of the client area of the window as per standard convention in xaml as follows:
<Window x:Class="RichCoreW.ScenEditWnd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ScenEditWnd" Height="490" Width="776" HorizontalAlignment="Right">
<DockPanel Name="mapDockP">
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Save" Name="menuISave" Click="menuISave_Click"/>
<MenuItem Header="Make Playable" Click="MakePlayable" />
</MenuItem>
<MenuItem Command="ApplicationCommands.Help" />
</Menu>
<StackPanel DockPanel.Dock="Bottom" Name="stackPanel1" Orientation="Horizontal" background="Yellow">
<Label Content="Playable:" Name="label1" />
<Label Name="labPlayable" />
</StackPanel>
</DockPanel>
</Window>
Then I add a an instance of the MapCanvEdit Class which inherits from Canvas in C# code as follows. As its the last child to be added to the Dockpanel it should take the remaining space in the Dockpanel. But it covers the menu and status bars as well covering the whole of the client area. To be precise it is the children of the Canvas that cover over the other two Stack Panels. Where the Canvas(MapCanvEdit) is empty you can see the Menu and Status bars:
public partial class ScenEditWnd : Window
{
ScenC scenC;
MapCanvEdit mapCanvE;
public ScenEditWnd(ScenC scenCI)
{
InitializeComponent();
scenC = scenCI;
mapCanvE = new MapCanvEdit(scenC);
mapDockP.Children.Add(mapCanvE);
MouseWheel += mapCanvE.Zoom;
mapCanvE.SizeChanged += delegate { mapCanvE.DrawHexs(); };
ContentRendered += delegate { mapCanvE.DrawHexs(); };
labPlayable.Content = scenC.playable.ToString();
}
}
I've left out the other methods for simplicity. Any help appreciated!
It's just the way Canvas works. It can place its children outside its own area. If you want it to restrict children to bounds set ClipToBounds="True" (see ClipToBounds on MSDN) or use another panel.

Change the content of a button at runtime in wpf

I have a button in a wpf (silverlight actually) application.
I want to change the content of this button at runtime in order to add an image to it (for example, if content was "button one", i want the content to become: stackpanel containing image1 + original button text).
Please help.
Check this:
var sp = new StackPanel();
var img = new Image() {Source = ...}
sp.Children.Add(img);
sp.Children.Add("Hello world");
btn.Content = sp; // btn - is the name of your button.
Instead of adding the image, hide and show it using BooleanToVisibilityConverter. ShowImage is a bool property that you set to true/false to show/hide the image.
<Button>
<StackPanel Orientation="Horizontal">
<Image Visibility="{Binding Path=ShowImage, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Margin="5,0,0,0" Text="button one" />
</StackPanel>
</Button>

How can I make the Silverlight ScrollViewer scroll to show a child control with focus?

I have a ScrollViewer which contains a Grid with multiple controls in it. The user can tab through the controls, but eventually they tab to a control that isn't in view - so they have to manully scroll to make the control visible again.
Is there any way to make the ScrollViewer scroll automatically so that the focussed control is always visible. Failing that, is there any way I can make this work, short of listening to a GotFocus event on every control and then scrolling the ScrollViewer to make the control visible?
At present I'm using Silverlight 2.
I tested this using Silverlight 3. I am not sure about SL2.
This is my XAML:
<ScrollViewer Height="200" Width="200" KeyUp="ScrollViewer_KeyUp">
<StackPanel>
<Button Content="1" Height="20" />
<Button Content="2" Height="20" />
<Button Content="3" Height="20" />
<Button Content="4" Height="20" />
<Button Content="5" Height="20" />
<Button Content="6" Height="20" />
<Button Content="7" Height="20" />
<Button Content="8" Height="20" />
<Button Content="9" Height="20" />
<Button Content="10" Height="20" />
<Button Content="11" Height="20" />
<Button Content="12" Height="20" />
<Button Content="13" Height="20" />
<Button Content="14" Height="20" />
<Button Content="15" Height="20" />
<Button Content="16" Height="20" />
<Button Content="17" Height="20" />
<Button Content="18" Height="20" />
<Button Content="19" Height="20" />
<Button Content="20" Height="20" />
</StackPanel>
</ScrollViewer>
And this is the code-behind:
private void ScrollViewer_KeyUp(object sender, KeyEventArgs e)
{
ScrollViewer scrollViewer = sender as ScrollViewer;
FrameworkElement focusedElement = FocusManager.GetFocusedElement() as FrameworkElement;
GeneralTransform focusedVisualTransform = focusedElement.TransformToVisual(scrollViewer);
Rect rectangle = focusedVisualTransform.TransformBounds(new Rect(new Point(focusedElement.Margin.Left, focusedElement.Margin.Top), focusedElement.RenderSize));
double newOffset = scrollViewer.VerticalOffset + (rectangle.Bottom - scrollViewer.ViewportHeight);
scrollViewer.ScrollToVerticalOffset(newOffset);
}
What I did was to click on Button #1 and tab until I get to Button #20. It worked for me. Give it a try and let me know how it works for you.
The silverlight toolkit contains a method "ScrollIntoView".
Add a reference to System.Windows.Controls.Toolkit.dll ans you should be able to use the code below.
scrollViewer.ScrollIntoView(control);
Just a slight enhancement. Still need to do this for Silverlight 4 by the way.
Instead of GotFocus for each control you can handle the GotFocus of the scrollviewer itself and implement it just once.
private void _ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
{
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null)
{
ScrollViewer scrollViewer = sender as ScrollViewer;
scrollViewer.ScrollToVerticalOffset(GetVerticalOffset(element, scrollViewer));
}
}
private double GetVerticalOffset(FrameworkElement child, ScrollViewer scrollViewer)
{
// Ensure the control is scrolled into view in the ScrollViewer.
GeneralTransform focusedVisualTransform = child.TransformToVisual(scrollViewer);
Point topLeft = focusedVisualTransform.Transform(new Point(child.Margin.Left, child.Margin.Top));
Rect rectangle = new Rect(topLeft, child.RenderSize);
double newOffset = scrollViewer.VerticalOffset + (rectangle.Bottom - scrollViewer.ViewportHeight);
return newOffset < 0 ? 0 : newOffset; // no use returning negative offset
}
I got this to work, with help of Kiril's answer above. The general context of this is that I have user-defineable forms in my application, and this code is used for rendering the controls on a form.
My general strategy was to add my controls to a Grid, then find all the children of the ScrollViewer using VisualTreeHelper, and add a GotFocus event handler to each control.
When the control gets focus, again using VisualTreeHelper, I search up the visual tree to find the control whose parent is the Grid that is being scrolled by the ScrollViewer. Then I scroll the ScrollViewer to make the control visible.
Here's the code (gridRender is the Grid that the controls are added to):
private void AfterFormRendered()
{
var controls = VisualTreeHelperUtil.FindChildren<Control>(gridRender);
foreach (var ctrl in controls)
{
ctrl.GotFocus += CtrlGotFocus;
}
}
private void CtrlGotFocus(object sender, RoutedEventArgs e)
{
var ctrl = sender as Control;
var gridChildControl = VisualTreeHelperUtil.FindParentWithParent(ctrl, gridRender) as FrameworkElement;
if (gridChildControl != null)
{
// Ensure the control is scrolled into view in the ScrollViewer.
GeneralTransform focusedVisualTransform = gridChildControl.TransformToVisual(scrollViewer);
Point topLeft = focusedVisualTransform.Transform(new Point(gridChildControl.Margin.Left, gridChildControl.Margin.Top));
Rect rectangle = new Rect(topLeft, gridChildControl.RenderSize);
double newOffset = scrollViewer.VerticalOffset + (rectangle.Bottom - scrollViewer.ViewportHeight);
scrollViewer.ScrollToVerticalOffset(newOffset);
}
}
Note: the VisualTreeHelperUtil class is my own class that adds some useful searching functionality to the VisualTreeHelper class.

How do get menu to open to the left in WPF?

I have a menu (with menuitems) in WPF. Unfortunately when I click on the menu heading it opens the menu to the right. The problem is that there is stuff on the right that I don't want it to overlap. How do I tell WPF to open the menu to the left? Do I need to do a control template? (control templates seem so heavy handed for such basic style changes).
Thanks!
KSG
While you can create a ControlTemplate to do this like they do here, I agree that it is a cumbersome method just to modify one value on a part of the MenuItems. Instead, I think that this is a great place to use an AttachedProperty. We can create something just like the ContextMenuService, but for Popups (In fact, I'm somewhat surprised that it isn't built in).
To change where the popup is opening, we're going to want to set the Popup's PlacementMode. We can use the propa shortcut to generate our AttachedProperty(or properties if you want to implement the rest). We need to add a callback to our PropertyMetadata, but if the AttachedProperty is set inline on the control in XAML then the callback will fire before the whole control is fully constructed. To ensure the MenuItem's template is applied, and the Popup exists before we try and set it's value, we can just attach to the Loaded event if it isn't already loaded.
Once it is loaded, we want to retrieve the Popup from the template, and if we look at the MenuItem class we can see that it has a TemplatePartAttribute defining the Popup's name as "PART_Popup". Once we have that, we can set the PlacementMode on the MenuItem's Popup.
public static PlacementMode GetMenuPlacement(DependencyObject obj)
{
return (PlacementMode)obj.GetValue(MenuPlacementProperty);
}
public static void SetMenuPlacement(DependencyObject obj, PlacementMode value)
{
obj.SetValue(MenuPlacementProperty, value);
}
// Using a DependencyProperty as the backing store for MenuPlacement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MenuPlacementProperty =
DependencyProperty.RegisterAttached("MenuPlacement",
typeof(PlacementMode),
typeof(Window1),
new FrameworkPropertyMetadata(PlacementMode.Bottom, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnMenuPlacementChanged)));
private static void OnMenuPlacementChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var menuItem = o as MenuItem;
if (menuItem != null)
{
if (menuItem.IsLoaded)
{
SetPopupPlacement(menuItem, (PlacementMode)e.NewValue);
}
else
{
menuItem.Loaded += new RoutedEventHandler((m, v) => SetPopupPlacement(menuItem, (PlacementMode)e.NewValue));
}
}
}
private static void SetPopupPlacement(MenuItem menuItem, PlacementMode placementMode)
{
Popup popup = menuItem.Template.FindName("PART_Popup", menuItem) as Popup;
if (popup != null)
{
popup.Placement = placementMode;
}
}
Now that we have our AttachedProperty, it's easy to change the Popup placement in the UI.
<Menu>
<MenuItem Header="Item 1"
local:Window1.MenuPlacement="Right">
<MenuItem Header="SubItem 1" />
<MenuItem Header="SubItem 2" />
<MenuItem Header="SubItem 3" />
<MenuItem Header="SubItem 4" />
</MenuItem>
<MenuItem Header="Item 2"
local:Window1.MenuPlacement="Left">
<MenuItem Header="SubItem 5" />
<MenuItem Header="SubItem 6" />
<MenuItem Header="SubItem 7" />
<MenuItem Header="SubItem 8" />
</MenuItem>
<MenuItem Header="Item 3"
local:Window1.MenuPlacement="Mouse">
<MenuItem Header="SubItem 9" />
<MenuItem Header="SubItem 10" />
<MenuItem Header="SubItem 11" />
<MenuItem Header="SubItem 12" />
</MenuItem>
</Menu>

Resources