WPF 中的菜单访问键

发布于 2024-11-01 09:46:15 字数 320 浏览 3 评论 0原文

我有一个带有指定访问键的经典菜单。

问题: 使用按 Alt+E(编辑菜单),然后在按住 Alt 的同时按 F。他期望子菜单编辑 ->将选择表单,但打开上层菜单“文件”。

如果他释放Alt - 一切都会好起来的。

同时,Visual Studio 在这种情况下的行为绝对正确。

有什么想法吗?

谢谢!

更新: 我有一种感觉 VS 使用 AccessKeyManager 作用域。

I have a classic menu with assigned access keys.

Problem:
Use presses Alt+E (Edit menu), then while holding Alt he presses F. He expects that submenu Edit -> Form will be selected, but instead upper level menu File opens.

If he releases Alt - everything will be ok.

At the same time Visual Studio behaves absolutely correctly in this situation.

Any ideas ?

Thanks!

upd:
I have a feeling that VS uses AccessKeyManager scoping.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

断念 2024-11-08 09:46:15

请参阅我的博文:
http://codeelief.net/2012/07/29/wpf- access-keys-scoping/

让我知道这是否适合您,它已经为我纠正了问题。

using System.Windows.Media;

namespace System.Windows.Input
{
    /// <summary>
    /// Contains attached dependency properties to correct the scoping of access keys
    /// within the WPF framework.
    /// </summary>
    public static class AccessKeysManagerScoping
    {
        /// <summary>
        /// Attached dependency property to enable or disable scoping of access keys.
        /// </summary>
        public static readonly DependencyProperty IsEnabledProperty
            = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool),
            typeof(AccessKeysManagerScoping), new PropertyMetadata(false, OnIsEnabledChanged));

        /// <summary>
        /// Gets the value of the <see cref="F:IsEnabledProperty"/> attached
        /// dependency property for a given dependency object.
        /// </summary>
        /// <param name="d">The dependency object.</param>
        /// <returns>Returns the attached dependency property value.</returns>
        [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
        public static bool GetIsEnabled(DependencyObject d)
        {
            return (bool)d.GetValue(IsEnabledProperty);
        }

        /// <summary>
        /// Sets the value of the <see cref="F:IsEnabledProperty"/> attached
        /// dependency property for a given dependency object.
        /// </summary>
        /// <param name="d">The dependency object.</param>
        /// <param name="value">The value.</param>
        public static void SetIsEnabled(DependencyObject d, bool value)
        {
            d.SetValue(IsEnabledProperty, value);
        }

        private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d == null)
                return;

            if ((bool)e.NewValue)
                AccessKeyManager.AddAccessKeyPressedHandler(d, new AccessKeyPressedEventHandler(HandleAccessKeyPressed));
            else
                AccessKeyManager.RemoveAccessKeyPressedHandler(d, new AccessKeyPressedEventHandler(HandleAccessKeyPressed));
        }

        /// <summary>
        /// Fixes access key scoping bug within the WPF framework.
        /// </summary>
        /// <param name="sender">Potential target of the current access keys.</param>
        /// <param name="e">
        /// Info object for the current access keys and proxy to effect it's confirmation.
        /// </param>
        /// <remarks>
        /// The problem is that all access key presses are scoped to the active window,
        /// regardless of what properties, handlers, scope etc. you may have set. Targets
        /// are objects that have potential to be the target of the access keys in effect.
        /// 
        /// If you happen to have a current object focused and you press the access keys
        /// of one of it's child's targets it will execute the child target. But, if you
        /// also have a ancestor target, the ancestor target will be executed instead.
        /// That goes against intuition and standard Windows behavior.

        /// The root of this logic (bug) is within the HwndSource.OnMnemonicCore method.
        /// If the scope is set to anything but the active window's HwndSource, the
        /// target will not be executed and the handler for the next target in the chain
        /// will be called.

        /// This handler gets called for every target within the scope, which because
        /// of the bug is always at the window level of the active window. If you set
        /// e.Handled to true, no further handlers in the chain will be executed. However
        /// because setting the scope to anything other than active window's HwndSource
        /// causes the target not to be acted on, we can use it to not act on the target
        /// while not canceling the chain either, thereby allowing us to skip to the next
        /// target's handler. Note that if a handler does act on the target it will
        /// inheritably break the chain because the menu will lose focus and the next
        /// handlers won't apply anymore; because a target has already been confirmed.

        /// We will use this knowledge to resolve the issue.
        /// We will set the scope to something other than the active window's HwndSource,
        /// if we find that the incorrect element is being targeted for the access keys
        /// (because the target is out of scope). This will cause the target to be
        /// skipped and the next target's handler will be called.

        /// If we detect the target is correct, we'll just leave everything alone so the
        /// target will be confirmed.
        /// 
        /// NOTE: Do not call AccessKeyManager.IsKeyRegistered as it will cause a
        /// <see cref="T:System.StackOverflowException"/> to be thrown. The key is
        /// registered otherwise this handler wouldn't be called for it, therefore
        /// there is no need to call it.
        /// </remarks>
        private static void HandleAccessKeyPressed(object sender, AccessKeyPressedEventArgs e)
        {
            FrameworkElement focusedElement = Keyboard.FocusedElement as FrameworkElement;
            if (focusedElement == null)
                return; // No focused element.

            if (sender == focusedElement)
                return; // This is the correct target.

            // Look through descendants tree to see if this target is a descendant of
            // the focused element. We will stop looking at either the end of the tree
            // or if a object with multiple children is encountered that this target
            // isn't a descendant of.

            // If no valid target is found, we'll set the scope to the sender which
            // results in skipping to the next target handler in the chain
            // (due to the bug).

            DependencyObject obj = focusedElement as DependencyObject;
            while (obj != null)
            {
                int childCount = VisualTreeHelper.GetChildrenCount(obj);
                for (int i = 0; i < childCount; i++)
                {
                    if (VisualTreeHelper.GetChild(obj, i) == sender)
                        return; // Found correct target; let it execute.
                }

                if (childCount > 1)
                {
                    // This target isn't a direct descendant and there are multiple
                    // direct descendants; skip this target.
                    e.Scope = sender;
                    return;
                }
                else if (childCount == 1)
                {
                    // This target isn't a direct descendant, but we'll keep looking
                    // down the descendants chain to see if it's a descendant of the
                    // direct descendant.
                    obj = VisualTreeHelper.GetChild(obj, 0) as DependencyObject;
                }
                else
                {
                    // End of the line; skip this target.
                    e.Scope = sender;
                    return;
                }
            }
        }
    }
}

