pablocity
pablocity

Reputation: 504

How can I properly implement TextChanged event via Commands with MVVM

I'm learning WPF with MVVM pattern. My app is counting Body Mass Index, so it's really simple - just to help me understand the foundations of this pattern.

I was experimenting a little bit and decided to implement TextChanged event via Commands to allow user see changes in overall BMI label while he's typing a height or weight.

My textBoxes in which I use the TextChanged command are binded to ViewModel properties in TwoWay mode, so I thought that if I raise INotifyPropertyChanged event on properties binded to these textBoxes when TextChanged event occurs it will automatically update View, but it doesn't.

So question is, what am I doing wrong and how can I implement it properly?

PS. Everything else excepting View update is working (command is used, I checked with breakpoint it just doesn't change the View)

Thanks in advance

CustomCommand class:

public class CustomCommand : ICommand
{

    Action<object> action;
    Predicate<object> predicate;

    public CustomCommand(Action<object> execute, Predicate<object> canExecute)
    {
        action = execute;
        predicate = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add
        {
            CommandManager.RequerySuggested += value;
        }
        remove
        {
            CommandManager.RequerySuggested -= value;
        }
    }

    public bool CanExecute(object parameter)
    {
        if (predicate(parameter))
            return true;
        else
            return false;
    }

    public void Execute(object parameter)
    {
        action(parameter);
    }
}

One of two textBoxes:

<TextBox HorizontalAlignment="Left" Height="23" Margin="148,83,0,0" TextWrapping="Wrap" Text="{Binding Person.Weight, Mode=TwoWay}" VerticalAlignment="Top" Width="76">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding Path=textChangedCommand}"></i:InvokeCommandAction>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>

And ViewModel, where TextChanged method is passed to a command

public class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler propertyChanged = PropertyChanged;

        if (propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }


    public ICommand textChangedCommand { get; set; }

    //public List<float> BMI_Changed;

    private PersonInfo person;


    public PersonInfo Person
    {
        get
        {
            return person;
        }
        set
        {
            person = value;
            OnPropertyChanged("Person");
        }
    }

    public MainWindowViewModel()
    {
        //BMI_Changed = new List<float>();
        textChangedCommand = new CustomCommand(TextChanged, CanBeChanged);
        person = Data.personInfo;
    }


    private void TextChanged(object obj)
    {
        OnPropertyChanged("BMI");
        OnPropertyChanged("Weight");
        OnPropertyChanged("Height");
    }

    private bool CanBeChanged(object obj)
    {
        return true;
    }
}

Rest of my View code, for general overview:

<Window x:Class="SportCalculators_MVVM.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:SportCalculators_MVVM"
    xmlns:enum="clr-namespace:SportCalculators_MVVM.Model"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    mc:Ignorable="d"
    Title="MainWindow" Height="340.278" Width="260.256" Loaded="Window_Loaded"
    DataContext="{Binding Source={StaticResource viewModelLocator}, Path=mainWindowViewModel}">

<Grid x:Name="grid">


    <Slider x:Name="mass" HorizontalAlignment="Right" Margin="0,128,58,0" VerticalAlignment="Top" Width="155" Value="{Binding Person.Weight, Mode=TwoWay}" Maximum="150" Minimum="20"/>
    <Slider x:Name="height" HorizontalAlignment="Left" Margin="40,210,0,0" VerticalAlignment="Top" Width="155" Minimum="100" Maximum="230" Value="{Binding Person.Height, Mode=TwoWay}"/>
    <RadioButton x:Name="sex" Content="Kobieta" HorizontalAlignment="Left" Margin="45,41,0,0" VerticalAlignment="Top" IsChecked="{Binding Person.Sex, Converter={StaticResource ResourceKey=genderConverter}, ConverterParameter={x:Static enum:Sex.Female}}"/>
    <RadioButton x:Name="sex1" Content="Mężczyzna" HorizontalAlignment="Left" Margin="150,41,0,0" VerticalAlignment="Top" IsChecked="{Binding Person.Sex, Converter={StaticResource ResourceKey=genderConverter}, ConverterParameter={x:Static enum:Sex.Male}}"/>
    <Label x:Name="massLabel" Content="Waga" HorizontalAlignment="Left" Margin="40,80,0,0" VerticalAlignment="Top"/>
    <Label x:Name="heightLabel" Content="Wzrost" HorizontalAlignment="Left" Margin="39,167,0,0" VerticalAlignment="Top"/>
    <Label x:Name="label" Content="{Binding Person.BMI}" HorizontalAlignment="Left" Margin="39,274,0,0" VerticalAlignment="Top"/>
    <Button Content="Statystyki" HorizontalAlignment="Left" Margin="149,274,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.325,-0.438"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="148,83,0,0" TextWrapping="Wrap" Text="{Binding Person.Weight, Mode=TwoWay}" VerticalAlignment="Top" Width="76">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding Path=textChangedCommand}"></i:InvokeCommandAction>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>

    <TextBox HorizontalAlignment="Left" Height="23" Margin="148,170,0,0" TextWrapping="Wrap" Text="{Binding Person.Height, Mode=TwoWay}" VerticalAlignment="Top" Width="76">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding Path=textChangedCommand}"></i:InvokeCommandAction>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>



</Grid>

Upvotes: 3

Views: 3803

Answers (1)

pablocity
pablocity

Reputation: 504

Ed Plunkett gave the simplest solution:

There is no need to write whole bunch of code to implement command while TextChanged occurs, there is a Binding property UpdateSourceTrigger which determines when there should be the update, by default it's set to LostFocus so it is for example when you click on another control, if you'd like to update it while user is typing, you need to set value to PropertyChanged and that's it!

<TextBox Text="{Binding Person.Weight, UpdateSourceTrigger=PropertyChanged}">

Upvotes: 9

Related Questions