{A}
{S0}简介
本文介绍了一种可以在WinForms应用程序使用的组件,并允许用户自定义菜单快捷方式。
很多残疾人士在使用他们的手麻烦,从而有麻烦,同时按下几个键。一个解决方案,在这里可以是一个应用程序可以自定义菜单快捷方式。例如,按Ctrl O打开一个文件,而不是(禁用)用户可以分配F2键。背景
基本的想法是创建一个组件,它是易于使用。在WinForms应用程序,这部分需要用户代码只有两行,得到它的工作。定制快捷方式保存在user.config(即使当应用程序本身并不具备之一)。
这个项目的目的是帮助生产,是无障碍的软件!使用该组件
要使用的组件,它拖到你想允许用户自定义菜单快捷方式的形式。组件的默认名称,然后customizeMenuShortCuts1。
组件提供几种语言选择:DE,EN,ES,FR,PT,RU(大多数语言翻译使用谷歌翻译,如果有任何错误,请让我知道)。
要加载user.config文件的快捷方式,使用此代码在Form_Load事件:private void Form1_Load(object sender, System.EventArgs e)
{
customizeMenuShortCuts1.LoadCustomShortCuts(menuStrip1);
}
做的定制也很简单:{C}实施
这篇文章是基于我先前关于此主题的文章(见{A2})。因此,不是每一个细节,在这里解释,因为他们已经对我以前的文章(代码是很好的注释太)。用户界面
组件的主要组成部分,是一种定制的用户界面。在加载的形式,一个TreeView充满菜单结构。快捷方式只能被分配到ToolStripMenuItems,因此每个项目的检查。private void FillTreeView(TreeNodeCollection nodes, ToolStripItemCollection items)
{
foreach (ToolStripItem item in items)
{
ToolStripMenuItem menuItem = item as ToolStripMenuItem;
// Check for ToolStripMenuItem
if (menuItem != null)
{
TreeNode tNode = new TreeNode();
tNode.Text = menuItem.Text.Replace("&", string.Empty);
tNode.Tag = menuItem;
tNode.ImageKey = "Sub";
nodes.Add(tNode);
// Add the shortcut to the list for checking if a
// shortcut already exists:
if (menuItem.ShortcutKeys != Keys.None)
_assignedShortCuts.Add(menuItem.ShortcutKeys);
// Recursion needed?
if (menuItem.HasDropDownItems)
{
tNode.ImageKey = "Main";
FillTreeView(tNode.Nodes, menuItem.DropDownItems);
}
}
}
}
在上面的片段,每一个快捷方式添加到一个通用的哈希表(。NET 3.5中的一个新功能),这就是后来用于检查如果已经被分配一个快捷。
当选择一个TreeView的节点(即一个菜单项),实际分配的快捷方式设置的形式:private void tvMenu_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
// Get selected item:
_menuItem = e.Node.Tag as ToolStripMenuItem;
// Check if the selected item is a parent for other toolstrip-items.
// If so no shortcut can be assigned:
if (_menuItem.HasDropDownItems)
{
MessageBox.Show(
MyRessource.ShortCutNotPossible,
Application.ProductName,
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
return;
}
// Enable editing:
groupBox1.Enabled = true;
// Set the actually assigned shortcut. The shortcut is a
// combination of different Keys so they're bit-masked:
Keys shortCut = _menuItem.ShortcutKeys;
chkCtrl.Checked = (shortCut & Keys.Control) == Keys.Control;
chkAlt.Checked = (shortCut & Keys.Alt) == Keys.Alt;
chkShift.Checked = (shortCut & Keys.Shift) == Keys.Shift;
// To get the letter or the F-key we have to eliminate the
// modifyers. This is done by combining all set modifiers
// and then by removing them from the shortcut with XOR.
// Example: shortCut = 111000101
// Ctrl = 111000000 XOR
// -----------------------
// Letter = 000000101
Keys modifiers = Keys.None;
if (chkCtrl.Checked) modifiers |= Keys.Control;
if (chkAlt.Checked) modifiers |= Keys.Alt;
if (chkShift.Checked) modifiers |= Keys.Shift;
Keys buchstabe = shortCut ^ modifiers;
// Select the value in the combobox:
cmbKeys.SelectedValue = buchstabe;
}
用户修改/定制快捷方式后,我们已经申请:
按键名单扩展设计器生成的设置private void btnApply_Click(object sender, EventArgs e)
{
// Applying happens in a try-catch-block. When there's a faulty
// input the shortcut is removed.
// (Maybe there's a better approach - but in the moment I'm to
// lazy for that;-)
try
{
// When no letter selected in the combobox remove the shortcut:
if (cmbKeys.SelectedIndex == -1)
throw new Exception();
// Create the shortcut:
Keys shortCut = (Keys)cmbKeys.SelectedValue;
if (chkCtrl.Checked) shortCut |= Keys.Control;
if (chkAlt.Checked) shortCut |= Keys.Alt;
if (chkShift.Checked) shortCut |= Keys.Shift;
// Check if the shortcut exists:
if (_assignedShortCuts.Contains(shortCut))
{
MessageBox.Show(
MyRessource.ShortCutAlreadyAssigned,
Application.ProductName,
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
return;
}
// Manage the list of assigned shortcuts:
Keys oldShortCut = _menuItem.ShortcutKeys;
if (shortCut != oldShortCut)
_assignedShortCuts.Remove(oldShortCut);
// Assign the new shortcut:
_menuItem.ShortcutKeys = shortCut;
}
catch
{
_menuItem.ShortcutKeys = Keys.None;
}
finally
{
// Disable editing:
groupBox1.Enabled = false;
}
}
要坚持快捷方式,我使用Visual Studio生成的默认的设计师设置和扩展它们来存储一个通用的列表。
该列表中的项目定义如下。请注意,它必须有Serializable属性,否则,它不能被保存的user.config。[Serializable()]
internal sealed class UserConfigEntry
{
public string Text { get; set; }
public Keys ShortCut { get; set; }
}
因为设计师不支持泛型列表,我只使用生成的代码,是用于访问配置的设计师。存储列表,即序列列表,使用下面的代码:/// <summary>
/// Extends the designer generated Settings so that a
/// custom list can be persisted.
/// </summary>
partial class Settings
{
/// <summary>
/// ShortCuts for the menus
/// </summary>
/// <remarks>
/// Is serialized as base64-string
/// </remarks>
[UserScopedSetting()]
[SettingsSerializeAs(SettingsSerializeAs.Binary)]
[DefaultSettingValue("")]
public List<UserConfigEntry> UserConfigEntries
{
get { return (List<UserConfigEntry>)this["UserConfigEntries"]; }
set { this["UserConfigEntries"] = value; }
}
}
正如你可以看到,序列化使用二进制格式化完成,虽然可以通过其他方法进行序列化。组件
实现一个组件是指一个类从基类组件的派生:
加载的快捷方式,并将其分配给[ToolboxBitmap(typeof(CustomizeMenuShortCuts))]
[Description("Allows the user to customize menu shortcuts")]
public sealed class CustomizeMenuShortCuts : Component
{
...
}
public void LoadCustomShortCuts(MenuStrip menuStrip)
{
// Get the list from the user.config:
List<UserConfigEntry> userList =
Properties.Settings.Default.UserConfigEntries;
if (userList.Count == 0) return;
// Create a list of the menu-items:
List<ToolStripItem> menuList = MenuToList(menuStrip.Items);
// Assign the shortcuts from the user.config to the menulist.
// It can't be assumed that the list from the user.config has
// the same length as the list from the menu-items, because
// there might be changes in the menuStrip (due to updates, etc.)
// Therefore the list from the menu-items gets iterated and
// for each item the proper item in the list from the
// user.config is searched (the variant with the loops is the
// fastest in comparison with LINQ and binary search).
foreach (ToolStripItem menuEntry in menuList)
{
ToolStripMenuItem menuItem = menuEntry as ToolStripMenuItem;
if (menuItem == null) break;
// Search:
foreach (UserConfigEntry userEntry in userList)
if (userEntry.Text == menuItem.Text)
{
// Found -> assign shortcur:
menuItem.ShortcutKeys = userEntry.ShortCut;
break;
}
}
}
因此,一个私有方法是使用:
为定制的方法private List<ToolStripItem> MenuToList(ToolStripItemCollection items)
{
List<ToolStripItem> list = new List<ToolStripItem>();
// Run throug all items:
foreach (ToolStripItem item in items)
{
ToolStripMenuItem menuItem = item as ToolStripMenuItem;
// Check if it's a ToolStripMenuItem (i.e. no seperator):
if (menuItem != null)
{
// Add to list:
list.Add(menuItem);
// Recursion needed?
if (menuItem.HasDropDownItems)
list.AddRange(MenuToList(menuItem.DropDownItems));
}
}
return list;
}
public void CustomizeShortCuts(MenuStrip menuStrip)
{
// Show form that allows for customization:
frmMain frmMain = new frmMain(menuStrip);
frmMain.ShowDialog();
// No we will persist the shortcuts of the menuStrip:
List<ToolStripItem> menuList = MenuToList(menuStrip.Items);
Properties.Settings.Default.UserConfigEntries =
new List<UserConfigEntry>(menuList.Count);
// Iterate over all menu-items
foreach (ToolStripItem item in menuList)
{
ToolStripMenuItem menuItem = item as ToolStripMenuItem;
// Separators, for instance, won't be a ToolStripMenuItem
// so check if it isn't null:
if (menuItem == null) break;
Properties.Settings.Default.UserConfigEntries.Add(
new UserConfigEntry
{
Text = menuItem.Text,
ShortCut = menuItem.ShortcutKeys
});
}
// The settings from a classlibrary have to be saved here,
// otherwise they will be lost:
Properties.Settings.Default.Save();
}
注意:重要的是这里保存设置(在这个类库),因为否则,设置不会被保存到user.config。兴趣点扩展设计器生成的设置。一个通用的列表存储在user.config。谢谢
我感谢{A3}启发我写这个代码和文章。特别是他的想法制作软件无障碍。
历史2008年10月21日:首次发布。