Compare commits

..

86 Commits

Author SHA1 Message Date
zsyg
6624cee1a3 添加软件图标 2025-07-11 16:10:24 +08:00
zsyg
165703575b 修改版本号 2025-07-11 16:08:47 +08:00
zsyg
efc1498a25 添加应用卡片 2025-07-11 16:07:43 +08:00
zsyg
481fc24735 修改版本号 2025-07-11 08:17:57 +08:00
zsyg
bebd804b7a 美化ui 2025-07-11 08:17:26 +08:00
zsyg
4202013265 添加依赖 2025-07-11 08:16:11 +08:00
zsyg
d26ac95dd5 添加图片转换器代码 2025-07-10 13:21:27 +08:00
zsyg
0432c5fa42 添加图片转换器工具图标 2025-07-10 13:21:03 +08:00
zsyg
94da204ca9 提交同转换器程序 2025-07-10 13:20:34 +08:00
zsyg
9770c58166 添加应用卡片 2025-07-10 13:19:02 +08:00
zsyg
c006f78693 修改版本号 2025-07-10 13:18:22 +08:00
zsyg
ab6080cdd0 修改版本号 2025-07-09 12:53:17 +08:00
zsyg
4b77884d32 增强调试信息 2025-07-09 11:36:12 +08:00
zsyg
d45e3029b7 支持显示图标 2025-07-09 11:35:36 +08:00
zsyg
41b056c074 支持显示图标 2025-07-09 11:35:08 +08:00
zsyg
6955655e64 支持显示图标 2025-07-09 11:34:21 +08:00
zsyg
4310557659 添加图标 2025-07-08 21:19:34 +08:00
zsyg
510362688a 添加Code::Blocks 2025-07-08 21:19:02 +08:00
zsyg
34962bba3f 添加Dev-cpp 2025-07-08 21:16:09 +08:00
zsyg
0bab6ccaab 添加构建工具 2025-07-08 21:08:34 +08:00
zsyg
973d984c55 废弃的零宽字符隐藏器
2025-07-07 20:37:34 +08:00
zsyg
280a9122b9 废弃的聊天室代码
😭😭😭
2025-07-07 20:34:34 +08:00
zsyg
837aba38ba Add files via upload 2025-07-07 19:15:34 +08:00
zsyg
92c2a57773 添加应用卡片 2025-07-07 19:14:20 +08:00
zsyg
43027d7953 Add files via upload 2025-07-07 18:07:29 +08:00
zsyg
6d2711da08 删除艺术字 2025-07-07 17:52:13 +08:00
zsyg
e47f905a8c Add files via upload 2025-07-07 16:59:18 +08:00
zsyg
6899e4767f 添加文本转换器程序 2025-07-07 16:53:44 +08:00
zsyg
d5a0564847 添加文本转换器代码 2025-07-07 16:52:56 +08:00
zsyg
525c823397 添加应用图标 2025-07-06 13:47:54 +08:00
zsyg
7db2d8813e Add files via upload 2025-07-06 13:47:04 +08:00
zsyg
7216f62cef 添加OCR代码
这是一个废弃代码
2025-07-06 12:51:43 +08:00
zsyg
a777991b8c 添加ClamAV图标 2025-07-05 19:34:02 +08:00
zsyg
e2c6c52b32 Add files via upload 2025-07-05 19:33:25 +08:00
zsyg
33089c39b6 Update features.html 2025-07-05 19:15:03 +08:00
zsyg
45805178cc 修改版本号 2025-07-05 19:09:08 +08:00
zsyg
08a11f025a 修复bug和扩展功能 2025-07-05 19:08:44 +08:00
zsyg
5bb3886bc5 添加乌班图的图标 2025-07-05 17:58:10 +08:00
zsyg
ce5f964776 添加应用卡片 2025-07-05 17:57:27 +08:00
zsyg
f52c7908d7 添加应用卡片图标 2025-07-05 17:32:23 +08:00
zsyg
abcbf06493 添加应用卡片图标 2025-07-05 17:32:04 +08:00
zsyg
f56bcb3627 添加应用卡片图标 2025-07-05 17:31:40 +08:00
zsyg
f789c7904a 修改版本号 2025-07-05 17:30:57 +08:00
zsyg
e04709637c 修改版本号 2025-07-05 17:30:38 +08:00
zsyg
e39f976607 添加更多应用卡片 2025-07-05 17:30:18 +08:00
zsyg
0d9ec0ad44 修改版本号 2025-07-05 16:44:38 +08:00
zsyg
122ada92d9 修改版本号 2025-07-05 16:44:11 +08:00
zsyg
d571729c89 修改版本号 2025-07-05 16:43:38 +08:00
zsyg
351e1e97c3 修改布局 2025-07-05 16:43:14 +08:00
zsyg
cff4c39e8c 添加版本号 2025-07-05 14:42:42 +08:00
zsyg
5f41f57e8d Add files via upload 2025-07-05 14:41:38 +08:00
zsyg
f2756ddbd8 添加图标 2025-07-05 14:40:40 +08:00
zsyg
d7a90ca422 添加notepad--图标 2025-07-05 14:30:46 +08:00
zsyg
7404bdfb5d 添加notepad-- 2025-07-05 14:28:40 +08:00
zsyg
5e8de310df 添加图标提取器图标 2025-07-05 14:17:32 +08:00
zsyg
f3cca9b3a1 添加图标提取器代码 2025-07-05 14:16:38 +08:00
zsyg
2154f465b7 Add files via upload 2025-07-05 14:16:00 +08:00
zsyg
99bbda4668 添加版本号 2025-07-05 10:24:08 +08:00
zsyg
d1d69da3e3 Add files via upload 2025-07-05 10:22:19 +08:00
zsyg
4c8cb807d9 添加ollama 2025-07-05 10:20:04 +08:00
zsyg
1ad64feab9 添加ollama 2025-07-05 10:19:19 +08:00
zsyg
ef7c582c50 删除视频压缩工具 2025-07-02 20:14:29 +08:00
zsyg
ee65689048 Add files via upload 2025-07-02 18:01:12 +08:00
zsyg
4ef8099054 添加自启动工具 2025-07-02 18:00:22 +08:00
zsyg
6dd8819f22 Add files via upload 2025-07-02 17:59:18 +08:00
zsyg
59900081da Create dotnet.yml 2025-07-02 17:19:33 +08:00
zsyg
2c60d0b970 Create dotnet-desktop.yml 2025-07-02 17:17:38 +08:00
zsyg
815ba41bdc 提高代码质量 2025-07-02 16:53:47 +08:00
zsyg
459c0bc9d7 Add files via upload 2025-07-02 16:05:28 +08:00
zsyg
7c78a118a9 解决非win环境下编译问题 2025-07-02 16:04:10 +08:00
zsyg
d5f944280e Add files via upload 2025-07-02 11:11:16 +08:00
zsyg
1cd722bf89 Add files via upload 2025-07-02 11:10:09 +08:00
zsyg
e34a954777 添加艺术字 2025-07-02 11:09:21 +08:00
zsyg
125bf6b0d4 移动位置 2025-07-02 10:29:36 +08:00
zsyg
983a0d5bf4 修改位置 2025-07-02 10:28:33 +08:00
zsyg
259b075541 允许自定义下载和修改编译路径 2025-07-02 10:25:17 +08:00
zsyg
d454ac0fdc 允许自定义下载路径 2025-07-02 10:10:02 +08:00
zsyg
d5c83d854c 删除没有必要的文件 2025-07-02 09:17:44 +08:00
zsyg
1c2bc713be 提高代码质量 2025-07-02 09:16:18 +08:00
zsyg
1539c665f0 提供代码质量 2025-07-02 09:15:43 +08:00
zsyg
074e55fbbc Add files via upload 2025-07-02 09:14:43 +08:00
zsyg
334fa56070 修复代码潜在问题 2025-07-02 09:12:49 +08:00
zsyg
0268e13b56 Add files via upload 2025-07-01 19:02:06 +08:00
zsyg
cba6c9eeca Update README.md 2025-07-01 11:31:02 +08:00
zsyg
70a776125a 修改密码生成器代码位置 2025-07-01 10:34:32 +08:00
zsyg
f7250dae08 修复主题系统带来的颜色 2025-07-01 10:26:26 +08:00
225 changed files with 8904 additions and 2615 deletions

115
.github/workflows/dotnet-desktop.yml vendored Normal file
View File

@@ -0,0 +1,115 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build, test, sign and package a WPF or Windows Forms desktop application
# built on .NET Core.
# To learn how to migrate your existing application to .NET Core,
# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework
#
# To configure this workflow:
#
# 1. Configure environment variables
# GitHub sets default environment variables for every workflow run.
# Replace the variables relative to your project in the "env" section below.
#
# 2. Signing
# Generate a signing certificate in the Windows Application
# Packaging Project or add an existing signing certificate to the project.
# Next, use PowerShell to encode the .pfx file using Base64 encoding
# by running the following Powershell script to generate the output string:
#
# $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte
# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt'
#
# Open the output file, SigningCertificate_Encoded.txt, and copy the
# string inside. Then, add the string to the repo as a GitHub secret
# and name it "Base64_Encoded_Pfx."
# For more information on how to configure your signing certificate for
# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing
#
# Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key".
# See "Build the Windows Application Packaging project" below to see how the secret is used.
#
# For more information on GitHub Actions, refer to https://github.com/features/actions
# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications,
# refer to https://github.com/microsoft/github-actions-for-desktop-apps
name: .NET Core Desktop
on:
push:
分支: [ "main" ]
pull_request:
分支: [ "main" ]
jobs:
build:
strategy:
matrix:
configuration: [Debug, Release]
runs-on: windows-latest # For a list of available runner types, refer to
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
env:
Solution_Name: your-solution-name # Replace with your solution name, i.e. MyWpfApp.sln.
Test_Project_Path: your-test-project-path # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.
Wap_Project_Directory: your-wap-project-directory-name # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package.
Wap_Project_Path: your-wap-project-path # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj.
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# Install the .NET Core workload
- name: Install .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v2
# Execute all unit tests in the solution
- name: Execute unit tests
run: dotnet test
# Restore the application to populate the obj folder with RuntimeIdentifiers
- name: Restore the application
run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration
env:
Configuration: ${{ matrix.configuration }}
# Decode the base 64 encoded pfx and save the Signing_Certificate
- name: Decode the pfx
run: |
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
$certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx
[IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte)
# Create the app package by building and packaging the Windows Application Packaging project
- name: Create the app package
run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }}
env:
Appx_Bundle: Always
Appx_Bundle_Platforms: x86|x64
Appx_Package_Build_Mode: StoreUpload
Configuration: ${{ matrix.configuration }}
# Remove the pfx
- name: Remove the pfx
run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx
# Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: MSIX Package
path: ${{ env.Wap_Project_Directory }}\AppPackages

28
.github/workflows/dotnet.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
name: .NET
on:
push:
分支: [ "main" ]
pull_request:
分支: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal

View File

@@ -1,144 +1,144 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace AppStore
{
public class AboutUserControl : UserControl
{
private PictureBox logo = null!;
private Label infoLabel = null!;
public AboutUserControl()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.Dock = DockStyle.Fill;
this.BackColor = ThemeManager.BackgroundColor;
this.Padding = new Padding(20);
// 创建主布局面板
TableLayoutPanel mainLayout = new TableLayoutPanel();
mainLayout.Dock = DockStyle.Fill;
mainLayout.BackColor = ThemeManager.BackgroundColor;
mainLayout.ColumnCount = 1;
mainLayout.RowCount = 2;
mainLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.Padding = new Padding(0, 20, 0, 20);
// 初始化并添加应用图标
logo = new PictureBox();
try
{
logo.Image = Image.FromFile("img/png/kortapp-z.png");
}
catch (Exception ex)
{
Logger.LogError($"无法加载应用图标: {ex.Message}");
logo.Image = SystemIcons.Application.ToBitmap();
}
logo.SizeMode = PictureBoxSizeMode.Zoom;
logo.Width = 200;
logo.Height = 200;
logo.Anchor = AnchorStyles.None;
logo.Margin = new Padding(0, 0, 0, 20);
mainLayout.Controls.Add(logo, 0, 0);
// 初始化并添加应用信息
infoLabel = new Label();
infoLabel.Text = "kortapp-z\n版本: 1.1.1\n作者: zs-yg\n一个简单、开源的应用商店\nkortapp-z是完全免费\n基于.NET8和C/C++的软件";
infoLabel.Font = new Font("Microsoft YaHei", 12);
infoLabel.AutoSize = false;
infoLabel.Width = 300;
infoLabel.Height = 130; // 增加高度确保文字完整显示
infoLabel.TextAlign = ContentAlignment.MiddleCenter;
infoLabel.Anchor = AnchorStyles.None;
mainLayout.Controls.Add(infoLabel, 0, 1);
// 调整主布局为3行
mainLayout.RowCount = 3;
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
// 在底部添加GitHub链接区域
TableLayoutPanel githubPanel = new TableLayoutPanel();
githubPanel.Dock = DockStyle.Bottom;
githubPanel.BackColor = ThemeManager.BackgroundColor;
githubPanel.Height = 60;
githubPanel.ColumnCount = 3;
githubPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
githubPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
githubPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
githubPanel.RowCount = 1;
githubPanel.Padding = new Padding(10);
// 添加GitHub图标
PictureBox githubIcon = new PictureBox();
try
{
githubIcon.Image = Image.FromFile("img/jpg/github.jpg");
}
catch (Exception ex)
{
Logger.LogError($"无法加载GitHub图标: {ex.Message}");
githubIcon.Image = SystemIcons.Application.ToBitmap();
}
githubIcon.SizeMode = PictureBoxSizeMode.Zoom;
githubIcon.Width = 30;
githubIcon.Height = 30;
githubIcon.Cursor = Cursors.Hand;
githubIcon.Click += (s, e) => {
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "https://github.com/zs-yg/kortapp-z",
UseShellExecute = true
});
};
// 添加文字说明
Label githubLabel = new Label();
githubLabel.Text = "🤗🤗🤗开源地址 🌟 欢迎点star和提交pr 🚀";
githubLabel.Font = new Font("Microsoft YaHei", 10);
githubLabel.AutoSize = true;
githubLabel.Margin = new Padding(10, 0, 0, 0);
// 创建包含图标和文字的面板
Panel linkPanel = new Panel();
linkPanel.AutoSize = true;
linkPanel.BackColor = ThemeManager.BackgroundColor;
linkPanel.Controls.Add(githubIcon);
linkPanel.Controls.Add(githubLabel);
githubIcon.Location = new Point(0, 0);
githubLabel.Location = new Point(githubIcon.Width + 10, 5);
// 将链接面板添加到中间列
githubPanel.Controls.Add(linkPanel, 1, 0);
this.Controls.Add(mainLayout);
this.Controls.Add(githubPanel);
}
}
// 保留原AboutForm作为容器(可选)
public class AboutForm : Form
{
public AboutForm()
{
this.Text = "关于 kortapp-z";
this.Size = new Size(500, 400);
this.StartPosition = FormStartPosition.CenterScreen;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
var aboutControl = new AboutUserControl();
this.Controls.Add(aboutControl);
}
}
}
using System;
using System.Drawing;
using System.Windows.Forms;
namespace AppStore
{
public class AboutUserControl : UserControl
{
private PictureBox logo = null!;
private Label infoLabel = null!;
public AboutUserControl()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.Dock = DockStyle.Fill;
this.BackColor = ThemeManager.BackgroundColor;
this.Padding = new Padding(20);
// 创建主布局面板
TableLayoutPanel mainLayout = new TableLayoutPanel();
mainLayout.Dock = DockStyle.Fill;
mainLayout.BackColor = ThemeManager.BackgroundColor;
mainLayout.ColumnCount = 1;
mainLayout.RowCount = 2;
mainLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.Padding = new Padding(0, 20, 0, 20);
// 初始化并添加应用图标
logo = new PictureBox();
try
{
logo.Image = Image.FromFile("img/png/kortapp-z.png");
}
catch (Exception ex)
{
Logger.LogError($"无法加载应用图标: {ex.Message}");
logo.Image = SystemIcons.Application.ToBitmap();
}
logo.SizeMode = PictureBoxSizeMode.Zoom;
logo.Width = 200;
logo.Height = 200;
logo.Anchor = AnchorStyles.None;
logo.Margin = new Padding(0, 0, 0, 20);
mainLayout.Controls.Add(logo, 0, 0);
// 初始化并添加应用信息
infoLabel = new Label();
infoLabel.Text = "kortapp-z\n版本: 1.3.1\n作者: zs-yg\n一个简单、开源的应用商店\nkortapp-z是完全免费\n基于.NET8和C/C++的软件";
infoLabel.Font = new Font("Microsoft YaHei", 12);
infoLabel.AutoSize = false;
infoLabel.Width = 300;
infoLabel.Height = 130; // 增加高度确保文字完整显示
infoLabel.TextAlign = ContentAlignment.MiddleCenter;
infoLabel.Anchor = AnchorStyles.None;
mainLayout.Controls.Add(infoLabel, 0, 1);
// 调整主布局为3行
mainLayout.RowCount = 3;
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
// 在底部添加GitHub链接区域
TableLayoutPanel githubPanel = new TableLayoutPanel();
githubPanel.Dock = DockStyle.Bottom;
githubPanel.BackColor = ThemeManager.BackgroundColor;
githubPanel.Height = 60;
githubPanel.ColumnCount = 3;
githubPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
githubPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
githubPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
githubPanel.RowCount = 1;
githubPanel.Padding = new Padding(10);
// 添加GitHub图标
PictureBox githubIcon = new PictureBox();
try
{
githubIcon.Image = Image.FromFile("img/jpg/github.jpg");
}
catch (Exception ex)
{
Logger.LogError($"无法加载GitHub图标: {ex.Message}");
githubIcon.Image = SystemIcons.Application.ToBitmap();
}
githubIcon.SizeMode = PictureBoxSizeMode.Zoom;
githubIcon.Width = 30;
githubIcon.Height = 30;
githubIcon.Cursor = Cursors.Hand;
githubIcon.Click += (s, e) => {
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "https://github.com/zs-yg/kortapp-z",
UseShellExecute = true
});
};
// 添加文字说明
Label githubLabel = new Label();
githubLabel.Text = "🤗🤗🤗开源地址 🌟 欢迎点star和提交pr 🚀";
githubLabel.Font = new Font("Microsoft YaHei", 10);
githubLabel.AutoSize = true;
githubLabel.Margin = new Padding(10, 0, 0, 0);
// 创建包含图标和文字的面板
Panel linkPanel = new Panel();
linkPanel.AutoSize = true;
linkPanel.BackColor = ThemeManager.BackgroundColor;
linkPanel.Controls.Add(githubIcon);
linkPanel.Controls.Add(githubLabel);
githubIcon.Location = new Point(0, 0);
githubLabel.Location = new Point(githubIcon.Width + 10, 5);
// 将链接面板添加到中间列
githubPanel.Controls.Add(linkPanel, 1, 0);
this.Controls.Add(mainLayout);
this.Controls.Add(githubPanel);
}
}
// 保留原AboutForm作为容器(可选)
public class AboutForm : Form
{
public AboutForm()
{
this.Text = "关于 kortapp-z";
this.Size = new Size(500, 400);
this.StartPosition = FormStartPosition.CenterScreen;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
var aboutControl = new AboutUserControl();
this.Controls.Add(aboutControl);
}
}
}

