Make custom widgets independent of any WinUI framework concerns. Although one can add WinUI concerns to ther widget if they want to build a fully customized widget. In theory, it may also be possible to host a widget of WPF, Avalonia, or Blazor.

This commit is contained in:
TheXamlGuy
2024-01-06 09:57:23 +00:00
parent 3e88950669
commit 4a27534e39
41 changed files with 218 additions and 205 deletions
@@ -1,15 +0,0 @@
using Hyperbar.Lifecycles;
using Microsoft.Extensions.DependencyInjection;
namespace Hyperbar.Windows.Contextual;
public class ContextualCommandWidgetBuilder :
ICommandWidgetBuilder
{
public void Create(IServiceCollection services)
{
services
.AddWritableConfiguration<ContextualCommandWidgetConfiguration>()
.AddCommandTemplate<ContextualCommandWidgetViewModel, ContextualCommandWidgetView>();
}
}
@@ -1,5 +0,0 @@
namespace Hyperbar.Windows.Contextual;
public class ContextualCommandWidgetConfiguration
{
}
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="Hyperbar.Windows.Contextual.ContextualCommandWidgetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Click="Button_Click" Content="This is a test" />
</Grid>
</Page>
@@ -1,15 +0,0 @@
using Hyperbar.Windows.Win32;
using Microsoft.UI.Xaml.Controls;
using Windows.System;
namespace Hyperbar.Windows.Contextual;
public sealed partial class ContextualCommandWidgetView : Page
{
public ContextualCommandWidgetView() => InitializeComponent();
private void Button_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
KeyInterop.Type(VirtualKey.L, VirtualKey.LeftWindows, VirtualKey.Control);
}
}
@@ -0,0 +1,15 @@
using Hyperbar.Lifecycles;
using Microsoft.Extensions.DependencyInjection;
namespace Hyperbar.Extensions.Contextual;
public class ContextualWidgetBuilder :
IWidgetBuilder
{
public void Create(IServiceCollection services)
{
services
.AddConfiguration<ContextualWidgetConfiguration>()
.AddWidgetTemplate<ContextualWidgetViewModel>();
}
}
@@ -0,0 +1,5 @@
namespace Hyperbar.Extensions.Contextual;
public class ContextualWidgetConfiguration
{
}
@@ -1,10 +1,10 @@
using Hyperbar.Lifecycles;
using Hyperbar.Templates;
namespace Hyperbar.Windows.Contextual;
namespace Hyperbar.Extensions.Contextual;
public class ContextualCommandWidgetViewModel(ITemplateFactory templateFactory) :
ICommandWidgetViewModel,
public class ContextualWidgetViewModel(ITemplateFactory templateFactory) :
IWidgetViewModel,
ITemplatedViewModel
{
public ITemplateFactory TemplateFactory { get; } = templateFactory;
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<UseRidGraph>true</UseRidGraph>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hyperbar\Hyperbar.csproj" />
</ItemGroup>
</Project>
@@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>Hyperbar.Windows.Primary</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.231202003-experimental1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25936-preview" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hyperbar\Hyperbar.csproj" />
</ItemGroup>
</Project>
@@ -1,7 +0,0 @@
namespace Hyperbar.Windows.Primary;
public class PrimaryCommandConfiguration : List<IPrimaryCommand>
{
}
@@ -1,15 +0,0 @@
using Hyperbar.Lifecycles;
using Microsoft.Extensions.DependencyInjection;
namespace Hyperbar.Windows.Primary;
public class PrimaryCommandWidgetBuilder :
ICommandWidgetBuilder
{
public void Create(IServiceCollection services)
{
services.AddWritableConfiguration<PrimaryCommandConfiguration>()
.AddCommandTemplate<PrimaryCommandWidgetViewModel, PrimaryCommandWidgetView>();
}
}
@@ -1,8 +0,0 @@
using Microsoft.UI.Xaml.Controls;
namespace Hyperbar.Windows.Primary;
public sealed partial class PrimaryCommandWidgetView : Page
{
public PrimaryCommandWidgetView() => InitializeComponent();
}
@@ -1,23 +0,0 @@
using Hyperbar.Lifecycles;
using Hyperbar.Templates;
using Windows.System;
namespace Hyperbar.Windows.Primary;
public class PrimaryCommandWidgetViewModel :
ICommandWidgetViewModel,
ITemplatedViewModel
{
public PrimaryCommandWidgetViewModel(ITemplateFactory templateFactory,
IWritableConfiguration<PrimaryCommandConfiguration> configuration)
{
TemplateFactory = templateFactory;
configuration.Write(args => { args.Add(new KeyAcceleratorCommand { Key = $"138" , Modifiers = [$"{VirtualKey.LeftWindows}"] }); });
configuration.Write(args => { args.Add(new KeyAcceleratorCommand { Key = $"{VirtualKey.Tab}", Modifiers = [$"{VirtualKey.LeftWindows}"] }); });
configuration.Write(args => { args.Add(new KeyAcceleratorCommand { Key = $"{VirtualKey.L}", Modifiers = [$"{VirtualKey.LeftWindows}", $"{VirtualKey.Control}"] }); });
}
public ITemplateFactory TemplateFactory { get; }
}
@@ -0,0 +1,15 @@
using Hyperbar.Lifecycles;
using Microsoft.Extensions.DependencyInjection;
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetBuilder :
IWidgetBuilder
{
public void Create(IServiceCollection services)
{
services.AddConfiguration<PrimaryWidgetConfiguration>()
.AddTransient<PrimaryWidgetViewModel>();
}
}
@@ -0,0 +1,8 @@
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetConfiguration :
List<IPrimaryCommand>
{
}
@@ -0,0 +1,14 @@
using Hyperbar.Lifecycles;
using Hyperbar.Templates;
namespace Hyperbar.Windows.Primary;
public class PrimaryWidgetViewModel :
WidgetViewModelBase
{
public PrimaryWidgetViewModel(ITemplateFactory templateFactory) : base(templateFactory)
{
}
}
+8 -10
View File
@@ -1,6 +1,4 @@
using Hyperbar.Windows.Contextual;
using Hyperbar.Windows.Controls;
using Hyperbar.Windows.Primary;
using Hyperbar.Windows.Controls;
using Hyperbar.Lifecycles;
using Hyperbar.Templates;
using Microsoft.Extensions.Configuration;
@@ -38,19 +36,19 @@ public partial class App :
services.AddTransient<ITemplateFactory, TemplateFactory>();
services.AddTransient<ITemplateGeneratorFactory, TemplateGeneratorFactory>();
services.AddDataTemplate<CommandViewModel, CommandView>();
services.AddContentTemplate<CommandViewModel, CommandView>();
services.AddCommand<ContextualCommandWidgetBuilder>("");
services.AddCommand<PrimaryCommandWidgetBuilder>("");
//services.AddCommand<ContextualCommandWidgetBuilder>("");
//services.AddWidget<PrimaryCommandWidgetBuilder>("");
services.AddTransient(provider =>
{
static IEnumerable<ICommandWidgetViewModel> Resolve(IServiceProvider services)
static IEnumerable<IWidgetViewModel> Resolve(IServiceProvider services)
{
foreach (ICommandWidgetContext commandContext in services.GetServices<ICommandWidgetContext>())
foreach (IWidgetContext commandContext in services.GetServices<IWidgetContext>())
{
if (commandContext.ServiceProvider.GetService<ICommandWidgetViewModel>() is
ICommandWidgetViewModel commandViewModel)
if (commandContext.ServiceProvider.GetService<IWidgetViewModel>() is
IWidgetViewModel commandViewModel)
{
yield return commandViewModel;
}
+2 -6
View File
@@ -14,10 +14,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="ContextualCommandView.xaml" />
<None Remove="Views\CommandView.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
@@ -37,9 +33,9 @@
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hyperbar.Windows.Contextual\Hyperbar.Windows.Contextual.csproj" />
<ProjectReference Include="..\Hyperbar.Windows.Contextual\Hyperbar.Widget.Contextual.csproj" />
<ProjectReference Include="..\Hyperbar.Windows.Controls\Hyperbar.Windows.Controls.csproj" />
<ProjectReference Include="..\Hyperbar.Windows.Primary\Hyperbar.Windows.Primary.csproj" />
<ProjectReference Include="..\Hyperbar.Windows.Primary\Hyperbar.Widget.Primary.csproj" />
<ProjectReference Include="..\Hyperbar\Hyperbar.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
@@ -7,10 +7,10 @@ namespace Hyperbar.Windows
{
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddCommand<TCommandBuilder>(this IServiceCollection services,
public static IServiceCollection AddWidget<TCommandBuilder>(this IServiceCollection services,
string key)
where TCommandBuilder :
ICommandWidgetBuilder, new()
IWidgetBuilder, new()
{
TCommandBuilder builder = new();
IHost? host = new HostBuilder()
@@ -22,7 +22,7 @@ namespace Hyperbar.Windows
builder.Create(isolatedServices);
}).Build();
services.AddTransient<ICommandWidgetContext>(provider => new CommandWidgetContext(host.Services));
services.AddTransient<IWidgetContext>(provider => new WidgetContext(host.Services));
return services;
}
}
@@ -9,7 +9,7 @@ using System.Linq;
namespace Hyperbar.Windows;
public class TemplateFactory(ITemplateGeneratorFactory factory,
IEnumerable<IDataTemplateDescriptor> descriptors,
IEnumerable<IContentTemplateDescriptor> descriptors,
IServiceProvider provider) :
DataTemplateSelector,
ITemplateFactory
@@ -20,7 +20,7 @@ public class TemplateFactory(ITemplateGeneratorFactory factory,
public object? Create(object key)
{
if (descriptors.FirstOrDefault(x => x.Key == key) is IDataTemplateDescriptor descriptor)
if (descriptors.FirstOrDefault(x => x.Key == key) is IContentTemplateDescriptor descriptor)
{
if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key) is { } template)
{
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
<UserControl
x:Class="Hyperbar.Windows.CommandView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -10,4 +10,4 @@
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Page>
</UserControl>
+2 -1
View File
@@ -3,7 +3,8 @@ using Microsoft.UI.Xaml.Input;
namespace Hyperbar.Windows;
public sealed partial class CommandView : Page
public sealed partial class CommandView :
UserControl
{
public CommandView() => InitializeComponent();
+1 -1
View File
@@ -9,7 +9,7 @@ public partial class CommandViewModel :
ITemplatedViewModel
{
public CommandViewModel(ITemplateFactory templateFactory,
IEnumerable<ICommandWidgetViewModel> commands)
IEnumerable<IWidgetViewModel> commands)
{
TemplateFactory = templateFactory;
AddRange(commands);
@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="Hyperbar.Windows.Primary.PrimaryCommandWidgetView"
<UserControl
x:Class="Hyperbar.Windows.WidgetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Primary commands" />
</Grid>
</Page>
<Grid />
</UserControl>
+10
View File
@@ -0,0 +1,10 @@
using Microsoft.UI.Xaml.Controls;
namespace Hyperbar.Windows;
public sealed partial class WidgetView :
UserControl,
IWidgetView
{
public WidgetView() => InitializeComponent();
}
+2 -2
View File
@@ -11,9 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Windows.Win32", "H
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar", "Hyperbar\Hyperbar.csproj", "{E5795878-C7E3-4386-86FA-33681BCF8D5B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Windows.Contextual", "Hyperbar.Windows.Contextual\Hyperbar.Windows.Contextual.csproj", "{C32D4073-2A9B-4257-8895-09951FAD8E7A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Widget.Contextual", "Hyperbar.Windows.Contextual\Hyperbar.Widget.Contextual.csproj", "{C32D4073-2A9B-4257-8895-09951FAD8E7A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Windows.Primary", "Hyperbar.Windows.Primary\Hyperbar.Windows.Primary.csproj", "{AFB8A3EB-8831-4041-AE05-3E0EF672887C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hyperbar.Widget.Primary", "Hyperbar.Windows.Primary\Hyperbar.Widget.Primary.csproj", "{AFB8A3EB-8831-4041-AE05-3E0EF672887C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -2,23 +2,25 @@
using Hyperbar.Options;
using Hyperbar.Templates;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Text.Json;
namespace Hyperbar;
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddWritableConfiguration<TConfiguration>(this IServiceCollection services,
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services,
string path = "Settings.json")
where TConfiguration :
class, new()
{
return services.AddWritableConfiguration<TConfiguration>(typeof(TConfiguration).Name, path);
return services.AddConfiguration<TConfiguration>(typeof(TConfiguration).Name, path);
}
public static IServiceCollection AddWritableConfiguration<TConfiguration>(this IServiceCollection services,
public static IServiceCollection AddConfiguration<TConfiguration>(this IServiceCollection services,
string section,
string path = "Settings.json",
Action<JsonSerializerOptions>? serializerDelegate = null)
@@ -55,23 +57,24 @@ public static class IServiceCollectionExtensions
return services;
}
public static IServiceCollection AddCommandTemplate<TCommand, TCommandTemplate>(this IServiceCollection services)
where TCommand :
ICommandWidgetViewModel
public static IServiceCollection AddWidgetTemplate<TWidgetContent>(this IServiceCollection services)
where TWidgetContent :
IWidgetViewModel
{
Type dataType = typeof(TCommand);
Type templateType = typeof(TCommandTemplate);
Type contentType = typeof(TWidgetContent);
Type templateType = typeof(IWidgetView);
string key = dataType.Name;
string key = contentType.Name;
services.AddTransient(typeof(ICommandWidgetViewModel), dataType);
services.AddTransient(templateType);
services.AddKeyedTransient(typeof(ICommandWidgetViewModel), key, dataType);
services.AddKeyedTransient(templateType, key);
services.AddTransient(typeof(IWidgetViewModel), contentType);
services.TryAddTransient(provider => provider.GetService<IWidgetView>()!);
services.AddKeyedTransient(typeof(IWidgetViewModel), key, contentType);
services.TryAddKeyedTransient(templateType, key);
services.AddTransient<IDataTemplateDescriptor>(provider => new DataTemplateDescriptor
services.AddTransient<IContentTemplateDescriptor>(provider => new ContentTemplateDescriptor
{
DataType = dataType,
ContentType = contentType,
TemplateType = templateType,
Key = key
});
@@ -79,20 +82,48 @@ public static class IServiceCollectionExtensions
return services;
}
public static IServiceCollection AddDataTemplate<TData, TTemplate>(this IServiceCollection services,
public static IServiceCollection AddWidgetTemplate<TWidgetContent, TWidgetTemplate>(this IServiceCollection services)
where TWidgetContent :
IWidgetViewModel
{
Type contentType = typeof(TWidgetContent);
Type templateType = typeof(TWidgetTemplate);
string key = contentType.Name;
services.AddTransient(typeof(IWidgetViewModel), contentType);
services.TryAddTransient(templateType);
services.AddKeyedTransient(typeof(IWidgetViewModel), key, contentType);
services.TryAddKeyedTransient(templateType, key);
services.AddTransient<IContentTemplateDescriptor>(provider => new ContentTemplateDescriptor
{
ContentType = contentType,
TemplateType = templateType,
Key = key
});
return services;
}
public static IServiceCollection AddContentTemplate<TContent, TTemplate>(this IServiceCollection services,
object? key = null)
{
Type dataType = typeof(TData);
Type contentType = typeof(TContent);
Type templateType = typeof(TTemplate);
key ??= dataType.Name;
key ??= contentType.Name;
services.AddKeyedTransient(dataType, key);
services.AddTransient(contentType);
services.TryAddTransient(templateType);
services.AddKeyedTransient(contentType, key);
services.AddKeyedTransient(templateType, key);
services.AddTransient<IDataTemplateDescriptor>(provider => new DataTemplateDescriptor
services.AddTransient<IContentTemplateDescriptor>(provider => new ContentTemplateDescriptor
{
DataType = dataType,
ContentType = contentType,
TemplateType = templateType,
Key = key
});
+6
View File
@@ -0,0 +1,6 @@
namespace Hyperbar;
public interface IWidgetView
{
}
-3
View File
@@ -3,11 +3,8 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="JsonPatch.Net" Version="2.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
@@ -1,7 +0,0 @@
namespace Hyperbar.Lifecycles;
public interface ICommandWidgetViewModel
{
}
@@ -2,7 +2,7 @@
namespace Hyperbar.Lifecycles;
public interface ICommandWidgetBuilder
public interface IWidgetBuilder
{
void Create(IServiceCollection services);
}
@@ -0,0 +1,6 @@
namespace Hyperbar.Lifecycles;
public interface IWidgetComponentViewModel
{
}
@@ -1,6 +1,6 @@
namespace Hyperbar.Lifecycles;
public interface ICommandWidgetContext
public interface IWidgetContext
{
IServiceProvider ServiceProvider { get; }
}
+6
View File
@@ -0,0 +1,6 @@
namespace Hyperbar.Lifecycles;
public interface IWidgetViewModel
{
}
@@ -2,14 +2,20 @@
namespace Hyperbar.Lifecycles;
public class ObservableCollectionViewModel :
ObservableCollection<object>
public class ObservableCollectionViewModel<TItem> :
ObservableCollection<TItem>
{
public void AddRange(IEnumerable<object> collection)
public void AddRange(IEnumerable<TItem> collection)
{
foreach (var item in collection)
foreach (TItem? item in collection)
{
Add(item);
}
}
}
public class ObservableCollectionViewModel :
ObservableCollectionViewModel<object>
{
}
@@ -0,0 +1,6 @@
namespace Hyperbar.Lifecycles;
public class WidgetButtonViewModel
{
}
@@ -1,7 +1,7 @@
namespace Hyperbar.Lifecycles;
public class CommandWidgetContext(IServiceProvider serviceProvider) :
ICommandWidgetContext
public class WidgetContext(IServiceProvider serviceProvider) :
IWidgetContext
{
public IServiceProvider ServiceProvider => serviceProvider;
}
@@ -0,0 +1,11 @@
using Hyperbar.Templates;
namespace Hyperbar.Lifecycles;
public class WidgetViewModelBase(ITemplateFactory templateFactory) :
ObservableCollectionViewModel<IWidgetComponentViewModel>,
IWidgetViewModel,
ITemplatedViewModel
{
public ITemplateFactory TemplateFactory { get; } = templateFactory;
}
@@ -1,9 +1,9 @@
namespace Hyperbar.Templates;
public record DataTemplateDescriptor :
IDataTemplateDescriptor
public record ContentTemplateDescriptor :
IContentTemplateDescriptor
{
public required Type DataType { get; set; }
public required Type ContentType { get; set; }
public required Type TemplateType { get; set; }
@@ -1,9 +1,11 @@
namespace Hyperbar.Templates;
public interface IDataTemplateDescriptor
public interface IContentTemplateDescriptor
{
Type DataType { get; set; }
Type ContentType { get; set; }
object Key { get; set; }
Type TemplateType { get; set; }
}