C# WPF Cmb Text in Textfield with Thread - c#

I´m using the BusyIndicator in my GUI because I have to work with a database that the GUI don´t freeze meanwhile.
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
richSammelbemerkung.Document.Blocks.Clear();
richSammelbemerkung.AppendText("Daten werden gesucht...");
GUIData guiData = new GUIData();
guiData = getInfoFromGUI();
ZeichnungCollection zeichnungen = new ZeichnungCollection();
BackgroundWorker worker = new BackgroundWorker();
busyIndicator.IsBusy = true;
worker.DoWork += (o, ea) =>
{
zeichnungen = searchDrawings(guiData);
};
worker.RunWorkerCompleted += (o, ea) =>
{
Application.Current.Dispatcher.Invoke((Action)(() => CollectionViewSource.GetDefaultView(dataOutOfDb.ItemsSource = zeichnungen).Refresh()));
busyIndicator.IsBusy = false;
if (zeichnungen.Count == 0)
{
MessageBox.Show("Keine Daten gefunden. Eventuell Index überprüfen.", "Info");
}
richSammelbemerkung.Document.Blocks.Clear();
dataOutOfDb.SelectedIndex = 0;
Keyboard.Focus(dataOutOfDb);
};
busyIndicator.IsBusy = true;
worker.RunWorkerAsync();
}
It´s look like this.
Before I put the BusyIndicator into my GUI I just run the code and used the SelectionChanged of the Cmb to set the selected Text into a Textfield.
The problem I have now is, that when the SelectionChanged is fired it throws a Exception even if I used a IF to ask for elements.
So I went on with DataBinding like this:
Text="{Binding ElementName=cmbTag, Path=SelectedItem}"
Now when it doesn´t throw a exception or anything else.
BUT, I can´t set a new value in the Textfield because it automatically refreshes on what is selected in the Combobox.
So, has anyone an idea how I can set the selected value from the Cmb to the Textfield without using SelectionChanged or DataBinding, or even so that it doens´t throw a exception?

This has nothing to do with the BusyIndocator.
The problem is that you want to access the UI from another thread. But any operation with the UI must be made from the UI-Thread.
In your example anything within the DoWork method (which runs in another thread) can not access the UI. You'll get an exception or Bindings won't work.
You can use a Dispatcher within the DoWork method to track the code back to the UI Thread, like this:
Application.Current.Dispatcher.Invoke(() => {
// this code will run in the UI Thread again
});
But I think it's more elegant to remove all UI manipulation from the DoWork Action or use the BackgroundWork's ReportProgress Method for that.

Related

Why check the isBusy property of a BackgroundWorker dirrectly after constructor?

I am currently maintaining some software written by another programmer
private void StartUpdate()
{
this.backgroundWorker = new BackgroundWorker();
if (backgroundWorker.IsBusy == false)
{
// Set up the BackgroundWorker object by attaching event handlers
backgroundWorker.DoWork += new DoWorkEventHandler(StartStepWorker);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(StepCompleted);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(StepProgressChanged);
// Start the asynchronous operation
backgroundWorker.RunWorkerAsync();
}
}
Is there any reason why, immediatly after creating the backgroundWorker, there is a check on the isBusy property? is there any reason why this is needed?
I assume that, since it was just created, the isBusy property will always be false? The RunWorkerAsync function is not used anywhere else in the code, so why checking?
I am afraid of removing the check, but can not think of any reason why it is there...
Can someone explain why he/she could have put this there?

ListView wont update

