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
ViewModelsder Hauptansichten, welche imAppShellangewählt werden können - ViewModels\Modals: Alle
ViewModelsfür modale Seiten, zu welchen navigiert werden kann - ViewModels\Pages: Alle
ViewModelsweitere statische Seiten - ViewModels\Settings: Alle
ViewModelsfür die einzelnen Einstellungesseiten, welche über dieSettingsPageaufgerufen 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;
}
#endregionBase- & 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
}
}