Text field bound to property in another class does not update - c#

I have a windows form 'MyForm' with a text box that is bound to a property in another class 'MyData'. The Data source update mode is set to "On Property Change"
I used the VisualStudio IDE. It created the following code for the binding
this.txtYield.DataBindings.Add(new Binding("Text", this.BindingSourceMyDataClass, "PropertyInMyDataClass", true, DataSourceUpdateMode.OnPropertyChanged));
In the form constructor, after initialize values code was added to bind the MyData Class to the form
myDataClassInstantiated = new MyDataClass();
BindingSourceMyDataClass.DataSource = myDataClassInstantiated;
The INotifyProperty Interface has been implemented:
public double PropertyInMyDataClass
{
get { return _PropertyInMyDataClass; }
set
{
if (!Equals(_PropertyInMyDataClass, value))
{
_PropertyInMyDataClass = value;
FirePropertyChanged("PropertyInMyDataClass");
}
}
}
A background worker is used to run the calculations and update the property 'PropertyInMyDataClass'
I expected that the text box on the form would update automatically when the background worker completed. That didn't happen
If I manually copy assign the value from the property to the form text box, the value is displayed properly
this.txtYield.Text = String.Format("{0:F0}", myDataClassInstantiated.PropertyInMyDataClass);
I tried to add Refresh() and Update() to the MyForm.MyBackgroundWorker_RunWorkerCompleted method, but the data still is not refreshed.
If I later run a different background worker that updates different text boxes on the same form, the text box bound to PropertyInMyDataClass gets updated
I would appreciate suggestions that will help me to understand and resolve this databinding problem

The problem comes from a couple of angles. If you are running the process on a background thread, the background thread cannot directly access a control on your form (which lives in a different thread) directly, otherwise you will get an exception. You also cannot expect the UI thread to update based on states in the background thread, unless you wire it up to do so. In order to do this, you need to invoke a delegate on the main UI thread..
Place this code (modify it to update whatever control you want with whatever value type you want) on the UI form.
public void UpdateOutput(string text)
{
this.Invoke((MethodInvoker) delegate {
lstOutput.Items.Add(text);
});
}
Then you can call this function in your background worker thread. (assuming your background process function lives in the same form, you can call it directly), if not then you will need a reference to the UI form in the class that the background process runs in.

Related

Error loading custom user controls from different threads

I have a WPF project and from the main window i am creating and loading some bunch of user controls, there is some large data i am loading in background and then updating a built-in control throw the dispatcher, that works fine, the problem is that some of the user controls loads a lot of data, for example the very first thing i load in the main area of my main window, what i want is to put a loading label instead, load the main window as fast as possible so the user see this label and run in background the creation of that user control and when is done add it as a child of my main container area on my main window while i remove the loading label, if i follow the same philosophy i run into the same error like when i run a task and then try to update the window without using the dispatcher. i want to be able of create the user control asynchronous then update the main window.
Code:
User Control:
public partial class CustomUserControlGallery : UserControl
{
public CustomUserControlGallery()
{
InitializeComponent();
}
...
}
On the backend class of the main window:
public partial class MainWindow : Window
{
CustomUserControlGallery _customUserControlGallery;
public MainWindow()
{
InitializeComponent();
Task t = new Task({
//Can't use the _customUserControlGallery's Dispatcher because object is uninitialized and this.Dispatcher not working either.
_customUserControlGallery = new CustomUserControlGallery(); //Error Here.
_gridContainer.Dispatcher.Invoke(new Action(() => _gridContainer.Children.Add(_customUserControlGallery)));
_loadingLabel.Visbility = Visibility.Collapse;
});
t.Start();
}
...
}
I don't know how to handle this situation with the thread associated to the user control and the main thread.
Error:
{"The calling thread must be STA, because many UI components require this."}
You're doing this wrong. All controls must be created & operate on the UI Thread. That said, you can use the BackgroundWorker class to load the data.
You typically do this by disabling the control whose data is being loaded in the background or hiding it & displaying a progress indicator in its place. Then, you start your BackgroundWorker. That can communicate how far along it is using the ReportProgress method. Finally, when it's finished running, the RunWorkerCompleted event is fired, and you use that to either enable the control, or to hide the progress indicator & show the control.
Some quick & dirty (untested) code:
Place this in your Initialize() or control constructor:
private BackgroundWorker loadData = new BackgroundWorker();
loadData.DoWork += loadData_DoWork;
loadData.ProgressChanged += loadData_ProgressChanged; // Only do this if you are going to report progress
loadData.WorkerReportsProgress = true;
loadData.WorkerSupportsCancellation = false; // You can set this to true if you provide a Cancel button
loadData.RunWorkerCompleted += loadData_RunWorkerCompleted;
private void DoWork( object sender, DoWorkEventArgs e ) {
BackgroundWorker worker = sender as BackgroundWorker;
bool done = false;
while ( !done ) {
// If you want to check for cancellation, include this if statement
if ( worker.CancellationPending ) {
e.Cancel = true;
return;
}
// Your code to load the data goes here.
// If you wish to display progress updates, compute how far along you are and call ReportProgress here.
}
}
private void loadData_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
// You code to report the progress goes here.
}
private void loadData_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
// Your code to do whatever is necessary to put the UI into the completed state goes here.
}
What you are essentially saying (I think) is that Your app becomes sluggish while your control renders a large amount of data.
This is a problem that needs to be solved via virtualisation. You cannot create a control on a background thread, have it render its data behind the scenes and then pop it into existence. You can create controls on separate dispatchers, but they cannot share the same visual and logical tree, so you will not be able to have one as a child of the other.
Virtualisation is what you need to focus on. Depending on the control you can use a variety of virtualisation settings. Try googleing the subject as there is a lot of information on how to achieve this effectively. Most likely you will want to use things like virtualizing stackpanels and container recycling.
You cannot create UI controls with different Dispatchers and use them with each other. It's just not allowed. What you want to do is on your Task you do the heavy lifting work without UI updates and when it is done you push it back to the Dispatcher to update the UI.
In your case, I wouldn't even use Dispatcher.Invoke. Since you are using Task, it has a TaskScheduler.FromCurrentSynchronizationContext() that you can pass in the constructor.
What is the purpose of instantiating controls in a different thread if you're just going to put it back to the Main dispatcher? It's not expensive to do that.

changing ellipse colour at runtime

I have not used wpf alot and thought it would be a simple proccess to change the colour of an ellipse at runtime. I have a FileWatcher and in the event created I want to change the colour of the ellipse to a colour and back again, creating a blinking effect. (created is the ellipse, br4 is a solid colour brush defined in xaml)
public void watcherCreated(object seneder, FileSystemEventArgs e)
{
Application.Current.Resources["br4"] = new SolidColorBrush(Colors.Green);
created.Fill = (SolidColorBrush)Application.Current.Resources["br4"];
}
as soon as a file is created in the path which fires the event i get this error: Invalid operation exception
The calling thread cannot access this object because a different thread owns it.
I have looked for a solution with the freeze() method, but with no success.
created.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(
delegate()
{
Application.Current.Resources["br4"] = new SolidColorBrush(Colors.Green);
created.Fill = (SolidColorBrush)Application.Current.Resources["br4"];
}
));
got it thanks for comments
You can only access UI elements from the same thread in which they were created.
You should use Dispatcher.Invoke or Dispatcher.BeginInvoke to have a delegate called on the UI thread...where you can then access the "created" elements' "Fill" property.
See this link for an explanation of the problem:
http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
Instead of trying to set the changing colour in the UI...what you can do is expose a property on your ViewModel which holds a state.
When your FileWatcher notifies you of a newly created file (by calling your watcherCreated method) you just set that state in your ViewModel.
In your UI...use a Binding with a Converter to bind to the state property in your ViewModel. The converter will determine what Brush to use depending on the state e.g. if state is 1 return a Green brush, if state is 0 return a Red brush.
To reset the state back to the "off" position...you could have a timer that after say 1 second, etc...sets the state value back to off.
By doing this....you separate the state from the UI.
If in the future you wanted a more sophisticated way of showing the state in the UI...e.g. having an animation (using StoryBoards/Visual State Manager) that gradually fades away from Green back to Red...then you could have that animation triggered, once again based on the state in the ViewModel.
In WPF all the UI controls are loaded in a different thread while as your application runs in a separate thread.
So , think it as , you are getting this error because your application(Main Thread) is trying to access the Elipse that is in UIThread. And this is not allowed for as threads can not access each others object directly.
So WPF has introduced dispatcher object. Use the following
if (this.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
{
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
Application.Current.Resources["br4"] = new SolidColorBrush(Colors.Green);
created.Fill = (SolidColorBrush)Application.Current.Resources["br4"];
}
));
}
Even simpler solution is to set the created.Fill on the UI thread itself. you will not need Dispatcher.Invoke or Dispatcher.BeginInvoke.

Ensuring that child controls are created in main UI thread