We've made an Application with a MainWindow called MV.
When main starts it launches our StartProgram method as a BackgroundWorker and the Application.Run(MW);
MainWindow MW = new MainWindow();
BackgroundWorker.DoWork += (obj, e) => StartProgram(MW);
BackgroundWorker.RunWorkerAsync();
Application.Run(MW);
In StartProgram we create instances of Patient, which we want to show in our listView1.
We do this by calling this method, which is in MW:
public void SetListSource(Patient p)
{
ListViewItem item = new ListViewItem("woohoo");
item.SubItems.Add("a");
listView1.Items.Add(item);
}
StartProgram stalls when it reaches listView1.Items.Add(item);
Our guess is, that it waits for MW (MainWindow), but we can't figure out how to fix it.
We have a button in MW, that does somethis similar, except it only sends "1" and "a" to the listView1.
private void Sort_Button_Click(object sender, EventArgs e)
{
ListViewItem item = new ListViewItem("1");
item.SubItems.Add("a");
listView1.Items.Add(item);
}
Does anybody know how to make SetListSource(...) work as Sort_Button_Click(...)?
EDIT
Solved with Invoke
You can't modify your GUI directly from another thread. You need to use a delegate and invoke your control. In your thread you have to do:
CONTROL.Invoke(new Action(() =>
{
CONTROL.Items.Add(item);
}
));
Source:
BackgroundWorker multithread access to form
You can use Invoke, but that's probably unnecessary. You can use BackgroundWorker.RunWorkerCompleted event instead:
BackgroundWorker.DoWork +=
(s, e) =>
{
e.Result = DoAllTheComplicatedWork();
}
BackgroundWorker.RunWorkerCompleted +=
(s, e) =>
{
// Back on the UI thread, we can do whatever we want
listView1.Items.Add(((SomeDTO)e.Result).Patient);
}
BackgroundWorker.RunWorkerAsync();
Or, you can do the whole thing using await:
MW.Load += async (s, e)
{
var result = await Task.Run(() => SomeCPUWork());
listView1.Items.Add(((SomeDTO)e.Result).Patient);
}
The key point is, you really want a separation between the UI and whatever you need to do in background. I definitely wouldn't pass a form (or any other control) to any method that's supposed to be executed on a different thread.

Canceling Backgroundworker that was set using a lambda

I have a backgroundworker that has been created using a lambda as shown here:
BackgroundWorker fileCountWorker= new BackgroundWorker();
fileCountWorker.WorkerSupportsCancellation = true;
fileCountWorker.DoWork += new DoWorkEventHandler((obj, e) => GetFileInfo(folder, subs));
fileCountWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountInFolderListViewForItem(index));
fileCountWorker.RunWorkerAsync();
I would like to be able to cancel the backgroundworker, and then know that it was canceled in the RunWorkerCompleted function using the RunWorkerCompletedEventArgs e.Canceled property.
So far I have been unable to figure out a way to pass a parameter to the RunWorkerCompleted function and still maintain the ability to access the RunWorkerCompletedEventArgs.
I tried adding a RunWorkerCompletedEventArgs parameter to the function called by RunWorkerCompleted, and then passing the RunWorkerCompletedEventArgs like so:
fileCountWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountInFolderListViewForItem(index, e));
But that didn't seem to work.
Is there a way to do this?
Edit:
Following comments below, I made the following changes:
I changed the DoWork Event as follows (adding the obj and e as parameters in the worker function):
fileCountWorker.DoWork += new DoWorkEventHandler((obj, e) => GetFileInfo(folder, subs,obj,e));
I then changed the RunWorkerCompleted function as follows (adding the obj and e as parameters in the RunWorkerCompleted function):
fileCountWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountInFolderListViewForItem(index, obj, e));
From my UI Thread I call CancelAsync:
if (bgw.WorkerSupportsCancellation)
{
bgw.CancelAsync();
}
Then from within the backgroundworker I check for cancellationpending like:
BackgroundWorker bwAsync = sender as BackgroundWorker;
if (bwAsync.CancellationPending)
{
e.Cancel = true;
return;
}
The result is that when I cancel the backgroundworker, it does stop the worker function, but theRunWorkerCompletedEventArgs in the RunWorkerCompleted function ( UpdateCountInFolderListViewForItem) still has a Canceled property set to False, so the function can't tell that the worker was canceled.
So i'm still stuck on getting the RunWorkerCompleted function to know that the worker was canceled instead of completing normally.
You just need to call BackgroundWorker.CancelAsync().
Your worker code needs to check BackgroundWorker.CancellationPending and stop what it's doing to "cancel"... But, your lambda isn't doing anything you can really cancel.
Normally what you'd do is something like this:
//...
fileCountWorker.DoWork += (obj, e) =>
{
for (int i = 0; i < 1000 && fileCountWorker.CancellationPending; ++i)
{
Thread.Sleep(500);/* really do other work here */
}
e.Cancel = fileCountWorker.CancellationPending;
};
fileCountWorker.RunWorkerAsync();
//...
fileCountWorker.CancelAsync();
If you provide some details of GetFileInfo, maybe some more detail could be provided.

BindingSource and Cross-Thread exceptions

