2

I have a simple page with a button and a label on it. When I click the button I want the label text to update, I have copied code from here

However when i use the following example. The text is not updated until the function is completed. Any ideas. WPF windows form With button and label dropped on it.

I have the following in the codebehind the button depresses and the first message is never shown.

The Thread.Sleep is to signify pulling data from a database to return to the screen, this can take anything from 3 - 30 seconds, hence why I want to make it work to show the user something.

Any ideas ???

using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace NetSpot.RESV4.Presentation
{
   /// <summary>
   /// Interaction logic for Window1.xaml
   /// </summary>
   public partial class Window1 : Window
   {
       public Window1()
       {
           InitializeComponent();
       }

       private void button1_Click(object sender, RoutedEventArgs e)
       {
           label1.Content = "Button 1 Clicked";
           ForceUIToUpdate();
           Thread.Sleep(4000);
           label1.Content = "button 1 Updated";
       }

       public static void ForceUIToUpdate()
       {
           DispatcherFrame frame = new DispatcherFrame();
           Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new     DespatcherOperationCallback(delegate(object parameter)
        {
            frame.Continue = false;
            return null;
        }), null);
        Dispatcher.PushFrame(frame);
    }

  }
}
Community
  • 1
  • 1
Welsh King
  • 3,178
  • 11
  • 38
  • 60
  • Is there a particular reason to not use a bound property, and INotifyPropertyChanged? If not, that seems less complicated than a Dispatcher. – ianschol Mar 01 '12 at 15:54

3 Answers3

3

If you want the label to update 4 seconds later you would probably be better off using a Dispatcher Timer.

public partial class Window1 : Window
{
   readonly DispatcherTimer m_timer = new DispatcherTimer();
   public Window1()
   {
       InitializeComponent();

       m_timer.Interval = TimeSpan.FromSeconds(4);
       m_timer.Tick += TimerOnTick;
   }

   private void button1_Click(object sender, RoutedEventArgs e)
   {
       label1.Content = "Button 1 Clicked";
       m_timer.Start();
   }

   private void TimerOnTick(object sender, EventArgs eventArgs)
   {
       m_timer.Stop();
       label1.Content1 = ...
   }
Phil
  • 42,255
  • 9
  • 100
  • 100
  • Apologies I was using the thread sleep to signify pulling data from the database. That can take any time from 3 - 30 seconds – Welsh King Mar 01 '12 at 17:07
  • Then accessing the database should be done asynchronously, or on a separate worker thread. – Phil Mar 01 '12 at 17:34
2

To answer your question directly (regardless of methodology), you're never signaling the UI that it should be updating after the initial value change. This signal is occurring automatically after the click event finishes. Add a call to label1.UpdateLayout(), like so :

   private void button1_Click(object sender, RoutedEventArgs e)
   {
       label1.Content = "Button 1 Clicked";
       label1.UpdateLayout();
       ForceUIToUpdate();
       Thread.Sleep(4000);
       label1.Content = "button 1 Updated";
   }

Now you should see the expected behavior.

Edit - Give this version of ForceUIToUpdate a try.

   public static void ForceUIToUpdate()
   {
       DispatcherFrame frame = new DispatcherFrame();
       Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new     DispatcherOperationCallback(delegate(object parameter)
       {
           frame.Continue = false;
           return null;
       }), null);
       Dispatcher.PushFrame(frame);
   }

This should cause ForceUIToUpdate to block until the frame gets through the Render queue, which is what you actually want.

Lennart
  • 9,657
  • 16
  • 68
  • 84
ianschol
  • 586
  • 2
  • 13
  • strangly this works the second time the button is clicked, but not the first. investigating now – Welsh King Mar 01 '12 at 17:41
  • In my scratch application (which is probably simpler than yours) it works right off the bat. You could try `label1.InvalidateArrange()`. – ianschol Mar 01 '12 at 21:31
  • yep it works, however ive found that by adding a thread.sleep(1000) it works much better. However now when i try using the code on the home page of my app it works really well. However when i try it on another Window it doesnt work. So fustrating. Ive stripped the code back to something really simple too :(. Im still looking for resolution – Welsh King Mar 02 '12 at 11:31
  • @Welsh King - Wait, you don't want anything else to occur before "Button 1 clicked" takes effect, right? Perhaps you should change `ForceUIToUpdate()` to use `Invoke` rather than `BeginInvoke`. This changes it to a blocking call, rather than async. I've updated my example with code. Side note, there may be a better way to do whatever you're trying to do, because it's rare to need to force a UI update, and it's also rare to need to use `Invoke` rather than `BeginInvoke`. You know what they say, two rares don't make a right. That's how it goes, isn't it? – ianschol Mar 02 '12 at 17:23
0

Try not to put to sleep the current thread, instead simply sets the value of the other thread through the dispatcher.

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        Task.Run(() =>
        {
            Dispatcher.Invoke(new Action(() =>
            {
                label1.Content = "Waiting";
            }), null);

            Thread.Sleep(2000);

            Dispatcher.Invoke(new Action(() =>
            {
                label1.Content = "Done";
            }), null);

        });
    }

Hope this helps.

Update: with async/await

    private async void button1_Click(object sender, RoutedEventArgs e)
    {
        label1.Content = "Waiting";

        await Task.Run(() => Thread.Sleep(2000));

        label1.Content = "Done";
    }
supertheg
  • 1
  • 3