从菜单启动时,InputManager 会忽略剪切/复制/粘贴

发布于 2024-09-05 00:32:52 字数 1411 浏览 6 评论 0原文

我正在使用 InputManager 来检查对控件的更改是否是由用户或代码完成的。这工作正常,除非用户使用上下文菜单进行剪切/复制/粘贴。如果用户在文本框中执行 ctrl+v,InputManager 会正确注意到它。但是,如果从文本框的上下文菜单完成粘贴,则 InputManager 永远不会触发 PreNotifyInput 或 PostNotifyInput 事件。有人知道为什么吗?或者如何检测这些用户操作?下面是一个工作示例。当用户使用上面文本框中的剪切/复制/粘贴菜单时,下部文本块永远不会更新,因为 PreNotifyInput 永远不会触发。

XAML:

<Window x:Class="InputMgrDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <StackPanel>
        <TextBox TextChanged="TextBox_TextChanged" />
        <TextBlock Name="_text" />
    </StackPanel>
</Window>

隐藏代码:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace InputMgrDemo
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            InputManager.Current.PreNotifyInput += ((sender, e) => _userInput = true);
            InputManager.Current.PostNotifyInput += ((sender, args) => _userInput = false);
        }

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (_userInput)
            {
                _text.Text = (sender as TextBox).Text;
            }
        }

        private bool _userInput;
    }
}

I'm using the InputManager to check if changes to controls are done by user or code. This works fine, except when user uses the context menu for cut/copy/paste. If the user do ctrl+v in a textbox, InputManager correctly notices it. However, if the paste is done from the context menu of the textbox, the InputManager never fires the PreNotifyInput or PostNotifyInput events. Anyone knows why? Or how to detect that these user actions? Below is a working sample. The lower textblock never gets updated when user uses the cut/copy/paste menu in the above textbox since PreNotifyInput never fires.

XAML:

<Window x:Class="InputMgrDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <StackPanel>
        <TextBox TextChanged="TextBox_TextChanged" />
        <TextBlock Name="_text" />
    </StackPanel>
</Window>

Code behind:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace InputMgrDemo
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            InputManager.Current.PreNotifyInput += ((sender, e) => _userInput = true);
            InputManager.Current.PostNotifyInput += ((sender, args) => _userInput = false);
        }

        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (_userInput)
            {
                _text.Text = (sender as TextBox).Text;
            }
        }

        private bool _userInput;
    }
}

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

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

发布评论

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

评论(1

悲歌长辞 2024-09-12 00:32:52

实际上,PreNotifyInput 事件确实在 MouseLeftButtonUp 事件上触发,但 PostNotifyInput 在实际粘贴发生之前触发。

以下是操作顺序:

  • 用户在菜单项上释放鼠标按钮
  • 调用 PreNotifyInput 事件处理程序
  • 引发 MouseLeftButtonUp 事件,该事件向上冒泡到 MenuItem
  • MenuItem 处理 MouseButtonUp 并将其转换OnClick
  • OnClick 引发 PreviewClickEvent,然后调度调度程序回调以引发 Click 事件并执行命令
  • 由于处理了 MouseLeftButtonUp 事件,因此调用 PostNotifyInput 事件处理程序

  • 由 Dispatcher 安排的任何渲染完成

  • Dispatcher 调用 MenuItem 中的回调

    p>

  • MenuItem 触发 Click 事件,该事件不执行任何操作
  • MenuItem 执行粘贴命令
  • TextBox 处理粘贴命令并粘贴数据
  • TextBox 触发 TextChanged 事件

在 WPF 中,“的效果用户输入”可以通过调度程序回调等任意延迟,因此您无法知道更改是否是由于用户输入引起的。

事实上,从理论上讲,这通常是正确的。考虑以下场景:

  1. 用户单击应用程序中其他位置的按钮,这会导致加载新数据,从而更新您的值。
  2. 用户单击另一个应用程序中的按钮来写入文件,从而使您的应用程序刷新并显示新数据。
  3. 用户走到另一台计算机并更新某处网站上的一些数据。您的应用程序正在监视该网站并检测更改。

显然,在每种情况下,更改都是由用户输入引起的;-) 你明白我的意思了吗?从哲学上讲,没有根本的方法来确定更改是由您的用户还是其他人进行的。

如果您真正想要的是“用户在此应用程序中单击鼠标或使用键盘的时间与应用程序空闲的时间之间发生的任何更改”,您可以实现此功能:

InputManager.Current.PreNotifyInput += (sender, e) =>
{
  _userInput = true;
  Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
  {
    _userInput = false;
  }));
}; 

但在这种情况下,如果您有数据传入从外部源动态输入的内容可能会被错误地视为用户输入。

另一种方法是将你的方法颠倒过来:每当你从外部数据源刷新数据时,设置一个标志来表明你正在这样做。然后,每当您看到未设置该标志的更改时,您都会认为这是用户交互。如果您可以确保所有外部数据更新都发生在 DispatcherPriority.Render 之上,那么这可能会更容易实现。

Actually the PreNotifyInput event does fire on the MouseLeftButtonUp event, but then the PostNotifyInput fires before the actual paste occurs.

Here is the sequence of operations:

  • The user releases the mouse button on the menu item
  • Your PreNotifyInput event handler is called
  • The MouseLeftButtonUp event is raised, which bubbles up to the MenuItem
  • The MenuItem handles the MouseButtonUp and converts it to OnClick
  • The OnClick raises a PreviewClickEvent, then schedules a dispatcher callback to raise the Click event and execute the command
  • Your PostNotifyInput event handler is called since the MouseLeftButtonUp event is handled

  • Any rendering scheduled by the Dispatcher completes

  • The Dispatcher invokes the callback in MenuItem

  • MenuItem fires the Click event, which does nothing
  • MenuItem executes the Paste command
  • TextBox handles the Paste command and pastes the data
  • TextBox fires the TextChanged event

In WPF the effects of "user input" can be arbitrarily delayed by dispatcher callbacks, etc, so you cannot know whether a change is due to user input or not.

In fact, theoretically speaking this is generally true. Consider the following scenarios:

  1. The user clicks a button elsewhere in your application which causes a new data to be loaded which updates your value.
  2. The user clicks a button in another application which writes a file, causing your application to refresh and display new data.
  3. The user walks to another computer and updates some data on a web site somewhere. Your application is monitoring this web site and detects the change.

Clearly in each of these cases the change was caused by user input ;-) Do you see where I'm going with this? Philosophically there is no fundamental way to decide whether a change was made by your user or someone else.

If what you really want is "any changes that occur between the time the user clicks the mouse or uses the keyboard in this application and the time the application goes idle," you can implement this:

InputManager.Current.PreNotifyInput += (sender, e) =>
{
  _userInput = true;
  Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
  {
    _userInput = false;
  }));
}; 

But in this case if you have data coming in dynamically from an external feed it may be erroneously considered to be user input.

Another approach is to flip yours upside down: Any time you refresh data from an external data source, set a flag saying you are doing so. Then any time you see changes with that flag not set, you assume it was user interaction. This may be easier to implement if you can ensure that all your external data updates happen above DispatcherPriority.Render.

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