#region Apache License 2.0
/*
Nuclex Foundation libraries for .NET
Copyright (C) 2002-2025 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.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Nuclex.Support;
using Nuclex.Avalonia.AutoBinding;
namespace Nuclex.Avalonia {
/// Manages an application's windows and views
public class WindowManager : Observable, IWindowManager {
#region class WindowManagerScope
/// Global scope that uses the WindowManager's CreateInstance()
public class WindowManagerScope : IWindowScope {
/// Initializes a new global window scope
///
/// Window manager whose CreateInstance() method will be used
///
public WindowManagerScope(WindowManager windowManager) {
this.windowManager = windowManager;
}
/// Creates an instance of the specified type in the scope
/// Type an instance will be created of
/// The created instance
object IWindowScope.CreateInstance(Type type) {
return this.windowManager.CreateInstance(type);
}
/// Does nothing because this is the global fallback scope
void IDisposable.Dispose() {}
/// WindowManager whose CreateInstance() method will be wrapped
private WindowManager windowManager;
}
#endregion // class WindwoManagerScope
#region class CancellableDisposer
/// Disposes an object that potentially implements IDisposable
private struct CancellableDisposer : IDisposable {
/// Initializes a new cancellable disposer
///
/// Object that potentially implements IDisposable
///
public CancellableDisposer(object? potentiallyDisposable = null) {
this.potentiallyDisposable = potentiallyDisposable;
}
///
/// Disposes the assigned object if the disposer has not been cancelled
///
public void Dispose() {
IDisposable? disposable = this.potentiallyDisposable as IDisposable;
if(disposable != null) {
disposable.Dispose();
}
}
/// Cancels the call to Dispose(), keeping the object alive
public void Dismiss() {
this.potentiallyDisposable = null;
}
/// Assigns a new potentially disposable object
///
/// Potentially disposable object that the disposer will dispose
///
public void Set(object? potentiallyDisposable) {
this.potentiallyDisposable = potentiallyDisposable;
}
/// Object that will be disposed unless the disposer is cancelled
private object? potentiallyDisposable;
}
#endregion // class CancellableDisposer
/// Initializes a new window manager
///
/// View model binder that will be used to bind all created views to their models
///
public WindowManager(IAutoBinder? autoBinder = null) {
this.autoBinder = autoBinder;
this.rootWindowActivatedDelegate = rootWindowActivated;
this.rootWindowClosedDelegate = rootWindowClosed;
this.windowManagerAsScope = new WindowManagerScope(this);
this.viewTypesForViewModels = new ConcurrentDictionary();
}
/// The currently active top-level or modal window
public Window? ActiveWindow {
get { return this.activeWindow; }
private set {
if(value != this.activeWindow) {
this.activeWindow = value;
OnPropertyChanged(nameof(ActiveWindow));
}
}
}
/// Opens a view as a new root window of the application
///
/// Type of view model a root window will be opened for
///
///
/// View model a window will be opened for. If null, the view model will be
/// created as well (unless the dialog already specifies one as a resource)
///
///
/// Whether the view model should be disposed when the view is closed
///
/// The window that has been opened by the window manager
public Window OpenRoot(
TViewModel? viewModel = null, bool disposeOnClose = false
) where TViewModel : class {
Window window = (Window)CreateView(viewModel);
window.Activated += this.rootWindowActivatedDelegate;
window.Closed += this.rootWindowClosedDelegate;
// If we either created the view model or the user explicitly asked us to
// dispose their view model, prepare the window so that it will dispose its
// view model when the window is done.
if((viewModel == null) || disposeOnClose) {
setupViewModelDisposal(window);
}
window.Show();
return window;
}
/// Displays a view as a modal window
///
/// Type of the view model for which a view will be displayed
///
///
/// View model a modal window will be displayed for. If null, the view model will
/// be created as well (unless the dialog already specifies one as a resource)
///
///
/// Whether the view model should be disposed when the view is closed
///
/// The return value of the modal window
public Task ShowModalAsync(
TViewModel? viewModel = null, bool disposeOnClose = false
) where TViewModel : class {
return ShowModalAsync