2

I'm trying to understand how I can call a Resfresh() ou Work() method each time I modify an option on a window with WPF (XAML). I already ask the question but I wasn't clear enough. So I will ask again with a better example.

I would like to know how I can update a label from many visual component. Let say we have 10 checkbox with label 0 to 9 and I would like to do the sum of them if they are checked.

In classic Winform I'll create an event handler OnClick() and call the event on each CheckBox state change. OnClick call a Refresh() global method. Refresh evaluate if each CheckBox is checked and sum them if required. At the end of the Refresh() method I set the Label Text property to my sum.

How can I do that with XAML and data binding ?

<CheckBox Content="0" Name="checkBox0" ... IsChecked="{Binding Number0}" />
<CheckBox Content="1" Name="checkBox1" ... IsChecked="{Binding Number1}" />
<CheckBox Content="2" Name="checkBox2" ... IsChecked="{Binding Number2}" />
<CheckBox Content="3" Name="checkBox3" ... IsChecked="{Binding Number3}" />
<CheckBox Content="4" Name="checkBox4" ... IsChecked="{Binding Number4}" />
...
<Label Name="label1" ... Content="{Binding Sum}"/>

In my ViewModel I have a data binded property for each checkBox and one for the Sum

private bool number0;
public bool Number0
{
    get { return number0; }
    set
    {
        number0 = value;
        NotifyPropertyChanged("Number0");
        // Should I notify something else here or call a refresh method?
        // I would like to create something like a global NotifyPropertyChanged("Number")
        // But how can I handle "Number" ???
    }
}

// Same for numer 1 to 9 ...

private bool sum;
public bool Sum
{
    get { return sum; }
    set
    {
        sum = value;
        NotifyPropertyChanged("Sum");
    }
}

private void Refresh() // or Work()
{ 
    int result = 0;
    if (Number0)
        result = result + 0; // Could be more complex that just addition
    if (Number1)
        result = result + 1; // Could be more complex that just addition
    // Same until 9 ...
    Sum = result.ToString(); 
}

My question is how and when should I call this Refresh method?

Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200

3 Answers3

1

try something like -

private bool number0;
    public bool Number0
    {
        get { return number0; }
        set
        {
            number0 = value;
            NotifyPropertyChanged("Number0");
            NotifyPropertyChanged("Sum");
        }
    }

    public bool Sum
    {
        get { return this.EvaluateSum(); }
    }

    private bool EvaluateSum() 
    {
        int result = 0; 
        if (Number0) 
            result = result + 0; // Could be more complex that just addition 
        if (Number1) 
            result = result + 1; // Could be more complex that just addition 
        // Same until 9 ... 

        return result.ToString(); 
    }

Note: this is not tested.

If you don't like the above then you can do the following change:

Declare new class

public class SomeClass: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private bool number0;
    public bool Number0
    {
        get { return number0; }
        set
        {
            number0 = value;
            this.NotifyPropertyChanged("Number0");
        }
    }

    private bool sum;
    public bool Sum
    {
        get { return sum; }
        set
        {
            sum = value;
            this.NotifyPropertyChanged("Sum");
        }
    }

    protected void NotifyPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
        if ((propertyChanged != null))
        {
            propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}

Note: Change the Binding correspondingly

On Property change of SomeClass:

void SomeClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName.Contains("Number"))
        {
            (sender as SomeClass).Sum = EvaluateSum(); // put or call the sum logic
        }
    }
SSK .NET PRO
  • 126
  • 4
  • @Baboon I think I'm agree with you. Could I say that if I'm adding a NotifyPropertyChanged("Sum") that is not corresponding to my current property (here "Number0") I'm doing something wrong? – Bastien Vandamme Jul 18 '12 at 09:53
  • Experience in WPF and MVVM has taught me that you should not hack setters any further than raising PropertyChanged, or you will regret it. – Louis Kottmann Jul 18 '12 at 10:07
1

You got several stuff wrong in your design. Here is what I would do:

Instead of having Number0...9, make a BindingList<bool> Numbers

Then in XAML, show the checkboxes like this:

<ItemsControl ItemsSource="{Binding Numbers}">
   <ItemsControl.ItemTemplate>
      <DataTemplate>
         <CheckBox IsChecked="{Binding} Content="Name the checkbox here" />
      </DataTemplate>
   </ItemsControl.ItemTemplate>