See my blog post:
http://coderelief.net/2012/07/29/wpf-access-keys-scoping/

Let me know if this works for you, it has corrected the issue for me.

using System.Windows.Media;

namespace System.Windows.Input
{
    /// <summary>
    /// Contains attached dependency properties to correct the scoping of access keys
    /// within the WPF framework.
    /// </summary>
    public static class AccessKeysManagerScoping
    {
        /// <summary>
        /// Attached dependency property to enable or disable scoping of access keys.
        /// </summary>
        public static readonly DependencyProperty IsEnabledProperty
            = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool),
            typeof(AccessKeysManagerScoping), new PropertyMetadata(false, OnIsEnabledChanged));

        /// <summary>
        /// Gets the value of the <see cref="F:IsEnabledProperty"/> attached
        /// dependency property for a given dependency object.
        /// </summary>
        /// <param name="d">The dependency object.</param>
        /// <returns>Returns the attached dependency property value.</returns>
        [AttachedPropertyBrowsableForType(typeof(DependencyObject))]
        public static bool GetIsEnabled(DependencyObject d)
        {
            return (bool)d.GetValue(IsEnabledProperty);
        }

        /// <summary>
        /// Sets the value of the <see cref="F:IsEnabledProperty"/> attached
        /// dependency property for a given dependency object.
        /// </summary>
        /// <param name="d">The dependency object.</param>
        /// <param name="value">The value.</param>
        public static void SetIsEnabled(DependencyObject d, bool value)
        {
            d.SetValue(IsEnabledProperty, value);
        }

        private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d == null)
                return;

            if ((bool)e.NewValue)
                AccessKeyManager.AddAccessKeyPressedHandler(d, new AccessKeyPressedEventHandler(HandleAccessKeyPressed));
            else
                AccessKeyManager.RemoveAccessKeyPressedHandler(d, new AccessKeyPressedEventHandler(HandleAccessKeyPressed));
        }

        /// <summary>
        /// Fixes access key scoping bug within the WPF framework.
        /// </summary>
        /// <param name="sender">Potential target of the current access keys.</param>
        /// <param name="e">
        /// Info object for the current access keys and proxy to effect it's confirmation.
        /// </param>
        /// <remarks>
        /// The problem is that all access key presses are scoped to the active window,
        /// regardless of what properties, handlers, scope etc. you may have set. Targets
        /// are objects that have potential to be the target of the access keys in effect.
        /// 
        /// If you happen to have a current object focused and you press the access keys
        /// of one of it's child's targets it will execute the child target. But, if you
        /// also have a ancestor target, the ancestor target will be executed instead.
        /// That goes against intuition and standard Windows behavior.

        /// The root of this logic (bug) is within the HwndSource.OnMnemonicCore method.
        /// If the scope is set to anything but the active window's HwndSource, the
        /// target will not be executed and the handler for the next target in the chain
        /// will be called.

        /// This handler gets called for every target within the scope, which because
        /// of the bug is always at the window level of the active window. If you set
        /// e.Handled to true, no further handlers in the chain will be executed. However
        /// because setting the scope to anything other than active window's HwndSource
        /// causes the target not to be acted on, we can use it to not act on the target
        /// while not canceling the chain either, thereby allowing us to skip to the next
        /// target's handler. Note that if a handler does act on the target it will
        /// inheritably break the chain because the menu will lose focus and the next
        /// handlers won't apply anymore; because a target has already been confirmed.

        /// We will use this knowledge to resolve the issue.
        /// We will set the scope to something other than the active window's HwndSource,
        /// if we find that the incorrect element is being targeted for the access keys
        /// (because the target is out of scope). This will cause the target to be
        /// skipped and the next target's handler will be called.

        /// If we detect the target is correct, we'll just leave everything alone so the
        /// target will be confirmed.
        /// 
        /// NOTE: Do not call AccessKeyManager.IsKeyRegistered as it will cause a
        /// <see cref="T:System.StackOverflowException"/> to be thrown. The key is
        /// registered otherwise this handler wouldn't be called for it, therefore
        /// there is no need to call it.
        /// </remarks>
        private static void HandleAccessKeyPressed(object sender, AccessKeyPressedEventArgs e)
        {
            FrameworkElement focusedElement = Keyboard.FocusedElement as FrameworkElement;
            if (focusedElement == null)
                return; // No focused element.

            if (sender == focusedElement)
                return; // This is the correct target.

            // Look through descendants tree to see if this target is a descendant of
            // the focused element. We will stop looking at either the end of the tree
            // or if a object with multiple children is encountered that this target
            // isn't a descendant of.

            // If no valid target is found, we'll set the scope to the sender which
            // results in skipping to the next target handler in the chain
            // (due to the bug).

            DependencyObject obj = focusedElement as DependencyObject;
            while (obj != null)
            {
                int childCount = VisualTreeHelper.GetChildrenCount(obj);
                for (int i = 0; i < childCount; i++)
                {
                    if (VisualTreeHelper.GetChild(obj, i) == sender)
                        return; // Found correct target; let it execute.
                }

                if (childCount > 1)
                {
                    // This target isn't a direct descendant and there are multiple
                    // direct descendants; skip this target.
                    e.Scope = sender;
                    return;
                }
                else if (childCount == 1)
                {
                    // This target isn't a direct descendant, but we'll keep looking
                    // down the descendants chain to see if it's a descendant of the
                    // direct descendant.
                    obj = VisualTreeHelper.GetChild(obj, 0) as DependencyObject;
                }
                else
                {
                    // End of the line; skip this target.
                    e.Scope = sender;
                    return;
                }
            }
        }
    }
}
蔚蓝源自深海 2024-11-08 09:46:15

不幸的是,WPF 在这方面与 Win32 应用程序有不同的行为。我也遇到过这个问题,MSDN 论坛上的其他一些问题也遇到过:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/91e031b7-091f-449d-9af2-f5fc3a071a45/

http://social. msdn.microsoft.com/Forums/en-US/wpf/thread/74f978b7-f445-4f4a-8416-57b38e04cb63/

我个人认为这是WPF中的一个错误,因为它没有多大意义。

Unfortunately WPF has different behaviour to Win32 applications in this regard. I've come across this problem as well, as have a few others on the MSDN forums:

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/91e031b7-091f-449d-9af2-f5fc3a071a45/

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/74f978b7-f445-4f4a-8416-57b38e04cb63/

Personally I think this is a bug in WPF, as it doesn't make a lot of sense.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文