Programmatically open context menu using UI automation?
I'm trying to implement a right click context menu using UI automation. Since UI automation does not have a native right click pattern I am adding an ExpandCollapse provider to the listview's AutomationPeer class and mapping the expand and collapse to opening and closing the context menu.
My question, is there a better method of invoking the context menu that doesn't involve trying to instantiate a class with a private constructor? I can't use SendKeys with Shift-F10. I'd like to use the PopupControlService but that is tagged as internal.
My awful workaround:
public class MyListViewAutomationPeer : ListViewAutomationPeer, IExpandCollapseProvider
{
public MyListViewAutomationPeer(MyListView owner)
: base(owner){}
public override object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.ExpandCollapse)
{
return this;
}
return base.GetPattern(patternInterface);
}
public void Expand()
{
MyListView owner = (MyListView)Owner;
//**********************
//Ouch!!! What a hack
//**********************
//ContextMenuEventArgs is a sealed class, with private constructors
//Instantiate it anyway ...
ContextMenuEventArgs cmea = (ContextMenuEventArgs)FormatterServices.GetUninitializedObject(typeof(ContextMenuEventArgs));
cmea.RoutedEvent = MyListView.ContextMenuOpeningEvent;
cmea.Source = owner;
//This will fire any developer code that is bound to the OpenContextMenuEvent
owner.RaiseEvent(cmea);
//The context menu didn't open because this is a hack, so force it open
owner.ContextMenu.Placement = PlacementMode.Center;
owner开发者_开发问答.ContextMenu.PlacementTarget = (UIElement)owner;
owner.ContextMenu.IsOpen = true;
}
I too am struggling with the same issue. As a work around i am using the mouse_event function using user32.dll and performing the right click after getting the X, Y coordinates of the clickable area.
This is not a good method as the X , Y coordinates of screen vary with the screen resolution.
What you have is a good start, but you should use Activator directly to create the ContextMenuEventArgs
class. (What you have is using something that internally uses the reflection. You should just use it directly.)
Also, for completeness, you should check the Handled
property before actually opening the menu to be consistent with the expected behavior.
I've illustrated both things below.
var owner = (MyListView)Owner;
// Construct the ContextMenuEventArgs
var constructorArgs = new object[] { owner, true };
var contextMenuEventArgs = (ContextMenuEventArgs)Activator.CreateInstance(
typeof(ContextMenuEventArgs),
BindingFlags.Instance | BindingFlags.NonPublic,
null,
constructorArgs,
null);
// Configure and use as needed
contextMenuEventArgs.RoutedEvent = FrameworkElement.ContextMenuOpeningEvent;
owner.RaiseEvent(contextMenuEventArgs);
if (contextMenuEventArgs.Handled)
return;
owner.ContextMenu.Placement = PlacementMode.Center;
owner.ContextMenu.PlacementTarget = (UIElement)owner;
owner.ContextMenu.IsOpen = true;
I took it a step further and created a global function I can call that simplifies the above calling of non-public constructors. Here's the function...
public static class PrivateActivator {
public static T CreateInstance<T>(params object?[] arguments) {
var item = Activator.CreateInstance(
typeof(T),
BindingFlags.Instance | BindingFlags.NonPublic,
null,
arguments,
null);
if (item is T typedItem)
return typedItem;
else
throw new Exception("Cannot create type");
}
}
And here's how to use it...
var contextMenuEventArgs = PrivateActivator.CreateInstance<ContextMenuEventArgs>(AssociatedObject, true);
精彩评论