tags:

views:

33

answers:

1

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;
    }
}
+1  A: 

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.

Ray Burns
Thanks for your (as always) detailed answer. It helped me understand the issue. I'm curious, how can you tell that the command is done through a dispatcher callback?
Wallstreet Programmer
Set a breakpoint in your TextChanged event, then look at the call stack. TextChanged is called by TextBoxBase, which is called by data-binding code, which is called by MenuItem click handling code, which is called by the Dispatcher. Since the MenuItem click handling code is called by the Dispatcher you know that MenuItem used a dispatcher callback.
Ray Burns
You'll need to disable "Just My Code" to and show all stack frames to see this. I recommend you always do this - stack traces through WPF can provide valuable clues as to how things work.
Ray Burns