Began porting my MultiPageViewModel from the Windows Forms library to the Avalonia library; updated copyright statement for the year 2026

This commit is contained in:
Markus Ewald 2026-04-27 16:12:25 +02:00
commit 5175af250e
29 changed files with 497 additions and 25 deletions

View file

@ -23,8 +23,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.7" /> <PackageReference Include="Avalonia" Version="11.3.14" />
<PackageReference Include="MessageBox.Avalonia" Version="3.2.0" /> <PackageReference Include="MessageBox.Avalonia" Version="3.3.1.1" />
<PackageReference Include="Nuclex.Foundation" Version="1.3.0" /> <PackageReference Include="Nuclex.Foundation" Version="1.3.0" />
<PackageReference Include="Nullable" Version="1.3.1"> <PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ limitations under the License.
#endregion // Apache License 2.0 #endregion // Apache License 2.0
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -0,0 +1,153 @@
#region Apache License 2.0
/*
Nuclex Foundation libraries for .NET
Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Nuclex.Avalonia.Commands {
/// <summary>Asynchronous command that delegates work to an external method</summary>
/// <typeparam name="TArgument">Type of argument accepted by the command</typeparam>
internal sealed class AsyncRelayCommand<TArgument> : IAsyncCommand<TArgument> {
/// <summary>Raised when the command's executable state has changed</summary>
public event EventHandler? CanExecuteChanged;
/// <summary>Initializes a new asynchronous relay command</summary>
/// <param name="executeAsync">Action that is executed when the command runs</param>
/// <param name="canExecute">
/// Optional predicate that decides whether execution is currently allowed
/// </param>
/// <param name="allowConcurrentExecutions">
/// Whether the command may be executed while a previous execution is still running
/// </param>
public AsyncRelayCommand(
Func<TArgument, Task> executeAsync,
Predicate<TArgument>? canExecute = null,
bool allowConcurrentExecutions = false
) {
this.executeAsync = executeAsync ?? throw new ArgumentNullException(nameof(executeAsync));
this.canExecute = canExecute;
this.allowConcurrentExecutions = allowConcurrentExecutions;
}
/// <summary>Whether the command is currently executing</summary>
public bool IsRunning {
get { return (Volatile.Read(ref this.executionCount) > 0); }
}
/// <summary>Checks whether the command may currently execute</summary>
/// <param name="parameter">Parameter to be passed to the command callback</param>
/// <returns>True if command execution is currently allowed</returns>
public bool CanExecute(object? parameter) {
if((!this.allowConcurrentExecutions) && IsRunning) {
return false;
}
if(this.canExecute == null) {
return true;
}
return this.canExecute(getParameter(parameter));
}
/// <summary>Executes the command callback</summary>
/// <param name="parameter">Parameter passed to the command callback</param>
public void Execute(object? parameter) {
_ = ExecuteAsync(getParameter(parameter));
}
/// <summary>Executes the command callback asynchronously</summary>
/// <param name="parameter">Parameter passed to the command callback</param>
/// <returns>A task that finishes when command execution has completed</returns>
public async Task ExecuteAsync(TArgument parameter) {
if(!CanExecute(parameter)) {
return;
}
if(this.allowConcurrentExecutions) {
Interlocked.Increment(ref this.executionCount);
try {
await this.executeAsync(parameter).ConfigureAwait(false);
}
finally {
Interlocked.Decrement(ref this.executionCount);
}
} else {
Interlocked.Increment(ref this.executionCount);
NotifyCanExecuteChanged();
try {
await this.executeAsync(parameter).ConfigureAwait(false);
}
finally {
Interlocked.Decrement(ref this.executionCount);
NotifyCanExecuteChanged();
}
} // if concurrent executions allowed / not allowed
}
/// <summary>
/// Notifies listeners that <see cref="CanExecute(object?)" /> should be reevaluated
/// </summary>
public void NotifyCanExecuteChanged() {
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>Converts and validates the untyped command parameter</summary>
/// <param name="parameter">Untyped parameter passed through <see cref="ICommand" /></param>
/// <returns>The parameter cast to the command's parameter type</returns>
private static TArgument getParameter(object? parameter) {
if(parameter is TArgument typedParameter) {
return typedParameter;
}
if(parameter == null) {
if(default(TArgument) == null) {
return default!;
}
throw new ArgumentException(
"This command expects a non-null parameter of the configured type.",
nameof(parameter)
);
}
throw new ArgumentException(
$"This command expects a parameter of type {typeof(TArgument).FullName}.",
nameof(parameter)
);
}
/// <summary>Asynchronous callback invoked when command execution is requested</summary>
private readonly Func<TArgument, Task> executeAsync;
/// <summary>Optional callback deciding whether command execution is currently allowed</summary>
private readonly Predicate<TArgument>? canExecute;
/// <summary>Whether the command may run while a previous invocation is still active</summary>
private readonly bool allowConcurrentExecutions;
/// <summary>Number of currently active command executions</summary>
private int executionCount;
}
} // namespace Nuclex.Avalonia.Commands

View file

@ -0,0 +1,43 @@
#region Apache License 2.0
/*
Nuclex Foundation libraries for .NET
Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Nuclex.Avalonia.Commands {
/// <summary>Command that executes asynchronously</summary>
/// <typeparam name="TArgument">Type of an optional argument for the command</typeparam>
public interface IAsyncCommand<TArgument> : ICommand {
/// <summary>Whether the command is currently executing</summary>
bool IsRunning { get; }
/// <summary>Runs the command asynchronously</summary>
/// <param name="argument">Optional argument for the command</param>
/// <returns>A task that completes when the command has executed</returns>
Task ExecuteAsync(TArgument argument);
/// <summary>Triggers reevaluation of <see cref="ICommand.CanExecute(object)" /></summary>
void NotifyCanExecuteChanged();
}
} // namespace Nuclex.Avalonia.Commands

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -0,0 +1,41 @@
#region Apache License 2.0
/*
Nuclex Foundation libraries for .NET
Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using Nuclex.Avalonia.Commands;
namespace Nuclex.Avalonia.ViewModels {
/// <summary>Interface for view models that can switch between different pages</summary>
/// <typeparam name="TPageEnumeration">Enum type by which pages can be indicated</typeparam>
public interface IMultiPageViewModel<TPageEnumeration> {
/// <summary>Command to switch the active tool page</summary>
IAsyncCommand<TPageEnumeration> SwitchPageCommand { get; }
/// <summary>The currently displayed page</summary>
TPageEnumeration? ActivePage { get; }
/// <summary>View model for the page that is currently being shown</summary>
object? ActivePageViewModel { get; }
}
} // namespace Nuclex.Avalonia.ViewModels

View file

@ -0,0 +1,36 @@
#region Apache License 2.0
/*
Nuclex Foundation libraries for .NET
Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Threading.Tasks;
namespace Nuclex.Avalonia.ViewModels {
/// <summary>
/// Can be implemented by view models that wish to know when their view is loaded
/// </summary>
public interface IViewLoadListener {
/// <summary>Called when the view has finished loading</summary>
/// <returns>A task what finishes when all view load processing is done</returns>
Task OnViewLoaded();
}
} // namespace Nuclex.Avalonia.ViewModels

View file

@ -0,0 +1,36 @@
#region Apache License 2.0
/*
Nuclex Foundation libraries for .NET
Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Threading.Tasks;
namespace Nuclex.Avalonia.ViewModels {
/// <summary>
/// Can be implemented by view models that wish to know when their view is unloading
/// </summary>
public interface IViewUnloadListener {
/// <summary>Called when the view is about to unload</summary>
/// <returns>A task what finishes when all view unload processing is done</returns>
Task OnViewUnloading();
}
} // namespace Nuclex.Avalonia.ViewModels

View file

@ -0,0 +1,164 @@
#region Apache License 2.0
/*
Nuclex Foundation libraries for .NET
Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Nuclex.Avalonia.Commands;
using Nuclex.Support;
namespace Nuclex.Avalonia.ViewModels {
/// <summary>Base class for view models that have multiple child view models</summary>
/// <typeparam name="TPageEnumeration">Enum type by which pages can be indicated</typeparam>
public abstract class MultiPageViewModel<TPageEnumeration> :
Observable, IMultiPageViewModel<TPageEnumeration>, IDisposable {
/// <summary>Initializes a new multi-page view model</summary>
/// <param name="windowManager">
/// Window manager the view model uses to create child views
/// </param>
/// <param name="cachePageViewModels">
/// Whether child view models will be kept alive and reused
/// </param>
public MultiPageViewModel(IWindowManager windowManager, bool cachePageViewModels = false) {
this.windowManager = windowManager;
this.createViewModelForPageDelegate = (
new Func<TPageEnumeration, object>(CreateViewModelForPage)
);
if(cachePageViewModels) {
this.cachedViewModels = new ConcurrentDictionary<TPageEnumeration, object>();
}
SwitchPageCommand = new AsyncRelayCommand<TPageEnumeration>(switchPageAsync);
}
/// <summary>Command to switch the active view</summary>
public IAsyncCommand<TPageEnumeration> SwitchPageCommand { get; }
/// <summary>Immediately releases all resources owned by the instance</summary>
public virtual void Dispose() {
// If view models are being cached, simply dispose anything in the cache that
// implements IDisposable, the active view will be part of the cache.
if(this.cachedViewModels != null) {
foreach(object cacheViewModel in this.cachedViewModels.Values) {
disposeIfSupported(cacheViewModel);
}
this.activePageViewModel = null;
this.cachedViewModels.Clear();
} else if(this.activePageViewModel != null) { // No cache? Dispose active view.
disposeIfSupported(this.activePageViewModel);
this.activePageViewModel = null;
}
}
/// <summary>Child page that is currently being displayed by the view model</summary>
public TPageEnumeration? ActivePage {
get { return this.activePage; }
}
/// <summary>Retrieves (and, if needed, creates) the view model for the active page</summary>
/// <returns>A view model for the active page on the multi-page view model</returns>
public object? ActivePageViewModel {
get { return this.activePageViewModel; }
}
/// <summary>Windowmanager that can create view models and display other views</summary>
protected IWindowManager WindowManager {
get { return this.windowManager; }
}
/// <summary>Creates a view model for the specified page</summary>
/// <param name="page">Page for which a view model will be created</param>
/// <returns>The view model for the specified page</returns>
protected abstract object CreateViewModelForPage(TPageEnumeration page);
/// <summary>Switches to another page</summary>
/// <param name="newPage">New page to switch to</param>
/// <returns>A task that will finish when the new page has been switched to</returns>
private Task switchPageAsync(TPageEnumeration? newPage) {
if(newPage.Equals(this.activePage)) {
return Task.CompletedTask;
}
object? viewModelToDispose;
if(this.cachedViewModels == null) {
viewModelToDispose = this.activePageViewModel;
object? newPageViewModel = CreateViewModelForPage(newPage);
this.activePage = newPage;
this.activePageViewModel = newPageViewModel;
} else {
viewModelToDispose = null;
// Double-checked locking to avoid creating a view model for the same page
// multiple times if the construction takes time
object newPageViewModel;
if(!this.cachedViewModels.TryGetValue(newPage, out newPageViewModel)) {
lock(this.cachedViewModels) {
if(!this.cachedViewModels.TryGetValue(newPage, out newPageViewModel)) {
newPageViewModel = CreateViewModelForPage(newPage);
this.cachedViewModels.TryAdd(newPage, newPageViewModel);
}
}
}
this.activePage = newPage;
this.activePageViewModel = newPageViewModel;
}
OnPropertyChanged(nameof(ActivePage));
OnPropertyChanged(nameof(ActivePageViewModel));
disposeIfSupported(viewModelToDispose);
return Task.CompletedTask;
}
/// <summary>Disposes the specified object if it is disposable</summary>
/// <param name="potentiallyDisposable">Object that will be disposed if supported</param>
private static void disposeIfSupported(object? potentiallyDisposable) {
var disposable = potentiallyDisposable as IDisposable;
if(disposable != null) {
disposable.Dispose();
}
}
/// <summary>Window manager that can be used to display other views</summary>
private readonly IWindowManager windowManager;
/// <summary>Delegate for the CreateViewModelForPage() method</summary>
private readonly Func<TPageEnumeration, object> createViewModelForPageDelegate;
/// <summary>Cached page view models, if caching is enabled</summary>
private readonly ConcurrentDictionary<TPageEnumeration, object>? cachedViewModels;
/// <summary>Page that is currently active in the multi-page view model</summary>
private TPageEnumeration activePage;
/// <summary>View model for the active page</summary>
private object? activePageViewModel;
}
} // namespace Nuclex.Avalonia.ViewModels

View file

@ -1,7 +1,7 @@
#region Apache License 2.0 #region Apache License 2.0
/* /*
Nuclex Foundation libraries for .NET Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 Markus Ewald / Nuclex Development Labs Copyright (C) 2002-2026 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.