一个跨平台的 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 ,
接下来给出所有代码,
< Windowxmlns= "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:ImageConverterx:Key= "ImageConverter"/>
< valueConverter:ChatToStyleConverterx:Key= "ChatToStyleConverter"/>
</ Window.Resources>
< Design.DataContext>
< vm:MainWindowViewModel/>
</ Design.DataContext>
< Window.Styles>
< StyleSelector= "Window">
< SetterProperty= "BorderThickness"Value= "0"/>
< SetterProperty= "Padding"Value= "0"/>
< SetterProperty= "Background"Value= "Transparent"/>
< SetterProperty= "BorderBrush"Value= "Transparent"/>
</ Style>
< StyleSelector= "TextBox.red:pointerover">
< SetterProperty= "Opacity"Value= "1"/>
</ Style>
</ Window.Styles>
< BorderName= "MainBorder"CornerRadius= "1000"Background= "Black"Margin= "0,0,0,0"Padding= "0,0,0,0"
HorizontalAlignment= "Left"VerticalAlignment= "Center"Width= "100"Height= "50">
< Grid>
<!-- 图标 -->
< ImageSource= "../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"/>
<!-- 模型选择 -->
< PopupName= "ModulePopup"IsOpen= "False"PlacementTarget= "{Binding ElementName=MainBorder}"
PlacementMode= "Top">
< StackPanelMargin= "5">
< BorderBackground= "#1F1F1F"BorderBrush= "Black"BorderThickness= "1"CornerRadius= "12"
MaxHeight= "400"Width= "120">
< ScrollViewerName= "ModuleScrollViewer"VerticalScrollBarVisibility= "Auto">
< ItemsControlCornerRadius= "12"ItemsSource= "{Binding Modules}"Margin= "2">
< ItemsControl.ItemTemplate>
< DataTemplate>
< BorderMargin= "5"
Background= "{Binding Color}"
PointerExited= "OnSelectStackPointerExited"
PointerEntered= "OnSelectStackPointerEntered"
PointerPressed= "OnSelectStackPointerPressed"
Tag= "{Binding GetThis}"
CornerRadius= "8">
<!-- 左边显示图标,右边显示名称 -->
< StackPanelOrientation= "Horizontal">
< Image
RenderOptions.BitmapInterpolationMode= "HighQuality"
Source= "{Binding Icon, Converter={StaticResource ImageConverter}}"
HorizontalAlignment= "Left"
Width= "20"
Height= "20"/>
< TextBlockTextWrapping= "Wrap"Width= "60"Text= "{Binding Title}"
Margin= "5"Foreground= "White"/>
</ StackPanel>
</ Border>
</ DataTemplate>
</ ItemsControl.ItemTemplate>
</ ItemsControl>
</ ScrollViewer>
</ Border>
</ StackPanel>
</ Popup>
<!-- 静止状态下的搜索按钮 -->
< BorderPointerPressed= "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">
< ImageSource= "../Assets/search.png"
RenderOptions.BitmapInterpolationMode= "HighQuality"
HorizontalAlignment= "Center"VerticalAlignment= "Center"
Width= "20"Height= "20"Margin= "0,0,0,0"/>
</ Border>
<!-- 当点击搜索按钮时,显示搜索框 -->
< TextBoxFontSize= "20"Name= "SearchText"Margin= "50,0,0,0"IsVisible= "False"Width= "0"Height= "40"
HorizontalAlignment= "Left"VerticalAlignment= "Center">
< TextBox.Styles>
< Styles>
< StyleSelector= "TextBox">
< SetterProperty= "CornerRadius"Value= "0,50,50,0"> </ Setter>
</ Style>
</ Styles>
</ TextBox.Styles>
< TextBox.Transitions>
< Transitions>
< DoubleTransitionProperty= "Width"Duration= "0:0:0.1"/>
</ Transitions>
</ TextBox.Transitions>
</ TextBox>
<!-- 消息显示区域 -->
< Popupx:Name= "MessagePopup"
IsOpen= "False"
PlacementTarget= "{Binding ElementName=MainBorder}"
PlacementMode= "Bottom">
< StackPanel
PointerEntered= "MessagePopup_OnPointerEntered"
PointerExited= "MessagePopup_OnPointerExited"Margin= "5">
< BorderName= "MessageBorder"
Background= "#1F1F1F"
BorderBrush= "Black"
BorderThickness= "1"
CornerRadius= "12"
MaxHeight= "300">
< ScrollViewerName= "ScrollViewer"VerticalScrollBarVisibility= "Auto">
< ItemsControlItemsSource= "{Binding Messages}"CornerRadius= "12"Margin= "2">
< ItemsControl.ItemTemplate>
< DataTemplate>
< StackPanelMargin= "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>
< StyleSelector= "ctxt|CCode">
< Style.Setters>
< SetterProperty= "BorderBrush"Value= "Green"/>
< SetterProperty= "BorderThickness"Value= "2"/>
< SetterProperty= "Padding"Value= "2"/>
< SetterProperty= "MonospaceFontFamily"Value= "Meiryo"/>
< SetterProperty= "Foreground"Value= "DarkGreen"/>
< SetterProperty= "Background"Value= "LightGreen"/>
</ Style.Setters>
</ Style>
< StyleSelector= "Border.CodeBlock">
< Style.Setters>
< SetterProperty= "BorderBrush"Value= "Blue"/>
< SetterProperty= "BorderThickness"Value= "0,5,0,5"/>
< SetterProperty= "Margin"Value= "5,0,5,0"/>
< SetterProperty= "Background"Value= "LightBlue"/>
</ Style.Setters>
</ Style>
< StyleSelector= "TextBlock.CodeBlock">
< Style.Setters>
< SetterProperty= "Foreground"Value= "DarkBlue"/>
< SetterProperty= "FontFamily"Value= "Meiryo"/>
</ Style.Setters>
</ Style>
< StyleSelector= "avedit|TextEditor">
< SetterProperty= "Background"Value= "Gray"/>
< SetterProperty= "CornerRadius"Value= "10"> </ Setter>
</ Style>
</ md:MarkdownScrollViewer.Styles>
</ md:MarkdownScrollViewer>
</ Border>
</ StackPanel>
</ DataTemplate>
</ ItemsControl.ItemTemplate>
</ ItemsControl>
</ ScrollViewer>
</ Border>
</ StackPanel>
</ Popup>
</ Grid>
< Border.Transitions>
< Transitions>
< DoubleTransitionProperty= "Width"Duration= "0:0:0.2"/>
</ Transitions>
</ Border.Transitions>
</ Border>
</ Window>
只需要设置无边框并且固定大小。悬浮窗的效果就达到了。
我们看看执行效果
image-20230702133719931
就这样简单的悬浮窗写好了,我们使用一下悬浮窗的搜索功能
image-20230702133757221
这个就是简单的使用效果,对比其他的工具,这个悬浮窗更简洁,并且跨平台和开源。
image-20230702133839454
目前的项目结构。
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 类,例如:
usingGotrays.Suspension.PlugIn;
publicclassSystemTools: PlugInBase
{
publicSystemTools( )
{
Name = "系统搜索";
// 获取system.png嵌入资源的Stream
varstream = GetType.Assembly.GetManifestResourceStream( "SystemTools.system.png");
if(stream == null) return;
// 读取Stream到byte数组
varbytes = newbyte[stream.Length];
varread = stream.Read(bytes, 0, bytes.Length);
Icon = bytes;
}
// 搜索触发
publicoverrideasyncTask SearchAsync( stringvalue)
{
// 打开系统搜索
Process.Start( "explorer.exe", "search://"+ value);
awaitTask.CompletedTask;
}
protectedoverrideasyncTask InitAsync( IServiceCollection services) {
// 插件首次加载时执行
}
publicoverrideasyncTask BuilderServiceAsync( IServiceProvider provider)
{
// 这里可以得到服务提供者,可以通过服务提供者获取其他服务
}
protectedoverridevoidSelection( )
{
// 当插件被选中时执行
}
protectedoverridevoidUnSelection( )
{
// 当插件被取消选中时执行
}
protectedoverrideasyncTask UnloadAsync( )
{
// 当插件被卸载插件发生
}
}
工具服务会进行自动发现,无需手动注册。 只需要将程序集放置在 ./plug-in 目录下即可。 服务会在一个程序集中发现所有的插件类,并且进行注册。
按照上面的方式非常的简单就集成了插件。
开源地址
Gitee:https://gitee.com/gotrays/gotrays-suspension返回搜狐,查看更多
责任编辑: