- A+
一个跨平台的ChatGPT
悬浮窗工具
使用avalonia
实现的ChatGPT
的工具,设计成悬浮窗,并且支持插件。
如何实现悬浮窗?
在使用avalonia
实现悬浮窗也是非常的简单的。
实现我们需要将窗体设置成无边框
在Window
根节点添加一下属性,想要在Linux下生效请务必添加SystemDecorations
属性
ExtendClientAreaToDecorationsHint="True" ExtendClientAreaChromeHints="NoChrome" ExtendClientAreaTitleBarHeightHint="-1" SystemDecorations="None"
这样我们的窗口就设置成了无边框。
然后我们还需要将窗体的大小固定,
Height="50" MaxHeight="50" Width="{Binding Width}" MaxWidth="{Binding Width}"
高度固定,宽度绑定到ViewModel
的Width
属性中,默认270
,
接下来给出所有代码,
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:Gotrays.Suspension.Client.ViewModels" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:valueConverter="clr-namespace:Gotrays.Suspension.Client.ValueConverter" xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" xmlns:avedit="https://github.com/avaloniaui/avaloniaedit" xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Gotrays.Suspension.Client.Views.MainWindow" x:DataType="vm:MainWindowViewModel" ExtendClientAreaToDecorationsHint="True" ExtendClientAreaChromeHints="NoChrome" ExtendClientAreaTitleBarHeightHint="-1" SystemDecorations="None" WindowStartupLocation="CenterScreen" Height="50" MaxHeight="50" Width="{Binding Width}" MaxWidth="{Binding Width}" Icon="/Assets/ai.png" Title="Gotrays.Suspension.Client"> <Window.Resources> <valueConverter:ImageConverter x:Key="ImageConverter" /> <valueConverter:ChatToStyleConverter x:Key="ChatToStyleConverter" /> </Window.Resources> <Design.DataContext> <vm:MainWindowViewModel /> </Design.DataContext> <Window.Styles> <Style Selector="Window"> <Setter Property="BorderThickness" Value="0" /> <Setter Property="Padding" Value="0" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="BorderBrush" Value="Transparent" /> </Style> <Style Selector="TextBox.red:pointerover"> <Setter Property="Opacity" Value="1" /> </Style> </Window.Styles> <Border Name="MainBorder" CornerRadius="1000" Background="Black" Margin="0,0,0,0" Padding="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Width="100" Height="50"> <Grid> <!-- 图标 --> <Image Source="../Assets/ai.png" Name="Logo" HorizontalAlignment="Left" VerticalAlignment="Center" Width="46" Tapped="Logo_OnTapped" RenderOptions.BitmapInterpolationMode="HighQuality" PointerPressed="OnLogoClick" PointerEntered="Logo_OnPointerEntered" PointerExited="Logo_OnPointerExited" Height="46" Margin="0,0,0,0" /> <!-- 模型选择 --> <Popup Name="ModulePopup" IsOpen="False" PlacementTarget="{Binding ElementName=MainBorder}" PlacementMode="Top"> <StackPanel Margin="5"> <Border Background="#1F1F1F" BorderBrush="Black" BorderThickness="1" CornerRadius="12" MaxHeight="400" Width="120"> <ScrollViewer Name="ModuleScrollViewer" VerticalScrollBarVisibility="Auto"> <ItemsControl CornerRadius="12" ItemsSource="{Binding Modules}" Margin="2"> <ItemsControl.ItemTemplate> <DataTemplate> <Border Margin="5" Background="{Binding Color}" PointerExited="OnSelectStackPointerExited" PointerEntered="OnSelectStackPointerEntered" PointerPressed="OnSelectStackPointerPressed" Tag="{Binding GetThis}" CornerRadius="8"> <!-- 左边显示图标,右边显示名称 --> <StackPanel Orientation="Horizontal"> <Image RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding Icon, Converter={StaticResource ImageConverter}}" HorizontalAlignment="Left" Width="20" Height="20" /> <TextBlock TextWrapping="Wrap" Width="60" Text="{Binding Title}" Margin="5" Foreground="White" /> </StackPanel> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Border> </StackPanel> </Popup> <!-- 静止状态下的搜索按钮 --> <Border PointerPressed="SearchBorder_OnPointerPressed" PointerEntered="searchBorder_PointerEnter" PointerExited="OnPointerExited" Name="searchBorder" CornerRadius="1000" Background="#000000" BorderBrush="#FFFFFF" BorderThickness="1" Margin="50,0,0,0" Padding="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Width="46" Height="46" Cursor="Hand"> <Image Source="../Assets/search.png" RenderOptions.BitmapInterpolationMode="HighQuality" HorizontalAlignment="Center" VerticalAlignment="Center" Width="20" Height="20" Margin="0,0,0,0" /> </Border> <!-- 当点击搜索按钮时,显示搜索框 --> <TextBox FontSize="20" Name="SearchText" Margin="50,0,0,0" IsVisible="False" Width="0" Height="40" HorizontalAlignment="Left" VerticalAlignment="Center"> <TextBox.Styles> <Styles> <Style Selector="TextBox"> <Setter Property="CornerRadius" Value="0,50,50,0"></Setter> </Style> </Styles> </TextBox.Styles> <TextBox.Transitions> <Transitions> <DoubleTransition Property="Width" Duration="0:0:0.1" /> </Transitions> </TextBox.Transitions> </TextBox> <!-- 消息显示区域 --> <Popup x:Name="MessagePopup" IsOpen="False" PlacementTarget="{Binding ElementName=MainBorder}" PlacementMode="Bottom"> <StackPanel PointerEntered="MessagePopup_OnPointerEntered" PointerExited="MessagePopup_OnPointerExited" Margin="5"> <Border Name="MessageBorder" Background="#1F1F1F" BorderBrush="Black" BorderThickness="1" CornerRadius="12" MaxHeight="300"> <ScrollViewer Name="ScrollViewer" VerticalScrollBarVisibility="Auto"> <ItemsControl ItemsSource="{Binding Messages}" CornerRadius="12" Margin="2"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Margin="5"> <StackPanel.Resources> <valueConverter:ChatToBackgroundConverter x:Key="ChatToBackgroundConverter" /> </StackPanel.Resources> <Border Background="{Binding Chat, Converter={StaticResource ChatToBackgroundConverter}}" CornerRadius="5"> <md:MarkdownScrollViewer VerticalAlignment="Stretch" MarkdownStyleName="Standard" SaveScrollValueWhenContentUpdated="True" Markdown="{Binding Message}"> <md:MarkdownScrollViewer.Styles> <Style Selector="ctxt|CCode"> <Style.Setters> <Setter Property="BorderBrush" Value="Green" /> <Setter Property="BorderThickness" Value="2" /> <Setter Property="Padding" Value="2" /> <Setter Property="MonospaceFontFamily" Value="Meiryo" /> <Setter Property="Foreground" Value="DarkGreen" /> <Setter Property="Background" Value="LightGreen" /> </Style.Setters> </Style> <Style Selector="Border.CodeBlock"> <Style.Setters> <Setter Property="BorderBrush" Value="Blue" /> <Setter Property="BorderThickness" Value="0,5,0,5" /> <Setter Property="Margin" Value="5,0,5,0" /> <Setter Property="Background" Value="LightBlue" /> </Style.Setters> </Style> <Style Selector="TextBlock.CodeBlock"> <Style.Setters> <Setter Property="Foreground" Value="DarkBlue" /> <Setter Property="FontFamily" Value="Meiryo" /> </Style.Setters> </Style> <Style Selector="avedit|TextEditor"> <Setter Property="Background" Value="Gray" /> <Setter Property="CornerRadius" Value="10"></Setter> </Style> </md:MarkdownScrollViewer.Styles> </md:MarkdownScrollViewer> </Border> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Border> </StackPanel> </Popup> </Grid> <Border.Transitions> <Transitions> <DoubleTransition Property="Width" Duration="0:0:0.2" /> </Transitions> </Border.Transitions> </Border> </Window>
只需要设置无边框并且固定大小。悬浮窗的效果就达到了。
我们看看执行效果
就这样简单的悬浮窗写好了,我们使用一下悬浮窗的搜索功能
这个就是简单的使用效果,对比其他的工具,这个悬浮窗更简洁,并且跨平台和开源。
目前的项目结构。
plugin
下面的项目是默认的插件,用于搜索系统文件(未完善)
Gotrays.Suspension.Client
则是实际的客户端。
Gotrays.Suspension.PlugIn
则是插件定义的接口规范。
Gotrays.Update
则是检查更新程序,用于更新主程序。
实现插件
plug-in
插件模块,用于扩展功能。
插件开发
1. 创建插件项目
在解决方案中创建一个类库项目,项目名称以Gotrays.Suspension.PlugIn.
开头,例如Gotrays.Suspension.PlugIn.Test
。
然后在项目中依赖Gotrays.Suspension.PlugIn
类库。
2. 创建插件类
在项目中创建一个类,继承Gotrays.Suspension.PlugIn.PlugInBase
类,例如:
using Gotrays.Suspension.PlugIn; public class SystemTools : PlugInBase { public SystemTools() { Name = "系统搜索"; // 获取system.png嵌入资源的Stream var stream = GetType().Assembly.GetManifestResourceStream("SystemTools.system.png"); if (stream == null) return; // 读取Stream到byte数组 var bytes = new byte[stream.Length]; var read = stream.Read(bytes, 0, bytes.Length); Icon = bytes; } // 搜索触发 public override async Task SearchAsync(string value) { // 打开系统搜索 Process.Start("explorer.exe", "search://" + value); await Task.CompletedTask; } protected override async Task InitAsync(IServiceCollection services){ // 插件首次加载时执行 } public override async Task BuilderServiceAsync(IServiceProvider provider) { // 这里可以得到服务提供者,可以通过服务提供者获取其他服务 } protected override void Selection() { // 当插件被选中时执行 } protected override void UnSelection() { // 当插件被取消选中时执行 } protected override async Task UnloadAsync() { // 当插件被卸载插件发生 } }
工具服务会进行自动发现,无需手动注册。
只需要将程序集放置在./plug-in
目录下即可。
服务会在一个程序集中发现所有的插件类,并且进行注册。
按照上面的方式非常的简单就集成了插件。
开源地址
Gitee:https://gitee.com/gotrays/gotrays-suspension
Github:https://github.com/239573049/Suspension
技术交流群:737776595