6.处理自定义控件热键

不知道大家平时有没有注意到 这样的一些现象 在使用一些程序的时候 比如:VS 当按下ALT键的时候 会发现一些神奇的效果 比如:


上面的菜单栏会出现很多下划线而此时点击下划线标出的那些字母 等同于点击了那个菜单

在比如一些其他的程序 如Win+R出来的运行窗口:


上面同样能看到一些下划线标注的字母 不过此时点击ALT+B等同于点下了【Browse...】按钮 就会弹出一个窗口让你选择程序运行 而如果点击ALT+O则 那个文本框会得到焦点并且选中所有文本

其实要完成这样的效果 在程序中是非常简单的 在控件的Text属性 里面的字符前加上&就能搞定了 比如:

此时看到的两个button他们的Text属性分别是【&A】和【&B】 这个非常简单、下面要会说的是怎么在自定义控件中完成这样的功能

首先创建一个自定义控件 代码如下:

public class HotKeyControl : Control
{
    public override string Text {
        get { return base.Text; }
        set {
            if (value == base.Text) return;
            base.Text = value;
            this.Invalidate();
        }
    }

    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        StringFormat sf = new StringFormat();
        sf.Alignment = StringAlignment.Center;
        sf.LineAlignment = StringAlignment.Center;
        using(SolidBrush sb = new SolidBrush(this.ForeColor)){
            g.DrawString(this.Text, this.Font, sb, this.ClientRectangle, sf);
        }
        g.DrawRectangle(Pens.DarkCyan, 0, 0, this.Width - 1, this.Height - 1);
        base.OnPaint(e);
    }
}
控件依然和以前一样绘制一个边框而已 不过这次多了绘制文本的代码 这里的文本直接用的控件的Text属性 因为没有必要去自己再定义一个属性出来 不过要注意的是 记得重写一下Text属性 要不然你Text属性的值变化的时候 界面上你可能看不到啥变化

YY一下 按理来说一个控件的Text属性发生变化 应该会自动去通知窗体(控件)重绘 因为通常情况下控件的Text属性都发生了变化 也就意味着 估计界面上会发生变化 但是这里的Text属性 默认是没有被拿来使用的(你有见到你直接创建一个自定义控件继承Control啥也不做 添加到窗体上 控件上显示了Text属性了么) 所以对于这里的控件来说就算Text发生改变也不会去通知窗体(控件)重绘 因为微软压根就不知道 你继承控件的时候 会不会用到Text这个属性都还不知道 没有必要自作多情的让Text发生改变了 就去通知窗体(控件重绘) 但是如果是其他控件也许就不一样了 比如你继承Button的时候 当Text发生改变的 时候会自己去通知重绘 所以如果你是继承的Button的话 你不需要重新Text属性然后去通知重绘 在比如窗体 因为窗体也有用到Text属性 不过不同的是窗体的Text是在非客户区 所以无论你在Paint里面怎么等等你也等待不到Text重绘的通知 Paint默认是处理窗体客户区的 如果要处理非客户区的话要override WndProc 然后去获取WM_NCXXX系列的消息 比如WM_NCPAINT(- -!、、扯远了 以上言论纯属个人YY仅供参考)

这样的话你就能看到这样的效果了:

当你改变Text属性的时候确实能看到 变化了 但是即使你把Text设置成【&C】他也直接就显示了 没有像上面的AB那样 这是正常的 因为代码本来就是直接绘制的Text的文本啊、、但是 如果对StringFormat进行一下设置就不一样了:

protected override void OnPaint(PaintEventArgs e) {
    Graphics g = e.Graphics;
    StringFormat sf = new StringFormat();
    sf.Alignment = StringAlignment.Center;
    sf.LineAlignment = StringAlignment.Center;
    //加上这句
    sf.HotkeyPrefix = this.ShowKeyboardCues ? HotkeyPrefix.Show : HotkeyPrefix.Hide;
    using(SolidBrush sb = new SolidBrush(this.ForeColor)){
        g.DrawString(this.Text, this.Font, sb, this.ClientRectangle, sf);
    }
    g.DrawRectangle(Pens.DarkCyan, 0, 0, this.Width - 1, this.Height - 1);
    base.OnPaint(e);
}
这样你就能看到这样的效果了:

如果HotkeyPrefix是Show的话 那么在热键状态下绘制文本的时候会把&之后的第一个字符加上下划线而&则不显示 而如果是Hide不会显示下划线&也同样不会显示 这个值还有一个状态是None 默认情况下是None 效果就会是像第一次的代码那样不做任何处理

(注意:无论你对HotKeyPrefix赋怎样的值都不会影响ALT+C的效果 因为这个值只是觉得在绘制过程中的长相而已 当然前提是你要处理了热键的点击)

而ShowKeyboardCues则和上一篇焦点里面的ShowFocusCues效果一样 这个值来决定当前是否应该显示提示 比如 默认情况下 这样运行程序ABC是不会有下划线的 而只有当用户点下ALT的时候才会显示 就和上一篇焦点一样 默认是不会显示虚线框的 当用户使用Tab切换的时候才会显示

现在分别为这三个控件的Click事件写上MessageBox.Show A B C 然后运行 你会发现ALT+A和ALT+B都能正确的弹出A和B 只有自定义的控件没有 这个同样和上一篇的焦点一样 当控件得到焦点的时候 回车或者换行 怎么的要自己去处理 为什么要自己去处理 那是因为 微软压根就不知道你继承这个Control是要去写啥控件 是一个按钮?文本框?列表框?所以他不会自作多情的去帮你处理

