Showing busy indicator on a STA thread - c#

I have a long operation wehre I'd like to show the Extended Toolkits busy indicator. I made a previous post about this and it was fixed Wpf Extended toolkit BusyIndicator not showing during operation. However, during that call I have to interact with a UI element (canvas) and I get a "The calling thread must be STA, because many UI components require this". I understand (now) that a background worker(see code):
private void CboItemId_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
BackgroundWorker _backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
ItemSearchBusyIndicator.IsBusy = true;
// Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
if (RdoItemSearch.IsChecked == false) return;
///backgroundWorker_DoWork(null, null);
if (CboItemId.SelectedValue == null) return;
if (CboItemId.SelectedValue.ToString() != string.Empty)
{
selectedItem = CboItemId.SelectedValue.ToString();
_backgroundWorker.RunWorkerAsync();
}
// Mouse.OverrideCursor = System.Windows.Input.Cursors.Arrow;
}
public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
LoadItemData(selectedItem);
}
uses MTA and cannot be set to STA. So i tried calling the internal function that uses the UI elelment in its own thread:
public void LoadItemData(string itemId)
{
Axapta ax = new Axapta();
files.Clear();
try
{
ax.Logon(Settings.Default.Server, null, Settings.Default.Test, null);
AxaptaContainer path = (AxaptaContainer)ax.CallStaticClassMethod(Settings.Default.ClassName, Settings.Default.ItemData, itemId);
for (int i = 1; i <= path.Count; i++)
{
AxaptaContainer somestring = (AxaptaContainer)path.get_Item(i);
for (int j = 1; j <= somestring.Count; j += 2)
{
string extension = Path.GetExtension(somestring.get_Item(j + 1).ToString().ToLower());
if (extension == ".jpg"
|| extension == ".jpeg"
|| extension == ".gif"
|| extension == ".png"
|| extension == ".bmp"
|| extension == ".pdf")
/* key=path - value=description */
files.Add(somestring.get_Item(j + 1).ToString(), somestring.get_Item(j).ToString());
}
}
// _canvas.Children.Clear();
Thread t = new Thread(new ThreadStart(LoadPictures));
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
ax.Logoff();
}
}
Heres where I interact with the canvas element:
private void LoadPictures()
{
foreach (DictionaryEntry filePath in files)
{
try
{
Picture p = new Picture();
ToolTip t = new ToolTip();
t.Content = filePath.Value;
p.ToolTip = t;
TextBlock tb = new TextBlock();
tb.Text = filePath.Value.ToString();
Canvas.SetTop(tb, y);
Canvas.SetLeft(tb, x);
p.ImagePath = filePath.Key.ToString();
p.OriginalImagePath = filePath.Key.ToString();
p.ImageName = filePath.Value.ToString();
_canvas.Children.Add(p); //<-------This is where i seem to error
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message,"File Load Error",MessageBoxButton.OK,MessageBoxImage.Error);
}
}
}
but I get a "The calling thread cannot access this object because a different thread owns it"
I don't know how to call the long running (LoadItemData()) function while showing the BusyIndicator without a backgroundworker. Any help appreciated

