I am not quite sure, where my problem/mistake is.
I am using WPF in combination with the MVVM pattern and my problem is at the login.
My first attempt worked fine. I had several windows, each with their own ViewModel.
In the Login ViewModel I had following code running:
PanelMainMessage = "Verbindung zum Server wird aufgebaut";
PanelLoading = true;
_isValid = _isSupportUser = false;
string server = Environment.GetEnvironmentVariable("CidServer");
string domain = Environment.GetEnvironmentVariable("SMARTDomain");
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, server + "." + domain))
{
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
_isValid = pc.ValidateCredentials(Username, _view.PasswortBox.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
}
catch (Exception ex)
{
//errormanagement -> later
}
if (_isValid)
{
PanelLoading = false;
if (_isSupportUser)
_mainwindowviewmodel.switchToQuestionView(true);
else
_mainwindowviewmodel.switchToQuestionView(false);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
That part connects to an Active Directory and first checks if the login was succesfull and then, if the user has a certain ad group (in method isSupport)
I have a display in the view, which is like a progress bar. It is active when PanelLoading equals true.
Until now everything worked.
Then I created a main window with a contentcontrol in it and changed my views to user controls, so I could swap them. (The intention was, not to open/create a new window for every view).
When I execute the code now, my GUI blocks, until said part is executed. I have tried several ways...
Moving the code snippet into an additional method and starting it as an own thread:
Thread t1 = new Thread(() => loginThread());
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
When I do it this way, I get an error that a ressource is owned by an another thread and thus cannot be accessed. (the calling thread cannot access this object because a different thread owns it)
Then, instead of an additional thread, trying to invoke the login part; login containing the previous code snippet
Application.Current.Dispatcher.Invoke((Action)(() =>
{
login();
}));
That does not work. At least not how I implemented it.
After that, I tried to run only the main part of the login snippet in a thread and after that finished, raising an previously registered event, which would handle the change of the content control. That is the part, where I get the error with the thread accessing a ressource owned by another thread, so I thought, I could work around that.
void HandleThreadDone(object sender, EventArgs e)
{
if (_isValid)
{
PanelLoading = false;
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
}
And in the login method I would call ThreadDone(this, EventArgs.Empty); after it finished. Well, I got the same error regarding the ressource owned by an another thread.
And now I am here, seeking for help...
I know that my code isn't the prettiest and I broke at least two times the idea behind the mvvm pattern. Also I have little understanding of the Invoke method, but I tried my best and searched for a while (2-3 hours) on stackoverflow and other sites, without succeeding.
To specify where the error with thread occurs:
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
which leads to the following method
public void switchToQuestionView(bool supportUser)
{
_view.ContentHolder.Content = new SwitchPanel(supportUser);
}
This is also one occasion, where I am not using Data Binding. I change the content of my contentcontrol:
<ContentControl Name="ContentHolder"/>
How would I implement this with Data Binding. Should the property have the type ContentControl? I couldn't really find an answer to this. And by changing this to DataBinding, would the error with the thread ownage be solved?
The project structure is as following:
Main View is entry point, in the constructor the data context is set to the mainviewmodel, which is created at that time. the main view has a contentcontrol, where I swap between my usercontrols, in this case my views.
from my mainviewmodel I set the content of the contentcontrol in the beginning at the usercontrol login, which creates a viewmodel in its contructors and sets it as datacontext.
The code snippets are from my loginviewmodel. Hope this helps.
I thought I found a workaround, but it still does not work. I forgot, how the timer works in the background, so it can be solved that way either.
The problem is that WPF, or XAML framawork in general, doesn't allow to modify visual elements on the main thread, from other threads. For solving this you should to distinguish which is the part of your code that update the view from the second thread. In your case I can see that:
_view.ContentHolder.Content = new SwitchPanel(supportUser);
changes the view.
For solving this you could try this answer. In which I use the synchronization context to the communication between threads.
Another way to solve it, (and it maybe is a wrong usage of the dispatcher) is using the dispatcher for "send" the actions that modify the view to the main thread. Some thing like this:
var dispatcher = Application.Current.Dispatcher;
//also could be a background worker
Thread t1 = new Thread(() =>
{
dispatcher .Invoke((Action)(() =>
{
login(); //or any action that update the view
}));
//loginThread();
});
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
Hope this helps...
One common approach is to implement an AsyncRelayCommand (in some tutorials also named AsyncDelegateCommand and bind it to the WPF view.
Here's an example implementation I used for a demo project to get familiar with WPF, MVVM and DataBinding.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
public class AsyncRelayCommand : ICommand {
protected readonly Func<Task> _asyncExecute;
protected readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncRelayCommand(Func<Task> execute)
: this(execute, null) {
}
public AsyncRelayCommand(Func<Task> asyncExecute, Func<bool> canExecute) {
_asyncExecute = asyncExecute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) {
if(_canExecute == null) {
return true;
}
return _canExecute();
}
public async void Execute(object parameter) {
await ExecuteAsync(parameter);
}
protected virtual async Task ExecuteAsync(object parameter) {
await _asyncExecute();
}
}
Here's the LoginViewModel.
// ViewBaseModel is a basic implementation of ViewModel and INotifyPropertyChanged interface
// and which implements OnPropertyChanged method to notify the UI that a property changed
public class LoginViewModel : ViewModelBase<LoginViewModel> {
private IAuthService authService;
public LoginViewModel(IAuthService authService) {
// Inject authService or your Context, whatever you use with the IoC
// framework of your choice, i.e. Unity
this.authService = authService
}
private AsyncRelayCommand loginCommand;
public ICommand LoginCommand {
get {
return loginCommand ?? (loginCommand = new AsyncCommand(Login));
}
}
private string username;
public string Username {
get { return this.username; }
set {
if(username != value) {
username = value;
OnPropertyChanged("Username");
}
}
}
private string password;
public string Password {
get { return this.password; }
set {
if(password != value) {
password = value;
OnPropertyChanged("Password");
}
}
}
private async Task Search() {
return await Task.Run( () => {
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
// for ViewModel properties you don't have to invoke/dispatch anything
// Only if you interact with i.e. Observable Collections, you have to
// run them on the main thread
_isValid = pc.ValidateCredentials(this.Username, this.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
} );
}
}
Now you bind Username and Password properties as Two-Way bindings to your text fields and Bind your LoginCommand command to your login button.
Last but not least, a very basic implementation of the ViewModelBase.
public abstract class ViewModelBase<T> : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Some remarks at the end:
There are several issues with your code above, as you already mentioned. You reference the View from ViewModel. This pretty much breaks the whole thing and if you begin to reference views from ViewModel, you can skip MVVM wholly and use WPF's CodeBehind.
Also you should avoid referencing other ViewModels form your ViewModel, as this tightly couples them and makes unit-tests pretty hard.
To navigate between Views/ViewModels, one usually implement a NavigationService. You define the Interface of the NavigationService (i.e. INavigationService) in your model. But the implementation of the NavigationService happens in the Presentation Layer (i.e. the place/Project where your Views reside), since this is the only place where you can implement a NavigationService.
A navigation service is very specific to an application/platform and hence needs to be implemented for each platform a new (Desktop, WinRT, Silverlight). Same goes for the DialogService which displays Dialog messages/popups.
Related
I have a simple requirement I am trying to achieve. Basically I have a view that gets populates with a list of businesses. The property for the list of businesses is embodied in my viewmodel class which in turn is bound to the view. This is a simple MVC application with a list of business.
However, the issue I have is that I derive the list of business for another class which is a dependency to the view model, and its basically similar to a repository which I call BusinessService. Busy service is comprised of async methods and this is the dilemma I have, when the call is made from the Ctor of the viewModel or the getter of the property, my application hangs. The call is to a EF database asynchrounous too within the businessservice and am not sure what is the correct approach for this. Please see code below:
ViewModel:
#region Ctor
public BusinessListViewModel(IBusinessService businessService, IStringBuilder builder)
{
_businessService = businessService;
_builder = builder;
InitBusinesses().Wait(); //OPTION 1
}
#endregion
#region Properties
public IEnumerable<BusinessViewModel> _businesses;
public IEnumerable<BusinessViewModel> Businesses
{
get
{
if (_businesses == null)
{
InitBusinesses().Wait(); //OPTION 2
}
return _businesses;
}
set => _businesses = value;
}
private async Task InitBusinesses()
{
var response = await _businessService.Get();
Businesses = response.IsSuccessful
? response.Data.Select(p => new BusinessViewModel(_builder, p))
: new List<BusinessViewModel>();
}
BUSINESS SERVICE:
#region Service Methods
public async Task<Response<IEnumerable<Models.Business>>> Get()
{
var data = await Db.Businesses.ToListAsync();
return new Response<IEnumerable<Models.Business>>
{
IsSuccessful = true,
Message = "Successful",
Data = Mapper.Map<List<Models.Business>>(data)
};
}
Please may you advise the best pattern and the correct way to do this, I already know this is wrong> Thank you
I wrote an article on the subject.
When the UI framework asks your code to display something, it must be displayed immediately (synchronously). ViewModel constructors and data-bound properties should be synchronous and immediate. Doing network I/O is simply not an option; even if you got it working (which is possible), all that would do is block your UI thread, degrading your user experience.
A more proper solution is to synchronously initialize into a loading state ("Loading..." message, spinner, whatever) and also start the asynchronous operation. Then, when the operation completes, update the UI with the actual data.
You should consider using a factory method that returns a Task
private BusinessListViewModel(IBusinessService businessService, IStringBuilder builder)
{
_businessService = businessService;
_builder = builder;
}
public static async Task<BusinessListViewModel> Create(IBusinessService businessService, IStringBuilder builder)
{
var instance = new BusinessListViewModel(businessService, builder)
await InitBusiness();
return instance;
}
I have used Relay Commands quite a bit in WPF projects but I am currently working on Windows Workflows and I am making a custom activity with its own designer. I want a button on my design to add new fields if pressed. I have spent days trawling the internet but it seems windows workflow has defeated me. This is what I have and it does not work, if anyone knows why please help.
[Designer(typeof(MyCustomActivityDesigner))]
public sealed class MyCustomActivity : AsyncCodeActivity
{
public MyCustomActivity()
{
AddSpecificParameter = new DelegateCommand(AddParameter);
}
public DelegateCommand AddSpecificParameter { get; set; }
public void AddParameter()
{
//add value to an obervable collection
}
}
Relay command implementation:
public class DelegateCommand :ICommand
{
//Parameterless constructor needed as windows workflow serialises it
public DelegateCommand()
{ }
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
And the xaml is simply a button with a command binding but due to windows workflow weird stuff it goes through ModelItem (at least for the other bindings I have)
<Button Command="{Binding Path=ModelItem.AddSpecificParameter}"/>
So the key differences from code that I have in WPF apps that works is the parameter-less constructor but this shouldn't effect it? Tested this, parameter-less constructor works fine in WPF apps,so it has to be due to the ModelItem not being able to handle commands?
Also the ModelItem Binding path works for all the other bindable properties on the viewmodel (for example strings on labels)
Note: The activities are tested/displayed in a rehosted designer.
Background info
I'm developing a Xamarin Forms (v4.1.1.3, testing on iOS) application in XAML, using MVVM with a View first approach; I'm assigning single-instance ViewModels to Views by using the ViewModelLocator service of MVVMLight:
BindingContext="{Binding [SearchViewModel], Source={StaticResource ViewModelLocator}}"
When navigating to another page, I'm constructing a new instance of the page, which will receive the very same ViewModel instance every time.
var page = new SearchView();
var tabbedPage = Application.Current.MainPage as TabbedPage;
if (tabbedPage != null)
await tabbedPage.CurrentPage.Navigation.PushAsync(page);
The issue
I've implemented a custom control (view?), that is supposed to show search results in a tile-like layout. This control is created when navigating from a search NavigationPage to a search results ContentPage.
Every time I return to the search page and navigate back to search results, the view is reconstructed and the PropertyChanged of the BindableProperties are subscribed. These PropertyChanged events are never unsubscribed, so every time I navigate to the search results view and change the bound ViewModel property, the event is fired increasingly multiple times.
In the following code the OnItemsPropertyChanged is triggered multiple times, based on how many times I've navigated from the search view to the search results view:
public class WrapLayout : Grid
{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create("Items", typeof(IEnumerable), typeof(WrapLayout), null, propertyChanged: OnItemsPropertyChanged);
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public WrapLayout()
{
...
}
private static void OnItemsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
...
}
}
My questions:
Shouldn't the BindableProperty unsubscribe from PropertyChanged and -Changing by itself?
Does this occur because of the way I associated Views with ViewModels and/or navigate through pages?
Should I handle unsubscribing these events myself, and how?
EDIT; additional navigation info
I have a MainView TabbedPage, which creates SearchView as NavigationPage:
public MainView()
{
InitializeComponent();
Children.Add(new NavigationPage(new SearchView())
{
Title = AppResources.Tab_Search,
Icon = "tab_search"
});
}
SearchView has, upon creation, a single-instance ViewModel assigned by the ViewModelLocator that was mentioned at the start of this topic, using MVVMLight's SimpleIoc container.
When a search command in SearchView is fired, I send a request to an API which returns search results. These results are displayed on another page, to which I navigate to from the SearchView's ViewModel:
await _navigationService.NavigateTo(ViewModelLocator.PageKeyFileResults, searchResult);
Which functionality looks somewhat like this:
public async Task NavigateTo(string pagekey, object viewModelParameter)
{
var constructor = _pagesByKey[pagekey].Constructor; //Gets the Func<Page> that simple creates the requested page, without using reflection.
var page = constructor() as Page;
var viewModel = page.BindingContext as BaseViewModel;
if (viewModel != null)
viewModel.Initialize(viewModelParameter);
var tabbedPage = Application.Current.MainPage as TabbedPage;
if (tabbedPage != null)
await tabbedPage.CurrentPage.Navigation.PushAsync(page);
else
await Application.Current.MainPage.Navigation.PushAsync(page);
}
The constructed page looks somewhat like:
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Views.FileResultsView"
xmlns:pages="clr-namespace:Views.Pages;assembly=Views"
xmlns:controls="clr-namespace:Views.Controls;assembly=Views"
BindingContext="{Binding [FileResultsViewModel], Source={StaticResource ViewModelLocator}}">
<ScrollView>
<controls:WrapLayout
Items="{Binding SearchResults}" />
</ScrollView>
</pages:BaseContentPage>
Where BaseContentPage is:
public class BaseContentPage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Subscribe<DialogMessage>(this, "ShowDialog", (dialogMessage) =>
{
if (string.IsNullOrWhiteSpace(dialogMessage.AcceptButton))
DisplayAlert(dialogMessage.Title, dialogMessage.Content, dialogMessage.CancelButton);
else
DisplayAlert(dialogMessage.Title, dialogMessage.Content, dialogMessage.AcceptButton, dialogMessage.CancelButton);
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<DialogMessage>(this, "ShowDialog");
}
}
And where ViewModel is basically like this:
public class FileResultsViewModel : BaseViewModel
{
private IEnumerable<ASRow> _searchResults;
public IEnumerable<ASRow> SearchResults
{
get { return _searchResults; }
set { Set(ref _searchResults, value); }
}
internal override void Initialize(object parameter)
{
base.Initialize(parameter);
if (parameter is AdvancedSearchResponse)
{
var searchResults = parameter as AdvancedSearchResponse;
SearchResults = new List<ASRow>(searchResults.Rows);
}
}
}
Shouldn't the BindableProperty unsubscribe from PropertyChanged and -Changing by itself?
Yes - it should. If it does not it is most certainly a bug
Does this occur because of the way I associated Views with ViewModels and/or navigate trough pages?
That is most likely also an option, since i didn't experience the behaviour you described yet. You would need to share more of your surrounding setup code.
Should I handle unsubscribing these events myself, and how?
It's hard for you to always control unsubscribing, since most of the time it will be the control subscribing to events (unless you do it yourself, in which case it's always your duty to unsub again)
While it is ugly it's sometimes necessary to get a quick workaround, which in your case would be browsing how xamarin holds a list of the change delegates and manually unsubscribe them on page appearing for example.
I hope that answers your question. Feel free to comment if it does not.
Update
In your case i would debug your page base, and verify wether or not
OnDisappearing is called correctly
Your handler is gone after unsubscribe
(This is lazy but i usually unsub an event before subbing it, just to make sure such a bug does not happen, because most EventManagement services won't throw if you're trying to unsub a handler which is not registered.)
at least that's the most likely causes of your issue.
Shouldn't the BindableProperty unsubscribe from PropertyChanged and -Changing by itself?
No. The Binding class takes care of this. Not the BindableProperty.
Does this occur because of the way I associated Views with ViewModels and/or navigate through pages?
You are seeing this because you are forgetting that the Navigation Stack keeps a list of pages in memory. Since multiple pages are pointing to the same BindingContext, there are multiple observers to changes. You would not have this particular issue if you didn't re-use View Models.
Should I handle unsubscribing these events myself, and how?
No. If it is really a concern then set BindingContext to null when a page disappears, and then restore it when reappearing. Keep in mind though that this still has cost to it, especially if your UI is really busy and has lots of dynamic content that is controlled by data bindings.
I use MVVM and I have to create a ViewModel class that should load lots of data when the View is opened.
Basically, when I create the viewmodel, it should use the database and get the data.
I used this approach first:
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
//collect the data
StartLongOperation();
}
private void StartLongOperation()
{
Thread t=new Thread(...);
t.start();
}
}
It works and loads the data without blocking the UI thread.
Later I found this guideline about how to use constructor, and it does not recommend starting long operation from constructor.
√ DO minimal work in the constructor.
Constructors should not do much work other than capture the
constructor parameters. The cost of any other processing should be
delayed until required.
In my case, the data is required on opening the view.
My first idea was to use an event.
How should I avoid calling the long operation from construcTor? What is the best practice?
Miguel Castro has talked about a solution to this in one of his great Pluralsight courses. He binds to a property in the viewmodel called ViewLoaded which will obviously get bound when the view loads, this in turn will call your long running method.
So this goes in the view (Or a base class for all views to help with re-use):
public ViewBase()
{
// Programmatically bind the view-model's ViewLoaded property to the view's ViewLoaded property.
BindingOperations.SetBinding(this, ViewLoadedProperty, new Binding("ViewLoaded"));
}
public static readonly DependencyProperty ViewLoadedProperty =
DependencyProperty.Register("ViewLoaded", typeof(object), typeof(UserControlViewBase),
new PropertyMetadata(null));
And this is the ViewModel base class code:
public object ViewLoaded
{
get
{
OnViewLoaded();
return null;
}
}
protected virtual void OnViewLoaded() { }
Simply override the OnViewLoaded() method in your ViewModel and call the long running method from there.
Maybe use a factory pattern to avoid having the MainViewModel around but not populated.
public class VMFactory
{
public async Task<MainViewModel> GetVM()
{
MainViewModel vm = new MainViewModel();
await vm.LongOperation();
return vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public async Task LongOperation()
{
(...)
}
}
or perhapse better. move the long running method out of the MainViewModel to a repository or service
public class VMRepository
{
public async Task LongOperation()
{
(...)
}
public async Task<MainViewModel> GetVM()
{
MainViewModel vm = new MainViewModel();
vm.DataWhichTakesAlongTime = await LongOperation();
return vm;
}
}
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public object DataWhichTakesAlongTime { get; set; }
}
To be honest though it sounds from the conversations around this question that you are simply using the constructor as a convenient trigger for a 'LoadDataNow' command and really you should add an ICommand, bind it to something in the view (Loaded) add loading spinners and completed events etc etc
Controversially I might also suggest you add a Controller Class to instantiate the repository view and vm and call the 'LoadData' method on the view. Not very MVVM I know but essentially doing the same stuff your IoC container does without having to jump through the hoops of configuration
Avoiding calling it is simple, just split it into 2 methods; The constructor and a GetData method you call when you open the view or after you set the data context.
The why is just about managing expectation. If you hadn't written the code and were writing a new view for someone else's view model, would you expect the constructor to start accessing a database? or would you expect it just to construct a view model and you need to make a second call to initiate getting the data?
Use your view lifecycle to execute this method. You can use Tasks to simplify the execution and you can bind to other properties to show progress. Example shown using a Windows Store App view.
ViewModel:
public class MainViewModel
{
public MainViewModel()
{
this.Title = "Main view";
}
public async Task StartLongOperationAsync()
{
this.IsLoading = true;
await Task.Run(() =>
{
//do work here
});
this.IsLoading = false;
}
}
And on the View:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await ((MainViewModel)this.DataContext).StartLongOperationAsync();
}
I don't know maybe it's wrong but sometimes i make(if i need retrun parameters)
public class MainViewModel
{
public MainViewModel()
{
//set some properties
Title = "Main view";
}
public static string GetMainViewModelString()
{
var mainViewModel = new MainViewModel();
return mainViewModel.GetString();
}
public string GetString()
{
/*your code*/
}
}
and then call
var stringData = MainViewModel.GetMainViewModelString();
but when it need i call some operation from constructor
I am wondering why MVVM light is missing command with async execution? I believe there are many cases where this could be useful, so let me name one.
Let's say that our UI contains one container that contains multiple screens. User can close a particular screen or a container with multiple screens. Let's say that a user has issued a close command on the container. Container in return invokes close command on each screen, and it needs to wait for screen to be closed. This in practice can means validating data. saving, etc. For this reason we need to issue an async call to keep the UI from becoming unresponsive, and also we need to wait for task to complete, in order to continue.
So, if we have something like this in Command
public RelayCommand CloseCommand
{
get { return _closeCommand ?? _closeCommand = new RelayCommand( async () =>
{
foreach (var screen in Screens)
{
if (!await screen.CloseCommand.ExecuteAsync(null))
{
// do something
}
}
}) }
}
We could also expose additional method on screen, but in my opinion it should be task of RelayCommand, since it already exist there.
Or there is a different methodology to handle such scenario?
Probably because there are many different ways of doing it; I describe a few approaches in my MSDN article on the subject.
Asynchronous lifetime commands are especially tricky. Something like a "close" command must be carefully considered. Is there some indication that a close is in progress? What happens if the user closes more than once ("close" in particular can often be initiated by an OS or another app even if a "close button" is disabled)?
I found this being in some ways a solution to making async commands in MVVM Light.
If fact it overwrap a async method with Task.Run. Our wrapped method has to verify if it not executed twice, and catches errors from lower async executions.
private bool isLoading;
public bool IsLoading
{
get { return isLoading; }
set
{
if (value != isLoading)
{
Set(ref isLoading, value);
//Used to refresh Commands CanExecute laying on IsLoading
CommandManager.InvalidateRequerySuggested();
}
}
}
private RelayCommand loadCommand;
public RelayCommand LoadCommand
{
get
{
return loadCommand ?? (loadCommand = new RelayCommand(
() => Task.Run(LoadAsync),
() => !IsLoading
));
}
}
private async Task LoadAsync()
{
//Prevents double execution in case of many mouse clicks on button
if (IsLoading)
{
return;
}
//Assignments which need to be done on UI tread
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
IsLoading = true;
});
try
{
list = await service.LoadAsync();
...
}
catch (Exception e)
{
...
}
finally
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
IsLoading = false;
});
}
}