Die ViewModels
beinhalten die Funktionen und Eigenschaften der einzelnen Views
. Bei der Namensnennung ist der Name des ViewModels
identisch zur View
, so bleibt das Projekt übersichtlicher.
public DashboardPageViewModel() { ... } public DashboardPage() { ... }
Des Weiteren sind alle ViewModels
und Views
in logische Unterordner organisiert.
- ViewModels: Alle
ViewModels
der Hauptansichten, welche imAppShell
angewählt werden können - ViewModels\Modals: Alle
ViewModels
für modale Seiten, zu welchen navigiert werden kann - ViewModels\Pages: Alle
ViewModels
weitere statische Seiten - ViewModels\Settings: Alle
ViewModels
für die einzelnen Einstellungesseiten, welche über dieSettingsPage
aufgerufen werden können
Alle ViewModels
nutzen DependencyInjection
, hier wird beim Erstellen der aktuelle IDispatcher
und IServiceProvider
injiziert, welche später für weitere Funktionen verwendet werden können.
#region Ctor public DashboardPageViewModel(IDispatcher dispatcher, IServiceProvider provider) : base(dispatcher, provider) { Dispatcher = dispatcher; OnDataChanged = Refresh; } #endregion
Base- & AppViewModel
Unser Template bietet zwei Basisklassen. von welchen alle weiteren ViewModels
erben sollten. Dabei unterscheiden sich beide Klassen wie folgt.
BaseViewModel
Das BaseViewModel
bietet alle grundlegenden und notwendigen Eigenschaften und Funktionen, welche ein ViewModel
benötigt, um richtig zu funktionieren. Einfache Seiten, wie die Einstellungsseiten, können von dieser Klasse erben, da kein Zugriff auf die App-Inhalte nötig ist.
AppViewModel
Das AppViewModel
hingegen, welches ebenfalls vom BaseViewModel
erbt, beinhaltet zusätzlich noch allen appspezifischen Eigenschaften und Funktionen. Das könnten zum Beispiel, bei einer Aktien-App, die Depots sein. Somit sollten alle weiteren ViewModels
, welche Inhalte der App anzeigen und verarbeiten, auch vom AppViewModel
erben.
Beispiel
Als Beispiel, das AppViewModel
unserer “Aktien-Monitor-App”. Die einzelnen Funktionen wurden gekürzt, um die Datei übersichtlicher zu halten.
namespace AppBasement.ViewModels { [QueryProperty(nameof(PrimaryDepot), "primarydepot")] [QueryProperty(nameof(PrimaryWatchlist), "primarywatchlist")] [QueryProperty(nameof(SelectedDepot), "depot")] [QueryProperty(nameof(CurrentStock), "stock")] [QueryProperty(nameof(SelectedWatchlist), "watchlist")] [QueryProperty(nameof(SelectedMarketplace), "marketplace")] public partial class AppViewModel : BaseViewModel { #region QueryParameter [ObservableProperty] protected Depot primaryDepot; partial void OnPrimaryDepotChanged(Depot value) { // Binding this directly seems to cause issues with the SfCircularGauge content... TotalWorth = value?.TotalWorth ?? 0; TotalCosts = value?.TotalCosts ?? 0; Growth = value?.Growth ?? 0; if (value != null) { value.DepotChanged -= _primaryDepot_DepotChanged; value.DepotChanged += _primaryDepot_DepotChanged; } } [ObservableProperty] protected Depot selectedDepot; [ObservableProperty] protected WatchList primaryWatchlist; [ObservableProperty] protected WatchList selectedWatchlist; [ObservableProperty] protected Marketplace selectedMarketplace; [ObservableProperty] Stock currentStock; #endregion #region Properties #region Actions public Func<Task> OnDataChanged; #endregion #region States [ObservableProperty] bool hasDepot = false; [ObservableProperty] bool hasWatchlist = false; [ObservableProperty] bool isLoadingDepots = false; [ObservableProperty] bool isLoadingWatchlists = false; [ObservableProperty] double totalWorth = 0; [ObservableProperty] double totalCosts = 0; [ObservableProperty] double growth = 0; #endregion #region Forms [ObservableProperty] bool keepFormOpen = false; #endregion #region ModalPopups [ObservableProperty] bool tutorialShown = false; [ObservableProperty] bool changeInfosShown = false; #endregion #endregion #region Collections [ObservableProperty] [NotifyPropertyChangedFor(nameof(HasDepot))] ObservableCollection<Depot> depots = new(); partial void OnDepotsChanged(ObservableCollection<Depot> value) { HasDepot = value?.Count > 0; } [ObservableProperty] [NotifyPropertyChangedFor(nameof(HasWatchlist))] ObservableCollection<WatchList> watchlists = new(); partial void OnWatchlistsChanged(ObservableCollection<WatchList> value) { HasWatchlist = value?.Count > 0; } [ObservableProperty] ObservableCollection<Marketplace> marketplaces = new(); #endregion #region Constructor public AppViewModel(IDispatcher dispatcher) : base(dispatcher) { Dispatcher = dispatcher; LoadSettings(); } #endregion #region Events public void Pages_Loaded(object sender, EventArgs e) { Task.Run(OnLoadedAsync); } protected void _primaryDepot_DepotChanged(object sender, DepotChangedEventArgs e) { try { if (sender is Depot primary) { TotalWorth = primary.TotalWorth; TotalCosts = primary.TotalCosts; Growth = primary.Growth; } } catch (Exception exc) { // Log Error EventManager.Instance.LogError(exc); } } public void Depots_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(Depots)); #if CloudSync if (SettingsApp.Cloud_EnableSync) { SettingsApp.SyncToCloud(CloudSettingsIdentifier.Customers); } #endif } public void Stocks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(Depots)); #if CloudSync if (SettingsApp.Cloud_EnableSync) { SettingsApp.SyncToCloud(CloudSettingsIdentifier.Customers); } #endif } protected void CurrentDepot_DepotChanged(object sender, DepotChangedEventArgs e) { OnPropertyChanged(nameof(Depots)); #if CloudSync if (SettingsApp.Cloud_EnableSync) { SettingsApp.SyncToCloud(CloudSettingsIdentifier.Customers); } #endif } #endregion #region Commands #region Database [RelayCommand] protected async Task LoadDepotsFromDatabase() { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task LoadWatchlistsFromDatabase() { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } #endregion #region ShellNavigation [RelayCommand] protected async Task NewItem(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task EditItem(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task ViewItem(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task DeleteItem(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task ExportItem(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task AddTransactionToItem(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task AddDividendToItem(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task AddStocksToDepot(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } [RelayCommand] protected async Task ShowStockDetails(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task RefreshStockRate(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task ShowDepotDetails(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } [RelayCommand] protected async Task MakeDepotPrimary(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } #endregion #region ListView [RelayCommand] protected async Task ActionFromHold(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } [RelayCommand] protected async Task ActionFromTap(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } [RelayCommand] protected async Task ActionFromDoubleTap(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } [RelayCommand] protected void ActionWhileSwiping(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } [RelayCommand] protected async Task ActionFromSwipeGesture(object parameter) { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } IsBusy = false; } #endregion #region Wiki [RelayCommand] protected async Task OpenWikiUri(object paramter) { try { // Something to be done here... } catch (Exception exc) { // Log Error EventManager.Instance.LogError(exc); } } #endregion #endregion #region Methods #region DataLoading /// <summary> /// Loads the data needed for this input form modal. /// Attention! This code runs not on the UI Thread! /// </summary> /// <returns></returns> protected async Task OnLoadDataAsync() { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } protected async Task OnLoadWatchlistsAsync() { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } /// <summary> /// Updates the needed information from the static AppData. /// Important: This must run on the UIThread! /// </summary> protected void UpdateFromStaticAppData() { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } /// <summary> /// Updates the needed information from the static AppData. /// Important: This must run on the UIThread! /// </summary> protected void UpdateWatchlistFromStaticAppData() { try { // Something to be done here... } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } protected async Task UpdateStockRateAsync(Stock stock, int daysBack = 7) { await UpdateStockRatesAsync(new() { stock }, daysBack); } protected async Task UpdateStockRatesAsync(List<Stock> stocks, int daysBack = 7) { try { // Something to be done here... } catch (Exception exc) { //Log error EventManager.Instance.LogError(exc); } } #endregion #endregion #region Lifecycle public async Task OnLoadedAsync() { try { // Something to be done here... EventManager.Instance.LogInfo(new() { Message = $"{AppInfo.Current.Name}: {nameof(OnLoadedAsync)} called from {nameof(AppViewModel)}.", SourceName = nameof(OnLoadedAsync), }); } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } public async Task OnAppearing() { try { await Task.Delay(1); EventManager.Instance.LogInfo(new() { Message = $"{AppInfo.Current.Name}: {nameof(OnAppearing)} called from {nameof(AppViewModel)}.", SourceName = nameof(OnAppearing), }); } catch (Exception exc) { // Log error EventManager.Instance.LogError(exc); } } #endregion } }