WPF – TextBox Input Behavior

Hier mal ein nettes Behavior was ich recht häufig benutze, um Eingaben in einer TextBox zu beschränken. Die Anwendung ist ganz einfach:

<TextBox>
    <i:Interaction.Behaviors>
       <behaviors:TextBoxInputBehavior InputMode="DigitInput"/>
    </i:Interaction.Behaviors>
</TextBox>

Und hier der Code dazu:

    public class TextBoxInputBehavior : Behavior<TextBox>
    {
        const NumberStyles validNumberStyles = NumberStyles.AllowDecimalPoint |
                                                   NumberStyles.AllowThousands |
                                                   NumberStyles.AllowLeadingSign;
        public TextBoxInputBehavior()
        {
            this.InputMode = TextBoxInputMode.None;
            this.JustPositivDecimalInput = false;
            this.MaxVorkommastellen = null;
        }

        public TextBoxInputMode InputMode { get; set; }

        public ushort? MaxVorkommastellen { get; set; }
        
        public static readonly DependencyProperty JustPositivDecimalInputProperty =
         DependencyProperty.Register("JustPositivDecimalInput", typeof(bool),
         typeof(TextBoxInputBehavior), new FrameworkPropertyMetadata(false));

        public bool JustPositivDecimalInput
        {
            get { return (bool)GetValue(JustPositivDecimalInputProperty); }
            set { SetValue(JustPositivDecimalInputProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.PreviewTextInput += AssociatedObjectPreviewTextInput;
            AssociatedObject.PreviewKeyDown += AssociatedObjectPreviewKeyDown;

            DataObject.AddPastingHandler(AssociatedObject, Pasting);

        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.PreviewTextInput -= AssociatedObjectPreviewTextInput;
            AssociatedObject.PreviewKeyDown -= AssociatedObjectPreviewKeyDown;

            DataObject.RemovePastingHandler(AssociatedObject, Pasting);
        }

        private void Pasting(object sender, DataObjectPastingEventArgs e)
        {
            if (e.DataObject.GetDataPresent(typeof(string)))
            {
                var pastedText = (string)e.DataObject.GetData(typeof(string));

                if (!this.IsValidInput(this.GetText(pastedText)))
                {
                    System.Media.SystemSounds.Beep.Play();
                    e.CancelCommand();
                }
            }
            else
            {
                System.Media.SystemSounds.Beep.Play();
                e.CancelCommand();
            }
        }

        private void AssociatedObjectPreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Space)
            {
                if (!this.IsValidInput(this.GetText(" ")))
                {
                    System.Media.SystemSounds.Beep.Play();
                    e.Handled = true;
                }
            }

            if (e.Key == Key.Back)
            {
                //wenn was selektiert wird dann wird nur das gelöscht mit BACK
                if (this.AssociatedObject.SelectionLength > 0)
                {
                    if (!this.IsValidInput(this.GetText("")))
                    {
                        System.Media.SystemSounds.Beep.Play();
                        e.Handled = true;
                    }
                }
                else if(this.AssociatedObject.CaretIndex > 0)
                {
                    //selber löschen
                    var txt = this.AssociatedObject.Text;
                    var backspace = txt.Remove(this.AssociatedObject.CaretIndex - 1, 1);

                    if (!this.IsValidInput(backspace))
                    {
                        System.Media.SystemSounds.Beep.Play();
                        e.Handled = true;
                    }
                }
            }

            if (e.Key == Key.Delete)
            {
                //wenn was selektiert wird dann wird nur das gelöscht mit ENTF
                if (this.AssociatedObject.SelectionLength > 0)
                {
                    if (!this.IsValidInput(this.GetText("")))
                    {
                        System.Media.SystemSounds.Beep.Play();
                        e.Handled = true;
                    }
                }
                else if (this.AssociatedObject.CaretIndex < this.AssociatedObject.Text.Length)
                {
                    //selber löschen
                    var txt = this.AssociatedObject.Text;
                    var entf = txt.Remove(this.AssociatedObject.CaretIndex, 1);

                    if (!this.IsValidInput(entf))
                    {
                        System.Media.SystemSounds.Beep.Play();
                        e.Handled = true;
                    }
                }
            }
        }

        private void AssociatedObjectPreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            if (!this.IsValidInput(this.GetText(e.Text)))
            {
                System.Media.SystemSounds.Beep.Play();
                e.Handled = true;
            }
        }

        private string GetText(string input)
        {
            var txt = this.AssociatedObject;

            int selectionStart = txt.SelectionStart;
            if (txt.Text.Length < selectionStart) 
                selectionStart = txt.Text.Length;

            int selectionLength = txt.SelectionLength;
            if (txt.Text.Length < selectionStart + selectionLength) 
                selectionLength = txt.Text.Length - selectionStart;

            var realtext = txt.Text.Remove(selectionStart, selectionLength);

            int caretIndex = txt.CaretIndex;
            if (realtext.Length < caretIndex) 
                caretIndex = realtext.Length;

            var newtext = realtext.Insert(caretIndex, input);

            return newtext;
        }

        private bool IsValidInput(string input)
        {
            if (input.Length == 0)
                return true;

            switch (InputMode)
            {
                case TextBoxInputMode.None:
                    return true;
                case TextBoxInputMode.DigitInput:
                    return CheckIsDigit(input);

                case TextBoxInputMode.DecimalInput:
                    decimal d;
                    //wen mehr als ein Komma
                    if (input.ToCharArray().Where(x => x == ',').Count() > 1)
                        return false;

                    if (input.Contains("-"))
                    {
                        if (this.JustPositivDecimalInput) 
                            return false;

                        
                        if (input.IndexOf("-",StringComparison.Ordinal) > 0) 
                            return false;

                        if(input.ToCharArray().Count(x=>x=='-') > 1)
                            return false;

                        //minus einmal am anfang zulässig
                        if (input.Length == 1) 
                            return true;
                    }
                   
                    var result = decimal.TryParse(input, validNumberStyles, CultureInfo.CurrentCulture, out d);
                    return result;
                    
                case TextBoxInputMode.PercentInput: //99,999 is zulässig und  nur positiv ohne 1000er Trennzeichen
                    float f;

                    if (input.Contains("-"))
                        return false;
                    //wen mehr als ein Komma
                    if (input.ToCharArray().Where(x => x == ',').Count() > 1)
                        return false;

                    var percentResult = float.TryParse(input, NumberStyles.Float, CultureInfo.CurrentCulture, out f);

                    if (MaxVorkommastellen.HasValue)
                    {
                        var vorkomma = Math.Truncate(f);
                        if (vorkomma.ToString(CultureInfo.CurrentCulture).Length > MaxVorkommastellen.Value)
                            return false;
                    }

                    return percentResult;
                    
                default: throw new ArgumentException("Unknown TextBoxInputMode");

            }
            return true;
        }

        private bool CheckIsDigit(string wert)
        {
            return wert.ToCharArray().All(Char.IsDigit);
        }
    }

    public enum TextBoxInputMode
    {
        None,
        DecimalInput,
        DigitInput,
        PercentInput
    }
Veröffentlicht unter Behavior - System.Windows.Interactivity | Kommentar hinterlassen

WPF – DataTable and multiple Views

In reference to the following stackoverflow question.

Download demo source code from here. NOTE: Rename the file extension from .DOC to .ZIP and then decompress it.

Veröffentlicht unter Wpf Data Binding | Verschlagwortet mit , , | Kommentar hinterlassen

WPF – MVVM, IDataErrorInfo, Validation.ErrorTemplate

Das ist das Ergebnis meiner Anforderungen bzgl. der Darstellung von Validierungsfehlern/Pflichtfeldern. Pflichtfelder sollten einfach durch ein * gekennzeichnet sein und fehlerhafte Eingaben sollten sowohl als Tooltip als auch durch ein Adorner (i) angezeigt werden. Die übliche rote Umrandung der Textboxen darf natürlich jeder gerne selber einfügen 😉

Damit das ganze funktioniert, habe ich erst einmal 2 Control Templates angelegt. Ein ControlTemplate für die „normale“ Validierung und ein zweites für die Anzeige von Pflichtfeldern.

<SolidColorBrush x:Key="BrushError">RED</SolidColorBrush>
<ControlTemplate x:Key="ValidationTemplate" >
 <DockPanel>
  <AdornedElementPlaceholder Name="MyAdornedElement" />
  <ToggleButton x:Name="MyInfo"
                Focusable="False"
                Visibility="{Binding ElementName=MyAdornedElement,Path=AdornedElement.Visibility}">
  <ToggleButton.Template>
   <ControlTemplate>
    <Border Margin="2,2,2,0" Height="20" Width="20" CornerRadius="10" BorderBrush="{StaticResource BrushError}" BorderThickness="1" Background="Transparent"
 VerticalAlignment="Top" HorizontalAlignment="Center"
 RenderTransformOrigin=".5,.5"
 ToolTip="Klick für weitere Informationen">
    <TextBlock Text="i" Foreground="{StaticResource BrushError}" FontWeight="Bold" HorizontalAlignment="Center"
 ToolTip="Klick für weitere Informationen"/>
   <Border.LayoutTransform>
    <ScaleTransform ScaleX=".8" ScaleY=".8"/>
   </Border.LayoutTransform>
  </Border>
 </ControlTemplate>
 </ToggleButton.Template>
 </ToggleButton>
 <Popup IsOpen="{Binding ElementName=MyInfo, Path=IsChecked}" PlacementTarget="{Binding ElementName=MyInfo}" Placement="Right" AllowsTransparency="True">
 <Grid Visibility="{Binding ElementName=MyAdornedElement,Path=AdornedElement.Visibility}">
 <Border Background="{StaticResource BrushError}" Margin="3,0,0,0" x:Name="ErrorControl" BorderBrush="White" BorderThickness="1">
 <TextBlock Margin="10,3,5,2" TextTrimming="WordEllipsis" TextWrapping="Wrap"
 Text="{Binding ElementName=MyAdornedElement,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"
 Foreground="White" FontWeight="Bold">
 </TextBlock>
 </Border>
 <Path x:Name="path" Margin="3,0,0,0" Data="M 0,10 L 10,0 " Fill="{StaticResource BrushError}"
 StrokeThickness="2" Stroke="White" />
 <Rectangle IsHitTestVisible="True" Fill="Transparent" ToolTip="{Binding ElementName=MyAdornedElement,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
 </Grid>
 </Popup>
 </DockPanel>
 </ControlTemplate>

