Как отследить, какой символ удален в TextBox в WPF? - PullRequest
6 голосов
/ 16 июня 2010

Я хочу отследить, какой символ удаляется пользователем с помощью клавиши Delete или BackSpace.

Я обрабатываю TextBox_ChangedEvent для текстового поля.

Можно ли извлечь удаленный символ из TextChangedEventArgs e.Changes и, если да, как я могу это сделать?

Я хочу запретить пользователю удалять любые символы из TextBox. Я хочу, чтобы пользователь мог удалить только два символа (скажем, "(" или ")")

Пожалуйста, предложите.

Ответы [ 3 ]

9 голосов
/ 16 июня 2010

Ниже вы найдете код для присоединенного свойства, которое можно использовать таким образом, чтобы предотвратить удаление чего-либо, кроме "(" или ")" из TextBox, точка.

<TextBox my:TextBoxRestriction.RestrictDeleteTo="()" ... />

Это будет правильно обрабатывать все обновления мыши и клавиатуры, такие как:

  1. Использование клавиши удаления с несколькими выбранными символами
  2. Использование клавиши Backspace
  3. Использование Ctrl-X для вырезания
  4. Нажатие кнопки «Вырезать» в строке меню

Из-за этого он намного мощнее, чем просто перехват PreviewKeyDown.

Это также отключает удаление чего-либо с помощью байта "(" или ")" путем непосредственного присвоения свойству .Text, поэтому произойдет сбой:

textBox.Text = "Good morning";

Из-за этого класс TextBoxRestriction также содержит другое присоединенное свойство с именем UnrestrictedText , которое, если установлено, может обновлять свойство Text, обходя ограничения. Это можно установить в коде, используя TextBoxRestriction.SetUnrestrictedText, или привязать данные следующим образом:

<TextBox my:TextBoxRestriction.RestrictDeleteTo="()"
         my:TextBoxRestriction.UnrestrictedText="{Binding PropertyNameHere}" />

В приведенной ниже реализации UnrestrictedText работает только в том случае, если также установлен RestrictDeleteTo. Можно было бы реализовать полную реализацию, которая регистрирует обработчик событий всякий раз, когда устанавливается любое из свойств, и сохраняет обработчик в третьем присоединенном свойстве для последующей отмены регистрации. Но для ваших текущих потребностей это, вероятно, не нужно.

Вот реализация, как и было обещано:

public class TextBoxRestriction : DependencyObject
{
  // RestrictDeleteTo:  Set this to the characters that may be deleted
  public static string GetRestrictDeleteTo(DependencyObject obj) { return (string)obj.GetValue(RestrictDeleteToProperty); }
  public static void SetRestrictDeleteTo(DependencyObject obj, string value) { obj.SetValue(RestrictDeleteToProperty, value); }
  public static readonly DependencyProperty RestrictDeleteToProperty = DependencyProperty.RegisterAttached("RestrictDeleteTo", typeof(string), typeof(TextBoxRestriction), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
      {
        var box = (TextBox)obj;
        box.TextChanged += (obj2, changeEvent) =>
          {
            var oldText = GetUnrestrictedText(box);
            var allowedChars = GetRestrictDeleteTo(box);
            if(box.Text==oldText || allowdChars==null) return;

            foreach(var change in changeEvent.Changes)
              if(change.RemovedLength>0)
              {
                string deleted = box.Text.Substring(change.Offset, change.RemovedLength);
                if(deleted.Any(ch => !allowedChars.Contains(ch)))
                  box.Text = oldText;
              }
            SetUnrestrictedText(box, box.Text);
          };
      }
  });

  // UnrestrictedText:  Bind or access this property to update the Text property bypassing all restrictions
  public static string GetUnrestrictedText(DependencyObject obj) { return (string)obj.GetValue(UnrestrictedTextProperty); }
  public static void SetUnrestrictedText(DependencyObject obj, string value) { obj.SetValue(UnrestrictedTextProperty, value); }
  public static readonly DependencyProperty UnrestrictedTextProperty = DependencyProperty.RegisterAttached("UnrestrictedText", typeof(string), typeof(TextBoxRestriction), new PropertyMetadata
  {
    DefaultValue = "",
    PropertyChangedCallback = (obj, e) =>
      {
        var box = (TextBox)obj;
        box.Text = (string)e.NewValue;
      }
  });

}

Как это работает: когда вы устанавливаете UnrestrictedText, он устанавливает текст и наоборот. Обработчик TextChanged проверяет, отличается ли Text от UnrestrictedText. Если это так, он знает, что текст был обновлен с помощью какого-то другого механизма, кроме установки UnrestrictedText, поэтому сканирует изменения на предмет незаконного удаления. Если один из них найден, он возвращает Text значение, все еще сохраненное в UnrestrictedText, предотвращая изменение.

1 голос
/ 16 июня 2010

Прикрепленное поведение для обработки

public static class TextInputBehaviour
{
    public static bool GetIsDeleteRestricted(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsDeleteRestrictedProperty);
    }

    public static void SetIsDeleteRestricted(DependencyObject obj, bool value)
    {
        obj.SetValue(IsDeleteRestrictedProperty, value);
    }

    public static readonly DependencyProperty IsDeleteRestrictedProperty=DependencyProperty.RegisterAttached("IsDeleteRestricted", typeof(bool), typeof(TextInputBehaviour), new UIPropertyMetadata(false, OnIsDeleteRestrictedChanged));
}

private static void OnIsDeleteRestrictedChanged(object sender, DependencyPropertyChangedEventArgs e)
{      
  TextBox textBox = (TextBox)sender;
  bool isDeleteRestricted = (bool)(e.NewValue);

  if (isDeleteRestricted)
    textBox.PreviewKeyDown += RestrictDeleteKey;
  else
    textBox.PreviewKeyDown -= RestrictDeleteKey;
}

private static void RestrictDeleteKey(object sender, KeyEventArgs e)
{
      e.Handled = (e.Key == Key.Delete);
}

Удалите поведение в разделе ресурсов

Затем в блоке разметки текстового поля установите поведение

<TextBox local:TextInputBehaviour.IsDeleteRestricted="True" />
0 голосов
/ 16 июня 2010

Я не знаю WPF, но предполагаю, что он такой же, как WinForms для этого (кажется вероятным). Единственный известный мне способ заключается в том, что вы фактически сохраняете текущий текст в переменной и при изменении текста, если это не удаление или возврат, вы обновляете этот текст, в противном случае вы используете его для сравнения того, что изменилось, и следует ли разрешить это изменение.

Редактировать: Глядя на TextChangedEventArgs.Changes, кажется, что способ, который я описал выше, все еще может быть правильным, но вы, возможно, могли бы использовать Changes для более эффективного сравнения текстов.

Возможно, вы уже подумали об этом, но в противном случае не забывайте также обрабатывать вырезки и вставки (и что пользователь может делать это с помощью мыши, а не клавиатуры).

...