Using WPF Extended Toolkit MessageBox from other threads and curious behavior

▼魔方 西西 提交于 2020-01-26 03:15:09

问题


I am having trouble using the WPF Extended Toolkit (version 2.1.0.0) MessageBox from other threads. The namespace is: Xceed.Wpf.Toolkit.MessageBox

I replaced my regular MessageBoxs (System.Windows.MessageBox) with the Toolkit MessageBox and get errors when I launch one from another thread. The System.Windows.MessageBox has no such problems. I saw this posting that reports the problem, but there seems to be no follow up:

https://wpftoolkit.codeplex.com/workitem/21046

I'm guessing there is a work around. An example is presented there that shows the problem, but here is my simple example:

First, I wrap the Toolkit.MessageBox. I do this primarily because I'm applying style (although I've commented that out to show that's not the problem)

public class CustomMessageBox
{
    //static DummyUserControl1 _ctrl = new DummyUserControl1();

    public static MessageBoxResult Show(string msgText, Style styleArg = null)
    {
        Cursor saveCursor = Mouse.OverrideCursor;
        Mouse.OverrideCursor = null;

        //Style style = styleArg != null ? styleArg : _ctrl.FindResource("MessageBoxStyle1") as Style;
       // MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show(msgText, "", MessageBoxButton.OK, style);

        MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show(msgText, "", MessageBoxButton.OK);
        Mouse.OverrideCursor = saveCursor;
        return result;
    }


}

The main window just has two buttons on it, and here's the code behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }



    private void btnMainThreadMsgBox_Click(object sender, RoutedEventArgs e)
    {
        CustomMessageBox.Show("Hello on main thread");
    }

    private void btnAltThreadMsgBox_Click(object sender, RoutedEventArgs e)
    {

        Thread altThread1 = new Thread(new ThreadStart(AltThread1Proc));

        altThread1.SetApartmentState(ApartmentState.STA);
        altThread1.Priority = ThreadPriority.AboveNormal;
        altThread1.IsBackground = true;

        altThread1.Start();
    }

    public void AltThread1Proc()
    {
        MessageBox.Show("Hello on Alt Thread");
        CustomMessageBox.Show("Hello on alt thread");

    }
}

The problems occur in AltThreadProc() with CustomMessageBox.Show(...). The curious behavior I referred to is this: If you hit the main thead button and then the Alt thread button, you get the error:

Cannot access Freezable 'System.Windows.Media.SolidColorBrush' across threads because it cannot be frozen.

However, if you skip the main thread button and just hit the Alt thread button, you get the error: The calling thread cannot access this object because a different thread owns it.

I'm curious what the "Freezable" error is all about and why you can get different errors based on what would seem to be an innocuous event: clicking/not clicking button that produces message box on main thread.

Ideally, it would be nice to just replace System.Windows.MessageBox with Xceed.Wpf.Toolkit.MessageBox, but if there is some sort of extra code to write, that might be acceptable. The documentation, and the link I provided hints at using a WindowContainer, but I can't really see any examples of how you do that. I was attracted to the Toolkit MessageBox as it allows one to do some cool stuff with MessageBox (which I don't show here) such as apply styles, change the text of the OK, CANCEL button, etc.

Any ideas would be much appreciated.

Thanks, Dave


Extra info: User1341210 suggestion works well if you just have one window. However, if you have a second window in it's own thread it doesn't work so well. Perhaps someone can tell me what I'm doing wrong. I use the suggestion of the TaskScheduler, but the code throws an exception if the TaskScheduler used is the one of the second window. That is, all works fine if I use the TaskScheduler of the first window, but throws an exception if I use the TaskScheduler of the second window. Here is the code behind for my second window:

public partial class AltThreadWindow : Window
{
    private TaskScheduler _ui;


    public AltThreadWindow()
    {
        InitializeComponent();
        _ui = TaskScheduler.FromCurrentSynchronizationContext();

    }

    // This constructor is for passing in the TaskScheduler of the mainwindow and works great
    public AltThreadWindow(TaskScheduler scheduler)
    {
        InitializeComponent();
        _ui = scheduler;
    }

    private void btnWindowsMsgBox_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Standard Windows message box");
    }

    private void btnCustomMsgBox_Click(object sender, RoutedEventArgs e)
    {
        MessageBoxResult result;
        Task.Factory.StartNew(() => { result = CustomMessageBox.Show("Custom MessageBox on separate window"); }, CancellationToken.None,
            TaskCreationOptions.None,
            _ui);
    }


}

Notice the two constructors. The default one assigns the TaskScheduler of the second window. The other constructor allows one to pas in the TaskScheduler of the main Window.
Here's the code I use to launch the second window from the main window. Again, I'm launching the second window on another thread, and I pass in the TaskScheduler of the main window. It would be nice to use the TaskScheduler of the second window instead.

 _altWindowThread = new Thread(new ThreadStart(AltWinThreadProc));
        _altWindowThread.SetApartmentState(ApartmentState.STA);
        _altWindowThread.Priority = ThreadPriority.AboveNormal;
        _altWindowThread.IsBackground = true;

        _altWindowThread.Start();

And the actual threadproc:

[EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
    public void AltWinThreadProc()
    {
        // Create our context, and install it:
        SynchronizationContext.SetSynchronizationContext(
            new DispatcherSynchronizationContext(
                Dispatcher.CurrentDispatcher));

        _altWindow = new AltThreadWindow(_ui);
        _altWindow.Show();
        System.Windows.Threading.Dispatcher.Run();

    }

Notice here I pass in the TaskScheduler of the MainWindow.


回答1:


we had the same issue in our application (I created the work item on codeplex). The error messages are quite confusing and I cant provide you an answer to that.

But:

We didn't used a separated WindowContainer to solve it. Instead came up with calling the separate task/thread with the UI scheduler:

Task.Factory.StartNew(
            () => { result = CustomMessageBox.Show(messageText); },
            CancellationToken.None,
            TaskCreationOptions.None,
            _ui);

Where _ui is assigned in a method that is executed from UI context (e.g. Constructor of your Window/Control:

_ui = TaskScheduler.FromCurrentSynchronizationContext();

Hope this helps for solving the "replace System.Windows.MessageBox with Xceed.Wpf.Toolkit.MessageBox" part of your question.


If you want that the messagebox shows up on another Window you have to set the "Owner" property of the message box to the other window.

Best regards.



来源:https://stackoverflow.com/questions/25298156/using-wpf-extended-toolkit-messagebox-from-other-threads-and-curious-behavior

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!