C# Problem setting Label Content in WPF App From Separate Thread - c#

I have a window that contains a label (player1). I also have a class that gathers data asynchronously in the background inside a thread. When that data has been gathered, I want to changed the content of my label. Since the label was created by the UI and I'm trying to edit it from another thread, I tried using Dispatcher. However, after hours of trying and different examples, I can't get it to work. In it's most simple form below, the method dispatchP1 changes the value of player1 when called from my main window. However, it doesn't work when called from my class. Also, I don't receive an error or anything.
public delegate void MyDelegate();
public void dispatchP1()
{
player1.Dispatcher.BeginInvoke(new MyDelegate(p1SetContent));
}
public void p1SetContent()
{
player1.Content = "text";
}
Any help would be appreciated.

That code doesn't seem particularly problematic - but WPF has a habit of swallowing exceptions. In your App.xaml, you can handle the event DispatcherUnhandledException and put a breakpoint in there to determine if it is really throwing an exception or not.

You know you can use anonymous delegates?
player1.Dispatcher.BeginInvoke( () =>
{
player1.Content = "text";
});

Related

Invoke a javascript function in WebBrowser and wait until javascript event fires

I'm working in .NET, C# to be specific, creating a Win Forms UserControl, which contains a WebBrowser control. The WebBrowser control hosts a page, which in turn uses a third-party javascript component. The problem I'm having is with invoking a javascript function to initialize the third-party javascript component and block the UI in the Windows Forms application until the component has been initialized, which the component notifies you of through an internal javascript event that it has.
Part of the problem is that the only way to change any configuration parameter of the third-party javascript component is to re-initialize it with the new configuration. So for example, if you want to make it read-only you have to re-initialize it with the read-only parameter.
I've got everything working in terms of being able to call the Document.InvokeScript and then in the web page call the UserControl method using window.external but the problem I'm having is how to block the UserControl code that makes the call to initialize the javascript component so that it waits and doesn't return control to the user until the initialization of the javascript component has been completed.
The reason I need it to work this way is because if I have a "Read-Only" checkbox on the form that changes the the ReadOnly property of the UserControl to control whether the javascript component shows the data as read-only and the user clicks that checkbox really quickly you will either get a javascript error or the checkbox will get out of sync with the actual read-only state of the javascript component. This seems to happen because the control hasn't re-initialized yet after it's configuration has changed and you're already trying to change it again.
I've spent hours and hours trying work out a way to make it work using everything from AutoResetEvent to Application.DoEvents and so on, but don't seem to be able to get it working.
The closest I've found is Invoke a script in WebBrowser, and wait for it to finish running (synchronized) but that uses features introduced in VS2012 (and I'm using VS2010) and I don't think it would work anyway as it's a bit different in that you're not waiting for a javascript event to fire.
Any help would be greatly appreciated.
The problem in the first place is the requirement to "block" the UI thread until some event has been fired. It's usually possible to re-factor the application to use asynchronous event handlers (with or without async/await), to yield execution control back to the message loop and avoid any blocking.
Now let's say, for some reason you cannot re-factor your code. In this case, you'd need a secondary modal message loop. You'd also need to disable the main UI while you're waiting for the event, to avoid nasty re-entrancy scenarios. The waiting itself should to be user-friendly (e.g., use the wait cursor or progress animation) and non-busy (avoid burning CPU cycles on a tight loop with DoEvents).
One way to do this is to use a modal dialog with a user-friendly message, which gets automatically dismissed when the desired JavaScript event/callback has occured. Here's a complete example:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WbTest
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IScripting))]
public partial class MainForm : Form, IScripting
{
WebBrowser _webBrowser;
Action _onScriptInitialized;
public MainForm()
{
InitializeComponent();
_webBrowser = new WebBrowser();
_webBrowser.Dock = DockStyle.Fill;
_webBrowser.ObjectForScripting = this;
this.Controls.Add(_webBrowser);
this.Shown += MainForm_Shown;
}
void MainForm_Shown(object sender, EventArgs e)
{
var dialog = new Form
{
Width = 100,
Height = 50,
StartPosition = FormStartPosition.CenterParent,
ShowIcon = false,
ShowInTaskbar = false,
ControlBox = false,
FormBorderStyle = FormBorderStyle.FixedSingle
};
dialog.Controls.Add(new Label { Text = "Please wait..." });
dialog.Load += (_, __) => _webBrowser.DocumentText =
"<script>setTimeout(function() { window.external.OnScriptInitialized}, 2000)</script>";
var canClose = false;
dialog.FormClosing += (_, args) =>
args.Cancel = !canClose;
_onScriptInitialized = () => { canClose = true; dialog.Close(); };
Application.UseWaitCursor = true;
try
{
dialog.ShowDialog();
}
finally
{
Application.UseWaitCursor = false;
}
MessageBox.Show("Initialized!");
}
// IScripting
public void OnScriptInitialized()
{
_onScriptInitialized();
}
}
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IScripting
{
void OnScriptInitialized();
}
}
Which looks like this:
Another option (a less user-friendly one) is to use something like WaitOneAndPump from here. You'd still need to take care about disabling the main UI and showing some kind of waiting feedback to the user.
Updated to address the comment. Is your WebBrowser actually a part of the UI and visible to the user? Should the user be able to interact with it? If so, you cannot use a secondary thread to execute JavaScript. You need to do it on the main thread and keep pumping messages, but WaitOne doesn't pump most of Windows messages (it only pumps a small fraction of them, related to COM). You might be able to use WaitOneAndPump which I mentioned above. You'd still need to disable the UI while waiting, to avoid re-entrancy.
Anyhow, that'd still be a kludge. You really shouldn't be blocking the execution just to keep the linear code flow. If you can't use async/await, you can always implement a simple state machine class and use callbacks to continue from where it was left. That's how it used to be before async/await.

Text field bound to property in another class does not update

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.

How do you manually refresh a window in WPF?

I'm using the ICommand interface to perform binding on a couple of buttons in my application, the Run and Close button. I'm using a helper method that was mentioned in this question to hook up the ICommand to a delegate as follows:
_someCommand = new DelegateCommand(this.DoSomething, this.CanDoSomething);
private ICommand _someCommand;
public ICommand SomeCommand
{
get { return _someCommand; }
}
private void DoSomething(object state)
{
// do something here
}
private bool CanDoSomething(object state)
{
// return true/false here is enabled/disable button
}
This appears to work just fine as, in my implementation, CanDoSomething returns the value of a property that I have in my application.
If I set the initial value of the property to true, then the button is enabled and false it is disabled.
I have a series of events that are raised from a BackgroundWorker in the application layer back to the ViewModel that change the value of the property to true or false based on the current state of the application.
The current problem I'm having is that the button is not "re-enabling" when I set the value to true after the work has completed. If I click somewhere within the window, it will update. So, therefore, I'm thinking than manually refreshing the window will solve my problem, at least for the interim. This feels a bit gross to do it this way, but I'm kind of at a loss for what else I could try.
If anyone has any suggestions, I'm all ears.
Thanks for the help!
Ian
Edit -
A little bit more information on the application itself. It uses a background worker in the application thread to handle the "work". The application is a simple utility to manage the creating of tables and loading of data into the tables. We use a lot of pre-defined SQL scripts to setup our test environment, so this is a simple utility that allows us to do that sort of thing based on parameters provided by the user in the UI.
Hopefully that helps, because when I re-read my question it read as if I were doing everything in the UI thread, which is not the case. Progress reports are raised back up to the UI thread and everything is updated as expected, except the button..
CommandManager.InvalidateRequerySuggested() may be the answer - it tells all the commands to check whether they are enabled or not.
You have to raise the CanExecuteChanged event:
http://msdn.microsoft.com/en-us/library/system.windows.input.icommand.canexecutechanged.aspx
This may be more useful than the other answer in cases where you know you should re-evaluate a single control, and re-evaluating all the controls would be costly. The other answer is simpler if you don't have a case like that, though.

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.

Winforms listbox not updating when bound data changes

The image below shows how my code works. When I press button2 the listbox is updated, but not when I press button1. Why?
Is the problem threading related? If it is, where should I add the call to (Begin)Invoke?
One interesting thing to note is that if I first press button1 and then button2 the data generated by the button1 click is shown when I click button2. So it seems like the data generated by doFoo is buffered somewhere, and then pushed to the listbox once I press button2.
EDIT:
I tried adding AddNumber to the form code, and added a call to Invoke when listBox1.InvokeRequired returns true. This solves the problem, but isn't the nicest of designs. I don't want the GUI to have to "worry" about how to add items to a list that's part of the model.
How can I keep the logic behind adding to the list internal to the list class, while still updating the gui when the list changes?
EDIT 2:
Now that we have confirmed that this is a threading issue I've updated the image to more closely reflect the design of the actual code I'm working on.
While Lucero's suggestion still solves the problem, I was hoping for something that doesn't require the form to know anything about the dll or CDllWrapper.
The model (ListBoxDataBindingSource etc) should know nothing at all about the view (listboxes, buttons, labels etc)
My guess is that this is due to the update message being handled on the wrong thread. Background: each thread has its own message queue. Messages posted into the message queue will land in the same thread as the caller by default. Therefore, the callback will maybe post a message on the wrong thread.
Try this: move the AddNumber() method to the form and use Invoke() (inherited by Control) to add the item in the correct thread. This may get rid of the issue.
Edit to reflect your followup:
The UI doesn't have to know about your component. What you need is just a proper synchronization between adding the item to your list and the UI, since UI updates will oly work if the thread matches. Therefore, you might want to supply the Control to your class which wraps the BindingList, and then do the Invoke on the list itself. This makes the list worry about triggering the upate on the UI thread and does take the worry from both the UI and the external component of invoking the handler on the correct thread.
Like this:
internal class ListBoxDataBindingSource {
private readonly Control uiInvokeControl;
private readonly BindingList<Item> list = new BindingList<Item>();
public ListBoxDataBindingSource(Control uiInvokeControl) {
if (uiInvokeControl == null) {
throw new ArgumentNullException("uiInvokeControl");
}
this.uiInvokeControl = uiInvokeControl;
CDIIWrapper.setFP(AddNumber);
}
public void AddNumber(int num) {
Item item = new Item(num.ToString());
if (uiInvokeControl.InvokeRequired) {
uiInvokeControl.Invoke(list.Add, item);
} else {
list.Add(item);
}
}
private BindingList<Item> List {
get {
return list;
}
}
}
I know this is old, although I had a very similar problem.
Here was the solution: BindingList not updating bound ListBox.
Instead of having the setFP set the callback to lbDataBindingSource.AddNumber, create a private method in your code to handle the callback and then call lbDataBindingSource.AddNumber from that callback.
void MyForm_Load(object sender, EventArgs e)
{
//...
cdll.setFP(FPCallback);
}
private void FPCallback(int num)
{
lbDataBindingSoruce.AddNumber(num);
}
I need to call my view model to add things to the bindinglist, so I need to write an anonymous function
Reference to Lucero's Answer and following post:
Anonymous method in Invoke call
My Code:
listBox.Invoke((Action)delegate
{
MyViewModel.AddItem(param1, param2);
});

Categories