<ControlTemplate x:Key="ValidationTemplateEmpty">
 <DockPanel>
 <TextBlock Text="*" Margin="0,0,3,0" Foreground="Red" Visibility="{Binding ElementName=MyAdornedElement,Path=AdornedElement.Visibility}"
 ToolTip="{Binding ElementName=MyAdornedElement,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
 <AdornedElementPlaceholder Name="MyAdornedElement" />
 </DockPanel>
 </ControlTemplate>

Vielleicht noch eine kurze Anmerkung. Ich hatte in meiner ersten Version einen Adorner benutzt und kein Popup. Das Problem mit Adornern ist, dass diese nur in AdornerDecorator Grenzen angezeigt werden können. Bestimmte Controls wie ein ScrollViewer haben diese AdornerDecorator im ControlTemplate und somit konnte mein ValidationAdorner nicht über diese Grenzen hinaus angezeigt werden. Das Popup hat dieses Problem zwar nicht, aber dafür andere Nebeneffekte. Damit kann ich aber leben 😉
Mit den 2 ControlTemplates sind die Grundvorraussetzungen also geschaffen. Nun zu den TextBoxen. Das Ziel habe ich ja Eingangs schon formuliert. Normalerweise sollten ja nun folgende Bedingungen reichen, damit alles so läuft wie gewollt. Zum einen muss die entsprechende TextBox ein Validierungsfehler haben, damit etwas angezeigt wird. Und zum anderen sollte dann unterschieden werden, ob es nur die Anzeige als Pflichtfeld ist oder ein „richtiger“ Validierungsfehler vorliegt.

