Pok*_*u22 10 .net c# reflection menuitem winforms
我在应用程序中使用传统MainMenu控件(带MenuItems)控件,并希望实现放大和缩小菜单项(使用Control+ +和Control+ -键盘快捷键).(注意我正在使用MainMenu而不是MenuStrip).MenuItem确实有一个Shortcut类型的属性,Shortcut但没有CtrlPlus选项.
我决定看一下在Shortcutreferencesource中是如何实现的,看起来每个枚举值的方式只是几个Keys枚举值的组合(例如CtrlA只是Keys.Control + Keys.A).所以我尝试创建一个自定义的快捷方式值,该值应该等于Control + Plus:
const Shortcut CONTROL_PLUS = (Shortcut)(Keys.Control | Keys.Oemplus);
zoomInMenuItem.Shortcut = CONTROL_PLUS;
Run Code Online (Sandbox Code Playgroud)
但是,这会InvalidEnumArgumentException在我尝试分配Shortcut属性时抛出.
所以我决定使用反射,修改(不公开)MenuItemData的shortcut属性,然后调用(非公共)UpdateMenuItem方法.这实际上有效(具有Control+Oemplus在菜单项中显示的副作用):
const Shortcut CONTROL_PLUS = (Shortcut)(Keys.Control | Keys.Oemplus);
var dataField = typeof(MenuItem).GetField("data", BindingFlags.NonPublic | BindingFlags.Instance);
var updateMenuItemMethod = typeof(MenuItem).GetMethod("UpdateMenuItem", BindingFlags.NonPublic | BindingFlags.Instance);
var menuItemDataShortcutField = typeof(MenuItem).GetNestedType("MenuItemData", BindingFlags.NonPublic)
.GetField("shortcut", BindingFlags.NonPublic | BindingFlags.Instance);
var zoomInData = dataField.GetValue(zoomInMenuItem);
menuItemDataShortcutField.SetValue(zoomInData, CONTROL_PLUS);
updateMenuItemMethod.Invoke(zoomInMenuItem, new object[] { true });
Run Code Online (Sandbox Code Playgroud)
虽然该方法有效,但它使用反射,我不确定它是否适合未来.
我正在使用MenuItem而不是更新,ToolStripMenuItem因为我需要拥有RadioCheck属性(以及其他原因); 切换远离那是不可取的.

