56 votes

Comment définir les restrictions d'entrée des TextBox ?

Comment puis-je restreindre TextBox pour qu'il n'accepte que les lettres majuscules, ou par exemple les chiffres, ou interdire de mettre tout caractère spécial ?

Bien sûr, c'est un jeu d'enfant d'attraper l'événement TextInput et de traiter le texte ici, mais est-ce la bonne façon de le faire ?

120voto

Kent Boogaart Points 97432

Je l'ai fait dans le passé avec un comportement joint, qui peut être utilisé comme ceci :

<TextBox b:Masking.Mask="^\p{Lu}*$"/>

Le code de comportement ci-joint ressemble à ceci :

/// <summary>
/// Provides masking behavior for any <see cref="TextBox"/>.
/// </summary>
public static class Masking
{
    private static readonly DependencyPropertyKey _maskExpressionPropertyKey = DependencyProperty.RegisterAttachedReadOnly("MaskExpression",
        typeof(Regex),
        typeof(Masking),
        new FrameworkPropertyMetadata());

    /// <summary>
    /// Identifies the <see cref="Mask"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
        typeof(string),
        typeof(Masking),
        new FrameworkPropertyMetadata(OnMaskChanged));

    /// <summary>
    /// Identifies the <see cref="MaskExpression"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaskExpressionProperty = _maskExpressionPropertyKey.DependencyProperty;

    /// <summary>
    /// Gets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask, or <see langword="null"/> if no mask has been set.
    /// </returns>
    public static string GetMask(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        return textBox.GetValue(MaskProperty) as string;
    }

    /// <summary>
    /// Sets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be set.
    /// </param>
    /// <param name="mask">
    /// The mask to set, or <see langword="null"/> to remove any existing mask from <paramref name="textBox"/>.
    /// </param>
    public static void SetMask(TextBox textBox, string mask)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        textBox.SetValue(MaskProperty, mask);
    }

    /// <summary>
    /// Gets the mask expression for the <see cref="TextBox"/>.
    /// </summary>
    /// <remarks>
    /// This method can be used to retrieve the actual <see cref="Regex"/> instance created as a result of setting the mask on a <see cref="TextBox"/>.
    /// </remarks>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask expression is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask expression as an instance of <see cref="Regex"/>, or <see langword="null"/> if no mask has been applied to <paramref name="textBox"/>.
    /// </returns>
    public static Regex GetMaskExpression(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        } 

        return textBox.GetValue(MaskExpressionProperty) as Regex;
    }

    private static void SetMaskExpression(TextBox textBox, Regex regex)
    {
        textBox.SetValue(_maskExpressionPropertyKey, regex);
    }

    private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var textBox = dependencyObject as TextBox;
        var mask = e.NewValue as string;
        textBox.PreviewTextInput -= textBox_PreviewTextInput;
        textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
        DataObject.RemovePastingHandler(textBox, Pasting);

        if (mask == null)
        {
            textBox.ClearValue(MaskProperty);
            textBox.ClearValue(MaskExpressionProperty);
        }
        else
        {
            textBox.SetValue(MaskProperty, mask);
            SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));
            textBox.PreviewTextInput += textBox_PreviewTextInput;
            textBox.PreviewKeyDown += textBox_PreviewKeyDown;
            DataObject.AddPastingHandler(textBox, Pasting);
        }
    }

    private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        var proposedText = GetProposedText(textBox, e.Text);

        if (!maskExpression.IsMatch(proposedText))
        {
            e.Handled = true;
        }
    }

    private static void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        //pressing space doesn't raise PreviewTextInput - no idea why, but we need to handle
        //explicitly here
        if (e.Key == Key.Space)
        {
            var proposedText = GetProposedText(textBox, " ");

            if (!maskExpression.IsMatch(proposedText))
            {
                e.Handled = true;
            }
        }
    }

    private static void Pasting(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var pastedText = e.DataObject.GetData(typeof(string)) as string;
            var proposedText = GetProposedText(textBox, pastedText);

            if (!maskExpression.IsMatch(proposedText))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    private static string GetProposedText(TextBox textBox, string newText)
    {
        var text = textBox.Text;

        if (textBox.SelectionStart != -1)
        {
            text = text.Remove(textBox.SelectionStart, textBox.SelectionLength);
        }

        text = text.Insert(textBox.CaretIndex, newText);

        return text;
    }
}

