Note: Question has been reworded to distinguish it more clearly from: this
I have some code firing from windows shell context menu, that brings up a form to display progress to the user.
The code implementing as follows:
The explorer interface:
public void InvokeCommand(IntPtr pici)
{
CMINVOKECOMMANDINFO ici = (CMINVOKECOMMANDINFO)Marshal.PtrToStructure(
pici, typeof(CMINVOKECOMMANDINFO));
// Identify what was clicked etc...
TransferSalesProject(ici.hwnd);
}
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214e4-0000-0000-c000-000000000046")]
internal interface IContextMenu
{
void InvokeCommand(IntPtr pici);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CMINVOKECOMMANDINFO
{
public uint cbSize;
public CMIC fMask;
public IntPtr hwnd;
public IntPtr verb;
[MarshalAs(UnmanagedType.LPStr)]
public string parameters;
[MarshalAs(UnmanagedType.LPStr)]
public string directory;
public int nShow;
public uint dwHotKey;
public IntPtr hIcon;
}
The code calling the form:
async void TransferSalesProject(IntPtr hWnd)
{
try
{
string basePath = @"C:\Folder1";
string destFolder = @"P:\Folder1"
// Check that project hasn't already been transferred.
if (!Directory.Exists(destFolder))
{
// Move folder to projects current folder.
DirectoryMove dirMove = new DirectoryMove(basePath, destFolder);
dirMove.ShowDialog();
}
else
{
MessageBox.Show("Destination Folder already exists.", "Error");
}
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Exception");
}
}
The code in the form:
public partial class DirectoryMove : Form
{
public string BaseFolder { get; set; }
public string DestFolder { get; set; }
public Progress<string[]> progress { get; set; }
public DirectoryMove(string baseFolder, string destFolder)
{
InitializeComponent();
this.BaseFolder = baseFolder;
this.DestFolder = destFolder;
progress = new Progress<string[]>();
progress.ProgressChanged += (sender, progressValue) =>
{
if (!this.IsDisposed && this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
this.BringToFront();
this.progressBar.PerformStep();
this.Text = progressValue[0];
this.progressText.Text = progressValue[1];
});
}
else
{
this.BringToFront();
this.progressBar.PerformStep();
this.Text = progressValue[0];
this.progressText.Text = progressValue[1];
}
};
}
private async void DirectoryMove_Load(object sender, EventArgs e)
{
try
{
await Task.Run(() =>
{
try
{
DirectoryCopy(BaseFolder, DestFolder);
}
catch
{
throw;
}
});
}
catch (Exception exception)
{
MessageBox.Show(this, exception.Message, "Exception");
}
finally
{
this.Close();
}
}
private void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs = true)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
((IProgress<string[]>)progress).Report(new string[] { "Copying Files", file.FullName });
file.CopyTo(temppath, false);
}
// If copying subdirectories, copy them and their contents to new location.
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
}
I seem to be getting a InvalidOperationException
when I fire this.Close()
stating (DirectoryMove is the name of my form):
Cross-thread operation not valid: Control 'DirectoryMove' accessed from a thread other than the thread it was created on.
However this exception only comes up in debug, and seems to be inconsistent in firing. It seems to mostly come up when I trigger other context menu item click events, then fire this one.
I get this off the stack:
at System.Windows.Forms.Control.get_Handle()
As the error suggests, its caused because I am trying to access the form from a non-UI thread. But the form_load event seems like it should run on the UI thread. Philippe Paré has suggested to Invoke
my form close, and this seems to have fixed it.
But it leads to the question, why is my form not running on the UI thread. The only reason I can see at the moment is that explorer is doing some sort of weird threading itself, when I call the form. Phillip has suggested it is due to the context of my code changing when I fire Task.Run, but I have tried to emulate this issue with simple form calling a form with the same structure, and haven't been able to replicate the issue.
Anyone who can explain why would be appreciated.