这是一些创建上述对话框的完整代码,它显示了我正在尝试完成的内容(方法中最相关的代码OnLoad):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
namespace ZoomMenuItemMCVE
{
public partial class ZoomForm : Form
{
private double zoom = 1.0;
public double Zoom {
get { return zoom; }
set {
zoom = value;
zoomTextBox.Text = "Zoom: " + zoom;
}
}
public ZoomForm() {
InitializeComponent();
}
protected override void OnLoad(EventArgs e) {
const Shortcut CONTROL_PLUS = (Shortcut)((int)Keys.Control + (int)Keys.Oemplus);
const Shortcut CONTROL_MINUS = (Shortcut)((int)Keys.Control + (int)Keys.OemMinus);
base.OnLoad(e);
//We set menu later as otherwise the designer goes insane (http://stackoverflow.com/q/28461091/3991344)
this.Menu = mainMenu;
var dataField = typeof(MenuItem).GetField("data", BindingFlags.NonPublic | BindingFlags.Instance);
var updateMenuItemMethod = typeof(MenuItem).GetMethod("UpdateMenuItem", BindingFlags.NonPublic | BindingFlags.Instance);
var menuItemDataShortcutField = typeof(MenuItem).GetNestedType("MenuItemData", BindingFlags.NonPublic)
.GetField("shortcut", BindingFlags.NonPublic | BindingFlags.Instance);
var zoomInData = dataField.GetValue(zoomInMenuItem);
menuItemDataShortcutField.SetValue(zoomInData, CONTROL_PLUS);
updateMenuItemMethod.Invoke(zoomInMenuItem, new object[] { true });
var zoomOutData = dataField.GetValue(zoomOutMenuItem);
menuItemDataShortcutField.SetValue(zoomOutData, CONTROL_MINUS);
updateMenuItemMethod.Invoke(zoomOutMenuItem, new object[] { true });
}
private void zoomInMenuItem_Click(object sender, EventArgs e) {
Zoom *= 2;
}
private void zoomOutMenuItem_Click(object sender, EventArgs e) {
Zoom /= 2;
}
}
}
Run Code Online (Sandbox Code Playgroud)
namespace ZoomMenuItemMCVE
{
partial class ZoomForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
System.Windows.Forms.MenuItem viewMenuItem;
this.zoomTextBox = new System.Windows.Forms.TextBox();
this.mainMenu = new System.Windows.Forms.MainMenu(this.components);
this.zoomInMenuItem = new System.Windows.Forms.MenuItem();
this.zoomOutMenuItem = new System.Windows.Forms.MenuItem();
viewMenuItem = new System.Windows.Forms.MenuItem();
this.SuspendLayout();
//
// zoomTextBox
//
this.zoomTextBox.Dock = System.Windows.Forms.DockStyle.Bottom;
this.zoomTextBox.Location = new System.Drawing.Point(0, 81);
this.zoomTextBox.Name = "zoomTextBox";
this.zoomTextBox.ReadOnly = true;
this.zoomTextBox.Size = new System.Drawing.Size(292, 20);
this.zoomTextBox.TabIndex = 0;
this.zoomTextBox.Text = "Zoom: 1.0";
//
// mainMenu
//
this.mainMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
viewMenuItem});
//
// viewMenuItem
//
viewMenuItem.Index = 0;
viewMenuItem.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.zoomInMenuItem,
this.zoomOutMenuItem});
viewMenuItem.Text = "View";
//
// zoomInMenuItem
//
this.zoomInMenuItem.Index = 0;
this.zoomInMenuItem.Text = "Zoom in";
this.zoomInMenuItem.Click += new System.EventHandler(this.zoomInMenuItem_Click);
//
// zoomOutMenuItem
//
this.zoomOutMenuItem.Index = 1;
this.zoomOutMenuItem.Text = "Zoom out";
this.zoomOutMenuItem.Click += new System.EventHandler(this.zoomOutMenuItem_Click);
//
// ZoomForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 101);
this.Controls.Add(this.zoomTextBox);
this.Name = "ZoomForm";
this.Text = "ZoomForm";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.MainMenu mainMenu;
private System.Windows.Forms.TextBox zoomTextBox;
private System.Windows.Forms.MenuItem zoomInMenuItem;
private System.Windows.Forms.MenuItem zoomOutMenuItem;
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码工作并做我想要的,但我不确定它是否是正确的方法(使用反射来修改私有变量通常看起来像是不正确的方法).我的问题是:
它使用反射,我不确定它是否适合未来
你会逃脱它,没有什么特别危险的事情发生在引擎盖下.MainMenu/MenuItem类是一成不变的,永远不会改变.对于Windows版本,您是面向未来的,这种快捷行为实际上并未由Windows实现,而是已添加到MenuItem中.它实际上是使其工作的Form.ProcessCmdKey()方法.在没有黑客攻击菜单项的情况下,这是你的提示.将此代码粘贴到表单类中:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == (Keys.Control | Keys.Oemplus)) {
zoomInMenuItem.PerformClick();
return true;
}
if (keyData == (Keys.Control | Keys.OemMinus)) {
zoomOutMenuItem.PerformClick();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
Run Code Online (Sandbox Code Playgroud)
你得到的快捷键描述是gawdawful,没有正常的人知道"Oemplus"可能意味着什么.不要使用自动生成的,自己编写.您无法使用设计器执行此操作,也不会让您在项目文本和快捷键描述之间输入制表符.但代码没问题:
public ZoomForm() {
InitializeComponent();
zoomInMenuItem.Text = "Zoom in\tCtrl +";
zoomOutMenuItem.Text = "Zoom out\tCtrl -";
}
Run Code Online (Sandbox Code Playgroud)
我正在使用MenuItem而不是更新的ToolStripMenuItem,因为......
这不是一个很好的理由.ToolStripMenuItem确实没有为radiobutton行为提供开箱即用的实现,但是很容易添加自己.Winforms使您可以轻松创建自己的ToolStrip项目类,这些项目在设计时可用,并且可以在运行时选择.在项目中添加一个新类并粘贴下面显示的代码.编译.在设计时使用Insert> RadioItem上下文菜单项插入一个,Edit DropdownItems ...上下文菜单项可以轻松添加几个.您可以设置Group属性以指示哪些项目属于一起,并且应该表现为无线电组.
using System;
using System.Windows.Forms;
using System.Windows.Forms.Design;
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.MenuStrip | ToolStripItemDesignerAvailability.ContextMenuStrip)]
public class ToolStripRadioItem : ToolStripMenuItem {
public int Group { get; set; }
protected override void OnClick(EventArgs e) {
if (!this.DesignMode) {
this.Checked = true;
var parent = this.Owner as ToolStripDropDownMenu;
if (parent != null) {
foreach (var item in parent.Items) {
var sibling = item as ToolStripRadioItem;
if (sibling != null && sibling != this and sibling.Group == this.Group) sibling.Checked = false;
}
}
}
base.OnClick(e);
}
}
Run Code Online (Sandbox Code Playgroud)