5

I already searched the forum but no asked question fits to my problem I think ...

I use a BackgroundWorker to handle a very time consuming operation. After this operation finishes a button should be set to enabled so that the user can perform another operation.

I'm using WPF and I'm following the MVVM pattern, so I have no direct access to this button. I have to call a method in the BackgroundWorker_RunWorkerCompleted event handler which sets a Property, representing the buttons enabled-state, to true.

That all works fine except one thing: The button is only redrawn after I e.g. click into the window (or maximize the window, ...). Thats very annoying and took me the whole day to get rid of this behaviour but I can't find a solution ...

The BackgroundWorker_RunWorkerCompleted event handler looks like this:

    void fileLoadBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        SetButtonToEnabled(); }

Has anyone any idea how to fix this issue?

Edit:

The button is bound to a command:

<Button Name="btnLoadTargetFile" Command="{Binding Path=LoadTargetCommand}" .../>

This command is a RelayCommand as Smith stated in his blog (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx) and looks like this:

public RelayCommand LoadTargetCommand
    {
        get
        {
            if (loadTargetCommand == null)
            {
                loadTargetCommand = new RelayCommand(param => this.OnRequestLoadFile(BusinessLogic.CustomTypes.TreeType.Target), param => this.CanLoadTarget);
            }
            return loadTargetCommand;
        }
        set { loadTargetCommand = value; }
    }

this.CanLoadTarget is set to true by the SetButtonToEnabled(); method

Edit 2:

the following code 'works':

fileLoadBackgroundWorker.RunWorkerAsync(argumentList);
while(fileLoadBackgroundWorker.IsBusy)
  System.Windows.Forms.Application.DoEvents();
SetButtonToEnabled();

but that is some sort of really dangerous and ugly code ...

lakai
  • 209
  • 1
  • 4
  • 14
  • When you set the property to true do you raise the `PropertyChanged` event (ie. does your viewmodel implement `INotifyPropertyChanged` correctly)? – Klaus Byskov Pedersen Jan 05 '10 at 15:19
  • If I call that method after fileLoadBackgroundWorker.RunWorkerAsync() everything works fine, so I don't think that is the problem?! – lakai Jan 05 '10 at 15:26
  • Just out of curiosity, are you using any WMI modifications to the form's shape or border (border color, etc.)? – Nathan Wheeler Jan 05 '10 at 15:29
  • klausbyskov is probably correct / has a good point. So - DO you implement INotifyPropertyChanged correctly? :) – Benjamin Podszun Jan 05 '10 at 15:30
  • @md5sum: no I don't! @benjamin: yes I do ;-) ... i ported the code from a non-threading environment to the current, everything worked fine before we started to use the BackgroundWorker – lakai Jan 05 '10 at 15:37

2 Answers2

2

Althought it doesnt explain your problem, you could try to add

System.Windows.Forms.Application.DoEvents();

or

Dispatcher.Invoke(new Action(delegate { }), DispatcherPriority.Background);

to the end of your SetButtonToEnabled function call (after you set the button Enabled to true). It looks like the backgroundworker is calling the Complete event, but the GUI is not refreshing.

Here is a complete example using the Backgroundworker in WPF. You might want to take a look at it and make sure you stuff looks the same.

You could also take a look at Asynchronous Threading with Background Worker Object. This is a good example of using the Dispatcher and the Backgroundworker

The WPF Threading Model is another great source to check out.

SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
  • thanks for your comment! my code looks like the example except that I have no direct access to the buttons because of the MVVM pattern I use – lakai Jan 06 '10 at 09:18
  • Could you post the code for SetButtonToEnabled. I dont think its a problem with backgroundworker, since you mentioned that Complete does get called. Its probably something you are doing on the main thread. What are you doing while the process is running in the background? – SwDevMan81 Jan 06 '10 at 12:33
  • 1
    You might want to check out InvalidateRequerySuggested: http://msdn.microsoft.com/en-us/library/system.windows.input.commandmanager.invalidaterequerysuggested.aspx Try adding this to the end of SetButtonToEnabled – SwDevMan81 Jan 06 '10 at 12:40
  • This post might also help: http://stackoverflow.com/questions/783104/refresh-wpf-command – SwDevMan81 Jan 06 '10 at 15:00
  • Thanks a lot for the links, the call of CommandManager.InvalidateRequerySuggested() fixed it! – lakai Jan 07 '10 at 08:20
1

Your problem probably has nothing to do with the BackgroundWorker. If you call the SetButtonToEnabled method directly from the main thread you will probably see the same behavior. Replace the call to RunWorkerAsync with one to SetButtonToEnabled to test this.

A couple of the commenters suggested that you look at your INotifyPropertyChanged implementation this sounds like good advice.

Scott Munro
  • 13,369
  • 3
  • 74
  • 80
  • thanks for your answer! If i call SetButtonToEnabled in the main thread everything works fine ... – lakai Jan 05 '10 at 15:47
  • Can you confirm that the RunWorkerCompleted event is being raised? Does a breakpoint that you set there get hit? – Scott Munro Jan 05 '10 at 15:51
  • The only other thing that I can think of is to confirm that the event handler is being executed on the main thread. – Scott Munro Jan 05 '10 at 16:07
  • 1
    In SetButtonToEnabled, check the Button's InvokeRequired Property to see if its true – SwDevMan81 Jan 05 '10 at 16:30
  • Actually for WPF, looks like you check the Dispatcher, so something like this: btnRefresh.Dispatcher.CheckAccess(). See this post: http://www.diranieh.com/NET_WPF/Threading.htm – SwDevMan81 Jan 05 '10 at 17:00
  • I think that the Debug Location Toolbar should show you which thread that you are on. Access it via View | Toolbars | Debug Location – Scott Munro Jan 05 '10 at 17:47