views:

76

answers:

3

Hi there. I am having some issues with WPF not fully repainting a button control when the button is changed from another thread, and I am not sure how to force it to do a full repaint.

The situation is that on receipt of a message (via WCF - but the source isn't important, except that it is an external thread) I update the foreground color and visibility of a button. WPF immediately repaints the text on the button face, but the surface of the button is not repainted until I click anywhere on the application.

I have tried calling InvalidateVisual() on the button, but that did not help. I think that I am not understanding how a background thread can force a repaint. But the frustrating thing is that something is getting repainted and every other control I am using (text and image controls) are also getting properly repainted when I update them from my same message receipt.

I have now tried sending an empty message to the Dispatcher of the application via Invoke(), but no luck there either.

So I am looking for tips on how to tell WPF that it needs to update the rest of the button and not just the text.

Edit

This is a rough skeleton of my program. Note that I have wrapped the button in a class as there is other related state information I am keeping with it.

class myButton
{
   Button theButton

   void SetButton()
   {
     theButton.Forground = a new color
   }
}

main
{
   myButton.theButton = (Button on WPF canvass)

   RegisterCallback( mycallbackFunction) with WCF client endpoint
}


void myCallbackFunction(message)
{
   if message has button related stuff,  call myButton.SetButton
}

Thanks!

Edit 2

Solved my problem .. it was actually a conflict between a "CanExecute" method and setting the buttons attributes in the callback. Once I removed the "CanExecute" function it all worked.

A: 

Setting properties on the button itself from code, especially another thread/callback, is an entrance to a painful world of inconsistent states.

What you should do is bind your button's properties to properties in your code, and then have your callback change those external properties.

I know the code you posted was kind of a mock up for what you actually want to do in your program, and I couldn't really follow your logic, but here's a complete program that operates similarly to your example and shows what I'm talking about. Let me know if I've missed the mark.

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    /// 
    public class MyButton : INotifyPropertyChanged
    {
        private Button _theButton;
        public Button TheButton
        {
            get { return _theButton; }
            set
            { 
                _theButton = value;               

                //set text binding
                Binding textBind = new Binding("Text");
                textBind.Source = this;
                textBind.Mode = BindingMode.OneWay;
                _theButton.SetBinding(Button.ContentProperty, textBind);

                //set color binding
                Binding colorBind = new Binding("Brush");
                colorBind.Source = this;
                colorBind.Mode = BindingMode.OneWay;
                _theButton.SetBinding(Button.ForegroundProperty, colorBind);

                NotifyPropertyChanged("TheButton"); 
            }
        }


        public void Set(string text, Brush brush)
        {
            this.Text = text;
            this.Brush = brush;
        }

        private string _text;
        public string Text
        {
            get { return _text; }
            set { _text = value; NotifyPropertyChanged("Text"); }
        }

        private Brush _brush;
        public Brush Brush
        {
            get { return _brush; }
            set { _brush = value; NotifyPropertyChanged("Brush"); }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        internal void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }

    public partial class MainWindow : Window
    {
        MyButton _myButton = new MyButton();

        public MainWindow()
        {
            InitializeComponent();

            //button1 is defined in XAML markup
            _myButton.TheButton = this.button1; 

            //or else this could be your callback, same thing really
            Thread t = new Thread(SetButton);
            t.Start();
        }

        void SetButton()
        {    
           _myButton.Text = "wo0t!";
           _myButton.Brush = Brushes.Red;

              //or
            _myButton.Set("giggidy!", Brushes.Yellow);
        }

    }   

}

Note that binding your Button properties in XAML is much less ugly, but then we're getting into UserControls and DataContexts which is another topic. I would look at inheriting the Button class to implement the features you want.

bufferz
@Bufferz - I implemented your pattern and it didn't solve my problem. Specifically I extended the pattern to set the "Background" color. It is this color that is not being repainted until I click on the application.
Peter M
A: 

I recommend reading the article (Build More Responsive Apps With The Dispatcher) from MSDN magazine that describes how WPF works with the Dispatcher when using BackgroundWorker.

Zamboni
@Zamboni - I looked at this and looked at the results of "this.CheckAccess()" at the start of my callback (with "this" hopefully pointing to the Dispatcher in my main). This returns "true" which says that the calling thread is the same as the dispatcher thread. This would imply that I am in the UI thread when my callback gets executed - which is confusing to me.
Peter M
A: 

As per my edit, I had conflict between the buttons CanExecute binding in the XAML and me setting the background color in the callback. I didn't really need the CanExecute, so getting rid of that solved my problem.

Peter M