Solved

WPF UI Not Updating

Posted on 2011-03-09
11
964 Views
Last Modified: 2013-11-12
Given a WPF form with a Button (Button1) set to default and a TextBox (TextBox1)

I would like to disable the button as soon as it is clicked, and then execute some long running code.

Normal WPF behaviour would not refresh the disabled button straight away, rather the interface will wait for the code in the button click handler to finish executing and then disable the button for a split second and re-enable it.

Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click

‘disable the button
Button1.IsEnabled = False

‘Now perform a long running code
System.Threading.Thread.Sleep(5000)

‘re-enable the button
Button1.IsEnabled = True

End Sub
               
Of all the solutions I have seen posted on the internet, majority of them involve forcing the Dispatcher to update the UI before running the long running code.  This works for most scenarios, however if the focus is on the TextBox, and the button click event is executed by clicking the ‘Enter’ button on your keyboard (Button is default button on form), the solution mentioned earlier would fail almost all of the time.

Does anyone out there have a solution to this problem where if I was to execute the button click event by pressing Enter on keyboard whilst the focus was on the TextBox, the button would disable straight away before executing the sleep?
0
Comment
Question by:TonySutt2
  • 6
  • 5
11 Comments
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 35085643
Is the long running code in its own thread?
Can you show the code that you are using?
0
 

Author Comment

by:TonySutt2
ID: 35085994
No the long running code is not on its own thread and is probably too long to paste in here.
However the code in the question is attempting to simulate the long running code with the system.threading.thread.sleep(5000) which I believe if we could get that working then it would solve my issue as well.
0
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 35090974
Hi TonySutt2;

Here is a solution using a BackgroundWorker component to run the long running code in the button click event.

Imports System.Threading
Imports System.Windows.Threading
Imports System.ComponentModel

Class MainWindow

    ' BackgroundWorker Component
    Private bgWorker As BackgroundWorker

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        ' Create the background worker
        bgWorker = New BackgroundWorker()
        ' Add Events
        AddHandler bgWorker.DoWork, AddressOf bgWorker_DoWork
        AddHandler bgWorker.RunWorkerCompleted, AddressOf bgWorker_RunWorkerCompleted

        ' Start the background worker thread
        bgWorker.RunWorkerAsync()

        ' Disable the button so user can not click it again until
        ' long running process is done
        Button1.IsEnabled = False

    End Sub

    Private Sub bgWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)

        ' This code runs in a different thread
        ' Now perform a long running code which is currently in your button event
        Thread.Sleep(5000)

    End Sub

    Private Sub bgWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)

        ' Long running process is completed re-enable the button so that it can be clicked again
        ' re-enable the button
        Button1.IsEnabled = True
        ' Remove the Events
        RemoveHandler bgWorker.DoWork, AddressOf bgWorker_DoWork
        RemoveHandler bgWorker.RunWorkerCompleted, AddressOf bgWorker_RunWorkerCompleted
        ' Clean up
        bgWorker.Dispose()
        bgWorker = Nothing

    End Sub

End Class

Open in new window


Fernando
0
 

Author Comment

by:TonySutt2
ID: 35093351
I would like to keep the long running code in the main thread as it needs access to a data context created on the main thread.
A background worker would not allow access to this unless you detach and attach the data context but that would be a long messy solution just to disable a button. I wonder if you have any thoughts on this?
0
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 35097246
Hi TonySutt2;

To your statement, "I would like to keep the long running code in the main thread as it needs access to a data context created on the main thread.", Data stored in a class is accessible from another thread. The only issue is modifying the control created by another thread and the BackgroundWorker component takes care of that if the updates to the forms controls are modified from the BackgroundWork ReportProgress event handler.

To your statement, "A background worker would not allow access to this unless you detach and attach the data context but that would be a long messy solution just to disable a button. I wonder if you have any thoughts on this?", The entities will not need to be detached and the re-attached. As I stated in the above the data of the class will be available to the thread.

Fernando
0
Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

 

Author Comment

by:TonySutt2
ID: 35107485
Hi

Thanks for your quick response.

The data context that I am using is a linq data context and the only concern I have about accessing this from a different thread is taken from a Microsoft statement below:

"Any public static (Shared in Visual Basic) members of this type are thread safe. any instance members are not guaranteed to be thread safe."

The above statement can be found at http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.aspx

0
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 35109399
Hi TonySutt2;

What does it mean to be thread safe, "A piece of code is thread-safe if it functions correctly during simultaneous execution by multiple threads.".

Unless you have more then one thread accessing the same data simultaneously you do not have a problem.

Basic Instincts Thread Synchronization:
http://msdn.microsoft.com/en-us/magazine/cc163929.aspx

Fernando
0
 

Author Comment

by:TonySutt2
ID: 35110305
Hi Fernando

Appreciate your speedy responses.

Have just tried to implement your code into the actual project but the BackgroundWorker thread method is opening up a can of worms in that the "long running routine" interacts with label controls which in turn need to refesh (render) immediately whilst the long routine is running. So there becomes the issue of manipulating a control that was created on a different thread and nested BackgroundWorker threads to force other controls to refresh (render).

Is there any other way to force these controls to refresh (render) immediately without resorting to the BackgroundWorker method?

Tony
0
 
LVL 62

Accepted Solution

by:
Fernando Soto earned 500 total points
ID: 35111805
Hi Tony;

This is an excerpt from a post in this thread, "To your statement, "I would like to keep the long running code in the main thread as it needs access to a data context created on the main thread.", Data stored in a class is accessible from another thread. The only issue is modifying the control created by another thread and the BackgroundWorker component takes care of that if the updates to the forms controls are modified from the BackgroundWork ReportProgress event handler."

The code snippet below shows how to overcome this issue.

Imports System.ComponentModel

Class MainWindow

    ' BackgroundWorker Component
    Private bgWorker As BackgroundWorker

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)

        ' Create the background worker
        bgWorker = New BackgroundWorker()
        ' Add Events
        AddHandler bgWorker.DoWork, AddressOf bgWorker_DoWork
        AddHandler bgWorker.RunWorkerCompleted, AddressOf bgWorker_RunWorkerCompleted
        '============================================================
        ' Added so that BackgroundWorker can report progress
        AddHandler bgWorker.ProgressChanged, AddressOf bgWorker_ProgressChanged
        bgWorker.WorkerReportsProgress = True
        '============================================================
 
        ' Start the background worker thread
        bgWorker.RunWorkerAsync()

        ' Disable the button so user can not click it again until
        ' long running process is done
        Button1.IsEnabled = False

    End Sub

    Private Sub bgWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)

        ' This code runs in a different thread
        ' Now perform a long running code which is currently in your button event
    
        Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)        
        ' Report status to a Label on the form
        ' First parameter is an integer of how much work is completed. If you
        ' do not need it just assign a value to it. The second parameter is
        ' of type Object, In this case I am using an array of strings which
        ' inherits from object. I am pass the name of a control to be updated
        ' and the value to update it with
        worker.ReportProgress(0, New String() {"Label", "Starting Long Process"})        
        Thread.Sleep(5000)
        ' Report status to a TextBox on the form
        worker.ReportProgress(0, New String() {"TextBox", "Long Process Has Ended"})

    End Sub

    Private Sub bgWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)

        ' Long running process is completed re-enable the button so that it can be clicked again
        ' re-enable the button
        Button1.IsEnabled = True
        ' Remove the Events
        RemoveHandler bgWorker.DoWork, AddressOf bgWorker_DoWork
        RemoveHandler bgWorker.RunWorkerCompleted, AddressOf bgWorker_RunWorkerCompleted
        ' Need to remove this now as well
        RemoveHandler bgWorker.ProgressChanged, AddressOf bgWorker_ProgressChanged        
        ' Clean up
        bgWorker.Dispose()
        bgWorker = Nothing

    End Sub

    ' This event handler updating the GUI that is on a different thread
    Private Sub bgWorker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)

        ' Get the information to update the form without throwing and exception
        ' In this set up I am using array index zero as the control to be updated
        ' and index one as the message to be used as the message.
        Dim status() As String = DirectCast(e.UserState, String())

        ' Select the control and update its status
        Select Case status(0)
            Case "Label"
                Label1.Content = status(1)
            Case "TextBox"
                TextBox1.Text = status(1)
        End Select

    End Sub

End Class

Open in new window


Fernando
0
 

Author Comment

by:TonySutt2
ID: 35128017
Hi Fernando

Your example code does work perfectly. Just before I award you the points do you know of any other way to achieve this as it will be a long hard slog to incorporate that method into our existing project.

Tony
0
 
LVL 62

Expert Comment

by:Fernando Soto
ID: 35128288
Hi Tony;

You state the following, "Your example code does work perfectly.", can you be more specific?

As you know WPF has not implemented the DoEvents function. Therefore in order to keep the GUI responsive you need to implement another thread and off load the long running code there.so that the GUI can respond to events.This is because the GUI is a sequentially processing single thread and so when it is in the button click event nothing else is being processing from the message pump but only the code in the button click event.  

Fernando
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

This tutorial is about how to put some of your C++ program's functionality into a standard DLL, and how to make working with the EXE and the DLL simple and seamless.   We'll be using Microsoft Visual Studio 2008 and we will cut out the noise; that i…
As more and more people are shifting to the latest .Net frameworks, the windows presentation framework is gaining importance by the day. Many people are now turning to WPF controls to provide a rich user experience. I have been using WPF controls fo…
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now