如果是是一个按钮而言 我们通常的理解当然是如果点下ALT+C希望他去执行Click事件 那么写上如下代码:

protected override bool ProcessMnemonic(char charCode) {
    if (this.Enabled && this.Visible && IsMnemonic(charCode, this.Text)) {
        this.OnClick(new EventArgs());
    }
    return base.ProcessMnemonic(charCode);
}
注意:这里虽然是override 但那东西不是一个事件 这个方法是干啥的?当进入热键状态后用户按下一个键的时候 那么窗体就会去检查每个控件是否按下了对应控件的热键 检查的方式就是调用控件的这个方法去检查 然而用户所按下的键则通过charCode传进来 此时就要判断是否是点下了当前控件的热键 如果是的话那么就调用一下Click事件 那个IsMnemonic方法就是来判断 是否是按下的当前控件的热键 第一个参数是用户按下的键 如果用户按下的是ALT+A那么那个charCode就是a 你就当那个方法这样去理解吧:
return this.Text.IndexOf("&" + charCode.ToString()) != -1;

这个是反编译出来的IsMnemonic的代码:

public static bool IsMnemonic(char charCode, string text) {
    if (charCode == '&') return false;
    if (text != null) {
        char ch2;
        int num = -1;
        char c = char.ToUpper(charCode, CultureInfo.CurrentCulture);
        do {
            if ((num + 1) >= text.Length) {
                goto Label_006E;
            }
            num = text.IndexOf('&', num + 1) + 1;
            if ((num <= 0) || (num >= text.Length)) {
                goto Label_006E;
            }
            ch2 = char.ToUpper(text[num], CultureInfo.CurrentCulture);
        }
        while ((ch2 != c) && 
            (char.ToLower(ch2, CultureInfo.CurrentCulture) 
            != char.ToLower(c, CultureInfo.CurrentCulture)));
        return true;
    }
Label_006E:
    return false;
}

还有前面两个判断不用解释你也知道是啥意思 要注意 这个和焦点有点不一样 焦点在控件禁用或者不显示的时候是不会得到焦点的 但是热键就不一样了 即使控件禁用或者没有显示 你点下ALT+C也是有效的所以你还需要判断Enable

现在你运行程序点下ALT+C就能看到提示框了 不过代码像刚才上面那么些 看上去是没啥问题 其实是有问题的 啥问题 如果你再向窗体添加一个自定义控件并且Text设置成【&C1】然后Click事件里面MessageBox C1

如果这个时候你点下ALT+C会这样? 很明确的告诉你会弹出两个框 分别是C和C1

如果你就是想要这样的效果 要不然就像上面那样写代码是会有问题的

再来回想一下刚才所说的 "当用户按下一个键的时候 那么窗体就会去检查每个控件是否按下了对应控件的热键 然后就会调用控件的ProcessMnemonic方法去检查 然而用户所按下的键则通过charCode传进来" 也就是当你无论按下ALT+X(X代表任意按键)的时候 就会去检查每个控件是否按下了对应的热键如果你写上这样的代码看看或许会明白:

protected override bool ProcessMnemonic(char charCode) {
    MessageBox.Show(charCode.ToString());
    return base.ProcessMnemonic(charCode);
}
不管按下的是不是ALT+C都会弹框出来 而弹出来的就是ALT+X的X 举个例子就好比 假设我是窗体 我现在有很多按钮 当用户按下ALT+C的时候 我就挨个的去问那些按钮的ProcessMnimonic方法然后把C给他 问他这个按键是不是点的你?如果他说不是 那好我继续问下一个 如果下一个说 是的就是点的我 那么窗体知道 哦 原来点的是你 然后就不继续问了

然而上面刚才的两个就好比问了C他没有回答说 是的点的就是我 所以继续找找到了C1 由于控件都是同样的代码所以 C1还是不老实 所没有 其实他们两个都被点了 然后窗体还在哪里傻不拉唧的继续找 点的是那个、、

那么重点是 要怎么告诉窗体说 说点的就是自己?看看ProcessMnemonic的返回值就知道了bool 由于刚才一直是在后面:

return base.ProcessMnemonic(charCode);
而base里面默认又是返回的false 所以就悲剧了 窗体就是根据这个返回值来判断是不是找到了

所以把代码改成这样就可以了:

protected override bool ProcessMnemonic(char charCode) {
    if (this.Enabled && this.Visible && IsMnemonic(charCode, this.Text)) {
        this.OnClick(new EventArgs());
        return true;
    }
    return base.ProcessMnemonic(charCode);
}

当确定点的是自己的时候 返回true否则base当然 你下面也可以直接写retur false 看你、、、

其实上面说的是窗体去检查是我猜的 、、、 仅供参考 自行判断、、、

关于热键问题 差不多就到这里了 哦对了那个&不一定就非得在字符的开始 也可以中间的某个位置 就如上面第一副截图VS里面的那种效果一样、、还有或许你就想要在你的Text属性里面加上&字符 而不是作为热键那么写两个&就些可以了 比如【&&C】...


添加时间:2014-03-31 00:20:39 编辑时间:2016-11-07 14:17:35 阅读:877 
C#自定义控件开发 C#控件开发
还没有人留言 要不你来抢一个沙发?
  • 编写评论

      我觉得区分大小写是一个码农的基本素质
[访问统计] 今天:60 总数:147833 提示:未成年人 请在大人陪同下浏览本站内容 还有:世界上最帅的码农 -> 石头 RSS:http://st233.com/rss Powered by -> Crystal_lz