There are multiple approaches:
1) Async binding, it's not recommended, but it is there. You can run long running task in property getter, framework will prevent UI from blocking, when it is finished - UI will get updated.
2) Use BackgroundWorker or Task/Thread to run code, but invoke it into UI thread. In your example:
Dispatcher.InvokeAsync(() => _canvas.Children.Add(p));
3) You can block UI thread of main window completely, no problems. But to indicate about its being busy you can create window in another thread and show there busy status (run animations, etc):
var thread = new Thread(() =>
{
var window = new SomeWindow();
window.ShowDialog();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();

Related

Sleep Thread With IF condition in C#

In my C# windows form, I want to set notification label with loop and thread before fill the data into data grid view . for that I try to use below method and thread.
My Thread is
public void Run()
{
System.Windows.Forms.MessageBox.Show("this is System Thread");
m = new Dashboard();
string text = "";
Int32 a = 0;
do
{
Thread.Sleep(1000);
notiCount++;
/*Notifications ne = new Notifications(m.loadNotification);
ne.Invoke(text);*/
//Thread.Sleep(1000);
}
while (notiCount <= 10) ;
}
My method is
private void loadINtransfer(string status)
{
Int32 x = 0;
string text="";
SystemThreadings s = new SystemThreadings();
Thread t = new Thread(s.Run);
t.Start();
Thread.Sleep(100);
do
{
if (s.notiCount == 0)
{
text = "Request Data";
Thread.Sleep(1000);
this.loadNotification(text);
// MessageBox.Show("request");
}
else if (s.notiCount == 3)
{
text = "Fetching Data";
Thread.Sleep(1000);
//MessageBox.Show("request");
this.loadNotification(text);
}
else if (s.notiCount == 6)
{
text = "Loading Data";
Thread.Sleep(1000);
this.loadNotification(text);
//MessageBox.Show("loading");
}
else if (s.notiCount == 9)
{
text = "Done";
Thread.Sleep(1000);
this.loadNotification(text);
//MessageBox.Show("done");
}
//Thread.Sleep(200);
}
while (s.notiCount != 10);
//Thread.Sleep(5000);
MessageBox.Show("attach");
dt = dl.loadTransfer(status);
dgvDashboard.AutoGenerateColumns = false;
dgvDashboard.DataSource = dt;
}
but it only show Done part. other if parts are passing but not show. And I want to wait some time the all parts before load data to grid view. how can I do this. Please help Me.
As I alluded to in the comments, this is pretty well a poster-child use case for BackgroundWorker. I created a new Windows Forms application, added a button and a label (I didn't bother to rename them), and then double-clicked the button to add an event handler. After adding the BackgroundWorker code, this is what Form1.cs ended up looking like:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var bw = new BackgroundWorker();
bw.DoWork += Bw_DoWork;
bw.WorkerReportsProgress = true;
bw.ProgressChanged += Bw_ProgressChanged;
bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
bw.RunWorkerAsync();
}
private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("attach");
//dt = dl.loadTransfer(status);
//dgvDashboard.AutoGenerateColumns = false;
//dgvDashboard.DataSource = dt;
}
private void Bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 0)
{
label1.Text = "Request Data";
}
else if (e.ProgressPercentage == 3)
{
label1.Text = "Fetching Data";
}
else if (e.ProgressPercentage == 6)
{
label1.Text = "Loading Data";
}
else if (e.ProgressPercentage == 9)
{
label1.Text = "Done";
}
}
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
int notiCount = 0;
do
{
Thread.Sleep(1000);
notiCount++;
worker.ReportProgress(notiCount);
}
while (notiCount <= 10);
}
}
}
Notice that we're not having to sleep or wait on the UI thread. When progress has been made, we're notified and can access the UI to update it. And when the work is complete, we again get notified.
The only Thread.Sleep we have is within the background worker, and I assume there is standing for actual useful work being done.
You absolutely should not have any Thread.Sleep calls in any constrained context such as the UI thread, where there aren't likely to be any other threads available to service other uses which may want to use your thread whilst you're just blocking it. Even Sleeping a threadpool thread is acting as a bad "team player" in modern multi-threaded applications.

WPF/MVVM - Is there is any event fired after GUI get updated

In my application I used to add a lot of text in Richtextbox. It takes a lot of time, so i have added a progress bar. I need to close that as soon as GUI is updated. Is there is any to to catch after GUI is updated.
I have tried with App.Current.MainWindow.IsLoaded == true, but it seems even the gui is not updated, but MainWindow.IsLoaded property is true for main window.
Herewith the code for the same.
private void OnWorkerMethodstart()
{
pbw = new ProgressBarWindow();
pbw.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
pbw.ShowDialog();
}
));
}
private void OnWorkerMethodStart()
{
ThreadStart tStart = new ThreadStart(OnWorkerMethodstart);
t = new Thread(tStart);
t.SetApartmentState(ApartmentState.STA);
t.Start();
this.OpenW();
BackgroundWorker _BackgroundWorker1 = new BackgroundWorker();
_BackgroundWorker1.DoWork += new DoWorkEventHandler(
delegate(object o, DoWorkEventArgs args)
{
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
DateTime da = DateTime.Now.AddMinutes(30);
MainWindow mw;
while (DateTime.Now != da)
{
mw = new MainWindow();
//if (mw.Editor.Workflow.Steps.Count > 1)
if (App.Current.MainWindow.IsLoaded == true)
{
t.Abort();
break;
}
else
{
//Thread.Sleep(1000);
}
mw = null;
}
}
));
});
_BackgroundWorker1.RunWorkerAsync();
}

