Busy indicator not working - c#

I'm building an MVVM Light WPF app in Visual Studio 2015. It has a user control that contains a WindowsFormsHost with a ReportViewer for SQL Server Reporting Services local report. A button in ReportView.xaml calls a command, which in turn sends a message to the code-behind of MasterListView.xaml to generate the report.
Here's the command called by the button in ReportViewModel.cs:
public ICommand RunReportRelayCommand =>
new RelayCommand(async ()=> { await RunReport(); });
private async Task RunReport()
{
try
{
IsBusy = true;
await Task.Run(() =>
{
Messenger.Default.Send(true, "RunMasterListReport");
});
}
finally
{
IsBusy = false;
}
}
Here's the definition of IsBusy property in ReportViewModel.cs:
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
if (value == _isBusy) return;
_isBusy = value;
RaisePropertyChanged();
}
}
The same view, ReportView.xaml, which contains the button calling the above command also contains the following Extended WPF Toolkit Busy Indicator:
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="MasterListViewTemplate">
<view:MasterListView />
</DataTemplate>
</UserControl.Resources>
<xctk:BusyIndicator IsBusy="{Binding IsBusy}">
<StackPanel>
<!-- Other XAML here -->
<ContentControl ContentTemplate="{StaticResource MasterListViewTemplate}" />
</StackPanel>
</xctk:BusyIndicator>
</UserControl>
Then in the MasterListView.cs code-behind, we have this:
public partial class MasterListView : UserControl
{
public MasterListView()
{
InitializeComponent();
Messenger.Default.Register<bool>(this, "RunMasterListReport", RunMasterListReport);
}
public async void RunMasterListReport(bool val)
{
await Task.Run(() =>
{
var dataSet = new DrugComplianceDataSet();
dataSet.BeginInit();
ReportViewer.ProcessingMode = ProcessingMode.Local;
ReportViewer.LocalReport.ShowDetailedSubreportMessages = true;
ReportViewer.LocalReport.DataSources.Clear();
var dataSource = new ReportDataSource
{
Name = "MasterListRandomDataSet",
Value = dataSet.MasterListRandom
};
ReportViewer.LocalReport.DataSources.Add(dataSource);
ReportViewer.LocalReport.ReportEmbeddedResource = "MasterListRandom.rdlc";
dataSet.EndInit();
var adapter = new MasterListRandomTableAdapter { ClearBeforeFill = true }
.Fill(dataSet.MasterListRandom);
Dispatcher.Invoke((MethodInvoker)(() => { ReportViewer.RefreshReport(); }));
});
}
}
However, the busy indicator doesn't trigger, though the report does show after 5 seconds or so. What am I doing wrong? Thanks.

You got a whole soup of wat in there. Things like
await Task.Run(() =>
suggest you don't really understand how async/await works. I'd step down and find some nice documentation to read. Also, you appear to be doing all your work on the UI thread.
Dispatcher.Invoke((MethodInvoker)(() => { ReportViewer.RefreshReport(); }));
You shouldn't be touching a dispatcher in a background worker unless you're updating the UI. It appears you're offloading the actual work you intend to do in a background thread (refreshing the report) on the UI thread.
Maybe your report viewer HAS to run in the UI thread. If that's so (maybe it was designed a while ago and doesn't take advantage of multitasking) there isn't much you can do about this situation. Your UI will be locked while it's processing.
If all your long-running work has to run on the UI thread, then strip out all that nonsense. Before kicking off your work, update IsBusy, then offload execution of ReportViewer.RefreshReport() onto the Dispatcher, but using a low priority DispatcherPriority, so that it runs after the UI updates and shows that it is busy. Your UI will be frozen during processing, but at least you'll give the user an indication of what's going on.

Related

Refreshing a binded property from another Thread doesn't work

In a Xamarin project, I am trying to update a label showed in a loading page.
The loading page contains also an activity indicator control and is showed after the user clicks OK on a display alert and the alert is showed after the user clicks on a button in the UI.
The loading page is necessary because some heavy activity happens in background. Every seconds I query the status of the activity and I want this status be showed on a loading page, giving the user some information about the activity. This activity is wrappred in a ICommand in the ViewModel.
When I run the command I want a variable in the viewModel referred by the page be updated.
The command is executed in a Task.Run method otherwise the loading page is not showed untill the command has done.
This is the code:
View code behind
private async void SelfLearningButton_OnClicked(object sender, EventArgs e)
{
var bindingContext = BindingContext as AutomationViewModel;
if (await DisplayAlert("message",
"OK", "Cancel"))
{
bindingContext.IsLearning = true; //a variable that drives the ISVisibleProperty of the loading page
//Task.Run because the loading page must be showed otherwise I wait for the command to be completed (with async/await too)
_ = Task.Run(() => bindingContext.TheCommand.Execute(null));
}
}
View.xaml
<Label IsVisible="{Binding IsLearning}"
TextColor="#f1c00e"
Padding="0, 20, 0, 0"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand"
FontAttributes="Bold"
x:Name="LearningStatus"
Text="{Binding Status}">
View Model
//The variable that must be updated
public string Status
{
private set
{
if (_status != value)
{
_status = value;
NotifyPropertyChanged();
}
}
get { return _status ; }
}
TheCommand= new Command(
async () =>
{
try
{
int i = 0;
while (i < 10000 )
{
++i;
//Doesn't work
Device.BeginInvokeOnMainThread( ()=> _status= i.ToString()); //sorry for the boxing
}
}
catch(Exception e)
{
//
}
finally
{
IsLearning = false;
}
});
When I run the code of the command the BeginInvokeOnMainThread doesn't update the variable. I use BeginInvokeOnMainThread because view items must be updated in the UI thread. At every iteration _status has the same value used for init it, and setter code of the field is not called. What is going wrong with this code?
Thank you

How to display the wait cursor while a ListBox is updating after a change in its ItemsSource collection?

I've a ListBox fed by a view model property (Photos) which is an ObservableCollection of objects containing paths to image files. The ListBox displays the images, which can be numerous:
View:
<ListBox ItemsSource="{Binding Path=Photos}"
SelectionChanged="PhotoBox_SelectionChanged">
...
</ListBox>
Code behind (which could be improved to run asynchronously...):
void RefreshPhotoCollection (string path) {
Photos.Clear ();
var info = new DirectoryInfo (path);
try {
foreach (var fileInfo in info.EnumerateFiles ()) {
if (FileFilters.Contains (fileInfo.Extension)) {
Photos.Add (new Photo (fileInfo.FullName));
}
}
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) {
...
}
}
I've managed to display a wait cursor when running RefreshPhotoCollection by using this method which involves a IDisposable:
using (this.BusyStackLifeTracker.GetNewLifeTracker())
{
// long job
}
But the cursor is reset to a pointer at the end of this method, when the view is notified of the collection change. The ListBox then renders itself, but the wait cursor is not displayed while this is done. There is were I have a problem.
How can I manage to have the ListBox displaying a wait cursor until the update is complete?
This pattern does these things:
Places the cursor change logic in a flag variable for other uses.
Provides a safe way for other threads to change GUI items on the gui thread.
Adds the photos in a separate thread which will allow for UI to be responsive.
Actual photo added into the collection is done on the Gui thread for safety.
Wait cursor won't jump/change state as photos are added.
Checks for a minimum waited processing time to give the appearance of doing something. That wait time can be changed as needed. If the minimum wait time is not hit, no extra time is done.
Notification on VM This allows any non gui thread to do a safe gui operation.
public static void SafeOperationToGuiThread(Action operation)
{
System.Windows.Application.Current?.Dispatcher?.Invoke(operation);
}
Status Flag
Then provide an Ongoing Flag which the view can be re-used if needed, which also sets the cursor:
private bool _IsOperationOnGoing;
public bool IsOperationOnGoing
{
get { return _IsOperationOnGoing; }
set {
if (_IsOperationOnGoing != value)
{
_IsOperationOnGoing = value;
SafeOperationToGuiThread(() => Mouse.OverrideCursor = (_IsOperationOnGoing) ? Cursors.Wait : null );
OnPropertyChanged(nameof(IsOperationOnGoing));
}
}
}
Add photos in Seperate Thread Then in your photo add, do this pattern where the gui thread turns off the cursor, then a separate task/thread loads the photos. Also the time is monitored for a viable consistent wait time cursor to be shown:
private Task RunningTask { get; set; }
void RefreshPhotoCollection (string path)
{
IsOperationOnGoing = true;
RunningTask = Task.Run(() =>
{
try {
TimeIt( () =>
{
... // Enumerate photos like before, but add the safe invoke:
SafeOperationToGuiThread(() => Photos.Add (new Photo (fileInfo.FullName););
},
4 // Must run for 4 seconds to show the user.
);
}
catch() {}
finally
{
SafeOperationToGuiThread(() => IsOperationOnGoing = false);
}
});
}
}
Wait time Check
How can I manage to maintain the wait cursor until after the view ListBox update is complete so that the user really knows when the UI is responsive again?
Here is the waiting operation method which will fill in the time to wait if the minimum operation time is not hit:
public static void TimeIt(Action work, int secondsToDisplay = 2)
{
var sw = Stopwatch.StartNew();
work.Invoke();
sw.Stop();
if (sw.Elapsed.Seconds < secondsToDisplay)
Thread.Sleep((secondsToDisplay - sw.Elapsed.Seconds) * 1000);
}
This can be modified to use an async call if needed with minimal changes.
Alternate to #mins tag line in profile:
Light a man a fire and he is warm for a day.
Set a man a fire and he is warm for the rest of his life.
Start by creating a "busy" flag in your view model:
private bool _Busy = false;
public bool Busy
{
get { return this._Busy; }
set
{
if (this._Busy != value)
{
this._Busy = value;
RaisePropertyChanged(() => this.Busy);
}
}
}
Then move all your work into a Task, which sets this busy flag and resets it afterwards (note how you'll have to invoke the GUI thread's dispatcher whenever you add the photo to the collection itself):
private void DoSomeWork()
{
Task.Run(WorkerProc);
}
private async Task WorkerProc()
{
this.Busy = true;
for (int i = 0; i < 100; i++)
{
// simulate loading a photo in 500ms
var photo = i.ToString();
await Task.Delay(TimeSpan.FromMilliseconds(500));
// add it to the main collection
Application.Current.Dispatcher.Invoke(() => this.Photos.Add(photo));
}
this.Busy = false;
}
And then in your XAML give your MainWindow a style that sets the cursor whenever this flag is set:
<Window.Style>
<Style TargetType="{x:Type Window}">
<Style.Triggers>
<DataTrigger Binding="{Binding Busy}" Value="True">
<Setter Property="Cursor" Value="Wait" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
UPDATE: Placing the work in a Task means you won't be starving the GUI thread, so the ListBox will be updating as you go along rather than waiting until you've finished loading all the photos. And if it's the ListBox update itself that's taking too long then that's what you should be trying to address, e.g. look into whether any converters are running slow or whether your Photo data structure needs to be provide to the view in a more optimal format. I think you'll find though that simply enabling virtualization will probably go a long way towards improving your front-end responsiveness:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Unfortunately virtualization is something that's very easy to break though, so you'll want to make sure it's working by checking your Live Visual Tree while your app is running to make sure that ListBoxItems are only being created for the items that are actually visible on screen.

How to correctly refresh data in a grid using Task in WPF

My small WPF code is giving me this error
The calling thread cannot access this object because a different thread owns it
I know what exactly it is saying but I am unable to understand how can I fix it. I have tried different things but no luck and I admit that I am not good in Task library.
This is what I am trying to achieve.
Load data when WPF form loads - This is ok
On the form user will press the Refresh button to refresh the data from the database.
here is my code
public partial class DocListView : UserControlListBase
{
private ScecoBillDataScope _scecoBillDataScope;
public EntityCollection<ScecoBillEntity> ScecoBills = new EntityCollection<ScecoBillEntity>();
public DocListView()
{
InitializeComponent();
LoadData();
}
private async void LoadData()
{
await Task.Run(() =>
{
_scecoBillDataScope.FetchData();
});
var collectionView = CollectionViewSource.GetDefaultView(_scecoBillDataScope.ScecoBills);
await Dispatcher.BeginInvoke(new ThreadStart(()=> LayoutRoot.DataContext = collectionView));
}
private void BbiRefresh_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
{
_scecoBillDataScope.Reset();
LoadData();
e.Handled = true;
}}
Actually the error is appearing when I click the Refresh button at this line _scecoBillDataScope.FetchData();
Please advice.
Your problem is this line:
await Dispatcher.BeginInvoke(new ThreadStart(()=> LayoutRoot.DataContext = collectionView));
You're just creating a new thread, you have to actually dispatch on the GUI thread:
Application.Current.Dispatcher.Invoke(() =>
{
LayoutRoot.DataContext = collectionView;
});
Since your collection items are data bound to your view, you have to treat them as part of your UI.
Try loading a new collection from within Task.Run, and then copying them over your existing items while on the UI thread. So, the new collection is built on a thread pool thread (Task.Run), and then the data-bound collection items are modified from the UI thread.
It looks like you want to do something in UI Thread.
As You know You can use Dispatcher class.
But There is another way to ask something to UI Thread.
var uiThread = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
// I assumed that It returns boolean value
_scecoBillDataScope.FetchData();
}).ContinueWith(x =>
{
// Here you can put the code to work on the UI thread.
if (x.Result)
{
var collectionView = CollectionViewSource.GetDefaultView(_scecoBillDataScope.ScecoBills);
LayoutRoot.DataContext = collectionView;
}
}, uiThread);
I hope it helps.
thank you.