0 votes

Je ne dirais pas que ça l'était. Je l'ai écrit pour être beaucoup plus flexible que ce dont vous avez besoin. N'hésitez pas à prendre le concept et à le simplifier pour vos besoins.

1 votes

Très bien, Kent. Une solution flexible et puissante pour limiter la saisie dans une TextBox. +1

0 votes

Élégance ! J'aimerais pouvoir donner plus que +1

46voto

VitalyB Points 2318

J'ai amélioré la réponse de Kent Boogaart en traitant également les actions suivantes qui pouvaient auparavant entraîner une violation du modèle :

  • Retour en arrière
  • Sélectionner et faire glisser du texte d'une manière qui peut violer le motif
  • Commande de coupe

Par exemple, la réponse de Kent Boogaart permettait à l'utilisateur d'entrer "ac" en entrant d'abord "abc" et en supprimant ensuite le "b" avec le retour arrière, ce qui viole la regex suivante

^(a|ab|abc)$

Utilisation (inchangée) :

<TextBox b:Masking.Mask="^\p{Lu}*$"/>

Classe de masque :

public static class Masking
{
    private static readonly DependencyPropertyKey _maskExpressionPropertyKey = DependencyProperty.RegisterAttachedReadOnly("MaskExpression",
            typeof(Regex),
            typeof(Masking),
            new FrameworkPropertyMetadata());

    /// <summary>
    /// Identifies the <see cref="Mask"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
            typeof(string),
            typeof(Masking),
            new FrameworkPropertyMetadata(OnMaskChanged));

    /// <summary>
    /// Identifies the <see cref="MaskExpression"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaskExpressionProperty = _maskExpressionPropertyKey.DependencyProperty;

    /// <summary>
    /// Gets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask, or <see langword="null"/> if no mask has been set.
    /// </returns>
    public static string GetMask(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        return textBox.GetValue(MaskProperty) as string;
    }

    /// <summary>
    /// Sets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be set.
    /// </param>
    /// <param name="mask">
    /// The mask to set, or <see langword="null"/> to remove any existing mask from <paramref name="textBox"/>.
    /// </param>
    public static void SetMask(TextBox textBox, string mask)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        textBox.SetValue(MaskProperty, mask);
    }

    /// <summary>
    /// Gets the mask expression for the <see cref="TextBox"/>.
    /// </summary>
    /// <remarks>
    /// This method can be used to retrieve the actual <see cref="Regex"/> instance created as a result of setting the mask on a <see cref="TextBox"/>.
    /// </remarks>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask expression is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask expression as an instance of <see cref="Regex"/>, or <see langword="null"/> if no mask has been applied to <paramref name="textBox"/>.
    /// </returns>
    public static Regex GetMaskExpression(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        return textBox.GetValue(MaskExpressionProperty) as Regex;
    }

    private static void SetMaskExpression(TextBox textBox, Regex regex)
    {
        textBox.SetValue(_maskExpressionPropertyKey, regex);
    }

    private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var textBox = dependencyObject as TextBox;
        var mask = e.NewValue as string;
        textBox.PreviewTextInput -= textBox_PreviewTextInput;
        textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
        DataObject.RemovePastingHandler(textBox, Pasting);
        DataObject.RemoveCopyingHandler(textBox, NoDragCopy);
        CommandManager.RemovePreviewExecutedHandler(textBox, NoCutting);

        if (mask == null)
        {
            textBox.ClearValue(MaskProperty);
            textBox.ClearValue(MaskExpressionProperty);
        }
        else
        {
            textBox.SetValue(MaskProperty, mask);
            SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));
            textBox.PreviewTextInput += textBox_PreviewTextInput;
            textBox.PreviewKeyDown += textBox_PreviewKeyDown;
            DataObject.AddPastingHandler(textBox, Pasting);
            DataObject.AddCopyingHandler(textBox, NoDragCopy);
            CommandManager.AddPreviewExecutedHandler(textBox, NoCutting);
        }
    }

    private static void NoCutting(object sender, ExecutedRoutedEventArgs e)
    {
        if(e.Command == ApplicationCommands.Cut)
        {
            e.Handled = true;
        }
    }

    private static void NoDragCopy(object sender, DataObjectCopyingEventArgs e)
    {
        if (e.IsDragDrop)
        {
            e.CancelCommand();
        }
    }

    private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        var proposedText = GetProposedText(textBox, e.Text);

        if (!maskExpression.IsMatch(proposedText))
        {
            e.Handled = true;
        }
    }

    private static void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        string proposedText = null;

        //pressing space doesn't raise PreviewTextInput, reasons here http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/446ec083-04c8-43f2-89dc-1e2521a31f6b?prof=required
        if (e.Key == Key.Space)
        {
            proposedText = GetProposedText(textBox, " ");
        }
        // Same story with backspace
        else if(e.Key == Key.Back)
        {
            proposedText = GetProposedTextBackspace(textBox);
        }

        if (proposedText != null && proposedText != string.Empty && !maskExpression.IsMatch(proposedText))
        {
            e.Handled = true;
        }

    }

    private static void Pasting(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        if (maskExpression == null)
        {
            return;
        }

        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var pastedText = e.DataObject.GetData(typeof(string)) as string;
            var proposedText = GetProposedText(textBox, pastedText);

            if (!maskExpression.IsMatch(proposedText))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    private static string GetProposedTextBackspace(TextBox textBox)
    {
        var text = GetTextWithSelectionRemoved(textBox);
        if (textBox.SelectionStart > 0 && textBox.SelectionLength == 0)
        {
            text = text.Remove(textBox.SelectionStart-1, 1);
        }

        return text;
    }

    private static string GetProposedText(TextBox textBox, string newText)
    {
        var text = GetTextWithSelectionRemoved(textBox);
        text = text.Insert(textBox.CaretIndex, newText);

        return text;
    }

    private static string GetTextWithSelectionRemoved(TextBox textBox)
    {
        var text = textBox.Text;

        if (textBox.SelectionStart != -1)
        {
            text = text.Remove(textBox.SelectionStart, textBox.SelectionLength);
        }
        return text;
    }
}

