There is a problem with UI update in WPF.
I have such code:
private void ButtonClick_EventHandler(object sender, RoutedEventArgs e)
{
Label.Visibility = Visibility.Visible;
TextBox.Text = "Processing...";
LongTimeMethod(); //some long operation
}
The problem is that until LongTimeMethod ends (that is event handler ends), Label.Visibility and TextBox.Text will not be changed.
I solved it like this so far:
private void ButtonClick_EventHandler(object sender, RoutedEventArgs e)
{
Label.Visibility = Visibility.Visible;
TextBox.Text = "Processing...";
Dispatcher.BeginInvoke(new Action(LongTimeMethod),
DispatcherPriority.Background);
}
Is there any other solution without using dispatcher invocation? Calling this.UpdateLayout() doesn't help.
With Dispatcher.BeginInvoke you are still using the UI thread for LongTimeMethod(). If this is not required (i.e. it is doing some kind of background processing) I would suggest using the TPL to run it on a background thread:
private void ButtonClick_EventHandler(object sender, RoutedEventArgs e)
{
Label.Visibility = Visibility.Visible;
TextBox.Text = "Processing...";
Task.Factory.StartNew(() => LongTimeMethod())
.ContinueWith(t =>
{
Dispatcher.BeginInvoke((Action)delegate()
{
TextBox.Text = "Done!";
});
});
}
With this method, the long running method is processed on a background thread (so the UI thread will be free to keep rendering and the app won't freeze up) and you can do anything that does alter the UI (such as updating the textbox text) on the UI Dispatcher when the background task completes
Visibility and Text are dependency properties which updated by dispatcher. Your solution is absolutely corrent, but my suggestion is to do it asynchronously.
On other hand, you might simulate Application.DoEvents in WPF (see the article).
Related
I am writing a program that reads information from a device through the serial port. I have a list of available devices in a datagrid. When the user clicks on a row, it fetches information from the device and displays it in a separate list. It usually takes about a second for the device to respond and the information to populate. However, during this second, the datagrid row does not highlight as selected. So it takes about a second from the time the mouse clicks on the datagrid row for it to actually show as highlighted/selected.
I thought a background worker thread would be the best thing to use for this, but I get the same results with the code below. Am I using it incorrectly, or is there something else I should be doing for selecting a datagrid row?
private void relayList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += GetLinks;
worker.RunWorkerAsync();
}
private void GetLinks(object sender, DoWorkEventArgs e)
{
//send message to viewModel and do time-consuming work here
}
BackgroundWorker has a RunWorkerCompleted method. It will run in the execution context of the UI thread.
There is also a result property that is used to pass a result to the next method which is the RunWorkerCompleted method.
private void yourMethod()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWorkMethod;
worker.RunWorkerCompleted += RunWorkerCompletedMethod;
worker.RunWorkerAsync();
}
private void RunWorkerCompletedMethod(object sender, RunWorkerCompletedEventArgs e)
{
string result = (string)e.Result;
// do stuff here
}
private void DoWorkMethod(object sender, DoWorkEventArgs e)
{
e.Result = string.Empty; // Your device data
}
If you desperatly want to not use the completed method. Do not use dispatcher, instead use SynchronizationContext.
You will need to store SynchronizationContext.Current on a field when you first run your form but after that you can invoke whatever you need on the UI thread.
I am writing a WP7 app which restores its state on the main UI thread in its page's OnNavigatedTo() override handler. In this handler, it sets the page's listbox ItemsSource property to the deserialized ObservableCollection of data items. The deserialization is from isolated storage and is quick enough that it doesn't hang the thread.
However, when the page comes up, the listbox is empty. Upon setting a breakpoint and inspecting the state of the page, the Items property is correctly populated and non-empty.
If I delay the settings of the ItemsSource property like so:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
int delayMs = 100; // Why 100 ?
runDelayedDispatch(Deployment.Current.Dispatcher,
delayMs, delegate()
{
deserializeFromStorageAndSetItemsSource();
});
}
...
// Does a BeginInvoke() after the specified delay.
public static void runDelayedDispatch(Dispatcher dispatcher,
int delayInMilliseconds, Action action)
{
Action dispatcherAction = delegate()
{
dispatcher.BeginInvoke(action);
};
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) => Thread.Sleep(delayInMilliseconds);
worker.RunWorkerCompleted += (s, e) => dispatcherAction.Invoke();
worker.RunWorkerAsync();
}
Then everything works fine.
What am I doing wrong? Should I be reading from isolated storage on a different handler or later in the application lifecycle?
Articles about app lifecycle have not shed any light on this :(
http://msdn.microsoft.com/en-us/library/system.windows.controls.page.onnavigatedto(v=vs.95).aspx
http://msdn.microsoft.com/en-us/library/cc838245(v=vs.95).aspx
http://windowsphonegeek.com/articles/WP7-Application-Lifecycle-and-Tombstoning
http://visualstudiomagazine.com/articles/2011/06/01/pcmob_app-lifecycle.aspx
Thanks!
It sounds like maybe your data context isn't set correctly, your binding isn't set correctly, or your INotifyPropertyChanged isn't firing.
PS: I think you should reword your question to get rid of your attempt to bypass the problem with a delay - that is giving you answers along lines you don't want and I'm pretty sure it isn't needed at all. Instead, put the ALL the RELEVANT code for the list and page in your question so we can see what you're doing.
All you have to do is to refresh the UI using INotifyPropertyChanged
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
int delayMs = 100; // Why 100 ?
runDelayedDispatch(Deployment.Current.Dispatcher,
delayMs, delegate()
{
deserializeFromStorageAndSetItemsSource();
});
NotifyPropertyChange("UI");
}
public static void runDelayedDispatch(Dispatcher dispatcher,
int delayInMilliseconds, Action action)
{
Action dispatcherAction = delegate()
{
dispatcher.BeginInvoke(action);
};
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) => Thread.Sleep(delayInMilliseconds);
worker.RunWorkerCompleted += (s, e) => dispatcherAction.Invoke();
worker.RunWorkerAsync();
NotifyPropertyChanged("UI");
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I found it, thanks all! I went back to the basics and tested this scenario from a bare bones list box and it worked in the OnNavigatedTo() without any problem.
The culprit was that I was hooking into receiving notification when the DataContext had changed in order to bind the width of the listbox items to the listbox width, but I could easily accomplish this by hooking into the LayoutUpdated event.
Here was the culprit code that I removed:
http://www.codeproject.com/Articles/38559/Silverlight-DataContext-Changed-Event
Thanks guys and sorry for wasting your time :|
I'm having problems getting the Silverlight Button Click event to immediately update a control's UI element and then continue doing some other process. For example, update the text of a control and then do some process. I've tried calling the UpdateLayout() method but that doesn't help.
Here is some sample code:
private void button1_Click(object sender, RoutedEventArgs e)
{
textBlock1.Text = "Testing";
textBlock1.UpdateLayout();
UpdateLayout();
Thread.Sleep(2000);
textBlock1.Text = "Done";
}
In that sample, the textBlock1 control will never display the text "Testing".
It's because you've blocked the UI thread with your Sleep statement which means that the text block won't update until after it's completed, but by that time you've set the text to "Done".
This doesn't work. The UI will block when you do not exit the method. This is why in Silverlight you usually do everything asynchronously:
private void button1_Click(object sender, RoutedEventArgs e)
{
textBlock1.Text = "Testing";
var myTask = /* ... */
myTask.Completed += new FancyDelegate(myTask_Completed);
}
private void myTask_Completed(object sender, RoutetEventArgs e)
{
textBlock1.Text = "Done.";
}
If your tasks do not have asynchronous functions, just wrap them. But be reminded, that when you want to change your textBlock1.Text property from another thread, you must invoke it with the Dispatcher.
My app has a UserControl that wraps a ServiceController to expose start/stop/restart win service functionality to the user. My concern at the moment is restarting. It takes a small amount of time and I want to reflect the restarting status inside of the control. This is roughly what I have for the restart button click handler
private void RestartButton_Click(object sender, RoutedEventArgs e)
{
startStopButton.Visibility = Visibility.Hidden;
restartButton.Visibility = Visibility.Hidden;
statusTextBlock.Text = "Restarting...";
Controller.Stop();
Controller.WaitForStatus(ServiceControllerStatus.Stopped);
Controller.Start();
Controller.WaitForStatus(ServiceControllerStatus.Running);
startStopButton.Visibility = Visibility.Visible;
restartButton.Visibility = Visibility.Visible;
statusTextBlock.Text = Controller.Status.ToString();
}
Even when I step through the debugger I don't see these changes reflected in the application. Must be something that I'm missing. Also, I've tried disabling the buttons instead of hiding them and that does not work either.
You're doing everything on the UI thread, so the UI isn't updated until this code completes. You should do the heavy lifting on a background thread. The BackgroundWorker component makes this easy:
private void RestartButton_Click(object sender, RoutedEventArgs e)
{
startStopButton.Visibility = Visibility.Hidden;
restartButton.Visibility = Visibility.Hidden;
statusTextBlock.Text = "Restarting...";
var backgroundWorker = new BackgroundWorker();
// this delegate will run on a background thread
backgroundWorker.DoWork += delegate
{
Controller.Stop();
Controller.WaitForStatus(ServiceControllerStatus.Stopped);
Controller.Start();
Controller.WaitForStatus(ServiceControllerStatus.Running);
};
// this delegate will run on the UI thread once the work is complete
backgroundWorker.RunWorkerCompleted += delegate
{
startStopButton.Visibility = Visibility.Visible;
restartButton.Visibility = Visibility.Visible;
statusTextBlock.Text = Controller.Status.ToString();
};
backgroundWorker.RunWorkerAsync();
}
That's because execution is happening in the UI thread. Your button won't update because between { and } the UI thread is busy doing your work and it cannot update the button.
I have a WPF form with 3 buttons and have routed events on them, commands are binded on start...
private void InitCommandBinding(UIElement frameworkElement) {
CommandBinding commandBinding;
commandBinding = new CommandBinding(ViewModelCommands.Save, Save_Executed, Save_CanExecute);
frameworkElement.CommandBindings.Add(commandBinding);
commandBinding = new CommandBinding(ViewModelCommands.SaveAndClose, SaveAndClose_Executed, SaveAndClose_CanExecute);
frameworkElement.CommandBindings.Add(commandBinding);
commandBinding = new CommandBinding(ViewModelCommands.Delete, Delete_Executed, Delete_CanExecute);
frameworkElement.CommandBindings.Add(commandBinding);
}
the details ui has code like
private void Delete_Executed(object sender, ExecutedRoutedEventArgs e) {
try
{do validations }
}
private void Delete_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = viewModel.IsValid(); (returns bool)
}
Validity enables and disables buttons etc.
The form has an instance of an object new or old and validations take place on the data
My issue is that the event just excute all the time and the form just hangs cause validation code does poll db etc to check....
how to I just get them to fire once when the form is loaded mmm....
If I understand you well it is only necessary to check the validity of the data at form load and the IsValid method is resource intensive?
Why don't you change the IsValid() method to an IsValid property and set this is in the Form_Loaded event?
The CanExute method will be checked any time the UI fires an event like TextChanged, LostFocus etc. So you better make such methods very lightweight.