Folgendes Problem hatte ich dabei nicht bedacht: Bei der Bindingeinstellung LostFocus, will der Nutzer ja erst seine Eingaben machen und dann beim Verlassen des Feldes, soll die Validierung durchgeführt werden. Soweit so gut, aber das hatte in der ersten Version meiner Validierung eine unschöne Auswirkung. sobald Text in der TextBox eingetragen war, hat mein Validation.ErrorTemplate umgeschaltet auf das normale Validierungstemplate und mein roter * für die Pflichtfeldanzeige war nicht mehr da.

Einen wirklich schönen Weg das ganze zu Umgehen habe ich nicht gefunden. Ich benutze jetzt noch eine zusätzliche Regel, um meine Pflichfeld Validierungen anzuzeigen. Und zwar prüfe ich die Fehlermeldung, ob in dieser das Wort „pflichtfeld“ vorkommt. Damit sieht der Style für meine TextBoxen wie folgt aus.

<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplate}"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="true">
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
</Trigger>
 <MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="True">
<Condition.Binding>
<MultiBinding Converter="{StaticResource ValidationPflichtfeldMultiConverter}">
<Binding Path="(Validation.HasError)" RelativeSource="{RelativeSource self}" />
<Binding Path="Text" RelativeSource="{RelativeSource self}" />
<Binding Path="(Validation.Errors).CurrentItem.ErrorContent" RelativeSource="{RelativeSource self}" />
</MultiBinding>
</Condition.Binding>
</Condition>
 </MultiDataTrigger.Conditions>
 <MultiDataTrigger.Setters>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationTemplateEmpty}"/>
<Setter Property="ToolTip" Value="{Binding Path=(Validation.Errors).CurrentItem.ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
</MultiDataTrigger.Setters>
 </MultiDataTrigger>
 </Style.Triggers>
 </Style>

Der Vollständigkeit halber noch der MultiConverter für das Auswerten der Regeln. Wenn der Converter „true“ zurück gibt, soll das ValidationTemplateEmpty für Pflichtfelder angezeigt werden.

public class ValidationErrorPflichtfeldMultiConverter : IMultiValueConverter
{
 #region Implementation of IMultiValueConverter

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
 {

if (values.Length != 3)
 return false;

//values[0] = Validation.HasError
 if (!(values[0] is bool))
 return false;

if (!(bool)values[0])
 return false;

//values[1] = Text vom TextBoxControl
 var s = (string)values[1];

if (string.IsNullOrWhiteSpace(s))
 return true;

//value[2] = (Validation.Errors).CurrentItem.ErrorContent
 var err = (string)values[2];

if (err.ToLower().Contains("pflichtfeld"))
 return true;

return false;
 }

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
 {
 throw new NotImplementedException();
 }

#endregion
 }

Für Hinweise und Verbesserungsvorschläge oder gänzlich andere Lösungsmöglichkeiten, bin ich natürlich dankbar.

Veröffentlicht unter MVVM | Verschlagwortet mit , , , | Kommentar hinterlassen

WPF – Watermark Textbox Behavior

Jeder kennt die schönen Ui’s wo  Eingabecontrols mittels  „Wasserzeichen“ aufgewertet werden. Wenn man ein wenig googelt findet man auch entsprechende Lösungen im Internet. Ich hab das ganze hier im Blog mal wieder als Behavior implementiert und am Ende sieht es dann so aus.

Alles was man dafür im Xaml schreiben muss, ist folgendes:

Im Label wird der anzuzeigende Text angegeben und im LabelStyle kann man das Aussehen des Wasserzeichens ein wenig gestalten.

Das TextBoxWatermark Behavior gestaltet sich relativ simple. Man benötigt 2 Dependency Properties: Label und LabelStyle:

Ich benutze einen einfachen TextBlockAdorner für die Anzeige des Wasserzeichen (Falls jemand einen besseren Adorner mit mehr Gestaltungsmöglichkeiten bei der Hand hat, lasst es mich wissen :)). Im Behavior wird dann zum einen der Load Event der TextBox benutzt und zum anderen der TextChanged Event.

Im TextChanged Event wird die Prüfung für die Anzeige des Wasserzeichen durchgeführt. Für mich war das irgendwie die einzige Möglichkeit im Behavior zu erkennen, ob z.B. mittels Binding sich die Text Eigenschaft geändert hat. Im Load Event wird der TextBlockAdorner angelegt. Ausserdem wird sich für die Änderung von IsFocused der TextBox registriert, da ja bei jedem Focus Wechsel wieder geprüft werden muss, ob man das Wasserzeichen anzeigen soll oder nicht. In meiner 1ten Version hatte ich dafür folgendes benutzt:

Aber damit holt man sich ein MemoryLeak in den Code. Wer das ganze einmal genauer nachlesen will dem sei folgender Post von Adrew Smith ans Herz gelegt. Ich habe den Code einfach übenommen. Die eigentliche Logik ob das Wasserzeichen angezeigt werden soll oder nicht, sieht dann wie folgt aus:

Sobald das Control den Focus hat oder die Text Eigenschaft gefüllt ist wird das Wasserzeichen nicht mehr angezeigt.

Man kann natürlich das ganze auch für eine Combobox oder PasswordBox machen, dazu muss man lediglich beim Behavior den entsprechenden Typ angeben und ggf andere Events beachten.

Download demo source code from here. NOTE: Rename the file extension from .DOC to .ZIP and then decompress it.

Veröffentlicht unter Behavior - System.Windows.Interactivity | Verschlagwortet mit , , , | 3 Kommentare

WPF – Masked Textbox Behavior

Pls use the lastest Version of my Behavior at the end of this Blog.

Auf der Suche nach einer MaskedTextbox für Wpf bin ich über die Implementierung von Marlon Grech gestolpert. In Anlehnung daran hab ich das ganze als Behavior für mein Projekt implementiert.

MasekdTextbox Behavior Sample

Die Grundlage für die MaskedTextBox ist der MaskedTextProvider, dieser übernimmt letztendlich das Auswerten der Eingaben bzgl. der InputMaske.

Was soll/muss das Behavior alles leisten:

  • InputMaske und PromptChar als Parameter
  • Eingaben, BackSpace, Delete Key etc. prüfen
  • Clipboard Pasting prüfen
  • Selektierten Text beachten
  • Ui + MVVM/CodeBehind in sync

Für die Parameter werden 2 DependencyProperties angelegt.

Folgende Events müssen behandelt werden:

Neben der Initialisierung des MasekdTextProvider in AssociatedObjectLoaded wird auch noch ein Eventhandler an das TextProperty der TextBox angehangen. Sollte das TextProperty der TextBox durch einen Converter, Binding(zb. MVVM) oder im CodeBehind geändert werden, kann man in diesem Eventhandler darauf reagieren.

Die Behandlung von Nutzereingaben sieht immer recht ähnlich aus. Zu beachten ist in jedem Fall ob der Nutzer in der TextBox Text selektiert hat und diesen dann entsprechend zu behandeln (TreatSelectedText();). Alles weitere übernimmt mehr oder weniger der MaskedTextProvider.

Was macht die UpdateText() Methode? Letzendlich wird hier nocheinmal geprüft ob der Text in dem MaskedTextProvider gleich dem Text der TextBox ist. Solange die Eingaben in die TextBox von „oben(Target)“ kommen, sollte dies auch immer der Fall sein. Sobald aber Eingaben von „unten(Source)“ kommen, ist das nicht unbedingt gewährleistet. Daher muss man sich jetzt auch entscheiden, was passieren soll wenn der Input aus der Source nicht mit der InputMaske der MaskedTextBox zusammen passt. Meine Philosophie lautet in diesem Fall: Ui + Source have to be in sync! D.h. in der Oberfläche sollen niemals Werte angezeigt werden, die nicht auch in der Quelle so sind und umgekehrt 🙂 Deshalb zeigt die UpdateText() Methode die Daten der Source an, wenn die InputMaske nicht passen sollte.

