I have a MVVM WPF app. I have a window, let's say "LvWindow", with a listview that is loaded from data comming from a database. From main window "MainWindow", I have a menu, which has some options. When I select the option to access "LvWindow", it is open. Then from ViewModel, in the constructor I have a call to a database from which I request some data that then I load into the listview.
My goal is to make the process to request data from database and then load it in the listview asynchronous. I want this in order to not block the whole app, I mean, during this window is loaded, user can go to the main window menu and select to open another type of window. Windows are open in tabs.
While the process of requesting data from database and being loaded into listview in window "LvWindow", I show a splash saying "Loading" on it(in fact this is a rectangle with zindex set to a higher number to avoid user can interact with listview until it is completely loaded). This splash will be closed when the listview is loaded with the data comming from database.
So to make the process asynchronous, I know in winforms it can be done with delegates by using beginInvoke, endInvoke and callbacks methods, see here.
Also, another possibility is to use a background worker, like posted here.
So in WPF which is the best way to do it? using delegates as winforms or background workers?
ATTEMPT #1:
I have tried XANIMAX solution as this:
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
public ObservableCollection<UserData> lstUsers
public ObservableCollection<UserData> LstUsers
{
get
{
return this.lstUsers;
}
private set
{
this.lstUsers= value;
OnPropertyChanged("LstUsers");
}
}
public TestViewModel()
{
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread((p) => LstUsers = result);
}));
}
ObservableCollection<UserData> getDataFromDatabase()
{
return this.RequestDataToDatabase();
}
static void UIThread(Action<object> a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a); <---- HERE EXCEPTION IS THROWN
}
}
but in line _dispatcher.Invoke(a) an exception is thrown:
TargetParameterCountException: the parameter count mismatch
UserData is my data model, it is a class with some public properties. Something like:
public class UserData
{
public string ID{ get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// Other properties
}
so the problem is that the call to database is returning "RequestDataToDatabase" is returning a collection of UserData (ObservableCollection) so the exception is thrown.
I do not know how to solve it. Could you help me, please?
Final solution:
As XAMIMAX said in the comments:
Change the signature from static void UIThread(Action a) to static void UIThread(Action a)
modify UIThread((p) => LstUsers = result); by UIThread(() => LstUsers
= result);
As you can't await asynchronous methods in a constructor in C# 7.0 (but async Main is coming in 7.1) you can extract your async function calls to a separate function in your ViewModel and synchronously call this within your View's code-behind constructor, after you have created your ViewModel and assigned it to the View's DataContext:
public MainWindow()
{
this.vm = new MyViewModel();
this.DataContext = this.vm;
this.InitializeComponent();
this.vm.AsychronousFunctionToCallDatabase();
}
As XAMIMAX says, you want to implement a ViewModel to handle business logic between your View and your models. Then if your ViewModel implements INotifyPropertyChanged and and you set up Binding in your XAML to your properties in the ViewModel - then the display will refresh after the database call without blocking the UI thread. Note, if you have any collections populated from the database call then they should be of type ObservableCollection.
But as Kundan says, in your AsychronousFunctionToCallDatabase() function you should include an await statement or a create a Task on the line that calls the database - this will return control to the calling function (in this case, to the MainWindow constructor).
Here is one of the possible solutions for you.
In your View Model you would have something like this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace VM
{
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
List<object> ListToDisplay { get; set; }//INPC omitted for brevity
public TestViewModel()
{
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread(() => ListToDisplay = result);
}));
}
List<object> getDataFromDatabase()
{
//your logic here
return new List<object>();
}
static void UIThread(Action a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a);
}
}
}
There are couple of options to run the method asynchronously.
async await - https://msdn.microsoft.com/library/hh191443(vs.110).aspx
Task Parallel Library :https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming
Related
I have a TabbedPage which shows deliveries in progress and finished deliveries. The model for both views is the same, only the service method from where we get the data is different, so I would like to reuse the ViewModel.
Would it be a good solution to reuse the ViewModel by passing some navigation data into my InitializeAsync method that would allow me to decide which service method to use to get the data for the view?
I would override OnCurrentPageChanged in TabbedPage View's code-behind and Initialize the ViewModel from there
TabbedPageView.xaml.cs
protected override async void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
if (!(CurrentPage.BindingContext is TabbedPageViewModel tabbedPageViewModel)) return;
if (CurrentPage == DeliveriesInProgress)
{
await tabbedPageViewModel.InitializeAsync("DeliveriesInProgress");
}
else if (CurrentPage == FinishedDeliveries)
{
await tabbedPageViewModel.InitializeAsync("FinishedDeliveries");
}
}
TabbedPageViewModel.cs
public async Task InitializeAsync(object navigationData)
{
if (navigationData is string deliveryType)
{
if (deliveryType == "InProgress")
{
Deliveries = await _deliveryService.GetDeliveriesInProgress();
}
else if (deliveryType == "Finished")
{
Deliveries = await _deliveryService.GetFinishedDeliveries();
}
}
}
What could be alternative solutions?
The best way is to use two different properties in your viewmodel. Then you can bind the two different views in the tabs to the associated property.
In your viewmodel:
public ObservableCollection<MyDeliveryModel> FinishedDeliveries;
public ObservableCollection<MyDeliveryModel> DeliveriesInProgress;
Know you can add two methods to load the data for those properties:
public async Task RefreshFinishedAsync()
{
// Your logic to load the data from the service
}
public async Task RefreshInProgressAsync()
{
// Your logic to load the data from the service
}
And then in your TabbedPage-Event:
if (CurrentPage == DeliveriesInProgress)
{
await tabbedPageViewModel.RefreshInProgressAsync();
}
else if (CurrentPage == FinishedDeliveries)
{
await tabbedPageViewModel.RefreshFinishedAsync();
}
With this solution you can separate the data and you don't need to reload the whole data everytime you change the tabs. You can check if there is already some data in the collection, and if so... just don't reload the data. Just do it if the user wants it.
This improves the performance and the "wait-time" for the user.
Or as an alternative:
Load all data at once and just filter the data for the two collection-properties. This reduces the service-calls.
You can accomplish this by using a base viewmodel and a view model for each tab that uses the base. The base then holds your commands and deliveries. you bind each tabbed page to the viewmodel for that page so you won't need to check on tab changed. When you construct each viewmodel, pass in the information needed to base to know how to query the data. For each tabbed view, if the views are the same for in progress and finished, use a partial view and put it in both tabbed pages. This gives flexibility in the long run.
public class InProgressDeliveriesViewModel: BaseDeliveryViewModel{
public InProgressDeliveriesViewModel():base(filterParams){}
}
public class FinishedDeliveriesViewModel: BaseDeliveryViewModel{
public FinishedDeliveriesViewModel():base(filterParams){}
}
public class BaseDeliveryViewModel{
private FilterObjectOfSomeSort _filterParams;
public BaseDeliveryViewModel(filterParams whatever){
//use these params to filter for api calls, data. If you are calling the same
//endpoint pass up the filter
_filterParams = whatever;
}
public ObservableCollection<MyDeliveryModel> Deliveries {get;set;}
public async Task LoadDeliveries(){
//use the filter params to load correct data
var deliveries = await apiClient.GetDeliveries(filterParams); //however you
//are gathering data
}
.... All of your other commands
}
I'm newbee in mvvm (and mvvlight of course). I have 3 modelviews (a MainWindow which have a container, and another 2 modelviews (Login and Menu)). In the LoginModelView, when the user login is successfully, this call the MenuViewModel (With Messenger.Default) changing the page in the MainWindow container. All is alright until that, then i call a Message.Default.Send sending a object from LoginModelView to MenuModelView which is correctly listened, catching the object associed and executing the method associated (ConfiguraMenu) wich define a RelayCommand (checked line by line and the method is executed without any exception) but the problem is this RelayCommand is not working until i back to the LoginViewModel and i login again. I try CommandManager.InvalidateRequerySuggested() and is not working either.
This is the code for the LoginViewModel:
//This method is called when the user press the login button. No problem with this
public void ActionVerificaUsuario()
{
Miusuario = db.getUsuario(Txtusuario, Txtpassword);
if (Miusuario.esUsuario())
{
Messenger.Default.Send(new MoveToViewMessage(Page.MenuView));
Messenger.Default.Send((UsuarioModel)Miusuario);
}
}
This code is for the MenuViewModel:
public RelayCommand AbreExeClaseCommand { get; private set; }
public MenuViewModel()
{
Messenger.Default.Register<UsuarioModel>(this, usuario_recibido => {Miusuario = usuario_recibido;ConfiguraMenu(); });
}
private void ConfiguraMenu() {
Mimenu = new MenuModel(Miusuario);
AbreExeClaseCommand = new RelayCommand(() => { Messenger.Default.Send(new MoveToViewMessage(Page.NeverReachedView)); }, () => Mimenu.Sw_reportes);
CommandManager.InvalidateRequerySuggested();
AbreExeClaseCommand.RaiseCanExecuteChanged();
}
I tried to hardcode the CanExecute with true but the Execute is still without work until back and login again.
I hope you can help me (i'm scratching my head for various days with none result).
MvvmLight provides two different RelayCommand classes in two different namespaces:
Galasoft.MvvmLight.Command
Galasoft.MvvmLight.CommandWpf
Make sure, that you are using the correct namespace Galasoft.MvvmLight.CommandWpf in your WPF application.
There was a bug in MVVMLight, which resulted in not working CanExecute() behavior. They fixed it with the new .CommandWpf namespace in MVVMLight Version V5.0.2.
You can also check out this GalaSoft blog post and the change log for further information.
You try to bind the CanExecute to a propertie.
So my guess is you didn't use RaisePropertie Changed in this propertie.
You must have something like:
public class MenuModel : ViewModelBase
{
// Other pieces of code....
private bool _sw_reportes;
public bool Sw_reportes
{
get { return _sw_reportes; }
set { _sw_reportes = value;
RaisePropertyChanged(() => Sw_reportes); }
}
}
I'm using mvvmcross and xamarin to bind an ObservableCollection to a UITableView. The collection is updated in place using the Add, Remove and Move methods. These calls correctly trigger INotifyCollectionChanged events and the TableView is updated as expected the first time the view containing the table is shown. If the user navigates away from the original view as part of the normal application flow but later returns the correct data is loaded into the table but calls to add, move and remove no longer update the table.
The INotifyCollectionChanged events are still being fired when the collection is updated
If I manually subscribe to these events in my subclass of MvxStandardTableViewSource and try and call ReloadData on the UITableView still does not update
My presenter is creating a new instance of the viewmodel and view each time the page is visited.
I'm also using Xamarin-Sidebar (https://components.xamarin.com/view/sidebarnavigation) for navigation in my application with a custom presenter to load the views but as far as I can tell the view is initialised via exactly the same code path whether it's the first or subsequent visit.
My presenters Show() method looks like this:
public override void Show(MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.ClearStack))
{
MasterNavigationController.ViewControllers = new UIViewController[0];
base.Show(request);
}
else if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.LoadView))
{
var root = MasterNavigationController.TopViewController as RootViewController;
var view = this.CreateViewControllerFor(request) as UIViewController;
root.SidebarController.ChangeContentView(view);
}
}
else
{
base.Show(request);
}
}
The binding in my ViewController looks like this:
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
var source = new TracksTableSource(TableView, "TitleText Title; ImageUrl ImageUrl", ViewModel);
TableView.Source = source;
var set = this.CreateBindingSet<TracksViewController, TracksViewModel>();
set.Bind(source).To(vm => vm.PlaylistTable);
set.Apply();
}
And my viewmodel is as below where PlaylistTable is a subclass of ObservableCollection with the Update method using add, move and remove to keep the collection up to date.
public class TracksViewModel : MvxViewModel
{
private readonly IPlaylistService _playlistService;
private readonly IMessengerService _messengerService;
private readonly MvxSubscriptionToken _playlistToken;
public PlaylistTable PlaylistTable { get; set; }
public TracksViewModel(IPlaylistService playlistService, IMessengerService messengerService)
{
_playlistService = playlistService;
_messengerService = messengerService;
if (!messengerService.IsSubscribed<PlaylistUpdateMessage>(GetType().Name))
_playlistToken = _messengerService.Subscribe<PlaylistUpdateMessage>(OnDirtyPlaylist, GetType().Name);
}
public void Init(NavigationParameters parameters)
{
PlaylistTable = new PlaylistTable(parameters.PlaylistId);
UpdatePlaylist(parameters.PlaylistId);
}
public async void UpdatePlaylist(Guid playlistId)
{
var response = await _playlistService.Get(playlistId);
PlaylistTable.Update(new Playlist(response));
}
private void OnDirtyPlaylist(PlaylistUpdateMessage message)
{
UpdatePlaylist(message.PlaylistId);
}
}
This setup works perfectly the first time the view is initialised and updates the table correctly, it's only the second and subsequent times the view is initialised that the table fails to update. Can anyone explain why the binding fails when it appears the view is created using the same techniques in both instances?
I can post additional code if required but I believe the issue will be how I'm using the presenter since the code I've not posted from PlaylistTable functions correctly in unit tests and on first viewing.
I am using an MVVM style architecture and the whole application works fine. But I am introducing a scanner to the app and am now having numerous multithreading issues. The following is just some pseudocode but is basically how I need it to work:
View.xaml
<DataGrid ItemsSource="{Binding MyList}"/>
View.xaml.cs
class View : UserControl
{
public View()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
ViewModel.cs
class ViewModel
{
private Scanner scanner;//this is my scanner, duh
public ViewModel()
{
scanner = new Scanner();
scanner.ScanEvent += ScanEvent;
//all this does is when the scanner scans something
//then it will trigger an event looking for method ScanEvent()
}
public ObservableCollection<string> MyList{ get; set; }
public void ScanEvent()
{
string strBarcode = scanner.strBarcode;
MyList.Insert(0, strBarcode);//this is where the error is thrown
}
}
The error that is thrown is This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread. The scanner works fine when I set it to one of my objects so I don't understand why I can't do the same with this ObservableCollection? Here is a snippet from my scanner class that deals with the event:
Scanner.cs
internal class Scanner
{
public delegate void EventHandler();
public event EventHandler ScanEvent = delegate { };
public Scanner()
{
m_pCoreScanner.BarcodeEvent += new _ICoreScannerEvents_BarcodeEventEventHandler(OnBarcodeEvent);
RegisterForEvents();
}
public void OnBarcodeEvent(short eventType, ref string scanData)
{
strBarcode = GetBarcodeFromXml(scanData);
ScanEvent();
}
//this class is huge, so I only included pertinent code
}
As the exception message says, you'll have to update the ObservableCollection in the UI (or Dispatcher) thread, because a UI element's property (DataGrid.ItemsSource) is bound to the collection.
Try this:
public void ScanEvent()
{
string strBarcode = scanner.strBarcode;
Application.Current.Dispatcher.Invoke(
new Action(() => MyList.Insert(0, strBarcode)));
}
If you want to avoid doing Dispatcher.Invoke everywhere when wanting to get back to the UI thread - which I agree is fugly and is not very helpful for testing you could try using Reactive Extensions (Rx .Net).
Using Rx you can handle the event processing in a 'push' manner instead of the standard .Net events way (pull model). This will then allow you to compose LINQ style queries over the event\data and importantly in your case handle the scheduling back onto the dispatcher (UI) thread.
Below is the code above re-written to use Rx instead of the standard .Net event model:
.Net framework version = 4.0 (WPF)
Rx version = 2.0.21103.1
There are newer version of the Rx framework available on nuGet, you will also need to include Rx-WPF if you're going to use them with WPF.
internal class Scanner
{
public IObservable<EventArgs> ScanEvent
{
get
{
return Observable.FromEventPattern<EventHandler, EventArgs>(
h => m_pCoreScanner.BarcodeEvent += h, h => m_pCoreScanner.BarcodeEvent -= h)
.Select(x => x.EventArgs);
}
}
}
class ViewModel : IDisposable
{
private Scanner scanner;
private IDisposable _disposable;
public ViewModel()
{
scanner = new Scanner();
MyList = new ObservableCollection<string>();
_disposable = scanner.ScanEvent
.ObserveOn(DispatcherScheduler.Current)
.Subscribe(x =>
{
string strBarcode = scanner.strBarcode;
MyList.Insert(0, strBarcode);
});
}
public void Dispose()
{
_disposable.Dispose();
}
public ObservableCollection<string> MyList { get; set; }
}
A good place to start with learning Rx is this free online e-book - http://www.introtorx.com/
If you use a framework such as MvvmLight, you could leverage the message mechanisms. You register a handler in your VM and then send the message from your scanner library
http://wbsimms.com/mvvm-light-toolkit-messaging-example/
These messages work across assemblies via a Default singleton, but all depends on whether you're scanner library is customisable.
I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}