0 votes

Vous avez amélioré la solution de 3 façons mais la lecture de sa courte description ne me dit rien. Je pense que cela a à voir avec le copier-coller du texte dans la TextBox mais la solution de Kent bloquait déjà le texte erroné. Pouvez-vous peut-être expliquer quels cas d'utilisation les améliorations empêchent ? Merci

0 votes

Ces améliorations empêchent l'utilisateur de violer la règle regex. Ainsi, par exemple, avant ces modifications, vous pouviez utiliser l'espace arrière pour rendre la règle illégale. Ainsi, si le motif était ^123$ vous pourriez taper 123 et ensuite supprimer 2 et il te laisserait faire. Maintenant, ce n'est plus le cas. Il en va de même pour les autres améliorations - Couper du texte et faire glisser du texte.

0 votes

Je comprends maintenant ce que vous voulez dire, mais seule une action de copier-coller peut amener l'utilisateur au point où il y a "123" dans la zone de texte pour commencer. Taper simplement le caractère "1" (ou tout autre caractère) sera impossible car il ne valide pas "^123$". L'utilisation du retour arrière sur un dactylographié dans la chaîne de caractères respectera toujours le motif car l'entrée précédente a déjà été validée. Certains utilisateurs vont certainement faire du copier-coller et votre amélioration permettra de résoudre ce cas de figure qui aurait été un mystère pendant un certain temps. Merci

14voto

Nathan Tornquist Points 982

J'ai changé le code de VitalyB pour supporter les thèmes de couleur. Au lieu de bloquer l'entrée de l'utilisateur si elle ne répond pas à la RegEx script, il met simplement en évidence la zone de texte. La zone de texte sera le thème par défaut sans interaction, puis passera à un vert clair ou à un rouge en fonction de la valeur après la saisie. Vous pouvez également définir les couleurs d'échec et de réussite de manière programmatique avec :

b:ColorMasking.PassColor = "Hexadecimal Value"
b:ColorMasking.FailColor = "Hexadecimal Value"

La classe est ci-dessous :

