2

I have 2 TextBox controllers in my UserControl let's call them TextBox1 and TextBox2.

In my old code I update the TextBox1 background when the TextBox2 TextChanged event is fired. Using an event handler in the xaml.cs, and that was easy and straightforward.

    private void textBox_TextChanged(object sender, TextChangedEventArgs e) {
     // use controllers Names.
    }

However I was reading that this violates the MVVM standards. Which is basically don't add extra code in the xaml.cs!

During my search for an answer I found 2 approaches that I kinda understood :

1- Some people suggested I use PropertyChanged to fire another event. I noticed that the PropertyChanged event wont fired until the TextBox loses focus. This is not what I'm looking for. I want TextBox1 to update immediately after a user input something to TextBox2. However, I'm still not sure where to tell the code "change TextBox1 Background if TextBox TextChanged".

2- Another approach was using Behaviours which is totally new for me, I was able to fire the event TextChanged on TextBox2 immediately, but I didn't know how to access TextBox1 properties!

My question: What is the proper way to handle the requirement I'm looking for in MVVM approach?

Muhannad
  • 405
  • 4
  • 21
  • I would expose properties on your view-model for the `Background` of each `Textbox`, and connect them using bindings. Then in the setter of whatever property your `Textbox.Text` is bound to, you can just update that property. The binding will push the updated values out to the controls. – Bradley Uffner Feb 28 '18 at 20:12
  • Does this approach require the textbox to lose focus for the setter to be called? – Muhannad Feb 28 '18 at 20:18
  • 1
    Regarding point 1, you are probably missing `UpdateSourceTrigger=PropertyChanged` in your xaml. This will update the property as soon as it is changed. I.E. if you are binding to the `Text` property, it will fire off everytime there is new input. – Sudsy1002 Feb 28 '18 at 20:20
  • @user3382285 By default it will, but if you add `UpdateSourceTrigger="PropertyChanged"` to the binding, it will trigger on every change of the text. – Bradley Uffner Feb 28 '18 at 20:23
  • @Sudsy1002 that makes sense. Do you think this is a good approach performance wise? Do you think there's a better way of doing it? I just want to get a better understanding of MVVM too. – Muhannad Feb 28 '18 at 20:23
  • 1
    Code behind does not violate the MVVM standard. It is totally ok to do UI related staff in .xaml.cs files. In your case you are changing color of textbox which is only UI related and does not contain any business logic. I would prefer to change background with event handler in code behind. – Ruben Vardanyan Feb 28 '18 at 20:24
  • @user3382285 it depends on what you are trying to accomplish. If it is something related to a property like background color then this approach would probably be fine. If you are trying to somehow modify the text and perform some kind of validation then you are probably better off using behaviors. Bradley's suggestion in his first comment is another common approach. – Sudsy1002 Feb 28 '18 at 20:26
  • @RubenVardanyan has a good point. The line about what can go in the code-behind, the viewmodel, behvaiors, etc... can be very fuzzy in MVVM. If you are just changing the color, it could go in the code-behind, but if the logic to pick the color is in any way complex, and depends on special rules, it should go someplace else. – Bradley Uffner Feb 28 '18 at 20:26

4 Answers4

1

You can do all of that logic in the View-Model. This specific example uses the AgentOctal.WpfLib NuGet package (disclaimer: I am the author of this package) for the base ViewModel class that raises PropertyChanged notifications, but you can use whatever system you want, as long as it property implements INotifyPropertyChanged.


In this example, the more letters you put in the first TextBox, the more blue the background of the 2nd TextBox gets.

The first TextBox has its Text property bound to the Text property on the view-model. The binding has UpdateSourceTrigger set to PropertyChanged so that the binding updates the view-model every time the property changes, not just when the control looses focus.

The 2nd TextBox has its Background property bound to a SolidColorBrush property named BackgroundColor on the view-model.

On the view-model, the setter of the TextBox contains the logic to determine to color of the 2nd TextBox.

This could probably be implemented a little better by using a Color instead of a SolidColorBrush, and an IValueConverter that can change that Color in to a Brush, but it should server as a decent starting point.

All the code lives in the view-model, the code-behind is empty.


XAML:

<Window
    x:Class="VmBindingExample.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:local="clr-namespace:VmBindingExample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="525"
    Height="350"
    mc:Ignorable="d">
    <Window.DataContext>
        <local:MainWindowVm />
    </Window.DataContext>
    <StackPanel Margin="20" Orientation="Vertical">
        <TextBox
            Margin="4"
            MaxLength="10"
            Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" />
        <TextBox Margin="4" Background="{Binding BackgroundColor}">The color of this will reflect the length of the first textbox.</TextBox>
    </StackPanel>
</Window>

View-Model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using AgentOctal.WpfLib;

namespace VmBindingExample
{
    using System.Windows.Media;

    public class MainWindowVm : ViewModel
    {
        private string _text;

        public string Text
        {
            get
            {
                return _text;
            }

            set
            {
                SetValue(ref _text, value);
                byte red = (byte)(255 / 10 * (10 - _text.Length));
                BackgroundColor = new SolidColorBrush(Color.FromArgb(255, red, 255, 255));
            }
        }

        private Brush _backgroundColor;

        public Brush BackgroundColor
        {
            get
            {
                return _backgroundColor;
            }

            set
            {
                SetValue(ref _backgroundColor, value);
            }
        }
    }
}
Bradley Uffner
  • 15,611
  • 2
  • 37
  • 61
0

The second approach is the way to go. In your viewmodel, add a ICommand DoOnTextChanged and dependency property BackgroundColor.

  • Bind the DoOnTextChanged command with the TextChanged event of TextBox1 using Behaviours
  • Bind the BackgroundColor property to the background of TextBox2 using converter.
  • In the Execute function of DoOnTextChanged, change the BackgroundColor property and you are done.

If you are using MVVMLight, binding to ICommand is easy. First add these two namespace xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform" and do the following:

<TextBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="TextChanged" >
            <cmd:EventToCommand Command="{Binding DoOnTextChanged}" PassEventArgsToCommand="False" >
            </cmd:EventToCommand>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

Update

As OP is using plain wpf/Xaml, I am updating my answer with implementation for plain wpf.

Add the following two helper class in your project:

public class ExecuteCommand : TriggerAction<DependencyObject>
{
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommand));
    public ICommand Command
    {
        get
        {
            return GetValue(CommandProperty) as ICommand;
        }
        set
        {
            SetValue(CommandProperty, value);
        }
    }

    protected override void Invoke(object parameter)
    {
        if (Command != null)
        {
            if (Command.CanExecute(parameter))
            {
                Command.Execute(parameter);
            }
        }
    }
}

public class EventCommand : ICommand
{
    private Action<object> func;

    public EventCommand(Action<object> func)
    {
        this.func = func;
    }

    public bool CanExecute(object parameter)
    {
        //Use your logic here when required
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        if (func != null)
        {
            func(parameter);
        }
    }
}

In your ViewModel, implement INotifyPropertyChanged and add the following ICommand and Background property.

public class MainViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public MainViewModel(IDataService dataService)
    {
        BackColor = Brushes.Aqua;
        DoOnTextChanged = new EventCommand((obj => BackColor = BackColor == Brushes.BurlyWood ? Brushes.Chartreuse : Brushes.BurlyWood));
    }

    public ICommand DoOnTextChanged { get; set; }

    private Brush backColor;
    public Brush BackColor
    {
        get
        {
            return backColor;
        }
        set
        {
            backColor = value;
            if (PropertyChanged != null)
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs("BackColor"));
            }
        }

    }
}

Finally, in you ViewName.xaml file, add this namespace xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity". You may need to add a reference to System.Windows.Interactivity. Then add the following to bind the button event to a command:

<TextBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="TextChanged" >
            <local:ExecuteCommand Command="{Binding DoOnTextChanged}"></local:ExecuteCommand>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

<TextBox Background="{Binding BackColor}"></TextBox>

Although it is a lot of code to accomplish something simple, it can be really helpful is certain cases. It is better to learn all the ways and use the one that fits perfectly to your need.

Faruq
  • 1,024
  • 8
  • 23
  • The 2nd option is "Behaviors". I don't see you describing Behaviors at all in your answer. Not saying your answer is wrong, but it doesn't seem to discuss Behaviors. – Bradley Uffner Feb 28 '18 at 20:14
  • @BradleyUffner Note the first bullet point. I am just giving the basic direction. – Faruq Feb 28 '18 at 20:16
  • I didn't know I can bind a command to TextChanged! is this in xaml? can I do something like ? – Muhannad Feb 28 '18 at 20:21
  • @user3382285 You can't. I have updated my answer with a brief example. – Faruq Feb 28 '18 at 20:32
  • While your `EventToCommand` example should be fine, it seems like way more work than just putting the logic in the getter of the view-model. It would have the same end-result and be just as MVVM compliant. – Bradley Uffner Feb 28 '18 at 20:33
  • @user3382285 The behavior would let you access events on the instance of the control the `Behavior` is attached to, but accessing other controls would be more difficult. So it would be easy to use one to change `TextBox1` background from `TextBox1` event, changing the background of `TextBox2` from `TextBox1` would be significantly harder. – Bradley Uffner Feb 28 '18 at 20:35
  • @BradleyUffner I am sure I read somewhere that optimally xaml.cs shouldn't have any code. Also I can see that textbox background color is not business logic. However, I would love to know the right way to do it. I have other event that involves business logic and consistency of handling them is a key. – Muhannad Feb 28 '18 at 20:37
  • @BradleyUffner I completely agree with you, either way is fine. – Faruq Feb 28 '18 at 20:37
  • @user3382285 If you can wait a few minutes, I can create a quick example of doing it all through the view-model for you. – Bradley Uffner Feb 28 '18 at 20:38
  • @user3382285 What you read about not having any code in xaml.cs is correct. But in real world projects you cannot follow that all the time. – Faruq Feb 28 '18 at 20:40
  • @BradleyUffner that would be great!! – Muhannad Feb 28 '18 at 20:42
  • @Faruq As I mentioned before I'm totally new to behaviors. I was using this article https://eladm.wordpress.com/2009/04/02/attached-behavior/ . it's pretty old is this outdated? I used the exact same code and I was able to fire the TextChanged event but I get only the current textbox I am changing the DependencyProperty for . Is there a better way? if yes what is a good/up to date source for it. – Muhannad Feb 28 '18 at 20:44
  • @user3382285 Are you using MVVMLight or something similar? – Faruq Feb 28 '18 at 20:45
  • @user3382285 the background color of the TextBox is no business logic but the requirement to change it when something happens (probably in the model) really is a business thing. – Ignacio Soler Garcia Feb 28 '18 at 20:49
  • @Faruq I would say no because I don't know what is that (that made me feel bad). I'm using plain wpf, and I follow MVVM articles and tutorials. I don't use any extra assembles. I was thinking it's better for me if I start with plain then upgrade to tools to get a better understanding. I also have the time because I'm refactoring a working code. – Muhannad Feb 28 '18 at 20:53
  • @user3382285 Don't feel bad, I am learning WPF as well. MVVMLight can make your life a little easier https://github.com/lbugnion/mvvmlight. There are other alternatives as well like https://github.com/PrismLibrary/Prism – Faruq Feb 28 '18 at 21:19
  • @user3382285 I have updated my answer with a sample code that runs on plain wpf. – Faruq Feb 28 '18 at 22:04
0

I would try to keep UI specific things in the xaml as far as it possible and use triggers in that case. Check following article about triggers and a converter for null values. DataTrigger where value is NOT null?

As previous mentioned by Bradly Uffner you should modify your bindung and add UpdateSourceTrigger="PropertyChanged" so the changes are immediately fired.

  • 2
    To me "UI specific things" its a code smell. Almost always there is a business need that originated the requirement that can be modeled in the ViewModel. – Ignacio Soler Garcia Feb 28 '18 at 20:47
0

Well, I always prefer to follow MVVM when it's possible and it is perfectly possible in this situation:

You are thinking in terms of View (update the TextBox1 background when the TextBox2 TextChanged) instead of thinking in terms of business logic (update the TextBox1 background when something in the business layer [the model for example] happens [a property changes its values?

You should have a View with a TextBox1 and a TextBox2 and a ViewModel with some properties, for example:

/* These properties should implement PropertyChanged, I'm too lazy */
public string WhateverInputsTextBox1 { get; set; }
public string WhateverInputsTextBox2 { get; set; }
public bool WhateverMeansTextBox1HasChanged { get; set; }

Then you should set WhateverMeansTextBox1HasChanged to true when the content of WhatevernInputsTextBox1 changes (in the set of the property).

Finally you must bind the TextBox1 text to the WhateverInputsTextBox1 property, the TextBox2 text to the WhateverInputsTextBox2 property and the TextBox1 background to the WhateverMeansTextBox1HasChanged property using a converter to convert the true to a color and the false to a different color (check IValueConverter). Remember to set the binding to UpdateSourceTrigger="PropertyChanged" where needed (this moves the data to the ViewModel when it is entered).

This way you have all the business logic of the View into the ViewModel and you can test it if you want to as the result of having all the responsibilities correctly distributed.

Another benefit is that (at least to me) is easier to understand the intention of the developer when I see that the background of a TextBox changes when "AccountNumberChanged" rather than when TextBox2 is edited.

marc_s
  • 675,133
  • 158
  • 1,253
  • 1,388
Ignacio Soler Garcia
  • 20,097
  • 26
  • 114
  • 195
  • Thank you for the answer. It's strange for me that most MVVM solutions end up using PropertyChanged and then do the logic in the setters. While there's an event that might be used directly TextChanged. Does MVVM have a standard way of handling events! I was hoping there's something as easy as but it seems to be not the case! – Muhannad Feb 28 '18 at 21:09
  • @user3382285 `PropertyChanged` is the heart of data-binding. It is absolutely essential to the MVVM pattern. – Bradley Uffner Feb 28 '18 at 21:11
  • 1
    The main idea you should understand is that you don't want to change TextBox1 background when someone writes something into TextBox2, you want to do it when someone changes a value of something of your business. – Ignacio Soler Garcia Feb 28 '18 at 21:14