14

With each loop iteration, I want to visually update the text of a textblock. My problem is that the WPF window or control does not visually refresh until the loop is complete.

for (int i = 0; i < 50; i++)
{
    System.Threading.Thread.Sleep(100);
    myTextBlock.Text = i.ToString();                
}

In VB6, I would call DoEvents() or control.Refresh. At the moment I'd just like a quick and dirty solution similar to DoEvents(), but I'd also like to know about alternatives or the "right" way to do this. Is there a simple binding statement I could add? What is the syntax? Thanks in advance.

Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
DeveloperDan
  • 4,626
  • 9
  • 40
  • 65

5 Answers5

29

If you really want the quick and dirty implementation and don't care about maintaining the product in the future or about the user experience, you can just add a reference to System.Windows.Forms and call System.Windows.Forms.Application.DoEvents():

for (int i = 0; i < 50; i++)
{
    System.Threading.Thread.Sleep(100);
    MyTextBlock.Text = i.ToString();
    System.Windows.Forms.Application.DoEvents();
}

The downside is that it's really really bad. You're going to lock up the UI during the Thread.Sleep(), which annoys the user, and you could end up with unpredictable results depending on the complexity of the program (I have seen one application where two methods were running on the UI thread, each one calling DoEvents() repeatedly...).

This is how it should be done:

  1. Any time your application has to wait for something to happen (ie a disk read, a web service call, or a Sleep()), it should be on a separate thread.
  2. You should not set TextBlock.Text manually - bind it to a property and implement INotifyPropertyChanged.

Here is an example showing the functionality you've asked for. It only takes a few seconds longer to write and it's so much easier to work with - and it doesn't lock up the UI.

Xaml:

<StackPanel>
    <TextBlock Name="MyTextBlock" Text="{Binding Path=MyValue}"></TextBlock>
    <Button Click="Button_Click">OK</Button>
</StackPanel>

CodeBehind:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 50; i++)
            {
                System.Threading.Thread.Sleep(100);
                MyValue = i.ToString();
            }
        });
    }

    private string myValue;
    public string MyValue
    {
        get { return myValue; }
        set
        {
            myValue = value;
            RaisePropertyChanged("MyValue");
        }
    }

    private void RaisePropertyChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

The code might seem a bit complicated, but it's a cornerstone of WPF, and it comes together with a bit of practice - it's well worth learning.

Greg Sansom
  • 20,442
  • 6
  • 58
  • 76
  • 2
    Excellent! The other answers are great, but I understand this one best and it includes the quick and dirty solution I asked for. To make Greg's solution work I added: using System.ComponentModel; (and) using System.Threading.Tasks;. Don't forget to reference System.Windows.Forms to make the DoEvents() solution work. I've learned a lot. Thanks! – DeveloperDan Apr 01 '11 at 13:40
  • 1
    Whats this code look like in .net 3.5 (specifically Task.Factory.StartNew) – David Oct 27 '11 at 17:42
  • Task.Factory.StartNew was introduced in .net 4. Just search for `.net 3.5 threading` to find the legacy mechanisms. – Greg Sansom Nov 11 '11 at 05:01
6

I tried the solution exposed here and it didn't work for me until I added the following:

Create an extension method and make sure you reference its containing assembly from your project.

public static void Refresh(this UIElement uiElement){
    uiElement.Dispatcher.Invoke(DispatcherPriority.Background, new Action( () => { }));
}

Then call it right after RaisePropertyChanged:

RaisePropertyChanged("MyValue");
myTextBlock.Refresh();

That will force the UI thread to take control for a small while and dispatch any pending changes on the UI element.

abhirathore2006
  • 3,317
  • 1
  • 25
  • 29
Pilo
  • 1,210
  • 15
  • 11
4

This is how you would do it normally:

ThreadPool.QueueUserWorkItem(ignored =>
    {
        for (int i = 0; i < 50; i++) {
            System.Threading.Thread.Sleep(100);
            myTextBlock.Dispatcher.BeginInvoke(
                new Action(() => myTextBlock.Text = i.ToString()));
        }
    });

This delegates the operation to a worker pool thread, which allows your UI thread to process messages (and keeps your UI from freezing). Because the worker thread cannot access myTextBlock directly, it needs to use BeginInvoke.

Although this approach is not "the WPF way" to do things, there's nothing wrong with it (and indeed, I don't believe there's any alternative with this little code). But another way to do things would go like this:

  1. Bind the Text of the TextBlock to a property of some object that implements INotifyPropertyChanged
  2. From within the loop, set that property to the value you want; the changes will be propagated to the UI automatically
  3. No need for threads, BeginInvoke, or anything else

If you already have an object and the UI has access to it, this is as simple as writing Text="{Binding MyObject.MyProperty}".

Update: For example, let's assume you have this:

class Foo : INotifyPropertyChanged // you need to implement the interface
{
    private int number;

    public int Number {
        get { return this.number; }
        set {
            this.number = value;
            // Raise PropertyChanged here
        }
    }
}

class MyWindow : Window
{
    public Foo PropertyName { get; set; }
}

The binding would be done like this in XAML:

<TextBlock Text="{Binding PropertyName.Number}" />
Jon
  • 428,835
  • 81
  • 738
  • 806
  • The only thing that this actually does is the Dispatcher.BeginInvoke. Which I had already said earlier... – Elad Katz Mar 31 '11 at 18:11
  • @EladKatz: *Moving the code to another thread* does nothing? I guess so, if having your UI frozen for stretches of 100msec counts as "nothing". – Jon Mar 31 '11 at 18:16
  • +1 for the 3 steps in the second part of your answer. this works perfectly and is very easy to implement. This is an essential part of MVVM as well. – xdumaine Mar 31 '11 at 18:39
  • While your code sample works nicely, it's too complex for me to remember. What binding syntax could I use for this.MyProperty? My quick and dirty form is using only code behind - no external objects. I haven't gotten the binding syntax right. How can I replace your MyObject.MyProperty with a binding statement that references a property on my WPF Window? Instead of setting myTextBlock.text directly I would update the Window property which in turn would update the bound textblock.text. – DeveloperDan Mar 31 '11 at 18:52
1

You can do Dispatcher.BeginInvoke in order to do a thread context-switch so that the rendering thread could do its job. However, this is not the right way for such things. You should use Animation + Binding for things like that, as this is the hack-free way of doing things like that in WPF.

Elad Katz
  • 7,483
  • 5
  • 35
  • 66
  • 2
    You can only use animations if the UI updates are pure eye candy. You can't do it if you actually want the UI to reflect the state of some process. – Jon Mar 31 '11 at 18:09
  • I beg to differ. Animation can do that also. the only thing u need to do is implement a DP where u want the animation to occur. – Elad Katz Mar 31 '11 at 18:11
  • @EladKatz: You can bind to a property and use an appropriate converter to bring about UI changes, sure. Where does the animation come in? – Jon Mar 31 '11 at 18:14
  • you animate the DP. the TextBox would be bound to that DP. – Elad Katz Mar 31 '11 at 18:14
  • @EladKatz: Why would you animate it when you want it to reflect the state of a process? – Jon Mar 31 '11 at 18:21
  • It really depends on what you're trying to do. If he's only trying to show progress visually then animation is the way to go. if it's state then the first way i suggested (and that u wrote again) is the right way. either way, i gave a complete answer. – Elad Katz Mar 31 '11 at 18:23
  • @EladKatz: I 'm not judging the completeness of your answer. – Jon Mar 31 '11 at 18:27
0
for (int i = 0; i < 50; i++)
{
    System.Threading.Thread.Sleep(100);
    myTextBlock.Text = i.ToString();                
}

Running the above code inside a background worker component and using a binding updatesourcetrigeer as propertychanged will reflect the changes immediately in the UI control.

Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
Padmanaban
  • 55
  • 7