So sieht das ganze dann im XAML aus.

Und immer daran denken eine MaskedTextBox hat nichts mit VALIDIERUNG zu tun.

Download demo source code from here. NOTE: Rename the file extension from .DOC to .ZIP and then decompress it.

Hier mal mein letzter Stand zu dem Behavior:

    public class TextBoxInputMaskBehavior : Behavior<TextBox>
    {
        private WeakPropertyChangeNotifier _notifier;

        #region DependencyProperties

        public static readonly DependencyProperty InputMaskProperty =
          DependencyProperty.Register("InputMask", typeof(string), typeof(TextBoxInputMaskBehavior), null);

        public string InputMask
        {
            get { return (string)GetValue(InputMaskProperty); }
            set { SetValue(InputMaskProperty, value); }
        }

        public static readonly DependencyProperty PromptCharProperty =
           DependencyProperty.Register("PromptChar", typeof(char), typeof(TextBoxInputMaskBehavior), new PropertyMetadata('_'));

        public char PromptChar
        {
            get { return (char)GetValue(PromptCharProperty); }
            set { SetValue(PromptCharProperty, value); }
        }

        public static readonly DependencyProperty ResetOnSpaceProperty =
           DependencyProperty.Register"ResetOnSpace", typeof(bool), typeof(TextBoxInputMaskBehavior), new PropertyMetadata(false));

        public bool ResetOnSpace
        {
            get { return (bool)GetValue(ResetOnSpaceProperty); }
            set { SetValue(ResetOnSpaceProperty, value); }
        }

        public static readonly DependencyProperty IgnorSpaceProperty =
          DependencyProperty.Register("IgnorSpace", typeof(bool), typeof(TextBoxInputMaskBehavior), new PropertyMetadata(true));

        public bool IgnorSpace
        {
            get { return (bool)GetValue(IgnorSpaceProperty); }
            set { SetValue(IgnorSpaceProperty, value); }
        }

        #endregion

        public MaskedTextProvider Provider { get; private set; }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += AssociatedObjectLoaded;
            AssociatedObject.PreviewTextInput += AssociatedObjectPreviewTextInput;
            AssociatedObject.PreviewKeyDown += AssociatedObjectPreviewKeyDown;

            DataObject.AddPastingHandler(AssociatedObject, Pasting);
        }
        
        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.Loaded -= AssociatedObjectLoaded;
            AssociatedObject.PreviewTextInput -= AssociatedObjectPreviewTextInput;
            AssociatedObject.PreviewKeyDown -= AssociatedObjectPreviewKeyDown;

            DataObject.RemovePastingHandler(AssociatedObject, Pasting);
        }

        /*
        Mask Character  Accepts  Required?  
        0  Digit (0-9)  Required  
        9  Digit (0-9) or space  Optional  
        #  Digit (0-9) or space  Required  
        L  Letter (a-z, A-Z)  Required  
        ?  Letter (a-z, A-Z)  Optional  
        &amp;amp;  Any character  Required  
        C  Any character  Optional  
        A  Alphanumeric (0-9, a-z, A-Z)  Required  
        a  Alphanumeric (0-9, a-z, A-Z)  Optional  
           Space separator  Required 
        .  Decimal separator  Required  
        ,  Group (thousands) separator  Required  
        :  Time separator  Required  
        /  Date separator  Required  
        $  Currency symbol  Required  

        In addition, the following characters have special meaning:

        Mask Character  Meaning  
        <  All subsequent characters are converted to lower case  
        >  All subsequent characters are converted to upper case  
        |  Terminates a previous &amp;lt; or &amp;gt;  
        \  Escape: treat the next character in the mask as literal text rather than a mask symbol  

        */
        void AssociatedObjectLoaded(object sender, System.Windows.RoutedEventArgs e)
        {
            this.Provider = new MaskedTextProvider(InputMask, CultureInfo.CurrentCulture);
            this.Provider.PromptChar = this.PromptChar;
            this.Provider.SkipLiterals = true;
            this.Provider.ResetOnSpace = this.ResetOnSpace;
            this.Provider.Set(HandleCharacterCasing(AssociatedObject.Text));
            this.AssociatedObject.AllowDrop = false;

            this.AssociatedObject.Text = GetProviderText();
           

            //seems the only way that the text is formatted correct, when source is updated
            //AddValueChanged for TextProperty in a weak manner
            this._notifier = new WeakPropertyChangeNotifier(this.AssociatedObject, TextBox.TextProperty);
            this._notifier.ValueChanged += new EventHandler(this.UpdateText);          
        }
        void AssociatedObjectPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
        {
#if DEBUG
            Debug("PreviewTextInput");
#endif
            e.Handled = true;
            var text = HandleCharacterCasing(e.Text);

            this.TreatSelectedText();

            var position = this.GetNextCharacterPosition(AssociatedObject.CaretIndex);

            if (Keyboard.IsKeyToggled(Key.Insert))
            {
                if(!this.Provider.Replace(text, position))
                {
                    System.Media.SystemSounds.Beep.Play();
                    return;
                }
            }
            else
            {
                if(!this.Provider.InsertAt(text, position))
                {
                    System.Media.SystemSounds.Beep.Play();
                    return;
                }
            }

            var nextposition = this.GetNextCharacterPosition(position + 1);
            this.RefreshText(nextposition);
        }

        void AssociatedObjectPreviewKeyDown(object sender, KeyEventArgs e)
        {
            //WICHTIG: TreatSelectedText oder sonst was nur in den IF's behandeln, weil KeyDown immer als erstes kommt
#if DEBUG
            Debug("PreviewKeyDown");
#endif
            if (e.Key == Key.Space)//handle the space
            {
                e.Handled = true;

                if (this.IgnorSpace)
                {
                    System.Media.SystemSounds.Beep.Play();
                    return;
                }

                this.TreatSelectedText();
                var position = this.GetNextCharacterPosition(AssociatedObject.CaretIndex);

                if (!this.Provider.InsertAt(" ", position))
                {
                    System.Media.SystemSounds.Beep.Play();
                    return;
                }

                this.RefreshText(AssociatedObject.CaretIndex + 1);
            }

            if (e.Key == Key.Back)//handle the back space
            {
                e.Handled = true;

                //wenn etwas markiert war und der nutzer Backspace klickt soll nur das markierte verschwinden
                if(this.TreatSelectedText())
                {
                    this.RefreshText(AssociatedObject.CaretIndex);
                    return;
                }
                
                //wenn man ganz vorne steht gibs nix zu löschen, ausser wenn was selektiert war, s.h.oben
                if(AssociatedObject.CaretIndex == 0)
                    return;

                var denDavor = AssociatedObject.CaretIndex - 1;

                if(this.Provider.IsEditPosition(denDavor))
                {
                    if (!this.Provider.RemoveAt(denDavor))
                    {
                        System.Media.SystemSounds.Beep.Play();
                        return;
                    }
                }

                this.RefreshText(AssociatedObject.CaretIndex - 1);
            }

            if (e.Key == Key.Delete)//handle the delete key
            {
                e.Handled = true;

                //wenn etwas markiert war und der nutzer Delete klickt soll nur das markierte verschwinden
                if (this.TreatSelectedText())
                {
                    this.RefreshText(AssociatedObject.CaretIndex);
                    return;
                }


                var position = AssociatedObject.CaretIndex;

                if (this.Provider.IsEditPosition(position))
                {
                    if (!this.Provider.RemoveAt(position))
                    {
                        System.Media.SystemSounds.Beep.Play();
                        return;
                    }
                }
                else
                {
                    System.Media.SystemSounds.Beep.Play();
                    return;
                }

                this.RefreshText(AssociatedObject.CaretIndex);
            }
        }

        /// <summary>
        /// Pasting prüft ob korrekte Daten reingepastet werden
        /// </summary>
        private void Pasting(object sender, DataObjectPastingEventArgs e)
        {
            //nur strg+c zulassen kein drag&drop
            if (e.DataObject.GetDataPresent(typeof(string)) && !e.IsDragDrop)
            {
                var pastedText = HandleCharacterCasing((string)e.DataObject.GetData(typeof(string)));

                this.TreatSelectedText();

                var position = GetNextCharacterPosition(AssociatedObject.CaretIndex);

                if (!this.Provider.InsertAt(pastedText, position))
                {
                    System.Media.SystemSounds.Beep.Play();
                }
                else
                {
                    this.RefreshText(position);
                    this.AssociatedObject.Focus();
                    
                }
            }

            e.CancelCommand();
        }

        private void UpdateText(object sender, EventArgs eventArgs)
        {
#if DEBUG
            Debug("UpdateText");
#endif
            //check Provider.Text + TextBox.Text
            if (HandleCharacterCasing(this.Provider.ToDisplayString()).Equals(HandleCharacterCasing(AssociatedObject.Text)))
                return;

            //use provider to format
            var success = this.Provider.Set(HandleCharacterCasing(AssociatedObject.Text));

            //ui and mvvm/codebehind should be in sync
            this.SetText(success ? GetProviderText() : HandleCharacterCasing(AssociatedObject.Text));
        }

        private string HandleCharacterCasing(string text)
        {
            switch (AssociatedObject.CharacterCasing)
            {
               case CharacterCasing.Lower:
                    return text.ToLower();
               case CharacterCasing.Upper:
                    return text.ToUpper();

                default:
                    return text;
            }
            
        }

        /// <;summary>
        /// Falls eine Textauswahl vorliegt wird diese entsprechend behandelt.
        /// </summary>
        private bool TreatSelectedText()
        {
            if (AssociatedObject.SelectionLength > 0)
            {
                this.Provider.RemoveAt(AssociatedObject.SelectionStart, AssociatedObject.SelectionStart + AssociatedObject.SelectionLength - 1);
                return true;
            }
            return false;
        }

        private void RefreshText(int position)
        {
            SetText(GetProviderText());

            Debug("SetText");
            AssociatedObject.CaretIndex = position;
        }

        private void SetText(string text)
        {
            AssociatedObject.Text = String.IsNullOrWhiteSpace(text) ? String.Empty : text;
        }

        private int GetNextCharacterPosition(int caretIndex)
        {
            var start = caretIndex + GetAnzahlIncludeLiterals(caretIndex);

            var position = this.Provider.FindEditPositionFrom(start, true);

            if (position == -1)
                return start;
            else
                return position;
        }

        private string GetProviderText()
        {
            //wenn noch gar kein Zeichen eingeben wurde, soll auch nix drin stehen
            //könnte man noch anpassen wenn man masken in der Oberfläche vllt doch haben will bei nem leeren feld
            return this.Provider.AssignedEditPositionCount > 0
                       ? HandleCharacterCasing(this.Provider.ToDisplayString())
                       : HandleCharacterCasing(this.Provider.ToString(false, false));
        }

        private int GetAnzahlIncludeLiterals(int index)
        {
            //todo??
            return anzLiterals;
        }

        private void Debug(string name)
        {
            System.Diagnostics.Debug.WriteLine(name + ": Textbox:  " + AssociatedObject.Text);
            System.Diagnostics.Debug.WriteLine(name + ": Provider: " + Provider.ToDisplayString());
        }
    }
Veröffentlicht unter Behavior - System.Windows.Interactivity | Verschlagwortet mit , , , , | 14 Kommentare

WPF – SelectAll() OnFocus Behavior für TextBox

Einfaches Behavior für die SelectAll() Funktionalität bei Focus für eine TextBox in Wpf.

Im Xaml sieht das ganze dann so aus.

Veröffentlicht unter Behavior - System.Windows.Interactivity | Verschlagwortet mit , , , | 1 Kommentar