gartenriese
gartenriese

Reputation: 4366

Binding does not work with a ViewModel

I want to change the Fill of a Rectangle depending on a boolean. I have the following classes.

Base class that extends INotifyPropertyChanged:

public class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Model:

public class ChangingVariable : PropertyChangedBase
{
    public ChangingVariable()
    {
        Variable = true;
    }

    private bool _variable;

    public bool Variable
    {
        get
        {
            return _variable;
        }

        set
        {
            if (_variable.CompareTo(value) != 0)
            {
                _variable = value;
                OnPropertyChanged();
            }
        }
    }
}

ViewModel:

public class BooleanRectangleViewModel : PropertyChangedBase
{
    public ChangingVariable Model { get; set; }
}

This is my View that does not work (Fill is gray):

xaml:

<UserControl.DataContext>
    <viewModels:BooleanRectangleViewModel x:Name="ViewModel"/>
</UserControl.DataContext>

<Rectangle x:Name="Rectangle">
    <Rectangle.Style>
        <Style TargetType="{x:Type Rectangle}">
            <Setter Property="Fill" Value="Gray" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Model.Variable}" Value="true">
                    <Setter Property="Fill" Value="GreenYellow" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Rectangle.Style>
</Rectangle>

cs:

public partial class BooleanRectangleView
{
    public BooleanRectangleView()
    {
        InitializeComponent();
        ViewModel.Model = new ChangingVariable();
    }
}

However when I change it such that the data context is the Model instead of the ViewModel, it works (Fill is green):

xaml:

<UserControl.DataContext>
    <models:ChangingVariable x:Name="ViewModel" />
</UserControl.DataContext>

<Rectangle x:Name="Rectangle">
    <Rectangle.Style>
        <Style TargetType="{x:Type Rectangle}">
            <Setter Property="Fill" Value="Gray" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Variable}" Value="true">
                    <Setter Property="Fill" Value="GreenYellow" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Rectangle.Style>
</Rectangle>

cs:

public partial class BooleanRectangleView
{
    public BooleanRectangleView()
    {
        InitializeComponent();
        ViewModel = new ChangingVariable();
    }
}

Why does it not work with the ViewModel inbetween?

Upvotes: 0

Views: 2223

Answers (3)

Jai
Jai

Reputation: 8363

As far as I know, when you do binding within a template or style, it is always better to use RelativeSource. I am not absolute sure, but you can try:

<Rectangle x:Name="Rectangle">
    <Rectangle.Style>
        <Style TargetType="{x:Type Rectangle}">
            <Setter Property="Fill" Value="Gray" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding DataContext.Model.Variable, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="true">
                    <Setter Property="Fill" Value="GreenYellow" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Rectangle.Style>
</Rectangle>



Update

I am also quite new to WPF/MVVM, but I would like to share something that I learnt over these short two to three months, that may help others in troubleshooting binding issues.

Whenever a binding does not work, the most likely reason is that binding engine is not able to find the binding source or the source property. Implementing INotifyPropertyChanged (or using DependencyProperty) simply provides a way for the binding to get notified when the source property's value is changed. If you remove that part out, the binding is going to work as if it is in OneTime mode. The reason is simple - the binding engine is intelligent enough to know that it has to get the value from the source property in order for the target property to work.

For example, you have <Border BorderBrush="{Binding MyBorderColor}" ....>. When your control is about to render itself, the control needs to know what color the border is, otherwise it cannot proceed to render. The binding engine is forced to search for the source property MyBorderColor. If the binding fails, the BorderBrush target property will not get a value, which would result in the Border using whatever default or inherited values. If MyBorderColor is not implementing 'INotifyPropertyChanged' nor is it a DependencyProperty, then if MyBorderColor changes value some time during runtime, it will not be reflected in BorderBrush, because the binding engine is not aware that the source property has changed.

Upvotes: 1

CodeNoob
CodeNoob

Reputation: 757

Your property Model is automatic property i.e. it doesn't implement the getter or setter and OnPropertyChanged. Try changing Model property from

public ChangingVariable Model { get; set; }

to

private ChangingVariable model;
public ChangingVariable Model 
{ 
   get
   {
      return model;
   }
   set
   {
      model = value;
      OnPropertyChanged("Model");
   }

The reason is that automatic properties don't notify the UI that they have changed.Even though the underlying property is changing. You have to basically call OnPropertyChanged method for Model as well. Hope this helps.

Upvotes: 1

Pedro G. Dias
Pedro G. Dias

Reputation: 3222

What you want is a bool to Color conversion, so I would write this class:

public class BooleanToColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var isTrue = (bool) value;

        if (isTrue)
            return new SolidColorBrush(Colors.Green);

        return new SolidColorBrush(Colors.Red);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

And then suggest this converter in your XAML binding. For the full documentation on using valueconverters, look here: http://www.wpf-tutorial.com/data-binding/value-conversion-with-ivalueconverter/

Upvotes: 2

Related Questions