For our application, we need to display the busy indicator during navigation since some controls taking time to load. Normally for long running operation i will create a separate task and will trigger busy indicator in UI Thread but in this case I couldn't do that.
Once the busy indicator started to spin, it got disturbed by Frame.Source or Frame.Navigate which also executes in ui thread. so busy indictor hidden away .
The below is the piece of code i have used for navigation. This is executed as separate task .
public virtual void NavigateTo(string pageKey, object parameter)
{
Frame frame = null;
Action actionToExecuteOnUIContext = () =>
{
frame = GetDescendantFromName(Application.Current.MainWindow, "ContentFrame") as Frame;
if (frame != null)
{
frame.LoadCompleted -= frame_LoadCompleted;
frame.LoadCompleted += frame_LoadCompleted;
frame.Source = _pagesByKey[pageKey];
frame.DataContext = parameter;
}
};
Application.Current.Dispatcher.BeginInvoke(actionToExecuteOnUIContext, DispatcherPriority.Normal);
}
}
Any way to fix this or an alternative
WPF & RadBusyIndicator & .Net 4.5
In order to display a busy indicater you have to use a background thread, because the navigation also needs the UI thread.
I suggest to use something like this :
protected async void Navigation(string pageKey, object parameter)
{
await NavigateTo(pageKey,parameter).ContinueWith((t) =>
{// do UI stuff
Dispatcher.CurrentDispatcher.InvokeAsync(delegate
{
IsBusy = false;
});
},
//sync with UI thread
TaskScheduler.FromCurrentSynchronizationContext());
}
/// <summary>
/// This method needs to be async to perform async binding
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private async Task NavigateTo(string pageKey, object parameter)
{
//your async method
}
where IsBusy is the value of the BusyIndicator. You have to set it to true, on navigation started.
Hope this post help you#!
Related
I have a child form launched form a parent form with:
ConfigForm cfg = new ConfigForm();
cfg.ShowDialog();
This child form is used to configure some application parameters.
I want to check if there are some changes not saved, and if so, warn the user.
So my On OnClosing event is declared this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
// Here i call a function that compare the current config with the saved config
bool isUptated = CheckUnsavedChanges();
// If updated is false, it means that there are unsaved changes...
if (!isUpdated)
{
e.Cancel = true;
// At this point i create a MessageDialog (Mahapps) to warn the user about unsaved changes...
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
// If we press Close, we want to close child form and go back to parent...
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
}
My logic says that if i declare e.cancel to false it will continue closing the form, but it doesn't happen, the child form remains open.
My guess is that the async call is doing something i don't understand, because if i declare ChildFormClosing in this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
bool isUptated = CheckUnsavedChanges();
e.Cancel = true;
if (!isUpdated)
{
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
else
{
e.Cancel = false;
}
}
The final else e.Cancel = false works and the child form is closed...
Any clue?
Thanks!
Since this method is an event handler for a window, it will be called on the UI thread already, so there is no need to show the message box asynchronously.
As for the strange behavior that you are seeing, this is related to the await in the event handler. When you await a method call, what is actually happening is that everything up until the await is executed as normal, but once the await statement is reach control returns to the caller. Once the method that is awaited upon returns, then the rest of the original method executes.
The code that fires the OnClosing event is probably not designed with asynchronous event handlers in mind, so it assumes that if an event handler returns, it has finished whatever work it needs to do. Since your event handler sets CancelEventArgs.Cancel to true before it awaits on a method call, the caller to your event handler sees that it is set to true, so it doesn't close the form.
This is why showing the message box synchronously works: the entire method is executed before control returns to the caller, so CancelEventArgs.Cancel is always set to its expected value.
Raymond Chen recently posted two articles about async that might be interesting reading: Crash course in async and await and The perils of async void. The second article describes why async event handlers tend to not work how you expect them to.
The main problem with using async/await in OnClosing is, as Andy explained, that as soon as the await statement is executed, control is returned to the caller and the closing process continues.
We can work around this by making another round trip back to OnClosing after awaiting, this time with a flag to indicate whether to actually close or not, but the problem is that calling Close while the Window is already closing, is not allowed and throws an exception.
The way to solve this issue is to simply defer the execution of Close to after the current closing process, at which point it becomes valid again to close the window.
I wanted to do something like this to allow the user to handle async closing logic in the ViewModel.
I don't know if there are other edge cases that I haven't covered, but this code so far works for me:
CoreWindow.cs
public class CoreWindow : Window
{
private bool _isClosing;
private bool _canClose;
private BaseDialogViewModel ViewModel => (BaseDialogViewModel) DataContext;
public CoreWindow()
{
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is BaseDialogViewModel oldDataContext)
{
oldDataContext.Closed -= OnViewModelClosed;
}
if (e.NewValue is BaseDialogViewModel newDataContext)
{
newDataContext.Closed += OnViewModelClosed;
}
}
private void OnViewModelClosed(object sender, EventArgs e)
{
if (!_isClosing)
{
_isClosing = true;
Close();
}
}
protected override async void OnClosing(CancelEventArgs e)
{
if (ViewModel == null)
{
base.OnClosing(e);
return;
}
if (!_canClose)
{
// Immediately cancel closing, because the decision
// to cancel is made in the ViewModel and not here
e.Cancel = true;
base.OnClosing(e);
try
{
// Ask ViewModel if allowed to close
bool closed = await ViewModel.OnClosing();
if (closed)
{
// Set _canClose to true, so that when we call Close again
// and return to this method, we proceed to close as usual
_canClose = true;
// Close cannot be called while Window is in closing state, so use
// InvokeAsync to defer execution of Close after OnClosing returns
_ = Dispatcher.InvokeAsync(Close, DispatcherPriority.Normal);
}
}
catch (Exception ex)
{
// TODO: Log exception
}
finally
{
_isClosing = false;
}
}
base.OnClosing(e);
}
}
BaseDialogViewModel.cs
public class BaseDialogViewModel : BaseViewModel
{
public event EventHandler Closed;
public bool? DialogResult { get; set; }
public void Close()
{
Closed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Override to add custom logic while dialog is closing
/// </summary>
/// <returns>True if should close dialog, otherwise false</returns>
public virtual Task<bool> OnClosing()
{
return Task.FromResult(true);
}
}
BaseViewModel just contains some validation and property notification stuff, not really relevant to show here.
Big thanks to Rick Strahl for the Dispatcher solution!
UPDATE:
It's possible to use await Task.Yield(); instead of Dispatcher.InvokeAsync.
My page within a frame takes to time to load meaning the controls take some time to appear on the page for the first time. Where in my main window.cs file should I set the IsBusy = true.I have no idea how to use busy indicator.When should I switch it to true or false. please guide me how should I use it ? Thanks in advance.
Generally you would set the busy indicator before you start doing a load of heavy processing so that is dependant on your code.
It would normally before just before you spawn a background thread to do a load of work leaving the UI to say it's busy at the moment and when the thread is completing then "unbusy" the UI.
Wrap you Xaml with a busy indicator . Assuming you are using MVVM
<xctk:BusyIndicator BusyContent="{Binding BusyText}" IsBusy="{Binding IsBusy}">
<Grid>
<!--Your controls and content here-->
</Grid>
</xctk:BusyIndicator>
In your viewmodel
/// <summary>
/// To handle the Busy Indicator's state to busy or not
/// </summary>
private bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
RaisePropertyChanged(() => IsBusy);
}
}
private string _busyText;
//Busy Text Content
public string BusyText
{
get { return _busyText; }
set
{
_busyText = value;
RaisePropertyChanged(() => BusyText);
}
}
Command and Command Handler
//A Command action that can bind to a button
private RelayCommand _myCommand;
public RelayCommand MyCommand
{
get
{
return _myCommand??
(_myCommand= new RelayCommand(async () => await CommandHandler(), CanExecuteBoolean));
}
}
internal async Task CommandHandler()
{
Isbusy = true;
BusyText = "Loading Something...";
Thread.Sleep(3000); // Do your operation over here
Isbusy = false;
}
I am having a problem with getting data from db and showing in UI asynchronously.
I am using MVVM light, when I click the button, action is triggered in ViewModel:
private void SearchQuery(string query)
{
_redisModel.GetFriendsListAsync(query);
}
At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done.
At this point I need to update ListBox ItemSource. But when I try to update is I get
“The calling thread cannot access this object because a different thread owns it”
I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() and different magic, but it still doesn’t work.
I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.
private string filterText = string.Empty;
public string FilterText
{
get { return filterText; }
set
{
filterText = value;
this.RaisePropertyChanged(() => this.FilterText);
this.FriendsList.View.Refresh(); // Here where exception is happening.
}
}
I tried to change this line to
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>this.FriendsList.View.Refresh())); - still the same.
I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods.
Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.
I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.
A bit more details on how _redisModel.GetFriendsListAsync works:
In the end(after all chain of calls) it ends up here:
public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
_cbMethod = cbMethod;
_state = state;
QueueWorkOnThreadPool(workToBeDone);
}
ThreadPool.QueueUserWorkItem(state =>
{
try
{
_result = workToBeDone();
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
UpdateStatusToComplete(); //1 and 2
NotifyCallbackWhenAvailable(); //3 callback invocation
}
});
In viewmodel I have method:
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => this.FriendsList.View.Refresh()));
}
}
Can anybody please help me with this ?
Thank you
You are creating CollectionViewSource in one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompleted to
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => {
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
this.FriendsList.View.Refresh();
}));
}
}
}
You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.
If you include the relevant code it would be easier to say for sure.
I am building a device emulator. When it starts, it takes some time for it to initialized. This would be logically represented by being turned on and going immediately to an "Initialization" state, and after some time it goes to "Ready" state.
I am using MVVM, so the ViewModel will for now represent all the device logic. Each of the possible states have a datatriggered style to be rendered by the View. If I just set the state when I build the viewmodel, the view renders with the correct appearance.
What I want to do is to create a "timeout state", that is, when some event occurs (starting the application, clicking a certain button), the device enters a state for a fixed time, and then falls back to the "ready", or "idle" state.
I thought about using Sleep, but sleep blocks the UI (so they say). So I think about using Threads, but I am not sure how to do it. This is what I have got so far:
using System.ComponentModel;
namespace EmuladorMiotool {
public class MiotoolViewModel : INotifyPropertyChanged {
Estados _estado;
public Estados Estado {
get {
return _estado;
}
set {
_estado = value;
switch (_estado) {
case Estados.WirelessProcurando:
// WAIT FOR TWO SECONDS WITHOUT BLOCKING GUI
// It should look like the device is actually doing something
// (but not indeed, for now)
_estado = Estados.WirelessConectado;
break;
}
RaisePropertyChanged("Estado");
}
}
public MiotoolViewModel() {
// The constructor sets the initial state to "Searching"
Estado = Estados.WirelessProcurando;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public enum Estados {
UsbOcioso,
UsbAquisitando,
UsbTransferindo,
WirelessNãoPareado,
WirelessPareado,
WirelessDesconectado,
WirelessProcurando,
WirelessConectado,
WirelessAquisitando,
DataLoggerOcioso,
DataLoggerAquisitando,
Erro,
Formatando
}
}
Firstly having a Sleep / Async operation in a property (getter / setter) is considered bad-practice
Try this as a replacement for Sleep without blocking the UI thread:
Create a function to set Estado to Estados.WirelessProcurando
Assuming WirelessProcurando means Initialising and WirelessConectado means Initialised
.net45:
private async Task SetWirelessProcurando(int milliSeconds) {
Estado = Estados.WirelessProcurando;
await Task.Delay(milliSeconds);
Estado = Estados.WirelessConectado;
}
The reason we have the function return a Task vs void is just to let the caller if required await on this function if the logic demands it accordingly
If you cannot use await:
private void SetWirelessProcurando(int milliSeconds) {
Estado = Estados.WirelessProcurando;
var tempTask = new Task
(
() => {
Thread.Sleep(milliSeconds);
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => Estado = Estados.WirelessConectado));
},
System.Threading.Tasks.TaskCreationOptions.LongRunning
);
tempTask.Start();
}
Now calling this function whenever you want to change the setter will immediately set the state to "Intiialising" and after the given milliSeconds switch to the Initialised state.
I have a wcf callback service and the following scenario:
A client send a request to the service, and modify the color of a rectangle in the database, the service notifies it that the color has changed, and I want now in the callback notified method, to color the rectangle that was clicked with the chosen color:
Here is the method which is invoked when I click on rectangle
private void ChangeRectangleState_Single(object sender, RoutedEventArgs e)
{
Path mc = (Path)sender;
String name = mc.Name;
flag_macaz = colorClass.getRectangleColor(mc.Name+"_a",rectangleServiceClient);
ColorClass.changeRectangleColor(flag_rectangle,macazServiceClient,mc.Name+"_a");
}
public void RectangleServiceCallback_ClientNotified(objectsender,Rectangle NotifiedEventArgs e)
{
String name = e.RectangleName;
object wantedNode_a = Window.FindName(e.RectangleName);
Path rectangle = wantedNode_a as Path;
if (e.RectangleColor == 1)
{
rectangle.fill=...
}
else
if (e.RectangleColor == 0)
{
rectangle.fill=...
}
}
But I get the error "The calling thread cannot access this object because a different thread owns it."
I have tried the idea from http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher but the client get blocked.
Does anybody has other idea?
The WCF thread can't call the UI thread directly.
You'll need to fire an event from the WCF thread and subscribe to it in the UI thread. Then in your UI event handler have something like:
this.albumArt.InvokeIfRequired(() => this.SetBackgroundColor());
where InvokeIfRequired is an extension method:
public static void InvokeIfRequired(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.Invoke(action);
}
else
{
action();
}
}