Implement WPF plugin settings hosting in Avalonia

- Created WpfControlHost to host WPF controls using HwndSource

- Updated PluginItemViewModel to load settings via ISettingProvider

- Updated PluginsSettingsPage to display settings in an expander
This commit is contained in:
Hongtao Zhang 2026-01-18 20:01:00 -08:00
parent 1209ca5194
commit 0e1af2279b
3 changed files with 120 additions and 15 deletions

View file

@ -46,8 +46,32 @@ public partial class PluginItemViewModel : ObservableObject
public PluginItemViewModel(PluginPair plugin)
{
_plugin = plugin;
if (plugin.Plugin is ISettingProvider settingProvider)
{
try
{
// Create the WPF settings panel
SettingControl = settingProvider.CreateSettingPanel();
HasSettings = SettingControl != null;
}
catch (System.Exception)
{
// TODO: Log error using logger
HasSettings = false;
}
}
}
[ObservableProperty]
private object? _settingControl;
[ObservableProperty]
private bool _hasSettings;
[ObservableProperty]
private bool _isExpanded;
public string Name => _plugin.Metadata.Name;
public string Description => _plugin.Metadata.Description;
public string Author => _plugin.Metadata.Author;

View file

@ -0,0 +1,61 @@
using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform;
using System.Windows.Interop;
using System.Windows;
namespace Flow.Launcher.Avalonia.Views.Controls
{
public class WpfControlHost : NativeControlHost
{
private HwndSource? _source;
public static readonly StyledProperty<object?> ContentProperty =
AvaloniaProperty.Register<WpfControlHost, object?>(nameof(Content));
public object? Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
var parameters = new HwndSourceParameters
{
ParentWindow = parent.Handle,
WindowStyle = 0x50000000, // WS_CHILD | WS_VISIBLE
PositionX = 0,
PositionY = 0,
};
_source = new HwndSource(parameters);
if (Content != null)
{
_source.RootVisual = Content as System.Windows.Media.Visual;
}
return new PlatformHandle(_source.Handle, "HwndSource");
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
_source?.Dispose();
_source = null;
base.DestroyNativeControlCore(control);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ContentProperty && _source != null)
{
_source.RootVisual = change.NewValue as System.Windows.Media.Visual;
}
}
}
}

View file

@ -5,6 +5,7 @@
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vm="using:Flow.Launcher.Avalonia.ViewModel.SettingPages"
xmlns:i18n="using:Flow.Launcher.Avalonia.Resource"
xmlns:controls="using:Flow.Launcher.Avalonia.Views.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="Flow.Launcher.Avalonia.Views.SettingPages.PluginsSettingsPage"
x:DataType="vm:PluginsSettingsViewModel">
@ -23,23 +24,42 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:PluginItemViewModel">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,5">
<Image Grid.Column="0" Source="{Binding IconPath}" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Version}" Foreground="Gray" FontSize="12" VerticalAlignment="Bottom" />
<StackPanel Spacing="5">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,5">
<Image Grid.Column="0" Source="{Binding IconPath}" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Version}" Foreground="Gray" FontSize="12" VerticalAlignment="Bottom" />
</StackPanel>
<TextBlock Text="{Binding Description}" Foreground="Gray" FontSize="12" TextWrapping="Wrap" />
<TextBlock Text="{Binding Author}" Foreground="LightGray" FontSize="11" />
</StackPanel>
<TextBlock Text="{Binding Description}" Foreground="Gray" FontSize="12" TextWrapping="Wrap" />
<TextBlock Text="{Binding Author}" Foreground="LightGray" FontSize="11" />
</StackPanel>
<ToggleSwitch Grid.Column="2"
IsChecked="{Binding !IsDisabled}"
VerticalAlignment="Center"
OnContent="" OffContent="" />
</Grid>
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
<ToggleButton IsChecked="{Binding IsExpanded}"
IsVisible="{Binding HasSettings}"
ToolTip.Tip="Settings">
<ui:SymbolIcon Symbol="Settings" />
</ToggleButton>
<ToggleSwitch IsChecked="{Binding !IsDisabled}"
OnContent="" OffContent="" />
</StackPanel>
</Grid>
<!-- Settings Panel -->
<Border IsVisible="{Binding IsExpanded}"
Background="{DynamicResource SolidBackgroundFillColorBase}"
BorderBrush="{DynamicResource ControlElevationBorderBrush}"
BorderThickness="1"
CornerRadius="4"
Padding="10"
Margin="48,0,0,10">
<controls:WpfControlHost Content="{Binding SettingControl}" Height="500" />
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>