View File

@@ -13,7 +13,9 @@ namespace AppStore
{
private PictureBox iconBox;
private Label nameLabel;
private Panel namePanel;
private Button downloadBtn;
private Color borderColor = SystemColors.ControlDark;
private static readonly ConcurrentDictionary<string, System.Drawing.Drawing2D.GraphicsPath> PathCache =
new ConcurrentDictionary<string, System.Drawing.Drawing2D.GraphicsPath>();
@@ -27,6 +29,7 @@ namespace AppStore
// 确保关键对象不为null
iconBox = new PictureBox() { SizeMode = PictureBoxSizeMode.StretchImage };
nameLabel = new Label() { Text = string.Empty };
namePanel = new Panel();
downloadBtn = new Button() { Text = "下载" };
// 确保DownloadManager已初始化
@@ -45,51 +48,142 @@ namespace AppStore
this.Padding = new Padding(10);
// 异步初始化卡片路径和边框
// 预加载边框路径
Task.Run(() => {
InitializeCardPath();
InitializeBorder();
// 确保在主线程注册事件
this.Invoke((MethodInvoker)(() => {
this.Paint += (sender, e) => {
if (BorderCache.IsEmpty)
{
Task.Run(() => {
InitializeBorder();
this.Invoke((MethodInvoker)(() => this.Invalidate()));
});
}
};
}));
});
// 应用图标
iconBox = new PictureBox();
iconBox.Size = new Size(80, 80);
iconBox.Location = new Point((Width - 80) / 2, 15);
iconBox.SizeMode = PictureBoxSizeMode.StretchImage;
this.Controls.Add(iconBox);
// 应用图标 - 添加null检查
if (iconBox != null && this != null && this.Controls != null)
{
iconBox.Size = new Size(80, 80);
iconBox.Location = new Point((Width - 80) / 2, 15);
iconBox.SizeMode = PictureBoxSizeMode.StretchImage;
this.Controls.Add(iconBox);
}
else
{
Logger.LogWarning("iconBox或Controls为null");
}
// 应用名称
// 应用名称 - 使用Panel包裹Label实现边框颜色
if (namePanel != null)
{
namePanel.Size = new Size(Width - 20, 40);
namePanel.Location = new Point(10, 100);
namePanel.Paint += (sender, e) => {
try
{
if (e != null && e.Graphics != null && namePanel != null)
{
var rect = namePanel.ClientRectangle;
if (rect.Width > 0 && rect.Height > 0)
{
ControlPaint.DrawBorder(e.Graphics, rect,
borderColor, ButtonBorderStyle.Solid);
}
}
}
catch (Exception ex)
{
Logger.LogWarning($"绘制namePanel边框失败: {ex.Message}");
}
};
}
nameLabel = new Label();
nameLabel.AutoSize = false;
nameLabel.Size = new Size(Width - 20, 40);
nameLabel.Location = new Point(10, 100);
nameLabel.Dock = DockStyle.Fill;
nameLabel.Font = new Font("Microsoft YaHei", 10, FontStyle.Bold);
nameLabel.TextAlign = ContentAlignment.MiddleCenter;
this.Controls.Add(nameLabel);
if (namePanel != null && nameLabel != null)
{
namePanel.Controls.Add(nameLabel);
}
// 初始主题设置
UpdateLabelTheme();
// 订阅主题变化事件
ThemeManager.ThemeChanged += (theme) => UpdateLabelTheme();
if (this != null && this.Controls != null && namePanel != null)
{
this.Controls.Add(namePanel);
}
// 下载按钮
downloadBtn = new Button();
downloadBtn.Text = "下载";
downloadBtn.Size = new Size(100, 32);
downloadBtn.Location = new Point((Width - 100) / 2, 150);
downloadBtn.BackColor = Color.FromArgb(0, 120, 215);
downloadBtn.ForeColor = Color.White;
downloadBtn.FlatStyle = FlatStyle.Flat;
downloadBtn.FlatAppearance.BorderSize = 0;
downloadBtn.Cursor = Cursors.Hand;
downloadBtn.Font = new Font("Microsoft YaHei", 9);
// 按钮悬停效果
downloadBtn.MouseEnter += (s, e) => {
downloadBtn.BackColor = Color.FromArgb(0, 150, 255);
};
downloadBtn.MouseLeave += (s, e) => {
// 下载按钮 - 添加null检查
if (downloadBtn != null)
{
downloadBtn.Text = "下载";
downloadBtn.Size = new Size(100, 32);
downloadBtn.Location = new Point((Width - 100) / 2, 150);
downloadBtn.BackColor = Color.FromArgb(0, 120, 215);
};
downloadBtn.Click += DownloadBtn_Click;
this.Controls.Add(downloadBtn);
downloadBtn.Visible = ShowDownloadButton;
downloadBtn.ForeColor = Color.White;
downloadBtn.FlatStyle = FlatStyle.Flat;
downloadBtn.FlatAppearance.BorderSize = 0;
downloadBtn.Cursor = Cursors.Hand;
downloadBtn.Font = new Font("Microsoft YaHei", 9);
// 按钮悬停效果 - 添加null检查
downloadBtn.MouseEnter += (s, e) => {
if (downloadBtn != null)
{
downloadBtn.BackColor = Color.FromArgb(0, 150, 255);
}
};
downloadBtn.MouseLeave += (s, e) => {
if (downloadBtn != null)
{
downloadBtn.BackColor = Color.FromArgb(0, 120, 215);
}
};
downloadBtn.Click += DownloadBtn_Click;
this.Controls.Add(downloadBtn);
downloadBtn.Visible = ShowDownloadButton;
}
}
private void UpdateLabelTheme()
{
if (ThemeManager.CurrentTheme == ThemeManager.ThemeMode.Dark)
{
nameLabel.BackColor = Color.Black;
nameLabel.ForeColor = Color.White;
namePanel.BackColor = Color.Black;
borderColor = Color.White;
}
else
{
nameLabel.BackColor = Color.White;
nameLabel.ForeColor = Color.Black;
namePanel.BackColor = Color.White;
borderColor = SystemColors.ControlDark;
}
if (namePanel != null && !namePanel.IsDisposed)
{
namePanel.Invalidate(); // 触发重绘
}
else
{
Logger.LogWarning("namePanel为null或已释放");
}
}
/// <summary>
@@ -101,52 +195,58 @@ namespace AppStore
// 使用卡片尺寸作为缓存键
string cacheKey = $"{Width}_{Height}_10";
// 检查缓存中是否已有路径
// 双重检查锁模式确保线程安全
if (!BorderCache.TryGetValue(cacheKey, out var borderPath))
{
// 创建临时文件存储路径数据
string tempFile = Path.GetTempFileName();
try
lock (BorderCache)
{
// 配置C++程序启动参数
ProcessStartInfo startInfo = new ProcessStartInfo
if (!BorderCache.TryGetValue(cacheKey, out borderPath))
{
FileName = Path.Combine(Application.StartupPath, "resource", "border_renderer.exe"),
Arguments = $"{Width} {Height} 10 \"{tempFile}\"", // 传递宽高和圆角半径
UseShellExecute = false, // 不显示命令行窗口
CreateNoWindow = true // 静默运行
};
// 启动C++程序计算路径
using (var process = Process.Start(startInfo))
{
process.WaitForExit();
// 检查计算结果
if (process.ExitCode == 0 && File.Exists(tempFile))
// 创建临时文件存储路径数据
string tempFile = Path.GetTempFileName();
try
{
// 读取C++程序生成的路径点
var lines = File.ReadAllLines(tempFile);
PointF[] points = lines.Select(line => {
var parts = line.Split(','); // 解析坐标点
return new PointF(float.Parse(parts[0]), float.Parse(parts[1]));
}).ToArray();
// 创建GraphicsPath对象
borderPath = new System.Drawing.Drawing2D.GraphicsPath();
borderPath.AddLines(points); // 添加路径
// 缓存路径对象
BorderCache.TryAdd(cacheKey, borderPath);
// 配置C++程序启动参数
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = Path.Combine(Application.StartupPath, "resource", "border_renderer.exe"),
Arguments = $"{Width} {Height} 10 \"{tempFile}\"", // 传递宽高和圆角半径
UseShellExecute = false, // 不显示命令行窗口
CreateNoWindow = true // 静默运行
};
// 启动C++程序计算路径
using (var process = Process.Start(startInfo))
{
process.WaitForExit();
// 检查计算结果
if (process.ExitCode == 0 && File.Exists(tempFile))
{
// 读取C++程序生成的路径点
var lines = File.ReadAllLines(tempFile);
PointF[] points = lines.Select(line => {
var parts = line.Split(','); // 解析坐标点
return new PointF(float.Parse(parts[0]), float.Parse(parts[1]));
}).ToArray();
// 创建GraphicsPath对象
borderPath = new System.Drawing.Drawing2D.GraphicsPath();
borderPath.AddLines(points); // 添加路径点
// 缓存路径对象
BorderCache.TryAdd(cacheKey, borderPath);
}
}
}
finally
{
// 确保临时文件被删除
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}
}
}
finally
{
// 确保临时文件被删除
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}
}
@@ -157,6 +257,12 @@ namespace AppStore
{
base.OnPaint(e);
// 确保边框已初始化
if (BorderCache.IsEmpty)
{
InitializeBorder();
}
// 绘制背景
using (var brush = new SolidBrush(this.BackColor)) {
e.Graphics.FillRectangle(brush, this.ClientRectangle);
@@ -219,22 +325,37 @@ namespace AppStore
};
// 启动C++程序计算路径
using (var process = Process.Start(startInfo)) {
process.WaitForExit();
// 检查计算结果
if (process.ExitCode == 0 && File.Exists(tempFile)) {
// 读取生成的路径点
var lines = File.ReadAllLines(tempFile);
PointF[] points = lines.Select(line => {
var parts = line.Split(','); // 解析坐标
return new PointF(float.Parse(parts[0]), float.Parse(parts[1]));
}).ToArray();
// 创建并缓存路径对象
path = new System.Drawing.Drawing2D.GraphicsPath();
path.AddLines(points);
PathCache.TryAdd(cacheKey, path);
if (startInfo != null)
{
using (var process = Process.Start(startInfo))
{
if (process != null)
{
process.WaitForExit();
// 检查计算结果
if (process.ExitCode == 0 && File.Exists(tempFile))
{
try
{
// 读取生成的路径点
var lines = File.ReadAllLines(tempFile);
PointF[] points = lines.Select(line => {
var parts = line.Split(','); // 解析坐标
return new PointF(float.Parse(parts[0]), float.Parse(parts[1]));
}).ToArray();
// 创建并缓存路径对象
path = new System.Drawing.Drawing2D.GraphicsPath();
path.AddLines(points);
PathCache.TryAdd(cacheKey, path);
}
catch (Exception ex)
{
Logger.LogWarning($"读取路径点失败: {ex.Message}");
}
}
}
}
}
} catch {
@@ -310,8 +431,18 @@ namespace AppStore
public void UpdateDisplay()
{
nameLabel.Text = AppName;
iconBox.Image = AppIcon;
if (nameLabel != null && AppName != null)
{
nameLabel.Text = AppName;
}
else
{
Logger.LogWarning("nameLabel或AppName为null");
}
if (iconBox != null && AppIcon != null)
{
iconBox.Image = AppIcon;
}
}
private void DownloadBtn_Click(object sender, EventArgs e)
@@ -321,14 +452,15 @@ namespace AppStore
// 更严格的null检查
// 更严格的null检查包括DownloadManager.Instance和其方法
// 全面的null和状态检查
var downloadManager = DownloadManager.Instance;
if (sender == null || e == null ||
string.IsNullOrWhiteSpace(DownloadUrl) ||
string.IsNullOrWhiteSpace(AppName) ||
!this.IsHandleCreated ||
this.IsDisposed ||
DownloadManager.Instance == null ||
DownloadManager.Instance.DownloadItems == null ||
DownloadManager.Instance.StartDownload == null)
downloadManager == null ||
downloadManager.DownloadItems == null ||
downloadManager.StartDownload == null)
{
return;
}
@@ -336,7 +468,7 @@ namespace AppStore
string safeAppName = AppName ?? "未知应用";
string fileName = $"{safeAppName.Replace(" ", "_")}.exe";
DownloadManager.Instance.StartDownload(fileName, DownloadUrl);
downloadManager.StartDownload(fileName, DownloadUrl);
string message = $"已开始下载: {safeAppName}";
this.Invoke((MethodInvoker)delegate {

57
AppSearch.cs Normal file
View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace AppStore
{
public static class AppSearch
{
/// <summary>
/// 搜索应用卡片
/// </summary>
/// <param name="flowPanel">包含应用卡片的FlowLayoutPanel</param>
/// <param name="searchText">搜索文本</param>
public static void SearchApps(FlowLayoutPanel flowPanel, string searchText)
{
if (flowPanel == null || string.IsNullOrWhiteSpace(searchText))
{
ShowAllApps(flowPanel);
return;
}
foreach (Control control in flowPanel.Controls)
{
if (control is AppCard appCard)
{
bool isMatch = IsMatchSearch(appCard.AppName, searchText);
control.Visible = isMatch;
}
}
}
/// <summary>
/// 显示所有应用卡片
/// </summary>
public static void ShowAllApps(FlowLayoutPanel? flowPanel)
{
if (flowPanel == null) return;
foreach (Control control in flowPanel.Controls)
{
control.Visible = true;
}
}
/// <summary>
/// 检查应用名称是否匹配搜索文本
/// </summary>
private static bool IsMatchSearch(string appName, string searchText)
{
if (string.IsNullOrEmpty(appName)) return false;
// 不区分大小写比较
return appName.Contains(searchText, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -12,6 +12,7 @@
<WarningsAsErrors>CS8618</WarningsAsErrors>
<ApplicationIcon>img\ico\icon.ico</ApplicationIcon>
<Platforms>x86;x64</Platforms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>
<ItemGroup>
@@ -33,4 +34,9 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SunnyUI" Version="3.8.6" />
<PackageReference Include="ZXing.Net" Version="0.16.9" />
</ItemGroup>
</Project>

View File

@@ -53,10 +53,11 @@ namespace AppStore
this.BackColor = ThemeManager.CurrentTheme == ThemeManager.ThemeMode.Light
? Color.White
: Color.Black;
this.BorderStyle = BorderStyle.FixedSingle;
this.BorderStyle = BorderStyle.None; // 禁用默认边框
this.ForeColor = ThemeManager.CurrentTheme == ThemeManager.ThemeMode.Light
? Color.Black
: Color.White;
this.Paint += DownloadItem_Paint; // 添加自定义绘制
// 文件名标签
nameLabel = new Label();
@@ -98,6 +99,17 @@ namespace AppStore
nameLabel.Text = FileName;
progressBar.Value = Progress;
statusLabel.Text = Status;
this.Invalidate(); // 触发重绘
}
private void DownloadItem_Paint(object sender, PaintEventArgs e)
{
// 自定义边框绘制
using (var pen = new Pen(ThemeManager.BorderColor, 1))
{
e.Graphics.DrawRectangle(pen,
new Rectangle(0, 0, this.Width - 1, this.Height - 1));
}
}
private void CancelBtn_Click(object sender, EventArgs e)

View File

@@ -1,346 +1,508 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppStore
{
public class DownloadManager
{
[DllImport("shell32.dll")]
private static extern int SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid,
uint dwFlags,
IntPtr hToken,
out IntPtr ppszPath);
private static DownloadManager instance = null!;
public static DownloadManager Instance => instance ??= new DownloadManager();
private Process? currentProcess;
public List<DownloadItem> DownloadItems { get; } = new List<DownloadItem>();
public event Action<DownloadItem> DownloadAdded = delegate { };
public event Action<DownloadItem> DownloadProgressChanged = delegate { };
public event Action<DownloadItem> DownloadCompleted = delegate { };
// 内部类封装进程结果
private class ProcessResult
{
public int ExitCode { get; set; } = -1;
public bool HasExited { get; set; }
}
// 安全获取进程结果
private ProcessResult GetProcessResult(Process? process)
{
var result = new ProcessResult();
if (process == null) return result;
try
{
if (!process.HasExited)
{
process.WaitForExit(5000);
}
result.HasExited = process.HasExited;
if (result.HasExited)
{
result.ExitCode = process.ExitCode;
}
}
catch
{
// 忽略所有异常,使用默认值
}
return result;
}
public void StartDownload(string fileName, string url)
{
// 从URL获取原始文件名用于显示
var uri = new Uri(url);
var originalFileName = Path.GetFileName(uri.LocalPath);
var downloadItem = new DownloadItem
{
FileName = originalFileName, // 显示原始文件名
Progress = 0,
Status = "准备下载"
};
DownloadItems.Add(downloadItem);
DownloadAdded?.Invoke(downloadItem);
Task.Run(() => DownloadFile(downloadItem, fileName, url));
}
private void DownloadFile(DownloadItem downloadItem, string fileName, string url)
{
try
{
// 设置下载目录为用户文件夹中的Downloads
// 获取系统下载文件夹路径
// 获取系统下载文件夹路径
string downloadsDir;
IntPtr pathPtr = IntPtr.Zero;
try
{
// 使用SHGetKnownFolderPath API获取下载文件夹
var downloadsFolderGuid = new Guid("374DE290-123F-4565-9164-39C4925E467B");
if (SHGetKnownFolderPath(downloadsFolderGuid, 0, IntPtr.Zero, out pathPtr) != 0)
{
throw new Exception("无法获取下载文件夹路径");
}
downloadsDir = Marshal.PtrToStringUni(pathPtr);
}
catch
{
throw new Exception("无法确定下载文件夹位置,请手动指定下载路径");
}
finally
{
if (pathPtr != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pathPtr);
}
}
Directory.CreateDirectory(downloadsDir);
// 构建aria2c路径
var aria2cPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "resource", "aria2c.exe");
if (!File.Exists(aria2cPath))
{
throw new FileNotFoundException($"找不到aria2c.exe: {aria2cPath}");
}
// 设置线程数为16并添加详细日志
// 从URL获取原始文件名
var uri = new Uri(url);
var originalFileName = Path.GetFileName(uri.LocalPath);
var arguments = $"--out=\"{originalFileName}\" --dir=\"{downloadsDir}\" --split=16 --max-connection-per-server=16 {url}";
currentProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = aria2cPath,
Arguments = arguments,
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
// 获取目标文件路径
string downloadPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Downloads",
fileName);
long totalSize = 0;
// 添加进度检测超时机制
var progressTimer = new System.Timers.Timer(5000); // 5秒无更新视为完成
progressTimer.Elapsed += (s, e) => {
progressTimer.Stop();
if (downloadItem.Progress < 100) {
downloadItem.Progress = 100;
downloadItem.Status = "下载完成 (100%)";
DownloadProgressChanged?.Invoke(downloadItem);
}
};
currentProcess.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
// 重置超时计时器
progressTimer.Stop();
progressTimer.Start();
// 解析总大小
if (e.Data.Contains("Length:"))
{
var sizeStr = e.Data.Split(new[]{"Length:"}, StringSplitOptions.RemoveEmptyEntries)[1]
.Split('(')[0].Trim();
if (long.TryParse(sizeStr, out totalSize))
{
}
}
// 解析进度百分比
if (e.Data.Contains("%)"))
{
var start = e.Data.IndexOf("(") + 1;
var end = e.Data.IndexOf("%)");
if (start > 0 && end > start)
{
var progressStr = e.Data.Substring(start, end - start);
if (int.TryParse(progressStr, out int progress))
{
progress = Math.Min(progress, 100);
downloadItem.Progress = progress;
downloadItem.Status = $"下载中({progress}%)";
DownloadProgressChanged?.Invoke(downloadItem);
}
}
}
}
};
currentProcess.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
downloadItem.Status = $"错误: {e.Data}";
DownloadProgressChanged?.Invoke(downloadItem);
}
};
currentProcess.Exited += (sender, e) =>
{
var process = currentProcess;
if (process == null) return;
var result = GetProcessResult(process);
if (result.ExitCode == 0)
{
// 最终状态强制更新
downloadItem.Progress = 100;
downloadItem.Status = "下载完成 (100%)";
// 验证文件完整性
string downloadPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Downloads",
downloadItem.FileName);
if (File.Exists(downloadPath))
{
}
else
{
}
// 触发界面更新
DownloadProgressChanged?.Invoke(downloadItem);
DownloadCompleted?.Invoke(downloadItem);
downloadItem.UpdateDisplay();
try
{
// 双重确保在主线程显示提示
if (Application.OpenForms.Count > 0)
{
var mainForm = Application.OpenForms[0];
mainForm.Invoke((MethodInvoker)delegate {
MessageBox.Show(mainForm,
$"文件 {downloadItem.FileName} 已成功下载到:\n{Path.Combine(downloadsDir, downloadItem.FileName)}",
"下载完成",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
});
}
else
{
}
}
catch
{
}
}
else
{
downloadItem.Status = $"下载失败 (代码: {result.ExitCode})";
MessageBox.Show($"文件 {downloadItem.FileName} 下载失败", "下载失败",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
DownloadCompleted?.Invoke(downloadItem);
try
{
process?.Dispose();
}
finally
{
if (process != null)
{
currentProcess = null;
}
}
// 强制更新显示
downloadItem.UpdateDisplay();
};
if (!currentProcess.Start())
{
throw new Exception("进程启动失败");
}
currentProcess.BeginOutputReadLine();
currentProcess.BeginErrorReadLine();
progressTimer.Start();
}
catch (Exception ex)
{
downloadItem.Status = $"下载错误: {ex.Message}";
DownloadCompleted?.Invoke(downloadItem);
}
}
public void CancelDownload(DownloadItem item)
{
try
{
var process = currentProcess;
if (process == null || process.HasExited || process.StartInfo == null)
{
item.Status = "已取消";
DownloadProgressChanged?.Invoke(item);
return;
}
process.Kill();
process.Dispose();
currentProcess = null;
item.Status = "已取消";
DownloadProgressChanged?.Invoke(item);
}
catch (Exception ex)
{
item.Status = $"取消失败: {ex.Message}";
DownloadProgressChanged?.Invoke(item);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppStore
{
public class DownloadManager
{
[DllImport("shell32.dll")]
private static extern int SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid,
uint dwFlags,
IntPtr hToken,
out IntPtr ppszPath);
private static DownloadManager instance = null!;
public static DownloadManager Instance => instance ??= new DownloadManager();
private Process? currentProcess;
public List<DownloadItem> DownloadItems { get; } = new List<DownloadItem>();
public event Action<DownloadItem> DownloadAdded = delegate { };
public event Action<DownloadItem> DownloadProgressChanged = delegate { };
public event Action<DownloadItem> DownloadCompleted = delegate { };
// 内部类封装进程结果
private class ProcessResult
{
public int ExitCode { get; set; } = -1;
public bool HasExited { get; set; }
}
// 安全获取进程结果
private ProcessResult GetProcessResult(Process? process)
{
var result = new ProcessResult();
if (process == null || process.StartInfo == null) return result;
try
{
if (!process.HasExited)
{
process.WaitForExit(5000);
}
result.HasExited = process.HasExited;
if (result.HasExited)
{
result.ExitCode = process.ExitCode;
}
}
catch
{
// 忽略所有异常,使用默认值
}
return result;
}
public void StartDownload(string fileName, string url)
{
// 从URL获取原始文件名用于显示
var uri = new Uri(url);
var originalFileName = Path.GetFileName(uri.LocalPath);
var downloadItem = new DownloadItem
{
FileName = originalFileName, // 显示原始文件名
Progress = 0,
Status = "准备下载"
};
DownloadItems.Add(downloadItem);
DownloadAdded?.Invoke(downloadItem);
Task.Run(() => DownloadFile(downloadItem, fileName, url));
}
private void DownloadFile(DownloadItem downloadItem, string fileName, string url)
{
string downloadsDir = string.Empty;
try
{
// 获取并验证下载路径
downloadsDir = GetDownloadPath();
try
{
// 检查路径是否有效
if (string.IsNullOrWhiteSpace(downloadsDir))
{
throw new Exception("下载路径为空");
}
// 尝试创建目录(如果不存在)
Directory.CreateDirectory(downloadsDir);
// 验证目录是否可写
string testFile = Path.Combine(downloadsDir, "write_test.tmp");
File.WriteAllText(testFile, "test");
File.Delete(testFile);
}
catch (Exception ex)
{
// 回退到默认下载路径
string defaultPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Downloads");
Logger.LogError($"下载路径{downloadsDir}不可用,将使用默认路径: {defaultPath}", ex);
downloadsDir = defaultPath;
Directory.CreateDirectory(downloadsDir);
}
// 构建aria2c路径
var aria2cPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "resource", "aria2c.exe");
if (!File.Exists(aria2cPath))
{
throw new FileNotFoundException($"找不到aria2c.exe: {aria2cPath}");
}
// 设置线程数为16并添加详细日志
// 从URL获取原始文件名
var uri = new Uri(url);
var originalFileName = Path.GetFileName(uri.LocalPath);
var arguments = $"--out=\"{originalFileName}\" --dir=\"{downloadsDir}\" --split=16 --max-connection-per-server=16 {url}";
currentProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = aria2cPath,
Arguments = arguments,
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
// 获取目标文件路径
string downloadPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Downloads",
fileName);
long totalSize = 0;
// 添加进度检测超时机制
var progressTimer = new System.Timers.Timer(5000); // 5秒无更新视为完成
progressTimer.Elapsed += (s, e) => {
progressTimer.Stop();
if (downloadItem.Progress < 100) {
downloadItem.Progress = 100;
downloadItem.Status = "下载完成 (100%)";
DownloadProgressChanged?.Invoke(downloadItem);
}
};
currentProcess.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
// 重置超时计时器
progressTimer.Stop();
progressTimer.Start();
// 解析总大小
if (e.Data.Contains("Length:"))
{
var sizeStr = e.Data.Split(new[]{"Length:"}, StringSplitOptions.RemoveEmptyEntries)[1]
.Split('(')[0].Trim();
if (long.TryParse(sizeStr, out totalSize))
{
}
}
// 解析进度百分比
if (e.Data.Contains("%)"))
{
var start = e.Data.IndexOf("(") + 1;
var end = e.Data.IndexOf("%)");
if (start > 0 && end > start)
{
var progressStr = e.Data.Substring(start, end - start);
if (int.TryParse(progressStr, out int progress))
{
progress = Math.Min(progress, 100);
downloadItem.Progress = progress;
downloadItem.Status = $"下载中({progress}%)";
DownloadProgressChanged?.Invoke(downloadItem);
}
}
}
}
};
currentProcess.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
downloadItem.Status = $"错误: {e.Data}";
DownloadProgressChanged?.Invoke(downloadItem);
}
};
currentProcess.Exited += (sender, e) =>
{
var process = currentProcess;
if (process == null) return;
var result = GetProcessResult(process);
if (result.ExitCode == 0)
{
// 最终状态强制更新
downloadItem.Progress = 100;
downloadItem.Status = "下载完成 (100%)";
// 验证文件完整性
string downloadPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Downloads",
downloadItem.FileName);
if (File.Exists(downloadPath))
{
}
else
{
}
// 触发界面更新
DownloadProgressChanged?.Invoke(downloadItem);
DownloadCompleted?.Invoke(downloadItem);
downloadItem.UpdateDisplay();
try
{
// 双重确保在主线程显示提示
if (Application.OpenForms.Count > 0)
{
var mainForm = Application.OpenForms[0];
mainForm.Invoke((MethodInvoker)delegate {
MessageBox.Show(mainForm,
$"文件 {downloadItem.FileName} 已成功下载到:\n{Path.Combine(downloadsDir, downloadItem.FileName)}",
"下载完成",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
});
}
else
{
}
}
catch
{
}
}
else
{
downloadItem.Status = $"下载失败 (代码: {result.ExitCode})";
MessageBox.Show($"文件 {downloadItem.FileName} 下载失败", "下载失败",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
DownloadCompleted?.Invoke(downloadItem);
try
{
process?.Dispose();
}
finally
{
if (process != null)
{
currentProcess = null;
}
}
// 强制更新显示
downloadItem.UpdateDisplay();
};
if (!currentProcess.Start())
{
throw new Exception("进程启动失败");
}
currentProcess.BeginOutputReadLine();
currentProcess.BeginErrorReadLine();
progressTimer.Start();
}
catch (Exception ex)
{
string errorDetails = $"下载错误: {ex.Message}\n";
errorDetails += $"目标路径: {downloadsDir}\n";
errorDetails += $"URL: {url}";
downloadItem.Status = $"下载失败: {ex.Message}";
Logger.LogError(errorDetails, ex);
MessageBox.Show($"下载失败:\n{errorDetails}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
DownloadCompleted?.Invoke(downloadItem);
}
}
public void CancelDownload(DownloadItem item)
{
try
{
var process = currentProcess;
if (process?.StartInfo == null || process.HasExited)
{
item.Status = "已取消";
DownloadProgressChanged?.Invoke(item);
return;
}
process.Kill();
process.Dispose();
currentProcess = null;
item.Status = "已取消";
DownloadProgressChanged?.Invoke(item);
}
catch (Exception ex)
{
item.Status = $"取消失败: {ex.Message}";
DownloadProgressChanged?.Invoke(item);
}
}
private string GetDownloadPath()
{
string fallbackPath = string.Empty;
// 1. 优先读取用户设置的下载路径
try
{
string jsonPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"zsyg", "kortapp-z", ".date", "dl_path", "download_path.json");
Logger.Log($"尝试读取下载路径配置文件: {jsonPath}");
if (File.Exists(jsonPath))
{
string jsonString = File.ReadAllText(jsonPath);
Logger.Log($"配置文件内容: {jsonString}");
var jsonData = JsonSerializer.Deserialize<JsonElement>(jsonString);
string customPath = jsonData.GetProperty("DownloadPath").GetString()?.Trim();
if (!string.IsNullOrWhiteSpace(customPath))
{
Logger.Log($"读取到自定义路径: {customPath}");
// 处理路径格式
customPath = customPath.Replace(@"\\", @"\");
try
{
// 处理路径中的环境变量和特殊字符
customPath = Environment.ExpandEnvironmentVariables(customPath);
customPath = Path.GetFullPath(customPath);
Logger.Log($"标准化后的路径: {customPath}");
// 确保路径以目录分隔符结尾
if (!customPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
customPath += Path.DirectorySeparatorChar;
}
// 验证驱动器是否存在
string drive = Path.GetPathRoot(customPath);
if (!Directory.Exists(drive))
{
Logger.LogError($"驱动器不存在: {drive}");
throw new Exception($"驱动器 {drive} 不存在");
}
// 验证路径
if (!Directory.Exists(customPath))
{
Logger.Log($"创建目录: {customPath}");
Directory.CreateDirectory(customPath);
}
// 更严格的路径可写性测试
string testFile = Path.Combine(customPath, $"write_test_{Guid.NewGuid()}.tmp");
Logger.Log($"测试路径可写性: {testFile}");
try
{
File.WriteAllText(testFile, DateTime.Now.ToString());
string content = File.ReadAllText(testFile);
File.Delete(testFile);
Logger.Log($"路径验证成功: {customPath}");
return customPath.TrimEnd(Path.DirectorySeparatorChar);
}
catch (Exception ex)
{
Logger.LogError($"路径不可写: {customPath}", ex);
throw new Exception($"路径不可写: {customPath}");
}
}
catch (Exception ex)
{
Logger.LogError($"路径处理失败: {customPath}", ex);
throw;
}
}
}
else
{
Logger.Log("未找到下载路径配置文件");
}
}
catch (Exception ex)
{
Logger.LogError("读取自定义下载路径失败", ex);
}
// 2. 回退到系统默认下载路径
IntPtr pathPtr = IntPtr.Zero;
try
{
var downloadsFolderGuid = new Guid("374DE290-123F-4565-9164-39C4925E467B");
if (SHGetKnownFolderPath(downloadsFolderGuid, 0, IntPtr.Zero, out pathPtr) == 0)
{
string? defaultPath = Marshal.PtrToStringUni(pathPtr);
if (!string.IsNullOrEmpty(defaultPath))
{
Directory.CreateDirectory(defaultPath);
return defaultPath;
}
else
{
Logger.LogWarning("获取到的系统下载路径为空");
}
}
}
catch (Exception ex)
{
Logger.LogError("获取系统下载路径失败", ex);
}
finally
{
if (pathPtr != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pathPtr);
}
}
// 3. 最终回退到相对路径 ~/Downloads
string relativePath = "~/Downloads";
string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ??
Environment.GetFolderPath(Environment.SpecialFolder.Desktop) ??
AppDomain.CurrentDomain.BaseDirectory;
if (!string.IsNullOrEmpty(userProfile))
{
fallbackPath = relativePath.Replace("~", userProfile);
if (!string.IsNullOrEmpty(fallbackPath))
{
fallbackPath = Path.GetFullPath(fallbackPath);
}
}
try {
Directory.CreateDirectory(fallbackPath);
// 测试路径可写性
string testFile = Path.Combine(fallbackPath, "write_test.tmp");
if (!string.IsNullOrEmpty(testFile))
{
File.WriteAllText(testFile, "test");
File.Delete(testFile);
}
return fallbackPath;
}
catch {
throw new Exception($"无法使用默认下载路径: {fallbackPath}");
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

View File

@@ -1,190 +1,190 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
namespace AppStore
{
public class ImageCompressorForm : Form
{
private Button btnSelectInput = new Button();
private Button btnSelectOutput = new Button();
private Button btnCompress = new Button();
private TextBox txtInput = new TextBox();
private TextBox txtOutput = new TextBox();
private RadioButton rbLossy = new RadioButton();
private RadioButton rbLossless = new RadioButton();
private TrackBar tbQuality = new TrackBar();
private Label lblQuality = new Label();
private CheckBox cbKeepExif = new CheckBox();
private ProgressBar progressBar = new ProgressBar();
public ImageCompressorForm()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.Text = "图片压缩工具";
this.Size = new Size(500, 350);
this.StartPosition = FormStartPosition.CenterScreen;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
// 输入文件选择
btnSelectInput.Text = "选择...";
btnSelectInput.Location = new Point(400, 20);
btnSelectInput.Click += (s, e) => SelectFile(txtInput);
this.Controls.Add(btnSelectInput);
txtInput.Location = new Point(20, 20);
txtInput.Size = new Size(370, 20);
txtInput.ReadOnly = true;
this.Controls.Add(txtInput);
Label lblInput = new Label();
lblInput.Text = "输入文件:";
lblInput.Location = new Point(20, 0);
this.Controls.Add(lblInput);
// 输出文件选择
btnSelectOutput.Text = "选择...";
btnSelectOutput.Location = new Point(400, 70);
btnSelectOutput.Click += (s, e) => SelectFile(txtOutput, true);
this.Controls.Add(btnSelectOutput);
txtOutput.Location = new Point(20, 70);
txtOutput.Size = new Size(370, 20);
this.Controls.Add(txtOutput);
Label lblOutput = new Label();
lblOutput.Text = "输出文件:";
lblOutput.Location = new Point(20, 50);
this.Controls.Add(lblOutput);
// 压缩类型
rbLossy.Text = "有损压缩 (JPEG)";
rbLossy.Location = new Point(20, 110);
rbLossy.Checked = true;
this.Controls.Add(rbLossy);
rbLossless.Text = "无损压缩 (PNG)";
rbLossless.Location = new Point(20, 135);
this.Controls.Add(rbLossless);
// 质量设置
tbQuality.Minimum = 1;
tbQuality.Maximum = 1000;
tbQuality.Value = 800;
tbQuality.Location = new Point(20, 190);
tbQuality.Size = new Size(300, 50);
tbQuality.Scroll += (s, e) => lblQuality.Text = $"压缩质量: {tbQuality.Value}";
this.Controls.Add(tbQuality);
lblQuality.Text = $"压缩质量: {tbQuality.Value}";
lblQuality.Location = new Point(20, 170);
this.Controls.Add(lblQuality);
// EXIF选项
cbKeepExif.Text = "保留EXIF信息";
cbKeepExif.Location = new Point(20, 240);
this.Controls.Add(cbKeepExif);
// 压缩按钮
btnCompress.Text = "开始压缩";
btnCompress.Location = new Point(20, 280);
btnCompress.Size = new Size(460, 30);
btnCompress.Click += BtnCompress_Click;
this.Controls.Add(btnCompress);
// 调整窗体大小
this.Size = new Size(500, 370);
}
private void SelectFile(TextBox target, bool isSave = false)
{
var dialog = isSave ? new SaveFileDialog() : new OpenFileDialog() as FileDialog;
dialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp|所有文件|*.*";
if (dialog.ShowDialog() == DialogResult.OK)
{
target.Text = dialog.FileName;
}
}
private void BtnCompress_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtInput.Text) || !File.Exists(txtInput.Text))
{
MessageBox.Show("请选择有效的输入文件", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (string.IsNullOrEmpty(txtOutput.Text))
{
MessageBox.Show("请指定输出文件", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
btnCompress.Enabled = false;
try
{
string toolPath = Path.Combine(Application.StartupPath, "resource", "image_compressor.exe");
if (!File.Exists(toolPath))
{
MessageBox.Show("图片压缩工具未找到", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
string args = $"\"{txtInput.Text}\" \"{txtOutput.Text}\"";
args += $" -t {(rbLossy.Checked ? "lossy" : "lossless")}";
args += $" -q {tbQuality.Value}";
if (cbKeepExif.Checked) args += " -e";
var process = new Process();
process.StartInfo.FileName = toolPath;
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.OutputDataReceived += (s, ev) => {
if (!string.IsNullOrEmpty(ev.Data))
Console.WriteLine(ev.Data);
};
process.ErrorDataReceived += (s, ev) => {
if (!string.IsNullOrEmpty(ev.Data))
Console.Error.WriteLine(ev.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode == 0)
{
MessageBox.Show("图片压缩完成", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("图片压缩失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"压缩过程中发生错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
btnCompress.Enabled = true;
progressBar.Visible = false;
}
}
}
}
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
namespace AppStore
{
public class ImageCompressorForm : Form
{
private Button btnSelectInput = new Button();
private Button btnSelectOutput = new Button();
private Button btnCompress = new Button();
private TextBox txtInput = new TextBox();
private TextBox txtOutput = new TextBox();
private RadioButton rbLossy = new RadioButton();
private RadioButton rbLossless = new RadioButton();
private TrackBar tbQuality = new TrackBar();
private Label lblQuality = new Label();
private CheckBox cbKeepExif = new CheckBox();
private ProgressBar progressBar = new ProgressBar();
public ImageCompressorForm()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.Text = "图片压缩工具";
this.Size = new Size(500, 350);
this.StartPosition = FormStartPosition.CenterScreen;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
// 输入文件选择
btnSelectInput.Text = "选择...";
btnSelectInput.Location = new Point(400, 20);
btnSelectInput.Click += (s, e) => SelectFile(txtInput);
this.Controls.Add(btnSelectInput);
txtInput.Location = new Point(20, 20);
txtInput.Size = new Size(370, 20);
txtInput.ReadOnly = true;
this.Controls.Add(txtInput);
Label lblInput = new Label();
lblInput.Text = "输入文件:";
lblInput.Location = new Point(20, 0);
this.Controls.Add(lblInput);
// 输出文件选择
btnSelectOutput.Text = "选择...";
btnSelectOutput.Location = new Point(400, 70);
btnSelectOutput.Click += (s, e) => SelectFile(txtOutput, true);
this.Controls.Add(btnSelectOutput);
txtOutput.Location = new Point(20, 70);
txtOutput.Size = new Size(370, 20);
this.Controls.Add(txtOutput);
Label lblOutput = new Label();
lblOutput.Text = "输出文件:";
lblOutput.Location = new Point(20, 50);
this.Controls.Add(lblOutput);
// 压缩类型
rbLossy.Text = "有损压缩 (JPEG)";
rbLossy.Location = new Point(20, 110);
rbLossy.Checked = true;
this.Controls.Add(rbLossy);
rbLossless.Text = "无损压缩 (PNG)";
rbLossless.Location = new Point(20, 135);
this.Controls.Add(rbLossless);
// 质量设置
tbQuality.Minimum = 1;
tbQuality.Maximum = 1000;
tbQuality.Value = 800;
tbQuality.Location = new Point(20, 190);
tbQuality.Size = new Size(300, 50);
tbQuality.Scroll += (s, e) => lblQuality.Text = $"压缩质量: {tbQuality.Value}";
this.Controls.Add(tbQuality);
lblQuality.Text = $"压缩质量: {tbQuality.Value}";
lblQuality.Location = new Point(20, 170);
this.Controls.Add(lblQuality);
// EXIF选项
cbKeepExif.Text = "保留EXIF信息";
cbKeepExif.Location = new Point(20, 240);
this.Controls.Add(cbKeepExif);
// 压缩按钮
btnCompress.Text = "开始压缩";
btnCompress.Location = new Point(20, 280);
btnCompress.Size = new Size(460, 30);
btnCompress.Click += BtnCompress_Click;
this.Controls.Add(btnCompress);
// 调整窗体大小
this.Size = new Size(500, 370);
}
private void SelectFile(TextBox target, bool isSave = false)
{
var dialog = isSave ? new SaveFileDialog() : new OpenFileDialog() as FileDialog;
dialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp|所有文件|*.*";
if (dialog.ShowDialog() == DialogResult.OK)
{
target.Text = dialog.FileName;
}
}
private void BtnCompress_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtInput.Text) || !File.Exists(txtInput.Text))
{
MessageBox.Show("请选择有效的输入文件", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (string.IsNullOrEmpty(txtOutput.Text))
{
MessageBox.Show("请指定输出文件", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
btnCompress.Enabled = false;
try
{
string toolPath = Path.Combine(Application.StartupPath, "resource", "image_compressor.exe");
if (!File.Exists(toolPath))
{
MessageBox.Show("图片压缩工具未找到", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
string args = $"\"{txtInput.Text}\" \"{txtOutput.Text}\"";
args += $" -t {(rbLossy.Checked ? "lossy" : "lossless")}";
args += $" -q {tbQuality.Value}";
if (cbKeepExif.Checked) args += " -e";
var process = new Process();
process.StartInfo.FileName = toolPath;
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.OutputDataReceived += (s, ev) => {
if (!string.IsNullOrEmpty(ev.Data))
Console.WriteLine(ev.Data);
};
process.ErrorDataReceived += (s, ev) => {
if (!string.IsNullOrEmpty(ev.Data))
Console.Error.WriteLine(ev.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode == 0)
{
MessageBox.Show("图片压缩完成", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("图片压缩失败", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"压缩过程中发生错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
btnCompress.Enabled = true;
progressBar.Visible = false;
}
}
}
}

View File

@@ -8,14 +8,43 @@ using System.Text;
using System.Text.Json;
using System.Diagnostics;
using AppStore;
using Sunny.UI;
using System.Runtime.InteropServices;
namespace AppStore
{
/// <summary>
/// 主窗体类,负责应用程序的主界面显示和交互
/// </summary>
public class MainForm : Form
public class MainForm : UIForm
{
[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern IntPtr CreateRoundRectRgn(
int nLeftRect,
int nTopRect,
int nRightRect,
int nBottomRect,
int nWidthEllipse,
int nHeightEllipse
);
[DllImport("dwmapi.dll")]
private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
[DllImport("dwmapi.dll")]
private static extern int DwmIsCompositionEnabled(ref int pfEnabled);
private struct MARGINS
{
public int leftWidth;
public int rightWidth;
public int topHeight;
public int bottomHeight;
}
private const int DWMWA_WINDOW_CORNER_PREFERENCE = 33;
private const int DWMWCP_ROUND = 2;
private static readonly string CacheDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"zsyg", "kortapp-z", ".cache");
@@ -126,6 +155,10 @@ namespace AppStore
private Button btnAbout = null!;
// 内容显示面板
private Panel contentPanel = null!;
// 系统托盘图标
private NotifyIcon trayIcon = null!;
// 托盘右键菜单
private ContextMenuStrip trayMenu = null!;
/// <summary>
/// 初始化窗体组件
@@ -135,10 +168,67 @@ namespace AppStore
// 设置窗体基本属性
// 窗体基本设置
this.Text = "kortapp-z";
this.Size = new Size(1430, 1050); // 增加窗体高度
this.MinimumSize = new Size(600, 600); // 设置最小尺寸
this.Size = new Size(1430, 1050);
this.MinimumSize = new Size(600, 600);
this.StartPosition = FormStartPosition.CenterScreen;
this.Icon = new Icon("img/ico/icon.ico"); // 设置窗体图标
this.Icon = new Icon("img/ico/icon.ico");
this.Style = UIStyle.Custom;
this.FormBorderStyle = FormBorderStyle.None;
// 应用现代化圆角
this.Region = Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height,
ThemeManager.FormRadius, ThemeManager.FormRadius));
// 启用窗口阴影
if (Environment.OSVersion.Version.Major >= 6)
{
int val = DWMWCP_ROUND;
DwmSetWindowAttribute(this.Handle, DWMWA_WINDOW_CORNER_PREFERENCE, ref val, sizeof(int));
MARGINS margins = new MARGINS()
{
leftWidth = 1,
rightWidth = 1,
topHeight = 1,
bottomHeight = 1
};
DwmExtendFrameIntoClientArea(this.Handle, ref margins);
}
// 初始化系统托盘
trayMenu = new ContextMenuStrip();
trayMenu.Items.Add("打开", null, (s, e) => {
this.Show();
this.WindowState = FormWindowState.Normal;
});
trayMenu.Items.Add("退出", null, (s, e) => Application.Exit());
trayIcon = new NotifyIcon();
trayIcon.Text = "kortapp-z";
trayIcon.Icon = new Icon("img/ico/icon.ico");
trayIcon.ContextMenuStrip = trayMenu;
trayIcon.Visible = true;
trayIcon.DoubleClick += (s, e) => {
this.Show();
this.WindowState = FormWindowState.Normal;
};
// 窗体最小化到托盘处理
this.Resize += (s, e) => {
if (this.WindowState == FormWindowState.Minimized)
{
this.Hide();
}
};
// 窗体关闭按钮处理 - 隐藏到托盘而不是退出
this.FormClosing += (s, e) => {
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
this.Hide();
}
};
// 注册主题变更事件
ThemeManager.ThemeChanged += (theme) =>
@@ -151,12 +241,68 @@ namespace AppStore
// 现代化顶部导航栏
Panel buttonPanel = new Panel();
buttonPanel.Dock = DockStyle.Top;
buttonPanel.Height = 70;
buttonPanel.Height = 80;
buttonPanel.BackColor = ThemeManager.ControlBackgroundColor;
buttonPanel.Padding = new Padding(10, 15, 10, 0);
buttonPanel.Padding = new Padding(15, 20, 15, 5);
buttonPanel.AutoScroll = true;
buttonPanel.AutoSize = true;
buttonPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// 添加支持作者按钮
UIButton btnSupport = new UIButton();
btnSupport.Text = "支持作者";
btnSupport.Font = new Font("微软雅黑", 10F, FontStyle.Bold);
btnSupport.Size = new Size(120, 45);
btnSupport.Location = new Point(buttonPanel.Width - 140, 15);
btnSupport.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnSupport.Style = UIStyle.Custom;
btnSupport.FillColor = ThemeManager.AccentColor;
btnSupport.ForeColor = Color.White;
btnSupport.Click += (s, e) => {
// 使用SunnyUI的现代化MessageBox
var form = new UIForm();
form.Style = UIStyle.Custom;
form.Text = "支持作者";
form.Size = new Size(400, 200);
form.StartPosition = FormStartPosition.CenterParent;
var label = new Label();
label.Text = "您确定要前往GitHub支持作者吗";
label.Font = new Font("Microsoft YaHei", 10);
label.AutoSize = false;
label.Size = new Size(300, 40);
label.TextAlign = ContentAlignment.MiddleCenter;
label.Location = new Point(50, 40);
form.Controls.Add(label);
var btnOK = new UIButton();
btnOK.Text = "确定";
btnOK.Style = UIStyle.Custom;
btnOK.Size = new Size(80, 30);
btnOK.Location = new Point(120, 100);
btnOK.Click += (s, e) => {
form.DialogResult = DialogResult.OK;
form.Close();
};
form.Controls.Add(btnOK);
var btnCancel = new UIButton();
btnCancel.Text = "取消";
btnCancel.Style = UIStyle.Custom;
btnCancel.Size = new Size(80, 30);
btnCancel.Location = new Point(220, 100);
btnCancel.Click += (s, e) => {
form.DialogResult = DialogResult.Cancel;
form.Close();
};
form.Controls.Add(btnCancel);
if (form.ShowDialog(this) == DialogResult.OK)
{
Process.Start(new ProcessStartInfo("https://github.com/zs-yg/kortapp-z") { UseShellExecute = true });
}
};
buttonPanel.Controls.Add(btnSupport);
// 导航按钮样式
Action<Button> styleButton = (Button btn) => {
@@ -242,6 +388,7 @@ namespace AppStore
contentPanel.Dock = DockStyle.Fill;
contentPanel.BackColor = ThemeManager.BackgroundColor;
contentPanel.Padding = new Padding(20);
contentPanel.AutoScroll = true;
this.Controls.Add(contentPanel);
// 添加分隔线
@@ -458,44 +605,6 @@ namespace AppStore
};
flowPanel.Controls.Add(systemInfoCard);
// 视频压缩工具卡片
var videoCompressorCard = new ToolCard();
videoCompressorCard.ToolName = "视频压缩工具";
try
{
string iconPath = Path.Combine(Application.StartupPath, "img", "resource", "png", "video_compressor.png");
if (File.Exists(iconPath))
{
videoCompressorCard.ToolIcon = Image.FromFile(iconPath);
}
else
{
videoCompressorCard.ToolIcon = SystemIcons.Shield.ToBitmap();
}
}
catch
{
videoCompressorCard.ToolIcon = SystemIcons.Shield.ToBitmap();
}
videoCompressorCard.UpdateDisplay();
videoCompressorCard.ToolCardClicked += (s, e) => {
try {
string toolPath = Path.Combine(Application.StartupPath, "resource", "video_compressor.exe");
if (File.Exists(toolPath)) {
Process.Start(toolPath);
} else {
MessageBox.Show("视频压缩工具未找到,请确保已正确安装", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
} catch (Exception ex) {
MessageBox.Show($"启动视频压缩工具失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
flowPanel.Controls.Add(videoCompressorCard);
// 计算器工具卡片
var calculatorCard = new CalculatorToolCard();
try
@@ -566,6 +675,114 @@ namespace AppStore
passwordGeneratorCard.UpdateDisplay();
flowPanel.Controls.Add(passwordGeneratorCard);
// 自启动管理工具卡片
var selfStartingManagerCard = new SelfStartingManagerToolCard();
try
{
string iconPath = Path.Combine(Application.StartupPath, "img", "resource", "png", "Self_starting_management.png");
if (File.Exists(iconPath))
{
selfStartingManagerCard.ToolIcon = Image.FromFile(iconPath);
}
selfStartingManagerCard.UpdateDisplay();
}
catch (Exception ex)
{
Logger.LogError("加载自启动管理工具图标失败", ex);
}
flowPanel.Controls.Add(selfStartingManagerCard);
// 图标提取器工具卡片
var iconExtractorCard = new AppStore.Tools.IconExtractor.IconExtractorToolCard();
try
{
string iconPath = Path.Combine(Application.StartupPath, "img", "resource", "png", "ico_extractor.png");
if (File.Exists(iconPath))
{
iconExtractorCard.ToolIcon = Image.FromFile(iconPath);
}
iconExtractorCard.UpdateDisplay();
}
catch (Exception ex)
{
Logger.LogError("加载图标提取器图标失败", ex);
}
flowPanel.Controls.Add(iconExtractorCard);
// 文本转换器工具卡片
var textConverterCard = new ToolCard();
textConverterCard.ToolName = "文本转换器";
try
{
string iconPath = Path.Combine(Application.StartupPath, "img", "resource", "png", "text converter.png");
if (File.Exists(iconPath))
{
textConverterCard.ToolIcon = Image.FromFile(iconPath);
}
else
{
textConverterCard.ToolIcon = SystemIcons.Shield.ToBitmap();
}
}
catch
{
textConverterCard.ToolIcon = SystemIcons.Shield.ToBitmap();
}
textConverterCard.UpdateDisplay();
textConverterCard.ToolCardClicked += (s, e) => {
try {
string toolPath = Path.Combine(Application.StartupPath, "resource", "text_converter.exe");
if (File.Exists(toolPath)) {
Process.Start(toolPath);
} else {
MessageBox.Show("文本转换器工具未找到,请确保已正确安装", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
} catch (Exception ex) {
MessageBox.Show($"启动文本转换器失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
flowPanel.Controls.Add(textConverterCard);
// 图片转换器工具卡片
var imageConverterCard = new ToolCard();
imageConverterCard.ToolName = "图片转换器";
try
{
string iconPath = Path.Combine(Application.StartupPath, "img", "resource", "png", "Image_format_converter.png");
if (File.Exists(iconPath))
{
imageConverterCard.ToolIcon = Image.FromFile(iconPath);
}
else
{
imageConverterCard.ToolIcon = SystemIcons.Shield.ToBitmap();
}
}
catch
{
imageConverterCard.ToolIcon = SystemIcons.Shield.ToBitmap();
}
imageConverterCard.UpdateDisplay();
imageConverterCard.ToolCardClicked += (s, e) => {
try {
string toolPath = Path.Combine(Application.StartupPath, "resource", "image_converter.exe");
if (File.Exists(toolPath)) {
Process.Start(toolPath);
} else {
MessageBox.Show("图片转换器工具未找到,请确保已正确安装", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
} catch (Exception ex) {
MessageBox.Show($"启动图片转换器失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
flowPanel.Controls.Add(imageConverterCard);
}
catch (Exception ex)
{
@@ -633,11 +850,11 @@ namespace AppStore
flowPanel.Dock = DockStyle.Fill;
flowPanel.AutoScroll = true;
flowPanel.Padding = new Padding(15, 15, 15, 15);
flowPanel.WrapContents = true;
flowPanel.WrapContents = false;
flowPanel.Margin = new Padding(0);
flowPanel.AutoSize = true;
flowPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
flowPanel.AutoScrollMinSize = new Size(0, 3350);
flowPanel.AutoScrollMinSize = new Size(0, 5000);
// 创建搜索框
TextBox searchBox = new TextBox();
@@ -665,11 +882,11 @@ namespace AppStore
flowPanel.Dock = DockStyle.Fill;
flowPanel.AutoScroll = true;
flowPanel.Padding = new Padding(15, 60, 15, 15);
flowPanel.WrapContents = true;
flowPanel.WrapContents = false;
flowPanel.Margin = new Padding(0);
flowPanel.AutoSize = true;
flowPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
flowPanel.AutoScrollMinSize = new Size(0, 3350);
flowPanel.AutoScrollMinSize = new Size(0, 5000);
contentPanel.Controls.Add(flowPanel);
// 添加窗体关闭事件处理
@@ -707,7 +924,7 @@ namespace AppStore
flowPanel.Margin = new Padding(0);
flowPanel.AutoSize = true;
flowPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
flowPanel.AutoScrollMinSize = new Size(0, 3350);
flowPanel.AutoScrollMinSize = new Size(0, 4050);//大概一行250像素
contentPanel.Controls.Add(flowPanel);
// 添加所有应用卡片并恢复位置
@@ -715,7 +932,7 @@ namespace AppStore
flowPanel.Controls.Add(CreateAppCard(
"XDM",
"https://github.com/subhra74/xdm/releases/download/7.2.11/xdm-setup.msi",
"https://ghproxy.net/https://github.com/subhra74/xdm/releases/download/7.2.11/xdm-setup.msi",
"img/png/XDM.png"));
flowPanel.Controls.Add(CreateAppCard(
@@ -780,9 +997,14 @@ namespace AppStore
flowPanel.Controls.Add(CreateAppCard(
"Msys2",
"https://github.com/msys2/msys2-installer/releases/download/2025-02-21/msys2-x86_64-20250221.exe",
"https://ghproxy.net/https://github.com/msys2/msys2-installer/releases/download/2025-02-21/msys2-x86_64-20250221.exe",
"img/png/MSYS2.png"));
flowPanel.Controls.Add(CreateAppCard(
"OpenJDK by Azul JDKs",
"https://cdn.azul.com/zulu/bin/zulu21.42.19-ca-jdk21.0.7-win_x64.msi",
"img/png/Azul_JDKs.png"));
flowPanel.Controls.Add(CreateAppCard(
".NET SDK 8.0",
"https://dotnet.microsoft.com/zh-cn/download/dotnet/thank-you/sdk-8.0.411-windows-x64-installer",
@@ -818,6 +1040,16 @@ namespace AppStore
"https://ghproxy.net/https://github.com/game1024/OpenSpeedy/releases/download/v1.7.1/OpenSpeedy-v1.7.1.zip",
"img/png/openspeedy.png"));
flowPanel.Controls.Add(CreateAppCard(
"Final2x",
"https://ghproxy.net/https://github.com/Tohrusky/Final2x/releases/download/2024-12-14/Final2x-windows-x64-setup.exe",
"img/png/Final2x.png"));
flowPanel.Controls.Add(CreateAppCard(
"Pixpin",
"https://download.pixpin.cn/PixPin_2.0.0.3.exe",
"img/png/pixpin.png"));
flowPanel.Controls.Add(CreateAppCard(
"QuickLook",
"https://ghproxy.net/https://github.com/QL-Win/QuickLook/releases/download/4.0.2/QuickLook-4.0.2.exe",
@@ -833,11 +1065,31 @@ namespace AppStore
"https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false",
"img/jpg/vs.jpg"));
flowPanel.Controls.Add(CreateAppCard(
"vs build tools 2019",
"https://download.visualstudio.microsoft.com/download/pr/8918edd5-ae24-4ac8-b90a-5e30583f8261/df275a4c77916fe65e39d24e85eafb369c4ee458cc3dd627b920fe18a4606ce0/vs_BuildTools.exe",
"img/jpg/vs.jpg"));
flowPanel.Controls.Add(CreateAppCard(
"vs build tools 2022",
"https://download.visualstudio.microsoft.com/download/pr/13907dbe-8bb3-4cfe-b0ae-147e70f8b2f3/a3193e6e6135ef7f598d6a9e429b010d77260dba33dddbee343a47494b5335a3/vs_BuildTools.exe",
"img/jpg/vs.jpg"));
flowPanel.Controls.Add(CreateAppCard(
"VSCodium",
"https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false",
"img/png/codium_cnl.png"));
flowPanel.Controls.Add(CreateAppCard(
"Dev-C++",
"https://down.wsyhn.com/23_355739",
"img/png/Dev-C++.png"));
flowPanel.Controls.Add(CreateAppCard(
"Code::Blocks",
"https://down.wsyhn.com/23_277571",
"img/png/CodeBlocks.png"));
flowPanel.Controls.Add(CreateAppCard(
"7-Zip",
"https://objects.githubusercontent.com/github-production-release-asset-2e65be/466446150/1645817e-3677-4207-93ff-e62de7e147be?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20250613%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250613T035936Z&X-Amz-Expires=300&X-Amz-Signature=5e02d5fc34f45bd8308029c9fc78052007e9475ce0e32775619921cb8f3b83ea&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3D7z2409-x64.exe&response-content-type=application%2Foctet-stream",
@@ -878,9 +1130,14 @@ namespace AppStore
"https://ghproxy.net/https://github.com/hiroi-sora/Umi-OCR/releases/download/v2.1.5/Umi-OCR_Paddle_v2.1.5.7z.exe",
"img/png/Umi-OCR.png"));
flowPanel.Controls.Add(CreateAppCard(
"pocketbase",
"https://ghproxy.net/https://github.com/pocketbase/pocketbase/releases/download/v0.28.4/pocketbase_0.28.4_windows_amd64.zip",
"img/png/pocketbase.png"));
flowPanel.Controls.Add(CreateAppCard(
"frp",
"https://github.com/fatedier/frp/releases/download/v0.62.1/frp_0.62.1_windows_amd64.zip",
"https://ghproxy.net/https://github.com/fatedier/frp/releases/download/v0.62.1/frp_0.62.1_windows_amd64.zip",
""));
flowPanel.Controls.Add(CreateAppCard(
@@ -994,6 +1251,31 @@ namespace AppStore
"https://ghproxy.net/https://github.com/vladelaina/Catime/releases/download/v1.1.1/catime_1.1.1.exe",
"img/png/catime_resize.png"));
flowPanel.Controls.Add(CreateAppCard(
"Cataclysm-DDA",
"https://ghproxy.cn/https://github.com/CleverRaven/Cataclysm-DDA/releases/download/0.H-RELEASE/cdda-windows-with-graphics-and-sounds-x64-2024-11-23-1857.zip",
"img/png/Cataclysm-DDA.png"));
flowPanel.Controls.Add(CreateAppCard(
"gophish",
"https://ghproxy.cn/https://github.com/gophish/gophish/releases/download/v0.12.1/gophish-v0.12.1-windows-64bit.zip",
"img/png/gophish.png"));
flowPanel.Controls.Add(CreateAppCard(
"NoteGen",
"https://ghproxy.cn/https://github.com/codexu/note-gen/releases/download/note-gen-v0.19.3/NoteGen_0.19.3_x64-setup.exe",
"img/png/NoteGen.png"));
flowPanel.Controls.Add(CreateAppCard(
"hashcat",
"https://ghproxy.cn/https://github.com/hashcat/hashcat/releases/download/v6.2.6/hashcat-6.2.6.7z",
"img/png/hashcat.png"));
flowPanel.Controls.Add(CreateAppCard(
"rpg-cli",
"https://gh-proxy.com/https://github.com/facundoolano/rpg-cli/releases/download/1.2.0/rpg-cli-1.2.0-windows.exe",
""));
flowPanel.Controls.Add(CreateAppCard(
"fluxy",
"https://ghproxy.net/https://github.com/alley-rs/fluxy/releases/download/v0.1.17/fluxy_0.1.17_x64-setup.exe",
@@ -1004,15 +1286,55 @@ namespace AppStore
"https://ghproxy.net/https://github.com/vnotex/vnote/releases/download/v3.19.2/VNote-3.19.2-win64.zip",
"img/png/vnote.png"));
flowPanel.Controls.Add(CreateAppCard(
"notepad--",
"https://www.ghproxy.cn/https://github.com/cxasm/notepad--/releases/download/notepad-v3.3/Notepad--v3.3-plugin-Installer.exe",
"img/png/notepad--.png"));
flowPanel.Controls.Add(CreateAppCard(
"chatlog",
"https://www.ghproxy.cn/https://github.com/sjzar/chatlog/releases/download/v0.0.15/chatlog_0.0.15_windows_amd64.zip",
"img/jpg/github.jpg"));
flowPanel.Controls.Add(CreateAppCard(
"PowerToys",
"https://ghproxy.net/https://github.com/microsoft/PowerToys/releases/download/v0.91.1/PowerToysSetup-0.91.1-x64.exe",
"img/png/PowerToys.png"));
flowPanel.Controls.Add(CreateAppCard(
"Powershell",
"https://ghproxy.net/https://github.com/Powershell/Powershell/releases/download/v7.5.2/Powershell-7.5.2-win-x64.exe",
"img/png/powershell.png"));
flowPanel.Controls.Add(CreateAppCard(
"terminal",
"https://ghproxy.net/https://github.com/microsoft/terminal/releases/download/v1.22.11141.0/Microsoft.WindowsTerminal_1.22.11141.0_x64.zip",
"img/png/terminal.png"));
flowPanel.Controls.Add(CreateAppCard(
"edit",
"https://ghproxy.net/https://github.com/microsoft/edit/releases/download/v1.2.0/edit-1.2.0-x86_64-windows.zip",
"img/png/edit.png"));
flowPanel.Controls.Add(CreateAppCard(
"github_cli",
"https://ghproxy.cn/https://github.com/cli/cli/releases/download/v2.74.2/gh_2.74.2_windows_arm64.msi",
"img/png/github_cli.png"));
flowPanel.Controls.Add(CreateAppCard(
"VideoCaptioner",
"https://ghproxy.cn/https://github.com/WEIFENG2333/VideoCaptioner/releases/download/v1.3.3/VideoCaptioner-Setup-win64-v1.3.3.exe",
"img/png/VideoCaptioner.png"));
flowPanel.Controls.Add(CreateAppCard(
"ReactOS",
"https://ghproxy.cn/https://github.com/reactos/reactos/releases/download/0.4.15-release/ReactOS-0.4.15-release-1-gdbb43bbaeb2-x86-iso.zip",
"img/png/ReactOS.png"));
flowPanel.Controls.Add(CreateAppCard(
"Ubuntu桌面发行版",
"https://releases.ubuntu.com/24.04/ubuntu-24.04.2-desktop-amd64.iso",
"img/png/Ubuntu.png"));
flowPanel.Controls.Add(CreateAppCard(
"typescript",
@@ -1024,6 +1346,11 @@ namespace AppStore
"https://mirror.nju.edu.cn/gimp/gimp/v3.0/windows/gimp-3.0.4-setup.exe",
"img/jpg/Gimp.jpg"));
flowPanel.Controls.Add(CreateAppCard(
"ClamAV",
"https://www.clamav.net/downloads/production/clamav-1.4.3.win.x64.msi",
"img/png/ClamAV.png"));
flowPanel.Controls.Add(CreateAppCard(
"Shotcut",
"https://sourceforge.net/projects/shotcut/files/v25.05.11/shotcut-win64-250511.exe/download",
@@ -1179,6 +1506,11 @@ namespace AppStore
"https://ghproxy.net/https://github.com/cloudreve/cloudreve/releases/download/3.8.3/cloudreve_3.8.3_windows_amd64.zip",
"img/png/cloudreve.png"));
flowPanel.Controls.Add(CreateAppCard(
"ollama",
"https://www.ghproxy.cn/https://github.com/ollama/ollama/releases/download/v0.9.5/OllamaSetup.exe",
"img/png/ollama.png"));
flowPanel.Controls.Add(CreateAppCard(
"SeelenUI",
"https://ghproxy.net/https://github.com/eythaann/Seelen-UI/releases/download/v2.3.8/Seelen.UI_2.3.8_x64-setup.exe",

View File

@@ -1,16 +1,16 @@
using System;
using System.Windows.Forms;
namespace AppStore
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
using System;
using System.Windows.Forms;
namespace AppStore
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

View File

@@ -5,14 +5,12 @@
## 项目开源行为
1. 项目代码开源,允许任何人使用、修改、分发、商用,但必须注明原作者。
2. 项目文档开源,允许任何人使用、修改、分发、商用,但必须注明原作者。
3. 项目图标、截图等资源开源,允许任何人使用、修改、分发、商用,但必须注明原作者
4. 项目的任何衍生品包括但不限于网站、APP、插件等必须遵循以上开源协议
5. 项目不接受任何形式的广告,不得在任何地方投放广告。
6. 项目不接受任何形式的捐赠。
7. 项目不接受任何形式的赞助。
8. 项目可以进行PR欢迎任何形式的PR不提交issue也可以
9. 本项目可以PR一些你自己的项目如果star数量不到1k都会被删除
2. 项目图标、截图等资源开源,允许任何人使用、修改、分发、商用,但必须注明原作者。
3. 项目的任何衍生品包括但不限于网站、APP、插件等必须遵循以上开源协议
4. 项目不接受任何形式的广告,不得在任何地方投放广告
5. 项目不接受任何形式的捐赠、赞助
6. 项目可以进行PR欢迎任何形式的PR不提交issue也可以
7. 项目可以PR一些你自己的项目如果star数量不到1k都会被删除
## 项目简介

View File

@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Text.Json;
namespace AppStore
{
@@ -49,7 +50,43 @@ namespace AppStore
btnCleanLogs.Click += (s, e) => CleanLogs();
this.Controls.Add(btnCleanLogs);
// 下载路径设置
Label lblDownloadPath = new Label();
lblDownloadPath.Text = "下载路径:";
lblDownloadPath.AutoSize = true;
lblDownloadPath.Location = new Point((this.Width - 300) / 2, 170);
lblDownloadPath.Font = new Font("Microsoft YaHei", 10);
lblDownloadPath.Anchor = AnchorStyles.Top;
this.Controls.Add(lblDownloadPath);
TextBox txtDownloadPath = new TextBox();
txtDownloadPath.Size = new Size(300, 30);
txtDownloadPath.Location = new Point((this.Width - 300) / 2, 200);
txtDownloadPath.Font = new Font("Microsoft YaHei", 10);
txtDownloadPath.Anchor = AnchorStyles.Top;
txtDownloadPath.ReadOnly = true;
this.Controls.Add(txtDownloadPath);
Button btnBrowse = new Button();
btnBrowse.Text = "浏览...";
btnBrowse.Size = new Size(80, 30);
btnBrowse.Location = new Point(txtDownloadPath.Right + 10, 200);
btnBrowse.Font = new Font("Microsoft YaHei", 10);
btnBrowse.Anchor = AnchorStyles.Top;
btnBrowse.Click += (s, e) => BrowseDownloadPath(txtDownloadPath);
this.Controls.Add(btnBrowse);
Button btnSavePath = new Button();
btnSavePath.Text = "保存路径";
btnSavePath.Size = new Size(100, 30);
btnSavePath.Location = new Point((this.Width - 100) / 2, 240);
btnSavePath.Font = new Font("Microsoft YaHei", 10);
btnSavePath.Anchor = AnchorStyles.Top;
btnSavePath.Click += (s, e) => SaveDownloadPath(txtDownloadPath.Text);
this.Controls.Add(btnSavePath);
ThemeManager.ThemeChanged += OnThemeChanged;
LoadDownloadPath(txtDownloadPath);
}
private void SwitchTheme(ThemeManager.ThemeMode theme)
@@ -84,5 +121,103 @@ namespace AppStore
MessageBox.Show($"清理日志时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void BrowseDownloadPath(TextBox txtBox)
{
using (FolderBrowserDialog dialog = new FolderBrowserDialog())
{
dialog.Description = "选择下载路径";
if (dialog.ShowDialog() == DialogResult.OK)
{
txtBox.Text = dialog.SelectedPath;
}
}
}
private void SaveDownloadPath(string path)
{
try
{
// 验证路径
if (string.IsNullOrWhiteSpace(path))
{
MessageBox.Show("下载路径不能为空", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 尝试创建目录(如果不存在)
try
{
Directory.CreateDirectory(path);
// 验证目录是否可写
string testFile = Path.Combine(path, "write_test.tmp");
File.WriteAllText(testFile, "test");
File.Delete(testFile);
}
catch
{
MessageBox.Show($"无法访问路径: {path}\n请确保路径存在且有写入权限", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 保存路径
string dlPathDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"zsyg", "kortapp-z", ".date", "dl_path");
if (!Directory.Exists(dlPathDir))
{
Directory.CreateDirectory(dlPathDir);
}
string jsonPath = Path.Combine(dlPathDir, "download_path.json");
var jsonData = new { DownloadPath = path };
string jsonString = JsonSerializer.Serialize(jsonData);
File.WriteAllText(jsonPath, jsonString);
MessageBox.Show($"下载路径已保存到:\n{path}", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
Logger.LogError("保存下载路径时出错", ex);
MessageBox.Show($"保存下载路径时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void LoadDownloadPath(TextBox txtBox)
{
// 默认下载路径为用户文件夹下的Downloads
string defaultPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Downloads");
try
{
string jsonPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"zsyg", "kortapp-z", ".date", "dl_path", "download_path.json");
if (File.Exists(jsonPath))
{
string jsonString = File.ReadAllText(jsonPath);
var jsonData = JsonSerializer.Deserialize<JsonElement>(jsonString);
string customPath = jsonData.GetProperty("DownloadPath").GetString() ?? "";
// 如果自定义路径有效则显示,否则显示默认路径
txtBox.Text = !string.IsNullOrWhiteSpace(customPath) ? customPath : defaultPath;
}
else
{
txtBox.Text = defaultPath;
}
}
catch (Exception ex)
{
Logger.LogError("加载下载路径时出错", ex);
txtBox.Text = defaultPath;
}
}
}
}

View File

@@ -42,6 +42,8 @@ namespace AppStore
try
{
var dir = Path.GetDirectoryName(ThemeConfigPath);
if (dir == null) return;
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
@@ -56,18 +58,26 @@ namespace AppStore
}
// 浅色主题颜色
private static readonly Color LightBackground = Color.FromArgb(255, 255, 255);
private static readonly Color LightControlBackground = Color.FromArgb(240, 240, 240);
private static readonly Color LightText = Color.FromArgb(30, 30, 30);
private static readonly Color LightButtonHover = Color.FromArgb(230, 230, 230);
private static readonly Color LightButtonActive = Color.FromArgb(220, 220, 220);
private static readonly Color LightBackground = Color.FromArgb(250, 250, 250);
private static readonly Color LightControlBackground = Color.FromArgb(245, 245, 245);
private static readonly Color LightText = Color.FromArgb(40, 40, 40);
private static readonly Color LightButtonHover = Color.FromArgb(235, 235, 235);
private static readonly Color LightButtonActive = Color.FromArgb(225, 225, 225);
private static readonly Color LightAccent = Color.FromArgb(0, 120, 215);
private static readonly Color LightAccentLight = Color.FromArgb(0, 150, 245);
// 深色主题颜色
private static readonly Color DarkBackground = Color.FromArgb(30, 30, 30);
private static readonly Color DarkControlBackground = Color.FromArgb(45, 45, 45);
private static readonly Color DarkText = Color.FromArgb(240, 240, 240);
private static readonly Color DarkButtonHover = Color.FromArgb(60, 60, 60);
private static readonly Color DarkButtonActive = Color.FromArgb(70, 70, 70);
private static readonly Color DarkBackground = Color.FromArgb(25, 25, 25);
private static readonly Color DarkControlBackground = Color.FromArgb(40, 40, 40);
private static readonly Color DarkText = Color.FromArgb(245, 245, 245);
private static readonly Color DarkButtonHover = Color.FromArgb(55, 55, 55);
private static readonly Color DarkButtonActive = Color.FromArgb(65, 65, 65);
private static readonly Color DarkBorder = Color.FromArgb(70, 70, 70);
private static readonly Color DarkAccent = Color.FromArgb(0, 150, 245);
private static readonly Color DarkAccentLight = Color.FromArgb(0, 180, 255);
// 浅色主题边框颜色
private static readonly Color LightBorder = Color.FromArgb(200, 200, 200);
public static event Action<ThemeMode> ThemeChanged = delegate {};
@@ -100,6 +110,19 @@ namespace AppStore
public static Color ButtonActiveColor =>
_currentTheme == ThemeMode.Light ? LightButtonActive : DarkButtonActive;
public static Color BorderColor =>
_currentTheme == ThemeMode.Light ? LightBorder : DarkBorder;
public static Color AccentColor =>
_currentTheme == ThemeMode.Light ? LightAccent : DarkAccent;
public static Color AccentLightColor =>
_currentTheme == ThemeMode.Light ? LightAccentLight : DarkAccentLight;
public static int ControlRadius => 8;
public static int FormRadius => 12;
public static void ApplyTheme(Control control)
{
ApplyThemeToControl(control);
@@ -116,6 +139,9 @@ namespace AppStore
button.FlatAppearance.BorderSize = 0;
button.FlatAppearance.MouseOverBackColor = ButtonHoverColor;
button.FlatAppearance.MouseDownBackColor = ButtonActiveColor;
button.BackColor = ControlBackgroundColor;
button.Font = new Font(button.Font, FontStyle.Bold);
button.Padding = new Padding(10, 5, 10, 5);
}
foreach (Control childControl in control.Controls)

View File

@@ -1,77 +1,113 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace AppStore
{
public class ToolCard : UserControl
{
private PictureBox iconBox = new PictureBox();
private Label nameLabel = new Label();
public string ToolName { get; set; } = string.Empty;
public Image ToolIcon { get; set; } = SystemIcons.Shield.ToBitmap();
// 自定义点击事件初始化为空委托
public event EventHandler ToolCardClicked = delegate {};
public ToolCard()
{
// 启用双缓冲
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
InitializeComponent();
}
private void InitializeComponent()
{
this.Size = new Size(240, 220);
this.BackColor = Color.White;
this.Padding = new Padding(5);
this.BorderStyle = BorderStyle.FixedSingle;
// 工具图标
iconBox = new PictureBox();
iconBox.Size = new Size(80, 80);
iconBox.Location = new Point((Width - 80) / 2, 15);
iconBox.SizeMode = PictureBoxSizeMode.StretchImage;
this.Controls.Add(iconBox);
// 工具名称
nameLabel = new Label();
nameLabel.AutoSize = false;
nameLabel.Size = new Size(Width - 20, 30);
nameLabel.Location = new Point(10, 100);
nameLabel.Font = new Font("Microsoft YaHei", 10, FontStyle.Bold);
nameLabel.TextAlign = ContentAlignment.MiddleCenter;
this.Controls.Add(nameLabel);
// 打开按钮
var openButton = new Button();
openButton.Text = "打开工具";
openButton.Size = new Size(100, 30);
openButton.Location = new Point((Width - 100) / 2, 140);
openButton.BackColor = Color.FromArgb(0, 120, 215);
openButton.ForeColor = Color.White;
openButton.FlatStyle = FlatStyle.Flat;
openButton.FlatAppearance.BorderSize = 0;
openButton.Cursor = Cursors.Hand;
// 按钮点击直接触发ToolCardClicked事件
openButton.Click += (s, e) => {
ToolCardClicked?.Invoke(this, e);
};
this.Controls.Add(openButton);
// 设置按钮悬停效果
openButton.BackColor = Color.FromArgb(0, 120, 215);
openButton.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 100, 180);
openButton.FlatAppearance.MouseDownBackColor = Color.FromArgb(0, 80, 160);
}
public void UpdateDisplay()
{
nameLabel.Text = ToolName;
iconBox.Image = ToolIcon;
}
}
}
using System;
using System.Drawing;
using System.Windows.Forms;
namespace AppStore
{
public class ToolCard : UserControl
{
private PictureBox iconBox = new PictureBox();
private Label nameLabel = new Label();
private Panel namePanel = new Panel();
private Color borderColor = SystemColors.ControlDark;
public string ToolName { get; set; } = string.Empty;
public Image ToolIcon { get; set; } = SystemIcons.Shield.ToBitmap();
// 自定义点击事件初始化为空委托
public event EventHandler ToolCardClicked = delegate {};
public ToolCard()
{
// 启用双缓冲
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
InitializeComponent();
}
private void InitializeComponent()
{
this.Size = new Size(240, 220);
this.BackColor = Color.White;
this.Padding = new Padding(5);
this.BorderStyle = BorderStyle.FixedSingle;
// 工具图标
iconBox = new PictureBox();
iconBox.Size = new Size(80, 80);
iconBox.Location = new Point((Width - 80) / 2, 15);
iconBox.SizeMode = PictureBoxSizeMode.StretchImage;
this.Controls.Add(iconBox);
// 工具名称 - 使用Panel包裹Label实现边框颜色
namePanel = new Panel();
namePanel.Size = new Size(Width - 20, 30);
namePanel.Location = new Point(10, 100);
namePanel.Paint += (sender, e) => {
ControlPaint.DrawBorder(e.Graphics, namePanel.ClientRectangle,
borderColor, ButtonBorderStyle.Solid);
};
nameLabel = new Label();
nameLabel.Dock = DockStyle.Fill;
nameLabel.Font = new Font("Microsoft YaHei", 10, FontStyle.Bold);
nameLabel.TextAlign = ContentAlignment.MiddleCenter;
namePanel.Controls.Add(nameLabel);
// 初始主题设置
UpdateLabelTheme();
// 订阅主题变化事件
ThemeManager.ThemeChanged += (theme) => UpdateLabelTheme();
this.Controls.Add(namePanel);
// 打开按钮
var openButton = new Button();
openButton.Text = "打开工具";
openButton.Size = new Size(100, 30);
openButton.Location = new Point((Width - 100) / 2, 140);
openButton.BackColor = Color.FromArgb(0, 120, 215);
openButton.ForeColor = Color.White;
openButton.FlatStyle = FlatStyle.Flat;
openButton.FlatAppearance.BorderSize = 0;
openButton.Cursor = Cursors.Hand;
// 按钮点击直接触发ToolCardClicked事件
openButton.Click += (s, e) => {
ToolCardClicked?.Invoke(this, e);
};
this.Controls.Add(openButton);
// 设置按钮悬停效果
openButton.BackColor = Color.FromArgb(0, 120, 215);
openButton.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 100, 180);
openButton.FlatAppearance.MouseDownBackColor = Color.FromArgb(0, 80, 160);
}
private void UpdateLabelTheme()
{
if (ThemeManager.CurrentTheme == ThemeManager.ThemeMode.Dark)
{
nameLabel.BackColor = Color.Black;
nameLabel.ForeColor = Color.White;
namePanel.BackColor = Color.Black;
borderColor = Color.White;
}
else
{
nameLabel.BackColor = Color.White;
nameLabel.ForeColor = Color.Black;
namePanel.BackColor = Color.White;
borderColor = SystemColors.ControlDark;
}
namePanel.Invalidate(); // 触发重绘
}
public void UpdateDisplay()
{
nameLabel.Text = ToolName;
iconBox.Image = ToolIcon;
UpdateLabelTheme();
}
}
}

View File

@@ -1,64 +1,64 @@
#include <windows.h>
#include <vector>
#include <fstream>
#include <cmath>
#include <d2d1.h>
// 高性能边框路径生成
void GenerateBorderPath(int width, int height, int radius, const char* outputPath) {
std::ofstream out(outputPath);
const float pi = 3.1415926f;
const int segments = 24; // 高分段数确保平滑
std::vector<POINTFLOAT> points;
// 优化后的圆角路径生成
auto addArc = [&](float startAngle, float endAngle, float cx, float cy) {
for (int i = 0; i <= segments; ++i) {
float angle = startAngle + (endAngle - startAngle) * i / segments;
points.push_back({
cx + radius * cosf(angle),
cy + radius * sinf(angle)
});
}
};
// 左上角
addArc(pi, 3*pi/2, radius, radius);
// 右上角
addArc(3*pi/2, 2*pi, width - radius, radius);
// 右下角
addArc(0, pi/2, width - radius, height - radius);
// 左下角
addArc(pi/2, pi, radius, height - radius);
// 闭合路径
points.push_back(points[0]);
// 写入优化格式
if (out.is_open()) {
for (const auto& p : points) {
out << p.x << "," << p.y << "\n";
}
}
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
int argc;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argc != 5) return 1;
int width = _wtoi(argv[1]);
int height = _wtoi(argv[2]);
int radius = _wtoi(argv[3]);
char outputPath[MAX_PATH];
wcstombs(outputPath, argv[4], MAX_PATH);
GenerateBorderPath(width, height, radius, outputPath);
return 0;
}
#include <windows.h>
#include <vector>
#include <fstream>
#include <cmath>
#include <d2d1.h>
// 高性能边框路径生成
void GenerateBorderPath(int width, int height, int radius, const char* outputPath) {
std::ofstream out(outputPath);
const float pi = 3.1415926f;
const int segments = 24; // 高分段数确保平滑
std::vector<POINTFLOAT> points;
// 优化后的圆角路径生成
auto addArc = [&](float startAngle, float endAngle, float cx, float cy) {
for (int i = 0; i <= segments; ++i) {
float angle = startAngle + (endAngle - startAngle) * i / segments;
points.push_back({
cx + radius * cosf(angle),
cy + radius * sinf(angle)
});
}
};
// 左上角
addArc(pi, 3*pi/2, radius, radius);
// 右上角
addArc(3*pi/2, 2*pi, width - radius, radius);
// 右下角
addArc(0, pi/2, width - radius, height - radius);
// 左下角
addArc(pi/2, pi, radius, height - radius);
// 闭合路径
points.push_back(points[0]);
// 写入优化格式
if (out.is_open()) {
for (const auto& p : points) {
out << p.x << "," << p.y << "\n";
}
}
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
int argc;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argc != 5) return 1;
int width = _wtoi(argv[1]);
int height = _wtoi(argv[2]);
int radius = _wtoi(argv[3]);
char outputPath[MAX_PATH];
wcstombs(outputPath, argv[4], MAX_PATH);
GenerateBorderPath(width, height, radius, outputPath);
return 0;
}

View File

@@ -1,90 +1,90 @@
#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <windows.h>
using namespace std;
struct Point {
float x;
float y;
};
void WritePathToFile(const vector<Point>& path, const string& filename) {
ofstream outFile(filename);
if (!outFile) {
cerr << "无法打开输出文件: " << filename << endl;
return;
}
for (const auto& point : path) {
outFile << point.x << "," << point.y << "\n";
}
outFile.close();
}
vector<Point> CalculateRoundedRectPath(int width, int height, int radius) {
vector<Point> pathPoints;
const int segments = 10;
const float angleStep = 3.1415926f / (2 * segments);
// 左上角
for (int i = 0; i <= segments; i++) {
float angle = 3.1415926f + i * angleStep;
pathPoints.push_back({
radius + radius * cosf(angle),
radius + radius * sinf(angle)
});
}
// 右上角
for (int i = 0; i <= segments; i++) {
float angle = 3 * 3.1415926f / 2 + i * angleStep;
pathPoints.push_back({
width - radius + radius * cosf(angle),
radius + radius * sinf(angle)
});
}
// 右下角
for (int i = 0; i <= segments; i++) {
float angle = 0 + i * angleStep;
pathPoints.push_back({
width - radius + radius * cosf(angle),
height - radius + radius * sinf(angle)
});
}
// 左下角
for (int i = 0; i <= segments; i++) {
float angle = 3.1415926f / 2 + i * angleStep;
pathPoints.push_back({
radius + radius * cosf(angle),
height - radius + radius * sinf(angle)
});
}
// 闭合路径
pathPoints.push_back(pathPoints[0]);
return pathPoints;
}
int main(int argc, char* argv[]) {
if (argc != 5) {
cout << "用法: card_calculator [宽度] [高度] [圆角半径] [输出文件]" << endl;
return 1;
}
int width = stoi(argv[1]);
int height = stoi(argv[2]);
int radius = stoi(argv[3]);
string outputFile = argv[4];
auto path = CalculateRoundedRectPath(width, height, radius);
WritePathToFile(path, outputFile);
return 0;
}
#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <windows.h>
using namespace std;
struct Point {
float x;
float y;
};
void WritePathToFile(const vector<Point>& path, const string& filename) {
ofstream outFile(filename);
if (!outFile) {
cerr << "无法打开输出文件: " << filename << endl;
return;
}
for (const auto& point : path) {
outFile << point.x << "," << point.y << "\n";
}
outFile.close();
}
vector<Point> CalculateRoundedRectPath(int width, int height, int radius) {
vector<Point> pathPoints;
const int segments = 10;
const float angleStep = 3.1415926f / (2 * segments);
// 左上角
for (int i = 0; i <= segments; i++) {
float angle = 3.1415926f + i * angleStep;
pathPoints.push_back({
radius + radius * cosf(angle),
radius + radius * sinf(angle)
});
}
// 右上角
for (int i = 0; i <= segments; i++) {
float angle = 3 * 3.1415926f / 2 + i * angleStep;
pathPoints.push_back({
width - radius + radius * cosf(angle),
radius + radius * sinf(angle)
});
}
// 右下角
for (int i = 0; i <= segments; i++) {
float angle = 0 + i * angleStep;
pathPoints.push_back({
width - radius + radius * cosf(angle),
height - radius + radius * sinf(angle)
});
}
// 左下角
for (int i = 0; i <= segments; i++) {
float angle = 3.1415926f / 2 + i * angleStep;
pathPoints.push_back({
radius + radius * cosf(angle),
height - radius + radius * sinf(angle)
});
}
// 闭合路径
pathPoints.push_back(pathPoints[0]);
return pathPoints;
}
int main(int argc, char* argv[]) {
if (argc != 5) {
cout << "用法: card_calculator [宽度] [高度] [圆角半径] [输出文件]" << endl;
return 1;
}
int width = stoi(argv[1]);
int height = stoi(argv[2]);
int radius = stoi(argv[3]);
string outputFile = argv[4];
auto path = CalculateRoundedRectPath(width, height, radius);
WritePathToFile(path, outputFile);
return 0;
}

View File

@@ -17,21 +17,7 @@
<h2>核心功能</h2>
<article class="feature">
<h3>应用程序管理</h3>
<p>批量安装、卸载(目前没有)和更新应用程序(目前没有),管理启动项(目前没有)。</p>
<p>优势:集中管理所有应用,节省时间,避免系统臃肿。</p>
</article>
<article class="feature">
<h3>资源监控(之后可能在内置工具里有)</h3>
<p>实时监控CPU、内存、磁盘和网络使用情况。</p>
<p>优势:直观的图表展示,及时发现资源瓶颈。</p>
</article>
<article class="feature">
<h3>文件管理(之后可能在内置工具里有)</h3>
<p>高级文件搜索、批量重命名和快速文件分类。</p>
<p>优势:提升文件管理效率,支持正则表达式搜索。</p>
<h3>目前没有什么东西,别看了,害羞(✿◡‿◡)
</article>
</section>
</main>

BIN
img/png/Azul_JDKs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
img/png/Cataclysm-DDA.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
img/png/ClamAV.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
img/png/CodeBlocks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
img/png/Dev-C++.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
img/png/Final2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
img/png/NoteGen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
img/png/ReactOS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
img/png/Ubuntu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

BIN
img/png/VideoCaptioner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
img/png/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
img/png/github_cli.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
img/png/gophish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
img/png/hashcat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
img/png/notepad--.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
img/png/ollama.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
img/png/pixpin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

BIN
img/png/pocketbase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

BIN
img/png/powershell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 KiB

View File

@@ -2,10 +2,10 @@
; 有关创建 Inno Setup 脚本文件的详细信息,请参阅帮助文档!
#define MyAppName "kortapp-z"
#define MyAppVersion "1.1.1"
#define MyAppVersion "1.3.1"
#define MyAppPublisher "zsyg"
#define MyAppURL "https://github.com/zs-yg/kortapp-z"
#define MyAppExeName "kortapp.exe"
#define MyAppExeName "kortapp-z.exe"
#define MyAppAssocName MyAppName + ""
#define MyAppAssocExt ".exe"
#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt

View File

@@ -2,10 +2,10 @@
; 有关创建 Inno Setup 脚本文件的详细信息,请参阅帮助文档!
#define MyAppName "kortapp-z"
#define MyAppVersion "1.1.1"
#define MyAppVersion "1.3.1"
#define MyAppPublisher "zsyg"
#define MyAppURL "https://github.com/zs-yg/kortapp-z"
#define MyAppExeName "kortapp.exe"
#define MyAppExeName "kortapp-z.exe"
#define MyAppAssocName MyAppName + ""
#define MyAppAssocExt ".exe"
#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt

View File

@@ -1,65 +1,65 @@
#include <iostream>
#include <filesystem>
#include <chrono>
namespace fs = std::filesystem;
int main() {
try {
auto start = std::chrono::high_resolution_clock::now();
// 定义日志目录路径
fs::path logDir;
#ifdef _WIN32
// Windows系统获取AppData路径
char* appData = nullptr;
size_t len = 0;
if (_dupenv_s(&appData, &len, "APPDATA") == 0 && appData != nullptr) {
logDir = fs::path(appData) / "zsyg" / "kortapp-z" / ".logs";
free(appData);
} else {
std::cerr << "无法获取APPDATA环境变量" << std::endl;
return 1;
}
#else
// 非Windows系统使用默认路径
logDir = fs::path(getenv("HOME")) / ".zsyg" / "kortapp-z" / ".logs";
#endif
size_t deletedCount = 0;
size_t errorCount = 0;
// 检查目录是否存在
if (fs::exists(logDir) && fs::is_directory(logDir)) {
// 遍历并删除所有日志文件
for (const auto& entry : fs::directory_iterator(logDir)) {
try {
if (fs::is_regular_file(entry)) {
fs::remove(entry);
deletedCount++;
}
} catch (const std::exception& e) {
std::cerr << "删除文件失败: " << entry.path() << " - " << e.what() << std::endl;
errorCount++;
}
}
} else {
std::cout << "日志目录不存在,无需清理" << std::endl;
return 0;
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "日志清理完成: " << std::endl;
std::cout << "删除文件数: " << deletedCount << std::endl;
std::cout << "错误数: " << errorCount << std::endl;
std::cout << "耗时: " << duration.count() << " 毫秒" << std::endl;
} catch (const std::exception& e) {
std::cerr << "发生错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
#include <iostream>
#include <filesystem>
#include <chrono>
namespace fs = std::filesystem;
int main() {
try {
auto start = std::chrono::high_resolution_clock::now();
// 定义日志目录路径
fs::path logDir;
#ifdef _WIN32
// Windows系统获取AppData路径
char* appData = nullptr;
size_t len = 0;
if (_dupenv_s(&appData, &len, "APPDATA") == 0 && appData != nullptr) {
logDir = fs::path(appData) / "zsyg" / "kortapp-z" / ".logs";
free(appData);
} else {
std::cerr << "无法获取APPDATA环境变量" << std::endl;
return 1;
}
#else
// 非Windows系统使用默认路径
logDir = fs::path(getenv("HOME")) / ".zsyg" / "kortapp-z" / ".logs";
#endif
size_t deletedCount = 0;
size_t errorCount = 0;
// 检查目录是否存在
if (fs::exists(logDir) && fs::is_directory(logDir)) {
// 遍历并删除所有日志文件
for (const auto& entry : fs::directory_iterator(logDir)) {
try {
if (fs::is_regular_file(entry)) {
fs::remove(entry);
deletedCount++;
}
} catch (const std::exception& e) {
std::cerr << "删除文件失败: " << entry.path() << " - " << e.what() << std::endl;
errorCount++;
}
}
} else {
std::cout << "日志目录不存在,无需清理" << std::endl;
return 0;
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "日志清理完成: " << std::endl;
std::cout << "删除文件数: " << deletedCount << std::endl;
std::cout << "错误数: " << errorCount << std::endl;
std::cout << "耗时: " << duration.count() << " 毫秒" << std::endl;
} catch (const std::exception& e) {
std::cerr << "发生错误: " << e.what() << std::endl;
return 1;
}
return 0;
}

156
logger.cs
View File

@@ -1,63 +1,93 @@
using System;
using System.IO;
using System.Text;
namespace AppStore
{
public static class Logger
{
private static readonly string LogsDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"zsyg", "kortapp-z", ".logs");
private static readonly object LockObject = new object();
static Logger()
{
try
{
// 确保logs目录存在
if (!Directory.Exists(LogsDirectory))
{
Directory.CreateDirectory(LogsDirectory);
}
}
catch (Exception ex)
{
Console.WriteLine($"无法创建日志目录: {LogsDirectory}, 错误: {ex.Message}");
throw;
}
}
public static void Log(string message)
{
lock (LockObject)
{
try
{
string fileName = $"{DateTime.Now:yyyyMMddHHmmss}.log";
string filePath = Path.Combine(LogsDirectory, fileName);
using (StreamWriter writer = new StreamWriter(filePath, true, Encoding.UTF8))
{
writer.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
}
}
catch (Exception ex)
{
// 日志记录失败时输出到控制台
Console.WriteLine($"日志记录失败: {ex.Message}");
}
}
}
public static void LogError(string message, Exception? ex = null)
{
string errorMessage = $"ERROR: {message}";
if (ex != null)
{
errorMessage += $"\nException: {ex}\nStackTrace: {ex.StackTrace}";
}
Log(errorMessage);
}
}
}
using System;
using System.IO;
using System.Text;
namespace AppStore
{
public static class Logger
{
private static readonly string LogsDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"zsyg", "kortapp-z", ".logs");
private static readonly object LockObject = new object();
static Logger()
{
try
{
// 确保logs目录存在
if (!Directory.Exists(LogsDirectory))
{
Directory.CreateDirectory(LogsDirectory);
}
}
catch (Exception ex)
{
Console.WriteLine($"无法创建日志目录: {LogsDirectory}, 错误: {ex.Message}");
throw;
}
}
public static void Log(string message)
{
lock (LockObject)
{
try
{
string fileName = $"{DateTime.Now:yyyyMMddHHmmss}.log";
string filePath = Path.Combine(LogsDirectory, fileName);
using (StreamWriter writer = new StreamWriter(filePath, true, Encoding.UTF8))
{
writer.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
}
}
catch (Exception ex)
{
// 日志记录失败时输出到控制台
Console.WriteLine($"日志记录失败: {ex.Message}");
}
}
}
public static void LogError(string message, Exception? ex = null)
{
string errorMessage = $"ERROR: {message}";
if (ex != null)
{
errorMessage += $"\nException: {ex}\nStackTrace: {ex.StackTrace}";
}
Log(errorMessage);
}
public static void LogWarning(string message, Exception? ex = null)
{
string warningMessage = $"WARNING: {message}";
if (ex != null)
{
warningMessage += $"\nException: {ex}\nStackTrace: {ex.StackTrace}";
}
Log(warningMessage);
}
public static void LogDebug(string message, Exception? ex = null)
{
string debugMessage = $"DEBUG: {message}";
if (ex != null)
{
debugMessage += $"\nException: {ex}\nStackTrace: {ex.StackTrace}";
}
Log(debugMessage);
}
public static void LogTip(string message, Exception? ex = null)
{
string tipMessage = $"TIP: {message}";
if (ex != null)
{
tipMessage += $"\nException: {ex}\nStackTrace: {ex.StackTrace}";
}
Log(tipMessage);
}
}
}

View File

@@ -0,0 +1,25 @@
CXX = g++
CXXFLAGS = -I"C:/msys64/ucrt64/include" -I"./include" -std=c++17 -Wall -mwindows
LDFLAGS = -L"C:/msys64/ucrt64/lib" -lfltk -lfltk_images -ltiff -ljpeg -lz -lzstd -lwebp -llerc -ljbig -llzma -ldeflate -lsharpyuv -lcomctl32 -lgdi32 -lole32 -luuid -lws2_32 -lwinspool -lcomdlg32 -static
SRC = src/main.cpp src/gui.cpp src/utils.cpp src/stb_impl.cpp \
src/png_to_jpg.cpp src/jpg_to_png.cpp src/image_loader.cpp \
src/bmp_to_png.cpp src/bmp_to_jpg.cpp src/png_to_bmp.cpp \
src/jpg_to_bmp.cpp src/tiff_to_bmp.cpp src/bmp_to_tiff.cpp \
src/tiff_to_png.cpp src/png_to_tiff.cpp src/tiff_to_jpg.cpp \
src/jpg_to_tiff.cpp
OBJ = $(SRC:src/%.cpp=obj/%.o)
TARGET = image_converter
all: $(TARGET)
$(TARGET): $(OBJ)
$(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS)
obj/%.o: src/%.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJ) $(TARGET)
.PHONY: all clean

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "common.hpp"
class BmpToJpgConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path,
int quality = 90);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "common.hpp"
class BmpToPngConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path,
int compression_level = 6);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include "common.hpp"
class BmpToTiffConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,23 @@
#pragma once
#include <vector>
#include <string>
#include <memory>
#include <stb/stb_image.h>
struct ImageData {
int width;
int height;
int channels;
std::unique_ptr<unsigned char, void(*)(void*)> pixels;
ImageData() : pixels(nullptr, stbi_image_free) {}
};
enum class ImageFormat {
PNG,
JPG,
TIFF,
UNKNOWN
};
ImageFormat get_format_from_extension(const std::string& path);

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "common.hpp"
class ConverterBase {
public:
virtual ~ConverterBase() = default;
virtual bool convert(const std::string& input,
const std::string& output) = 0;
protected:
virtual bool validate(const ImageData& data) = 0;
};

View File

@@ -0,0 +1,23 @@
#pragma once
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Choice.H>
#include <string>
class MainWindow : public Fl_Window {
public:
MainWindow(int w, int h, const char* title);
private:
Fl_Input* input_path;
Fl_Output* output_path;
Fl_Choice* format_choice;
Fl_Button* convert_btn;
static void input_file_cb(Fl_Widget* w, void* data);
static void output_file_cb(Fl_Widget* w, void* data);
static void convert_cb(Fl_Widget* w, void* data);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include "common.hpp"
#include <string>
class ImageLoader {
public:
static ImageData load(const std::string& path);
static bool save_png(const std::string& path, const ImageData& data);
static bool save_jpg(const std::string& path, const ImageData& data, int quality = 90);
private:
static void validate_image(const unsigned char* data, int width, int height);
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include "common.hpp"
class JpgToBmpConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "common.hpp"
class JpgToPngConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path,
int compression_level = 6);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include "common.hpp"
class JpgToTiffConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include "common.hpp"
class PngToBmpConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "common.hpp"
class PngToJpgConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path,
int quality = 90);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include "common.hpp"
class PngToTiffConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include "common.hpp"
class TiffToBmpConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "common.hpp"
class TiffToJpgConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path,
int quality = 90);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include "common.hpp"
class TiffToPngConverter {
public:
static bool convert(const std::string& input_path,
const std::string& output_path,
int compression_level = 6);
private:
static bool validate_input(const ImageData& data);
};

View File

@@ -0,0 +1,35 @@
#include "bmp_to_jpg.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <string>
bool BmpToJpgConverter::convert(const std::string& input_path,
const std::string& output_path,
int quality) {
// 加载BMP图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入格式
if (!validate_input(data)) {
return false;
}
// 保存为JPG
return stbi_write_jpg(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
quality);
}
bool BmpToJpgConverter::validate_input(const ImageData& data) {
// 确保是有效的图像数据
return data.width > 0 && data.height > 0 &&
(data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,35 @@
#include "bmp_to_png.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <string>
bool BmpToPngConverter::convert(const std::string& input_path,
const std::string& output_path,
int compression_level) {
// 加载BMP图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入格式
if (!validate_input(data)) {
return false;
}
// 保存为PNG
return stbi_write_png(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
data.width * data.channels);
}
bool BmpToPngConverter::validate_input(const ImageData& data) {
// 确保是有效的图像数据
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,61 @@
#include "bmp_to_tiff.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <tiffio.h>
#include <stb/stb_image.h>
#include <string>
bool BmpToTiffConverter::convert(const std::string& input_path,
const std::string& output_path) {
// 加载BMP图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入
if (!validate_input(data)) {
return false;
}
// 创建TIFF文件
TIFF* tif = TIFFOpen(output_path.c_str(), "w");
if (!tif) {
return false;
}
// 设置TIFF标签
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, data.width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, data.height);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, data.channels);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
// 根据通道数设置PhotometricInterpretation
if (data.channels == 1) {
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
} else if (data.channels == 3 || data.channels == 4) {
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
} else {
TIFFClose(tif);
return false;
}
// 写入图像数据
tsize_t linebytes = data.width * data.channels;
unsigned char* buf = (unsigned char*)_TIFFmalloc(linebytes);
for (int y = 0; y < data.height; y++) {
memcpy(buf, &data.pixels.get()[y * linebytes], linebytes);
TIFFWriteScanline(tif, buf, y, 0);
}
_TIFFfree(buf);
TIFFClose(tif);
return true;
}
bool BmpToTiffConverter::validate_input(const ImageData& data) {
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,137 @@
#include <locale.h>
#include "gui_interface.hpp"
#include "png_to_jpg.hpp"
#include "jpg_to_png.hpp"
#include "bmp_to_png.hpp"
#include "bmp_to_jpg.hpp"
#include "png_to_bmp.hpp"
#include "jpg_to_bmp.hpp"
#include "tiff_to_bmp.hpp"
#include "bmp_to_tiff.hpp"
#include "tiff_to_png.hpp"
#include "png_to_tiff.hpp"
#include "tiff_to_jpg.hpp"
#include "jpg_to_tiff.hpp"
#include <FL/Fl_File_Chooser.H>
#include <FL/fl_ask.H>
#include <stdexcept>
static void init_locale() {
setlocale(LC_ALL, "chs");
}
MainWindow::MainWindow(int w, int h, const char* title)
: Fl_Window(w, h, "图像格式转换器") {
init_locale();
input_path = new Fl_Input(100, 20, 250, 25, "输入文件:");
Fl_Button* input_btn = new Fl_Button(360, 20, 30, 25, "...");
input_btn->callback(input_file_cb, this);
output_path = new Fl_Output(100, 60, 250, 25, "输出文件:");
Fl_Button* output_btn = new Fl_Button(360, 60, 30, 25, "...");
output_btn->callback(output_file_cb, this);
format_choice = new Fl_Choice(100, 100, 150, 25, "转换格式:");
format_choice->add("PNG to JPG");
format_choice->add("JPG to PNG");
format_choice->add("BMP to PNG");
format_choice->add("BMP to JPG");
format_choice->add("PNG to BMP");
format_choice->add("JPG to BMP");
format_choice->add("TIFF to BMP");
format_choice->add("BMP to TIFF");
format_choice->add("TIFF to PNG");
format_choice->add("PNG to TIFF");
format_choice->add("TIFF to JPG");
format_choice->add("JPG to TIFF");
format_choice->value(0);
convert_btn = new Fl_Button(150, 150, 100, 30, "转换");
convert_btn->callback(convert_cb, this);
end();
}
void MainWindow::input_file_cb(Fl_Widget* w, void* data) {
MainWindow* win = static_cast<MainWindow*>(data);
Fl_File_Chooser chooser(".", "*.*", Fl_File_Chooser::SINGLE, "选择输入文件");
chooser.show();
while(chooser.shown()) Fl::wait();
if(chooser.value()) {
win->input_path->value(chooser.value());
}
}
void MainWindow::output_file_cb(Fl_Widget* w, void* data) {
MainWindow* win = static_cast<MainWindow*>(data);
Fl_File_Chooser chooser(".", "*.*", Fl_File_Chooser::CREATE, "选择输出文件");
chooser.show();
while(chooser.shown()) Fl::wait();
if(chooser.value()) {
win->output_path->value(chooser.value());
}
}
void MainWindow::convert_cb(Fl_Widget* w, void* data) {
MainWindow* win = static_cast<MainWindow*>(data);
std::string input = win->input_path->value();
std::string output = win->output_path->value();
if (input.empty() || output.empty()) {
fl_alert("请输入有效的文件路径!");
return;
}
bool success = false;
try {
switch(win->format_choice->value()) {
case 0: // PNG to JPG
success = PngToJpgConverter::convert(input, output);
break;
case 1: // JPG to PNG
success = JpgToPngConverter::convert(input, output);
break;
case 2: // BMP to PNG
success = BmpToPngConverter::convert(input, output);
break;
case 3: // BMP to JPG
success = BmpToJpgConverter::convert(input, output);
break;
case 4: // PNG to BMP
success = PngToBmpConverter::convert(input, output);
break;
case 5: // JPG to BMP
success = JpgToBmpConverter::convert(input, output);
break;
case 6: // TIFF to BMP
success = TiffToBmpConverter::convert(input, output);
break;
case 7: // BMP to TIFF
success = BmpToTiffConverter::convert(input, output);
break;
case 8: // TIFF to PNG
success = TiffToPngConverter::convert(input, output);
break;
case 9: // PNG to TIFF
success = PngToTiffConverter::convert(input, output);
break;
case 10: // TIFF to JPG
success = TiffToJpgConverter::convert(input, output);
break;
case 11: // JPG to TIFF
success = JpgToTiffConverter::convert(input, output);
break;
}
if (!success) throw std::runtime_error("转换失败");
} catch (const std::exception& e) {
fl_alert("转换错误: %s", e.what());
return;
}
if (success) {
fl_message("转换成功!");
} else {
fl_alert("转换失败,请检查输入文件!");
}
}

View File

@@ -0,0 +1,62 @@
#include "image_loader.hpp"
#include "common.hpp"
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <stdexcept>
#include <iostream>
ImageData ImageLoader::load(const std::string& path) {
ImageData data;
// 加载图像
unsigned char* pixels = stbi_load(path.c_str(),
&data.width,
&data.height,
&data.channels,
0);
if (!pixels) {
throw std::runtime_error("无法加载图像: " + std::string(stbi_failure_reason()));
}
// 验证图像数据
try {
validate_image(pixels, data.width, data.height);
} catch (...) {
stbi_image_free(pixels);
throw;
}
// 转移所有权到智能指针
data.pixels.reset(pixels);
return data;
}
bool ImageLoader::save_png(const std::string& path, const ImageData& data) {
if (!data.pixels || data.width <= 0 || data.height <= 0) {
return false;
}
return stbi_write_png(path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
data.width * data.channels);
}
bool ImageLoader::save_jpg(const std::string& path, const ImageData& data, int quality) {
if (!data.pixels || data.width <= 0 || data.height <= 0) {
return false;
}
return stbi_write_jpg(path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
quality);
}
void ImageLoader::validate_image(const unsigned char* data, int width, int height) {
if (!data || width <= 0 || height <= 0) {
throw std::runtime_error("无效的图像数据");
}
}

View File

@@ -0,0 +1,33 @@
#include "jpg_to_bmp.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <string>
bool JpgToBmpConverter::convert(const std::string& input_path,
const std::string& output_path) {
// 加载JPG图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入格式
if (!validate_input(data)) {
return false;
}
// 保存为BMP
return stbi_write_bmp(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get());
}
bool JpgToBmpConverter::validate_input(const ImageData& data) {
// 确保是有效的图像数据
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,35 @@
#include "jpg_to_png.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <string>
bool JpgToPngConverter::convert(const std::string& input_path,
const std::string& output_path,
int compression_level) {
// 加载JPG图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入格式
if (!validate_input(data)) {
return false;
}
// 保存为PNG
return stbi_write_png(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
data.width * data.channels);
}
bool JpgToPngConverter::validate_input(const ImageData& data) {
// 确保是有效的图像数据
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3);
}

View File

@@ -0,0 +1,61 @@
#include "jpg_to_tiff.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <tiffio.h>
#include <stb/stb_image.h>
#include <string>
bool JpgToTiffConverter::convert(const std::string& input_path,
const std::string& output_path) {
// 加载JPG图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入
if (!validate_input(data)) {
return false;
}
// 创建TIFF文件
TIFF* tif = TIFFOpen(output_path.c_str(), "w");
if (!tif) {
return false;
}
// 设置TIFF标签
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, data.width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, data.height);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, data.channels);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
// 根据通道数设置PhotometricInterpretation
if (data.channels == 1) {
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
} else if (data.channels == 3 || data.channels == 4) {
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
} else {
TIFFClose(tif);
return false;
}
// 写入图像数据
tsize_t linebytes = data.width * data.channels;
unsigned char* buf = (unsigned char*)_TIFFmalloc(linebytes);
for (int y = 0; y < data.height; y++) {
memcpy(buf, &data.pixels.get()[y * linebytes], linebytes);
TIFFWriteScanline(tif, buf, y, 0);
}
_TIFFfree(buf);
TIFFClose(tif);
return true;
}
bool JpgToTiffConverter::validate_input(const ImageData& data) {
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,8 @@
#include "gui_interface.hpp"
#include <FL/Fl.H>
int main(int argc, char** argv) {
MainWindow window(400, 300, "Image Format Converter");
window.show(argc, argv);
return Fl::run();
}

View File

@@ -0,0 +1,33 @@
#include "png_to_bmp.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <string>
bool PngToBmpConverter::convert(const std::string& input_path,
const std::string& output_path) {
// 加载PNG图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入格式
if (!validate_input(data)) {
return false;
}
// 保存为BMP
return stbi_write_bmp(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get());
}
bool PngToBmpConverter::validate_input(const ImageData& data) {
// 确保是有效的图像数据
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,35 @@
#include "png_to_jpg.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>
#include <string>
bool PngToJpgConverter::convert(const std::string& input_path,
const std::string& output_path,
int quality) {
// 加载PNG图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入格式
if (!validate_input(data)) {
return false;
}
// 保存为JPG
return stbi_write_jpg(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
quality);
}
bool PngToJpgConverter::validate_input(const ImageData& data) {
// 确保是有效的图像数据
return data.width > 0 && data.height > 0 &&
(data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,61 @@
#include "png_to_tiff.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <tiffio.h>
#include <stb/stb_image.h>
#include <string>
bool PngToTiffConverter::convert(const std::string& input_path,
const std::string& output_path) {
// 加载PNG图像
ImageData data = ImageLoader::load(input_path);
if (!data.pixels) {
return false;
}
// 验证输入
if (!validate_input(data)) {
return false;
}
// 创建TIFF文件
TIFF* tif = TIFFOpen(output_path.c_str(), "w");
if (!tif) {
return false;
}
// 设置TIFF标签
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, data.width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, data.height);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, data.channels);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
// 根据通道数设置PhotometricInterpretation
if (data.channels == 1) {
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
} else if (data.channels == 3 || data.channels == 4) {
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
} else {
TIFFClose(tif);
return false;
}
// 写入图像数据
tsize_t linebytes = data.width * data.channels;
unsigned char* buf = (unsigned char*)_TIFFmalloc(linebytes);
for (int y = 0; y < data.height; y++) {
memcpy(buf, &data.pixels.get()[y * linebytes], linebytes);
TIFFWriteScanline(tif, buf, y, 0);
}
_TIFFfree(buf);
TIFFClose(tif);
return true;
}
bool PngToTiffConverter::validate_input(const ImageData& data) {
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,4 @@
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb/stb_image.h>
#include <stb/stb_image_write.h>

View File

@@ -0,0 +1,72 @@
#include "tiff_to_bmp.hpp"
#include "common.hpp"
#include "image_loader.hpp"
#include <tiffio.h>
#include <stb/stb_image_write.h>
#include <string>
bool TiffToBmpConverter::convert(const std::string& input_path,
const std::string& output_path) {
// 使用libtiff加载TIFF图像
TIFF* tif = TIFFOpen(input_path.c_str(), "r");
if (!tif) {
return false;
}
// 获取图像信息
uint32 width, height;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
// 获取TIFF格式信息
uint16 samplesperpixel, bitspersample, photometric;
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitspersample);
TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
// 验证TIFF格式
if (bitspersample != 8) {
TIFFClose(tif);
return false;
}
// 设置输出通道数
ImageData data;
data.width = width;
data.height = height;
data.channels = samplesperpixel;
data.pixels.reset(new unsigned char[width * height * data.channels]);
// 读取图像数据
tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif));
for (uint32 row = 0; row < height; row++) {
if (TIFFReadScanline(tif, buf, row) == -1) {
_TIFFfree(buf);
TIFFClose(tif);
return false;
}
memcpy(&data.pixels.get()[row * width * data.channels],
buf,
width * data.channels);
}
_TIFFfree(buf);
TIFFClose(tif);
// 验证输入
if (!validate_input(data)) {
return false;
}
// 保存为BMP
return stbi_write_bmp(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get());
}
bool TiffToBmpConverter::validate_input(const ImageData& data) {
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,73 @@
#include "tiff_to_jpg.hpp"
#include "common.hpp"
#include <tiffio.h>
#include <stb/stb_image_write.h>
#include <string>
bool TiffToJpgConverter::convert(const std::string& input_path,
const std::string& output_path,
int quality) {
// 使用libtiff加载TIFF图像
TIFF* tif = TIFFOpen(input_path.c_str(), "r");
if (!tif) {
return false;
}
// 获取图像信息
uint32 width, height;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
// 获取TIFF格式信息
uint16 samplesperpixel, bitspersample, photometric;
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitspersample);
TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
// 验证TIFF格式
if (bitspersample != 8) {
TIFFClose(tif);
return false;
}
// 设置输出通道数
ImageData data;
data.width = width;
data.height = height;
data.channels = samplesperpixel;
data.pixels.reset(new unsigned char[width * height * data.channels]);
// 读取图像数据
tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif));
for (uint32 row = 0; row < height; row++) {
if (TIFFReadScanline(tif, buf, row) == -1) {
_TIFFfree(buf);
TIFFClose(tif);
return false;
}
memcpy(&data.pixels.get()[row * width * data.channels],
buf,
width * data.channels);
}
_TIFFfree(buf);
TIFFClose(tif);
// 验证输入
if (!validate_input(data)) {
return false;
}
// 保存为JPG
return stbi_write_jpg(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
quality);
}
bool TiffToJpgConverter::validate_input(const ImageData& data) {
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,73 @@
#include "tiff_to_png.hpp"
#include "common.hpp"
#include <tiffio.h>
#include <stb/stb_image_write.h>
#include <string>
bool TiffToPngConverter::convert(const std::string& input_path,
const std::string& output_path,
int compression_level) {
// 使用libtiff加载TIFF图像
TIFF* tif = TIFFOpen(input_path.c_str(), "r");
if (!tif) {
return false;
}
// 获取图像信息
uint32 width, height;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
// 获取TIFF格式信息
uint16 samplesperpixel, bitspersample, photometric;
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitspersample);
TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
// 验证TIFF格式
if (bitspersample != 8) {
TIFFClose(tif);
return false;
}
// 设置输出通道数
ImageData data;
data.width = width;
data.height = height;
data.channels = samplesperpixel;
data.pixels.reset(new unsigned char[width * height * data.channels]);
// 读取图像数据
tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif));
for (uint32 row = 0; row < height; row++) {
if (TIFFReadScanline(tif, buf, row) == -1) {
_TIFFfree(buf);
TIFFClose(tif);
return false;
}
memcpy(&data.pixels.get()[row * width * data.channels],
buf,
width * data.channels);
}
_TIFFfree(buf);
TIFFClose(tif);
// 验证输入
if (!validate_input(data)) {
return false;
}
// 保存为PNG
return stbi_write_png(output_path.c_str(),
data.width,
data.height,
data.channels,
data.pixels.get(),
data.width * data.channels);
}
bool TiffToPngConverter::validate_input(const ImageData& data) {
return data.width > 0 && data.height > 0 &&
(data.channels == 1 || data.channels == 3 || data.channels == 4);
}

View File

@@ -0,0 +1,11 @@
#include "common.hpp"
#include <algorithm>
ImageFormat get_format_from_extension(const std::string& path) {
std::string ext = path.substr(path.find_last_of(".") + 1);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == "png") return ImageFormat::PNG;
if (ext == "jpg" || ext == "jpeg") return ImageFormat::JPG;
return ImageFormat::UNKNOWN;
}

View File

@@ -0,0 +1,29 @@
# FLTK聊天室项目Makefile
CXX = g++
FLTK_CONFIG = fltk-config
CXXFLAGS = -std=c++17 -Wall -Iinclude
LDFLAGS = $(shell $(FLTK_CONFIG) --use-images --ldstaticflags) -static -lws2_32
SRC_DIR = src
OBJ_DIR = obj
INC_DIR = include
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRCS))
TARGET = chat_room.exe
all: $(OBJ_DIR) $(TARGET)
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
$(TARGET): $(OBJS)
$(CXX) $^ -o $@ $(LDFLAGS)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf $(OBJ_DIR)/*.o $(TARGET)
.PHONY: all clean

View File

@@ -0,0 +1,17 @@
#ifndef APPLICATION_HPP
#define APPLICATION_HPP
class MainWindow;
class Application {
public:
Application(int argc, char** argv);
~Application();
int run();
private:
MainWindow* mainWindow;
};
#endif // APPLICATION_HPP

View File

@@ -0,0 +1,36 @@
#ifndef CHATROOM_HPP
#define CHATROOM_HPP
#include "NetworkManager.hpp"
#include "User.hpp"
#include <string>
#include <vector>
#include <memory>
class ChatRoom {
public:
ChatRoom();
~ChatRoom();
bool createRoom(int port, const std::string& password = "");
bool joinRoom(const std::string& ip, int port, const std::string& password);
void sendChatMessage(const std::string& message);
void addUser(const std::string& username);
void removeUser(const std::string& username);
const std::vector<std::string>& getMessages() const;
const std::vector<std::shared_ptr<User>>& getUsers() const;
std::string getCurrentUsername() const;
bool isConnected() const;
NetworkManager* getNetworkManager() const;
std::string getRoomPassword() const;
std::string getLocalIP() const;
private:
std::unique_ptr<NetworkManager> network;
std::vector<std::string> messages;
std::vector<std::shared_ptr<User>> users;
std::string roomPassword;
};
#endif // CHATROOM_HPP

View File

@@ -0,0 +1,31 @@
#ifndef CHATWINDOW_HPP
#define CHATWINDOW_HPP
#include <FL/Fl_Window.H>
#include <FL/Fl_Text_Display.H>
#include <FL/Fl_Text_Buffer.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Browser.H>
#include <memory>
#include "ChatRoom.hpp"
class ChatWindow : public Fl_Window {
public:
ChatWindow(int w, int h, const char* title, std::shared_ptr<ChatRoom> chatRoom);
void appendMessage(const std::string& message);
void updateUserList();
private:
std::shared_ptr<ChatRoom> chatRoom;
Fl_Text_Display* messageDisplay;
Fl_Text_Buffer* messageBuffer;
Fl_Input* messageInput;
Fl_Button* sendButton;
Fl_Browser* userList;
static void onSendMessageCallback(Fl_Widget* w, void* data);
};
#endif // CHATWINDOW_HPP

View File

@@ -0,0 +1,22 @@
#ifndef CONFIGMANAGER_HPP
#define CONFIGMANAGER_HPP
#include <string>
#include <map>
class ConfigManager {
public:
static ConfigManager& getInstance();
void loadConfig(const std::string& filename);
void saveConfig(const std::string& filename);
std::string getValue(const std::string& key);
void setValue(const std::string& key, const std::string& value);
private:
ConfigManager() = default;
std::map<std::string, std::string> configMap;
};
#endif // CONFIGMANAGER_HPP

View File

@@ -0,0 +1,14 @@
#ifndef CONSTANTS_HPP
#define CONSTANTS_HPP
class Constants {
public:
static const int DEFAULT_PORT = 12345;
static const int MAIN_WINDOW_WIDTH = 400;
static const int MAIN_WINDOW_HEIGHT = 300;
static const int CHAT_WINDOW_WIDTH = 800;
static const int CHAT_WINDOW_HEIGHT = 600;
static const char MESSAGE_DELIMITER = '|';
};
#endif // CONSTANTS_HPP

View File

@@ -0,0 +1,45 @@
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Output.H>
#include <memory>
#include "ChatRoom.hpp"
class MainWindow : public Fl_Window {
public:
MainWindow(int w, int h, const char* title);
~MainWindow();
void showCreateRoomDialog();
void showJoinRoomDialog();
void showUsernameDialog();
private:
Fl_Button* createRoomBtn;
Fl_Button* joinRoomBtn;
Fl_Box* titleBox;
Fl_Window* createRoomDialog;
Fl_Window* joinRoomDialog;
Fl_Window* usernameDialog;
Fl_Input* portInput;
Fl_Input* ipInput;
Fl_Input* passwordInput;
Fl_Input* usernameInput;
std::unique_ptr<ChatRoom> chatRoom;
static void onCreateRoomCallback(Fl_Widget* w, void* data);
static void onJoinRoomCallback(Fl_Widget* w, void* data);
static void onCreateRoomConfirmCallback(Fl_Widget* w, void* data);
static void onJoinRoomConfirmCallback(Fl_Widget* w, void* data);
static void onUsernameConfirmCallback(Fl_Widget* w, void* data);
};
#endif // MAINWINDOW_HPP

View File

@@ -0,0 +1,36 @@
#ifndef MESSAGE_HPP
#define MESSAGE_HPP
#include <string>
#include <chrono>
#include <memory>
#include "User.hpp"
enum class MessageType {
NORMAL, // 普通消息
SYSTEM, // 系统消息
TEXT_MESSAGE, // 文本消息
USER_JOIN, // 用户加入
USER_LEAVE, // 用户离开
AUTH // 认证消息
};
class Message {
public:
Message(const std::shared_ptr<User>& sender,
const std::string& content,
MessageType type = MessageType::NORMAL);
const std::shared_ptr<User>& getSender() const;
const std::string& getContent() const;
MessageType getType() const;
std::chrono::system_clock::time_point getTimestamp() const;
private:
std::shared_ptr<User> sender;
std::string content;
MessageType type;
std::chrono::system_clock::time_point timestamp;
};
#endif // MESSAGE_HPP

View File

@@ -0,0 +1,15 @@
#ifndef MESSAGEPROTOCOL_HPP
#define MESSAGEPROTOCOL_HPP
#include <string>
#include <vector>
#include "Message.hpp"
class MessageProtocol {
public:
static std::string encodeMessage(const Message& message);
static Message decodeMessage(const std::string& data);
static bool validateMessage(const std::string& data);
};
#endif // MESSAGEPROTOCOL_HPP

View File

@@ -0,0 +1,34 @@
#ifndef NETWORKEVENTHANDLER_HPP
#define NETWORKEVENTHANDLER_HPP
#include <memory>
#include "ChatRoom.hpp"
#include "MessageProtocol.hpp"
#include <memory>
#include <thread>
#include <atomic>
#include "ChatRoom.hpp"
#include "Message.hpp"
class NetworkEventHandler {
public:
explicit NetworkEventHandler(std::shared_ptr<ChatRoom> chatRoom);
~NetworkEventHandler();
void start();
void stop();
void onConnected();
void onMessageReceived(const std::string& message);
void onError(const std::string& error);
private:
void heartbeatCheck();
std::shared_ptr<ChatRoom> chatRoom;
std::atomic<bool> running;
std::thread heartbeatThread;
};
#endif // NETWORKEVENTHANDLER_HPP

View File

@@ -0,0 +1,38 @@
#ifndef NETWORKMANAGER_HPP
#define NETWORKMANAGER_HPP
#include <string>
#include <vector>
#include <memory>
#include <thread>
#include <mutex>
#include <functional>
#include <winsock2.h>
class NetworkManager {
public:
using MessageCallback = std::function<void(const std::string&)>;
NetworkManager();
~NetworkManager();
bool startServer(int port);
bool connectToServer(const std::string& ip, int port);
void sendMessage(const std::string& message);
std::vector<std::string> receiveMessages();
bool isConnected() const;
void setMessageCallback(MessageCallback callback) {
messageCallback = callback;
}
private:
SOCKET serverSocket;
SOCKET clientSocket;
std::vector<SOCKET> clients;
std::mutex clientsMutex;
std::thread acceptThread;
MessageCallback messageCallback;
};
#endif // NETWORKMANAGER_HPP

View File

@@ -0,0 +1,29 @@
#ifndef ROOMINFO_HPP
#define ROOMINFO_HPP
#include <string>
class RoomInfo {
public:
RoomInfo(const std::string& name,
const std::string& ip,
int port,
const std::string& password);
const std::string& getName() const;
const std::string& getIpAddress() const;
int getPort() const;
const std::string& getPassword() const;
int getUserCount() const;
void setUserCount(int count);
private:
std::string name;
std::string ipAddress;
int port;
std::string password;
int userCount;
};
#endif // ROOMINFO_HPP

View File

@@ -0,0 +1,15 @@
#ifndef STRINGUTILS_HPP
#define STRINGUTILS_HPP
#include <string>
#include <vector>
class StringUtils {
public:
static std::vector<std::string> split(const std::string& str, char delimiter);
static std::string trim(const std::string& str);
static bool startsWith(const std::string& str, const std::string& prefix);
static bool endsWith(const std::string& str, const std::string& suffix);
};
#endif // STRINGUTILS_HPP

View File

@@ -0,0 +1,22 @@
#ifndef USER_HPP
#define USER_HPP
#include <string>
#include <chrono>
class User {
public:
User(const std::string& username, const std::string& ip);
const std::string& getUsername() const;
const std::string& getIpAddress() const;
bool isActive() const;
void updateLastActive();
private:
std::string username;
std::string ipAddress;
std::chrono::system_clock::time_point lastActive;
};
#endif // USER_HPP

View File

@@ -0,0 +1,20 @@
#ifndef USERMANAGER_HPP
#define USERMANAGER_HPP
#include <algorithm>
#include <vector>
#include <memory>
#include "User.hpp"
class UserManager {
public:
void addUser(std::shared_ptr<User> user);
void removeUser(const std::string& username);
std::vector<std::shared_ptr<User>> getUsers() const;
std::shared_ptr<User> findUser(const std::string& username) const;
private:
std::vector<std::shared_ptr<User>> users;
};
#endif // USERMANAGER_HPP

View File

@@ -0,0 +1,46 @@
#include "Application.hpp"
#include "MainWindow.hpp"
#include <FL/Fl.H>
#include <iostream>
Application::Application(int argc, char** argv)
: mainWindow(nullptr) {
try {
std::cout << "正在初始化应用程序..." << std::endl;
mainWindow = new MainWindow(400, 300, "局域网聊天室");
std::cout << "主窗口创建成功" << std::endl;
} catch (const std::exception& e) {
std::cerr << "初始化失败: " << e.what() << std::endl;
}
}
int Application::run() {
std::cout << "应用程序启动..." << std::endl;
if (!mainWindow) {
std::cerr << "致命错误: 主窗口对象创建失败" << std::endl;
return 1;
}
try {
std::cout << "初始化FLTK图形界面..." << std::endl;
Fl::scheme("gtk+");
std::cout << "显示主窗口..." << std::endl;
mainWindow->show();
std::cout << "窗口尺寸: " << mainWindow->w() << "x" << mainWindow->h() << std::endl;
std::cout << "进入主事件循环..." << std::endl;
int ret = Fl::run();
std::cout << "应用程序正常退出" << std::endl;
return ret;
} catch (const std::exception& e) {
std::cerr << "运行时错误: " << e.what() << std::endl;
return 1;
}
}
Application::~Application() {
delete mainWindow;
}

Some files were not shown because too many files have changed in this diff Show More