Initial porting attempt of my WinForms MVVM library to Avalonia; added IWindowScope as a new reature because this library should support Microsoft's dependency injector
This commit is contained in:
		
						commit
						225da0d7e9
					
				
					 16 changed files with 1512 additions and 0 deletions
				
			
		
							
								
								
									
										34
									
								
								Nuclex.Avalonia (netstandard-2.0).csproj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Nuclex.Avalonia (netstandard-2.0).csproj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					<Project Sdk="Microsoft.NET.Sdk">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <PropertyGroup>
 | 
				
			||||||
 | 
					    <TargetFramework>netstandard2.0</TargetFramework>
 | 
				
			||||||
 | 
					    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
 | 
				
			||||||
 | 
					    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 | 
				
			||||||
 | 
					    <GenerateDocumentationFile>True</GenerateDocumentationFile>
 | 
				
			||||||
 | 
					    <AssemblyName>Nuclex.Avalonia</AssemblyName>
 | 
				
			||||||
 | 
					    <RootNamespace>Nuclex.Avalonia</RootNamespace>
 | 
				
			||||||
 | 
					    <IntermediateOutputPath>obj\source</IntermediateOutputPath>
 | 
				
			||||||
 | 
					    <Nullable>enable</Nullable>
 | 
				
			||||||
 | 
					    <LangVersion>8.0</LangVersion>
 | 
				
			||||||
 | 
					  </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <Compile Remove="Tests\**" />
 | 
				
			||||||
 | 
					    <EmbeddedResource Remove="Tests\**" />
 | 
				
			||||||
 | 
					    <None Remove="Tests\**" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <None Remove=".git" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <PackageReference Include="Avalonia" Version="11.3.1" />
 | 
				
			||||||
 | 
					    <PackageReference Include="MessageBox.Avalonia" Version="3.2.0" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <ProjectReference Include="..\Nuclex.Support\Nuclex.Support (netstandard-2.0).csproj" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</Project>
 | 
				
			||||||
							
								
								
									
										33
									
								
								Nuclex.Avalonia.Tests (netstandard-2.0).csproj
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Nuclex.Avalonia.Tests (netstandard-2.0).csproj
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					<Project Sdk="Microsoft.NET.Sdk">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <PropertyGroup>
 | 
				
			||||||
 | 
					    <TargetFramework>netstandard2.0</TargetFramework>
 | 
				
			||||||
 | 
					    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
 | 
				
			||||||
 | 
					    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 | 
				
			||||||
 | 
					    <GenerateDocumentationFile>True</GenerateDocumentationFile>
 | 
				
			||||||
 | 
					    <AssemblyName>Nuclex.Support.Tests</AssemblyName>
 | 
				
			||||||
 | 
					    <RootNamespace>Nuclex.Support.Tests</RootNamespace>
 | 
				
			||||||
 | 
					    <IntermediateOutputPath>obj\tests</IntermediateOutputPath>
 | 
				
			||||||
 | 
					  </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <Compile Remove="Source\**" />
 | 
				
			||||||
 | 
					    <EmbeddedResource Remove="Source\**" />
 | 
				
			||||||
 | 
					    <None Remove="Source\**" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <None Remove=".git" />
 | 
				
			||||||
 | 
					    <None Remove="packages.config" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <PackageReference Include="Moq" Version="4.20.70" />
 | 
				
			||||||
 | 
					    <PackageReference Include="NUnit" Version="3.14.0" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <ProjectReference Include="Nuclex.Avalonia (netstandard-2.0).csproj" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</Project>
 | 
				
			||||||
							
								
								
									
										50
									
								
								Properties/AssemblyInfo.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								Properties/AssemblyInfo.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					#region Apache License 2.0
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Nuclex .NET Framework
 | 
				
			||||||
 | 
					Copyright (C) 2002-2024 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.Reflection;
 | 
				
			||||||
 | 
					using System.Runtime.CompilerServices;
 | 
				
			||||||
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// General Information about an assembly is controlled through the following 
 | 
				
			||||||
 | 
					// set of attributes. Change these attribute values to modify the information
 | 
				
			||||||
 | 
					// associated with an assembly.
 | 
				
			||||||
 | 
					[assembly: AssemblyTitle("Nuclex.Avalonia")]
 | 
				
			||||||
 | 
					[assembly: AssemblyProduct("Nuclex.Avalonia")]
 | 
				
			||||||
 | 
					[assembly: AssemblyDescription("Lean and elegant MVVM library with extras for Avalonia")]
 | 
				
			||||||
 | 
					[assembly: AssemblyCompany("Nuclex Development Labs")]
 | 
				
			||||||
 | 
					[assembly: AssemblyCopyright("Copyright © Markus Ewald / Nuclex Development Labs 2002-2025")]
 | 
				
			||||||
 | 
					[assembly: AssemblyTrademark("")]
 | 
				
			||||||
 | 
					[assembly: AssemblyCulture("")]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setting ComVisible to false makes the types in this assembly not visible 
 | 
				
			||||||
 | 
					// to COM components.  If you need to access a type in this assembly from 
 | 
				
			||||||
 | 
					// COM, set the ComVisible attribute to true on that type.
 | 
				
			||||||
 | 
					[assembly: ComVisible(false)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The following GUID is for the ID of the typelib if this project is exposed to COM
 | 
				
			||||||
 | 
					[assembly: Guid("08254ad9-394e-4638-8412-098c8c4a4c39")]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Version information for an assembly consists of the following four values:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//      Major Version
 | 
				
			||||||
 | 
					//      Minor Version 
 | 
				
			||||||
 | 
					//      Build Number
 | 
				
			||||||
 | 
					//      Revision
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					[assembly: AssemblyVersion("1.3.0.0")]
 | 
				
			||||||
							
								
								
									
										62
									
								
								Source/AutoBinding/ConventionBinder.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Source/AutoBinding/ConventionBinder.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					#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 Avalonia.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia.AutoBinding {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>
 | 
				
			||||||
 | 
					  ///   Binds a view to its model using a convention-over-configuration approach
 | 
				
			||||||
 | 
					  /// </summary>
 | 
				
			||||||
 | 
					  public class ConventionBinder : IAutoBinder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Binds the specified view to an explicitly selected view model</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of view model the view will be bound to
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="view">View that will be bound to a view model</param>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">View model the view will be bound to</param>
 | 
				
			||||||
 | 
					    public void Bind<TViewModel>(Control view, TViewModel? viewModel)
 | 
				
			||||||
 | 
					      where TViewModel : class {
 | 
				
			||||||
 | 
					      if(viewModel != null) {
 | 
				
			||||||
 | 
					        bind(view, viewModel);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    ///   Binds the specified view to the view model specified in its DataContext
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    /// <param name="viewControl">View that will be bound</param>
 | 
				
			||||||
 | 
					    public void Bind(Control viewControl) {
 | 
				
			||||||
 | 
					      if(viewControl.DataContext != null) {
 | 
				
			||||||
 | 
					        bind(viewControl, viewControl.DataContext);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Binds a view to a view model</summary>
 | 
				
			||||||
 | 
					    /// <param name="view">View that will be bound</param>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">View model the view will be bound to</param>
 | 
				
			||||||
 | 
					    private void bind(Control view, object viewModel) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia.AutoBinding
 | 
				
			||||||
							
								
								
									
										46
									
								
								Source/AutoBinding/IAutoBinder.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Source/AutoBinding/IAutoBinder.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					#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 Avalonia.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia.AutoBinding {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Binds views to their view models</summary>
 | 
				
			||||||
 | 
					  public interface IAutoBinder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Binds the specified view to an explicitly selected view model</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of view model the view will be bound to
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="view">View that will be bound to a view model</param>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">View model the view will be bound to</param>
 | 
				
			||||||
 | 
					    void Bind<TViewModel>(Control view, TViewModel? viewModel)
 | 
				
			||||||
 | 
					      where TViewModel : class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    ///   Binds the specified view to the view model specified in its DataContext
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    /// <param name="view">View that will be bound</param>
 | 
				
			||||||
 | 
					    void Bind(Control view);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia.AutoBinding
 | 
				
			||||||
							
								
								
									
										35
									
								
								Source/IActiveWindowTracker.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Source/IActiveWindowTracker.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					#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 Avalonia.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Allows the currently active top level window to be looked up</summary>
 | 
				
			||||||
 | 
					  public interface IActiveWindowTracker {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>The currently active top-level or modal window</summary>
 | 
				
			||||||
 | 
					    Window? ActiveWindow { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										114
									
								
								Source/IWindowManager.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								Source/IWindowManager.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,114 @@
 | 
				
			||||||
 | 
					#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.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Avalonia.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Manages open windows and connecting them to view models</summary>
 | 
				
			||||||
 | 
					  public interface IWindowManager : IActiveWindowTracker {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Opens a view as a new root window of the application</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of view model a root window will be opened for
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   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)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="disposeOnClose">
 | 
				
			||||||
 | 
					    ///   Whether the view model should be disposed when the view is closed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The window that has been opened by the window manager</returns>
 | 
				
			||||||
 | 
					    Window OpenRoot<TViewModel>(
 | 
				
			||||||
 | 
					      TViewModel? viewModel = null, bool disposeOnClose = true
 | 
				
			||||||
 | 
					    ) where TViewModel : class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays a view as a modal window</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of the view model for which a view will be displayed
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   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)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="disposeOnClose">
 | 
				
			||||||
 | 
					    ///   Whether the view model should be disposed when the view is closed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The return value of the modal window</returns>
 | 
				
			||||||
 | 
					    Task ShowModalAsync<TViewModel>(
 | 
				
			||||||
 | 
					      TViewModel? viewModel = null, bool disposeOnClose = false
 | 
				
			||||||
 | 
					    ) where TViewModel : class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays a view as a modal window</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TResult">
 | 
				
			||||||
 | 
					    ///   Type of result the modal dialog will return to the caller
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of the view model for which a view will be displayed
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   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)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="disposeOnClose">
 | 
				
			||||||
 | 
					    ///   Whether the view model should be disposed when the view is closed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The return value of the modal window</returns>
 | 
				
			||||||
 | 
					    Task<TResult> ShowModalAsync<TResult, TViewModel>(
 | 
				
			||||||
 | 
					      TViewModel? viewModel = null, bool disposeOnClose = true
 | 
				
			||||||
 | 
					    ) where TViewModel : class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Creates the view for the specified view model</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of view model for which a view will be created
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   View model a view will be created for. If null, the view model will be
 | 
				
			||||||
 | 
					    ///   created as well (unless the dialog already specifies one as a resource)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The view for the specified view model</returns>
 | 
				
			||||||
 | 
					    Control CreateView<TViewModel>(TViewModel? viewModel = null)
 | 
				
			||||||
 | 
					      where TViewModel : class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Creates a view model without a matching view</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">Type of view model that will be created</typeparam>
 | 
				
			||||||
 | 
					    /// <returns>The new view model</returns>
 | 
				
			||||||
 | 
					    /// <remarks>
 | 
				
			||||||
 | 
					    ///   <para>
 | 
				
			||||||
 | 
					    ///     This is useful if a view model needs to create child view models (i.e. paged container
 | 
				
			||||||
 | 
					    ///     and wants to ensure the same dependency injector (if any) if used as the window
 | 
				
			||||||
 | 
					    ///     manager uses for other view models it creates.
 | 
				
			||||||
 | 
					    ///   </para>
 | 
				
			||||||
 | 
					    ///   <para>
 | 
				
			||||||
 | 
					    ///     This way, view models can set up their child view models without having to immediately
 | 
				
			||||||
 | 
					    ///     bind a view to them. Later on, views can use the window manager to create a matching
 | 
				
			||||||
 | 
					    ///     child view and store it in a container.
 | 
				
			||||||
 | 
					    ///   </para>
 | 
				
			||||||
 | 
					    /// </remarks>
 | 
				
			||||||
 | 
					    TViewModel CreateViewModel<TViewModel>()
 | 
				
			||||||
 | 
					      where TViewModel : class;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										64
									
								
								Source/IWindowScope.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								Source/IWindowScope.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,64 @@
 | 
				
			||||||
 | 
					#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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Constructs views and view model in a scope</summary>
 | 
				
			||||||
 | 
					  /// <remarks>
 | 
				
			||||||
 | 
					  ///   <para>
 | 
				
			||||||
 | 
					  ///     By default the <see cref="WindowManager" /> uses its own
 | 
				
			||||||
 | 
					  ///     <see cref="WindowManager.CreateInstance" /> method to construct views
 | 
				
			||||||
 | 
					  ///     and view models via <see cref="Activator.CreateInstance(Type)" />,
 | 
				
			||||||
 | 
					  ///     which is enough to create forms (which the Windows Forms designer already
 | 
				
			||||||
 | 
					  ///     requires to have parameterless constructors) and view models, so long as
 | 
				
			||||||
 | 
					  ///     they also have parameterless constructors.
 | 
				
			||||||
 | 
					  ///   </para>
 | 
				
			||||||
 | 
					  ///   <para>
 | 
				
			||||||
 | 
					  ///     To support dependency injection via constructor parameters, you can
 | 
				
			||||||
 | 
					  ///     inherit from the <see cref="WindowManager" /> and provide your own override
 | 
				
			||||||
 | 
					  ///     of <see cref="WindowManager.CreateInstance" /> that constructs the required
 | 
				
			||||||
 | 
					  ///     instance via your dependency injector. This is decent until you have multiple
 | 
				
			||||||
 | 
					  ///     view models all accessing the same resource (i.e. a database) via threads.
 | 
				
			||||||
 | 
					  ///   </para>
 | 
				
			||||||
 | 
					  ///   <para>
 | 
				
			||||||
 | 
					  ///     In this final case, "scopes" have become a common solution. Each
 | 
				
			||||||
 | 
					  ///     scope has access to singleton services (these exist for the lifetime of
 | 
				
			||||||
 | 
					  ///     the entire application), but there are also scoped services which will have
 | 
				
			||||||
 | 
					  ///     new instances constructed within each scope. By implementing the
 | 
				
			||||||
 | 
					  ///     <see cref="WindowManager.CreateWindowScope" /> method, you can make
 | 
				
			||||||
 | 
					  ///     the window manager set up an implicit scope per window or dialog.
 | 
				
			||||||
 | 
					  ///   </para>
 | 
				
			||||||
 | 
					  /// </remarks>
 | 
				
			||||||
 | 
					  public interface IWindowScope : IDisposable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Creates an instance of the specified type in the scope</summary>
 | 
				
			||||||
 | 
					    /// <param name="type">Type an instance will be created of</param>
 | 
				
			||||||
 | 
					    /// <returns>The created instance</returns>
 | 
				
			||||||
 | 
					    /// <remarks>
 | 
				
			||||||
 | 
					    ///   Use this to wire up your dependency injection container. By default,
 | 
				
			||||||
 | 
					    ///   the Activator class will be used to create instances which only works
 | 
				
			||||||
 | 
					    ///   if all of your view models are concrete classes.
 | 
				
			||||||
 | 
					    /// </remarks>
 | 
				
			||||||
 | 
					    object CreateInstance(Type type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia
 | 
				
			||||||
							
								
								
									
										188
									
								
								Source/Messages/AvaloniaMessagePresenter.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								Source/Messages/AvaloniaMessagePresenter.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,188 @@
 | 
				
			||||||
 | 
					#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.Threading.Tasks;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					using System.Runtime.Versioning;
 | 
				
			||||||
 | 
					#if NET6_0_OR_GREATER
 | 
				
			||||||
 | 
					using System.Runtime.Versioning;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Avalonia.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using MessageBoxIcon = MsBox.Avalonia.Enums.Icon;
 | 
				
			||||||
 | 
					using MessageBoxButtons = MsBox.Avalonia.Enums.ButtonEnum;
 | 
				
			||||||
 | 
					using MessageDialogResult = MsBox.Avalonia.Enums.ButtonResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia.Messages {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Uses Avalonia to display message boxes</summary>
 | 
				
			||||||
 | 
					  public class AvaloniaMessagePresenter : IMessageService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #region class MessageScope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Triggers the message displayed and acknowledged events</summary>
 | 
				
			||||||
 | 
					    private class MessageScope : IDisposable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>
 | 
				
			||||||
 | 
					      ///   Initializes a new message scope, triggering the message displayed event
 | 
				
			||||||
 | 
					      /// </summary>
 | 
				
			||||||
 | 
					      /// <param name="self">Message service the scope belongs to</param>
 | 
				
			||||||
 | 
					      /// <param name="image">Image of the message being displayed</param>
 | 
				
			||||||
 | 
					      /// <param name="text">Text contained in the message being displayed</param>
 | 
				
			||||||
 | 
					      public MessageScope(
 | 
				
			||||||
 | 
					        AvaloniaMessagePresenter self, MessageBoxIcon image, MessageText text
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        EventHandler<MessageEventArgs>? messageDisplayed = self.MessageDisplaying;
 | 
				
			||||||
 | 
					        if(messageDisplayed != null) {
 | 
				
			||||||
 | 
					          messageDisplayed(this, new MessageEventArgs(image, text));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.self = self;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Triggers the message acknowledged event</summary>
 | 
				
			||||||
 | 
					      public void Dispose() {
 | 
				
			||||||
 | 
					        EventHandler? messageAcknowledged = self.MessageAcknowledged;
 | 
				
			||||||
 | 
					        if(messageAcknowledged != null) {
 | 
				
			||||||
 | 
					          messageAcknowledged(this, EventArgs.Empty);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Message service the scope belongs to</summary>
 | 
				
			||||||
 | 
					      private AvaloniaMessagePresenter self;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #endregion // class MessageScope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Triggered when a message is displayed to the user</summary>
 | 
				
			||||||
 | 
					    public event EventHandler<MessageEventArgs>? MessageDisplaying;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Triggered when the user has acknowledged the current message</summary>
 | 
				
			||||||
 | 
					    public event EventHandler? MessageAcknowledged;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Initializes a new Avalonia message service</summary>
 | 
				
			||||||
 | 
					    /// <param name="tracker">Used to determine the current top-level window</param>
 | 
				
			||||||
 | 
					    public AvaloniaMessagePresenter(IActiveWindowTracker tracker) {
 | 
				
			||||||
 | 
					      this.tracker = tracker;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Asks the user a question that can be answered via several buttons</summary>
 | 
				
			||||||
 | 
					    /// <param name="image">Image that will be shown on the message box</param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be shown to the user</param>
 | 
				
			||||||
 | 
					    /// <param name="buttons">Buttons available for the user to click on</param>
 | 
				
			||||||
 | 
					    /// <returns>The button the user has clicked on</returns>
 | 
				
			||||||
 | 
					    public Task<MessageDialogResult> ShowQuestionAsync(
 | 
				
			||||||
 | 
					      MessageBoxIcon image, MessageText text, MessageBoxButtons buttons
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      using(var scope = new MessageScope(this, image, text)) {
 | 
				
			||||||
 | 
					        MsBox.Avalonia.Base.IMsBox<MessageDialogResult> messageBox = (
 | 
				
			||||||
 | 
					          MsBox.Avalonia.MessageBoxManager.GetMessageBoxStandard(
 | 
				
			||||||
 | 
					            new MsBox.Avalonia.Dto.MessageBoxStandardParams() {
 | 
				
			||||||
 | 
					              ContentTitle = text.Caption,
 | 
				
			||||||
 | 
					              ContentHeader = text.Message,
 | 
				
			||||||
 | 
					              ContentMessage = text.Details ?? string.Empty,
 | 
				
			||||||
 | 
					              ButtonDefinitions = buttons,
 | 
				
			||||||
 | 
					              Icon = image,
 | 
				
			||||||
 | 
					              WindowStartupLocation = WindowStartupLocation.CenterOwner
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return messageBox.ShowAsync(); // TODO: Make modal to current or main window
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays a notification to the user</summary>
 | 
				
			||||||
 | 
					    /// <param name="image">Image that will be shown on the message bx</param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be shown to the user</param>
 | 
				
			||||||
 | 
					    public Task ShowNotificationAsync(MessageBoxIcon image, MessageText text) {
 | 
				
			||||||
 | 
					      using(var scope = new MessageScope(this, image, text)) {
 | 
				
			||||||
 | 
					        MsBox.Avalonia.Base.IMsBox<MessageDialogResult> messageBox = (
 | 
				
			||||||
 | 
					          MsBox.Avalonia.MessageBoxManager.GetMessageBoxStandard(
 | 
				
			||||||
 | 
					            new MsBox.Avalonia.Dto.MessageBoxStandardParams() {
 | 
				
			||||||
 | 
					              ContentTitle = text.Caption,
 | 
				
			||||||
 | 
					              ContentHeader = text.Message,
 | 
				
			||||||
 | 
					              ContentMessage = text.Details ?? string.Empty,
 | 
				
			||||||
 | 
					              ButtonDefinitions = MessageBoxButtons.Ok,
 | 
				
			||||||
 | 
					              Icon = image,
 | 
				
			||||||
 | 
					              WindowStartupLocation = WindowStartupLocation.CenterOwner
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Window? activeWindow = this.tracker.ActiveWindow;
 | 
				
			||||||
 | 
					        if(activeWindow == null) {
 | 
				
			||||||
 | 
					          return messageBox.ShowAsync();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          //return messageBox.ShowAsPopupAsync(activeWindow);
 | 
				
			||||||
 | 
					          return messageBox.ShowWindowDialogAsync(activeWindow);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Reports an error using the system's message box functions</summary>
 | 
				
			||||||
 | 
					    /// <param name="title">Title of the message box</param>
 | 
				
			||||||
 | 
					    /// <param name="message">Message text that will be displayed</param>
 | 
				
			||||||
 | 
					    public static void FallbackReportError(string title, string message) {
 | 
				
			||||||
 | 
					      // TODO: Escape quotes for the command-line tools
 | 
				
			||||||
 | 
					      // TODO: Wait for the child process to exit so display is certain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
 | 
				
			||||||
 | 
					        MessageBoxW(IntPtr.Zero, message, title, MB_OK | MB_ICONEXCLAMATION);
 | 
				
			||||||
 | 
					      } else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
 | 
				
			||||||
 | 
					        Process.Start("zenity", $"--error --title=\"{title}\" --text=\"{message}\"");
 | 
				
			||||||
 | 
					      } else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
 | 
				
			||||||
 | 
					        Process.Start("osascript", $"-e 'display dialog \"{message}\" with title \"{title}\" with icon stop'");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Windows only: display a message box with an OK button</summary>
 | 
				
			||||||
 | 
					#if NET6_0_OR_GREATER
 | 
				
			||||||
 | 
					  [SupportedOSPlatform("windows")]
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    private const uint MB_OK = 0x00000000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Windows only: display a message box with an Exclamation icon</summary>
 | 
				
			||||||
 | 
					#if NET6_0_OR_GREATER
 | 
				
			||||||
 | 
					  [SupportedOSPlatform("windows")]
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    private const uint MB_ICONEXCLAMATION = 0x00000030;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Windows only: displays a native Windows message box</summary>
 | 
				
			||||||
 | 
					    /// <param name="parentWindowHandle">Handle of the window that owns the message box</param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that should be in the message box</param>
 | 
				
			||||||
 | 
					    /// <param name="caption">Caption or window title of the message box</param>
 | 
				
			||||||
 | 
					    /// <param name="type">Which icons and buttons that message box should have</param>
 | 
				
			||||||
 | 
					    /// <returns>How the user closed the message box and which button they clicked</returns>
 | 
				
			||||||
 | 
					    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
 | 
				
			||||||
 | 
					#if NET6_0_OR_GREATER
 | 
				
			||||||
 | 
					  [SupportedOSPlatform("windows")]
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    private static extern int MessageBoxW(IntPtr parentWindowHandle, string text, string caption, uint type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // <summary>Provides the currently active top-level window</summary>
 | 
				
			||||||
 | 
					    private IActiveWindowTracker tracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia.Messages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										58
									
								
								Source/Messages/IMessageService.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								Source/Messages/IMessageService.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					#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.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using MessageBoxIcon = MsBox.Avalonia.Enums.Icon;
 | 
				
			||||||
 | 
					using MessageBoxButtons = MsBox.Avalonia.Enums.ButtonEnum;
 | 
				
			||||||
 | 
					using MessageDialogResult = MsBox.Avalonia.Enums.ButtonResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia.Messages {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Performs simple user interaction</summary>
 | 
				
			||||||
 | 
					  /// <remarks>
 | 
				
			||||||
 | 
					  ///   Methods provided by this service can be covered using plain old message boxes
 | 
				
			||||||
 | 
					  ///   and do not require special dialogs or calls to the task dialog API.
 | 
				
			||||||
 | 
					  /// </remarks>
 | 
				
			||||||
 | 
					  public interface IMessageService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Triggered when a message is about to be displayed to the user</summary>
 | 
				
			||||||
 | 
					    event EventHandler<MessageEventArgs> MessageDisplaying;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Triggered when the user has acknowledged the current message</summary>
 | 
				
			||||||
 | 
					    event EventHandler MessageAcknowledged;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Asks the user a question that can be answered via several buttons</summary>
 | 
				
			||||||
 | 
					    /// <param name="image">Image that will be shown on the message box</param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be shown to the user</param>
 | 
				
			||||||
 | 
					    /// <param name="buttons">Buttons available for the user to click on</param>
 | 
				
			||||||
 | 
					    /// <returns>The button the user has clicked on</returns>
 | 
				
			||||||
 | 
					    Task<MessageDialogResult> ShowQuestionAsync(
 | 
				
			||||||
 | 
					      MessageBoxIcon image, MessageText text, MessageBoxButtons buttons
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays a notification to the user</summary>
 | 
				
			||||||
 | 
					    /// <param name="image">Image that will be shown on the message bx</param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be shown to the user</param>
 | 
				
			||||||
 | 
					    Task ShowNotificationAsync(MessageBoxIcon image, MessageText text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia.Messages
 | 
				
			||||||
							
								
								
									
										54
									
								
								Source/Messages/MessageEventArgs.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								Source/Messages/MessageEventArgs.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					#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 MessageBoxIcon = MsBox.Avalonia.Enums.Icon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia.Messages {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Provides a displayed message and its severity to event subscribers</summary>
 | 
				
			||||||
 | 
					  public class MessageEventArgs : EventArgs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Initializes a new message box event argument container</summary>
 | 
				
			||||||
 | 
					    /// <param name="image">Image the message box will be displaying</param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be displayed in the message box</param>
 | 
				
			||||||
 | 
					    public MessageEventArgs(MessageBoxIcon image, MessageText text) {
 | 
				
			||||||
 | 
					      this.image = image;
 | 
				
			||||||
 | 
					      this.text = text;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Image that indicates the severity of the message being displayed</summary>
 | 
				
			||||||
 | 
					    public MessageBoxIcon Image {
 | 
				
			||||||
 | 
					      get { return this.image; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Text that is being displayed in the message box</summary>
 | 
				
			||||||
 | 
					    public MessageText Text {
 | 
				
			||||||
 | 
					      get { return this.text; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Image that indicates the severity of the message being displayed</summary>
 | 
				
			||||||
 | 
					    private MessageBoxIcon image;
 | 
				
			||||||
 | 
					    /// <summary>Text that is being displayed in the message box</summary>
 | 
				
			||||||
 | 
					    private MessageText text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia.Messages
 | 
				
			||||||
							
								
								
									
										117
									
								
								Source/Messages/MessageServiceHelper.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								Source/Messages/MessageServiceHelper.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,117 @@
 | 
				
			||||||
 | 
					#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.Threading.Tasks;
 | 
				
			||||||
 | 
					#if NET6_0_OR_GREATER
 | 
				
			||||||
 | 
					using System.Runtime.Versioning;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using MessageBoxIcon = MsBox.Avalonia.Enums.Icon;
 | 
				
			||||||
 | 
					using MessageBoxButtons = MsBox.Avalonia.Enums.ButtonEnum;
 | 
				
			||||||
 | 
					using MessageDialogResult = MsBox.Avalonia.Enums.ButtonResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia.Messages {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Contains helper methods for the message service</summary>
 | 
				
			||||||
 | 
					#if NET6_0_OR_GREATER
 | 
				
			||||||
 | 
					  [SupportedOSPlatform("windows")]
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  public static class MessageServiceHelper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Asks the user a question that can be answered with yes or no</summary>
 | 
				
			||||||
 | 
					    /// <param name="messageService">
 | 
				
			||||||
 | 
					    ///   Message service that will be used to display the question
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be shown on the message box</param>
 | 
				
			||||||
 | 
					    /// <returns>The button the user has clicked on</returns>
 | 
				
			||||||
 | 
					    public static Task<MessageDialogResult> AskYesNoAsync(
 | 
				
			||||||
 | 
					      this IMessageService messageService, MessageText text
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return messageService.ShowQuestionAsync(
 | 
				
			||||||
 | 
					        MessageBoxIcon.Question, text, MessageBoxButtons.YesNo
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Asks the user a question that can be answered with ok or cancel</summary>
 | 
				
			||||||
 | 
					    /// <param name="messageService">
 | 
				
			||||||
 | 
					    ///   Message service that will be used to display the question
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be shown on the message box</param>
 | 
				
			||||||
 | 
					    /// <returns>The button the user has clicked on</returns>
 | 
				
			||||||
 | 
					    public static Task<MessageDialogResult> AskOkCancelAsync(
 | 
				
			||||||
 | 
					      this IMessageService messageService, MessageText text
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return messageService.ShowQuestionAsync(
 | 
				
			||||||
 | 
					        MessageBoxIcon.Question, text, MessageBoxButtons.OkCancel
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    ///   Asks the user a question that can be answered with yes, no or cancel
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    /// <param name="messageService">
 | 
				
			||||||
 | 
					    ///   Message service that will be used to display the question
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text that will be shown on the message box</param>
 | 
				
			||||||
 | 
					    /// <returns>The button the user has clicked on</returns>
 | 
				
			||||||
 | 
					    public static Task<MessageDialogResult> AskYesNoCancelAsync(
 | 
				
			||||||
 | 
					      this IMessageService messageService, MessageText text
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return messageService.ShowQuestionAsync(
 | 
				
			||||||
 | 
					        MessageBoxIcon.Question, text, MessageBoxButtons.YesNoCancel
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays an informative message</summary>
 | 
				
			||||||
 | 
					    /// <param name="messageService">
 | 
				
			||||||
 | 
					    ///   Message service that will be used to display the warning
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text to be displayed on the warning message</param>
 | 
				
			||||||
 | 
					    public static Task InformAsync(
 | 
				
			||||||
 | 
					      this IMessageService messageService, MessageText text
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return messageService.ShowNotificationAsync(MessageBoxIcon.Info, text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays a warning</summary>
 | 
				
			||||||
 | 
					    /// <param name="messageService">
 | 
				
			||||||
 | 
					    ///   Message service that will be used to display the warning
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text to be displayed on the warning message</param>
 | 
				
			||||||
 | 
					    public static Task WarnAsync(
 | 
				
			||||||
 | 
					      this IMessageService messageService, MessageText text
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return messageService.ShowNotificationAsync(MessageBoxIcon.Warning, text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Reports an error</summary>
 | 
				
			||||||
 | 
					    /// <param name="messageService">
 | 
				
			||||||
 | 
					    ///   Message service that will be used to display the warning
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="text">Text to be displayed on the warning message</param>
 | 
				
			||||||
 | 
					    public static Task ReportErrorAsync(
 | 
				
			||||||
 | 
					      this IMessageService messageService, MessageText text
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return messageService.ShowNotificationAsync(MessageBoxIcon.Error, text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia.Messages
 | 
				
			||||||
							
								
								
									
										57
									
								
								Source/Messages/MessageText.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Source/Messages/MessageText.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					#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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia.Messages {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Text that will be displayed in a message box</summary>
 | 
				
			||||||
 | 
					  public class MessageText {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Initializs a new message text</summary>
 | 
				
			||||||
 | 
					    public MessageText() {
 | 
				
			||||||
 | 
					      Caption = "Message";
 | 
				
			||||||
 | 
					      Message = "No message provided";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Initializes a new message text by copying another instance</summary>
 | 
				
			||||||
 | 
					    /// <param name="other">Instance that will be copied</param>
 | 
				
			||||||
 | 
					    public MessageText(MessageText other) {
 | 
				
			||||||
 | 
					      Caption = other.Caption;
 | 
				
			||||||
 | 
					      Message = other.Message;
 | 
				
			||||||
 | 
					      Details = other.Details;
 | 
				
			||||||
 | 
					      ExpandedDetails = other.ExpandedDetails;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>The caption used when the is displayed in a message box</summary>
 | 
				
			||||||
 | 
					    public string Caption { get; set; }
 | 
				
			||||||
 | 
					    /// <summary>Main message being displayed to the user</summary>
 | 
				
			||||||
 | 
					    public string Message { get; set; }
 | 
				
			||||||
 | 
					    /// <summary>Message details shown below the main message</summary>
 | 
				
			||||||
 | 
					    public string? Details { get; set; }
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    ///   Additional informations the user can display by expanding
 | 
				
			||||||
 | 
					    ///   the message dialog. Can be null, in which case the message dialog
 | 
				
			||||||
 | 
					    ///   will not be expandable.
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public string? ExpandedDetails { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia.Messages
 | 
				
			||||||
							
								
								
									
										37
									
								
								Source/NullActiveWindowTracker.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Source/NullActiveWindowTracker.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					#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 Avalonia.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Avalonia {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Dummy implementation of the active window tracker service</summary>
 | 
				
			||||||
 | 
					  internal class NullActiveWindowTracker : IActiveWindowTracker {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>The default instance of the dummy window tracker</summary>
 | 
				
			||||||
 | 
					    public static readonly NullActiveWindowTracker Default = new NullActiveWindowTracker();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>The currently active top-level or modal window</summary>
 | 
				
			||||||
 | 
					    public Window? ActiveWindow { get { return null; } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia
 | 
				
			||||||
							
								
								
									
										39
									
								
								Source/ViewModels/IDialogViewModel.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Source/ViewModels/IDialogViewModel.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					#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.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Nuclex.Support;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Nuclex.Windows.Forms.ViewModels {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Interface for dialog view models (typically modal ones)</summary>
 | 
				
			||||||
 | 
					  public interface IDialogViewModel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Indicates that the view should close</summary>
 | 
				
			||||||
 | 
					    event EventHandler Submitted;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Indicates that the dialog should be closed</summary>
 | 
				
			||||||
 | 
					    /// <returns>A task that finishes when the submit notification has been sent</returns>
 | 
				
			||||||
 | 
					    Task SubmitAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Windows.Forms.ViewModels
 | 
				
			||||||
							
								
								
									
										524
									
								
								Source/WindowManager.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										524
									
								
								Source/WindowManager.cs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,524 @@
 | 
				
			||||||
 | 
					#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 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// <summary>Manages an application's windows and views</summary>
 | 
				
			||||||
 | 
					  public class WindowManager : Observable, IWindowManager {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #region class WindowManagerScope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Global scope that uses the WindowManager's CreateInstance()</summary>
 | 
				
			||||||
 | 
					    public class WindowManagerScope : IWindowScope {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Initializes a new global window scope</summary>
 | 
				
			||||||
 | 
					      /// <param name="windowManager">
 | 
				
			||||||
 | 
					      ///   Window manager whose CreateInstance() method will be used
 | 
				
			||||||
 | 
					      /// </param>
 | 
				
			||||||
 | 
					      public WindowManagerScope(WindowManager windowManager) {
 | 
				
			||||||
 | 
					        this.windowManager = windowManager;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Creates an instance of the specified type in the scope</summary>
 | 
				
			||||||
 | 
					      /// <param name="type">Type an instance will be created of</param>
 | 
				
			||||||
 | 
					      /// <returns>The created instance</returns>
 | 
				
			||||||
 | 
					      object IWindowScope.CreateInstance(Type type) {
 | 
				
			||||||
 | 
					        return this.windowManager.CreateInstance(type);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Does nothing because this is the global fallback scope</summary>
 | 
				
			||||||
 | 
					      void IDisposable.Dispose() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>WindowManager whose CreateInstance() method will be wrapped</summary>
 | 
				
			||||||
 | 
					      private WindowManager windowManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #endregion // class WindwoManagerScope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #region class CancellableDisposer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Disposes an object that potentially implements IDisposable</summary>
 | 
				
			||||||
 | 
					    private struct CancellableDisposer : IDisposable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Initializes a new cancellable disposer</summary>
 | 
				
			||||||
 | 
					      /// <param name="potentiallyDisposable">
 | 
				
			||||||
 | 
					      ///   Object that potentially implements IDisposable
 | 
				
			||||||
 | 
					      /// </param>
 | 
				
			||||||
 | 
					      public CancellableDisposer(object? potentiallyDisposable = null) {
 | 
				
			||||||
 | 
					        this.potentiallyDisposable = potentiallyDisposable;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>
 | 
				
			||||||
 | 
					      ///   Disposes the assigned object if the disposer has not been cancelled
 | 
				
			||||||
 | 
					      /// </summary>
 | 
				
			||||||
 | 
					      public void Dispose() {
 | 
				
			||||||
 | 
					        IDisposable? disposable = this.potentiallyDisposable as IDisposable;
 | 
				
			||||||
 | 
					        if(disposable != null) {
 | 
				
			||||||
 | 
					          disposable.Dispose();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Cancels the call to Dispose(), keeping the object alive</summary>
 | 
				
			||||||
 | 
					      public void Dismiss() {
 | 
				
			||||||
 | 
					        this.potentiallyDisposable = null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Assigns a new potentially disposable object</summary>
 | 
				
			||||||
 | 
					      /// <param name="potentiallyDisposable">
 | 
				
			||||||
 | 
					      ///   Potentially disposable object that the disposer will dispose
 | 
				
			||||||
 | 
					      /// </param>
 | 
				
			||||||
 | 
					      public void Set(object? potentiallyDisposable) {
 | 
				
			||||||
 | 
					        this.potentiallyDisposable = potentiallyDisposable;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /// <summary>Object that will be disposed unless the disposer is cancelled</summary>
 | 
				
			||||||
 | 
					      private object? potentiallyDisposable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #endregion // class CancellableDisposer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Initializes a new window manager</summary>
 | 
				
			||||||
 | 
					    /// <param name="autoBinder">
 | 
				
			||||||
 | 
					    ///   View model binder that will be used to bind all created views to their models
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    public WindowManager(IAutoBinder? autoBinder = null) {
 | 
				
			||||||
 | 
					      this.autoBinder = autoBinder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.rootWindowActivatedDelegate = rootWindowActivated;
 | 
				
			||||||
 | 
					      this.rootWindowClosedDelegate = rootWindowClosed;
 | 
				
			||||||
 | 
					      this.windowManagerAsScope = new WindowManagerScope(this);
 | 
				
			||||||
 | 
					      this.viewTypesForViewModels = new ConcurrentDictionary<Type, Type>();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>The currently active top-level or modal window</summary>
 | 
				
			||||||
 | 
					    public Window? ActiveWindow {
 | 
				
			||||||
 | 
					      get { return this.activeWindow; }
 | 
				
			||||||
 | 
					      private set {
 | 
				
			||||||
 | 
					        if(value != this.activeWindow) {
 | 
				
			||||||
 | 
					          this.activeWindow = value;
 | 
				
			||||||
 | 
					          OnPropertyChanged(nameof(ActiveWindow));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Opens a view as a new root window of the application</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of view model a root window will be opened for
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   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)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="disposeOnClose">
 | 
				
			||||||
 | 
					    ///   Whether the view model should be disposed when the view is closed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The window that has been opened by the window manager</returns>
 | 
				
			||||||
 | 
					    public Window OpenRoot<TViewModel>(
 | 
				
			||||||
 | 
					      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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays a view as a modal window</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of the view model for which a view will be displayed
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   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)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="disposeOnClose">
 | 
				
			||||||
 | 
					    ///   Whether the view model should be disposed when the view is closed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The return value of the modal window</returns>
 | 
				
			||||||
 | 
					    public Task ShowModalAsync<TViewModel>(
 | 
				
			||||||
 | 
					      TViewModel? viewModel = null, bool disposeOnClose = false
 | 
				
			||||||
 | 
					    ) where TViewModel : class {
 | 
				
			||||||
 | 
					      return ShowModalAsync<object?, TViewModel>(viewModel, disposeOnClose);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Displays a view as a modal window</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TResult">
 | 
				
			||||||
 | 
					    ///   Type of result the modal dialog will return to the caller
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of the view model for which a view will be displayed
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   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)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="disposeOnClose">
 | 
				
			||||||
 | 
					    ///   Whether the view model should be disposed when the view is closed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The return value of the modal window</returns>
 | 
				
			||||||
 | 
					    public Task<TResult> ShowModalAsync<TResult, TViewModel>(
 | 
				
			||||||
 | 
					      TViewModel? viewModel = null, bool disposeOnClose = false
 | 
				
			||||||
 | 
					    ) where TViewModel : class {
 | 
				
			||||||
 | 
					      if(this.activeWindow == null) {
 | 
				
			||||||
 | 
					        throw new InvalidOperationException("Showing a modal dialog requires an active window");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Window window = (Window)CreateView(viewModel);
 | 
				
			||||||
 | 
					      Window? parentWindow = this.activeWindow;
 | 
				
			||||||
 | 
					      window.Activated += this.rootWindowActivatedDelegate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        // If we either created the view model or the user explicitly asked us to
 | 
				
			||||||
 | 
					        // dispose his view model, prepare the window so that it will dispose its
 | 
				
			||||||
 | 
					        // view model when the window is done.
 | 
				
			||||||
 | 
					        if((viewModel == null) || disposeOnClose) {
 | 
				
			||||||
 | 
					          setupViewModelDisposal(window);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return window.ShowDialog<TResult>(this.activeWindow);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      finally {
 | 
				
			||||||
 | 
					        window.Activated -= this.rootWindowActivatedDelegate;
 | 
				
			||||||
 | 
					        ActiveWindow = parentWindow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(disposeOnClose) {
 | 
				
			||||||
 | 
					          disposeIfDisposable(window);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Creates the view for the specified view model</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">
 | 
				
			||||||
 | 
					    ///   Type of view model for which a view will be created
 | 
				
			||||||
 | 
					    /// </typeparam>
 | 
				
			||||||
 | 
					    /// <param name="viewModel">
 | 
				
			||||||
 | 
					    ///   View model a view will be created for. If null, the view model will be
 | 
				
			||||||
 | 
					    ///   created as well (unless the dialog already specifies one as a resource)
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The view for the specified view model</returns>
 | 
				
			||||||
 | 
					    public virtual Control CreateView<TViewModel>(
 | 
				
			||||||
 | 
					      TViewModel? viewModel = null
 | 
				
			||||||
 | 
					    ) where TViewModel : class {
 | 
				
			||||||
 | 
					      Control viewControl;
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        Type viewType = LocateViewForViewModel(typeof(TViewModel));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        IWindowScope scope = CreateWindowScope();
 | 
				
			||||||
 | 
					        using(var scopeDisposer = new CancellableDisposer(scope)) {
 | 
				
			||||||
 | 
					          viewControl = (Control)scope.CreateInstance(viewType);
 | 
				
			||||||
 | 
					          using(var viewDisposer = new CancellableDisposer(viewControl)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Create a view model if none was provided, and in either case assign
 | 
				
			||||||
 | 
					            // the view model to the view (provided it implements IView).
 | 
				
			||||||
 | 
					            using(var viewModelDisposer = new CancellableDisposer()) {
 | 
				
			||||||
 | 
					              if(viewModel == null) { // No view model provided, create one
 | 
				
			||||||
 | 
					                if(viewControl.DataContext == null) { // View has no view model
 | 
				
			||||||
 | 
					                  viewModel = (TViewModel)scope.CreateInstance(typeof(TViewModel));
 | 
				
			||||||
 | 
					                  viewModelDisposer.Set(viewModel);
 | 
				
			||||||
 | 
					                  viewControl.DataContext = viewModel;
 | 
				
			||||||
 | 
					                } else { // There's an existing view model
 | 
				
			||||||
 | 
					                  viewModel = viewControl.DataContext as TViewModel;
 | 
				
			||||||
 | 
					                  if(viewModel == null) { // The existing view model is another type
 | 
				
			||||||
 | 
					                    viewModel = (TViewModel)scope.CreateInstance(typeof(TViewModel));
 | 
				
			||||||
 | 
					                    viewModelDisposer.Set(viewModel);
 | 
				
			||||||
 | 
					                    viewControl.DataContext = viewModel;
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              } else { // Caller has provided a view model
 | 
				
			||||||
 | 
					                viewControl.DataContext = viewModel;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              // If an auto binder was provided, automatically bind the view to the view model
 | 
				
			||||||
 | 
					              if(this.autoBinder != null) {
 | 
				
			||||||
 | 
					                this.autoBinder.Bind(viewControl, viewModel);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              viewModelDisposer.Dismiss(); // Everything went well, we keep the view model
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            viewDisposer.Dismiss(); // Everything went well, we keep the view
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          scopeDisposer.Dismiss(); // Everything went well, we keep the scope
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setupScopeDisposal(viewControl, scope);
 | 
				
			||||||
 | 
					      } // beauty scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return viewControl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Creates a view model without a matching view</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="TViewModel">Type of view model that will be created</typeparam>
 | 
				
			||||||
 | 
					    /// <returns>The new view model</returns>
 | 
				
			||||||
 | 
					    /// <remarks>
 | 
				
			||||||
 | 
					    ///   <para>
 | 
				
			||||||
 | 
					    ///     This is useful if a view model needs to create child view models (i.e. paged container
 | 
				
			||||||
 | 
					    ///     and wants to ensure the same dependency injector (if any) if used as the window
 | 
				
			||||||
 | 
					    ///     manager uses for other view models it creates.
 | 
				
			||||||
 | 
					    ///   </para>
 | 
				
			||||||
 | 
					    ///   <para>
 | 
				
			||||||
 | 
					    ///     This way, view models can set up their child view models without having to immediately
 | 
				
			||||||
 | 
					    ///     bind a view to them. Later on, views can use the window manager to create a matching
 | 
				
			||||||
 | 
					    ///     child view and store it in a container.
 | 
				
			||||||
 | 
					    ///   </para>
 | 
				
			||||||
 | 
					    /// </remarks>
 | 
				
			||||||
 | 
					    public TViewModel CreateViewModel<TViewModel>() where TViewModel : class {
 | 
				
			||||||
 | 
					      return (TViewModel)CreateInstance(typeof(TViewModel));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Locates the view that will be used to a view model</summary>
 | 
				
			||||||
 | 
					    /// <param name="viewModelType">
 | 
				
			||||||
 | 
					    ///   Type of view model for which the view will be located
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The type of view that should be used for the specified view model</returns>
 | 
				
			||||||
 | 
					    protected virtual Type LocateViewForViewModel(Type viewModelType) {
 | 
				
			||||||
 | 
					      Type? viewType;
 | 
				
			||||||
 | 
					      if(!this.viewTypesForViewModels.TryGetValue(viewModelType, out viewType)) {
 | 
				
			||||||
 | 
					        string viewName = viewModelType.Name;
 | 
				
			||||||
 | 
					        if(viewName.EndsWith("ViewModel")) {
 | 
				
			||||||
 | 
					          viewName = viewName.Substring(0, viewName.Length - 9);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Type[] exportedTypes = viewModelType.Assembly.GetExportedTypes();
 | 
				
			||||||
 | 
					        Type[] namespaceTypes = filterTypesByNamespace(exportedTypes, viewModelType.Namespace);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // First, search the own namespace (because if two identical view models exist in
 | 
				
			||||||
 | 
					        // different namespaces, the one in the same namespace is most likely the desired one)
 | 
				
			||||||
 | 
					        viewType = findBestMatch(
 | 
				
			||||||
 | 
					          namespaceTypes,
 | 
				
			||||||
 | 
					          viewName + "View",
 | 
				
			||||||
 | 
					          viewName + "Page",
 | 
				
			||||||
 | 
					          viewName + "Form",
 | 
				
			||||||
 | 
					          viewName + "Window",
 | 
				
			||||||
 | 
					          viewName + "Dialog",
 | 
				
			||||||
 | 
					          viewName + "Control"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If the view model doesn't exist in the same namespace, expand the search to
 | 
				
			||||||
 | 
					        // the entire assembly the view is in.
 | 
				
			||||||
 | 
					        if(viewType == null) {
 | 
				
			||||||
 | 
					          viewType = findBestMatch(
 | 
				
			||||||
 | 
					            exportedTypes,
 | 
				
			||||||
 | 
					            viewName + "View",
 | 
				
			||||||
 | 
					            viewName + "Page",
 | 
				
			||||||
 | 
					            viewName + "Form",
 | 
				
			||||||
 | 
					            viewName + "Window",
 | 
				
			||||||
 | 
					            viewName + "Dialog",
 | 
				
			||||||
 | 
					            viewName + "Control"
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Still no view found? We give up!
 | 
				
			||||||
 | 
					        if(viewType == null) {
 | 
				
			||||||
 | 
					          throw new InvalidOperationException(
 | 
				
			||||||
 | 
					            string.Format("Could not locate view for view model '{0}'", viewModelType.Name)
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.viewTypesForViewModels.TryAdd(viewModelType, viewType);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return viewType;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Creates an instance of the specified type</summary>
 | 
				
			||||||
 | 
					    /// <param name="type">Type an instance will be created of</param>
 | 
				
			||||||
 | 
					    /// <returns>The created instance</returns>
 | 
				
			||||||
 | 
					    /// <remarks>
 | 
				
			||||||
 | 
					    ///   Use this to wire up your dependency injection container. By default,
 | 
				
			||||||
 | 
					    ///   the Activator class will be used to create instances which only works
 | 
				
			||||||
 | 
					    ///   if all of your view models are concrete classes.
 | 
				
			||||||
 | 
					    /// </remarks>
 | 
				
			||||||
 | 
					    protected virtual object CreateInstance(Type type) {
 | 
				
			||||||
 | 
					      return Activator.CreateInstance(type);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Creates a new scope in which window-specific instances live</summary>
 | 
				
			||||||
 | 
					    /// <returns>
 | 
				
			||||||
 | 
					    ///   A new scope in which scoped services requested by the window's view model
 | 
				
			||||||
 | 
					    ///   will live
 | 
				
			||||||
 | 
					    /// </returns>
 | 
				
			||||||
 | 
					    /// <remarks>
 | 
				
			||||||
 | 
					    ///   If you do not override this method, services will be constructed through
 | 
				
			||||||
 | 
					    ///   the normal <see cref="CreateInstance" /> method (which actually may not
 | 
				
			||||||
 | 
					    ///   work without managing your own service scope in case your dependency
 | 
				
			||||||
 | 
					    ///   injector supports scopes and some of your services are scoped). By
 | 
				
			||||||
 | 
					    ///   overriding this method, you can automatically cause a new scope to be
 | 
				
			||||||
 | 
					    ///   created for each window or dialog. That way, an implicit service scope
 | 
				
			||||||
 | 
					    ///   will cover the lifetime of each window and its view model and any
 | 
				
			||||||
 | 
					    ///   non-singleton services will use new instances, avoiding, for example,
 | 
				
			||||||
 | 
					    ///   that multiple dialogs access the same database connection simultaneously.
 | 
				
			||||||
 | 
					    /// </remarks>
 | 
				
			||||||
 | 
					    protected virtual IWindowScope CreateWindowScope() {
 | 
				
			||||||
 | 
					      return this.windowManagerAsScope;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Called when one of the application's root windows is closed</summary>
 | 
				
			||||||
 | 
					    /// <param name="sender">Window that has been closed</param>
 | 
				
			||||||
 | 
					    /// <param name="arguments">Not used</param>
 | 
				
			||||||
 | 
					    private void rootWindowClosed(object sender, EventArgs arguments) {
 | 
				
			||||||
 | 
					      Window closedWindow = (Window)sender;
 | 
				
			||||||
 | 
					      closedWindow.Closed -= this.rootWindowClosedDelegate;
 | 
				
			||||||
 | 
					      closedWindow.Activated -= this.rootWindowActivatedDelegate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lock(this) {
 | 
				
			||||||
 | 
					        ActiveWindow = null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // The IoC container already does this and it's the user's responsibility anyways
 | 
				
			||||||
 | 
					      //disposeIfDisposable(closedWindow);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Called when one of the application's root windows is activated</summary>
 | 
				
			||||||
 | 
					    /// <param name="sender">Window that has been put in the foreground</param>
 | 
				
			||||||
 | 
					    /// <param name="arguments">Not used</param>
 | 
				
			||||||
 | 
					    private void rootWindowActivated(object? sender, EventArgs arguments) {
 | 
				
			||||||
 | 
					      lock(this) {
 | 
				
			||||||
 | 
					        ActiveWindow = (Window?)sender;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Tries to find the best match for a named type in a list of types</summary>
 | 
				
			||||||
 | 
					    /// <param name="types">List of types the search will take place in</param>
 | 
				
			||||||
 | 
					    /// <param name="typeNames">
 | 
				
			||||||
 | 
					    ///   The candidates the method will look for, starting with the best match
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>The best match in the list of types, if any match was found</returns>
 | 
				
			||||||
 | 
					    private static Type? findBestMatch(Type[] types, params string[] typeNames) {
 | 
				
			||||||
 | 
					      int bestMatchFound = typeNames.Length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Type? type = null;
 | 
				
			||||||
 | 
					      for(int index = 0; index < types.Length; ++index) {
 | 
				
			||||||
 | 
					        for(int nameIndex = 0; nameIndex < bestMatchFound; ++nameIndex) {
 | 
				
			||||||
 | 
					          if(types[index].Name == typeNames[nameIndex]) {
 | 
				
			||||||
 | 
					            bestMatchFound = nameIndex;
 | 
				
			||||||
 | 
					            type = types[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(bestMatchFound == 0) { // There can be no better match
 | 
				
			||||||
 | 
					              return type;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return type;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Disposes the specified object if it implements IDisposable</summary>
 | 
				
			||||||
 | 
					    /// <typeparam name="T">Type of object that will disposed if possible</typeparam>
 | 
				
			||||||
 | 
					    /// <param name="instance">Object that the method will attempt to dispose</param>
 | 
				
			||||||
 | 
					    private static void disposeIfDisposable<T>(T instance) where T : class {
 | 
				
			||||||
 | 
					      var disposable = instance as IDisposable;
 | 
				
			||||||
 | 
					      if(disposable != null) {
 | 
				
			||||||
 | 
					        disposable.Dispose();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Attaches a view model disposer to a control</summary>
 | 
				
			||||||
 | 
					    /// <param name="control">
 | 
				
			||||||
 | 
					    ///   Control whose view model will be disposed when it is itself disposed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    private static void setupViewModelDisposal(Control control) {
 | 
				
			||||||
 | 
					      IDisposable? disposableViewModel = control.DataContext as IDisposable;
 | 
				
			||||||
 | 
					      if(disposableViewModel != null) {
 | 
				
			||||||
 | 
					        control.Unloaded += delegate(object? sender, RoutedEventArgs arguments) {
 | 
				
			||||||
 | 
					          disposableViewModel.Dispose();
 | 
				
			||||||
 | 
					          control.DataContext = null;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      //window.Tag = "DisposeViewModelOnClose"; // TODO: Wrap SetProp() instead?
 | 
				
			||||||
 | 
					      //window.SetValue(DisposeViewModelOnCloseProperty, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Attaches a scope disposer to a control</summary>
 | 
				
			||||||
 | 
					    /// <param name="control">
 | 
				
			||||||
 | 
					    ///   Control that will dispose a scope when it is itself disposed
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <param name="scope">
 | 
				
			||||||
 | 
					    ///   Scope that will be disposed together with the control 
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    private void setupScopeDisposal(Control control, IWindowScope scope) {
 | 
				
			||||||
 | 
					      IDisposable disposableScope = (IDisposable)scope;
 | 
				
			||||||
 | 
					      if(disposableScope != null) {
 | 
				
			||||||
 | 
					        control.Unloaded += delegate(object? sender, RoutedEventArgs arguments) {
 | 
				
			||||||
 | 
					          disposableScope.Dispose();
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>Filters a list of types to contain only those in a specific namespace</summary>
 | 
				
			||||||
 | 
					    /// <param name="exportedTypes">List of exported types that will be filtered</param>
 | 
				
			||||||
 | 
					    /// <param name="filteredNamespace">
 | 
				
			||||||
 | 
					    ///   Namespace the types in the filtered list will be in
 | 
				
			||||||
 | 
					    /// </param>
 | 
				
			||||||
 | 
					    /// <returns>A subset of the specified types that are in the provided namespace</returns>
 | 
				
			||||||
 | 
					    private static Type[] filterTypesByNamespace(Type[] exportedTypes, string? filteredNamespace) {
 | 
				
			||||||
 | 
					      var filteredTypes = new List<Type>(exportedTypes.Length / 2);
 | 
				
			||||||
 | 
					      for(int index = 0; index < exportedTypes.Length; ++index) {
 | 
				
			||||||
 | 
					        Type exportedType = exportedTypes[index];
 | 
				
			||||||
 | 
					        if(exportedType.Namespace == filteredNamespace) {
 | 
				
			||||||
 | 
					          filteredTypes.Add(exportedType);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return filteredTypes.ToArray();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>The application's currently active root window</summary>
 | 
				
			||||||
 | 
					    private Window? activeWindow;
 | 
				
			||||||
 | 
					    /// <summary>Invoked when a root window is put in the foreground</summary>
 | 
				
			||||||
 | 
					    private EventHandler rootWindowActivatedDelegate;
 | 
				
			||||||
 | 
					    /// <summary>Invoked when a root window has been closed</summary>
 | 
				
			||||||
 | 
					    private EventHandler rootWindowClosedDelegate;
 | 
				
			||||||
 | 
					    /// <summary>Scope that uses the WindowManager's global CreateInstance() method</summary>
 | 
				
			||||||
 | 
					    private WindowManagerScope windowManagerAsScope;
 | 
				
			||||||
 | 
					    /// <summary>View model binder that will be used on all created views</summary>
 | 
				
			||||||
 | 
					    private IAutoBinder? autoBinder;
 | 
				
			||||||
 | 
					    /// <summary>Caches the view types to use for a view model</summary>
 | 
				
			||||||
 | 
					    private ConcurrentDictionary<Type, Type> viewTypesForViewModels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace Nuclex.Avalonia
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue