2
votes

entrée de texte au format de chaîne personnalisée wpf

J'ai des valeurs au format [double-type-value] [unit] où l'unité peut être "g" ou "mg" (g pour grammes et mg pour milligrammes). Existe-t-il un moyen d'autoriser l'utilisateur à entrer du texte dans TextBox UNIQUEMENT dans ce format. Par exemple pour être comme une mini zone de texte qui n'accepte que des nombres et une mini zone de liste déroulante où les valeurs sont "g" ou "mg" dans la zone de texte normale ou autre chose? Ce serait bien pour l'unité d'avoir la valeur par défaut "g" avant que quelque chose ne soit tapé dans la zone de texte afin que l'utilisateur n'ait pas à taper g ou mg à la fin de la zone de texte à chaque fois s'il y a plus de zones de texte.

MODIFIER J'utilise le modèle MVVM, donc le code derrière le viole.


6 commentaires

vous pouvez utiliser un convertisseur pour cela


@DenisSchaf si vous voulez implémenter une classe qui hérite de IValueConverter, cela convertira simplement la valeur valide que l'utilisateur a tapée au format dont j'ai besoin. Je veux empêcher un utilisateur de taper autre chose qu'une valeur dans ce format.


dans ce cas, vous n'avez qu'une seule possibilité et c'est d'utiliser l'événement PreviewTextInput dans cet événement convertir le texte d'entrée en une valeur double si la conversion échoue gérer l'événement d'entrée afin qu'il ne soit pas accepté si la conversion réussit, quittez simplement votre fonction. Cela permet à sanse de créer un contrôle personnalisé à cette fin afin que vous puissiez le réutiliser facilement si nécessaire


Le quantificateur d'unité est-il pertinent pour le code? Je veux dire, comment codez-vous si la valeur donnée est en mg ou g et multiplie / divise la valeur en conséquence (ou est-ce complètement hors de propos).


@LittleBit J'ai une base de données et tout dans la base de données est enregistré en grammes. J'utilise uniquement l'interface utilisateur de conversion pour une meilleure expérience utilisateur. Par exemple, l'utilisateur veut entrer 0,01 mg, je ne veux pas qu'il écrive 0,00001 à chaque fois.


@Kevin Cook ça va, mais dans certains cas, l'utilisateur écrit des valeurs dans DataGrid. Dans ce cas, je ne peux mettre que I combobox pour DataGrid entier, mais ce n'est pas la solution.


4 Réponses :


-1
votes

Pour empêcher les utilisateurs d'entrer autre chose que des nombres, vous devez utiliser l'événement PrevieTextInput, il est logique de créer un contrôle personnalisé pour cela. Les quelques lignes ci-dessous empêcheront l'utilisateur de saisir autre chose que des chiffres

    <Grid>            
        <TextBox Text="{Binding Text}" PreviewTextInput="TextBox_PreviewTextInput"/>
        <TextBlock HorizontalAlignment="Right" Margin="5,0">g</TextBlock>
    </Grid>


    private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var tb = sender as TextBox;
        e.Handled = !double.TryParse(tb.Text+e.Text, out double d);
    }

P.S. si vous n'aimez pas l'utilisation de try Catch, vous pouvez utiliser une expression régulière pour cela


10 commentaires

Elle devrait utiliser un Regex car elle veut autoriser des chaînes comme "105g" ou "11mg" si je comprends bien.


Je ne pense pas qu'elle veuille avoir un "g" dans la chaîne réelle qui rendrait le traitement plus difficile inutilement difficile, je suppose qu'elle veut montrer son gramme ou mg. Cela pourrait être facilement réalisé en superposant un bloc de texte (modifié ma réponse)


Est-ce que cela viole le modèle MVVM, parce que je l'utilise?


il viole en effet le modèle MVVM si vous le faites dans le code derrière! Mais pour atteindre votre objectif, il n'y a tout simplement pas d'autre moyen d'empêcher les entrées de l'utilisateur. C'est pourquoi j'ai recommandé de créer un contrôle personnalisé afin que cela n'entre pas en conflit avec le modèle MVVM de votre projet.Je travaille également personnellement avec le modèle MVVM, mais j'utilise parfois des gestionnaires d'événements dans mes contrôles personnalisés, car MVVM a ses limites


Il ne viole pas MVVM si vous écrivez un contrôle / modèle qui n'autorise qu'une entrée spécifique. En fait, vous pouvez l'implémenter en tant que zones de texte de comportement.


@jaster pourriez-vous s'il vous plaît élaborer un peu plus?


De plus, je vous recommande de regarder de plus près l ' événement utilisé. Par exemple, PreviewTextInput n'empêche pas les chaînes mal formées lorsque vous collez quelque chose dans la TextBox . Et veuillez ne pas utiliser ce modèle Try / catch pour vérifier quelque chose quand il y a une méthode spécifique pour la conversion -> Double.TryParse () .


@Danis Schaf Je veux réellement de l'utilisateur à l'unité d'entrée (par exemple 5,653 mg), puis je convertis cette chaîne en une valeur double en grammes. Je ne sais pas s'il existe une meilleure approche pour cela, mais je veux simplement simplifier l'utilisateur pour qu'il saisisse 5,653 mg au lieu de 0,00563 g


