Link to home
Start Free TrialLog in
Avatar of TonySutt2
TonySutt2

asked on

WPF UI Not Updating

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?
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Is the long running code in its own thread?
Can you show the code that you are using?
Avatar of TonySutt2
TonySutt2

ASKER

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.
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
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?
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
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

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
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
ASKER CERTIFIED SOLUTION
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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
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