1

I am using this wonderful framework for doing modal dialogs in WPF.

Using this framework, I am trying to get a modal dialog to overlay another modal dialog when a user clicks a button from within the first dialog. There is an example of this in the DemoApp for this framework which just uses the _dialogmanager to first pop up one MessageDialog and then another.

The code that does this from the DemoApp looks like this:

private void ShowLayeredDialog()
{
    _dialogManager
        .CreateMessageDialog("Wait 2 secs...", "I'm the 1st dialog", DialogMode.Ok)
        .Show();

    ThreadPool.QueueUserWorkItem(o =>
        {
            Thread.Sleep(2000);
            _dialogManager
                .CreateMessageDialog("Hello again...", "I'm the 2nd dialog", DialogMode.Ok)
                .Show();
        });
}

I tried to do something similar but instead of using the method call to CreateMessageDialog, I wanted to use their CreateCustomContentDialog(), which takes an object and displays its contents (provided its a UIElement) in a modal view.

So, having already called the _dialogManager to get me into the first modal view, I created a button on that view that would spawn a new CustomContentDialog like so using a technique similar to their DemoApp code:

ThreadPool.QueueUserWorkItem(o => _dialogManager.CreateCustomContentDialog(new SpikePhaseView(), DialogMode.Ok).Show());

Unfortunately, I get the exception 'The calling thread must be STA, because many UI components require this' on the constructor for SpikePhaseView(), which is a vanilla UserControl.

So having researched this error here and here I implemented the un-accepted, but highly up-voted solution from the second link of setting the ApartmentState(ApartmentState.STA) like so:

var test = new Thread(() => _dialogManager.CreateCustomContentDialog(new SpikePhaseView(), DialogMode.Ok).Show());
test.SetApartmentState(ApartmentState.STA);
test.Start();

But then somewhere down WpfDialogManagment framework code, I get this error 'The calling thread cannot access this object because a different thread owns it.' on this block of code:

public void SetCustomContent(object content)
{
    CustomContent.Content = content;
}

Above, the CustomContent (A System.Windows.Controls.ContentControl) is being set to my SpikePhaseView object.

Edit

In the DemoApp they are able to launch two modal dialogs successfully (without error). Why can't I have one UserControl (view) spawn another without having this conflict over which thread is setting the content of this CustomContext object?

It seems like setting the ApartmentState helped get me past the first error but if this all boils down to using the Dispatcher, can someone provide an example of how I can use it in my situation to fire off a call to launch the second modal view?

Thanks

Community
  • 1
  • 1
Isaiah Nelson
  • 2,450
  • 4
  • 34
  • 53
  • Dude, I'm too lazy to read all this. I'll just tell you that regardless of `[STAThread]` you cannot use multiple threads to manipulate a single UI in WPF. Try something else. – Federico Berasategui Jun 06 '13 at 17:05

1 Answers1

2

You don't want to have multiple UI threads in your application unless you're really, really sure that you need it. It will greatly complicate matters if you always need to be thinking about which thread owns which controls. It's much easier if there is just one UI thread and it owns everything.

Rather than trying to make the new thread that you create an STA thread, just ensure that your second popup is created/accessed/shown from the main UI thread.

If you're using C# 5.0 this is very easy:

//Consider changing the return type to Task
//if this method is not used as an event handler
private async void ShowLayeredDialog()
{
    _dialogManager
        .CreateMessageDialog("Wait 2 secs...", "I'm the 1st dialog", DialogMode.Ok)
        .Show();

    await Task.Delay(2000);

    _dialogManager
        .CreateMessageDialog("Hello again...", "I'm the 2nd dialog", DialogMode.Ok)
        .Show();
}

The await call will ensure that the remainder of the method (the creation of the second dialog) is run as a continuation of the first task, and it will ensure that it runs in the captured SynchronizationContext (in this case representing the UI thread). The end result is that the code doesn't sit there blocking a background thread, keeps you at just one declaration scope, will perform better, handles errors better, is less typing, etc.

The C# 4.0 method of doing this is a bit more code:

private void ShowLayeredDialog()
{
    _dialogManager
        .CreateMessageDialog("Wait 2 secs...", "I'm the 1st dialog", DialogMode.Ok)
        .Show();

    Task.Delay(2000).ContinueWith(t =>
    {
        _dialogManager
            .CreateMessageDialog("Hello again...", "I'm the 2nd dialog", DialogMode.Ok)
            .Show();
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

That's (more or less) what the first snippet is turned into by the compiler.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • The caveat I see here is that I am already in the first modal dialog and I am simply trying to launch another from where I am presently at. Does this affect the code you have here? Or should I still try to use async? – Isaiah Nelson Jun 06 '13 at 17:21
  • @IsaiahNelson Well, if you want to be interacting with two forms at the same time, what it means is that you shouldn't be using modal dialogs; you should be using non-modal windows (and disabling/hiding any windows that you don't want the user interacting with at the current time) instead. – Servy Jun 06 '13 at 17:23
  • You make a good point. I am using modal dialogs because the user needs to see what is going on in the background while they run a "test" in the foreground. Nonetheless, I applied your code. It works and now I am dumbfounded why it works. I need to read more on Tasks it seems or async. – Isaiah Nelson Jun 06 '13 at 17:28
  • By the way, thank you for your effort in reading and answering my post. If you felt like my post was organized and showed some research effort, please give it an upvote as well :). Its hard getting points! – Isaiah Nelson Jun 06 '13 at 17:30