How to keep UWP UI responsive?

I have a UWP app in which one of the pages needs to do three tasks - the first is to load the main content for the page (a collection of 'Binders' objects retrieved from our API) then after that load some other content which are not dependent on the first task in any way.
My page is backed with a ViewModel (I'm using the default Template10 MVVM model) and when the page is navigated to I do this in the VM OnNavigatedToAsync method:
public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
{
IsBusy = true; //Show progress ring
CreateServices(); //Create API service
//Download binders for board and populate ObservableCollection<Binder>
//This has a cover image and other info I want to show in the UI immediately
await PopulateBinders();
//Get files and calendar events for board
//Here I want to run this on a different thread so it does
//not stop UI from updating when PopulateBinders() is finished
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
await base.OnNavigatedToAsync(parameter, mode, state);
return;
}
}
So the main task is PopulateBinders() - this calls the API, returns the data and loads it into an ObservableCollection of Binder. When this has run I want the UI to update it's bindings and show the Binder objects immediately but instead it waits until the two other tasks in the WhenAll Task) have run before updating the UI. (All three of these Tasks are defined as private async Task<bool>...)
I realise I'm missing something basic here - but I thought calling a Task from an async method would allow the UI to update? Since it clearly doesn't how do I refactor this to make my page bindings update after the first method?
I tried Task.Run(() => PopulateBinders()); but it made no difference.
Instead of running it inside OnNavigatedToAsync(), run the asynchronous task when the page is already loaded since you are unintentionally "block" the app to run base.OnNavigatedToAsync() for several seconds untill Task.WhenAll finished running.
Running on loaded event in MVVM can be achieved by implementing Microsoft.Xaml.Interactivity to bind Page.Loaded event with a DelegateCommand class in your viewmodel.
XAML Page ( assuming you are using Prism as your MVVM framework )
<Page ...
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:InvokeCommandAction Command="{x:Bind Path=Vm.PageLoaded}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Page>
and inside your viewmodel:
public class PageViewModel : ... //some interface or else
{
public DelegateCommand PageLoaded;
public PageViewModel(...)
{
PageLoaded = new DelegateCommand(async () =>
{
IsBusy = true;
CreateServices();
await PopulateBinders();
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
});
}
}
Read more : Binding UWP Page Loading/ Loaded to command with MVVM
I hope this code will help you to update the UI as expected:
public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
{
IsBusy = true; //Show progress ring
CreateServices(); //Create API service
//Download binders for board and populate ObservableCollection<Binder>
//This has a cover image and other info I want to show in the UI immediately
await PopulateBinders();
await PouplateBoardData();
await base.OnNavigatedToAsync(parameter, mode, state);
return;
}
}
private async void PopulateBoardData()
{
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
}

WPF MVVM: Async method does not always update the UI

I bound a button to a command in the view model that triggers a long-running operation. The operation is run in a separate task. Before and after the task, a property is set to reflect it running in the UI.
My problem now is: Sometimes, when I click the button, the change of the property BEFORE the long operation runs is registered (i.e., the UI changes accordingly), but when the task finishes, it is not immediately. I have to click somewhere in the UI to make the change to be reflected.
Here is how I did it (using MVVM Light):
_longRunningOpCommand = new RelayCommand(async () => await DoLongRunningThingAsync(), CanDoLongRunningThing);
// ...
public ICommand LongRunningOpCommand { get { return _longRunningOpCommand; } }
// ...
private async Task DoLongRunningThingAsync() {
try {
IsDoingStuff = true; // triggers a PropertyChangeEvent, is bound to UI
await Task.Factory.StartNew(async () => {
await _something.DoLenghtyOperation();
}, TaskCreationOptions.LongRunning);
} finally {
IsDoingStuff = false;
}
}
Now, the UI elements update as soon as IsDoingStuff is becoming false as they should, but they are not updated when IsDoingStuff is becoming true. I have to click into the UI to have them updated.
I think I'm doing something wrong, but can't track down what.

Categories