Vous devez également gérer l'événement PreviewKeyDown, car PreviewTextInput n'est pas déclenché sur l'entrée de caractères d'espaces. Et vous devez également gérer l'événement attaché DataObject.Pasting pour empêcher l'utilisateur de coller du texte non valide à partir du clavier.


@LittleBit Merci! je ne savais pas pour tryparse!



1
votes

Vous devez en fait gérer trois événements:

  • PreviewTextInput
  • PreviewKeyDown - pour empêcher la saisie de caractères d'espacement, car ils ne sont pas gérés par PreviewTextInput
  • DataObject.Pasting événement joint pour empêcher l'utilisateur de coller du texte non valide à partir du presse-papiers

Il vaut mieux encapsuler cette logique dans un comportement. A ont des exemples de comportements similaires: TextBoxIntegerInputBehavior , TextBoxDoubleInputBehavior .


0 commentaires

0
votes

Vous pouvez utiliser une expression régulière avec les événements PreviewTextInput , DataObject.Pasting et PreviewKeyDown sur une TextBox pour vérifier si la nouvelle chaîne correspond à une regex , si ce n'est pas le cas, vous pouvez annuler l'opération.

Comme ça:

xaml code >:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight"

...

<TextBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="PreviewTextInput">
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=PreviewTextInputCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
        <i:EventTrigger EventName="DataObject.Pasting">
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=DataObject_PastingCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
        <i:EventTrigger EventName="PreviewKeyDown">
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=PreviewKeyDownCommand}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

Code derrière:

public partial class MainWindow : Window
{
    private Regex gramOrMilliGramRegex = new Regex("^[0-9.-]+(m?g)?$");

    public MainWindow ()
    {
        InitializeComponent();
    }

    private void txtbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        if(sender is TextBox txtbox)
        {
            string newString = txtbox.Text.Substring(0, txtbox.CaretIndex) + e.Text + txtbox.Text.Substring(txtbox.CaretIndex); //Build the new string
            e.Handled = !gramOrMilliGramRegex.IsMatch(e.Text); //Check if it matches the regex
        }

    }

    private void txtbox_Pasting(object sender, DataObjectPastingEventArgs e)
    {
        if(sender is TextBox txtbox)
        {
            string newString = txtbox.Text.Substring(0, txtbox.CaretIndex) + e.DataObject.GetData(typeof(string)) as string + txtbox.Text.Substring(txtbox.CaretIndex); //Build new string
            if (!digitOnlyRegex.IsMatch(newString)) //Check if it matches the regex
            {
                e.CancelCommand();
            }
        }

    private void txtbox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        //Prevents whitespace
        if (e.Key == Key.Space)
        {
            e.Handled = true;
        }
        base.OnPreviewKeyDown(e);
    }
}


MISE À JOUR: Comme vous le mentionnez maintenant, vous utilisez MVVM et ne voulez pas enfreindre le modèle.

Vous devrez acheminer ces événements vers les commandes de votre ViewModel et placer le événements ci-dessus.

Vous pouvez le faire en utilisant ce code dans votre TextBox dans le xaml:

...
<TextBox PreviewTextInput="txtbox_PreviewTextInput" DataObject.Pasting="txtbox_Pasting" PreviewKeyDown="txtbox_PreviewKeyDown" />
...


4 commentaires

Cela enfreint le modèle MVVM, que j'ai dit que je l'utilisais dans les commentaires. J'ai modifié le message maintenant


@TatjanaCerovic Mis à jour pour pouvoir être implémenté sans violer le modèle MVVM.


Les commandes @Alfie Routing pour modifier votre interface graphique en un ViewModel enfreindront toujours le modèle MVVM même si le code derrière reste vide ...


@TatjanaCerovic pour être honnête, je pense que la solution originale est tout à fait correcte en ce qui concerne MVVM, c'est le travail des vues pour gérer les entrées de l'utilisateur et interagir avec le modèle de vue, donc la validation de l'entrée de l'utilisateur est la responsabilité de la vue et ne rompt pas le modèle MVVM . C'est un sujet un peu arbitraire cependant, il n'y a pas de mauvaise ou bonne réponse ici, je ne veux pas lancer un débat, juste mon avis.



0
votes

En raison de la nature de cette entrée, je vous suggère de créer un petit CustomControl , plus spécifique un TextBox qui est capable de limiter la Input et convertissez le Text en la valeur correspondante -> un GramTextBox .

Le GramTextBox a un DependencyProperty appelé Gram qui représente la valeur du Text saisi et peut être lié à un ViewModel (REMARQUE: la liaison doit contenir Mode = TwoWay car GramTextBox essaie de mettre à jour la Source) liée .

<DataGrid AutoGenerateColumns="False" ... >
    <DataGrid.Columns>
       <!-- Put some other Columns here like DataGridTextColumn -->
       <DataGridTemplateColumn Header="Mass">
           <DataGridTemplateColumn.CellTemplate>
               <DataTemplate>
                   <cc:GramTextBox Gram="{Binding VMDoubleProperty, Mode=TwoWay}" ... />
               </DataTemplate>
           </DataGridTemplateColumn.CellTemplate>
       </DataGridTemplateColumn>
       <!-- Put some other Columns here -->
   </DataGrid.Columns>
</DataGrid> 

Utilisation

Mettez cette Classe dans YOURNAMESPACE et dans le XAML ajouter un alias d'espace de noms

<cc:GramTextBox Gram="{Binding VMDoubleProperty, Mode=TwoWay}" ... />

La GramTextBox peut maintenant être utilisée comme ceci

xmlns:cc="clr-namespace:YOURNAMESPACE"

il mettra à jour la Propriété liée dans ViewModel chaque fois que le Texte du Modifications de GramTextBox (par exemple. entrées valides du clavier / coller, etc.).

Notes

Il est prévu que les entrées absurdes telles que .00g , 0.0m , .mg définissez la Gram Property sur 0 (comme une valeur de repli) .

Note personnelle

Merci @Pavel pour le PasteHandler

Modifier strong>

Pour utiliser ce GramTextBox dans un DataGrid , vous pouvez remplacer le CellTemplate de la Colonne :

public sealed class GramTextBox : TextBox
{
    //Constructor
    public GramTextBox() : base()
    {
        Text = "0g"; //Initial value
        TextChanged += OnTextChanged;
        DataObject.AddPastingHandler(this, OnPaste);
    }

    //Style override (get the Style of a TextBox for the GramTextBox)
    static GramTextBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(GramTextBox), new FrameworkPropertyMetadata(typeof(TextBox)));
    }

    //Define a DependencyProperty to make it bindable (dont forget 'Mode=TwoWay' due the bound value is updated from this GramTextBox)
    [Category("Common"), Description("Converted double value from the entered Text in gram")]
    [Browsable(true)]
    [Bindable(true)]
    public double Gram
    {
        get { return (double)GetValue(PathDataProperty); }
        set { SetCurrentValue(PathDataProperty, value); }
    }
    public static DependencyProperty PathDataProperty = DependencyProperty.Register("Gram", typeof(double), typeof(GramTextBox), new PropertyMetadata(0d));

    //Extract the Gram value when Text has changed
    private void OnTextChanged(object sender, TextChangedEventArgs e)
    {
        ExtractGram(Text);
    }

    //Suppress space input
    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        e.Handled = e.Key == Key.Space;
    }

    //Check text inputs
    protected override void OnPreviewTextInput(TextCompositionEventArgs e)
    {
        e.Handled = !IsValidText(Text.Insert(CaretIndex, e.Text));
    }

    //check paste inputs
    private void OnPaste(object sender, DataObjectPastingEventArgs e)
    {
        //Check if pasted object is string
        if(e.SourceDataObject.GetData(typeof(string)) is string text)
        {
            //Check if combined string is valid
           if(!IsValidText(Text.Insert(CaretIndex, text))) { e.CancelCommand(); }
        }
        else { e.CancelCommand(); }
    }

    //Check valid format for extraction (supports incomplete inputs like 0.m -> 0g)
    private bool IsValidText(string text)
    {
        return Regex.IsMatch(text, @"^([0-9]*?\.?[0-9]*?m?g?)$");
    }

    //Extract value from entered string
    private void ExtractGram(string text)
    {
        //trim all unwanted characters (only allow 0-9 dots and m or g)
        text = Regex.Replace(text, @"[^0-9\.mg]", String.Empty);
        //Expected Format -> random numbers, dots and couple m/g

        //trim all text after the letter g 
        text = text.Split('g')[0];
        //Expected Format -> random numbers, dots and m

        //trim double dots (only one dot is allowed)
        text = Regex.Replace(text, @"(?<=\..*)(\.)", String.Empty);
        //Expected Format -> random numbers with one or more dots and m

        //Check if m is at the end of the string to indicate milli (g was trimmed earlier)
        bool isMilli = text.EndsWith("m");

        //Remove all m, then only a double number should remain
        text = text.Replace("m", String.Empty);
        //Expected Format -> random numbers with possible dot

        //trim all leading zeros
        text = text.TrimStart(new char[] { '0' });
        //Expected Format -> random numbers with possible dot

        //Check if dot is at the beginning
        if (text.StartsWith(".")) { text = $"0{text}"; }
        //Expected Format -> random numbers with possible dot

        //Check if dot is at the end
        if (text.EndsWith(".")) { text = $"{text}0"; }
        //Expected Format -> random numbers with possible dot

        //Try to convert the remaining String to a Number, if it fails -> 0
        Double.TryParse(text, out double result);

        //Update Gram Property (divide when necessary)
        Gram = (isMilli) ? result / 1000d : result;
    }
}


2 commentaires

Puis-je utiliser une méthode similaire pour datagrid? Parce qu'il y a des cas où l'utilisateur écrit des données dans datagrid?


@TatjanaCerovic Bien sûr, mais vous devez le définir comme CellTemplate du DataGrid et pouvez définir les autres Colonnes manuellement. Détails voir Modifier.