How to change label content with timers throwing InvalidOperationException - c#

I'm making an application and I'm using a timer in that application to change label content in WPF C# .NET.
In the timer's elapsed event I'm writing the following code
lblTimer.Content = "hello";
but its throwing an InvalidOperationException and gives a message The calling thread cannot access this object because a different thread owns it.
I'm using .NET framework 3.5 and WPF with C#.
Please help me.
Thanks in advance.

For .NET 4.0 it is much simpler to use a DispatcherTimer. The eventhandler is then in the UI thread and it can set properties of the control directly.
private DispatcherTimer updateTimer;
private void initTimer
{
updateTimer = new DispatcherTimer(DispatcherPriority.SystemIdle);
updateTimer.Tick += new EventHandler(OnUpdateTimerTick);
updateTimer.Interval = TimeSpan.FromMilliseconds(1000);
updateTimer.Start();
}
private void OnUpdateTimerTick(object sender, EventArgs e)
{
lblTimer.Content = "hello";
}

InvokeRequired doesn't work in wpf.
The proper way the update a GUI element owned by another thread is this :
Declare this on module level :
delegate void updateLabelCallback(string tekst);
This is the method to update your label :
private void UpdateLabel(string tekst)
{
if (label.Dispatcher.CheckAccess() == false)
{
updateLabelCallback uCallBack = new updateLabelCallback(UpdateLabel);
this.Dispatcher.Invoke(uCallBack, tekst);
}
else
{
//update your label here
}
}

Related

WPF, how to implement async/await?

I'm learning how to webscrape in WPF. I check the site every 20sec, update my ObservableCollection (myClients) according to search results and display it in Listview (myList). I have 2 Buttons, one to start search and one to stop it.
I didn't know how to implement button autoclick every X sec (which would solve all my problems, am i right?) so i had to use Task.Delay(20000). Program works, it doesn't freeze right at the start like if i had used Thread.Sleep(), but if i press the Stop button and then Start, everything freezes.
I will upload only portion of the code that seems to be the problem. Note that the whole program at the moment is mostly reverse-engineered from several different programs as i am still a beginner.
private async void Button_Click(object sender, RoutedEventArgs e) //Start button
{
string car;
string price;
string link;
wantToAbort = false;
while (!wantToAbort)
{
// ----Simulate GET request----
//-----End GET----
myList.ItemsSource = myClients;
string searchCar = txtBlock.Text + " " + txtBlock2.Text;
var articleNodes = htmlDoc.DocumentNode.SelectNodes($"//*[#id='main_content']/div[1]/div[2]/ul[1]//*[text()[contains(., '{searchCar}')]]");
if (articleNodes != null && articleNodes.Any())
{
foreach (var articleNode in articleNodes)
{
car = WebUtility.HtmlDecode(articleNode.InnerText);
price = WebUtility.HtmlDecode(articleNode.ParentNode.ParentNode.SelectSingleNode("span").InnerText);
link = WebUtility.HtmlDecode(articleNode.ParentNode.ParentNode.Attributes["href"].Value);
var tempUser = new User(car, price, link);
if (!myClients.Any(x=>x.Link == tempUser.Link))
{
myClients.Insert(0, tempUser); //Inserts new item if Links are different
txtBlock3.Text = "Searching...";
}
}
await Task.Delay(20000); //This seems to be an issue
}
}
}
private void Button_Click_1(object sender, RoutedEventArgs e) //Stop button
{
wantToAbort = true;
txtBlock3.Text = "Ready to search again!";
}
Running a while loop on the UI thread may freeze the application as the UI thread cannot both process UI events and execute a loop or doing anything else simultaneously.
If you want to do something every x seconds you could use a timer as suggested by EJoshuaS. There is a DispatcherTimer class in WPF that fires a Tick event on the UI thread at an interval specified by the Interval property: https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatchertimer%28v=vs.110%29.aspx
You don't want to perform the GET request to the web server on the UI thread though so you should probably use a System.Timer.Timer: https://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx. This is a different type of timer that runs on a background thread.
Since you can only access UI controls such as TextBlocks and ListBoxes on the thread on which they were originally created - that is the UI thread - you will have to use the dispatcher to marshall any code that access these controls back to the UI thread in your Elapsed event handler:
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
//call the web server here....
//dispatch any access to any UI control
txtBlock3.Dispatcher.Invoke(new Action(() = > { txtBlock3.Text = "Searching..."; }));
}
The golden rule to maintain a responsive application is to execute any long-running code on a background thread but you must only access UI controls back on the UI thread. Please refer to MSDN for more information about the threading model in WPF: https://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx
DispatcherTimer may be a better solution in this case, like in the below example:
public partial class MainWindow : Window
{
private DispatcherTimer timer;
public MainWindow()
{
InitializeComponent();
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 220);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
// Do something on your UI
Trace.TraceInformation("Timer expired");
}
}
Basically, this will raise an event at a given interval. Note that Windows Forms also has a timer, as does System.Threading, but you want to make sure you use DispatcherTimer rather than those. In particular, the one from System.Threading tends not to mix well with UIs because it runs its actions on the thread pool and WPF in particular is very fussy about how you update your UI from background threads.
The documentation I link to, as well as this answer, also give details on this.

Control.Invoke must be used to interact with controls created on a separate thread

Below is the method to start a thread in compact framework 3.5
public ScanEntry(string scanId)
{
InitializeComponent();
_scanId = scanId;
//reader = deviceFactory.Create();
//reader.YMEvent += new ScanEventHandler(reader_Reading);
//reader.Enable();
}
private void CasesEntry_Load(object sender, EventArgs e)
{
caseCounterLabel.Text = cases.Count.ToString();
scanIdValueLabel.Text = _scanId;
}
internal void menuItemNewScan_Click(object sender, EventArgs e)
{
System.Threading.ThreadStart threadDelegate = new System.Threading.ThreadStart(ScanEvents);
System.Threading.Thread newThread = new System.Threading.Thread(threadDelegate);
newThread.Start();
}
which calls the below method on thread
private void ScanEvents()
{
try
{
//some other codes
if (scanIdValueLabel.InvokeRequired)
{
scanIdValueLabel.Invoke((Action)(() => scanIdValueLabel.Text = "value"));
}
attributeNode = docEventFile.CreateNode(XmlNodeType.Element, "Attribute", string.Empty);
XMLUtils.CreateAttribute(docEventFile, attributeNode, "name", "SCANID");
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
attributeSetNode.AppendChild(attributeNode);
//some other codes
}
catch(Execption e)
{
Message.Show(e.Message);
}
}
Errors:
TextAlign = 'scanIdValueLabel.TextAlign' threw an exception of type 'System.NotSupportedException'
base {System.SystemException} = {"Control.Invoke must be used to interact with controls created on a separate thread."}
In Line
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
I am getting Control.Invoke must be used to interact with controls created on a separate thread at this line
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
I have googled and tried with that solutions but not worked for me.Can any one help me in doing this.
Thanks
When you're dealing with Winforms, WPF, Silverlight there's the following sentence which is very important:
The UI elements can only be accessed by the UI thread. WinForms, WPF, Silverlight doesn't allow access to controls from multiple threads.
However, there is a solution which can be found here:
Update: I've created a sample application to make some things clear:
I've created a form first with a button and a label on it. The label is not visible because it doesn't contain text, but it's right underneath the button.
Scenario 1: Updating without threads:
private void btnStartThread_Click(object sender, EventArgs e)
{
lblMessage.Text = "Button has been clicked.";
}
Off course this is not a problem. It's some standard code:
Scenario 2: Updating with threads:
private void btnStartThread_Click(object sender, EventArgs e)
{
System.Threading.ThreadStart threadDelegate = new System.Threading.ThreadStart(ScanEvents);
System.Threading.Thread newThread = new System.Threading.Thread(threadDelegate);
newThread.Start();
}
private void ScanEvents()
{
lblMessage.Text = "Exected in another thread.";
}
This will fail because I'm MODIFYING the controls on my form from another thread:
Now, I will modify the code so that I'm changing the label with an action through an invoke on the label.
private void ScanEvents()
{
if (lblMessage.InvokeRequired)
{
lblMessage.Invoke((Action)(() => lblMessage.Text = "This text was placed from within a thread."));
}
}
This will make the text change.
So, I hope that it helps. If not, please shout :-)

Update UI control by FileSystemWatcher

Im having some problems in my code:
private void start_watcher()
{
fswFiler = new FileSystemWatcher(Control.filer.get_path(),"*.*");
//fswStorage = new FileSystemWatcher(Control.storage.get_path());
fswFiler.Changed += new FileSystemEventHandler(updatePend);
fswFiler.Deleted += new FileSystemEventHandler(updatePend);
fswFiler.Created += new FileSystemEventHandler(updatePend);
fswFiler.Renamed += new RenamedEventHandler(updatePend);
fswFiler.EnableRaisingEvents = true;
}
private void updatePend(object sender, FileSystemEventArgs e)
{
this.viewPend.Nodes.Clear();
Control.filer.refresh_files_list();
this.viewPend.Nodes.Add(Control.filer.get_files_node());
}
throws me out of the program.
any idea why is that happening ?
The FileSystemWatcher notifications occur in another thread than the UI uses. You must Invoke
See: how to update a windows form GUI from another class?
Or even better: How to update the GUI from another thread in C#?

C# backgroundworkers + timer: crossthreading issue

Background
From the valuable advice I received here I have now moved all of my database intensive code to a backgroundworker, specifically the direct calls to the database. That code is executed during the backgroundworker's DoWork event. If a DataTable is returned during the DoWork event, I set that DataTable to a class-wide variable. This is done, to avoid having to invoke the controls requiring the DataTable every time I run this code.
While that code is being executed, I have a label that is updated in the main UI thread, to let the user know that something is occurring. To update the label I use a timer, such that every 750 ms a "." is appended to the label's string.
The first thing that I noticed was that the backgroundworker's RunWorkerCompleted event wasn't triggering. To solve this I did an Application.DoEvents(); before each call I made to the backgroundworker. It was ugly, but it caused the event to trigger. If anyone has an alternative to fix this, I am all ears.
I then came across an interesting predicament. If I run the program within Visual Studio 2010, in the debugging mode, I get an InvalidOperationException error stating that the "Cross-thread operation not valid: Control 'lblStatus' accessed from a thread other than the thread it was created on." This error occurs during the backgroundworker's RunWorkerCompleted event, where I set the text of a label in the main UI thread. But, when I launch the application directly, through the executable, it works exactly as desired (i.e. the label's text is set correctly).
Question
Can anyone explain what is going on / offer advice on how to improve upon this?
Code
I can't post all of the code involved, but here's some relevant stuff:
namespace Test
{
public partial class frmMain : Form
{
public static Boolean bStatus = false;
static Boolean bTimer = false;
System.Timers.Timer MyTimer = new System.Timers.Timer();
public frmMain()
{
InitializeComponent();
MyTimer.Elapsed += new System.Timers.ElapsedEventHandler(MyTimer_Elapsed);
MyTimer.Interval = 750; // Every 3/4 of a second
ExampleTrigger();
}
/// <Insert>Lots of unshown code here</Insert>
private void ExampleTrigger()
{
// This is used to simulate an event that would require the backgroundworker
Application.DoEvents();
bgw.RunWorkerAsync(0);
WaitText("Example - 1");
}
private static void MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
bTimer = true;
}
// Update status text
private void WaitText(string txt)
{
MyTimer.Enabled = true;
lblStatus.Text = txt;
bStatus = false;
while (!bStatus)
{
if (bTimer)
{
txt = txt + ".";
lblStatus.Text = txt;
lblStatus.Update();
bTimer = false;
}
}
MyTimer.Enabled = false;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
int iSelect = (int)e.Argument;
switch (iSelect)
{
case 0:
// Hit the database
break;
/// <Insert>Other cases here</Insert>
default:
// Do something magical!
break;
}
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bStatus = true;
lblStatus.Text = "Ready!"; // This is where the exception occurs!
}
}
}
Never run a while() loop like that in the UI thread.
You're freezing the UI until the loop terminates; this defeats the purpose.
In addition, System.Timers.Timer doesn't run callbacks in the UI thread.
Use a WinForms Timer instead.
Once you switch to a WinForms timer, you can simply append to the label inside the timer callback, and disable the timer when the operation finishes.

Call method on the GUI thread from a timers thread

In my application I am using a timer to check for updates in an RSS feed, if new items are found I pop up a custom dialog to inform the user. When I run the check manually everything works great, but when the automatic check runs in the timers Elapsed event the custom dialog is not displayed.
First of all is this a thread issue? (I am assuming it is because both the manual and automatic check use the same code).
When I run the automatic check, do I have to invoke the method that runs the check from the Timers Elapsed event handler?
Is there something I need to do in my custom dialog class?
Edit:
this is a winforms application.
Here is an example of what the code is like. (Please don't point out syntax errors in this code example, this is just a simple example not real code).
public class MainForm : System.Windows.Forms.Form
{
//This is the object that does most of the work.
ObjectThatDoesWork MyObjectThatDoesWork = new ObjectThatDoesWork();
MyObjectThatDoesWork.NewItemsFound += new NewItemsFoundEventHandler(Found_New_Items);
private void Found_New_Items(object sender, System.EventArgs e)
{
//Display custom dialog to alert user.
}
//Method that doesn't really exist in my class,
// but shows that the main form can call Update for a manual check.
private void Button_Click(object sender, System.EventArgs e)
{
MyObjectThatDoesWork.Update();
}
//The rest of MainForm with boring main form stuff
}
public class ObjectThatDoesWork
{
System.Timers.Timer timer;
public ObjectThatDoesWork()
{
timer = new System.Timers.Timer();
timer.Interval = 600000;
timer.AutoReset = true;
timer.Elapsed += new new System.Timers.ElapsedEventHandler(TimeToWork);
timer.Start();
}
private void TimeToWork(object sender, System.Timers.ElapsedEventArgs e)
{
Update();
}
public void Update()
{
//Check for updates and raise an event if new items are found.
//The event is consumed by the main form.
OnNewItemsFound(this);
}
public delgate void NewItemsFoundEventHandler(object sender, System.EventArgs e);
public event NewItemsFoundEventHandler NewItemsFound;
protected void OnNewItemsFound(object sender)
{
if(NewItemsFound != null)
{
NewItemsFound(sender, new System.EventArgs());
}
}
}
After reading some of the comments and answers, I think my problem is that I am using a System.Timers.Timer not a System.Windows.Forms.Timer.
EDIT:
After changing to a Forms.Timer initial testing looks good (but no new items exist yet so have not seen the custom dialog). I added a bit of code to output the thread ID to a file when the update method is called. Using the Timers.Timer the thread ID was not the GUI thread, but using the Forms.Timer the thread ID is the same as the GUI.
Which timer are you using? System.Windows.Forms.Timer automatically fires the event on the UI thread. If you are using other one you will need to use Control.Invoke to call the method on UI thread.
You should use Forms.Timer here, or if you use other kind of timers, serialize calls to UI with .Invoke()
Is your application a WPF-Application? If so, you must delegate the work from your background-thread to the Dispatcher associated with the UI thread.
Post some code, so you can get better help and have a look at the Dispatcher class http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.invoke.aspx
private static System.Threading.SynchronizationContext _UI_Context;
//call this function once from the UI thread
internal static void init_CallOnUIThread()
{
_UI_Context = System.Threading.SynchronizationContext.Current;
}
public static void CallOnUIThread(Action action, bool asynchronous = false)
{
if (!asynchronous)
_UI_Context.Send((o) =>
{
action();
}, null);
else
_UI_Context.Post((o) =>
{
action();
}, null);
}

Categories

Resources