CodeSlinger

I have a menu with several menuitems and sub menuitems. A classic type File menu.

I set a MenuItem style with an IsMouseOver trigger and it works but...

When the mouse is anywhere in any part of the entire menu dropdown ALL the menu items respond to the style rather than just the item that the mouse is over. I can move the mouse out of the menu dropdown and the style is removed from all items at once.

Is this a bug or am I doing something wrong

I just want the one menu item that I am over to have the style.

Thanks,

Dave

Here is the style:

<Style TargetType="{x:Type MenuItem}">

<Setter Property = "Foreground" Value= "{DynamicResource {x:Static SystemColors.MenuTextBrushKey}}"/>

<Style.Triggers>

<Trigger Property="MenuItem.IsMouseOver" Value="true">

<Setter Property = "Foreground" Value="Red"/>

<Setter Property = "FontStyle" Value="Italic"/>

</Trigger>

</Style.Triggers>

</Style>

Here is the menu:

<Menu DockPanel.Dock="Top">

<MenuItem Header="_File" VerticalAlignment="Top" Name="gFileMenuItem">

<MenuItem Header="_New" Name="gNewMenuItem">

<MenuItem Header="Sequential Workflow" Name="gNewSeqMenuItem" Command="{x:Static custom:WWConsoleWindow.NewSeqCmd}"/>

<MenuItem Header="State Machine Workflow" Name="gNewStateMenuItem" Command="{x:Static custom:WWConsoleWindow.NewStateCmd}" />

</MenuItem>

<MenuItem Header="_Open..." Name="gOpenMenuItem" Command="Open" />

<MenuItem Header="_Save..." Name="gSaveMenuItem" Command="Save" />

<MenuItem Header="_Save As..." Name="gSaveAsMenuItem" Command="SaveAs" />

<Separator />

<MenuItem Header="E_xit" Name="gExitMenuItem" Command="{x:Static custom:WWConsoleWindow.ExitCmd}" />

</MenuItem>

</Menu>




Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

Dr. WPF

You need to use IsFocused for your trigger:

Code Block

<Trigger Property="IsFocused" Value="True">

<Setter Property = "Foreground" Value="Red"/>

<Setter Property = "FontStyle" Value="Italic"/>

</Trigger>

With a hierarchical items control, the mouse is considered to be over each item in the parent hierarchy, but only one has focus at any given time.






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

DaveAtDotNetCodeSlingers

Unfortunately I tried that and it did not work very well. Sort of worked but really acted weird if you did NOT have the mouse over a MenuItem - esp. if you held the mouse below Exit and the end of the drop down would oscillate really weird. Also tried a MultipleTrigger condition with IsMouseOver and IsFocused which also sort fo worked but not as one would expect.

What worked sort of the best was the below where I added a key to the style and applied the style only to terminal MenuItems and not to any parent items. So no parents ever get the style. Personally I think they messed the implementation of IsMouseOver up.

<Style x:Key="Triggers" TargetType="{x:Type MenuItem}">

<Style.Triggers>

<Trigger Property="MenuItem.IsMouseOver" Value="true">

<Setter Property = "Foreground" Value="Red"/>

<Setter Property = "FontSize" Value="16"/>

<Setter Property = "FontStyle" Value="Italic"/>

</Trigger>

</Style.Triggers>

</Style>

and

<Menu DockPanel.Dock="Top">

<MenuItem Header="_File" Name="gFileMenuItem">

<MenuItem Header="_New" Name="gNewMenuItem">

<MenuItem Header="Sequential Workflow" Name="gNewSeqMenuItem" Command="{x:Static custom:WWConsoleWindow.NewSeqCmd}" Style="{StaticResource Triggers}"/>

<MenuItem Header="State Machine Workflow" Name="gNewStateMenuItem" Command="{x:Static custom:WWConsoleWindow.NewStateCmd}" Style="{StaticResource Triggers}"/>

</MenuItem>

<MenuItem Header="_Open..." Name="gOpenMenuItem" Command="Open" Style="{StaticResource Triggers}" />

<MenuItem Header="_Save..." Name="gSaveMenuItem" Command="Save" Style="{StaticResource Triggers}" />

<MenuItem Header="_Save As..." Name="gSaveAsMenuItem" Command="SaveAs" Style="{StaticResource Triggers}" />

<Separator />

<MenuItem Header="E_xit" Name="gExitMenuItem" Command="{x:Static custom:WWConsoleWindow.ExitCmd}" Style="{StaticResource Triggers}" />

</MenuItem>

</Menu>






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

DaveAtDotNetCodeSlingers

Dr. W - is there any documentation on this hierarchial behaviour I've not been able to find any and woul dlike to read more about it.

Thanks,

Dave






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

Dr. WPF

Don't know... haven't read the docs. (That is certainly not a commentary on the SDK docs... reflector was the only documentation when I learned Avalon.)

