2
votes

Context

Before to dive into the issue, I first need to indicate that I'm trying to display a matlab modal figure when clicking some button in a winforms application.

The matlab code for displaying the figure is easy and works all fine in matlab environment (i.e. the figure is modal and I cannot click anywhere else as expected):

function [] = ShowModalDlg()
%[
    % Create a modal figure
    fig = figure('WindowStyle', 'modal');

    % Add some drawings in it
    membrane();

    % Block execution until figure gets closed.
    uiwait(fig);
%]

Compiling matlab code to .NET and integrating it into my winforms application is easy too (just need to use the Matlab Compiler SDK):

private void onBtnClick(object sender, EventArgs e)
{
    matlabComponent.ShowModalDlg();
} 

Issue

When clicking the button in my winforms application, everything seems to work fine:

  • The matlab figure popups as expected.
  • The figure stays on top and I cannot click anywhere else (i.e. modal behavior).
  • The winforms thread really blocks in matlabComponent.ShowModalDlg() until closing the figure before to continue with next lines of code.

But, because there's a but, it is all like the winform application is still continuing to enqueue UI events and when I close the matlab figure they are all processed in a row. For instance, while the matlab figure is displayed, if I try to move the winform that hold the button nothing happens, but as soon as I close the figure, the below winform moves!!

I suspect matlab-side to use different dispaching model or whatever (this is java in the background) ... anyway I'd like to block winforms to workaround this issue, for instance something like:

private void onBtnClick(object sender, EventArgs e)
{
    using(var oo = new DiscardUIEvents(this))
    {
        matlabComponent.ShowModalDlg();
    }
}  

Is this possible ?

2

2 Answers

1
votes

You can probably achieve what you need by intercepting the events using the Windows API. Take a look at this which uses the windows API and prevents a user manually moving a form. So it would be a case of adapting that for your own ends.

Edit

Try adding this code to your form and setting the _controlsEnabled in your click event handler before/after you launch your dialog. This should prevent the form from being moved. (you would obviously have to evolve this to cater for all the events you want to ignore).

    private void onBtnClick(object sender, EventArgs e)
    {
        _controlsEnabled = false;
            matlabComponent.ShowModalDlg();
        _controlsEnabled = true;
    } 

    private bool _controlsEnabled = true;

    protected override void WndProc(ref Message m)
    {
            const int WM_SYSCOMMAND = 0x0112;
            const int SC_MOVE = 0xF010;

            switch (m.Msg)
            {
                case WM_SYSCOMMAND:
                    if (!_controlsEnabled)
                    {
                         int command = m.WParam.ToInt32() & 0xfff0;
                         if (command == SC_MOVE)
                             return;
                    }
                    break;
            }
            base.WndProc(ref m);
    }
1
votes

Googling from @DotNetHitMan answer, I finally reached to PeekMessage that let me remove unwanted messages from the windows queue when leaving matlab code, so it's easy:

private void onBtnClick(object sender, EventArgs e)
{
    // Call matlab
    matlabComponent.ShowModalDlg();

    // Remove unwanted messages from the queue
    NativeMessage msg;
    while (PeekMessage(out msg, new HandleRef(this, this.Handle), WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)); // Remove mouse messages
    while (PeekMessage(out msg, new HandleRef(this, this.Handle), WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); // Remove keyboard messages
}

NB: Syntax for PeekMessage and NativeMessage interop can be read from here.

Edit

Here is the code to refactor things as a using statement:

using System;
using System.Drawing;
using System.Runtime.InteropServices;

/// <summary>Allows discarding keyboard and mouse events while thread is blocked in executing some code while its windows message queue is continuing to accumulate events.</summary>
///<example>If you don't disable events while showing matlab modal dialog, events will continue to accumulate in message queue and will be processed after modal dialog is closed (i.e. not the behaviour one expects for a modal dialog).</example>
public class DiscardUiEvents : IDisposable
{
    /// <summary>Create new discard object.</summary>
    /// <param name="hr">Reference to the control that need to block its events.</param>
    /// <remarks>
    /// Must be used like this:
    /// 
    ///    using(new DiscardUiEvent(new HandleRef(myControl, myControl.Handle)))
    ///    {
    ///         matlabCompiledCode.ShowSomeModalFigure();
    ///    }
    /// </remarks>
    public DiscardUiEvents(HandleRef hr)
    {
        this.hr = hr;    
    }

    /// <summary>Dispose discard object.</summary>
    public void Dispose()
    {
        if (disposed) { return; }

        NativeMessage msg;
        while (PeekMessage(out msg, hr, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) { /* Remove all mouse messages */ }
        while (PeekMessage(out msg, hr, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) { /* Remove all keyboard messages */ }
    }

    private readonly HandleRef hr; 
    private bool disposed = false;

    private const UInt32 PM_REMOVE = 0x0001;
    private const UInt32 WM_MOUSEFIRST = 0x0200;
    private const UInt32 WM_MOUSELAST = 0x020D;
    private const UInt32 WM_KEYFIRST = 0x0100;
    private const UInt32 WM_KEYLAST = 0x0108;

    // ReSharper disable MemberCanBePrivate.Local
    [StructLayout(LayoutKind.Sequential)]
    private struct NativeMessage
    {            
        public IntPtr handle;
        public UInt32 msg;
        public IntPtr wParam;
        public IntPtr lParam;
        public UInt32 time;
        public Point p;            
    }
    // ReSharper restore MemberCanBePrivate.Local

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool PeekMessage(out NativeMessage lpMsg, HandleRef hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
}

It can be used like this:

using(new DiscardUiEvent(new HandleRef(myControl, myControl.Handle)))
{
    matlabCompiledCode.ShowSomeModalFigure();
}