c# threads and cannot call invoke

I have start c# a few time ago, so there is somethings that are totally diferent from java, a few time ago I face a problem about a thread changing the UI (add rows to a DataGridView) and I found that I had to call the method Invoke to make that happen. I did and all works fine, but now I'm facing a new problem. So, I have a frame that will display some labels added dynamically and in Java i would like this:
Thread t = new Thread() {
public void run() {
while (true) {
// for each label
for (it = labels.iterator(); it.hasNext();) {
JLabel lb = it.next();
if (lb.getLocation().x + lb.getWidth() < 0) {
if (msgsRemover.contains(lb.getText().toString())) {
it.remove();
MyPanel.this.remove(lb);
msgsRemover.remove(lb.getText().toString());
} else {
// if there is no message to be removed, this will just continue
// going to the end of the queue
MyPanel.this.remove(lb);
MyPanel.this.add(lb);
}
MyPanel.this.repaint();
MyPanel.this.validate();
}
lb.setLocation(lb.getLocation().x - 3, 0);
}
MyPanel.this.repaint();
try {
SwingUtilities.invokeAndWait(running);
sleep(30);
} catch (InterruptedException ex) {
Logger.getLogger(MyPanel.class.getName()).log(Level.SEVERE, null, ex);
} catch (InvocationTargetException ex) {
Logger.getLogger(MyPanel.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
};
But in C# I have a problem when the thread does:
MyPanel.this.remove(lb);
MyPanel.this.add(lb);
So I did:
if (lb.Location.X + lb.Width < 0) {
if (msgsRemover.Contains(lb.Text.ToString())) {
labels.Remove(label);
this.Invoke(new MethodInvoker(() => { this.Controls.Remove(lb); }));
msgsRemover.Remove(lb.Text.ToString());
} else {
// if there is no message to be removed, this will just continue
// going to the end of the queue
this.Invoke(new MethodInvoker(() => { this.Controls.Remove(lb); }));
this.Invoke(new MethodInvoker(() => { this.Controls.Add(lb); }));
}
this.Invoke(new MethodInvoker(() => { this.Refresh(); }));
But know I'm getting an error called "Can not call Invoke or BeginInvoke on a control until the window handle has been created."
I have searched for solutions but I didn't find out what can I do to solve this.
Thank you in advance for your help!
Edit: start the thread is the last thing I do in the constructor... There is the code:
public MyPanel(Color corLabel, Color back, Font text){
this.color = corLabel;
this.backg = back;
this.textFont = text;
this.Width = 500000;
texto = new LinkedList<string>();
msgs = new LinkedList<MensagemParaEcra>();
labels = new LinkedList<Label>();
var it = labels.GetEnumerator();
var it2 = msgsRemover.GetEnumerator();
this.FlowDirection = FlowDirection.LeftToRight;
this.BackColor = backg;
this.Size = new Size(500000, 30);
this.Refresh();
startThread();
}
You must start the thread after the control has a handle created in order to be able to do Invoke, the easiest way to do that is override the OnHandleCreated method and start your thread there instead.
public MyPanel(Color corLabel, Color back, Font text)
{
this.color = corLabel;
this.backg = back;
this.textFont = text;
this.Width = 500000;
texto = new LinkedList<string>();
msgs = new LinkedList<MensagemParaEcra>();
labels = new LinkedList<Label>();
var it = labels.GetEnumerator();
var it2 = msgsRemover.GetEnumerator();
this.FlowDirection = FlowDirection.LeftToRight;
this.BackColor = backg;
this.Size = new Size(500000, 30);
this.Refresh();
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
startThread();
}
The error you are receiving indicates that the target Window hasn't fully created yet. Probably the constructor hasn't finished. Try to hook up to one of the default events (Load, Show etc.) of the target window and make the invoke calls after these are handled.

Control.BeginInvoke does not call delegate when UI function is idle

I am modifying a windows desktop application that works with some external hardware. When the user activates the hardware from the application a progress (UI) form is started. This form creates a thread that performs all of the work with the hardware. The problem comes when I try to report progress back to the UI thread. It appears that the first of my Control.BeginInvoke ("Negotiating message") works fine. However, the second one (first adjustment to progressbar) never seems to call it's delegate and as a result the application locks up on the subsequent endinvoke. I believe the issue is that the GUI is now in an idle state, but I am not sure how to fix the situation. Any help would be appreciated. Code found below:
In the UI Load Method Thread:
private void frmTwainAquire_Load(object sender, EventArgs e)
{
try
{
//Show the GUI
this.Visible = showGUI;
pbScanningProgress.Value = 0;
btnCancel.Enabled = false;
btnCancel.Visible = false;
// Set the delegates.
SetScanMessageDelegate = new SetScanMessage(this.SetScanMessageMethod);
SetRegistrationMessageDelegate = new SetRegistrationMessage(this.SetRegistrationMessageMethod);
AddScanProgressDelegate = new AddScanProgress(this.AddScanProgressMethod);
AddRecogProgressDelegate = new AddRecogProgress(this.AddRecogProgressMethod);
// Set progress bars.
pbScanningProgress.Value = 0;
pbRecognition.Value = 0;
abortScan = false;
// Create thread here!
twainInstance = new rScan.Twain();
rScanning = new rScanThread(this, twainInstance);
// Start the thread.
rScanning.tScan = new Thread(rScanning.Scan);
rScanning.tScan.Start();
}
catch (Exception ex)
{
// Error checking here.
}
}
Delegate Methods:
public void SetScanMessageMethod(string scanMessage)
{
this.lblScanMessage.Text = scanMessage;
}
public void SetRegistrationMessageMethod(string recogMessage)
{
this.lblRecognition.Text = recogMessage;
}
public void AddScanProgressMethod(int progress)
{
this.pbScanningProgress.Value += progress;
}
public void AddRecogProgressMethod(int progress)
{
this.pbRecognition.Value += progress;
}
Thread method that is giving the problem. Please note that the thread is in a different class then the previous two code blocks (both are in the UI class):
public class rScanThread : IMessageFilter
public void Scan()
{
// Set progress bar message.
IAsyncResult result;
if (frmTwainAquireInstance.lblScanMessage.IsHandleCreated && frmTwainAquireInstance.lblScanMessage.InvokeRequired)
{
result = frmTwainAquireInstance.lblScanMessage.BeginInvoke(frmTwainAquireInstance.SetScanMessageDelegate, "Negotiating Capabilities with Scanner.");
frmTwainAquireInstance.lblScanMessage.EndInvoke(result);
}
else
{
frmTwainAquireInstance.lblScanMessage.Text = "Negotiating Capabilities with Scanner.";
}
// Start the intialization of the rScan process.
bool intializeSuccess = twainInstance.Initialize(frmTwainAquireInstance.Handle);
// If the process could not be started then quit.
if (!intializeSuccess)
{
frmTwainAquireInstance.Close();
return;
}
if (frmTwainAquireInstance.pbScanningProgress.IsHandleCreated && frmTwainAquireInstance.pbScanningProgress.InvokeRequired)
{
result = frmTwainAquireInstance.pbScanningProgress.BeginInvoke(frmTwainAquireInstance.AddScanProgressDelegate, 33);
frmTwainAquireInstance.pbScanningProgress.EndInvoke(result); // Lock up here.
}
else
{
frmTwainAquireInstance.pbScanningProgress.Value += 33;
}
// Do more work after. The code never makes it this far.
} // End of rScanThread.Scan()

Add files to Listbox via Thread?

in my application i want to add files into my list box.
if my file isn't pcap extension i want to send the file path to my class and convet it to pcap extension and then add this file to my Listbox.
in case i am choose to add namy files the GUI not responding until my application finish to add or convert this file and i wonder how to add the option to do all this via threads.
private void btnAddfiles_Click(object sender, EventArgs e)
{
System.IO.Stream stream;
OpenFileDialog thisDialog = new OpenFileDialog();
thisDialog.InitialDirectory = (lastPath.Length > 0 ? lastPath : "c:\\");
thisDialog.Filter = "(*.snoop, *.pcap, *.cap, *.net, *.pcapng, *.5vw, *.bfr, *.erf, *.tr1)" +
"|*.snoop; *.pcap; *.cap; *.net; *.pcapng; *.5vw; *.bfr; *.erf; *.tr1|" + "All files (*.*)|*.*";
thisDialog.FilterIndex = 1;
thisDialog.RestoreDirectory = false;
thisDialog.Multiselect = true;
thisDialog.Title = "Please Select Source File";
if (thisDialog.ShowDialog() == DialogResult.OK)
{
if (thisDialog.FileNames.Length > 0)
{
lastPath = Path.GetDirectoryName(thisDialog.FileNames[0]);
}
foreach (String file in thisDialog.FileNames)
{
try
{
if ((stream = thisDialog.OpenFile()) != null)
{
using (stream)
{
string fileToAdd = string.Empty;
Editcap editcap = new Editcap();
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += new DoWorkEventHandler(
(s3, e3) =>
{
if (!editcap.isLibpcapFormat(file))
{
fileToAdd = editcap.getNewFileName(file);
}
else
{
listBoxFiles.Items.Add(file);
}
});
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
(s3, e3) =>
{
listBoxFiles.Items.Add(fileToAdd);
});
backgroundWorker.RunWorkerAsync();
lastPath = Path.GetDirectoryName(thisDialog.FileNames[0]);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
}
}
Your application is freezing because you're doing a lot of work in the UI thread. You need to move the long running tasks to a background thread and then just update the UI in the UI thread.
The first thing that you need to do, in order to do that, is seperate out your long running task from your UI manipulation. Currently you're intermingliing the two, which is what's causing your confusion as to how to map it to a BackgroundWorker.
As long as you don't need to be updating the listbox iteratively and it's okay to just add all of the items at the end all at once (that's what I would expect out of a listbox) you can simply do your file IO in one place, adding the results into a collection of some sort (List is likely appropriate here) and then, separately, you can add all of the items in the list to your ListBox (or use data binding).
Once you make that change the move to using something like a BackgroundWorker is quite easy. The IO work that populates the List goes in the DoWork, runs in the background, and then sets the Result. The RunWorkerCompleted event then takes that lists and adds the items to the ListBox.
If you have a compelling need to add the items to the listbox as you go, so you see one item, then the next, etc. over time, then just think of it as "reporting progress" and use the relevant progress reporting functionality built into BackgroundWorker. Update the progress inside of the loop, and in the progress reported event handler take the value given to you and put it in the ListBox.
Here is an implementation:
private void btnAddfiles_Click(object sender, EventArgs e)
{
System.IO.Stream stream;
OpenFileDialog thisDialog = new OpenFileDialog();
thisDialog.InitialDirectory = (lastPath.Length > 0 ? lastPath : "c:\\");
thisDialog.Filter = "(*.snoop, *.pcap, *.cap, *.net, *.pcapng, *.5vw, *.bfr, *.erf, *.tr1)" +
"|*.snoop; *.pcap; *.cap; *.net; *.pcapng; *.5vw; *.bfr; *.erf; *.tr1|" + "All files (*.*)|*.*";
thisDialog.FilterIndex = 1;
thisDialog.RestoreDirectory = false;
thisDialog.Multiselect = true;
thisDialog.Title = "Please Select Source File";
if (thisDialog.ShowDialog() == DialogResult.OK)
{
if (thisDialog.FileNames.Length > 0)
{
lastPath = Path.GetDirectoryName(thisDialog.FileNames[0]);
}
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork +=
(s3, e3) =>
{
//TODO consider moving everything inside of the `DoWork` handler to another method
//it's a bit long for an anonymous method
foreach (String file in thisDialog.FileNames)
{
try
{
if ((stream = thisDialog.OpenFile()) != null)
{
using (stream)
{
Editcap editcap = new Editcap();
if (!editcap.isLibpcapFormat(file))
{
string fileToAdd = editcap.getNewFileName(file);
backgroundWorker.ReportProgress(0, fileToAdd);
}
else
{
backgroundWorker.ReportProgress(0, file);
}
lastPath = Path.GetDirectoryName(thisDialog.FileNames[0]);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
};
backgroundWorker.ProgressChanged +=
(s3, arguments) =>
{
listBoxFiles.Items.Add(arguments.UserState);
};
backgroundWorker.RunWorkerAsync();
}
}
You can do it with BackgroundWorker:
Add a backgroundWorker to your form via the Toolbox.
Start it with:
backgroundWorker.RunWorkerAsync(new string[] {parm1, parm2});
Add a events to backgroundWorker (Properties window)
Use DoWork to do your calculations. Then use RunWorkerCompleted to apply the settings.

Categories

Resources