To explain this problem i put everything needed into a small sample application which hopefully explains the problem. I really tried to push everything in as less lines as possible, but in my real application these different actors don't know each other and also shouldn't. So, simple answer like "take the variable a few lines above and call Invoke on it" wouldn't work.
So let's start with the code and afterwards a little more explanation. At first there is a simple class that implements INotifyPropertyChanged:
public class MyData : INotifyPropertyChanged
{
private string _MyText;
public MyData()
{
_MyText = "Initial";
}
public string MyText
{
get { return _MyText; }
set
{
_MyText = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyText"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So nothing special about. And here the example code which can simply be put into any empty console application project:
static void Main(string[] args)
{
// Initialize the data and bindingSource
var myData = new MyData();
var bindingSource = new BindingSource();
bindingSource.DataSource = myData;
// Initialize the form and the controls of it ...
var form = new Form();
// ... the TextBox including data bind to it
var textBox = new TextBox();
textBox.DataBindings.Add("Text", bindingSource, "MyText");
textBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
textBox.Dock = DockStyle.Top;
form.Controls.Add(textBox);
// ... the button and what happens on a click
var button = new Button();
button.Text = "Click me";
button.Dock = DockStyle.Top;
form.Controls.Add(button);
button.Click += (_, __) =>
{
// Create another thread that does something with the data object
var worker = new BackgroundWorker();
worker.RunWorkerCompleted += (___, ____) => button.Enabled = true;
worker.DoWork += (___, _____) =>
{
for (int i = 0; i < 10; i++)
{
// This leads to a cross-thread exception
// but all i'm doing is simply act on a property in
// my data and i can't see here that any gui is involved.
myData.MyText = "Try " + i;
}
};
button.Enabled = false;
worker.RunWorkerAsync();
};
form.ShowDialog();
}
If you would run this code you would get a cross-thread exception by trying to change the MyText property. This comes, cause the MyData object calls PropertyChanged which will be catched by the BindindSource. This will then, according to the Binding, try to update the Text property of the TextBox. Which clearly leads to the exception.
My biggest problem here comes from the fact that the MyData object shouldn't know anything about a gui (cause it is a simple data object). Also the worker thread doesn't know anything about a gui. It simply acts on a bunch of data objects and manipulates them.
IMHO i think the BindingSource should check on which thread the receiving object is living and do an appropiate Invoke() to get the value their. Unfortunately this isn't built into it (or am i wrong?), so my question is:
How can resolve this cross-thread exception if the data object nor the worker thread know anything about a binding source that is listening for their events to push the data into a gui.
Here is the part of the above example that solves this problem:
button.Click += (_, __) =>
{
// Create another thread that does something with the data object
var worker = new BackgroundWorker();
worker.DoWork += (___, _____) =>
{
for (int i = 0; i < 10; i++)
{
// This doesn't lead to any cross-thread exception
// anymore, cause the binding source was told to
// be quiet. When we're finished and back in the
// gui thread tell her to fire again its events.
myData.MyText = "Try " + i;
}
};
worker.RunWorkerCompleted += (___, ____) =>
{
// Back in gui thread let the binding source
// update the gui elements.
bindingSource.ResumeBinding();
button.Enabled = true;
};
// Stop the binding source from propagating
// any events to the gui thread.
bindingSource.SuspendBinding();
button.Enabled = false;
worker.RunWorkerAsync();
};
So this doesn't lead to any cross-thread exceptions anymore. The drawback of this solution is that you won't get any intermediate results shown within the textbox, but it's better than nothing.
I realize that your question was posed some time ago, but I've decided to submit an answer just in case it's helpful to someone out there.
I suggest you consider subscribing to myData's property changed event within your main application, then updating your UI. Here's what it might look like:
//This delegate will help us access the UI thread
delegate void dUpdateTextBox(string text);
//You'll need class-scope references to your variables
private MyData myData;
private TextBox textBox;
static void Main(string[] args)
{
// Initialize the data and bindingSource
myData = new MyData();
myData.PropertyChanged += MyData_PropertyChanged;
// Initialize the form and the controls of it ...
var form = new Form();
// ... the TextBox including data bind to it
textBox = new TextBox();
textBox.Dock = DockStyle.Top;
form.Controls.Add(textBox);
// ... the button and what happens on a click
var button = new Button();
button.Text = "Click me";
button.Dock = DockStyle.Top;
form.Controls.Add(button);
button.Click += (_, __) =>
{
// Create another thread that does something with the data object
var worker = new BackgroundWorker();
worker.RunWorkerCompleted += (___, ____) => button.Enabled = true;
worker.DoWork += (___, _____) =>
{
for (int i = 0; i < 10; i++)
{
myData.MyText = "Try " + i;
}
};
button.Enabled = false;
worker.RunWorkerAsync();
};
form.ShowDialog();
}
//This handler will be called every time "MyText" is changed
private void MyData_PropertyChanged(Object sender, PropertyChangedEventArgs e)
{
if((MyData)sender == myData && e.PropertyName == "MyText")
{
//If we are certain that this method was called from "MyText",
//then update the UI
UpdateTextBox(((MyData)sender).MyText);
}
}
private void UpdateTextBox(string text)
{
//Check to see if this method call is coming in from the UI thread or not
if(textBox.RequiresInvoke)
{
//If we're not on the UI thread, invoke this method from the UI thread
textBox.BeginInvoke(new dUpdateTextBox(UpdateTextBox), text);
return;
}
//If we've reached this line of code, we are on the UI thread
textBox.Text = text;
}
Granted, this does away with the binding pattern you were trying before. However every update to MyText should be received and displayed without issue.
You can't update the BindingSource from another thread if it's bound to a winforms control. In your MyText setter you must Invoke PropertyChanged on the UI thread rather than running it directly.
If you want an extra layer of abstraction between your MyText class and the BindingSource you can do that, but you can't separate the BindngSource from the UI thread.
In Windows Froms
In cross thread i just used
// this = form on which listbox control is created.
this.Invoke(new Action(() =>
{
//you can call all controls it will not raise exception of cross thread
//example
SomeBindingSource.ResetBindings(false);
Label1.Text = "any thing"
TextBox1.Text = "any thing"
}));
and VOILA
/////////// Edit //////////
If there is chance of call from same thread it is created on then add following check
// this = form on which listbox control is created.
if(this.InvokeRequired)
this.Invoke(new Action(() => { SomeBindingSource.ResetBindings(false); }));
else
SomeBindingSource.ResetBindings(false);
You can try reporting progress from the background thread which will rise an event in the UI thread. Alternatively, you can try remembering the current context (your UI thread) before calling DoWork and then inside the DoWork you can use the remembered context to post data.
I know this is an old post, but I just ran into this issue on a winforms app and this seemed to work.
I made a subclass of BindingSource and intercepted the OnListChanged handler to invoke on the UI thread.
public class MyBindingSource : BindingSource
{
private readonly ISynchronizeInvoke context;
protected override void OnListChanged(ListChangedEventArgs e)
{
if (context == null) base.OnListChanged(e);
else context.InvokeIfRequired(c => base.OnListChanged(e));
}
public MyBindingSource(ISynchronizeInvoke context = null)
{
this.context = context;
}
}
Where InvokeIfRequired is the handy extension method mentioned by a few others in this post.

WPF application in a loop, how to not have the whole application freeze?

I am having fun with WPF and got a problem. I have googled and found this website that has the same problem of me but without any working solution.
The problem is that I have a button that do some processing of data (around 30 sec). I want to have the button to disable and to have log writing in a text box... the problem is that it doesn't disable and it doesn't wrote any thing on the textbox until the processing is completely done.
Any idea?
private void button1_Click(object sender, RoutedEventArgs e)
{
this.button1.IsEnabled = false;
//Long stuff here
txtLog.AppendText(Environment.NewLine + "Blabla");
//End long stuff here
this.button1.IsEnabled = true;
}
As others have said, use the BackgroundWorker or some other method of doing work asychronously.
You can declare it under your Window, initialize it somewhere like the Loaded event, and use it in the Click event. Here's your method, modified to use BackgroundWorker, assuming you've declared it under the Window as _bw:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_bw = new BackgroundWorker();
_bw.DoWork += new DoWorkEventHandler((o, args) =>
{
//Long stuff here
this.Dispatcher.Invoke((Action)(() => txtLog.AppendText(Environment.NewLine + "Blabla")));
});
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler((o, args) =>
{
//End long stuff here
this.Dispatcher.Invoke((Action)(() => this.button1.IsEnabled = true));
});
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.button1.IsEnabled = false;
_bw.RunWorkerAsync();
}
Note that anything that modifies your UI from another thread must be done within a Dispatcher.Invoke or Dispatcher.BeginInvoke call, WPF does not allow you to get or set DependencyProperty values from any thread but the one where the object was created (more about this here).
If you wanted to read from txtLog instead of modifying it, the code would be the same:
//Long stuff here
this.Dispatcher.Invoke((Action)(() =>
{
string myLogText = txtLog.Text;
myLogText = myLogText + Environment.NewLine + "Blabla";
txtLog.Text = myLogText;
}));
That operation is being performed on the UI thread. This means that it will block the Windows message pump from processing until it has completed. no pump = no UI updates. You should launch the job on another thread. I don't know WPF, but in C# I would use either the Thread or BackgroundWorker classes.
do it async. create a backgroundworker process to handle the data and the application will continue to respond. MSDN Resources on the Class. Since WPF is using C# (or VB.net) you can still use the same types of threading objects. I've used the background worker successfully in a WPF app myself.

Categories