Search code examples
c#wpfmvvmdata-bindingicommand

How to trigger TextChanged Event in MVVM with ICommand


I am using 2 textboxes. If the user types something into one of the textboxes, the other textbox will be disabled. If the user deletes all text in one of the textboxes, the other textbox will be re-enabled.

These rules are to make sure that only one textbox can contain text. The textbox with text inside is the search textbox that listens to the search button trigger "Suchen".

Here is how the view looks like:

enter image description here

In order for these rules to work I want to use the TextChanged-Events as ICommands according to MVVM standards. I gave this a try but it doesn't do what I want it to. What does it do? - If I type something inside the "Artikelbezeichnung"-textbox, the "Lieferant"-textbox won't disable and if I delete all text inside "Artikelbezeichnung", the "Lieferant"-textbox will disable (and never re-enable). I believe that I cannot grasp the logic of this strange behaviour and that is why I need your help. I reduced the code to a minimum to make things easier for you.

What do I need to change to make my rules work? Please take a look at the following code and help me out. Thanks a lot for trying!

XAML-View

 <StackPanel Height="423" VerticalAlignment="Bottom">
    <Label Name="lblArtikelbezeichnung" Content="Artikelbezeichnung:" Margin="20, 20, 20, 0"></Label>
    <TextBox Name="txtArtikelbezeichnung" 
             Width="Auto" 
             Margin="20, 0, 20, 0"
             IsEnabled="{Binding BezEnabled}"
             Text="{Binding BezText}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding TextChangedBez}" />
            </i:EventTrigger>
            <i:EventTrigger EventName="KeyUp">
                <i:InvokeCommandAction Command="{Binding KeyUpBez}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>
    <!--TextChanged="txtArtikelbezeichnung_TextChanged" 
             KeyUp="txtArtikelbezeichnung_KeyUp"-->
    <Label Name="lblLieferant" Content="Lieferant:" Margin="20, 0, 20, 0"></Label>
    <TextBox Name="txtLieferant" 
             Width="Auto" 
             Margin="20, 0, 20, 0"
             IsEnabled="{Binding LiefEnabled}"
             Text="{Binding LiefText}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding TextChangedLief}" />
            </i:EventTrigger>
            <i:EventTrigger EventName="KeyUp">
                <i:InvokeCommandAction Command="{Binding KeyUpLief}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>
        <!--TextChanged="txtLieferant_TextChanged" 
             KeyUp="txtLieferant_KeyUp"-->
    <Button Name="btnSuchen" 
            Content="Suchen" 
            Width="100" Height="25" 
            Margin="20, 10,240, 10" 
            Command="{Binding GefilterteSuche}">
    </Button>
...
<StackPanel>

Code Behind

 using System.Windows;

namespace Lieferscheine
{
    /// <summary>
    /// Interaktionslogik für artikelHinzu.xaml
    /// </summary>
    public partial class artikelHinzu : Window
    {

        public artikelHinzu()
        {
            InitializeComponent();
            DataContext = new ArtikelHinzuViewModel();
        }     
    }
}

View Model

public class ArtikelHinzuViewModel : INotifyPropertyChanged
{

    //ICommands
    public ICommand TextChangedLief => new DelegateCommand<object>(TextChangedLieferant);        
    public ICommand TextChangedBez => new DelegateCommand<object>(TextChangedBezeichnung);


    private bool _bezEnabled = true;
    private bool _liefEnabled = true;
    public bool BezEnabled
    {
        get
        {
            return _bezEnabled;
        }
        set
        {
            _bezEnabled = value;
            OnPropertyChanged("BezEnabled");
        }
    }
    public bool LiefEnabled
    {
        get 
        { 
            return _liefEnabled;
        }

        set
        {
            _liefEnabled = value;
            OnPropertyChanged("LiefEnabled");
        }
    }

    private string _bezText;
    private string _liefText;
    public string LiefText
    {
        get
        {
            return _liefText;
        }
        set
        {
            _liefText = value;
            OnPropertyChanged("LiefText");
        }
    }

    public string BezText
    {
        get
        {
            return _bezText;
        }
        set
        {
            _bezText = value;
            OnPropertyChanged("BezText");
        }
    }

    public void TextChangedBezeichnung(object param)
    {
        if (!String.IsNullOrWhiteSpace(BezText))
        {
            LiefEnabled = false;
        }
        else
        {
          LiefEnabled = true;
        }
    }

    public void TextChangedLieferant(object param)
    {
        if (!String.IsNullOrWhiteSpace(LiefText))
        {
           BezEnabled = false;
        }
        else
        {
            BezEnabled = true;
        }
    }
public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    //Konstruktor
    public ArtikelHinzuViewModel()
    {

    }
}

Solution

  • I believe the undesired behavior is caused by Event Racing.

    Note that the data binding mechanism, by default, calls the setter of Text property upon LostFocus of binding target (UI element). But TextChanged Event had been fired before the TextBox lose focus. This causes your command fail to yield the correct logic.

    A quick solution will be

    Text="{Binding BezText, UpdateSourceTrigger=PropertyChanged}">
    

    Of course I don't know your exact situation, but I don't think it is necessary to use ICommand and System.Windows.Interactivity even in a MVVM sense. You might consider the following:

    ViewModel

            public string LiefText
            {
                get
                {
                    return _liefText;
                }
                set
                {
                    _liefText = value;
                    OnPropertyChanged("LiefText");
    
                    if (!String.IsNullOrWhiteSpace(_liefText))
                        BezEnabled = false;
                    else
                        BezEnabled = true;
                }
            }
    
            public string BezText
            {
                get
                {
                    return _bezText;
                }
                set
                {
                    _bezText = value;
                    OnPropertyChanged("BezText");
    
                    if (!String.IsNullOrWhiteSpace(_bezText))
                        LiefEnabled = false;
                    else
                        LiefEnabled = true;
                }
            }
    

    View

            <TextBox Name="txtArtikelbezeichnung" 
                 Width="Auto" 
                 Margin="20, 0, 20, 0"
                 IsEnabled="{Binding BezEnabled}"
                 Text="{Binding BezText, UpdateSourceTrigger=PropertyChanged}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="KeyUp">
                        <i:InvokeCommandAction Command="{Binding KeyUpBez}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBox>
    
           <TextBox Name="txtLieferant" 
                 Width="Auto" 
                 Margin="20, 0, 20, 0"
                 IsEnabled="{Binding LiefEnabled}"
                 Text="{Binding LiefText, UpdateSourceTrigger=PropertyChanged}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="KeyUp">
                        <i:InvokeCommandAction Command="{Binding KeyUpLief}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBox>