I am trying to use a FolderBrowserDialog as it was mentioned here:
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
If I call the Dialog when a button is pressed, it works just fine. But I want to open the Dialog in the middle of my code (there is an incoming file through a socket, so between receiving it and saving it I try to get the path to save it to), and it simply won't happen.
Here is the part of the code where it is called:
byte[] clientData = new byte[1024 * 5000];
int receivedBytesLen = clientSocket.Receive(clientData);
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
string filePath = dialog.SelectedPath;
int fileNameLen = BitConverter.ToInt32(clientData, 0);
string fileName = Encoding.ASCII.GetString(clientData, 4, fileNameLen);
BinaryWriter bWrite = new BinaryWriter(File.Open(filePath + "/" + fileName, FileMode.Append)); ;
bWrite.Write(clientData, 4 + fileNameLen, receivedBytesLen - 4 - fileNameLen);
bWrite.Close();
How should I try to open the Dialog for it to work?
As others stated you are most likely in a separate thread when trying to call a UI dialog.
In the code you posted you can use the WPF method BeginInvoke with a new Action that will force the FolderBrowserDialog to be invoked in a UI thread.
System.Windows.Forms.DialogResult result = new DialogResult();
string filePath = string.Empty;
var invoking = Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
var dialog = new System.Windows.Forms.FolderBrowserDialog();
result = dialog.ShowDialog();
filePath = dialog.SelectedPath;
}));
invoking.Wait();
If you are creating a separate thread you can set the ApartmentState to STA and this will allow you to call UI dialogs without having to invoke.
Thread testThread = new Thread(method);
testThread.SetApartmentState(ApartmentState.STA);
testThread.Start();
Because you're getting the STA exception, it means you are probably running on a background thread.
InvokeRequired/BeginInvoke pattern to call the dialog:
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new MethodInvoker(ShowDialog)); // where ShowDialog is your method
}
see: http://www.yoda.arachsys.com/csharp/threads/winforms.shtml.
see: Single thread apartment issue
Related
I am creating a download application in C# that downloads files from Amazon AWS S3 storage. I am able to download the file without issue, but I am trying to create a progress event.
To create the event I am using the following code within the download function:
Application.DoEvents();
response2.WriteObjectProgressEvent += displayProgress;
Application.DoEvents();
The event handler I created is as follows:
private void displayProgress(object sender, WriteObjectProgressArgs args)
{
// Counter for Event runs
label7.BeginInvoke(new Action(() => label7.Text = (Convert.ToInt32(label7.Text)+1).ToString()));
Application.DoEvents();
// transferred bytes
label4.BeginInvoke(new Action(() => label4.Text = args.TransferredBytes.ToString()));
Application.DoEvents();
// progress bar
progressBar1.BeginInvoke(new Action(() => progressBar1.Value = args.PercentDone));
Application.DoEvents();
}
The issue is that it only updates when a file it downloaded, but the event runs more often. When I download the last file (12MB); lable7 (event counter) jumps from 3 to 121, so I know it is running, but just not updating.
I have also tried just a 'standard' Invoke, but I had the same result.
Additional Code of the function:
AmazonS3Config S3Config = new AmazonS3Config
{
ServiceURL = "https://s3.amazonaws.com"
};
var s3Client = new AmazonS3Client(stuff, stuff, S3Config);
ListBucketsResponse response = s3Client.ListBuckets();
GetObjectRequest request = new GetObjectRequest();
request.BucketName = "dr-test";
request.Key = locationoffile[currentdownload];
GetObjectResponse response2 = s3Client.GetObject(request);
response2.WriteObjectProgressEvent += displayProgress;
string pathlocation = Path.GetDirectoryName(Directory.GetCurrentDirectory()) + "\\" + Instrument[currentdownload] + "\\" + NewFileName[currentdownload];
response2.WriteResponseStreamToFile(pathlocation);
You're not using the asynchronous call for GetObject or WriteResponseStreamToFile, so the UI thread (that you're calling it from) is going to be blocked, which means that it can't update the progress (regardless of those DoEvents calls, which are generally considered evil and you should avoid).
Without actually being able to try it myself, here's what I think you need to do.
private async void Button_Click(object sender, EventArgs e)
{
foreach(...download....in files to download){
AmazonS3Config S3Config = new AmazonS3Config
{
ServiceURL = "https://s3.amazonaws.com"
};
var s3Client = new AmazonS3Client(stuff, stuff, S3Config);
ListBucketsResponse response = s3Client.ListBuckets();
GetObjectRequest request = new GetObjectRequest();
request.BucketName = "dr-test";
request.Key = locationoffile[currentdownload];
GetObjectResponse response2 = await s3Client.GetObjectAsync(request, null);
response2.WriteObjectProgressEvent += displayProgress;
string pathlocation = Path.GetDirectoryName(Directory.GetCurrentDirectory()) + "\\" + Instrument[currentdownload] + "\\" + NewFileName[currentdownload];
await response2.WriteResponseStreamToFileAsync(pathlocation, null);
}
}
The two nulls that I added are for cancellation tokens, I can't tell from the AWS docs if it's allowed to pass a null token, if not please create one and pass it in.
I'm stuck trying to do something that seems like it should be easier than I'm making it.
I would like to update a status label (I.E. progress) on the GUI of the program from some worker threads.
I can't use a synchronous invoke call because the main thread is blocked on thread.Join(); to re-enable interface input/cleanup threads. This causes freezing in the program.
I do not want to use an asynchronous BeginInvoke because then it waits until the program is finished to update the progress...defeating the purpose.
tl;dr - I know WHY it won't work, but I'm not sure how to fix it.
Button_Search_Click(object sender, EventArgs e)()
{
DisableInput(); //Block input on start of function
Cursor.Current = Cursors.WaitCursor; //set wait cursor
//read combobox input
string objectType = comboBox_Object.SelectedItem.ToString();
string conditionType = comboBox_ConditionType.SelectedItem.ToString();
string conditionOperator = comboBox_equals.SelectedItem.ToString();
//create a list of worker threads
List<Thread> workerThreads = new List<Thread>();
//for each line in the textbox
foreach (string searchText in textBox_SearchText.Lines)
{
if (String.IsNullOrWhiteSpace(searchText)) continue;
//foreach line in a listbox
foreach (String uri in Get_selected_sites())
{
string cred = (creddict[uri]);
Thread thread = new Thread(() => Search(uri, cred, objectType, conditionType, conditionOperator, searchText));
workerThreads.Add(thread);
thread.Start(); //start main "work" function
}
// Wait for all the threads to finish
foreach (Thread thread in workerThreads)
{
thread.Join();
}
}
Displaydata();
EnableInput(); //unlock input
Cursor.Current = Cursors.Default; //set normal cursor
}
Search(uri, cred, objectType, conditionType, conditionOperator, searchText)
{
DoStuff();
//Update toolStripStatusLabel with progress stored in global int variable
//This doesn't work well because it waits until Thread.join() to update status label.
//BeginInvoke(new Action(() => Results_Count.Text = "Total Results = " + totalResults));
//This doesn't work at all because the main thread is blocked on Thread.Join() -- it freezes the program
//statusStrip1.Invoke(new Action(() => Results_Count.Text = "Total Results = " + totalResults));
//This doesn't work/errors/crashes because the GUI is being modified by a thread other than main
//Results_Count.Text = "Total Results = " + totalResults;
DoMoreStuff();
}
Thank you very much for any tips you can provide!
Fix your method so it looks more like this:
async void Button_Search_Click(object sender, EventArgs e)()
{
DisableInput(); //Block input on start of function
Cursor.Current = Cursors.WaitCursor; //set wait cursor
//read combobox input
string objectType = comboBox_Object.SelectedItem.ToString();
string conditionType = comboBox_ConditionType.SelectedItem.ToString();
string conditionOperator = comboBox_equals.SelectedItem.ToString();
//create a list of worker threads
List<Task> workerTasks = new List<Task>();
//for each line in the textbox
foreach (string searchText in textBox_SearchText.Lines)
{
if (String.IsNullOrWhiteSpace(searchText)) continue;
//foreach line in a listbox
foreach (String uri in Get_selected_sites())
{
string cred = (creddict[uri]);
workerTasks.Add(Task.Run(() => Search(uri, cred, objectType, conditionType, conditionOperator, searchText)));
}
await Task.WhenAll(workerTasks);
}
Displaydata();
EnableInput(); //unlock input
Cursor.Current = Cursors.Default; //set normal cursor
}
That way, the Button_Search_Click() won't block the UI thread while the tasks are running, and you can use normal Invoke() or whatever mechanism you like to post updates to the UI thread.
I'm getting InvalidOperationException. This code is running in a thread. I've delegate it to the UI thread to handle the UI. Is the problem occuring because of my resources?
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri("pack://application:,,,/LiarDice;component/" + imagePath);
logo.EndInit();
currentGuessDice.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(delegate()
{
currentGuessDice.Source = logo;
}));
I changed my code slightly a little and now the error changes to:
Part URI cannot end with a forward slash.
New Code:
currentGuessDice.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(delegate()
{
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.UriSource = new Uri("pack://application:,,,/LiarDice;component/" + imagePath);
logo.EndInit();
currentGuessDice.Source = logo;
}));
Besides that your imagePath variable my be null, the problem in your original code is that you create a BitmapImage in a thread other than the UI thread, and then use it in the UI thread.
BitmapImage is a DispatcherObject, which means that is has thread affinity. Since it is also a Freezable, you can freeze it in order to make it accessible to threads other than the one in which it was created:
var logo = new BitmapImage(
new Uri("pack://application:,,,/LiarDice;component/" + imagePath));
logo.Freeze(); // here
currentGuessDice.Dispatcher.Invoke(new Action(() => currentGuessDice.Source = logo));
I have a ListBox which I put some files, if the file is not AVI I automatically converts it but I want when the files converting message will write on a label that the files are now converted to another format, i know i need use Dispatcher in order to update the UI thread but i use now Winform instead of WPF, and i need help with this.
BTW i cannot use Task because i am using .Net 3.5
private void btnAdd_Click(object sender, EventArgs e)
{
System.IO.Stream myStream;
OpenFileDialog thisDialog = new OpenFileDialog();
thisDialog.InitialDirectory = "c:\\";
thisDialog.Filter = "All files (*.*)|*.*";
thisDialog.FilterIndex = 1;
thisDialog.RestoreDirectory = false;
thisDialog.Multiselect = true; // Allow the user to select multiple files
thisDialog.Title = "Please Select Source File";
thisDialog.FileName = lastPath;
List<string> list = new List<string>();
if (thisDialog.ShowDialog() == DialogResult.OK)
{
foreach (String file in thisDialog.FileNames)
{
try
{
if ((myStream = thisDialog.OpenFile()) != null)
{
using (myStream)
{
listBoxFiles.Items.Add(file);
lastPath = file;
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
for (int i = 0; i < listBoxFiles.Items.Count; i++)
{
string path = (string)listBoxFiles.Items[i];
FileInfo fileInfo = new FileInfo(path);
if (fileInfo.Extension != ".AVI")
{
listToRemove.Add(path);
}
}
(new System.Threading.Thread(sendFilesToConvertToPcap)).Start();
foreach (string file in listToRemove) //remove all non .AVI files from listbox
{
listBoxFiles.Items.Remove(file);
}
}
}
this function need to change the Label:
public void sendFilesToConvertToPcap()
{
if (listToRemove.Count == 0) // nothing to do
{
return;
}
lblStatus2.Content = "Convert file to .AVI...";
foreach (String file in listToRemove)
{
FileInfo fileInfo = new FileInfo(file);
myClass = new (class who convert the files)(fileInfo);
String newFileName = myClass.mNewFileName;
listBoxFiles.Items.Add(myClass._newFileName);
}
lblStatus2.Content = "Finished...";
}
From your question, it seems that you'd like to convert several files. You may want to consider using the BackgroundWorker class and overwrite the DoWork and ProgressChanged events as described in this article. You can update the label and other controls in the ProgressChanged event.
public void sendFilesToConvertToPcap()
{
.....
....
this.Invoke((MethodInvoker)delegate {
lblStatus2.Text = "Convert file to .AVI..."; });
....
}
This is a very common requirement for long-running processes. If you don't explicitly call methods on a separate thread, they will automatically run on the main (see: UI) thread and cause your UI to hand (as I suspect you already know).
http://www.dotnetperls.com/backgroundworker
Here is an old, but excellent link with an example on how to use the background worker to handle the threadpool for you.
This way, you can just create a worker to manage running your conversion process on a separate thread, and I believe there is even an example for creating a process bar.
I'm new to C#, but i've done a lots of java. Here's my problem : I'm trying to open a "SaveFileDialog" from a thread that is not the UI thread.
This is exactly what I try to do:
public partial class Form1: Form
{
public string AskSaveFile()
{
var sfd = new SaveFileDialog();
sfd.Filter = "Fichiers txt (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
sfd.FilterIndex = 1;
sfd.RestoreDirectory = true;
DialogResult result = (DialogResult) Invoke(new Action(() => sfd.ShowDialog(this)));
if(result == DialogResult.OK)
{
return sfd.FileName;
}
return null;
}
}
This method will always be called from a thread different from the one who owns the Form. The problem is that when I execute this code, the "Form1" freeze and the "SaveFileDialog" doesn't show up.
Do you have some clue to help me to show the dialog from an independant thread?
Make it look like this:
public string AskSaveFile() {
if (this.InvokeRequired) {
return (string)Invoke(new Func<string>(() => AskSaveFile()));
}
else {
var sfd = new SaveFileDialog();
sfd.Filter = "Fichiers txt (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
sfd.FilterIndex = 1;
sfd.RestoreDirectory = true;
return sfd.ShowDialog() == DialogResult.OK ? sfd.FileName : null;
}
}
If you still get deadlock then be sure to use the debugger's Debug + Windows + Threads window and look at what the UI thread is doing. Control.Invoke() cannot complete unless the UI thread is idle and pumping the message loop. Waiting for the worker thread to finish is always going to cause deadlock.
Also consider that this kind of code is risky, the user might not expect this dialog to suddenly show up and accidentally close it while mousing or keyboarding in the window(s) owned by the UI thread.
Try this:
public partial class Form1: Form
{
public string AskSaveFile()
{
if (this.InvokeRequired)
{
Invoke( new MethodInvoker( delegate() { AskSaveFile(); } ) );
}
else
{
var sfd = new SaveFileDialog();
sfd.Filter = "Fichiers txt (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
sfd.FilterIndex = 1;
sfd.RestoreDirectory = true;
if(sfd.ShowDialog() == DialogResult.OK) return sfd.FileName;
}
return null;
}
}