7

I'm developing a WPF desktop application in c# 4.0 which has to handle a lot of long-running operations (loading data from the DB, calculating simulations, optimizing routes, etc.).

When these long-running operations run in the background I want to show a Please-Wait dialog. When the Please-Wait dialog is shown the application should be locked, but to just disable the application window isn't a good idea because all the DataGrids would lose their status (SelectedItem).

What I have so far works but there are some problems: A new WaitXUI is created using the Create-factory method. The Create method expects caption text and a refernce to the host control that should be locked. The Create method sets the StartupLocation of the window, the caption text and the host to lock:

WaitXUI wait = WaitXUI.Create("Simulation running...", this);
wait.ShowDialog(new Action(() =>
{
    // long running operation
}));

With the overloaded ShowDialog method the WaitXUI can then be displayed. The ShowDialog overload does expect an Action which wraps the long running operation.

In the ShowDialog overload I just start the Action in its own thread and then disable the host control (set Opacity to 0.5 and set IsEnabled to false) and call ShowDialog of the base class.

public bool? ShowDialog(Action action) 
    {
    bool? result = true;

    // start a new thread to start the submitted action
    Thread t = new Thread(new ThreadStart(delegate()
    {
        // start the submitted action
        try
        {
            Dispatcher.UnhandledException += Dispatcher_UnhandledException;
            Dispatcher.Invoke(DispatcherPriority.Normal, action);
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            // close the window
            Dispatcher.UnhandledException -= Dispatcher_UnhandledException;
            this.DoClose();
        }
    }));
    t.Start();

    if (t.ThreadState != ThreadState.Stopped)
    {
        result = this.ShowDialog();
    }
    return result;
}

private new bool? ShowDialog() 
{
    DisableHost();
    this.Topmost = true;
    return base.ShowDialog();
}

private void DisableHost()
{
    if (host != null)
    {
        host.Dispatcher.Invoke(new Action(delegate()
        {
            this.Width = host.Width - 20;
            host.Cursor = Cursors.Wait;
            host.IsEnabled = false;
            host.Opacity = 0.5;
        }));
    }
}

Here are the problems with this:

  • Disabling the host control results in lost of status information (SelectedItems...)
  • The WaitXUI sometimes is shown for only some milliseconds when the thread ends a few ms after the WaitXUI is shown
  • Sometimes the dialog doesn't appear at all although the thread is still running

These are the main problems which come to my mind at the moment. How can this concept be improved, or what other methods can be employed to address this problem?

Thanks in advance!

CJBS
  • 15,147
  • 6
  • 86
  • 135
goflo
  • 353
  • 1
  • 4
  • 11
  • Not even going to post as a question as it is ugly. Set every control to IsEnabled = false. Or capture any input at the Window and set e.handled = true. – paparazzo Feb 12 '14 at 17:08
  • This is certainly a duplicate. – UIlrvnd Feb 12 '14 at 17:17
  • http://stackoverflow.com/questions/3480966/display-hourglass-when-application-is-busy – Jimmy Feb 12 '14 at 17:21
  • @Blam But that doesn't solve any of my problems. – goflo Feb 13 '14 at 07:36
  • @Akane There are similar questions but all with other (lower) requirements! Stating my question as a duplicate and not posting a solution isn't helpful ;-) – goflo Feb 13 '14 at 07:40

2 Answers2

11

A little lateral thinking always helps when developing WPF applications. You can fulfil your requirements easily with just a Grid, a Rectangle, a bool property (which you could already have) and a BooleanToVisibilityConverter and you won't have to disable any controls.

The idea is simple. Add a white Rectangle in front of your view content with its Opacity property set between 0.5 and around 0.75. Data bind its Visibility property to the bool property in your view model or code behind and plug in the BooleanToVisibilityConverter:

<Grid>
    <Grid>
        <!--Put your main content here-->
    </Grid>
    <Rectangle Fill="White" Opacity="0.7" Visibility="{Binding IsWaiting, 
        Converter={StaticResource BooleanToVisibilityConverter}}" />
    <!--You could add a 'Please Wait' TextBlock here-->
</Grid>

Now when you want to disable the controls, you just set the bool property to true and the Rectangle will make the UI appear faded:

IsWaiting = true;
Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Thanks for your answer. That's the same concept BusyIndicator (suggested by _Eugene P_) is using. Would be a possible solution. The only thing I don't like is that I have to add the code above to each UI from which a WaitXUI is called. And there are a lot of UIs (> 50) using the wait dialog. Would be nice if a can do that at one place (in the WaitXUI)! :-/ – goflo Feb 13 '14 at 08:00
  • You seem to have misunderstood... If you put this in `MainWindow.xaml` and your `bool` property in the main view model, then you only need one. Put *all* your normal app content where it says `Put your main content here`. – Sheridan Feb 13 '14 at 09:10
  • Ok, you are right. I need that pice of code only in each window and not in each UI. I will try that. Thanks! – goflo Feb 18 '14 at 08:04
  • 1
    Without giving the full solution with the concept of BoolToVisibilityConverter or any hint for that the answer is not complete. – Adam Ri May 06 '19 at 09:19
  • Is that your way of asking for clarification, @AdamRi? If you had read my answer properly, you would have seen that I mentioned the standard `BooleanToVisibilityConverter`, that is included with the .NET Framework. Therefore, it would have clear to you that the reference to the mysterious `BoolToVisibilityConverter` was nothing more than a typing error and that you should use the aforementioned `BooleanToVisibilityConverter` instead. But thanks for your comment. – Sheridan May 07 '19 at 08:29
1

Don't really need to create own implementation, I think it's redundant.

take a look into already created component, like BusyIndicator, for similar needs. which is vital and effective. .

more info from codeplex

Eugene P.
  • 2,645
  • 19
  • 23
  • Thanks for your answer. BusyIndicator is using the same concept suggested by Sharidan. That would be a possible solution. The only thing I don't like is that I have to add the UI code for the BusyIndicator to each UI from which a WaitXUI is called. And there are a lot of UIs (> 50) using the wait dialog. Would be nice if a can do that at one place (in the WaitXUI)! :-/ – goflo Feb 13 '14 at 08:01
  • I'm not sure I got idea you try to reach, but anyway, busyindicator is bindable, you can set it to root view placeholder, and then inject any child(page/etc) view. Next you can use Binding for control customization easily – Eugene P. Feb 13 '14 at 10:00