There are two HeaderedItemsControl classes that behave as hierarchical items controls (meaning that their Items collection is made up of additional headered items controls of the same type). They are MenuItem and TreeViewItem. Both behave the same. If the mouse is over a subitem (an item in the Items collection), IsMouseOver will also be true for the "parent" item (if you wish to think of it that way).

If you think about this, it makes sense. The HeaderedItemsControl consists of both the header and the items collection. So if the mouse is over an element in the items collection, it is certainly also over the HeaderedItemsControl itself.

If you draw a box around each MenuItem in a hierarchy, you'll note that it's impossible to put the mouse over a child item without it being within the box of the parent too.

|----------------------|

| MenuItem |

| |-------------------|

| | MenuItem |

| |-------------------|

| | MenuItem |

| | |----------------|

| | | MenuItem |

| | |----------------|

| | | MenuItem |

| | |----------------|

| |-------------------|

|----------------------|

IsMouseOver does not tell you that the mouse is "strictly" over a single item. The mouse may be over dozens of elements within the element tree all at once. For instance, if you have a Grid that contains a Button and you put the mouse over the button, IsMouseOver will be true on the Grid too.




Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

Marco Zhou - MSFT

Dr WPF hits the actual points. IsMouser will be true for the entire HeaderedItemsControl. Fortunately Mike Hillberg has come up with a good solution to get the "actual" child item the mouse is over:

http://blogs.msdn.com/mikehillberg/archive/2006/09/21/MyTreeViewHelperIsMouseDirectlyOverItem.aspx

Hope this helps





Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

DaveAtDotNetCodeSlingers

Thanks for the replies guys. Not sure why I was not alerted either of you had made a response but appreciate your answers and need to digest.

Dave






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

DaveAtDotNetCodeSlingers

Dr. W,

Thanks for the explanation and it makes sense. Would also make sense imo if MSFT also had some sort of test for the mouse being over a specific MenuItem or TreeviewItem.

Do you know why "IsMouseDirectlyOver" does not work for this as at least in name it seems similar in purpose as to the solution that Marco mentioned that Mike Hillberg came up with.

Thanks,

Dave






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

DaveAtDotNetCodeSlingers

Thanks Marco.

I created a similar helper class for Menu and now none of the parent or siblings are style'd but all of the children are so guess I need to tweak the class some besides a straight sub of Menu for Treeview. This was good to learn as I realize more that I have a big learning curve still ahead with WPF.

Thanks very much,

Dave






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

Dr. WPF

DaveAtDotNetCodeSlingers wrote:

Would also make sense imo if MSFT also had some sort of test for the mouse being over a specific MenuItem or TreeviewItem.

I totally agree. That would have been really nice.

DaveAtDotNetCodeSlingers wrote:

Do you know why "IsMouseDirectlyOver" does not work for this...

IsMouseDirectlyOver is only true for the element that the mouse is directly over. Since the MenuItem contains header text, there is a TextBlock "on top of" (within) the MenuItem that displays the header. As such, IsMouseDirectlyOver will be true for this TextBlock, rather than the MenuItem.






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

DaveAtDotNetCodeSlingers

Well I think I give up for now. Need to make some progress in other areas. I modifed the MyTreeviewHelper to become a MyMenuHelper and it works to some extent but the children are always styled like the menuitem the mouse is over and I wanted just the particular item and no other. I noticed that when the mouse is over disabled items then the parent item seems to be the current item. I even recursed the visual tree looking for some element that had the mouse over but had no luck. Here is my latest attempt if anyone wants to persue further. GlobalLogging is just my code to capture tracing so substitue in your favorite. Sorry the formatting was lost in the code example. I formatted the one routine of interest where I tried to detect IsMouseDIrectlyOver on each child control found in the visual tree of the menuitem which seems to be reasonable.

Dave

public static class MyMenuHelper

{

//

// The MenuItem that the mouse is currently directly over (or null).

//

private static MenuItem _currentItem = null;

//

// IsMouseDirectlyOverItem: A DependencyProperty that will be true only on the

// MenuItem that the mouse is directly over. I.e., this won't be set on that

// parent item.

//

// This is the only public member, and is read-only.

//

// The property key (since this is a read-only DP)

private static readonly DependencyPropertyKey IsMouseDirectlyOverItemKey =

DependencyProperty.RegisterAttachedReadOnly(

"IsMouseDirectlyOverItem",

typeof(bool),

typeof(MyMenuHelper),

new FrameworkPropertyMetadata(null,

new CoerceValueCallback(CalculateIsMouseDirectlyOverItem)));

// The DP itself

public static readonly DependencyProperty IsMouseDirectlyOverItemProperty =

IsMouseDirectlyOverItemKey.DependencyProperty;

// A strongly-typed getter for the property.

public static bool GetIsMouseDirectlyOverItem(DependencyObject obj)

{

return (bool)obj.GetValue(IsMouseDirectlyOverItemProperty);

}

static public bool FindControl(Visual myVisual)

{

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)

{

// Retrieve child visual at specified index value.

Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);

GlobalLogging.Log("Visual type '{0}' found.", childVisual.GetType().ToString());

bool lHit = (bool)childVisual.GetValue(IsMouseDirectlyOverItemProperty);

if (lHit)

{

GlobalLogging.Log("Control '{0}' found with mouseover.", childVisual.GetType().ToString());

return true;

}

// search children of the child visual object

FindControl(childVisual);

}

