views:

657

answers:

3

I have a Button and the button content is binded to a custom class called MyClass

Button button = new Button();
MyClass myClass = new MyClass() { A = 1, B = 2 };
button.Content = myClass;
stackPanel.Children.Add(button);

.... // later, on my running code

Button button = (Button)stackPanel.Children[0];
MyClass myClass = (MyClass)button.Content;
myClass.A = 421;
button.Content = myClass;

but the Button does not refresh the UI to reflect the new binded object, wich is the same instance but changed.

What is the fix for this? I don't want to set the button.Content = null; and then button.Content = myClass. Also I didn't finded any helper Refresh method.

+1  A: 

Does MyClass implement INotifyPropertyChanged. That is the standard way to propogate changes to the UI.

Simply implement the INotifyPropertyChanged interface then in the Property Setter for A and B raise the Event. This should cause the button to refresh its content.

luke
In order for this to work, you would need to set the data context of the button equal to myClass. You would then have to actually bind (not just set), the content equal to either myClass or a property on myClass. Otherwise, you'll probably have to set it to null and then set it back again.
Jacob Adams
+2  A: 

Lets break this down.

Whats happening

Your MyClass will have an override on ToString the generates a string that represents the value of the object which will be function of the properties you've assigned. When you assign the object to Content the SL framework it calls the ToString to get some text to display. As you have rightly discovered assigning the same reference doesn't invoke this call again.

A simple solution that will get you going and is likely good enough

Button button = new Button();
MyClass myClass = new MyClass() { A = 1, B = 2 };
button.Tag = myClass;
button.Content = myClass.ToString();
stackPanel.Children.Add(button);

...

Button button = (Button)stackPanel.Children[0];
MyClass myClass = (MyClass)button.Tag;
myClass.A = 421;
button.Content = myClass.ToString();

The Tag property is effectively a peg on which we can hang a related object that we may need later. In this case we are performing the ToString() operation hance Content always gets a different reference to what it already has (at least always when the values are actually different).

A more traditional SL solution but not necessarily desirable in this case

To achieve what you want you would need to do several things. First you will need to expose a property of string type on MyClass that contains the string you want to present, lets call it DisplayValue.

public string DisplayValue { get { return String.Format("{0} {1}", A, B); } }

Now you need to bind the result of this to the Button in some way so that it can display the value. A start would be to assign the instance of MyClass to the DataContext proeprty instead of Tag. However Button doesn't' have a Text property where in other cases you would bind the DisplayProperty. Hence you need to add a TextBlock as the content of the button and bind its Text property:-

Button button = new Button();
TextBlock tb = new TextBlock();
tb.SetBinding(TextBlock.TextProperty, new Binding("DisplayValue"));
button.Content = tb;
button.DataContext = myClass;

However in order for control to know that the bound value has been changed your MyClass needs to implement INotifyPropertyChanged. You add this to your class like this:-

 public MyClass : INotifyChanged
 {
   // ... the rest of you code
   public event PropertyChangedEventHandler PropertyChanged;
 }

You then need to raise the event whenever a property is changed a typical example:-

 private int _A
 public int A
 {
    get { return _A; }
    set
    {
      _A = value;
      if (this.PropertyChanged != null)
        this.PropertyChanged(this, new PropertyChangedEventArgs("A"));
    }
 }

Still this typical example doesn't help. In your case you've bound to DisplayValue and since the value of A influences its value your class also needs to notify DisplayValue changed:-

      if (this.PropertyChanged != null)
      {
        this.PropertyChanged(this, new PropertyChangedEventArgs("A"));
        this.PropertyChanged(this, new PropertyChangedEventArgs("DisplayValue"));
      }

As you can see its significantly more complex than the other solution yet doesn't seem to deliver any more. The key advantage is that logic in you application can now concentrate on mutating object values without having concern itself with keeping the display up-to-date, that takes care of itself. Hence it enables good separation of logical code from presentation code.

So why might this not be desirable? It depends really what your real class represents but in effect the DisplayValue is dictating how the objects state should be represented on screen. However does this responsibility really rest with the class or is that not the responsibility of presentation code, typically the latter is true and the DisplayValue is generally not a good idea (and is the reason why few class actually bother overriding ToString).

AnthonyWJones
+1 very complete answer
Dave Swersky
A: 

11124325454311244354356463

54z4z