1

So I'm using the CommonOpenFileDialog from the windowsAPICodepack. In a previous version of the application I'm writing, the CommonOpenFileDialog worked without any problems. In the current version targeted at a higher version of the .Net Framework, I get Cross-thread operation not valid exceptions even though the dialog is called from the main UI thread trough a toolstripMenuItem click event from the main form. Before it used to be called in a similar way from a button clickhandler of the main form.

Explicitly Invoking the same code that shows the CommonOpenFileDialog on the same form solves this, but the usual dialog behavior gets lost this way.

This works, but without the this.Invoke it does not.

   private void loadWorkspaceToolStripMenuItem_Click(object sender, EventArgs e)
    {
        bool wait = true;
        this.Invoke((MethodInvoker)delegate ()
        {
            string startPath = LastUsedPath;
            if (FileFolderTools.ShowFolderChooser(ref startPath))
            {
                workspace.LoadWorkspace(startPath);
                LastUsedPath = startPath;
            }

            wait = false;
        });

        while(wait){}

    }

Furthermore, although the while(wait) is there, the UI is still responsive, while usually that is not the case when a blocking call is made from a button press. Not sure what is going on here...

EDIT: more extensive stacktrace at the bottom.

Call stack when this.Invoke is called: Stack frame when this.Invoke is called

Edit - this is what the ShowfolderChooser does (returns true when ok, false on cancel):

 public static bool ShowFolderChooser(ref string path){
        CommonOpenFileDialog dialog = new CommonOpenFileDialog();
        dialog.InitialDirectory = path;
        dialog.IsFolderPicker = true;
        CommonFileDialogResult res = dialog.ShowDialog();
        if (res == CommonFileDialogResult.Ok)
        {
            path = dialog.FileName; //set new path on OK
            return true;
        }
        return false;
    }

Full exception:

This exception was originally thrown at this call stack:
[External Code]
DataManagement.DataManagementCore.Helpers.FileFolderTools.ShowFolderChooser(ref string) in FileFolderTools.cs
Camera._Camera.btn_exportAll_Click(object, System.EventArgs) in Camera.cs
[External Code]
Camera.Program.Main() in Program.cs

But wait...! There's more...

So I've tried putting that code in a separate application and there it works flawlessly. So I guess it is something with my application. One big difference I see is that my Stack frame on the loadWorkspaceToolStripMenuItem_Click has a stack entry user32.dll![Frames... Any idea where that one comes from? I assume it has something to do with that...

EDIT2 More extensive stacktrace:

at System.Windows.Forms.Control.get_Handle()
at Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialog.ApplyNativeSettings(IFileDialog dialog)
at Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialog.ShowDialog()
at Camera._Camera.btn_exportAll_Click(Object sender, EventArgs e) in D:\XXXXXXXXXXXXXX\TestProjects\DataManagementTestProject\Camera\Mainscreen\Camera.cs:line 661
at System.Windows.Forms.Control.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ButtonBase.WndProc(Message& m)
at System.Windows.Forms.Button.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at DataManagementTestProject.Program.Main() in D:\XXXXXXXXXXXXXX\TestProjects\DataManagementTestProject\Program.cs:line 20

From this stacktrace I can confirm that the call is done on the main thread of the UI, since it is called by the MessageLoop as it should be. It seems that there is an issue with the getHandle that goes wrong somewhere. But this is in the API pack it should work...

Thomas
  • 43
  • 6
  • https://stackoverflow.com/questions/661561/how-do-i-update-the-gui-from-another-thread – Ian Kemp Oct 26 '20 at 11:45
  • @IanKemp The fact is that the call to the object is (seemingly) from the main UI thread where the object is created. So it should not create this cross thread exception, yet it does. – Thomas Oct 26 '20 at 12:45
  • If you comment everything except `while(wait) { }`, what happens? -- What are `FileFolderTools.ShowFolderChooser()` and `workspace.LoadWorkspace()` doing? Maybe someone tried to decouple the Dialog from the UI Thread... – Jimi Oct 26 '20 at 13:19
  • @Jimi that results in a unresponsive as one would expect. That is also the behavior I want from a showdialog. Have the dialog in front, and not be able to do anything else on the main UI until the dialog is closed. The strange thing is that I'm doing the exact thing as in a different version and the behavior is totally different. Furthermore I'm sure that the dialog call is on the main thread. – Thomas Oct 26 '20 at 13:26
  • Can you show what `FileFolderTools.ShowFolderChooser()` and `workspace.LoadWorkspace()` are doing? – Jimi Oct 26 '20 at 13:28
  • I've added the content of the ShowFolderChooser() in an edit, LoadWorkspace loads the workspace based on the supplied folderpath. But it never gets there. – Thomas Oct 26 '20 at 13:37
  • From what I see from the original call stack that threw the exception, it is caused by external code, and not the my code. Furthermore changing controls of the form itself works, so the calling thread is definitely the UI thread. – Thomas Oct 26 '20 at 13:57
  • I don't know what tasks this code is meant to perform, but - given the `Camera` name - if you are using a library to read CameraRaw output or other video output, these libraries usually raise events to notify that a new Frame is available. These events are raised in a Thread other than the UI Thread. That's where you usually `BeginInvoke()`. If you don't, from that point on, everything you do is done in this other thread. – Jimi Oct 26 '20 at 14:50
  • The event is definitely on the main UI thread. the folderchooser is called from a toolstripmenuItem click eventhandler. This is called when the UI has a click on the menu item. It is fully separate from any other thread handling the camera. – Thomas Oct 26 '20 at 15:03

1 Answers1

1

So after a few hours of work, and digging through the issues of the package on github I found an answer here.

Although the issue is different the cause is the same: problems in getting the window handle. By calling the dialog with the handle of the current form specified there was no cross-thread access anymore in getting the handle.

Final solution: Adding a pointer to the function call. And passing the handle of the form to the dialog.

string path = LastUsedPath;
if (FileFolderTools.ShowFolderChooser(ref path, this.Handle)){
      workspace.LoadWorkspace(path);
      LastUsedPath = path;
}
    public static bool ShowFolderChooser(ref string path, IntPtr handle)
        {
            CommonOpenFileDialog dialog = new CommonOpenFileDialog();
            dialog.InitialDirectory = path;
            dialog.IsFolderPicker = true;
            CommonFileDialogResult res = dialog.ShowDialog(handle);
            if (res == CommonFileDialogResult.Ok)
            {
                path = dialog.FileName; //set new path on OK
                return true;
            }
            return false;
        }
Thomas
  • 43
  • 6
  • I had the same issue - in my case, the first item in Application.OpenForms is created for use on a different thread. It's probably just grabbing the first in the collection if you don't specify an IntPtr manually and if that's on a different thread, you get this exception. Your solution is the fix. – MineR Nov 28 '21 at 07:48