public class ColorMasking : DependencyObject
{
    private static readonly DependencyPropertyKey _maskExpressionPropertyKey = DependencyProperty.RegisterAttachedReadOnly("MaskExpression",
            typeof(Regex),
            typeof(ColorMasking),
            new FrameworkPropertyMetadata());

    /// <summary>
    /// Identifies the <see cref="Mask"/> dependency property.
    /// </summary>
    /// 
    public static readonly DependencyProperty PassColorProperty = DependencyProperty.RegisterAttached("PassColor",
            typeof(string),
            typeof(ColorMasking),
            new PropertyMetadata("#99FF99"));

    public static void SetPassColor(DependencyObject obj, string passColor)
    {
        obj.SetValue(PassColorProperty, passColor);
    }

    public static string GetPassColor(DependencyObject obj)
    {
        return (string)obj.GetValue(PassColorProperty);
    }

    public static readonly DependencyProperty FailColorProperty = DependencyProperty.RegisterAttached("FailColor",
            typeof(string),
            typeof(ColorMasking),
            new PropertyMetadata("#FFCCFF"));

    public static void SetFailColor(DependencyObject obj, string failColor)
    {
        obj.SetValue(FailColorProperty, failColor);
    }

    public static string GetFailColor(DependencyObject obj)
    {
        return (string)obj.GetValue(FailColorProperty);
    }

