├── src ├── LauncherAppAvalonia │ ├── Assets │ │ └── avalonia-logo.ico │ ├── ViewModels │ │ ├── ViewModelBase.cs │ │ ├── LauncherItemViewModel.cs │ │ ├── AppSettingsViewModel.cs │ │ ├── MainWindowViewModel.cs │ │ ├── ItemEditorViewModel.cs │ │ └── MainWindowViewModel.Commands.cs │ ├── Views │ │ ├── ItemEditorView.axaml.cs │ │ ├── AppSettingsView.axaml.cs │ │ ├── MainWindow.axaml.cs │ │ ├── ItemEditorView.axaml │ │ ├── MainWindow.axaml │ │ └── AppSettingsView.axaml │ ├── Models │ │ ├── LauncherItem.cs │ │ └── AppSettings.cs │ ├── App.axaml │ ├── Program.cs │ ├── ViewLocator.cs │ ├── app.manifest │ ├── Converters │ │ └── LauncherItemTypeToStringConverter.cs │ ├── LauncherAppAvalonia.csproj │ ├── App.axaml.cs │ ├── Services │ │ └── DataService.cs │ └── Utility │ │ └── Utility.cs └── LauncherAppAvalonia.sln ├── .vscode └── settings.json ├── LICENSE ├── .gitignore ├── README.md └── .github └── copilot-instructions.md /src/LauncherAppAvalonia/Assets/avalonia-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolarianZ/LauncherAppAvalonia/main/src/LauncherAppAvalonia/Assets/avalonia-logo.ico -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace LauncherAppAvalonia.ViewModels; 4 | 5 | public class ViewModelBase : ObservableObject; -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Views/ItemEditorView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace LauncherAppAvalonia.Views; 4 | 5 | public partial class ItemEditorView : UserControl 6 | { 7 | public ItemEditorView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Views/AppSettingsView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace LauncherAppAvalonia.Views; 4 | 5 | public partial class AppSettingsView : UserControl 6 | { 7 | public AppSettingsView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/.DS_Store": true, 7 | "**/Thumbs.db": true, 8 | "**/bin": true, 9 | "**/obj": true, 10 | "**/.idea": true 11 | }, 12 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Models/LauncherItem.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace LauncherAppAvalonia.Models; 4 | 5 | public enum LauncherItemType 6 | { 7 | File, 8 | Folder, 9 | Url, 10 | Command, 11 | } 12 | 13 | public partial class LauncherItem(LauncherItemType type, string path, string? name) 14 | : ObservableObject 15 | { 16 | [ObservableProperty] 17 | private LauncherItemType _type = type; 18 | 19 | [ObservableProperty] 20 | private string _path = path; 21 | 22 | [ObservableProperty] 23 | private string? _name = name; 24 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/App.axaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Views/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Input; 3 | using LauncherAppAvalonia.ViewModels; 4 | 5 | namespace LauncherAppAvalonia.Views; 6 | 7 | public partial class MainWindow : Window 8 | { 9 | public MainWindow() 10 | { 11 | InitializeComponent(); 12 | } 13 | 14 | private void OnItemListBoxItemDoubleTapped(object? sender, TappedEventArgs e) 15 | { 16 | LauncherItemViewModel? itemVM = (sender as ListBox)?.SelectedItem as LauncherItemViewModel; 17 | if (itemVM == null) 18 | return; 19 | 20 | if (DataContext is MainWindowViewModel viewModel) 21 | viewModel.OpenItemCommand.Execute(itemVM); 22 | } 23 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Program.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using System; 3 | 4 | namespace LauncherAppAvalonia; 5 | 6 | sealed class Program 7 | { 8 | // Initialization code. Don't use any Avalonia, third-party APIs or any 9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 10 | // yet and stuff might break. 11 | [STAThread] 12 | public static void Main(string[] args) => BuildAvaloniaApp() 13 | .StartWithClassicDesktopLifetime(args); 14 | 15 | // Avalonia configuration, don't remove; also used by visual designer. 16 | public static AppBuilder BuildAvaloniaApp() 17 | => AppBuilder.Configure() 18 | .UsePlatformDetect() 19 | .WithInterFont() 20 | .LogToTrace(); 21 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/ViewModels/LauncherItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using LauncherAppAvalonia.Models; 2 | 3 | namespace LauncherAppAvalonia.ViewModels; 4 | 5 | public class LauncherItemViewModel(LauncherItem launcherItem) : ViewModelBase 6 | { 7 | public LauncherItem LauncherItem { get; } = launcherItem; 8 | public LauncherItemType Type => LauncherItem.Type; 9 | public string Path => LauncherItem.Path; 10 | public string? Name => string.IsNullOrEmpty(LauncherItem.Name) 11 | ? LauncherItem.Path 12 | : LauncherItem.Name; 13 | public string Icon => LauncherItem.Type switch 14 | { 15 | LauncherItemType.File => "📄", 16 | LauncherItemType.Folder => "📂", 17 | LauncherItemType.Url => "🌐", 18 | LauncherItemType.Command => "⌨", 19 | _ => "❓" 20 | }; 21 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/ViewLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using LauncherAppAvalonia.ViewModels; 5 | 6 | namespace LauncherAppAvalonia; 7 | 8 | public class ViewLocator : IDataTemplate 9 | { 10 | public Control? Build(object? param) 11 | { 12 | if (param is null) 13 | return null; 14 | 15 | string name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); 16 | Type? type = Type.GetType(name); 17 | 18 | if (type != null) 19 | { 20 | return (Control)Activator.CreateInstance(type)!; 21 | } 22 | 23 | return new TextBlock { Text = "Not Found: " + name }; 24 | } 25 | 26 | public bool Match(object? data) 27 | { 28 | return data is ViewModelBase; 29 | } 30 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LauncherAppAvalonia", "LauncherAppAvalonia\LauncherAppAvalonia.csproj", "{34192E6D-1EA1-41BF-8176-7E5CC66D48DB}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {34192E6D-1EA1-41BF-8176-7E5CC66D48DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {34192E6D-1EA1-41BF-8176-7E5CC66D48DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {34192E6D-1EA1-41BF-8176-7E5CC66D48DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {34192E6D-1EA1-41BF-8176-7E5CC66D48DB}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Converters/LauncherItemTypeToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Avalonia.Data.Converters; 4 | using LauncherAppAvalonia.Models; 5 | 6 | namespace LauncherAppAvalonia.Converters; 7 | 8 | public class LauncherItemTypeToStringConverter : IValueConverter 9 | { 10 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 11 | { 12 | if (value is LauncherItemType type) 13 | { 14 | return type switch 15 | { 16 | LauncherItemType.File => "文件", 17 | LauncherItemType.Folder => "文件夹", 18 | LauncherItemType.Url => "网址", 19 | LauncherItemType.Command => "命令", 20 | _ => value.ToString() 21 | }; 22 | } 23 | 24 | return value; 25 | } 26 | 27 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 ZQY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Models/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommunityToolkit.Mvvm.ComponentModel; 3 | 4 | namespace LauncherAppAvalonia.Models; 5 | 6 | public partial class AppSettings 7 | : ObservableObject 8 | { 9 | public static IReadOnlyList Themes { get; } = ["System", "Light", "Dark"]; 10 | public static IReadOnlyList Languages { get; } = ["System", "zh-CN", "en-US"]; 11 | 12 | [ObservableProperty] 13 | private string _theme = Themes[0]; 14 | 15 | [ObservableProperty] 16 | private string _language = Languages[0]; 17 | 18 | [ObservableProperty] 19 | private string? _hotkey = "Alt+Shift+Q"; 20 | 21 | 22 | public AppSettings() { } 23 | 24 | public AppSettings(AppSettings source) => Clone(source); 25 | 26 | public void Reset() 27 | { 28 | Theme = Themes[0]; 29 | Language = Languages[0]; 30 | Hotkey = "Alt+Shift+Q"; 31 | } 32 | 33 | public void Clone(AppSettings source) 34 | { 35 | Theme = source.Theme; 36 | Language = source.Language; 37 | Hotkey = source.Hotkey; 38 | } 39 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## A streamlined .gitignore for modern .NET projects 2 | ## including temporary files, build results, and 3 | ## files generated by popular .NET tools. If you are 4 | ## developing with Visual Studio, the VS .gitignore 5 | ## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 6 | ## has more thorough IDE-specific entries. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/main/Dotnet.gitignore 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | [Rr]eleases/ 15 | x64/ 16 | x86/ 17 | [Ww][Ii][Nn]32/ 18 | [Aa][Rr][Mm]/ 19 | [Aa][Rr][Mm]64/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | [Ll]ogs/ 25 | 26 | # .NET Core 27 | project.lock.json 28 | project.fragment.lock.json 29 | artifacts/ 30 | 31 | # ASP.NET Scaffolding 32 | ScaffoldingReadMe.txt 33 | 34 | # NuGet Packages 35 | *.nupkg 36 | # NuGet Symbol Packages 37 | *.snupkg 38 | 39 | # MSBuild Binary and Structured Log 40 | *.binlog 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # IDE 52 | .idea/ 53 | .vs/ 54 | 55 | # Others 56 | .DS_Store 57 | ~$* 58 | *~ 59 | CodeCoverage/ 60 | src/LauncherAppAvalonia.sln.DotSettings.user 61 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/LauncherAppAvalonia.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net9.0 5 | enable 6 | true 7 | app.manifest 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | None 23 | All 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Launcher App 2 | 3 | [English](./README.md) 4 | 5 | > 🚀 快速访问常用文件、文件夹、URL 和命令的桌面启动器应用 6 | 7 | ![使用Avalonia UI构建](https://img.shields.io/badge/Built%20with-AvaloniaUI-47848F) 8 | ![MIT许可证](https://img.shields.io/badge/License-MIT-green) 9 | 10 | ![Main Window](./doc/imgs/launcher_app_main_window.png) 11 | 12 | ## 📋 项目简介 13 | 14 | Launcher App 是一款基于 Avalonia UI 开发的桌面快速启动器工具,帮助用户快速访问常用的文件、文件夹、网站和命令。 15 | 16 | > ⚠️ **声明:** 17 | > 1. 本项目的代码主要由人工智能辅助生成。项目结构、功能实现及界面设计均使用了 AI 技术。 18 | > 2. 此文档内容亦主要由 AI 生成。 19 | > 3. 软件未经严格测试。 20 | 21 | ## ✨ 主要功能 22 | 23 | - 🗂️ 添加和管理多种类型的项目 24 | - 文件 25 | - 文件夹 26 | - 网址和 Deep Link 27 | - 命令行指令 28 | - 🔍 快速搜索项目 29 | - 🖱️ 支持拖放文件/文件夹直接添加 30 | - 📋 右键菜单提供丰富操作选项 31 | - 🌓 支持深色和浅色主题 32 | - 🌍 支持多语言 33 | - 内置中文、英文支持 34 | - 可以通过添加翻译文件来支持更多语言 35 | - ⌨️ 自定义全局快捷键呼出应用(默认是Alt+Shift+Q) 36 | - 🧩 系统托盘集成,显示最近使用的项目 37 | - 🔄 支持拖拽重新排序列表 38 | - ⚡ 通过双击或回车快速打开项目 39 | - 💬 跨平台支持 (Windows, macOS, Linux,未严格测试) 40 | 41 | ## 📥 构建 42 | 43 | 1. 确保已安装 [Node.js](https://nodejs.org/) (推荐 22 LTS 或更高版本) 44 | 45 | 2. 克隆仓库 46 | 47 | ```bash 48 | git clone https://github.com/SolarianZ/LauncherAppAvalonia.git 49 | cd LauncherAppAvalonia 50 | ``` 51 | 52 | 3. 安装依赖 53 | 54 | TODO 55 | 56 | 4. 启动应用 57 | 58 | TODO 59 | 60 | ### 📦 打包应用 61 | 62 | TODO 63 | 64 | ## 🧩 技术实现 65 | 66 | - **Avalonia UI**:跨平台桌面应用框架 67 | - **本地存储**:JSON文件持久化数据 68 | - **国际化**:多语言支持系统 69 | - **响应式UI**:适配不同尺寸和主题 70 | 71 | 更多项目细节参考 [copilot-instructions](./.github/copilot-instructions.md) 。 72 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/ViewModels/AppSettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Avalonia.Media; 5 | using CommunityToolkit.Mvvm.ComponentModel; 6 | using CommunityToolkit.Mvvm.Input; 7 | using LauncherAppAvalonia.Models; 8 | using LauncherAppAvalonia.Services; 9 | 10 | namespace LauncherAppAvalonia.ViewModels; 11 | 12 | public partial class AppSettingsViewModel : ViewModelBase 13 | { 14 | [ObservableProperty] 15 | private AppSettings _appSettings; 16 | 17 | [ObservableProperty] 18 | private IBrush? _viewBackground; 19 | 20 | 21 | private readonly Action _onClose; 22 | private readonly Action _onSave; 23 | 24 | 25 | // For Preview 26 | public AppSettingsViewModel() : this(new AppSettings(), () => { }, _ => { }) { } 27 | 28 | public AppSettingsViewModel(AppSettings appSettings, Action onClose, Action onSave) 29 | { 30 | _appSettings = appSettings; 31 | 32 | _onClose = onClose; 33 | _onSave = onSave; 34 | } 35 | 36 | 37 | #region Command 38 | 39 | [RelayCommand] 40 | private void SaveSettings() => _onSave(AppSettings); 41 | 42 | [RelayCommand] 43 | private void CloseSettings() => _onClose(); 44 | 45 | [RelayCommand] 46 | private void ClearItems() 47 | { 48 | // TODO 49 | } 50 | 51 | [RelayCommand] 52 | private void OpenDataFolder() 53 | { 54 | string dataFolder = DataService.UserDataFolder; 55 | if (!Directory.Exists(dataFolder)) 56 | Directory.CreateDirectory(dataFolder); 57 | 58 | Utility.OpenFolder(dataFolder); 59 | } 60 | 61 | #endregion 62 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Avalonia; 3 | using Avalonia.Controls.ApplicationLifetimes; 4 | using Avalonia.Data.Core.Plugins; 5 | using Avalonia.Markup.Xaml; 6 | using LauncherAppAvalonia.Services; 7 | using LauncherAppAvalonia.ViewModels; 8 | using LauncherAppAvalonia.Views; 9 | 10 | namespace LauncherAppAvalonia; 11 | 12 | public partial class App : Application 13 | { 14 | public override void Initialize() 15 | { 16 | AvaloniaXamlLoader.Load(this); 17 | } 18 | 19 | public override void OnFrameworkInitializationCompleted() 20 | { 21 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 22 | { 23 | // Avoid duplicate validations from both Avalonia and the CommunityToolkit. 24 | // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins 25 | DisableAvaloniaDataAnnotationValidation(); 26 | 27 | DataService dataService = new DataService(); 28 | desktop.MainWindow = new MainWindow 29 | { 30 | DataContext = new MainWindowViewModel(dataService), 31 | }; 32 | } 33 | 34 | base.OnFrameworkInitializationCompleted(); 35 | } 36 | 37 | private void DisableAvaloniaDataAnnotationValidation() 38 | { 39 | // Get an array of plugins to remove 40 | var dataValidationPluginsToRemove = 41 | BindingPlugins.DataValidators.OfType().ToArray(); 42 | 43 | // remove each entry found 44 | foreach (var plugin in dataValidationPluginsToRemove) 45 | { 46 | BindingPlugins.DataValidators.Remove(plugin); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using CommunityToolkit.Mvvm.ComponentModel; 6 | using LauncherAppAvalonia.Models; 7 | using LauncherAppAvalonia.Services; 8 | 9 | namespace LauncherAppAvalonia.ViewModels; 10 | 11 | public partial class MainWindowViewModel : ViewModelBase 12 | { 13 | public bool IsItemEditorViewVisible => ItemEditorViewModel is not null; 14 | public bool IsAppSettingsViewVisible => AppSettingsViewModel is not null; 15 | 16 | [ObservableProperty] 17 | private ObservableCollection _items; 18 | 19 | [ObservableProperty] 20 | private string? _searchText = string.Empty; 21 | 22 | [ObservableProperty] 23 | private ObservableCollection _filteredItems = new(); 24 | 25 | [ObservableProperty] 26 | private LauncherItemViewModel? _selectedItem; 27 | 28 | [ObservableProperty] 29 | [NotifyPropertyChangedFor(nameof(IsItemEditorViewVisible))] 30 | private ItemEditorViewModel? _itemEditorViewModel; 31 | 32 | [ObservableProperty] 33 | [NotifyPropertyChangedFor(nameof(IsAppSettingsViewVisible))] 34 | private AppSettingsViewModel? _appSettingsViewModel; 35 | 36 | private readonly DataService _dataService; 37 | 38 | 39 | public MainWindowViewModel(DataService dataService) 40 | { 41 | _dataService = dataService; 42 | 43 | // 初始化数据 44 | List items = _dataService.GetItems(); 45 | _items = new ObservableCollection(items); 46 | Items.CollectionChanged += (_, _) => UpdateFilteredItems(); 47 | UpdateFilteredItems(); 48 | } 49 | 50 | /// 51 | protected override void OnPropertyChanged(PropertyChangedEventArgs e) 52 | { 53 | base.OnPropertyChanged(e); 54 | 55 | if (e.PropertyName is nameof(SearchText) or nameof(Items)) 56 | { 57 | UpdateFilteredItems(); 58 | } 59 | } 60 | 61 | private void UpdateFilteredItems() 62 | { 63 | FilteredItems.Clear(); 64 | foreach (LauncherItem item in Items) 65 | { 66 | if (MatchesSearchText(item, SearchText)) 67 | FilteredItems.Add(new LauncherItemViewModel(item)); 68 | } 69 | } 70 | 71 | private static bool MatchesSearchText(LauncherItem item, string? searchText) 72 | { 73 | if (string.IsNullOrWhiteSpace(searchText)) 74 | return true; 75 | 76 | return item.Name?.Contains(searchText, StringComparison.OrdinalIgnoreCase) == true || 77 | item.Path.Contains(searchText, StringComparison.OrdinalIgnoreCase) || 78 | item.Type.ToString().Contains(searchText, StringComparison.OrdinalIgnoreCase); 79 | } 80 | } -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # GitHub Copilot 指导文件 2 | 3 | ## 项目概述 4 | 5 | Launcher App 是一个基于 Avalonia UI 的启动器应用程序,用于管理用户常用的文件、文件夹、URL和指令。 6 | 该应用程序包含项目列表界面(主界面)、项目编辑界面、项目右键菜单、设置界面、托盘图标、托盘右键菜单等。 7 | 8 | 9 | ## 主要功能 10 | 11 | 1. **项目管理** 12 | - 添加、编辑和移除项目(文件、文件夹、URL、命令) 13 | - 拖放添加文件/文件夹 14 | - 项目排序与搜索 15 | - 右键菜单操作(打开、复制路径、在文件夹中显示等) 16 | 17 | 2. **用户界面** 18 | - 主窗口(项目列表) 19 | - 项目编辑窗口 20 | - 设置窗口 21 | - 系统托盘集成(显示最近使用的8个项目和退出选项) 22 | - 键盘导航与快捷键(Alt+Shift+Q 呼出应用) 23 | - 统一的Toast提示组件 24 | 25 | 26 | ## 项目结构 27 | 28 | ``` 29 | LauncherAppAvalonia/src/ 30 | ├── Program.cs # 应用入口 31 | └── TODO 其他待补充... 32 | ``` 33 | 34 | 35 | ## 编码规范 36 | 37 | 此项目是 Avalonia UI 技术的示例项目,需要遵守以下规范,以便入门者阅读学习。 38 | 39 | 1. 注重代码质量和可读性,使用有意义的变量和函数名称。 40 | 2. 添加详细的注释,尤其是处理平台特定代码时。 41 | 3. 分离关注点,将不同功能模块化处理。 42 | 4. 使用异步编程处理IO操作,避免阻塞主线程。 43 | 5. 避免代码重复,将常用功能提取为模块或共享函数。 44 | 6. 遵守Avalonia UI推荐的最佳实践。 45 | 7. 打印日志时使用英文,不需要多语言支持,避免乱码。 46 | 47 | 关注Avalonia UI的版本变更:https://docs.avaloniaui.net/docs/stay-up-to-date/upgrade-from-0.10 48 | 49 | 50 | ### 代码优化建议 51 | 52 | 1. **UI/UX 一致性** 53 | - 确保所有窗口在主题切换、语言切换时有一致的行为 54 | 55 | 2. **模块化改进** 56 | - 将重复的主题和语言处理函数抽象为共享模块 57 | - 通过预加载脚本提供共享的类型和常量定义,避免重复定义 58 | 59 | 3. **错误处理** 60 | - 添加更全面的错误捕获和用户友好的错误显示 61 | - 实现操作日志记录功能 62 | 63 | 64 | ## Avalonia UI 常见错误及解决方案 65 | 66 | 在开发过程中遇到的一些常见错误和解决方案,可以作为参考: 67 | 68 | 1. **类型转换错误** 69 | - **错误描述**: 从 `IEnumerable` 转换为 `IReadOnlyList` 失败 70 | - **解决方案**: 使用 `.ToList()` 方法进行显式转换,例如: 71 | ```csharp 72 | // 转换为List以匹配需要的参数类型 73 | ViewModel.HandleDroppedItem(files.ToList(), this); 74 | ``` 75 | 76 | 2. **XAML绑定错误** 77 | - **错误描述**: 无法解析转换器,如 `ObjectConverters.IsZero` 或 `BoolConverters.ToString` 78 | - **解决方案**: 79 | - 使用内联表达式代替转换器,例如 `IsVisible="{Binding !FilteredItems.Count}"` 80 | - 对于标题等简单文本,可以使用静态文本而非复杂绑定 81 | 82 | 3. **DataTemplate绑定错误** 83 | - **错误描述**: 无法解析DataTemplate中的属性 84 | - **解决方案**: 85 | - 显式声明DataTemplate的数据类型:`` 86 | - 确保绑定到公共属性而非方法,如将 `{Binding GetIcon()}` 替换为 `{Binding Icon}` 87 | - 在ViewModel中添加相应的属性:`public string Icon => GetIcon();` 88 | 89 | 4. **ContextMenu绑定问题** 90 | - **错误描述**: ContextMenu默认不继承DataContext,导致绑定失败 91 | - **解决方案**: 使用特殊的绑定语法访问父元素的DataContext: 92 | ```xml 93 | 94 | 95 | 96 | ``` 97 | 98 | 5. **编译时文件锁定** 99 | - **错误描述**: DLL被其他进程锁定,无法覆盖 100 | - **解决方案**: 101 | - 使用 `dotnet clean` 清理项目后再构建 102 | - 关闭所有可能运行的应用实例 103 | - 重启IDE或开发工具 104 | 105 | 6. **ListBox控件命名错误** 106 | - **错误描述**: `Items` 属性不存在 107 | - **解决方案**: 在Avalonia中使用 `ItemsSource` 而非 `Items` 绑定集合数据 108 | 109 | 这些问题大多与Avalonia UI的特定实现方式有关,特别是与其他XAML框架(如WPF或UWP)的微小差异造成的。在Avalonia开发中,推荐经常参考[官方文档](https://docs.avaloniaui.net)以及示例项目。 110 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Views/ItemEditorView.axaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 编辑项目 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 44 | 45 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | ERROR TEXT 错误信息 68 | 69 | ERROR TEXT 错误信息 70 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Services/DataService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | using LauncherAppAvalonia.Models; 8 | 9 | namespace LauncherAppAvalonia.Services; 10 | 11 | public class DataService 12 | { 13 | public static string UserDataFolder { get; } 14 | public static string ItemsFilePath { get; } 15 | public static string SettingsFilePath { get; } 16 | 17 | private readonly List _items = new(); 18 | private readonly AppSettings _settings = new(); 19 | private readonly JsonSerializerOptions _jsonSerializerOptions = new() 20 | { 21 | WriteIndented = true, 22 | Converters = 23 | { 24 | new JsonStringEnumConverter() 25 | } 26 | }; 27 | 28 | 29 | static DataService() 30 | { 31 | string appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 32 | UserDataFolder = Path.Combine(appDataFolder, "LauncherAppAvalonia", "UserData"); 33 | ItemsFilePath = Path.Combine(UserDataFolder, "items.json"); 34 | SettingsFilePath = Path.Combine(UserDataFolder, "settings.json"); 35 | } 36 | 37 | public DataService() 38 | { 39 | LoadItems(); 40 | LoadSettings(); 41 | } 42 | 43 | 44 | #region Items 45 | 46 | public List GetItems() 47 | { 48 | return [.._items]; 49 | } 50 | 51 | public void SetItems(IEnumerable? items) 52 | { 53 | _items.Clear(); 54 | if (items != null) 55 | _items.AddRange(items); 56 | SaveItems(); 57 | } 58 | 59 | private void LoadItems() 60 | { 61 | _items.Clear(); 62 | 63 | try 64 | { 65 | if (!File.Exists(ItemsFilePath)) 66 | return; 67 | 68 | string json = File.ReadAllText(ItemsFilePath); 69 | List? loadedItems = JsonSerializer.Deserialize>(json, _jsonSerializerOptions); 70 | if (loadedItems != null) 71 | _items.AddRange(loadedItems); 72 | } 73 | catch (Exception ex) 74 | { 75 | Debug.WriteLine($"Error loading items: {ex.Message}"); 76 | } 77 | } 78 | 79 | private void SaveItems() 80 | { 81 | try 82 | { 83 | Directory.CreateDirectory(UserDataFolder); 84 | string json = JsonSerializer.Serialize(_items, _jsonSerializerOptions); 85 | File.WriteAllText(ItemsFilePath, json); 86 | } 87 | catch (Exception ex) 88 | { 89 | Debug.WriteLine($"Error saving items: {ex.Message}"); 90 | } 91 | } 92 | 93 | #endregion 94 | 95 | 96 | #region Settings 97 | 98 | public AppSettings GetSettings() 99 | { 100 | return new AppSettings(_settings); 101 | } 102 | 103 | public void SetSettings(AppSettings? settings) 104 | { 105 | if (settings != null) 106 | _settings.Clone(settings); 107 | else 108 | _settings.Reset(); 109 | 110 | SaveSettings(); 111 | } 112 | 113 | private void LoadSettings() 114 | { 115 | _settings.Reset(); 116 | 117 | try 118 | { 119 | if (!File.Exists(SettingsFilePath)) 120 | return; 121 | 122 | string json = File.ReadAllText(SettingsFilePath); 123 | AppSettings? loadedSettings = JsonSerializer.Deserialize(json, _jsonSerializerOptions); 124 | if (loadedSettings != null) 125 | _settings.Clone(loadedSettings); 126 | } 127 | catch (Exception ex) 128 | { 129 | Debug.WriteLine($"Error loading settings: {ex.Message}"); 130 | } 131 | } 132 | 133 | private void SaveSettings() 134 | { 135 | try 136 | { 137 | Directory.CreateDirectory(UserDataFolder); 138 | string json = JsonSerializer.Serialize(_settings, _jsonSerializerOptions); 139 | File.WriteAllText(SettingsFilePath, json); 140 | } 141 | catch (Exception ex) 142 | { 143 | Debug.WriteLine($"Error saving settings: {ex.Message}"); 144 | } 145 | } 146 | 147 | #endregion 148 | } -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Views/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 46 | 49 | 52 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 68 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/LauncherAppAvalonia/Views/AppSettingsView.axaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 |