Strange behavior of ContextMenu: placement "mirrored" - c#

I've got faced a really strange behavior (at least for me) while using ContextMenu. Here's simplified xaml (MainWindow.xaml):
<Window x:Class="ContextMenuTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500"
Height="300">
<Button Content="Do this" Height="25" Width="80" ContextMenuService.Placement="Right">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Do this" />
<MenuItem Header="Do that" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</Window>
With this xaml, the expected result by right-clicking the button is a ContextMenu placed at the right of the button. But the result is:
http://i.stack.imgur.com/pSd0Q.png
So, the ContextMenu is strangely placed at the left of the button. I also tried to set the property ContextMenuService.Placement to Left, Top, Bottom. And the result is:
Left -> The ContextMenu is placed at the right of the button.
Top -> The ContextMenu is placed at the top-right of the button. (not top-left)
Bottom -> The ContextMenu is placed at the bottom-right of the button (not bottom-left)
It seems to me that the coordinate system is mirrored (that is, the origin of the coordinate system is at the top-right of the window, not top-left). I don't know why at all. I need help to place the ContextMenu at the bottom-left of the button.
(P.S. This sample project is the same as the default project created by Visual Studio 2013 except the MainWindow.xaml.)

OK, I finally (yes, finally) found the solution. The problem is maybe isomorphic to the one described here: WPF Handedness with Popups
So I wrote a behavior:
namespace Test {
public class WorkaroundForTheBugOfPopupBehavior : Behavior<FrameworkElement> {
private static readonly FieldInfo _menuDropAlignmentField;
static WorkaroundForTheBugOfContextMenuBehavior() {
_menuDropAlignmentField = typeof(SystemParameters).GetField("_menuDropAlignment", BindingFlags.NonPublic | BindingFlags.Static);
System.Diagnostics.Debug.Assert(_menuDropAlignmentField != null);
EnsureStandardPopupAlignment();
SystemParameters.StaticPropertyChanged += OnStaticPropertyChanged;
}
private static void EnsureStandardPopupAlignment() {
if (SystemParameters.MenuDropAlignment && _menuDropAlignmentField != null) {
_menuDropAlignmentField.SetValue(null, false);
}
}
private static void OnStaticPropertyChanged(object sender, PropertyChangedEventArgs e) {
EnsureStandardPopupAlignment();
}
}
}
... and attached it to MainWindow:
<i:Interaction.Behaviors>
<local:WorkaroundForTheBugOfPopupBehavior />
</i:Interaction.Behaviors>
where:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:Test"
And the problem is gone. Hope this helps someone else...

Related

How to add to Click event to Context Menu buttons in WPF NotifyIcon?

I am making an app that has a notify icon in WPF. I am using HardCodet NotifyIcon. They do have a tutorial on code project and it is pretty useful but it does not have any explanation on how to set up OnClick or Click event when the buttons in the context menu are pressed.
I have gone through every property in NotifyIcon.ContextMenu.Items and NotifyIcon.ContextMenu.Items.GetItemAt(i) (TaskbarIcon NotifyIcon = (TaskbarIcon) FindResource("MyNotifyIcon")) but there is nothing I found. I also tried typecasting the buttons to MenuItem and using its Click event but it didn't help.
This is my App.xaml:
<Application.Resources>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tb="http://www.hardcodet.net/taskbar">
<tb:TaskbarIcon x:Key="MyNotifyIcon"
ToolTipText="Hello There">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu Background="White">
<MenuItem Header="Open"/>
<MenuItem Header="Settings"/>
<MenuItem Header="Sign Out"/>
<MenuItem Header="Help"/>
<MenuItem Header="Exit"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
</ResourceDictionary>
</Application.Resources>
I need the buttons to control the MainWindow e.g. change the Visibility etc..
There is no difference to other controls, you can just set up a Click handler on each MenuItem.
<MenuItem Header="Open" Click="Open_OnClick"/>
In your example, you would implement the event handler in App.xaml.cs.
public partial class App : Application
{
// ...application code.
private void Open_OnClick(object sender, RoutedEventArgs e)
{
// ...do something.
}
}
You could also assign a view model as DataContext to TaskbarIcon and use a command instead.

WPF: simplest way to call some method by shortcut

I checked a lot of of answers here about shortcuts bindings, but did not find the simple way to execute some method from the C# code.
First, in the following classic example I don't understand what actually we are binding. What the meaning of Command attribute? What is the ApplicationCommands? Where is ApplicationCommands.Open has been declared?
<Window.InputBindings>
<KeyBinding Command="ApplicationCommands.Open"
Gesture="CTRL+R" />
</Window.InputBindings>
In the one of answer of question similar to my one, "XAML is the markup language, so we could not call the method from there" has been told. OK, in this case, WHY we can call the method OnClickBtn1 from the code below?
<Button
x:Name="SomeButton"
Click="OnClickBtn1"/>
Finally, my problem. All I need is to execute OnClickBtn1 method by Ctrl+H (for example) shortcut same as effect when the button Btn1 clicked. I understand that following code is not enough.
XAML:
<Window x:Name="MainDisplay"
<!-- ... --->
>
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+H" Command="{Binding OnClickBtn1}" />
</Window.InputBindings>
<!-- ... -->
<Button x:Name="Btn1"
Width="70"
Content="Button"
Click="OnClickBtn1"/>
C#:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void OnClickBtn1(object sender, RoutedEventArgs e) {
System.Diagnostics.Debug.WriteLine("Btn1 has been clicked or Ctrl+H had been inputed");
}
}

Fire a command from a Button inside a ContentControl?

I'm new to WPF and I'm trying to dynamically add a Button inside a ContentControl, which should fire a command when clicked. I'm using MVVMLight to handle the Commands.
Below I have an example with two buttons. The top button is placed directly into the StackPanel. This button fires off the Command as expected.
The second button is placed inside a ContentControl. It displays correctly, but the Command does not fire when the button is clicked.
I assumed this is because the Binding does not transfer down through the DataTemplate, but it seems to work if I use regular Commands instead of MVVMLight RelayCommands.
I don't want to remove the framework, so I'm wondering if anyone knows how to fix it? Thanks
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="MyButton" >
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<!--When this button is clicked, the Command executes as expected-->
<Button Content="SUBMIT" Command="{Binding MyCommand}" Width="200" Height="50"/>
<!--Nothing happens when this button is clicked-->
<ContentControl ContentTemplate="{StaticResource MyButton}"/>
</StackPanel>
</Window>
Here's the ViewModel with the command:
public class MainViewModel : ViewModelBase
{
public ICommand MyCommand { get; private set; }
public MainViewModel()
{
MyCommand = new RelayCommand(MyCommand_Executed, MyCommand_CanExecute);
}
private bool MyCommand_CanExecute()
{
return true;
}
private void MyCommand_Executed()
{
MessageBox.Show("The command executed");
}
}
The problem here is the implicit DataContext in ContentTemplate is the Content and this has not been set to anything. You need to set Content to some Binding to bridge the DataContext currently in the visual tree, something like this:
<ContentControl ContentTemplate="{StaticResource MyButton}" Content="{Binding}"/>
Another solution is to give your Window a name:
<Window x:Class="ContentControlExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ContentControlExample.ViewModel"
x:Name="_this">
Then bind via its context instead:
<Button Content="SUBMIT" Command="{Binding ElementName=_this, Path=DataContext.MyCommand}" Width="200" Height="50"/>
This is particularly handy for things like ListViews and ItemControls, as their DCs get set to the list elements. Keep in mind though that this will only work on members within the same visual tree, if that's not the case (e.g. popup menus etc) then you need to proxy a binding as described in this article.

ContextMenuOpening event not firing in WPF?

I have a resource dictionary inside which I have a context menu:
<ResourceDictionary x:Class="MyApp.Components.MyContextMenu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp"
xmlns:components="clr-namespace:MyApp.Components">
<ContextMenu ContextMenuOpening="OnContextMenuOpening">
and the resource dictionary XAML has this code behind:
using System;
using System.Windows;
using System.Windows.Controls;
namespace MyApp.Components
{
public partial class MyContextMenu : ResourceDictionary
{
public MyContextMenu()
{
InitializeComponent();
}
void OnContextMenuOpening(object sender, ContextMenuEventArgs e)
{
Console.WriteLine("here i am!");
}
}
}
The log is not appearing. I wonder why the event is not firing or getting to the right place -- is the problem because I have wrapped the context menu inside this resource dictionary?
Update: Interestingly if I remove the code-behind function, I get an error during compilation:
does not contain a definition for 'ContextMenu_OnContextMenuOpening'
and no extension method 'ContextMenu_OnContextMenuOpening' accepting a
first argument of type 'MyApp.Components.MyContextMenu' could be found
(are you missing a using directive or an assembly reference?)
Update 2: Looks like that both Console.WriteLine and Debug.WriteLine produce output, but only "randomly" and especially when I'm clicking near the bottom of the item. Some sort of collision detection not working maybe?
ContextMenuOpening event must be handled on an ancestor of the ContextMenu not on the ContextMenu itself. If you try handling it on the ContextMenu the event only fires when you right click once ContextMenu is already open.
It is a bug in the framework: http://connect.microsoft.com/VisualStudio/feedback/details/353112/contextmenu-opening-event-doesnt-fire-properly
A contextmenu's opening event doesn't fire on the first right click.
It only fires when you do two sequential right clicks while not moving
the mouse.
I believe kurrazyman has the right answer, but it took me a while to understand it.
In my case I had a TreeView control with a context menu.
Using myTreeView.ContextMenu.ContextMenuOpening didn't work, but using myTreeView.ContextMenuOpening did.
Its not a bug, it is working... here is the most common mistake that most people is doing with ContextMenuOpening event... consider this two different scenario to figure out the actual cause of this problem,
SCENARIO 1 (This will not work):
<ListBox Name="lb_sizes" Height="120">
<ListBox.ContextMenu>
<ContextMenu ContextMenuOpening="My_ContextMenuOpening">
<MenuItem Header="Delete"/>
<MenuItem Header="Delete All"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
SCENARIO 2 (This will work):
<ListBox Name="lb_sizes" Height="120" ContextMenuOpening="My_ContextMenuOpening">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"/>
<MenuItem Header="Delete All"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
The only difference is assigning ContextMenuOpening event to proper element...
in scenario 1 it is assigned (attached) to <ContextMenu> element and in scenario 2, it is assigned to <ListBox> element which is proper way to do it and should work.
I'm using IsVisibleChanged event instead:
private void ContextMenu_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var isVisible = (bool)e.NewValue;
if (isVisible)
{
//...
}
}

TabControl- preventing user from changing the selected tab: MessageBox causing bug

I've been pounding away at this issue for a little while, and have only found part of the solution.
I'm trying to set up a TabControl so that I can in some cases prevent the user from changing the currently selected tab. When the user is prevented from changing the currently selected tab, then they are shown a dialog box.
I have already read the following documents:
WPF - reset ListBox scroll position when ItemsSource changes
http://wizardsofsmart.net/uncategorized/itemssourcechanged-event-using-attached-dependency-properties/
http://joshsmithonwpf.wordpress.com/2009/09/04/how-to-prevent-a-tabitem-from-being-selected/
http://social.expression.microsoft.com/Forums/en-US/wpf/thread/f7b46018-1e97-4bbe-ada8-49b75dbc1da2/
I have implemented the solution indicated in the 3rd link (though all of the above create the same error seen below). And it works, but...
Things mess up thoroughly if the user does the following:
attempts to change the tab when such an action is disallowed. The MessageBox pops up with the error.
the user clicks "OK" and is returned to the original window.
the user tries again to change the tab. No MessageBox appears.
if the user minimizes the window, and then maximizes it again, then the MessageBox that was supposed to appear earlier appears.
the user clicks "OK" and is returned to the original window... but the tab has been changed to the one they selected before, even though they should not be able to change tabs.
This is obviously not ideal behavior. Why isn't the MessageBox appearing the second time, and why is the tab changing when it should be disallowed from doing so?
If I remove the MessageBox part, it works fine.
Here is the code for the TabControl.SelectionChanged event handler:
bool _isChanging = false;
private void tabControlForNavigation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_isChanging && canChangeTabs.IsChecked.HasValue)
{
_isChanging = true;
bool canLeave = canChangeTabs.IsChecked.Value; //normally this would be replaced by a check in the ViewModel
if (!canLeave)
{
int prevIndex = tabControlForNavigation.Items.IndexOf(tabControlForNavigation.SelectedContent);
tabControlForNavigation.SelectedIndex = prevIndex;
MessageBox.Show("Can't change tabs!"); //if I comment out this line, everything works fine.
}
_isChanging = false;
}
}
I am using MVVM to implement this. The Window looks like this:
<Window x:Class="TestTabControlSwitching.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox x:Name="canChangeTabs"
Content="Can Change Tabs"
IsChecked="True" />
<TabControl x:Name="tabControlForNavigation"
Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem}"
SelectionChanged="tabControlForNavigation_SelectionChanged"
Margin="4"
HorizontalAlignment="Stretch">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
I'm omitting the rest of the code for sake of brevity- there is a pretty straight-forward ViewModel structure backing the window.
As you noticed, the problem is the MessageBox inside the event handler. The focus will change to the MessageBox and you can get all kind of undesired effects. I've had my own problems with this.
Here is a couple of SO question on the same subject
WPF: Does MessageBox Break PreviewMouseDown?
Wpf stop routing event when MessageBox appear?
If you must display a message to the user then an alternate approach might be to create a new Window which you style like a MessageBox and then call Show (not ShowDialog) on it inside the event handler.
I know this post is a bit old, but I have a very easy way to accomplish this:
Use the tab_Enter event and create a method that performs your check and displays a MessageBox to the user and then set myTabs.SelectedIndex to the prior index. A simple example:
private void someTab_Enter(object sender, EventArgs e)
{
if (myCondition)
{
MessageBox.Show("Sorry, myCondition will not let you move to this tab.");
myTabs.SelectedIndex = someOtherTabIndex;
}
}
This was a very detailed question. I had the same problem you had (i.e. the message box doesn't display on 2nd or 3rd selection changed until you minimize and maximize the window) and after much debugging and multiple google searches, stumbled on the below linked MSDN forum post.
[TabControl SelectionChanged Strange Behaviour?]
Please ignore the poorly formatted question and answer. But as mentioned in the answer, putting it inside a dispatcher and focussing the selected tab after setting the index resolved the issue for me.
You are missing an easy trick. Just make focusable=False for the Tab header.
<TabItem Header="MY TAB" Focusable="False">
You could bind this property to your view model.
<TabItem Header="MY TAB" Focusable="{Binding Bool_CanHasCheeseBurger}">

Categories

Resources