// no textblock found with the mouse over it

return false;

}

// A coercion method for the property

private static object CalculateIsMouseDirectlyOverItem(DependencyObject item, object value)

{

// This method is called when the IsMouseDirectlyOver property is being calculated for a MenuItem.

if (item == _currentItem && FindControl(_currentItem) == true)

{

GlobalLogging.Log("IsMouseDirectlyOver over item '{0}'.", _currentItem.Header);

return true;

}

else

return false;

}

//

// UpdateOverItem: A private RoutedEvent used to find the nearest encapsulating

// MenuItem to the mouse's current position.

//

private static readonly RoutedEvent UpdateOverItemEvent = EventManager.RegisterRoutedEvent(

"UpdateOverItem", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyMenuHelper));

//

// Class constructor

//

static MyMenuHelper()

{

// Get all Mouse enter/leave events for MenuItem.

EventManager.RegisterClassHandler(typeof(MenuItem),

MenuItem.MouseEnterEvent,

new MouseEventHandler(OnMouseTransition), true);

EventManager.RegisterClassHandler(typeof(MenuItem),

MenuItem.MouseLeaveEvent,

new MouseEventHandler(OnMouseTransition), true);

// Listen for the UpdateOverItemEvent on all MenuItem's.

EventManager.RegisterClassHandler(typeof(MenuItem),

UpdateOverItemEvent,

new RoutedEventHandler(OnUpdateOverItem));

}

//

// OnUpdateOverItem: This method is a listener for the UpdateOverItemEvent. When it is received,

// it means that the sender is the closest MenuItem to the mouse (closest in the sense of the

// tree, not geographically).

static void OnUpdateOverItem(object sender, RoutedEventArgs args)

{

// Mark this object as the tree view item over which the mouse

// is currently positioned.

_currentItem = sender as MenuItem;

// Tell that item to re-calculate the IsMouseDirectlyOverItem property

_currentItem.InvalidateProperty(IsMouseDirectlyOverItemProperty);

// Prevent this event from notifying other tree view items higher in the tree.

args.Handled = true;

}

//

// OnMouseTransition: This method is a listener for both the MouseEnter event and

// the MouseLeave event on MenuItems. It updates the _currentItem, and updates

// the IsMouseDirectlyOverItem property on the previous MenuItem and the new

// MenuItem.

static void OnMouseTransition(object sender, MouseEventArgs args)

{

lock (IsMouseDirectlyOverItemProperty)

{

if (_currentItem != null)

{

// Tell the item that previously had the mouse that it no longer does.

DependencyObject oldItem = _currentItem;

_currentItem = null;

oldItem.InvalidateProperty(IsMouseDirectlyOverItemProperty);

}

// Get the element that is currently under the mouse.

IInputElement currentPosition = Mouse.DirectlyOver;

// See if the mouse is still over something (any element, not just a tree view item).

if (currentPosition != null)

{

// Yes, the mouse is over something.

// Raise an event from that point. If a MenuItem is anywhere above this point

// in the tree, it will receive this event and update _currentItem.

RoutedEventArgs newItemArgs = new RoutedEventArgs(UpdateOverItemEvent);

currentPosition.RaiseEvent(newItemArgs);

}

}

}

}






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

DaveAtDotNetCodeSlingers

Hi Microsoft!

Do you guys read these

How about an IsMouseReallyIsOver property for MenuItem and TreeItem controls that is for that and only that item

Thanks,

Dave






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

Marco Zhou - MSFT

I think what you really need is MenuItem.IsHighlighted DependencyProperty, you can use Trigger to monitor this property and change the style of the MenuItem accordingly.

Hope this helps





Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

Dr. WPF

Marco Zhou wrote:

I think what you really need is MenuItem.IsHighlighted DependencyProperty

IsHighlighted is *close* to what Dave is asking for... Unfortunately, it suffers from the same problem as IsMouseOver. Namely, parent menu items remain highlighted when the mouse moves down the submenu tree.

A second issue with IsHighlighted (which may or may not be of concern to Dave) is that it also responds to keyboard focus. If you put the mouse over a menu item and then use the keyboard to navigate to another menu item, the other item will steal the highlight. IsHighlighted is now false for the item under the mouse. This is definitely the desired behavior for menu navigation, but it does not provide an accurate "IsMouseReallyOver" property.






Re: Windows Presentation Foundation (WPF) MENUITEM.IsMouseOver true for all menuitems at once?

Dr. WPF

I've posted a solution here. Let me know how it works for you.