Vaccano
Vaccano

Reputation: 82467

Is using an "Attached Property" a good practice for input capturing?

I am looking to find a generic way to support keyboard wedge scanning for my WPF TextBox controls.

(I am really a novice when it comes to more advanced WPF features, so I would like to ask if I am going in the right direction before I put a lot of time into research.)

What I am wanting to do is to add an Attached Property (or something) to my TextBoxes that will cause it to read all input into the box and then call a custom "ScanCompleted" command with the scanned input.

If an Attached Property is not a good fit for this, then is there a way to get this command on a TextBox without descending my own custom "ScanableTextBox"?

(Note: The criteria for a scan (instead of typed data) is that it will start with the Pause key (#19) and end with a Return key (#13).)

Upvotes: 2

Views: 1669

Answers (1)

Jay
Jay

Reputation: 57959

I think this could probably be accomplished with attached properties (behaviors), but would be much simpler and more straightforward to simply subclass TextBox and override the OnTextChanged, OnKeyDown, OnKeyUp and similar methods to add custom functionality.

Why don't you want to create your own control in this way?

update: Attached Behaviour

If you really don't want a derived control, here is an attached behaviour that accomplishes this (explanation below):

  public class ScanReading
  {
    private static readonly IDictionary<TextBox, ScanInfo> TrackedTextBoxes = new Dictionary<TextBox, ScanInfo>();

    public static readonly DependencyProperty ScanCompletedCommandProperty =
      DependencyProperty.RegisterAttached("ScanCompletedCommand", typeof (ICommand), typeof (ScanReading), 
      new PropertyMetadata(default(ICommand), OnScanCompletedCommandChanged));

    public static void SetScanCompletedCommand(TextBox textBox, ICommand value)
    {
      textBox.SetValue(ScanCompletedCommandProperty, value);
    }

    public static ICommand GetScanCompletedCommand(TextBox textBox)
    {
      return (ICommand) textBox.GetValue(ScanCompletedCommandProperty);
    }

    private static void OnScanCompletedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var textBox = d as TextBox;
      if (textBox == null)
        return;

      var command = (ICommand) e.NewValue;

      if (command == null)
      {
        textBox.Unloaded -= OnTextBoxUnloaded;
        textBox.KeyUp -= OnTextBoxKeyUp;
        TrackedTextBoxes.Remove(textBox);
      }
      else
      {
        textBox.Unloaded += OnTextBoxUnloaded;
        TrackedTextBoxes.Add(textBox, new ScanInfo(command));

        textBox.KeyUp += OnTextBoxKeyUp;
      }
    }

    static void OnTextBoxKeyUp(object sender, KeyEventArgs e)
    {
      var textBox = (TextBox) sender;

      var scanInfo = TrackedTextBoxes[textBox];
      if (scanInfo.IsTracking)
      {
        if (e.Key == Key.Return)
        {
          scanInfo.ScanCompletedCommand.Execute(textBox.Text);
          scanInfo.IsTracking = false;
        }
      }
      else if (string.IsNullOrEmpty(textBox.Text) && e.Key == Key.Pause)
      {
        TrackedTextBoxes[textBox].IsTracking = true;
      }
    }

    static void OnTextBoxUnloaded(object sender, RoutedEventArgs e)
    {
      var textBox = (TextBox) sender;
      textBox.KeyUp -= OnTextBoxKeyUp;
      textBox.Unloaded -= OnTextBoxUnloaded;
      TrackedTextBoxes.Remove(textBox);
    }
  }

  public class ScanInfo
  {
    public ScanInfo(ICommand scanCompletedCommand)
    {
      ScanCompletedCommand = scanCompletedCommand;
    }

    public bool IsTracking { get; set; }
    public ICommand ScanCompletedCommand { get; private set; }
  }

Consume this by declaring a TextBox like so (where local is the namespace of your attached property, and ScanCompleted is an ICommand on your view-model):

<TextBox local:ScanReading.ScanCompletedCommand="{Binding ScanCompleted}" />

Now when this property is set, we add the TextBox to a static collection along with its associated ICommand.

Each time a key is pressed, we check whether it is the Pause key. If it is, and if the TextBox is empty, we set a flag to true to start looking for the Enter key.

Now each time a key is pressed, we check whether it is the Enter key. If it is, we execute the command, passing in the TextBox.Text value, and reset the flag to false for that TextBox.

We've also added a handler for the TextBox.Unloaded event to clean up our event subscriptions and remove the TextBox from the static list.

Upvotes: 4

Related Questions