</ItemsControl>

<Label Content="{Binding Numbers, Converter={StaticResource NumbersToSumConverter}}" />

With NumbersToSumConverter being an IValueConverter, such as:

public NumbersToSumConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   {
      var numbers = value as BindingList<bool>();
      //Do your sum here.
   }

   public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
   {
      throw new NotImplementedException();
   }
}

That is an example of how it's done in MVVM, if you need to store more than just a bool in the BindingList<T>,
create a new class that implements INotifyPropertyChanged, and add as many properties as you need (make sure to raise PropertyChanged in their setter).
Then use that as the type of your BindingList<T>.

Hope this helps,

Bab.

Louis Kottmann
  • 16,268
  • 4
  • 64
  • 88
  • If I understand, the idea is to bind my Label to a unique object (here a BindingList) that contain all properties that may influence the result of my Label. – Bastien Vandamme Jul 18 '12 at 09:48
  • @DranDane yes, that's the essence of it. Also don't make one property per boolean, put them in a collection (here, BindingList) and display them through an ItemsControl. – Louis Kottmann Jul 18 '12 at 10:09
  • You should absolutely not have a collection of Controls in your ViewModel. Make a collection of String and POCOs and bind the content of your controls to that. – Louis Kottmann Jul 18 '12 at 10:20
  • So make a BindingList and use that. I will add the necessary stuff to my answer. – Louis Kottmann Jul 18 '12 at 12:51
  • BindingList in WPF is a super ObservableCollection: it notifies of changes when its children's properties change. – Louis Kottmann Jul 18 '12 at 14:43
  • I'm not sure I agree that this is the MVVM way. Your solution uses a converter to do logic that should be in the viewmodel and thus is not testable, and relies on the view implementation. – GazTheDestroyer Jul 18 '12 at 14:44
  • An IValueConverter is testable, it is loose logic just like a ViewModel. And any view can use it. A ViewModel itself can use it, but I'd make a third, strongly typed method, for that purpose. – Louis Kottmann Jul 18 '12 at 14:53
  • I also read a lot of message (on stackoverflow and on the web) saying that with MVVM we should avoid using Converter. – Bastien Vandamme Jul 18 '12 at 15:56
  • Care to link to those? Why don't you try both approach and see which one feels like the best design? – Louis Kottmann Jul 18 '12 at 16:39
  • http://stackoverflow.com/q/2237834/196526 or http://stackoverflow.com/q/1007487/196526 or simply search "Converter MVVM" on Google. – Bastien Vandamme Jul 19 '12 at 12:12
0

Personally I would just call Refresh() in each Number setter like you suggest.

I'm not keen on directly raising PropertyChanged notifications within setters for other properties. What happens in the future if some other property changes as a result of one of the Number* properties changing? You'd have to go through every setter and raise PropertyChanged events for that property too. It goes against the single reponsibility principle. Number* setters are there to set the property, not worry about the results of other properties changing.

With a dedicated Refresh() function, that function takes care of updating other properties, and they raise their own change events as normal.

Yes, calling Refresh() in a setter is not fantastic, but the "proper" way to do it would be for your viewmodel to subscribe to its own PropertyChanged event and call Refresh() in the event handler. This adds unnecessary complication IMO, and calling Refresh() in each setter is effectively the same logic.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • The "proper" way you describe is not proper at all. I'm so sick of people advising to raise and subscribe to PropertyChanged everywhere: it is NOT a good practice, and it does NOT enforce an MVVM design. In fact it's poor design and leads to memory leaks. – Louis Kottmann Jul 18 '12 at 12:50
  • @GazTheDestroyer What if the job I perform in Refresh() should change a property that will call Refresh() again that will change another property that will change Refresh() again that will change another property that will change Refresh() again that will change another property that will change ... – Bastien Vandamme Jul 18 '12 at 14:11
  • @Baboon. I agree perhaps "proper" was a bad word. I agree this would be a bad idea hence why I said it would be overcomplicated. I was trying to impart the fact that the job of calculating new state, and the job of a property setter are two different things that in an ideal world would be uncoupled. As I said, calling Refresh() direct would be my chosen solution. – GazTheDestroyer Jul 18 '12 at 14:20
  • @DranDane. Most property setters on observable objects check the value and only raise if it is different. In this case your example would not result in infinite calls. – GazTheDestroyer Jul 18 '12 at 14:25