none
Dispatcher.PushFrame Issue RRS feed

  • Question

  • I've created my own MessageBox class is functionally the same as the old Win32 one, but prettier.

     

    It has static methods that create a new canvas with some controls on it and adds it to my main window.

     

    I then call:

    Code Snippet

    DispatcherFrame frame = new DispatcherFrame();

    Dispatcher.PushFrame(frame);

     

    Internally within my MessageBox class, it has a reference to the frame object I created, and on the click event handler for my OK button, I call:

    Code Snippet

    if (_frame != null)

    {

        _frame.Continue = false;

    }

     

    Which exits the new message loop (or whichever term WPF uses).  However, this causes a strange sequence of events:

    1. When called from within a delegate that was called by Dispatcher.Invoke or Dispatcher.BeginInvoke on my main window via a seperate thread, the MessageBox never paints.
    2. The program's execution halts on Dispatcher.PushFrame(frame) (as it's supposed to), however it still processes my click events as though the MessageBox isn't there.
    3. So effectively I'm able to click throughout my entire program without the screen ever repainting.  So it's somehow deadlocked, without being really deadlocked.

    I know creating nested message-loops can cause problems, but this is very strange.  Is there a better way I can make this MessageBox-like functionality work?  or is there a better for calling the MessageBox from within Invoke/BeginInvoke?  (There are many times I need to throw execution on the main thread, so that the MessageBox won't throw cross-thread exceptions)

    Wednesday, May 23, 2007 3:51 PM

All replies

  • Note: this behavior doesn't happen in every case, and I've been trying to create a project that reproduces the issue.

     

    However, I've not found any difference between the cases that work and the cases that don't work.  Any ideas or known problems where I should look?

    Wednesday, May 23, 2007 4:44 PM
  • Showing a modal dialog is more involved that just doing a nested message loop. At a minimum, you should disable the parent window to prevent the kind of unexpected input handling you are describing (that's what MessageBox does). If you have multiple windows on the same thread, you should consider disabling them all.

    The best way to do a "custom message box" in WPF is by using Window.ShowDialog().

    Thursday, May 24, 2007 5:36 PM
  • The reason I don't use ShowDialog is b/c my custom MessageBox has transparency associated with it, and enabling layered windows (AllowsTransparency=True, WindowStyle=None) significantly raises CPU usage for my application.

     

    My app has to run on "less-than-optimal" pcs and has continuous animations, a graphically rich UI, etc.  So I've had to make many adjustments to account for performance.

     

    So this nested message loop is the best I've been able to come up with thus far.  It's quick, lightweight, only creates a canvas, a couple buttons, and labels rather than creating a whole new Window.  I've just had this strange nested message loop issue holding me up in one rare case, and was wondering if there was a better solution than message loops or a way to fix my problem.

     

    Since my original post, I now call VerifyAccess() immediately in any MessageBox function to enforce cross-threading errors (before I had an InvokeRequired property I checked via CheckAccess() and I would throw the execution on the main thread if needed).  And this has fixed all places where the error happens, except for one.

     

    Should I post more information on the issue?  maybe a stacktrace?  I can't really post any source-code since my app is 30,000+ LOC (even with object-oriented code) and I haven't been able to produce another project that replicates the problem.  However, I suppose I could post my MessageBox class in it's entirety.

     

    PS - my entire App is only one Window, but with lots of logic/functionality within.

    Thursday, May 24, 2007 6:25 PM
  • My class (which i wish had tabs):

     

    Code Snippet

    public class MessageBox : FadeCanvas

    {

    #region Static Members

    private static List<MessageBox> _messageBoxes = new List<MessageBox>();

    private static Style _textStyle = null,

    _buttonStyle = null;

    private static DropShadowBitmapEffect _dropShadow = null;

    private static Rectangle _background = null;

    static MessageBox()

    {

    Global.Window.Dispatcher.Invoke(DispatcherPriority.Normal, (MethodInvoker)

    delegate

    {

    //Background Rectangle

    _background = new Rectangle();

    _background.Fill = Brushes.Black;

    _background.Opacity = .7;

    _background.Width = 800;

    _background.Height = 600;

    });

    }

    private static void AddInstance(MessageBox mbox)

    {

    _messageBoxes.Add(mbox);

    if (Global.Window != null)

    {

    UIElementCollection collection = ((MainWindow)Global.Window).Root.Children;

    if (!collection.Contains(_background))

    {

    collection.Add(_background);

    }

    collection.Add(mbox);

    }

    }

    private static void RemoveInstance(MessageBox mbox)

    {

    _messageBoxes.Remove(mbox);

    if (Global.Window != null)

    {

    UIElementCollection collection = ((MainWindow)Global.Window).Root.Children;

    collection.Remove(mbox);

    if (_messageBoxes.Count == 0)

    {

    collection.Remove(_background);

    }

    }

    }

    /// <summary>

    /// Show a MessageBox with no buttons

    /// </summary>

    /// <param name="header">Message header</param>

    /// <param name="contents">Message contents</param>

    /// <returns>

    /// A delegate with which upon calling, closes the MessageBox

    /// *Note, you must call the returned delegate on the main thread

    /// </returns>

    public static MethodInvoker ShowWithoutButtons(string header, string content)

    {

    //Throws a cross thread exception if we're on the wrong thread

    Global.Window.VerifyAccess();

    MessageBox mbox = new MessageBox(header, content, null);

    AddInstance(mbox);

    return (MethodInvoker)

    delegate

    {

    RemoveInstance(mbox);

    };

    }

    //Span overloads

    public static MessageBoxResult Show(string header, params Inline[] contents)

    {

    return Show(header, MessageBoxButton.OK, null, contents);

    }

    public static MessageBoxResult Show(string header, MessageBoxButton buttons, params Inline[] contents)

    {

    return Show(header, buttons, null, contents);

    }

    public static MessageBoxResult Show(string header, MessageBoxButton buttons, ImageSource source, params Inline[] contents)

    {

    //Throws a cross thread exception if we're on the wrong thread

    Global.Window.VerifyAccess();

    DispatcherFrame frame = new DispatcherFrame();

    MessageBox mbox = null;

    if (source == null)

    {

    mbox = new MessageBox(header, buttons, frame, contents);

    }

    else

    {

    mbox = new MessageBox(header, buttons, source, frame, contents);

    }

    AddInstance(mbox);

    Dispatcher.PushFrame(frame);

    RemoveInstance(mbox);

    return mbox.Result;

    }

    //String only overloads

    public static MessageBoxResult Show(string header, string content)

    {

    return Show(header, content, MessageBoxButton.OK, null);

    }

    public static MessageBoxResult Show(string header, string content, MessageBoxButton buttons)

    {

    return Show(header, content, buttons, null);

    }

    public static MessageBoxResult Show(string header, string content, MessageBoxButton buttons, ImageSource source)

    {

    //Throws a cross thread exception if we're on the wrong thread

    Global.Window.VerifyAccess();

    DispatcherFrame frame = new DispatcherFrame();

    MessageBox mbox = null;

    if (source == null)

    {

    mbox = new MessageBox(header, content, buttons, frame);

    }

    else

    {

    mbox = new MessageBox(header, content, buttons, source, frame);

    }

    AddInstance(mbox);

    Dispatcher.PushFrame(frame);

    RemoveInstance(mbox);

    return mbox.Result;

    }

    #endregion

    #region Instance Members

    private DispatcherFrame _frame = null;

    private MessageBoxResult _result = MessageBoxResult.None;

    private MessageBox(string header, DispatcherFrame frame)

    {

    //set styles if they're null

    if (Global.Window != null && _textStyle == null && _buttonStyle == null && _dropShadow == null)

    {

    _textStyle = Global.Window.Resources["BaseTextStyle"] as Style;

    _buttonStyle = Global.Window.Resources["ButtonStyle"] as Style;

    _dropShadow = Global.Window.Resources["DropShadow"] as DropShadowBitmapEffect;

    }

    //Save the DispatcherFrame

    _frame = frame;

    //Main image

    Image image = new Image();

    image.Source = new BitmapImage(new Uri("/Images/MessageBox.png", UriKind.Relative));

    Canvas.SetLeft(image, 155);

    Canvas.SetTop(image, 105);

    Children.Add(image);

    //Header

    TextBlock textblock = new TextBlock();

    textblock.Style = _textStyle;

    textblock.TextWrapping = TextWrapping.NoWrap;

    textblock.TextTrimming = TextTrimming.CharacterEllipsis;

    textblock.FontSize = 28;

    textblock.FontFamily = Fonts.Bold;

    textblock.Width = 365;

    textblock.Text = header;

    textblock.BitmapEffect = _dropShadow;

    Canvas.SetLeft(textblock, 235);

    Canvas.SetTop(textblock, 140);

    Children.Add(textblock);

    }

    private MessageBox(string header, string content, DispatcherFrame frame)

    : this(header, frame)

    {

    //Content

    TextBlock textblock = new TextBlock();

    textblock.Style = _textStyle;

    textblock.TextWrapping = TextWrapping.Wrap;

    textblock.TextTrimming = TextTrimming.CharacterEllipsis;

    textblock.FontSize = 20;

    textblock.Width = 400;

    textblock.Height = 195;

    textblock.Text = content;

    Canvas.SetLeft(textblock, 200);

    Canvas.SetTop(textblock, 210);

    Children.Add(textblock);

    }

    private MessageBox(string header, DispatcherFrame frame, params Inline[] contents)

    : this(header, frame)

    {

    //Content

    TextBlock textblock = new TextBlock();

    textblock.Style = _textStyle;

    textblock.TextWrapping = TextWrapping.Wrap;

    textblock.TextTrimming = TextTrimming.CharacterEllipsis;

    textblock.FontSize = 20;

    textblock.Width = 400;

    textblock.Height = 195;

    foreach (Inline inline in contents)

    {

    textblock.Inlines.Add(inline);

    }

    Canvas.SetLeft(textblock, 200);

    Canvas.SetTop(textblock, 210);

    Children.Add(textblock);

    }

    private MessageBox(string header, string content, MessageBoxButton buttons, DispatcherFrame frame)

    : this(header, content, frame)

    {

    CreateButtons(buttons);

    }

    private MessageBox(string header, MessageBoxButton buttons, DispatcherFrame frame, params Inline[] contents)

    : this(header, frame, contents)

    {

    CreateButtons(buttons);

    }

    private MessageBox(string header, string content, MessageBoxButton buttons, ImageSource image, DispatcherFrame frame)

    : this(header, content, buttons, frame)

    {

    CreateImage(image);

    }

    private MessageBox(string header, MessageBoxButton buttons, ImageSource image, DispatcherFrame frame, params Inline[] contents)

    : this(header, buttons, frame, contents)

    {

    CreateImage(image);

    }

    private void CreateImage(ImageSource image)

    {

    Image picture = new Image();

    picture.Width = 148;

    picture.Height = 158;

    picture.Stretch = Stretch.UniformToFill;

    picture.Source = image;

    Canvas.SetLeft(picture, 225);

    Canvas.SetTop(picture, 275);

    Children.Add(picture);

    }

    private void CreateButtons(MessageBoxButton buttons)

    {

    Button button = null;

    ButtonUris uris = null;

    switch (buttons)

    {

    case MessageBoxButton.OK:

    //OK button

    button = new Button();

    button.Style = _buttonStyle;

    uris = new ButtonUris();

    uris.Up = new Uri("MBOKUp.png", UriKind.Relative);

    uris.Down = new Uri("MBOKDown.png", UriKind.Relative);

    button.Content = uris;

    button.Click += new RoutedEventHandler(OK_Click);

    Canvas.SetLeft(button, 530);

    Canvas.SetTop(button, 395);

    Children.Add(button);

    break;

    case MessageBoxButton.OKCancel:

    //TODO: Implement if needed

    break;

    case MessageBoxButton.YesNo:

    //No button

    button = new Button();

    button.Style = _buttonStyle;

    uris = new ButtonUris();

    uris.Up = new Uri("MBNoUp.png", UriKind.Relative);

    uris.Down = new Uri("MBNoDown.png", UriKind.Relative);

    button.Content = uris;

    button.Click += new RoutedEventHandler(No_Click);

    Canvas.SetLeft(button, 530);

    Canvas.SetTop(button, 395);

    Children.Add(button);

    //Yes button

    button = new Button();

    button.Style = _buttonStyle;

    uris = new ButtonUris();

    uris.Up = new Uri("MBYesUp.png", UriKind.Relative);

    uris.Down = new Uri("MBYesDown.png", UriKind.Relative);

    button.Content = uris;

    button.Click += new RoutedEventHandler(Yes_Click);

    Canvas.SetLeft(button, 430);

    Canvas.SetTop(button, 395);

    Children.Add(button);

    break;

    case MessageBoxButton.YesNoCancel:

    //TODO: Implement if needed

    break;

    default:

    break;

    }

    }

    private MessageBoxResult Result

    {

    get { return _result; }

    }

    private void OK_Click(object sender, RoutedEventArgs e)

    {

    _result = MessageBoxResult.OK;

    if (_frame != null)

    {

    _frame.Continue = false;

    }

    }

    private void Yes_Click(object sender, RoutedEventArgs e)

    {

    _result = MessageBoxResult.Yes;

    if (_frame != null)

    {

    _frame.Continue = false;

    }

    }

    private void No_Click(object sender, RoutedEventArgs e)

    {

    _result = MessageBoxResult.No;

    if (_frame != null)

    {

    _frame.Continue = false;

    }

    }

    #endregion

    }

     

    Calling a regular show method causes the problem when it's called from within an anonymous method via Invoke from a seperate thread.

    Thursday, May 24, 2007 6:46 PM
  • Anyone?

     

    Is this just a difficult issue?

    Thursday, May 31, 2007 4:54 PM
  • Your explanation for why you don't use ShowDialog makes no sense at all. You make it sound like ShowDialog somehow uses more resources that what you're trying to do, which it does not.

     

    You want to show a dialog modally, like a message box. So use ShowDialog.

    Thursday, May 31, 2007 7:04 PM
  • Well...

     

    My MessageBox is 50% transparent in places.

     

    So I'd have to set AllowsTransparency="True" on the new dialog window.  This significantly raises the CPU usage if you set this on a window with continuous animations. (Note: you have to set it on the main window and the dialog) or the animation will not paint correctly for the layered windows.

     

    Try it out if you don't believe me, but that is what happened from my results.  The process used 10% cpu without AllowsTransparency and 60-70% with it (on the machine my app runs on, of course which is Tier 1 and has awful on-board graphics).

     

    That is why I can't use a modal dialog, which I thought was thoroughly explained above.  Is there a reason this doesn't make sense?  Am I wrong in creating a new Window class and calling ShowDialog in that manner?  Or is there a better ModalDialog class that I don't know of in WPF?

    Thursday, May 31, 2007 7:19 PM
  • If your window has transparency, you're going to need to have AllowsTransparency set to true, end of story. If you don't set it to true, how exactly did you expect your transparency to work? Perhaps your derived window has some code to enable transparency without setting AllowsTransparency to true, but I'd be very wary of doing this because WPF won't understand it.

     

    Either way, you still have a window derivative presumably, so why can't you call ShowDialog with it? Unless of course you're not using Window as your base class, but why throw away all that WPF functionality?

    Friday, June 1, 2007 12:11 PM
  • That is pretty obvious.

     

    That is why I'm not using a Window (my code is above, and there is no Window for the MessageBox), I created a MessageBox wrapper class that creates a Canvas and adds it to my main Window.  It then calls Dispatcher.PushFrame to block in the MessageBox.Show method until the OK button is pressed.  That way I never had to use AllowsTransparency and I had plenty of CPU to work with in my Application.

     

    So I don't get what is confusing or why we're having this conversation about ShowDialog or AllowsTransparency.  Dispatcher.PushFrame is what we should be discussing (as suggested by the title of my post), as deriving from Window is obviously not an option if I want to use strictly WPF.

     

    Here is my options as I see it:

    1. Fix the Dispatcher.PushFrame option (which is what I want to do), if someone has a suggestion to why it is giving the strange behavior as explained initially.
    2. Derive from Window & use ShowDialog and somehow not use AllowsTransparency--which is probably impossible (and I thought I said this initially).
    3. Another solution, which may be what I'm looking for. (Win32 interop, etc.)

    I know any option that would work in my situation is not going to be optimal in anyone's eyes--creating a graphically rich UI that can run on a very crappy PC is difficult.  What I'm looking for here is a workaround, not an obvious or initial answer to the problem. 

     

    Thanks for any help so far.

    Friday, June 1, 2007 2:48 PM
  • Here's a screenshot of my app to see what I'm talking about:

     

                                     My Screenshot

     

    The darkened region around the box is the transparent portion.  My original version used WinForms (this is a pic of the newer WPF one), and this functionality has been much easier in WPF.

    Friday, June 1, 2007 2:53 PM
  • Jonathan,

    What you are trying to do is not possible: to run a "modal canvas" within a WPF window. Input is routed across the entire element tree within a PresentationSource. (Good overview in the SDK: http://msdn2.microsoft.com/en-us/library/ms742806.aspx.) Calling Dispatcher.PushFrame() just creates a nested message loop, but the message routing is the same. There are no public APIs that can let you restrict event routing to a subtree.

    There are known performance problems with transparent Windows. Improvements will be coming up in the next release/service pack.

    Wednesday, June 6, 2007 7:04 PM
  • I'm not necessarily trying to create a "modal canvas" it was just a method that seems to work via Dispatcher.PushFrame.  Add a new canvas to my window, nest a message loop, exit from the new loop when they hit OK.  This only works because my app is full screen, and the new canvas covers the entire window, so no click events are able to go through the canvas. (Users only have access to a touch screen)

     

    A modal window is the best solution for my problem, but isn't currently possible for my situation.  When will the next release with the performance fix be available?  And are you referring to .Net 3.5 and Orcas?

    Thursday, June 7, 2007 6:24 PM
  • I know this is an old thread but I wanted to share my solution in case anyone is interested.

    This is what I've found appears to be working:

    var dialog = new MyDialog(...);
    panel.Children.Add(dialog);
    ComponentDispatcher.PushModal();
    try
    {
     DispatcherFrame frame = new DispatcherFrame(true);
     dialog.Frame = frame;
     Dispatcher.PushFrame(frame);
    }
    finally
    {
     ComponentDispatcher.PopModal();
     panel.Children.Remove(dialog);
    }
    

    The key is the call to the "ComponentDispatcher.PushModal()" method. The same method is used by "Window.ShowDialog" when a window is displayed as a modal dialog.

    You can also check this solution as an alternative.

    Regards,
    Jecho

    • Proposed as answer by Jecho Jekov Friday, October 8, 2010 9:44 AM
    Friday, October 8, 2010 9:34 AM
  • Sorry to tell you, that also does not work. I'll have to find another solution.
    Thursday, October 21, 2010 9:09 PM