    public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
            typeof(string),
            typeof(ColorMasking),
            new FrameworkPropertyMetadata(OnMaskChanged));

    private static void OnPassColorChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var textBox = dependencyObject as TextBox;
        var color = e.NewValue as string;
        textBox.SetValue(PassColorProperty, color);
    }

    /// <summary>
    /// Identifies the <see cref="MaskExpression"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty MaskExpressionProperty = _maskExpressionPropertyKey.DependencyProperty;

    /// <summary>
    /// Gets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask, or <see langword="null"/> if no mask has been set.
    /// </returns>
    public static string GetMask(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        return textBox.GetValue(MaskProperty) as string;
    }

    /// <summary>
    /// Sets the mask for a given <see cref="TextBox"/>.
    /// </summary>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask is to be set.
    /// </param>
    /// <param name="mask">
    /// The mask to set, or <see langword="null"/> to remove any existing mask from <paramref name="textBox"/>.
    /// </param>
    public static void SetMask(TextBox textBox, string mask)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        textBox.SetValue(MaskProperty, mask);
    }

    /// <summary>
    /// Gets the mask expression for the <see cref="TextBox"/>.
    /// </summary>
    /// <remarks>
    /// This method can be used to retrieve the actual <see cref="Regex"/> instance created as a result of setting the mask on a <see cref="TextBox"/>.
    /// </remarks>
    /// <param name="textBox">
    /// The <see cref="TextBox"/> whose mask expression is to be retrieved.
    /// </param>
    /// <returns>
    /// The mask expression as an instance of <see cref="Regex"/>, or <see langword="null"/> if no mask has been applied to <paramref name="textBox"/>.
    /// </returns>
    public static Regex GetMaskExpression(TextBox textBox)
    {
        if (textBox == null)
        {
            throw new ArgumentNullException("textBox");
        }

        return textBox.GetValue(MaskExpressionProperty) as Regex;
    }

    private static void SetMaskExpression(TextBox textBox, Regex regex)
    {
        textBox.SetValue(_maskExpressionPropertyKey, regex);
    }

    private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var textBox = dependencyObject as TextBox;
        var mask = e.NewValue as string;
        textBox.PreviewTextInput -= textBox_PreviewTextInput;
        textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
        DataObject.RemovePastingHandler(textBox, Pasting);
        DataObject.RemoveCopyingHandler(textBox, NoDragCopy);
        CommandManager.RemovePreviewExecutedHandler(textBox, NoCutting);

        if (mask == null)
        {
            textBox.ClearValue(MaskProperty);
            textBox.ClearValue(MaskExpressionProperty);
        }
        else
        {
            textBox.SetValue(MaskProperty, mask);
            SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));
            textBox.PreviewTextInput += textBox_PreviewTextInput;
            textBox.PreviewKeyDown += textBox_PreviewKeyDown;
            DataObject.AddPastingHandler(textBox, Pasting);
            DataObject.AddCopyingHandler(textBox, NoDragCopy);
            CommandManager.AddPreviewExecutedHandler(textBox, NoCutting);
        }
    }

    private static void NoCutting(object sender, ExecutedRoutedEventArgs e)
    {
        if (e.Command == ApplicationCommands.Cut)
        {
            e.Handled = true;
        }
    }

    private static void NoDragCopy(object sender, DataObjectCopyingEventArgs e)
    {
        if (e.IsDragDrop)
        {
            e.CancelCommand();
        }
    }

    private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        string passHex = (string)textBox.GetValue(PassColorProperty);
        string failHex = (string)textBox.GetValue(FailColorProperty);
        Color passColor = Extensions.ToColorFromHex(passHex);
        Color failColor = Extensions.ToColorFromHex(failHex);

        if (maskExpression == null)
        {
            return;
        }

        var proposedText = GetProposedText(textBox, e.Text);

        if (!maskExpression.IsMatch(proposedText))
        {
            textBox.Background = new SolidColorBrush(failColor);
        }
        else
        {
            textBox.Background = new SolidColorBrush(passColor);
        }
    }

    private static void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        var textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        string passHex = (string)textBox.GetValue(PassColorProperty);
        string failHex = (string)textBox.GetValue(FailColorProperty);
        Color passColor = Extensions.ToColorFromHex(passHex);
        Color failColor = Extensions.ToColorFromHex(failHex);

        if (maskExpression == null)
        {
            return;
        }

        string proposedText = null;

        //pressing space doesn't raise PreviewTextInput, reasons here http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/446ec083-04c8-43f2-89dc-1e2521a31f6b?prof=required
        if (e.Key == Key.Space)
        {
            proposedText = GetProposedText(textBox, " ");
        }
        // Same story with backspace
        else if (e.Key == Key.Back)
        {
            proposedText = GetProposedTextBackspace(textBox);
        }

        if (proposedText != null && !maskExpression.IsMatch(proposedText))
        {
            textBox.Background = new SolidColorBrush(failColor);
        }
        else
        {
            textBox.Background = new SolidColorBrush(passColor);
        }

    }

    private static void Pasting(object sender, DataObjectPastingEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        var maskExpression = GetMaskExpression(textBox);

        string passHex = (string)textBox.GetValue(PassColorProperty);
        string failHex = (string)textBox.GetValue(FailColorProperty);
        Color passColor = Extensions.ToColorFromHex(passHex);
        Color failColor = Extensions.ToColorFromHex(failHex);

        if (maskExpression == null)
        {
            return;
        }

        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var pastedText = e.DataObject.GetData(typeof(string)) as string;
            var proposedText = GetProposedText(textBox, pastedText);

            if (!maskExpression.IsMatch(proposedText))
            {
                textBox.Background = new SolidColorBrush(failColor);
            }
            else
            {
                textBox.Background = new SolidColorBrush(passColor);
            }
        }
        else
        {
            textBox.Background = new SolidColorBrush(failColor);
        }
    }

    private static string GetProposedTextBackspace(TextBox textBox)
    {
        var text = GetTextWithSelectionRemoved(textBox);
        if (textBox.SelectionStart > 0)
        {
            text = text.Remove(textBox.SelectionStart - 1, 1);
        }

        return text;
    }

    private static string GetProposedText(TextBox textBox, string newText)
    {
        var text = GetTextWithSelectionRemoved(textBox);
        text = text.Insert(textBox.CaretIndex, newText);

        return text;
    }

    private static string GetTextWithSelectionRemoved(TextBox textBox)
    {
        var text = textBox.Text;

        if (textBox.SelectionStart != -1)
        {
            text = text.Remove(textBox.SelectionStart, textBox.SelectionLength);
        }
        return text;
    }
}

Pour fonctionner, le script nécessite une classe écrite par Aaron C, expliquée ici : Silverlight/WPF définit une ellipse avec une couleur hexadécimale montré ici : http://www.wiredprairie.us/blog/index.php/archives/659

Le code est ci-dessous au cas où le site web serait déplacé :

public static class Extensions
{
    public static void SetFromHex(this Color c, string hex)
    {
        Color c1 = ToColorFromHex(hex);

        c.A = c1.A;
        c.R = c1.R;
        c.G = c1.G;
        c.B = c1.B;
    }

