A:增加可选页面模块
This commit is contained in:
1 parent
37aa848b5f
commit
30451cabbd
27 files changed
+2046
-2
No files matched your search
@@ -13,6 +13,7 @@ using Sharp.NetServer.DependencyInjection;
|
|||||||
using Sharp.Wpf.Services;
|
using Sharp.Wpf.Services;
|
||||||
using Sharp.Wpf.Abstractions;
|
using Sharp.Wpf.Abstractions;
|
||||||
using Sharp.Wpf.HandyControl.DependencyInjection;
|
using Sharp.Wpf.HandyControl.DependencyInjection;
|
||||||
|
using Sharp.Wpf.DevTools.DependencyInjection;
|
||||||
using Plc.Siemens.DependencyInjection;
|
using Plc.Siemens.DependencyInjection;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Sharp.Process.DependencyInjection;
|
using Sharp.Process.DependencyInjection;
|
||||||
@@ -116,6 +117,7 @@ namespace Client
|
|||||||
|
|
||||||
// WPF app services
|
// WPF app services
|
||||||
services.AddSharpHandyControl(); // HandyControl 对话框服务(也注册了 IDialogService)
|
services.AddSharpHandyControl(); // HandyControl 对话框服务(也注册了 IDialogService)
|
||||||
|
services.AddSharpDevTools(); // 开发者工具(开发者工具、日志查看器、性能监控等)
|
||||||
services.AddSingleton<IAuthenticationService, DemoAuthenticationService>();
|
services.AddSingleton<IAuthenticationService, DemoAuthenticationService>();
|
||||||
services.AddSingleton<IUserSession, UserSession>();
|
services.AddSingleton<IUserSession, UserSession>();
|
||||||
// Register DispatcherService with the current UI dispatcher
|
// Register DispatcherService with the current UI dispatcher
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Wpf\Wpf.csproj" />
|
<ProjectReference Include="..\Wpf\Wpf.csproj" />
|
||||||
<ProjectReference Include="..\Wpf.HandyControl\Wpf.HandyControl.csproj" />
|
<ProjectReference Include="..\Wpf.HandyControl\Wpf.HandyControl.csproj" />
|
||||||
|
<ProjectReference Include="..\Wpf.DevTools\Wpf.DevTools.csproj" />
|
||||||
<ProjectReference Include="..\Config\Config.csproj" />
|
<ProjectReference Include="..\Config\Config.csproj" />
|
||||||
<ProjectReference Include="..\NetClient\NetClient.csproj" />
|
<ProjectReference Include="..\NetClient\NetClient.csproj" />
|
||||||
<ProjectReference Include="..\NetServer\NetServer.csproj" />
|
<ProjectReference Include="..\NetServer\NetServer.csproj" />
|
||||||
|
|||||||
@@ -16,8 +16,39 @@
|
|||||||
<hc:GlowWindow.Resources>
|
<hc:GlowWindow.Resources>
|
||||||
<conv:BoolToVisibilityConverter x:Key="Bool2Vis" />
|
<conv:BoolToVisibilityConverter x:Key="Bool2Vis" />
|
||||||
</hc:GlowWindow.Resources>
|
</hc:GlowWindow.Resources>
|
||||||
|
|
||||||
|
<!-- 快捷键绑定 -->
|
||||||
|
<hc:GlowWindow.InputBindings>
|
||||||
|
<KeyBinding Key="F12" Command="{Binding OpenDevToolsCommand}" />
|
||||||
|
<KeyBinding Key="F11" Command="{Binding OpenLogViewerCommand}" />
|
||||||
|
<KeyBinding Key="F10" Command="{Binding OpenPerformanceMonitorCommand}" />
|
||||||
|
</hc:GlowWindow.InputBindings>
|
||||||
<Grid Background="{DynamicResource RegionBrush}">
|
<Grid Background="{DynamicResource RegionBrush}">
|
||||||
<Grid Margin="24">
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 菜单栏 -->
|
||||||
|
<Menu Grid.Row="0" Background="{DynamicResource RegionBrush}" Padding="8,4">
|
||||||
|
<MenuItem Header="文件(_F)">
|
||||||
|
<MenuItem Header="退出(_X)" InputGestureText="Alt+F4" Click="MenuItem_Exit_Click" />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="工具(_T)">
|
||||||
|
<MenuItem Header="PLC 调试(_P)" Command="{Binding OpenPlcToolCommand}" />
|
||||||
|
<MenuItem Header="串口调试(_S)" Command="{Binding OpenSerialPortToolCommand}" />
|
||||||
|
<MenuItem Header="Process/Python 测试(_T)" Command="{Binding OpenProcessTestCommand}" />
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="开发者工具(_D)" Command="{Binding OpenDevToolsCommand}" InputGestureText="F12" />
|
||||||
|
<MenuItem Header="日志查看器(_L)" Command="{Binding OpenLogViewerCommand}" InputGestureText="F11" />
|
||||||
|
<MenuItem Header="性能监控(_M)" Command="{Binding OpenPerformanceMonitorCommand}" InputGestureText="F10" />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="帮助(_H)">
|
||||||
|
<MenuItem Header="关于(_A)" Command="{Binding OpenAboutCommand}" />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" Margin="24">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Windows;
|
||||||
using HandyControl.Controls;
|
using HandyControl.Controls;
|
||||||
using Client.ViewModels;
|
using Client.ViewModels;
|
||||||
|
|
||||||
@@ -10,4 +11,9 @@ public partial class MainWindow : GlowWindow
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = viewModel;
|
DataContext = viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MenuItem_Exit_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,8 @@ using Client.Hardware;
|
|||||||
using Plc.Proxy;
|
using Plc.Proxy;
|
||||||
using Plc.Abstractions;
|
using Plc.Abstractions;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
using Sharp.Wpf.Abstractions;
|
using Sharp.Wpf.Abstractions;
|
||||||
|
using Sharp.Wpf.DevTools.Views;
|
||||||
|
|
||||||
namespace Client.ViewModels;
|
namespace Client.ViewModels;
|
||||||
|
|
||||||
@@ -61,6 +61,35 @@ public sealed class MainViewModel : ViewModelBase
|
|||||||
win.Show();
|
win.Show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// DevTools 窗口命令
|
||||||
|
OpenDevToolsCommand = new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
var sp = Global.Get<IServiceProvider>();
|
||||||
|
var win = sp.GetRequiredService<DevToolsWindow>();
|
||||||
|
win.Show();
|
||||||
|
});
|
||||||
|
|
||||||
|
OpenLogViewerCommand = new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
var sp = Global.Get<IServiceProvider>();
|
||||||
|
var win = sp.GetRequiredService<LogViewerWindow>();
|
||||||
|
win.Show();
|
||||||
|
});
|
||||||
|
|
||||||
|
OpenPerformanceMonitorCommand = new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
var sp = Global.Get<IServiceProvider>();
|
||||||
|
var win = sp.GetRequiredService<PerformanceMonitorWindow>();
|
||||||
|
win.Show();
|
||||||
|
});
|
||||||
|
|
||||||
|
OpenAboutCommand = new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
var sp = Global.Get<IServiceProvider>();
|
||||||
|
var win = sp.GetRequiredService<AboutWindow>();
|
||||||
|
win.ShowDialog();
|
||||||
|
});
|
||||||
|
|
||||||
// 1. 获取 PLC 服务
|
// 1. 获取 PLC 服务
|
||||||
var plcService = Global.Get<IServiceProvider>().GetRequiredService<IPlcService>();
|
var plcService = Global.Get<IServiceProvider>().GetRequiredService<IPlcService>();
|
||||||
|
|
||||||
@@ -120,6 +149,12 @@ public sealed class MainViewModel : ViewModelBase
|
|||||||
public ICommand StartMachineCommand { get; }
|
public ICommand StartMachineCommand { get; }
|
||||||
public ICommand StopMachineCommand { get; }
|
public ICommand StopMachineCommand { get; }
|
||||||
|
|
||||||
|
// DevTools 命令
|
||||||
|
public ICommand OpenDevToolsCommand { get; }
|
||||||
|
public ICommand OpenLogViewerCommand { get; }
|
||||||
|
public ICommand OpenPerformanceMonitorCommand { get; }
|
||||||
|
public ICommand OpenAboutCommand { get; }
|
||||||
|
|
||||||
private async Task ShowDialogAsync()
|
private async Task ShowDialogAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Process", "Process\Process.
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Python", "Python\Python.csproj", "{99FD794A-E5A7-4A55-8193-F30541B3EEF5}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Python", "Python\Python.csproj", "{99FD794A-E5A7-4A55-8193-F30541B3EEF5}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wpf.DevTools", "Wpf.DevTools\Wpf.DevTools.csproj", "{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -335,6 +337,18 @@ Global
|
|||||||
{99FD794A-E5A7-4A55-8193-F30541B3EEF5}.Release|x64.Build.0 = Release|Any CPU
|
{99FD794A-E5A7-4A55-8193-F30541B3EEF5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{99FD794A-E5A7-4A55-8193-F30541B3EEF5}.Release|x86.ActiveCfg = Release|Any CPU
|
{99FD794A-E5A7-4A55-8193-F30541B3EEF5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{99FD794A-E5A7-4A55-8193-F30541B3EEF5}.Release|x86.Build.0 = Release|Any CPU
|
{99FD794A-E5A7-4A55-8193-F30541B3EEF5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{3DFC9CBE-D9A3-43A8-80D9-D877000A0BAA}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Sharp.Wpf.DevTools.Services;
|
||||||
|
using Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
using Sharp.Wpf.DevTools.Views;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.DependencyInjection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DevTools 服务注册扩展
|
||||||
|
/// </summary>
|
||||||
|
public static class DevToolsServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 添加 Sharp.Wpf.DevTools 开发者工具
|
||||||
|
/// 注册所有开发者工具窗口和服务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">服务集合</param>
|
||||||
|
/// <returns>服务集合</returns>
|
||||||
|
public static IServiceCollection AddSharpDevTools(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
// 注册服务
|
||||||
|
services.TryAddSingleton<IDevToolsService, DevToolsService>();
|
||||||
|
services.TryAddSingleton<ILogReaderService, LogReaderService>();
|
||||||
|
|
||||||
|
// 注册 ViewModels
|
||||||
|
services.AddTransient<DevToolsViewModel>();
|
||||||
|
services.AddTransient<LogViewerViewModel>();
|
||||||
|
services.AddTransient<PerformanceMonitorViewModel>();
|
||||||
|
services.AddTransient<AboutViewModel>();
|
||||||
|
|
||||||
|
// 注册 Windows
|
||||||
|
services.AddTransient<DevToolsWindow>();
|
||||||
|
services.AddTransient<LogViewerWindow>();
|
||||||
|
services.AddTransient<PerformanceMonitorWindow>();
|
||||||
|
services.AddTransient<AboutWindow>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,646 @@
|
|||||||
|
# Sharp.Wpf.DevTools
|
||||||
|
|
||||||
|
WPF 开发者工具模块,提供开箱即用的开发辅助窗口,包括日志查看器、性能监控、系统信息等实用工具。
|
||||||
|
|
||||||
|
## 📦 模块概述
|
||||||
|
|
||||||
|
Sharp.Wpf.DevTools 是一个独立的 WPF 开发工具集合,为开发者和运维人员提供常用的诊断和调试窗口。本模块**不强制绑定快捷键**,所有窗口均通过依赖注入提供,由调用者决定如何集成和使用。
|
||||||
|
|
||||||
|
### 特性
|
||||||
|
|
||||||
|
- 🔧 **开发者工具窗口**:快捷操作和系统信息一览
|
||||||
|
- 📄 **日志查看器**:实时查看、搜索、过滤应用日志
|
||||||
|
- 📊 **性能监控器**:实时监控 CPU、内存、GC 等性能指标
|
||||||
|
- ℹ️ **关于窗口**:显示应用程序和框架信息
|
||||||
|
- 💉 **完全 DI 集成**:所有窗口和服务均可注入
|
||||||
|
- 🎨 **无 UI 库依赖**:纯 WPF 实现,可与任何 UI 库搭配使用
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet add reference Sharp.Wpf.DevTools.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
或通过 NuGet(待发布):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet add package Sharp.Wpf.DevTools
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 注册服务
|
||||||
|
|
||||||
|
在 `App.xaml.cs` 的 `OnStartup` 方法中注册 DevTools 服务:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Sharp.Wpf.DevTools.DependencyInjection;
|
||||||
|
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
// 注册 DevTools(一行代码搞定)
|
||||||
|
services.AddSharpDevTools();
|
||||||
|
|
||||||
|
// 注册其他服务...
|
||||||
|
|
||||||
|
_serviceProvider = services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用窗口
|
||||||
|
|
||||||
|
通过依赖注入获取窗口并显示:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 注入到 ViewModel 或其他服务
|
||||||
|
public class MainViewModel
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public MainViewModel(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowDevTools()
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<DevToolsWindow>();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowLogViewer()
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<LogViewerWindow>();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowPerformanceMonitor()
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<PerformanceMonitorWindow>();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowAbout()
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<AboutWindow>();
|
||||||
|
window.ShowDialog(); // 模态显示
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 可用窗口
|
||||||
|
|
||||||
|
### 1. DevToolsWindow - 开发者工具
|
||||||
|
|
||||||
|
提供快捷操作和系统信息查看。
|
||||||
|
|
||||||
|
**主要功能**:
|
||||||
|
- 📁 打开运行目录:快速访问应用程序根目录
|
||||||
|
- 📄 打开日志目录:快速访问日志文件夹
|
||||||
|
- ⚙️ 打开配置文件:直接打开 appsettings.json
|
||||||
|
- 🗑️ 清除缓存:清空应用缓存(如果有)
|
||||||
|
- 📦 导出诊断信息:打包配置、日志、系统信息为 ZIP 文件到桌面
|
||||||
|
- 🔄 刷新信息:更新系统信息显示
|
||||||
|
|
||||||
|
**系统信息显示**:
|
||||||
|
- 操作系统版本
|
||||||
|
- .NET 运行时版本
|
||||||
|
- CPU 核心数
|
||||||
|
- 内存使用(工作集)
|
||||||
|
- 应用程序运行时长
|
||||||
|
|
||||||
|
**使用示例**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在菜单项中集成
|
||||||
|
private void MenuItem_DevTools_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<DevToolsWindow>();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. LogViewerWindow - 日志查看器
|
||||||
|
|
||||||
|
实时查看和搜索应用程序日志文件。
|
||||||
|
|
||||||
|
**主要功能**:
|
||||||
|
- 📋 日志文件列表:自动发现 logs 目录下的所有日志文件
|
||||||
|
- 🔍 实时搜索:输入关键字即时过滤日志条目
|
||||||
|
- 📅 时间排序:按时间倒序显示(最新的在前)
|
||||||
|
- 🎨 级别着色:根据日志级别(Info/Warning/Error)显示不同颜色
|
||||||
|
- 📝 详情查看:点击日志条目查看完整内容(支持多行日志、堆栈跟踪)
|
||||||
|
- 🔄 刷新列表:重新加载日志文件
|
||||||
|
- 🗑️ 清除日志:清空所有日志文件(谨慎使用)
|
||||||
|
|
||||||
|
**使用示例**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在工具栏按钮中集成
|
||||||
|
private void ToolbarButton_Logs_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<LogViewerWindow>();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**日志格式要求**:
|
||||||
|
|
||||||
|
LogViewerWindow 使用正则表达式解析日志,建议使用以下格式:
|
||||||
|
|
||||||
|
```
|
||||||
|
[2025-12-22 14:30:45] [Info] 应用程序启动
|
||||||
|
[2025-12-22 14:30:46] [Warning] 配置文件不存在,使用默认配置
|
||||||
|
[2025-12-22 14:30:50] [Error] 数据库连接失败
|
||||||
|
System.Data.SqlClient.SqlException: 连接超时
|
||||||
|
at System.Data.SqlClient.SqlConnection.Open()
|
||||||
|
at MyApp.Database.Connect()
|
||||||
|
```
|
||||||
|
|
||||||
|
格式:`[日期时间] [级别] 消息内容`
|
||||||
|
|
||||||
|
多行日志(如堆栈跟踪)会自动附加到上一条日志的 FullContent 中。
|
||||||
|
|
||||||
|
### 3. PerformanceMonitorWindow - 性能监控器
|
||||||
|
|
||||||
|
实时监控应用程序性能指标。
|
||||||
|
|
||||||
|
**监控指标**:
|
||||||
|
- **CPU 使用率**:当前进程的 CPU 占用百分比
|
||||||
|
- **内存使用**:
|
||||||
|
- 工作集(Working Set):进程占用的物理内存
|
||||||
|
- 私有内存(Private Bytes):进程独占的内存
|
||||||
|
- **线程数**:当前进程的线程总数
|
||||||
|
- **GC 统计**:
|
||||||
|
- Gen 0 回收次数
|
||||||
|
- Gen 1 回收次数
|
||||||
|
- Gen 2 回收次数
|
||||||
|
- **运行时长**:应用程序启动以来的运行时间
|
||||||
|
|
||||||
|
**控制按钮**:
|
||||||
|
- ▶️ 开始监控:启动定时刷新(每秒)
|
||||||
|
- ⏸️ 停止监控:停止刷新
|
||||||
|
- 🗑️ 强制 GC:立即触发垃圾回收
|
||||||
|
|
||||||
|
**使用示例**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在性能分析时使用
|
||||||
|
private void MenuItem_Performance_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<PerformanceMonitorWindow>();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- 性能监控器默认自动启动,每秒刷新一次
|
||||||
|
- CPU 使用率为累计平均值,不是瞬时值
|
||||||
|
- 强制 GC 会触发三次完整回收(包括 Finalizers),可能影响性能
|
||||||
|
|
||||||
|
### 4. AboutWindow - 关于窗口
|
||||||
|
|
||||||
|
显示应用程序和框架信息的美观对话框。
|
||||||
|
|
||||||
|
**显示信息**:
|
||||||
|
- 应用程序名称(从 AssemblyTitle 读取)
|
||||||
|
- 版本号(从 AssemblyVersion 读取)
|
||||||
|
- 版权信息(从 AssemblyCopyright 读取)
|
||||||
|
- 框架版本:Sharp.Basic 版本
|
||||||
|
- .NET 运行时版本
|
||||||
|
- 操作系统版本
|
||||||
|
- 开源许可证信息
|
||||||
|
|
||||||
|
**使用示例**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在菜单"关于"中显示
|
||||||
|
private void MenuItem_About_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var window = _serviceProvider.GetRequiredService<AboutWindow>();
|
||||||
|
window.ShowDialog(); // 模态显示
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**自定义程序集信息**:
|
||||||
|
|
||||||
|
在项目的 `.csproj` 中添加程序集属性:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyTitle>我的应用程序</AssemblyTitle>
|
||||||
|
<Company>我的公司</Company>
|
||||||
|
<Copyright>Copyright © 2025 我的公司</Copyright>
|
||||||
|
<Version>1.2.3</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
或在 `AssemblyInfo.cs` 中设置:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[assembly: AssemblyTitle("我的应用程序")]
|
||||||
|
[assembly: AssemblyCompany("我的公司")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025 我的公司")]
|
||||||
|
[assembly: AssemblyVersion("1.2.3")]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 服务接口
|
||||||
|
|
||||||
|
### IDevToolsService
|
||||||
|
|
||||||
|
提供开发者工具的核心操作。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IDevToolsService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 打开应用程序运行目录
|
||||||
|
/// </summary>
|
||||||
|
void OpenRuntimeDirectory();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开日志目录
|
||||||
|
/// </summary>
|
||||||
|
void OpenLogDirectory();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开配置文件(appsettings.json)
|
||||||
|
/// </summary>
|
||||||
|
void OpenConfigFile();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除缓存(如果应用有缓存)
|
||||||
|
/// </summary>
|
||||||
|
Task ClearCacheAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出诊断信息为 ZIP 文件到桌面
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>导出文件的完整路径</returns>
|
||||||
|
Task<string> ExportDiagnosticsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统信息
|
||||||
|
/// </summary>
|
||||||
|
SystemInfo GetSystemInfo();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ILogReaderService
|
||||||
|
|
||||||
|
提供日志文件读取和解析。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface ILogReaderService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有可用的日志文件列表
|
||||||
|
/// </summary>
|
||||||
|
List<string> GetLogFiles();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取指定日志文件的所有条目
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">日志文件路径(为空则读取最新文件)</param>
|
||||||
|
Task<List<LogEntry>> ReadLogsAsync(string? filePath = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除所有日志文件
|
||||||
|
/// </summary>
|
||||||
|
Task ClearLogsAsync();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用服务示例
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在自定义服务中使用 DevTools 服务
|
||||||
|
public class MyCustomService
|
||||||
|
{
|
||||||
|
private readonly IDevToolsService _devTools;
|
||||||
|
private readonly ILogReaderService _logReader;
|
||||||
|
|
||||||
|
public MyCustomService(IDevToolsService devTools, ILogReaderService logReader)
|
||||||
|
{
|
||||||
|
_devTools = devTools;
|
||||||
|
_logReader = logReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformDiagnostics()
|
||||||
|
{
|
||||||
|
// 获取系统信息
|
||||||
|
var sysInfo = _devTools.GetSystemInfo();
|
||||||
|
Console.WriteLine($"内存: {sysInfo.WorkingSetMB} MB");
|
||||||
|
|
||||||
|
// 读取最新日志
|
||||||
|
var logs = await _logReader.ReadLogsAsync();
|
||||||
|
var errors = logs.Where(l => l.Level == "Error").ToList();
|
||||||
|
|
||||||
|
if (errors.Count > 10)
|
||||||
|
{
|
||||||
|
// 自动导出诊断信息
|
||||||
|
var zipPath = await _devTools.ExportDiagnosticsAsync();
|
||||||
|
Console.WriteLine($"诊断信息已导出到: {zipPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 集成场景
|
||||||
|
|
||||||
|
### 场景 1:菜单栏集成
|
||||||
|
|
||||||
|
在主窗口菜单中添加"工具"菜单:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Menu>
|
||||||
|
<MenuItem Header="工具(_T)">
|
||||||
|
<MenuItem Header="开发者工具(_D)" Click="MenuItem_DevTools_Click"/>
|
||||||
|
<MenuItem Header="日志查看器(_L)" Click="MenuItem_LogViewer_Click"/>
|
||||||
|
<MenuItem Header="性能监控(_P)" Click="MenuItem_Performance_Click"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="关于(_A)" Click="MenuItem_About_Click"/>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
代码后台:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private void MenuItem_DevTools_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_serviceProvider.GetRequiredService<DevToolsWindow>().Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MenuItem_LogViewer_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_serviceProvider.GetRequiredService<LogViewerWindow>().Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MenuItem_Performance_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_serviceProvider.GetRequiredService<PerformanceMonitorWindow>().Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MenuItem_About_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_serviceProvider.GetRequiredService<AboutWindow>().ShowDialog();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 2:工具栏按钮集成
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<ToolBar>
|
||||||
|
<Button Content="🔧 工具" Click="Toolbar_DevTools_Click" ToolTip="开发者工具"/>
|
||||||
|
<Button Content="📄 日志" Click="Toolbar_Logs_Click" ToolTip="查看日志"/>
|
||||||
|
<Button Content="📊 性能" Click="Toolbar_Performance_Click" ToolTip="性能监控"/>
|
||||||
|
</ToolBar>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 3:快捷键集成(可选)
|
||||||
|
|
||||||
|
在主窗口 XAML 中定义 InputBindings:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Window.InputBindings>
|
||||||
|
<KeyBinding Key="F12" Command="{Binding ShowDevToolsCommand}"/>
|
||||||
|
<KeyBinding Key="F11" Command="{Binding ShowLogsCommand}"/>
|
||||||
|
<KeyBinding Key="F10" Command="{Binding ShowPerformanceCommand}"/>
|
||||||
|
</Window.InputBindings>
|
||||||
|
```
|
||||||
|
|
||||||
|
ViewModel 中:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public ICommand ShowDevToolsCommand => new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
_serviceProvider.GetRequiredService<DevToolsWindow>().Show();
|
||||||
|
});
|
||||||
|
|
||||||
|
public ICommand ShowLogsCommand => new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
_serviceProvider.GetRequiredService<LogViewerWindow>().Show();
|
||||||
|
});
|
||||||
|
|
||||||
|
public ICommand ShowPerformanceCommand => new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
_serviceProvider.GetRequiredService<PerformanceMonitorWindow>().Show();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 4:调试模式专用
|
||||||
|
|
||||||
|
只在调试模式下显示开发者工具菜单:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
// 仅在 Debug 模式下显示工具菜单
|
||||||
|
ToolsMenu.Visibility = Visibility.Visible;
|
||||||
|
#else
|
||||||
|
ToolsMenu.Visibility = Visibility.Collapsed;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 5:权限控制
|
||||||
|
|
||||||
|
根据用户权限显示开发者工具:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public MainViewModel(IUserSession userSession, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
if (userSession.CurrentUser?.IsAdmin == true)
|
||||||
|
{
|
||||||
|
// 管理员可见
|
||||||
|
ShowDevToolsCommand = new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
serviceProvider.GetRequiredService<DevToolsWindow>().Show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 普通用户不可见
|
||||||
|
ShowDevToolsCommand = new RelayCommand(() =>
|
||||||
|
{
|
||||||
|
MessageBox.Show("您没有权限访问开发者工具", "权限不足", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ 配置
|
||||||
|
|
||||||
|
### 日志目录配置
|
||||||
|
|
||||||
|
DevTools 默认使用以下路径查找日志:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 默认日志目录
|
||||||
|
Path.Combine(AppContext.BaseDirectory, "logs")
|
||||||
|
```
|
||||||
|
|
||||||
|
如果使用 Sharp.Log 模块,确保日志输出到 `logs` 目录:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// appsettings.json
|
||||||
|
{
|
||||||
|
"SharpLog": {
|
||||||
|
"LogDirectory": "logs",
|
||||||
|
"RollingInterval": "Day"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置文件路径
|
||||||
|
|
||||||
|
DevTools 默认打开以下配置文件:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Path.Combine(AppContext.BaseDirectory, "appsettings.json")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义目录
|
||||||
|
|
||||||
|
如果需要自定义日志或配置目录,可以继承 `DevToolsService` 并重写相关方法:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class CustomDevToolsService : DevToolsService
|
||||||
|
{
|
||||||
|
protected override string GetLogDirectory()
|
||||||
|
{
|
||||||
|
return @"C:\MyApp\CustomLogs";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string GetConfigFilePath()
|
||||||
|
{
|
||||||
|
return @"C:\MyApp\config.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册时替换默认实现
|
||||||
|
services.AddSingleton<IDevToolsService, CustomDevToolsService>();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 依赖项
|
||||||
|
|
||||||
|
- **Sharp.Common**:Result<T>、Global 容器等核心功能
|
||||||
|
- **Sharp.Wpf**:ViewModelBase、Commands、对话框服务等 MVVM 基础设施
|
||||||
|
- **Sharp.Config**:ConfigManager(可选,用于 ConfigEditorWindow)
|
||||||
|
- **.NET 8.0-windows**:WPF 框架
|
||||||
|
|
||||||
|
**无外部 UI 库依赖**:本模块不依赖 HandyControl 或其他第三方 UI 库,使用纯 WPF 控件。
|
||||||
|
|
||||||
|
## 🔍 FAQ
|
||||||
|
|
||||||
|
### 1. 为什么不强制绑定快捷键?
|
||||||
|
|
||||||
|
不同应用有不同的快捷键需求,框架不应做假设。我们提供窗口,由调用者决定如何触发(菜单、工具栏、快捷键、右键菜单等)。
|
||||||
|
|
||||||
|
### 2. 窗口可以多次打开吗?
|
||||||
|
|
||||||
|
可以。所有窗口均注册为 `Transient` 生命周期,每次注入都会创建新实例。如果需要单例行为(同一窗口只打开一次),可以在 ViewModel 中自行管理:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private DevToolsWindow? _devToolsWindow;
|
||||||
|
|
||||||
|
private void ShowDevTools()
|
||||||
|
{
|
||||||
|
if (_devToolsWindow == null || !_devToolsWindow.IsLoaded)
|
||||||
|
{
|
||||||
|
_devToolsWindow = _serviceProvider.GetRequiredService<DevToolsWindow>();
|
||||||
|
_devToolsWindow.Closed += (s, e) => _devToolsWindow = null;
|
||||||
|
_devToolsWindow.Show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_devToolsWindow.Activate(); // 已存在则激活
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 如何定制窗口样式?
|
||||||
|
|
||||||
|
所有窗口都是标准 WPF Window,可以通过以下方式定制:
|
||||||
|
|
||||||
|
**方式 1:继承并修改 XAML**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MyDevToolsWindow : DevToolsWindow
|
||||||
|
{
|
||||||
|
public MyDevToolsWindow(DevToolsViewModel viewModel) : base(viewModel)
|
||||||
|
{
|
||||||
|
// 自定义样式
|
||||||
|
Background = Brushes.DarkGray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**方式 2:应用全局样式**
|
||||||
|
|
||||||
|
在 App.xaml 中定义 Window 样式:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<Style TargetType="{x:Type local:DevToolsWindow}">
|
||||||
|
<Setter Property="Background" Value="#FF2D2D30"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
</Style>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 性能监控会影响应用性能吗?
|
||||||
|
|
||||||
|
性能监控使用 `DispatcherTimer` 每秒刷新一次,对性能影响微乎其微(< 0.1% CPU)。如果担心影响,可以在不需要时关闭窗口,监控会自动停止。
|
||||||
|
|
||||||
|
### 5. 日志查看器支持哪些日志格式?
|
||||||
|
|
||||||
|
目前支持常见的结构化日志格式:`[时间] [级别] 消息`。如果使用 Serilog 或 NLog,建议配置类似格式:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Serilog
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.WriteTo.File("logs/app.log",
|
||||||
|
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level}] {Message:lj}{NewLine}{Exception}")
|
||||||
|
.CreateLogger();
|
||||||
|
```
|
||||||
|
|
||||||
|
如果日志格式不同,可以继承 `LogReaderService` 并重写 `ReadLogsAsync` 方法,使用自定义解析逻辑。
|
||||||
|
|
||||||
|
### 6. 可以只使用部分窗口吗?
|
||||||
|
|
||||||
|
可以。虽然 `AddSharpDevTools()` 会注册所有窗口,但由于是 Transient 生命周期,不使用的窗口不会被实例化,不会占用资源。如果确实想减少注册,可以手动注册:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
services.AddSingleton<IDevToolsService, DevToolsService>();
|
||||||
|
services.AddTransient<DevToolsViewModel>();
|
||||||
|
services.AddTransient<DevToolsWindow>();
|
||||||
|
// 只注册需要的窗口
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
本模块遵循 Sharp.Basic 框架的许可证:**GNU AGPLv3**
|
||||||
|
|
||||||
|
- 开源项目可自由使用
|
||||||
|
- 商业项目如果通过网络提供服务,需要开源修改部分
|
||||||
|
- 如需商业许可,请联系作者
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request!
|
||||||
|
|
||||||
|
## 📞 支持
|
||||||
|
|
||||||
|
- GitHub Issues: https://github.com/your-org/sharp-basic/issues
|
||||||
|
- 文档: https://your-docs-site.com
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Sharp.Basic** - 固执己见的 .NET 8 框架,让开发更简单。
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开发者工具服务实现
|
||||||
|
/// </summary>
|
||||||
|
public class DevToolsService : IDevToolsService
|
||||||
|
{
|
||||||
|
private readonly ILogger<DevToolsService> _logger;
|
||||||
|
private static readonly DateTime _startTime = DateTime.Now;
|
||||||
|
|
||||||
|
public DevToolsService(ILogger<DevToolsService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenRuntimeDirectory()
|
||||||
|
{
|
||||||
|
var path = AppContext.BaseDirectory;
|
||||||
|
OpenDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenLogDirectory()
|
||||||
|
{
|
||||||
|
var logPath = Path.Combine(AppContext.BaseDirectory, "logs");
|
||||||
|
if (!Directory.Exists(logPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(logPath);
|
||||||
|
}
|
||||||
|
OpenDirectory(logPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenConfigFile()
|
||||||
|
{
|
||||||
|
var configPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||||
|
if (File.Exists(configPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = configPath,
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "打开配置文件失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearCacheAsync()
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cachePath = Path.Combine(AppContext.BaseDirectory, "cache");
|
||||||
|
if (Directory.Exists(cachePath))
|
||||||
|
{
|
||||||
|
Directory.Delete(cachePath, true);
|
||||||
|
Directory.CreateDirectory(cachePath);
|
||||||
|
}
|
||||||
|
_logger.LogInformation("缓存已清除");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "清除缓存失败");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExportDiagnosticsAsync()
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var exportPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||||
|
$"Diagnostics_{DateTime.Now:yyyyMMdd_HHmmss}.zip");
|
||||||
|
|
||||||
|
using var zip = ZipFile.Open(exportPath, ZipArchiveMode.Create);
|
||||||
|
|
||||||
|
// 添加配置文件
|
||||||
|
var configPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||||
|
if (File.Exists(configPath))
|
||||||
|
{
|
||||||
|
zip.CreateEntryFromFile(configPath, "appsettings.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加日志文件
|
||||||
|
var logPath = Path.Combine(AppContext.BaseDirectory, "logs");
|
||||||
|
if (Directory.Exists(logPath))
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(logPath, "*.log"))
|
||||||
|
{
|
||||||
|
zip.CreateEntryFromFile(file, $"logs/{Path.GetFileName(file)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加系统信息
|
||||||
|
var sysInfo = GetSystemInfo();
|
||||||
|
var sysInfoEntry = zip.CreateEntry("SystemInfo.txt");
|
||||||
|
using var writer = new StreamWriter(sysInfoEntry.Open());
|
||||||
|
writer.WriteLine($"操作系统: {sysInfo.OSVersion}");
|
||||||
|
writer.WriteLine($".NET版本: {sysInfo.DotNetVersion}");
|
||||||
|
writer.WriteLine($"计算机名: {sysInfo.MachineName}");
|
||||||
|
writer.WriteLine($"用户名: {sysInfo.UserName}");
|
||||||
|
writer.WriteLine($"CPU核心数: {sysInfo.ProcessorCount}");
|
||||||
|
writer.WriteLine($"总内存: {sysInfo.TotalMemoryMB} MB");
|
||||||
|
writer.WriteLine($"工作集内存: {sysInfo.WorkingSetMB} MB");
|
||||||
|
writer.WriteLine($"运行时长: {sysInfo.Uptime}");
|
||||||
|
|
||||||
|
_logger.LogInformation("诊断信息已导出到: {Path}", exportPath);
|
||||||
|
|
||||||
|
// 打开文件所在目录
|
||||||
|
Process.Start("explorer.exe", $"/select,\"{exportPath}\"");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "导出诊断信息失败");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public SystemInfo GetSystemInfo()
|
||||||
|
{
|
||||||
|
var process = Process.GetCurrentProcess();
|
||||||
|
|
||||||
|
return new SystemInfo
|
||||||
|
{
|
||||||
|
OSVersion = Environment.OSVersion.ToString(),
|
||||||
|
DotNetVersion = Environment.Version.ToString(),
|
||||||
|
MachineName = Environment.MachineName,
|
||||||
|
UserName = Environment.UserName,
|
||||||
|
ProcessorCount = Environment.ProcessorCount,
|
||||||
|
TotalMemoryMB = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes / 1024 / 1024,
|
||||||
|
WorkingSetMB = process.WorkingSet64 / 1024 / 1024,
|
||||||
|
Uptime = DateTime.Now - _startTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenDirectory(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = path,
|
||||||
|
UseShellExecute = true,
|
||||||
|
Verb = "open"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "打开目录失败: {Path}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
namespace Sharp.Wpf.DevTools.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开发者工具服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IDevToolsService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 打开运行目录
|
||||||
|
/// </summary>
|
||||||
|
void OpenRuntimeDirectory();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开日志目录
|
||||||
|
/// </summary>
|
||||||
|
void OpenLogDirectory();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开配置文件
|
||||||
|
/// </summary>
|
||||||
|
void OpenConfigFile();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除缓存
|
||||||
|
/// </summary>
|
||||||
|
Task ClearCacheAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出诊断信息
|
||||||
|
/// </summary>
|
||||||
|
Task ExportDiagnosticsAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取系统信息
|
||||||
|
/// </summary>
|
||||||
|
SystemInfo GetSystemInfo();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Sharp.Wpf.DevTools.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志读取服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface ILogReaderService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 读取日志文件
|
||||||
|
/// </summary>
|
||||||
|
Task<List<LogEntry>> ReadLogsAsync(string? filePath = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取日志文件列表
|
||||||
|
/// </summary>
|
||||||
|
List<string> GetLogFiles();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除日志
|
||||||
|
/// </summary>
|
||||||
|
Task ClearLogsAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Sharp.Wpf.DevTools.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志条目
|
||||||
|
/// </summary>
|
||||||
|
public class LogEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志级别
|
||||||
|
/// </summary>
|
||||||
|
public string Level { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 完整内容(包含堆栈跟踪等)
|
||||||
|
/// </summary>
|
||||||
|
public string FullContent { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志读取服务实现(简化版本)
|
||||||
|
/// </summary>
|
||||||
|
public class LogReaderService : ILogReaderService
|
||||||
|
{
|
||||||
|
private static readonly Regex LogPattern = new Regex(
|
||||||
|
@"^\[(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\]\s\[(\w+)\]\s(.+)$",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public async Task<List<LogEntry>> ReadLogsAsync(string? filePath = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
// 使用今天的日志文件
|
||||||
|
var logPath = Path.Combine(AppContext.BaseDirectory, "logs");
|
||||||
|
if (!Directory.Exists(logPath))
|
||||||
|
return new List<LogEntry>();
|
||||||
|
|
||||||
|
var todayLog = Path.Combine(logPath, $"log_{DateTime.Now:yyyyMMdd}.log");
|
||||||
|
if (!File.Exists(todayLog))
|
||||||
|
{
|
||||||
|
// 尝试找最新的日志文件
|
||||||
|
var files = Directory.GetFiles(logPath, "*.log");
|
||||||
|
if (files.Length == 0)
|
||||||
|
return new List<LogEntry>();
|
||||||
|
|
||||||
|
todayLog = files.OrderByDescending(f => File.GetLastWriteTime(f)).First();
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath = todayLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
return new List<LogEntry>();
|
||||||
|
|
||||||
|
var logs = new List<LogEntry>();
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(filePath);
|
||||||
|
string? line;
|
||||||
|
while ((line = reader.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
var match = LogPattern.Match(line);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
logs.Add(new LogEntry
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Parse(match.Groups[1].Value),
|
||||||
|
Level = match.Groups[2].Value,
|
||||||
|
Message = match.Groups[3].Value,
|
||||||
|
FullContent = line
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (logs.Count > 0)
|
||||||
|
{
|
||||||
|
// 多行日志(如堆栈跟踪)追加到上一条
|
||||||
|
logs[^1].FullContent += Environment.NewLine + line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return logs.OrderByDescending(l => l.Timestamp).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetLogFiles()
|
||||||
|
{
|
||||||
|
var logPath = Path.Combine(AppContext.BaseDirectory, "logs");
|
||||||
|
if (!Directory.Exists(logPath))
|
||||||
|
return new List<string>();
|
||||||
|
|
||||||
|
return Directory.GetFiles(logPath, "*.log")
|
||||||
|
.OrderByDescending(f => File.GetLastWriteTime(f))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearLogsAsync()
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var logPath = Path.Combine(AppContext.BaseDirectory, "logs");
|
||||||
|
if (Directory.Exists(logPath))
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(logPath, "*.log"))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
namespace Sharp.Wpf.DevTools.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统信息
|
||||||
|
/// </summary>
|
||||||
|
public class SystemInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 操作系统版本
|
||||||
|
/// </summary>
|
||||||
|
public string OSVersion { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// .NET 版本
|
||||||
|
/// </summary>
|
||||||
|
public string DotNetVersion { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算机名
|
||||||
|
/// </summary>
|
||||||
|
public string MachineName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CPU 核心数
|
||||||
|
/// </summary>
|
||||||
|
public int ProcessorCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总内存(MB)
|
||||||
|
/// </summary>
|
||||||
|
public long TotalMemoryMB { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 工作集内存(MB)
|
||||||
|
/// </summary>
|
||||||
|
public long WorkingSetMB { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 运行时长
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan Uptime { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Sharp.Wpf.Mvvm;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关于窗口 ViewModel
|
||||||
|
/// </summary>
|
||||||
|
public class AboutViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public AboutViewModel()
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
|
||||||
|
var assemblyName = assembly.GetName();
|
||||||
|
|
||||||
|
ApplicationName = assembly.GetCustomAttribute<AssemblyTitleAttribute>()?.Title ?? "Sharp Application";
|
||||||
|
Version = assemblyName.Version?.ToString() ?? "1.0.0";
|
||||||
|
Copyright = assembly.GetCustomAttribute<AssemblyCopyrightAttribute>()?.Copyright ?? "Copyright © 2025";
|
||||||
|
Description = assembly.GetCustomAttribute<AssemblyDescriptionAttribute>()?.Description ?? "";
|
||||||
|
|
||||||
|
FrameworkVersion = "Sharp.Basic v1.0.0";
|
||||||
|
DotNetVersion = $".NET {Environment.Version.Major}.{Environment.Version.Minor}";
|
||||||
|
OSVersion = Environment.OSVersion.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用程序名称
|
||||||
|
/// </summary>
|
||||||
|
public string ApplicationName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 版本号
|
||||||
|
/// </summary>
|
||||||
|
public string Version { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 版权信息
|
||||||
|
/// </summary>
|
||||||
|
public string Copyright { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 框架版本
|
||||||
|
/// </summary>
|
||||||
|
public string FrameworkVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// .NET 版本
|
||||||
|
/// </summary>
|
||||||
|
public string DotNetVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作系统版本
|
||||||
|
/// </summary>
|
||||||
|
public string OSVersion { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
using Sharp.Wpf.Commands;
|
||||||
|
using Sharp.Wpf.DevTools.Services;
|
||||||
|
using Sharp.Wpf.Mvvm;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开发者工具 ViewModel
|
||||||
|
/// </summary>
|
||||||
|
public class DevToolsViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IDevToolsService _devTools;
|
||||||
|
private SystemInfo? _systemInfo;
|
||||||
|
|
||||||
|
public DevToolsViewModel(IDevToolsService devTools)
|
||||||
|
{
|
||||||
|
_devTools = devTools;
|
||||||
|
|
||||||
|
OpenRuntimeDirectoryCommand = new RelayCommand(OpenRuntimeDirectory);
|
||||||
|
OpenLogDirectoryCommand = new RelayCommand(OpenLogDirectory);
|
||||||
|
OpenConfigFileCommand = new RelayCommand(OpenConfigFile);
|
||||||
|
ClearCacheCommand = new AsyncCommand(ClearCacheAsync);
|
||||||
|
ExportDiagnosticsCommand = new AsyncCommand(ExportDiagnosticsAsync);
|
||||||
|
RefreshSystemInfoCommand = new RelayCommand(RefreshSystemInfo);
|
||||||
|
|
||||||
|
RefreshSystemInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand OpenRuntimeDirectoryCommand { get; }
|
||||||
|
public ICommand OpenLogDirectoryCommand { get; }
|
||||||
|
public ICommand OpenConfigFileCommand { get; }
|
||||||
|
public IAsyncCommand ClearCacheCommand { get; }
|
||||||
|
public IAsyncCommand ExportDiagnosticsCommand { get; }
|
||||||
|
public ICommand RefreshSystemInfoCommand { get; }
|
||||||
|
|
||||||
|
public SystemInfo? SystemInfo
|
||||||
|
{
|
||||||
|
get => _systemInfo;
|
||||||
|
set => SetProperty(ref _systemInfo, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenRuntimeDirectory()
|
||||||
|
{
|
||||||
|
_devTools.OpenRuntimeDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenLogDirectory()
|
||||||
|
{
|
||||||
|
_devTools.OpenLogDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenConfigFile()
|
||||||
|
{
|
||||||
|
_devTools.OpenConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClearCacheAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _devTools.ClearCacheAsync();
|
||||||
|
// 可以显示成功提示
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// 可以显示错误提示
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExportDiagnosticsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _devTools.ExportDiagnosticsAsync();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// 可以显示错误提示
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshSystemInfo()
|
||||||
|
{
|
||||||
|
SystemInfo = _devTools.GetSystemInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Sharp.Wpf.Commands;
|
||||||
|
using Sharp.Wpf.DevTools.Services;
|
||||||
|
using Sharp.Wpf.Mvvm;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志查看器 ViewModel
|
||||||
|
/// </summary>
|
||||||
|
public class LogViewerViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ILogReaderService _logReader;
|
||||||
|
private string? _selectedLogFile;
|
||||||
|
private LogEntry? _selectedLog;
|
||||||
|
private string _searchText = string.Empty;
|
||||||
|
|
||||||
|
public LogViewerViewModel(ILogReaderService logReader)
|
||||||
|
{
|
||||||
|
_logReader = logReader;
|
||||||
|
|
||||||
|
LoadLogsCommand = new AsyncCommand(LoadLogsAsync);
|
||||||
|
RefreshCommand = new AsyncCommand(RefreshAsync);
|
||||||
|
ClearLogsCommand = new AsyncCommand(ClearLogsAsync);
|
||||||
|
|
||||||
|
LogFiles = new ObservableCollection<string>(_logReader.GetLogFiles());
|
||||||
|
SelectedLogFile = LogFiles.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncCommand LoadLogsCommand { get; }
|
||||||
|
public IAsyncCommand RefreshCommand { get; }
|
||||||
|
public IAsyncCommand ClearLogsCommand { get; }
|
||||||
|
|
||||||
|
public ObservableCollection<string> LogFiles { get; }
|
||||||
|
public ObservableCollection<LogEntry> Logs { get; } = new();
|
||||||
|
|
||||||
|
public string? SelectedLogFile
|
||||||
|
{
|
||||||
|
get => _selectedLogFile;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedLogFile, value))
|
||||||
|
{
|
||||||
|
_ = LoadLogsAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEntry? SelectedLog
|
||||||
|
{
|
||||||
|
get => _selectedLog;
|
||||||
|
set => SetProperty(ref _selectedLog, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SearchText
|
||||||
|
{
|
||||||
|
get => _searchText;
|
||||||
|
set => SetProperty(ref _searchText, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadLogsAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SelectedLogFile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logs = await _logReader.ReadLogsAsync(SelectedLogFile);
|
||||||
|
|
||||||
|
Logs.Clear();
|
||||||
|
foreach (var log in logs)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SearchText) ||
|
||||||
|
log.Message.Contains(SearchText, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Logs.Add(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshAsync()
|
||||||
|
{
|
||||||
|
LogFiles.Clear();
|
||||||
|
foreach (var file in _logReader.GetLogFiles())
|
||||||
|
{
|
||||||
|
LogFiles.Add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
await LoadLogsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClearLogsAsync()
|
||||||
|
{
|
||||||
|
await _logReader.ClearLogsAsync();
|
||||||
|
await RefreshAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using Sharp.Wpf.Commands;
|
||||||
|
using Sharp.Wpf.Mvvm;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 性能监控 ViewModel
|
||||||
|
/// </summary>
|
||||||
|
public class PerformanceMonitorViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly DispatcherTimer _timer;
|
||||||
|
private readonly Process _process;
|
||||||
|
private readonly DateTime _startTime;
|
||||||
|
|
||||||
|
private double _cpuUsage;
|
||||||
|
private long _workingSetMB;
|
||||||
|
private long _privateMB;
|
||||||
|
private int _threadCount;
|
||||||
|
private int _gen0Collections;
|
||||||
|
private int _gen1Collections;
|
||||||
|
private int _gen2Collections;
|
||||||
|
|
||||||
|
public PerformanceMonitorViewModel()
|
||||||
|
{
|
||||||
|
_process = Process.GetCurrentProcess();
|
||||||
|
_startTime = _process.StartTime;
|
||||||
|
|
||||||
|
_timer = new DispatcherTimer
|
||||||
|
{
|
||||||
|
Interval = TimeSpan.FromSeconds(1)
|
||||||
|
};
|
||||||
|
_timer.Tick += OnTimerTick;
|
||||||
|
|
||||||
|
StartMonitoringCommand = new RelayCommand(StartMonitoring);
|
||||||
|
StopMonitoringCommand = new RelayCommand(StopMonitoring);
|
||||||
|
ForceGCCommand = new RelayCommand(ForceGC);
|
||||||
|
|
||||||
|
UpdateMetrics();
|
||||||
|
StartMonitoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand StartMonitoringCommand { get; }
|
||||||
|
public ICommand StopMonitoringCommand { get; }
|
||||||
|
public ICommand ForceGCCommand { get; }
|
||||||
|
|
||||||
|
public double CpuUsage
|
||||||
|
{
|
||||||
|
get => _cpuUsage;
|
||||||
|
set => SetProperty(ref _cpuUsage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long WorkingSetMB
|
||||||
|
{
|
||||||
|
get => _workingSetMB;
|
||||||
|
set => SetProperty(ref _workingSetMB, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long PrivateMB
|
||||||
|
{
|
||||||
|
get => _privateMB;
|
||||||
|
set => SetProperty(ref _privateMB, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ThreadCount
|
||||||
|
{
|
||||||
|
get => _threadCount;
|
||||||
|
set => SetProperty(ref _threadCount, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Gen0Collections
|
||||||
|
{
|
||||||
|
get => _gen0Collections;
|
||||||
|
set => SetProperty(ref _gen0Collections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Gen1Collections
|
||||||
|
{
|
||||||
|
get => _gen1Collections;
|
||||||
|
set => SetProperty(ref _gen1Collections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Gen2Collections
|
||||||
|
{
|
||||||
|
get => _gen2Collections;
|
||||||
|
set => SetProperty(ref _gen2Collections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Uptime => DateTime.Now - _startTime;
|
||||||
|
|
||||||
|
private void OnTimerTick(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
UpdateMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMetrics()
|
||||||
|
{
|
||||||
|
_process.Refresh();
|
||||||
|
|
||||||
|
// CPU使用率(简化计算)
|
||||||
|
CpuUsage = _process.TotalProcessorTime.TotalMilliseconds / (DateTime.Now - _startTime).TotalMilliseconds * 100 / Environment.ProcessorCount;
|
||||||
|
|
||||||
|
// 内存
|
||||||
|
WorkingSetMB = _process.WorkingSet64 / 1024 / 1024;
|
||||||
|
PrivateMB = _process.PrivateMemorySize64 / 1024 / 1024;
|
||||||
|
|
||||||
|
// 线程
|
||||||
|
ThreadCount = _process.Threads.Count;
|
||||||
|
|
||||||
|
// GC
|
||||||
|
Gen0Collections = GC.CollectionCount(0);
|
||||||
|
Gen1Collections = GC.CollectionCount(1);
|
||||||
|
Gen2Collections = GC.CollectionCount(2);
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(Uptime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartMonitoring()
|
||||||
|
{
|
||||||
|
_timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopMonitoring()
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ForceGC()
|
||||||
|
{
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
GC.Collect();
|
||||||
|
UpdateMetrics();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<Window x:Class="Sharp.Wpf.DevTools.Views.AboutWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:conv="clr-namespace:Sharp.Wpf.Converters;assembly=Sharp.Wpf"
|
||||||
|
Title="关于" Height="450" Width="500"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
WindowStyle="SingleBorderWindow">
|
||||||
|
<Window.Resources>
|
||||||
|
<conv:NullOrEmptyToVisibilityConverter x:Key="NullOrEmptyToVis" Inverse="True"/>
|
||||||
|
</Window.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 头部 -->
|
||||||
|
<Border Grid.Row="0" Background="#FF0078D7" Padding="30,40">
|
||||||
|
<StackPanel HorizontalAlignment="Center">
|
||||||
|
<!-- 应用图标 -->
|
||||||
|
<TextBlock Text="📱" FontSize="48" HorizontalAlignment="Center" Margin="0,0,0,15"/>
|
||||||
|
|
||||||
|
<!-- 应用名称 -->
|
||||||
|
<TextBlock Text="{Binding ApplicationName}"
|
||||||
|
FontSize="24" FontWeight="Bold"
|
||||||
|
Foreground="White"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- 版本号 -->
|
||||||
|
<TextBlock FontSize="14" Foreground="White" HorizontalAlignment="Center" Margin="0,5,0,0">
|
||||||
|
<Run Text="版本 "/>
|
||||||
|
<Run Text="{Binding Version, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 信息区域 -->
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel Margin="30,20">
|
||||||
|
<!-- 版权信息 -->
|
||||||
|
<TextBlock Text="{Binding Copyright}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="13"
|
||||||
|
Foreground="#666"
|
||||||
|
Margin="0,0,0,20"/>
|
||||||
|
|
||||||
|
<!-- 描述 -->
|
||||||
|
<TextBlock Text="{Binding Description}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="13"
|
||||||
|
Margin="0,0,0,20"
|
||||||
|
Visibility="{Binding Description, Converter={StaticResource NullOrEmptyToVis}}"/>
|
||||||
|
|
||||||
|
<!-- 框架信息 -->
|
||||||
|
<Separator Margin="0,0,0,15"/>
|
||||||
|
|
||||||
|
<TextBlock FontSize="13" Margin="0,5">
|
||||||
|
<Run Text="构建于:" FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding FrameworkVersion, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<TextBlock FontSize="13" Margin="0,5">
|
||||||
|
<Run Text="运行环境:" FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding DotNetVersion, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<TextBlock FontSize="13" Margin="0,5">
|
||||||
|
<Run Text="操作系统:" FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding OSVersion, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<!-- 许可证 -->
|
||||||
|
<Separator Margin="0,15"/>
|
||||||
|
|
||||||
|
<TextBlock Text="开源许可证" FontSize="14" FontWeight="Bold" Margin="0,10,0,5"/>
|
||||||
|
<TextBlock Text="GNU AGPLv3" FontSize="12" Foreground="#666"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<Border Grid.Row="2" BorderBrush="#E0E0E0" BorderThickness="0,1,0,0" Padding="20">
|
||||||
|
<Button Content="确定" Width="100" Height="32"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
IsDefault="True"
|
||||||
|
Click="CloseButton_Click"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.Views;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AboutWindow.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class AboutWindow : Window
|
||||||
|
{
|
||||||
|
public AboutWindow(AboutViewModel viewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<Window x:Class="Sharp.Wpf.DevTools.Views.DevToolsWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="开发者工具" Height="550" Width="700"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
ResizeMode="CanResize">
|
||||||
|
<Grid Margin="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 快捷操作 -->
|
||||||
|
<GroupBox Header="快捷操作" Grid.Row="0" Margin="0,0,0,15">
|
||||||
|
<Grid Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 第一行 -->
|
||||||
|
<Button Grid.Row="0" Grid.Column="0"
|
||||||
|
Command="{Binding OpenRuntimeDirectoryCommand}"
|
||||||
|
Content="📁 打开运行目录"
|
||||||
|
Margin="5" Padding="10" Height="40"/>
|
||||||
|
<Button Grid.Row="0" Grid.Column="1"
|
||||||
|
Command="{Binding OpenLogDirectoryCommand}"
|
||||||
|
Content="📄 打开日志目录"
|
||||||
|
Margin="5" Padding="10" Height="40"/>
|
||||||
|
<Button Grid.Row="0" Grid.Column="2"
|
||||||
|
Command="{Binding OpenConfigFileCommand}"
|
||||||
|
Content="⚙️ 打开配置文件"
|
||||||
|
Margin="5" Padding="10" Height="40"/>
|
||||||
|
|
||||||
|
<!-- 第二行 -->
|
||||||
|
<Button Grid.Row="1" Grid.Column="0"
|
||||||
|
Command="{Binding ClearCacheCommand}"
|
||||||
|
Content="🗑️ 清除缓存"
|
||||||
|
Margin="5" Padding="10" Height="40"/>
|
||||||
|
<Button Grid.Row="1" Grid.Column="1"
|
||||||
|
Command="{Binding ExportDiagnosticsCommand}"
|
||||||
|
Content="📦 导出诊断信息"
|
||||||
|
Margin="5" Padding="10" Height="40"/>
|
||||||
|
<Button Grid.Row="1" Grid.Column="2"
|
||||||
|
Command="{Binding RefreshSystemInfoCommand}"
|
||||||
|
Content="🔄 刷新信息"
|
||||||
|
Margin="5" Padding="10" Height="40"/>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 系统信息 -->
|
||||||
|
<GroupBox Header="系统信息" Grid.Row="1" Margin="0,0,0,15">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel Margin="15" DataContext="{Binding SystemInfo}">
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="🖥️ 操作系统: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding OSVersion, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="⚙️ .NET版本: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding DotNetVersion, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="💻 计算机名: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding MachineName, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="👤 用户名: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding UserName, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="🔧 CPU核心数: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding ProcessorCount, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="💾 总内存: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding TotalMemoryMB, Mode=OneWay}"/>
|
||||||
|
<Run Text=" MB"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="📊 工作集内存: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding WorkingSetMB, Mode=OneWay}"/>
|
||||||
|
<Run Text=" MB"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="⏱️ 运行时长: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding Uptime, Mode=OneWay, StringFormat='{}{0:d\\.hh\\:mm\\:ss}'}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<Button Grid.Row="2" Content="关闭"
|
||||||
|
Width="100" Height="32"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Click="CloseButton_Click"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.Views;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DevToolsWindow.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class DevToolsWindow : Window
|
||||||
|
{
|
||||||
|
public DevToolsWindow(DevToolsViewModel viewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<Window x:Class="Sharp.Wpf.DevTools.Views.LogViewerWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="日志查看器" Height="600" Width="900"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<ToolBar Grid.Row="0" Padding="5">
|
||||||
|
<Label Content="日志文件:"/>
|
||||||
|
<ComboBox ItemsSource="{Binding LogFiles}"
|
||||||
|
SelectedItem="{Binding SelectedLogFile}"
|
||||||
|
Width="250" Margin="5,0"/>
|
||||||
|
|
||||||
|
<Separator/>
|
||||||
|
|
||||||
|
<Label Content="搜索:"/>
|
||||||
|
<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Width="150" Margin="5,0"/>
|
||||||
|
|
||||||
|
<Separator/>
|
||||||
|
|
||||||
|
<Button Content="🔄 刷新" Command="{Binding RefreshCommand}" Margin="5,0"/>
|
||||||
|
<Button Content="🗑️ 清除日志" Command="{Binding ClearLogsCommand}" Margin="5,0"/>
|
||||||
|
</ToolBar>
|
||||||
|
|
||||||
|
<!-- 日志列表 -->
|
||||||
|
<DataGrid Grid.Row="1"
|
||||||
|
ItemsSource="{Binding Logs}"
|
||||||
|
SelectedItem="{Binding SelectedLog}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal"
|
||||||
|
AlternatingRowBackground="#F5F5F5"
|
||||||
|
Margin="5">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="时间" Binding="{Binding Timestamp, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Width="150"/>
|
||||||
|
<DataGridTextColumn Header="级别" Binding="{Binding Level}" Width="80"/>
|
||||||
|
<DataGridTextColumn Header="消息" Binding="{Binding Message}" Width="*"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
|
<!-- 详情 -->
|
||||||
|
<GroupBox Grid.Row="2" Header="详细信息" Height="150" Margin="5">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBox Text="{Binding SelectedLog.FullContent, Mode=OneWay}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
IsReadOnly="True"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
FontSize="12"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 底部 -->
|
||||||
|
<Button Grid.Row="3" Content="关闭"
|
||||||
|
Width="100" Height="32"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="5"
|
||||||
|
Click="CloseButton_Click"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.Views;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogViewerWindow.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class LogViewerWindow : Window
|
||||||
|
{
|
||||||
|
public LogViewerWindow(LogViewerViewModel viewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<Window x:Class="Sharp.Wpf.DevTools.Views.PerformanceMonitorWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="性能监控" Height="500" Width="600"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
<Grid Margin="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,15">
|
||||||
|
<Button Content="▶ 开始监控" Command="{Binding StartMonitoringCommand}" Margin="0,0,10,0" Padding="10,5"/>
|
||||||
|
<Button Content="⏸ 停止监控" Command="{Binding StopMonitoringCommand}" Margin="0,0,10,0" Padding="10,5"/>
|
||||||
|
<Button Content="🗑️ 强制GC" Command="{Binding ForceGCCommand}" Padding="10,5"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 指标显示 -->
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel>
|
||||||
|
<!-- CPU -->
|
||||||
|
<GroupBox Header="CPU 使用率" Margin="0,0,0,15">
|
||||||
|
<StackPanel Margin="10">
|
||||||
|
<TextBlock FontSize="32" FontWeight="Bold">
|
||||||
|
<Run Text="{Binding CpuUsage, StringFormat='{}{0:F1}', Mode=OneWay}"/>
|
||||||
|
<Run Text="%" FontSize="20"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 内存 -->
|
||||||
|
<GroupBox Header="内存使用" Margin="0,0,0,15">
|
||||||
|
<Grid Margin="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="工作集内存" FontSize="12" Foreground="#666"/>
|
||||||
|
<TextBlock FontSize="24" FontWeight="Bold">
|
||||||
|
<Run Text="{Binding WorkingSetMB, Mode=OneWay}"/>
|
||||||
|
<Run Text="MB" FontSize="14"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1">
|
||||||
|
<TextBlock Text="私有内存" FontSize="12" Foreground="#666"/>
|
||||||
|
<TextBlock FontSize="24" FontWeight="Bold">
|
||||||
|
<Run Text="{Binding PrivateMB, Mode=OneWay}"/>
|
||||||
|
<Run Text="MB" FontSize="14"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- GC统计 -->
|
||||||
|
<GroupBox Header="GC 统计" Margin="0,0,0,15">
|
||||||
|
<Grid Margin="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="0">
|
||||||
|
<TextBlock Text="Gen 0" FontSize="12" Foreground="#666"/>
|
||||||
|
<TextBlock Text="{Binding Gen0Collections, Mode=OneWay}" FontSize="20" FontWeight="Bold"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1">
|
||||||
|
<TextBlock Text="Gen 1" FontSize="12" Foreground="#666"/>
|
||||||
|
<TextBlock Text="{Binding Gen1Collections, Mode=OneWay}" FontSize="20" FontWeight="Bold"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="2">
|
||||||
|
<TextBlock Text="Gen 2" FontSize="12" Foreground="#666"/>
|
||||||
|
<TextBlock Text="{Binding Gen2Collections, Mode=OneWay}" FontSize="20" FontWeight="Bold"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 其他 -->
|
||||||
|
<GroupBox Header="其他信息">
|
||||||
|
<StackPanel Margin="10">
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="线程数: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding ThreadCount, Mode=OneWay}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Margin="0,5">
|
||||||
|
<Run Text="运行时长: " FontWeight="Bold"/>
|
||||||
|
<Run Text="{Binding Uptime, Mode=OneWay, StringFormat='{}{0:d\\.hh\\:mm\\:ss}'}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- 说明 -->
|
||||||
|
<TextBlock Grid.Row="2" Text="提示:数据每秒自动更新"
|
||||||
|
FontSize="11" Foreground="#666"
|
||||||
|
Margin="0,10,0,10"/>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<Button Grid.Row="3" Content="关闭"
|
||||||
|
Width="100" Height="32"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Click="CloseButton_Click"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Sharp.Wpf.DevTools.ViewModels;
|
||||||
|
|
||||||
|
namespace Sharp.Wpf.DevTools.Views;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PerformanceMonitorWindow.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class PerformanceMonitorWindow : Window
|
||||||
|
{
|
||||||
|
public PerformanceMonitorWindow(PerformanceMonitorViewModel viewModel)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<RootNamespace>Sharp.Wpf.DevTools</RootNamespace>
|
||||||
|
<AssemblyName>Sharp.Wpf.DevTools</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Wpf\Wpf.csproj" />
|
||||||
|
<ProjectReference Include="..\Config\Config.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in new issue
Block a user