I'm modifying existing WinForms project. The project has UserControl.
This UserControl has DataSet variable which is set from another part of the program in different thread.
What I want to do is to dynamically add another controls to this control depending on the DataSet.
So, after DataSet is loaded, I'm calling RefreshChildControl function and trying to add my new ChildUserControls to flowLayoutPanel. And that's where the problems begin:). I get the "Cross-thread operation not valid: Control 'ChildUserControl' accessed from a thread other than the thread it was created on" exception. I tried to use if(this.InvokeRequired) and Invoke this method, but it does not help. InvokeRequired on MyUserControl is false.
So, is there any good way of performing such task? Or am I missing something important?
EDIT:
I tried to skip InvokeRequired test and just call this.FindForm().Invoke on this method. I've got "Invoke or BeginInvoke cannot be called on a control until the window handle has been created." exception. And, by the way, when I open another form with this control everything worked fine.
First. The simplest solution is to perform Invoke everytime. Nothing bad will happen.
Second, use SynchronizationContext.
using System.Threading;
public class YourForm
{
SynchronizationContext sync;
public YourForm()
{
sync = SynchronizationContext.Current;
// Any time you need to update controls, call it like this:
sync.Send(UpdateControls);
}
public void UpdateControls()
{
// Access your controls.
}
}
SynchronizationContext will manage all threading issues for you. It checks, whether you call from the same or from the other thread. If from same it will just immediately execute your code. Otherwise it will do Invoke through form's message loop.
If your user control is not immediately visible after you construct it, the handle will not be created on the thread that you think it is created on. It's not the C# object whose thread parent is important, it is the Windows Handle object whose parent is important.
To force a control to be immediately created on the thread that you thought you created it on, then
read out the control.Handle which will force the control to actually be made and assigned a handle.
MyUserControl uc = new MyUserControl(); // the handle is not created here
uc.Visible = false;
IntPtr dummy = uc.Handle; // The control is immediately given a real handle
You can also try to fiddle around with uc.CreateControl, but this won't create the handle if the control is not visible.
Now you can have another thread update your user control even if the user control is not visible.
uc.BeginInvoke((Action)(() => uc.Text = "ha ha"));
If you leave out the dummy = uc.Handle line, you will get an exception that you can't call BeginInvoke on a control that does not have a handle.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.createcontrol(v=vs.90).aspx

C# Combo values visible after thread is ready

In my application I update a treeview in a backgrounworker thread. While updating the treeview, the combobox values are not visible. When the treeview is updated, the values appear.
Here's my backgroundworker code:
void _bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
tvCategories.Invoke((MethodInvoker)delegate()
{
FillCategoryTreeView(); // Fills the treeview
}
);
}
The code that fills my combobox:
private void FillCategoryCombo()
{
Category categorie = new Category();
List<Category> categories = categorie.GetQuestionCategories();
cmbCategories.DataSource = categories;
cmbCategories.DisplayMember = "Description";
cmbCategories.ValueMember = "Id";
}
The combobox is filled in the constructor of the form.
The reason that I've put the treeview in a seperate thread is because the treeview must be updated. In the meantime I want to continue using the application. Therefore I need access to a combobox. But the values of the combobox are invisible while the treeview is being updated.
What to do to solve this?
I'm not quite sure there is enough information in your post to fully answer the question... but assuming that you create the Background worker thread in the Constructor prior to calling the FillCategoryCombo() method... this makes sense.
In your background worker method, you immediately call Invoke which switches control right back to the UI thread, which will then be doing the work of FillCategoryTreeView() before FillCategoryCombo() has a chance to run.
If you want to asynchronously fill your treeview (assuming it comes from a long running database call), then what you need to do is actually have separate Invoke calls in FillCategoryTreeView when you specifically need to add a tree view item. That way as each database call (or whatever takes a long time) finishes, it only does an operation on the UI thread when it needs to add a physical tree node.

Removing a Control from a Form

So I've got some serious problems with removing a Control from a Form of my application. It's kinda messed up but I can't change anything. I have a form and I have a separated user Control. The control opens an exe file and shows a progress bar while loading it's bytes. And here comes the problem. I do all of it with a BackgroundWorker and when the worker_DoWorkerCompleted method is called the original form should show a MessageBox and remove the Control.
BackGround_Loader bgLoad = new BackGround_Loader();
bgLoad.Location = new Point(this.Width/2 - bgLoad.Width/2, this.Height/2 - bgLoad.Height/2);
this.Controls.Add(bgLoad);
bgLoad.BringToFront();
bgLoad.AddReferences(this.executableFile, this.SourceReader);
bgLoad.occuredEvent();
At first I set the control's location to be in the middle of the Form itself. Then I add the control to the form, and bring it to the front. After these I send the path of the executable and a RichTextBox's reference to this. With the occuredEvent I start the BackgroundWorker itself. And here comes my problem. I should show a MessageBox in the Form when the in the bgLoad the backgroundworker gets to the DoWorkerCompleted status. Kindly I have no idea how to do it. It works just perfect however the control stays in the middle of the form.
UI actions must be performed on the main UI thread. The events that get raised from the background worker thread are (obviously) in a different thread.
You need something like the following code:
private void backgroundWorker_DoWork(object sender, AlbumInfoEventArgs e)
{
// Check with an element on the form whether this is a cross thread call
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { AddToGrid(e.AlbumInfo); });
}
else
{
AddToGrid(e.AlbumInfo);
}
}
In this case AddToGrid is my method for adding a row to a DataGridView, but in your case it will be a method that does what you need to do.
Similarly for the backgroundWorker_RunWorkerCompleted method
See this MSDN example
I could find a way to solve the problem but I don't really like it. In the addReferences method I pass the Form itself and an object of the bgLoad class. Then in the RunWorkerCompleted I check if the control is on the form and if it is then I remove it.
bgLoad.AddReferences(this, bgLoad, this.executableFile, this.SourceReader);
...
private void worker_DoWorkerCompleted(object sender, DoWorkerEventArgs e) {
if(this.MainForm.Controls.Contains(this.Control) {
this.MainForm.Controls.Remove(this.Control);
}
}
Like this it works but it's awful for me.

Categories