2

i'm using the Win32 progress dialog. The damnest thing is that when i call:

progressDialog.StopProgressDialog();

it doesn't disappear. It stays on screen until the user moves her mouse over it - then it suddenly disappers.

The call to StopProgressDialog returns right away (i.e. it's not a synchronous call). i can prove this by doing things after the call has returned:

private void button1_Click(object sender, EventArgs e)
{
   //Force red background to prove we've started
   this.BackColor = Color.Red;
   this.Refresh();

   //Start a progress dialog
   IProgressDialog pd = (IProgressDialog)new ProgressDialog();
   pd.StartProgressDialog(this.Handle, null, PROGDLG.Normal, IntPtr.Zero);

   //The long running operation
   System.Threading.Thread.Sleep(10000);

   //Stop the progress dialog
   pd.SetLine(1, "Stopping Progress Dialog", false, IntPtr.Zero);
   pd.StopProgressDialog();
   pd = null;

   //Return form to normal color to prove we've stopped.
   this.BackColor = SystemColors.Control;
   this.Refresh();
}

The form:

  • starts gray
  • turns red to show we've stared
  • turns back to gray color to show we've called stop

So the call to StopProgressDialog has returned, except the progress dialog is still sitting there, mocking me, showing the message:

Stopping Progress Dialog

alt text


Doesn't Appear for 10 seconds

Additionally, the progress dialog does not appear on screen until the

System.Threading.Thread.Sleep(10000); 

ten second sleep is over.


Not limited to .NET WinForms

The same code also fails in Delphi, which is also an object wrapper around Window's windows:

procedure TForm1.Button1Click(Sender: TObject);
var
   pd: IProgressDialog;
begin
   Self.Color := clRed;
   Self.Repaint;

   pd := CoProgressDialog.Create;
   pd.StartProgressDialog(Self.Handle, nil, PROGDLG_NORMAL, nil);

   Sleep(10000);

   pd.SetLine(1, StringToOleStr('Stopping Progress Dialog'), False, nil);
   pd.StopProgressDialog;
   pd := nil;

   Self.Color := clBtnFace;
   Self.Repaint;
end;

PreserveSig

An exception would be thrown if StopProgressDialog was failing.

Most of the methods in IProgressDialog, when translated into C# (or into Delphi), use the compiler's automatic mechanism of converting failed COM HRESULTS into a native language exception.

In other words the following two signatures will throw an exception if the COM call returned an error HRESULT (i.e. a value less than zero):

//C#
void StopProgressDialog();

//Delphi
procedure StopProgressDialog; safecall;

Whereas the following lets you see the HRESULT's and react yourself:

//C#
[PreserveSig]
int StopProgressDialog();

//Delphi
function StopProgressDialog: HRESULT; stdcall;

HRESULT is a 32-bit value. If the high-bit is set (or the value is negative) it is an error.

i am using the former syntax. So if StopProgressDialog is returning an error it will be automatically converted to a language exception.

Note: Just for SaG i used the [PreserveSig] syntax, the returned HRESULT is zero;


MsgWait?

The symptom is similar to what Raymond Chen described once, which has to do with the incorrect use of PeekMessage followed by MsgWaitForMultipleObjects:

"Sometimes my program gets stuck and reports one fewer record than it should. I have to jiggle the mouse to get the value to update. After a while longer, it falls two behind, then three..."

But that would mean that the failure is in IProgressDialog, since it fails equally well on CLR .NET WinForms and native Win32 code.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219

3 Answers3

4

To really hide the dialog, I've added the following to my C++ wrapper class:

void CProgressDlg::Stop()
{
    if ((m_isVisible)&&(m_bValid))
    {
        HWND hDlgWnd = NULL;
        //Sometimes the progress dialog sticks around after stopping it,
        //until the mouse pointer is moved over it or some other triggers.
        //This process finds the hwnd of the progress dialog and hides it
        //immediately.
        IOleWindow *pOleWindow;
        HRESULT hr=m_pIDlg->QueryInterface(IID_IOleWindow,(LPVOID *)&pOleWindow);
        if(SUCCEEDED(hr))
        {
            hr=pOleWindow->GetWindow(&hDlgWnd);
            if(FAILED(hr))
            {
                hDlgWnd = NULL;
            }
            pOleWindow->Release();
        }
        m_pIDlg->StopProgressDialog();
        if (hDlgWnd)
            ShowWindow(hDlgWnd, SW_HIDE);

        m_isVisible = false;
        m_pIDlg->Release();
        m_bValid = false;
    }
}

This is in C++, but you should be able to adapt this to C# without much problems.

Stefan
  • 43,293
  • 10
  • 75
  • 117
0

Check the return value of the StopProgressDialog Method, maybe that will give you more information about what is going on:

HRESULT StopProgressDialog(VOID);

Returns S_OK if successful, or an error value otherwise.

Community
  • 1
  • 1
Treb
  • 19,903
  • 7
  • 54
  • 87
0

The full P/Invoke signature is available, but here's the condensed version for easy reading:

[ComImport]
[Guid("EBBC7C04-315E-11d2-B62F-006097DF5BD4")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IProgressDialog
{
    void StartProgressDialog(IntPtr hwndParent,
    [MarshalAs(UnmanagedType.IUnknown)]    object punkEnableModless, //IUnknown
        PROGDLG dwFlags,  //DWORD
        IntPtr pvResevered //LPCVOID
        );
    void StopProgressDialog();
    void SetTitle(
        [MarshalAs(UnmanagedType.LPWStr)] string pwzTitle //LPCWSTR
        );
    void SetAnimation(
        IntPtr hInstAnimation, //HINSTANCE
        ushort idAnimation //UINT
        );
    [PreserveSig]
    [return: MarshalAs(UnmanagedType.Bool)]
    bool HasUserCancelled();
    void SetProgress(
        uint dwCompleted, //DWORD
        uint dwTotal //DWORD
        );
    void SetProgress64(
        ulong ullCompleted, //ULONGLONG
        ulong ullTotal //ULONGLONG
        );
    void SetLine(
        uint dwLineNum, //DWORD
        [MarshalAs(UnmanagedType.LPWStr)] string pwzString, //LPCWSTR
        [MarshalAs(UnmanagedType.VariantBool)] bool fCompactPath, //BOOL
        IntPtr pvResevered //LPCVOID
        );
    void SetCancelMsg(
        [MarshalAs(UnmanagedType.LPWStr)] string pwzCancelMsg,
        object pvResevered
        );
    void Timer(PDTIMER dwTimerAction, object pvResevered);
}

Note: That almost all of the methods follow proper COM rules for their signatures. Except HasUserCancelled. It is does not follow the rules for the signature of a method in a COM class. All methods are supposed to return an HRESULT, and return values are supposed to be in an out retval paramater. HasUserCancelled actually returns a Boolean value.

Note: That almost all these worlds are yours. Except Europa. Attempt no landing there.

Note: That almost all your base are belong to us. Except WhatYouSay. Main light turn on.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219