    public static Color ToColorFromHex(string hex)
    {
        if (string.IsNullOrEmpty(hex))
        {
            throw new ArgumentNullException("hex");
        }

        // remove any "#" characters
        while (hex.StartsWith("#"))
        {
            hex = hex.Substring(1);
        }

        int num = 0;
        // get the number out of the string
        if (!Int32.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out num))
        {
            throw new ArgumentException("Color not in a recognized Hex format.");
        }

        int[] pieces = new int[4];
        if (hex.Length > 7)
        {
            pieces[0] = ((num >> 24) & 0x000000ff);
            pieces[1] = ((num >> 16) & 0x000000ff);
            pieces[2] = ((num >> 8) & 0x000000ff);
            pieces[3] = (num & 0x000000ff);
        }
        else if (hex.Length > 5)
        {
            pieces[0] = 255;
            pieces[1] = ((num >> 16) & 0x000000ff);
            pieces[2] = ((num >> 8) & 0x000000ff);
            pieces[3] = (num & 0x000000ff);
        }
        else if (hex.Length == 3)
        {
            pieces[0] = 255;
            pieces[1] = ((num >> 8) & 0x0000000f);
            pieces[1] += pieces[1] * 16;
            pieces[2] = ((num >> 4) & 0x000000f);
            pieces[2] += pieces[2] * 16;
            pieces[3] = (num & 0x000000f);
            pieces[3] += pieces[3] * 16;
        }
        return Color.FromArgb((byte)pieces[0], (byte)pieces[1], (byte)pieces[2], (byte)pieces[3]);
    }

}

3voto

Oungawak Points 31

Voici encore une autre version des solutions existantes ici. Il s'agit d'un comportement simulant un événement "TextChanging" fournissant l'ancien texte, le nouveau texte et un drapeau pour annuler le changement. De cette façon, nous pouvons mettre en œuvre le filtre que nous voulons.

Je me suis débarrassé du handler "PreviewKeyDown" utilisé uniquement pour le caractère espace. TextBox semble tout gérer avec des commandes routées et "Space" a sa propre commande, bien qu'elle ne soit pas publique. J'ai aussi ajouté le support pour des raccourcis clavier additionnels comme "Ctrl+Backspace" (effacer le mot précédent).

public class TextChangingBehavior : Behavior<TextBox>
{
    public event EventHandler<TextChangingEventArgs> TextChanging;

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.PreviewTextInput += OnPreviewTextInput;
        CommandManager.AddPreviewExecutedHandler(AssociatedObject, OnPreviewExecutedHandler);
        DataObject.AddCopyingHandler(AssociatedObject, OnCopying);
        DataObject.AddPastingHandler(AssociatedObject, OnPasting);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
        CommandManager.RemovePreviewExecutedHandler(AssociatedObject, OnPreviewExecutedHandler);
        DataObject.RemoveCopyingHandler(AssociatedObject, OnCopying);
        DataObject.RemovePastingHandler(AssociatedObject, OnPasting);
    }

    #region Text

    private enum CharCategory
    {
        LetterOrDigit,
        Whitespace,
        Other
    }

    private CharCategory GetCharCategory(char c)
    {
        if (char.IsLetterOrDigit(c))
            return CharCategory.LetterOrDigit;
        else if (char.IsWhiteSpace(c))
            return CharCategory.Whitespace;
        else
            return CharCategory.Other;
    }

    private string GetText(string input = null)
    {
        var box = AssociatedObject;
        var text = box.Text ?? string.Empty;
        if (input != null)
        {
            // Delete selection
            var deleteCount = box.SelectionLength;
            if (deleteCount > 0)
                text = text.Remove(box.SelectionStart, deleteCount);
            // Insert input
            if (input.Length > 0)
                text = text.Insert(box.CaretIndex, input);
        }
        return text;
    }

    #endregion

    private void OnPreviewExecutedHandler(object sender, ExecutedRoutedEventArgs e)
    {
        var box = AssociatedObject;
        var selectionExists = box.SelectionLength > 0;
        var caretIndex = box.CaretIndex;
        string newText = null;

        if (e.Command == ApplicationCommands.Cut)
        {
            if (selectionExists)
                newText = GetText(string.Empty);
        }
        else if (e.Command == EditingCommands.Backspace)
        {
            if (selectionExists)
                newText = GetText(string.Empty);
            else if (caretIndex > 0)
                newText = GetText().Remove(caretIndex - 1, 1);
        }
        else if (e.Command == EditingCommands.Delete)
        {
            if (selectionExists)
                newText = GetText(string.Empty);
            else
            {
                newText = GetText();
                if (caretIndex >= newText.Length)
                    newText = null;
                else
                    newText = newText.Remove(caretIndex, 1);
            }
        }
        else if (e.Command == EditingCommands.DeletePreviousWord)
        {
            if (selectionExists)
                newText = GetText(string.Empty);
            else if (caretIndex > 0)
            {
                newText = GetText();
                var startIndex = caretIndex;
                // Include whitespaces
                do
                    startIndex--;
                while (startIndex > 0 && char.IsWhiteSpace(newText[startIndex]));
                // Include the next block
                var currentCategory = GetCharCategory(newText[startIndex]);
                while (startIndex > 0 && GetCharCategory(newText[startIndex - 1]) == currentCategory)
                    startIndex--;

                newText = newText.Remove(startIndex, caretIndex - startIndex);
            }
        }
        else if (e.Command == EditingCommands.DeleteNextWord)
        {
            if (selectionExists)
                newText = GetText(string.Empty);
            else
            {
                newText = GetText();
                if (caretIndex >= newText.Length)
                    newText = null;
                else
                {
                    var endIndex = caretIndex + 1;
                    // Include the current block
                    var currentCategory = GetCharCategory(newText[caretIndex]);
                    while (endIndex < newText.Length && GetCharCategory(newText[endIndex]) == currentCategory)
                        endIndex++;
                    // Include whitespaces
                    while (endIndex < newText.Length && char.IsWhiteSpace(newText[endIndex]))
                        endIndex++;

                    newText = newText.Remove(caretIndex, endIndex - caretIndex);
                }
            }
        }
        else if (e.Command is RoutedUICommand cmd && new[] { "Space", "ShiftSpace" }.Contains(cmd.Name))
        {
            newText = GetText(" ");
        }

        if (newText != null && OnProcessChange(newText))
            e.Handled = true;
    }

    private void OnCopying(object sender, DataObjectCopyingEventArgs e)
    {
        if (e.IsDragDrop)
        {
            if (OnProcessChange(GetText(string.Empty)))
                e.CancelCommand();
        }
    }

    private void OnPasting(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            if (OnProcessChange(GetText((string)e.DataObject.GetData(typeof(string)))))
                e.CancelCommand();
        }
    }

    private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        if (!string.IsNullOrEmpty(e.Text))
        {
            if (OnProcessChange(GetText(e.Text)))
                e.Handled = true;
        }
    }

    private bool OnProcessChange(string newValue)
    {
        var oldValue = GetText();

        if (string.Equals(oldValue, newValue, StringComparison.Ordinal))
            return false;
        else
        {
            var args = new TextChangingEventArgs(oldValue, newValue);
            OnTextChanging(args);
            return args.Cancel;
        }
    }

    protected virtual void OnTextChanging(TextChangingEventArgs e)
    {
        TextChanging?.Invoke(this, e);
    }
}

public class TextChangingEventArgs : EventArgs
{
    public string OldValue { get; }

    public string NewValue { get; }

    public bool Cancel { get; set; }

    public TextChangingEventArgs(string oldValue, string newValue)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}

0 votes

Exactement ce que je recherchais. Je veux juste ajouter que le paquet nuget Microsoft.Xaml.Behaviors.Wpf est nécessaire pour que cela fonctionne.

3voto

WhoIsNinja Points 1024
private void TextBox1_SelectionChanged(object sender, RoutedEventArgs e)
{
    string txt = TextBox1.Text;
    if (txt != "")
    {
        TextBox1.Text = Regex.Replace(TextBox1.Text, "[^0-9]", "");
        if (txt != TextBox1.Text)
        {
            TextBox1.Select(TextBox1.Text.Length, 0);
        }
    }
}

0 votes

Je ne sais pas comment en faire une propriété personnalisée, mais c'est une solution simple. J'apprécierais si quelqu'un pouvait convertir cela en propriété comme Kent l'a fait.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X