Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05e5ceeb43 | |||
| fcbbad71d2 | |||
| 6f9161439f | |||
| aa0c4fb841 | |||
| cff50d1f81 | |||
| b8581b6368 | |||
| 094f1223d1 | |||
| 6802476afa | |||
| a0539dce9b | |||
| bf2b8fec35 | |||
| 082c9a03ec | |||
| 4ccdd862ba | |||
| e5a20ed0fc | |||
| a72022704e | |||
| 2acc7ada30 | |||
| e61882c331 | |||
| 61c145689a | |||
| 40ea9664a7 | |||
| bccd2d0f3e | |||
| b918809dca | |||
| e7c2e92879 | |||
| cf03c921a7 | |||
| 2c45c839b1 | |||
| 83f5fc58d1 | |||
| 1a267f1e5a | |||
| c3fd5551d8 | |||
| 1baa74bb69 | |||
| 261ecefb17 | |||
| d81d8f7c5d | |||
| de1af12157 | |||
| 803cbbdee9 | |||
| 008477d5fa | |||
| 7f0d29ebd2 | |||
| 11bf8cffb2 | |||
| 87b9ebc7e1 | |||
| b89d27411b | |||
| 24c37f1d3e | |||
| 58b0a0a3be | |||
| ed58873a82 | |||
| a8dcbd4af0 | |||
| 4b2f29442a | |||
| dfab0d7ddf | |||
| ce1998b701 | |||
| 92c631d6ce | |||
| 01009f9e35 | |||
| 74eca093da | |||
| e7d89e65b2 | |||
| 24b2bffe8e | |||
| d2906476c8 | |||
| 2b31a355ae | |||
| b602048186 | |||
| 4fb7031060 | |||
| 4ef77c2e72 | |||
| 72ba1a9f58 | |||
| 0c3938b652 | |||
| 16f80adb0d | |||
| 637b6bb4f9 | |||
| 12e91927a5 | |||
| 8f6f22ba7f | |||
| b520d6a334 | |||
| b7bff30445 | |||
| 0d790bbd80 | |||
| 8394e99127 | |||
| 16ae32bfd7 | |||
| cc6423e384 | |||
| 06cc587599 | |||
| 9d36088f1d | |||
| f9f73b015c | |||
| 77ffd696bb | |||
| bdb8bed053 | |||
| 4b17c8e96e | |||
| 8109711f4e | |||
| 327eba3fa7 | |||
| f3dccb2e99 | |||
| 7112d58e7c | |||
| 6e0aad853c | |||
| c64b1d0846 | |||
| 6eba16ce99 | |||
| 2f6f719843 | |||
| f34bac49e4 | |||
| 39cdc6231f | |||
| 745e24da70 | |||
| a4f4f4fb15 | |||
| 3833c229c6 | |||
| 3dc3e9b5a8 | |||
| 12eeb79e9f | |||
| e08c84f70d | |||
| 10f55d5b65 | |||
| 761992d089 | |||
| 991a823700 | |||
| 61dbcf762c | |||
| 60b0149a9c | |||
| 501c034cfa |
@@ -1 +1 @@
|
||||
1.7.15.0
|
||||
1.7.18.0
|
||||
|
||||
+69
-9
@@ -31,7 +31,7 @@ namespace Ink_Canvas
|
||||
Mutex mutex;
|
||||
|
||||
public static string[] StartArgs;
|
||||
public static string RootPath = Environment.GetEnvironmentVariable("APPDATA") + "\\Ink Canvas\\";
|
||||
public static string RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
||||
|
||||
// 新增:标记是否通过--board参数启动
|
||||
public static bool StartWithBoardMode = false;
|
||||
@@ -46,7 +46,7 @@ namespace Ink_Canvas
|
||||
// 新增:退出信号文件路径
|
||||
private static string watchdogExitSignalFile = Path.Combine(Path.GetTempPath(), "icc_watchdog_exit_" + Process.GetCurrentProcess().Id + ".flag");
|
||||
// 新增:崩溃日志文件路径
|
||||
private static string crashLogFile = Path.Combine(Environment.GetEnvironmentVariable("APPDATA"), "Ink Canvas", "crash_logs");
|
||||
private static string crashLogFile = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Crashes");
|
||||
// 新增:进程ID
|
||||
private static int currentProcessId = Process.GetCurrentProcess().Id;
|
||||
// 新增:应用启动时间
|
||||
@@ -107,7 +107,6 @@ namespace Ink_Canvas
|
||||
|
||||
if (isWindows7)
|
||||
{
|
||||
LogHelper.WriteLogToFile("检测到Windows 7系统,配置TLS协议支持");
|
||||
|
||||
// 启用所有TLS版本以支持Windows 7
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
|
||||
@@ -117,17 +116,14 @@ namespace Ink_Canvas
|
||||
ServicePointManager.Expect100Continue = false;
|
||||
ServicePointManager.UseNagleAlgorithm = false;
|
||||
|
||||
LogHelper.WriteLogToFile("TLS协议配置完成,已启用TLS 1.2/1.1/1.0支持");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 对于更新的Windows版本,不进行任何TLS配置,使用系统默认设置
|
||||
LogHelper.WriteLogToFile($"检测到Windows版本: {osVersion.VersionString},使用系统默认TLS配置");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"配置TLS协议时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +342,25 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
var exception = e.ExceptionObject as Exception;
|
||||
|
||||
if (exception is InvalidOperationException invalidOpEx)
|
||||
{
|
||||
string exceptionMessage = invalidOpEx.Message ?? "";
|
||||
string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
|
||||
|
||||
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
||||
exceptionMessage.Contains("because another thread owns it") ||
|
||||
exceptionStackTrace.Contains("DynamicRenderer") ||
|
||||
exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
$"检测到DynamicRenderer线程访问异常: {invalidOpEx.Message}",
|
||||
LogHelper.LogType.Warning
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string errorMessage = exception?.ToString() ?? "未知异常";
|
||||
lastErrorMessage = errorMessage;
|
||||
|
||||
@@ -361,8 +376,11 @@ namespace Ink_Canvas
|
||||
// 尝试在最后时刻记录错误
|
||||
try
|
||||
{
|
||||
string timeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
||||
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
||||
: DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
File.AppendAllText(
|
||||
Path.Combine(crashLogFile, $"critical_error_{DateTime.Now:yyyyMMdd_HHmmss}.log"),
|
||||
Path.Combine(crashLogFile, $"Crash_{timeStr}.txt"),
|
||||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 记录未处理异常时发生错误: {ex.Message}\r\n"
|
||||
);
|
||||
}
|
||||
@@ -500,7 +518,10 @@ namespace Ink_Canvas
|
||||
Directory.CreateDirectory(crashLogFile);
|
||||
}
|
||||
|
||||
string logFileName = Path.Combine(crashLogFile, $"crash_{DateTime.Now:yyyyMMdd}.log");
|
||||
string appStartTimeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
||||
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
||||
: DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
string logFileName = Path.Combine(crashLogFile, $"Crash_{appStartTimeStr}.txt");
|
||||
|
||||
// 收集系统状态信息
|
||||
string memoryUsage = (Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024)) + " MB";
|
||||
@@ -549,6 +570,31 @@ namespace Ink_Canvas
|
||||
|
||||
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
// 检查是否是DynamicRenderer线程访问UI对象的已知问题
|
||||
if (e.Exception is InvalidOperationException invalidOpEx)
|
||||
{
|
||||
string exceptionMessage = invalidOpEx.Message ?? "";
|
||||
string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
|
||||
|
||||
// 检查是否是DynamicRenderer相关的线程访问问题
|
||||
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
||||
exceptionMessage.Contains("because another thread owns it") ||
|
||||
exceptionStackTrace.Contains("DynamicRenderer") ||
|
||||
exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
|
||||
{
|
||||
// 这是WPF InkCanvas的已知问题,DynamicRenderer的后台线程尝试访问UI对象
|
||||
// 这个异常不会影响应用程序功能,可以安全地忽略
|
||||
LogHelper.WriteLogToFile(
|
||||
$"检测到DynamicRenderer线程访问异常(已安全处理): {invalidOpEx.Message}",
|
||||
LogHelper.LogType.Warning
|
||||
);
|
||||
|
||||
// 标记为已处理,不显示错误消息,不触发重启
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 InkCanvasForClass 运行不稳定。\n建议保存墨迹后重启应用。");
|
||||
LogHelper.NewLog(e.Exception.ToString());
|
||||
|
||||
@@ -706,6 +752,20 @@ namespace Ink_Canvas
|
||||
{
|
||||
LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
LogHelper.WriteLogToFile("App | 最终应用启动,删除AutoUpdate文件夹");
|
||||
AutoUpdateHelper.DeleteUpdatesFolder();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"App | 删除AutoUpdate文件夹失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果不是最终应用启动,才检查更新标记文件
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.15.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.15.0")]
|
||||
[assembly: AssemblyVersion("1.7.18.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.0")]
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<UserControl x:Class="Ink_Canvas.Controls.QuickDrawFloatingButtonControl"
|
||||
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"
|
||||
Width="65" Height="45">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<!-- 悬浮按钮资源 -->
|
||||
<SolidColorBrush x:Key="QuickDrawFloatingButtonBackground" Color="#80000000"/>
|
||||
<SolidColorBrush x:Key="QuickDrawFloatingButtonBorderBrush" Color="#40000000"/>
|
||||
<SolidColorBrush x:Key="QuickDrawFloatingButtonIconForeground" Color="White"/>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border Background="{DynamicResource QuickDrawFloatingButtonBackground}"
|
||||
CornerRadius="8"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource QuickDrawFloatingButtonBorderBrush}">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="Black" Direction="315" ShadowDepth="3" Opacity="0.3" BlurRadius="5"/>
|
||||
</Border.Effect>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="22"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 拖动区域 -->
|
||||
<Border Grid.Column="0"
|
||||
MouseLeftButtonDown="DragArea_MouseLeftButtonDown"
|
||||
MouseMove="DragArea_MouseMove"
|
||||
MouseLeftButtonUp="DragArea_MouseLeftButtonUp"
|
||||
Cursor="SizeAll"
|
||||
Background="Transparent">
|
||||
<Grid VerticalAlignment="Center" Height="14" IsHitTestVisible="False">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 三个白色横线 -->
|
||||
<Border Grid.Row="0" Background="{DynamicResource QuickDrawFloatingButtonIconForeground}" Height="2" Width="10"
|
||||
HorizontalAlignment="Center"
|
||||
CornerRadius="1" Opacity="0.8" IsHitTestVisible="False"/>
|
||||
<Border Grid.Row="2" Background="{DynamicResource QuickDrawFloatingButtonIconForeground}" Height="2" Width="10"
|
||||
HorizontalAlignment="Center"
|
||||
CornerRadius="1" Opacity="0.8" IsHitTestVisible="False"/>
|
||||
<Border Grid.Row="4" Background="{DynamicResource QuickDrawFloatingButtonIconForeground}" Height="2" Width="10"
|
||||
HorizontalAlignment="Center"
|
||||
CornerRadius="1" Opacity="0.8" IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 半透明分割线 -->
|
||||
<Rectangle Grid.Column="1" Width="1" Fill="#20FFFFFF" Margin="0,8,0,8"/>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<Border Grid.Column="2"
|
||||
MouseLeftButtonDown="FloatingButton_Click"
|
||||
Cursor="Hand"
|
||||
Background="Transparent">
|
||||
<Grid IsHitTestVisible="False">
|
||||
<Path Data="M5 7C5 8.06087 5.42143 9.07828 6.17157 9.82843C6.92172 10.5786 7.93913 11 9 11C10.0609 11 11.0783 10.5786 11.8284 9.82843C12.5786 9.07828 13 8.06087 13 7C13 5.93913 12.5786 4.92172 11.8284 4.17157C11.0783 3.42143 10.0609 3 9 3C7.93913 3 6.92172 3.42143 6.17157 4.17157C5.42143 4.92172 5 5.93913 5 7Z M3 21V19C3 17.9391 3.42143 16.9217 4.17157 16.1716C4.92172 15.4214 5.93913 15 7 15H11C12.0609 15 13.0783 15.4214 13.8284 16.1716C14.5786 16.9217 15 17.9391 15 19V21 M16 3.13C16.8604 3.35031 17.623 3.85071 18.1676 4.55232C18.7122 5.25392 19.0078 6.11683 19.0078 7.005C19.0078 7.89318 18.7122 8.75608 18.1676 9.45769C17.623 10.1593 16.8604 10.6597 16 10.88 M21 21V19C20.9949 18.1172 20.6979 17.2608 20.1553 16.5644C19.6126 15.868 18.8548 15.3707 18 15.15"
|
||||
Stroke="{DynamicResource QuickDrawFloatingButtonIconForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using Ink_Canvas.Windows;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using VerticalAlignment = System.Windows.VerticalAlignment;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 快抽悬浮按钮控件
|
||||
/// </summary>
|
||||
public partial class QuickDrawFloatingButtonControl : UserControl
|
||||
{
|
||||
private bool _isDragging = false;
|
||||
private Point _dragStartPoint;
|
||||
private Point _controlStartPoint;
|
||||
|
||||
public QuickDrawFloatingButtonControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快抽按钮点击事件
|
||||
/// </summary>
|
||||
private void FloatingButton_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果正在拖动,不触发点击事件
|
||||
if (_isDragging) return;
|
||||
|
||||
// 打开快抽窗口
|
||||
var quickDrawWindow = new QuickDrawWindow();
|
||||
quickDrawWindow.ShowDialog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Helpers.LogHelper.WriteLogToFile($"打开快抽窗口失败: {ex.Message}", Helpers.LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动区域鼠标按下事件
|
||||
/// </summary>
|
||||
private void DragArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_isDragging = false;
|
||||
|
||||
// 记录鼠标在屏幕上的初始位置
|
||||
_dragStartPoint = this.PointToScreen(e.GetPosition(this));
|
||||
|
||||
// 记录控件的初始位置
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
{
|
||||
var transform = this.TransformToVisual(parent);
|
||||
var currentPos = transform.Transform(new Point(0, 0));
|
||||
_controlStartPoint = currentPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentMargin = this.Margin;
|
||||
_controlStartPoint = new Point(
|
||||
double.IsNaN(currentMargin.Left) ? 0 : currentMargin.Left,
|
||||
double.IsNaN(currentMargin.Top) ? 0 : currentMargin.Top);
|
||||
}
|
||||
|
||||
((UIElement)sender).CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动区域鼠标移动事件
|
||||
/// </summary>
|
||||
private void DragArea_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed && ((UIElement)sender).IsMouseCaptured)
|
||||
{
|
||||
// 获取鼠标在屏幕上的当前位置
|
||||
Point currentScreenPoint = this.PointToScreen(e.GetPosition(this));
|
||||
Vector diff = currentScreenPoint - _dragStartPoint;
|
||||
|
||||
if (!_isDragging && (Math.Abs(diff.X) > 3 || Math.Abs(diff.Y) > 3))
|
||||
{
|
||||
_isDragging = true;
|
||||
// 切换到绝对定位模式
|
||||
this.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
this.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
// 计算新位置
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
{
|
||||
// 计算屏幕坐标相对于父容器的位置
|
||||
var parentPoint = parent.PointFromScreen(currentScreenPoint);
|
||||
var startParentPoint = parent.PointFromScreen(_dragStartPoint);
|
||||
|
||||
// 计算相对于初始位置的偏移
|
||||
double offsetX = parentPoint.X - startParentPoint.X;
|
||||
double offsetY = parentPoint.Y - startParentPoint.Y;
|
||||
|
||||
// 新位置 = 初始位置 + 偏移
|
||||
double newLeft = _controlStartPoint.X + offsetX;
|
||||
double newTop = _controlStartPoint.Y + offsetY;
|
||||
|
||||
// 限制在父容器范围内
|
||||
newLeft = Math.Max(0, Math.Min(newLeft, parent.ActualWidth - this.ActualWidth));
|
||||
newTop = Math.Max(0, Math.Min(newTop, parent.ActualHeight - this.ActualHeight));
|
||||
|
||||
// 更新Margin
|
||||
this.Margin = new Thickness(newLeft, newTop, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动区域鼠标释放事件
|
||||
/// </summary>
|
||||
private void DragArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (((UIElement)sender).IsMouseCaptured)
|
||||
{
|
||||
((UIElement)sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => { _isDragging = false; }),
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,8 +281,16 @@ namespace Ink_Canvas.Helpers
|
||||
// 应用旋转
|
||||
Bitmap rotatedFrame = ApplyRotation(sourceFrame);
|
||||
|
||||
// 应用分辨率调整
|
||||
_currentFrame = ResizeImage(rotatedFrame, _resolutionWidth, _resolutionHeight);
|
||||
int targetWidth = _resolutionWidth;
|
||||
int targetHeight = _resolutionHeight;
|
||||
|
||||
if (_rotationAngle == 1 || _rotationAngle == 3)
|
||||
{
|
||||
targetWidth = _resolutionHeight;
|
||||
targetHeight = _resolutionWidth;
|
||||
}
|
||||
|
||||
_currentFrame = ResizeImageWithAspectRatio(rotatedFrame, targetWidth, targetHeight);
|
||||
|
||||
rotatedFrame?.Dispose();
|
||||
}
|
||||
@@ -357,6 +365,33 @@ namespace Ink_Canvas.Helpers
|
||||
return rotated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整图像大小
|
||||
/// </summary>
|
||||
private Bitmap ResizeImageWithAspectRatio(Bitmap source, int targetWidth, int targetHeight)
|
||||
{
|
||||
if (source.Width == targetWidth && source.Height == targetHeight)
|
||||
return new Bitmap(source);
|
||||
|
||||
double scaleX = (double)targetWidth / source.Width;
|
||||
double scaleY = (double)targetHeight / source.Height;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
|
||||
// 计算实际尺寸
|
||||
int actualWidth = (int)(source.Width * scale);
|
||||
int actualHeight = (int)(source.Height * scale);
|
||||
|
||||
var resized = new Bitmap(actualWidth, actualHeight, PixelFormat.Format24bppRgb);
|
||||
using (var graphics = Graphics.FromImage(resized))
|
||||
{
|
||||
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
|
||||
graphics.DrawImage(source, 0, 0, actualWidth, actualHeight);
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整图像大小
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlass API 客户端,用于与服务端通信
|
||||
/// </summary>
|
||||
public class DlassApiClient : IDisposable
|
||||
{
|
||||
private const string DEFAULT_BASE_URL = "https://dlass.tech";
|
||||
private readonly string _appId;
|
||||
private readonly string _appSecret;
|
||||
private readonly string _baseUrl;
|
||||
private HttpClient _httpClient;
|
||||
private string _accessToken;
|
||||
private DateTime _tokenExpiresAt;
|
||||
|
||||
private string _userToken;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 Dlass API 客户端
|
||||
/// </summary>
|
||||
/// <param name="appId">应用ID</param>
|
||||
/// <param name="appSecret">应用密钥</param>
|
||||
/// <param name="baseUrl">API基础URL,如果为空则使用默认URL</param>
|
||||
/// <param name="userToken">用户Token,如果提供则优先使用用户token而不是App Secret</param>
|
||||
public DlassApiClient(string appId, string appSecret, string baseUrl = null, string userToken = null)
|
||||
{
|
||||
_appId = appId ?? throw new ArgumentNullException(nameof(appId));
|
||||
_appSecret = appSecret ?? throw new ArgumentNullException(nameof(appSecret));
|
||||
_userToken = userToken;
|
||||
_baseUrl = baseUrl ?? DEFAULT_BASE_URL;
|
||||
|
||||
_baseUrl = _baseUrl.TrimEnd('/');
|
||||
if (!_baseUrl.StartsWith("http://") && !_baseUrl.StartsWith("https://"))
|
||||
{
|
||||
_baseUrl = "https://" + _baseUrl;
|
||||
}
|
||||
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent", "InkCanvas/1.0");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取访问令牌(Access Token)
|
||||
/// </summary>
|
||||
public async Task<string> GetAccessTokenAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
return _userToken;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_accessToken) && DateTime.Now < _tokenExpiresAt.AddMinutes(-5))
|
||||
{
|
||||
return _accessToken;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var requestData = new
|
||||
{
|
||||
app_id = _appId,
|
||||
app_secret = _appSecret,
|
||||
grant_type = "client_credentials"
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(requestData);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/oauth/token", content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
|
||||
_accessToken = tokenResponse.AccessToken;
|
||||
_tokenExpiresAt = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn ?? 3600);
|
||||
return _accessToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"获取Access Token失败: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"获取Access Token时网络错误: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception("获取Access Token时请求超时", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"获取Access Token时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送GET请求
|
||||
/// </summary>
|
||||
public async Task<T> GetAsync<T>(string endpoint, bool requireAuth = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"请求超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送POST请求
|
||||
/// </summary>
|
||||
public async Task<T> PostAsync<T>(string endpoint, object data = null, bool requireAuth = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(data);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"请求超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送PUT请求
|
||||
/// </summary>
|
||||
public async Task<T> PutAsync<T>(string endpoint, object data = null, bool requireAuth = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
// 如果是用户token,使用X-User-Token header
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(data);
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"请求超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送DELETE请求
|
||||
/// </summary>
|
||||
public async Task<bool> DeleteAsync(string endpoint, bool requireAuth = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Delete, endpoint);
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
// 如果是用户token,使用X-User-Token header
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
request.Headers.Add("X-User-Token", token);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传笔记文件
|
||||
/// </summary>
|
||||
/// <param name="endpoint">上传端点</param>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="boardId">白板ID</param>
|
||||
/// <param name="secretKey">白板密钥</param>
|
||||
/// <param name="title">笔记标题(可选)</param>
|
||||
/// <param name="description">笔记描述(可选)</param>
|
||||
/// <param name="tags">笔记标签(可选)</param>
|
||||
public async Task<T> UploadNoteAsync<T>(string endpoint, string filePath, string boardId, string secretKey, string title = null, string description = null, string tags = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"文件不存在: {filePath}");
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||
|
||||
// 设置白板认证头
|
||||
request.Headers.Add("X-Board-ID", boardId);
|
||||
request.Headers.Add("X-Secret-Key", secretKey);
|
||||
|
||||
// 创建multipart/form-data内容
|
||||
var content = new MultipartFormDataContent();
|
||||
|
||||
// 添加文件
|
||||
var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
content.Add(fileContent, "file", fileName);
|
||||
|
||||
// 添加可选参数
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
content.Add(new StringContent(title), "title");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
content.Add(new StringContent(description), "description");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(tags))
|
||||
{
|
||||
content.Add(new StringContent(tags), "tags");
|
||||
}
|
||||
|
||||
request.Content = content;
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return JsonConvert.DeserializeObject<T>(responseContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"上传文件失败: {response.StatusCode} - {responseContent}");
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"上传文件时网络错误: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"上传文件超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"上传文件时出错: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_httpClient?.Dispose();
|
||||
}
|
||||
|
||||
#region 内部类
|
||||
|
||||
/// <summary>
|
||||
/// Token响应模型
|
||||
/// </summary>
|
||||
private class TokenResponse
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[JsonProperty("expires_in")]
|
||||
public int? ExpiresIn { get; set; }
|
||||
|
||||
[JsonProperty("token_type")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,758 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlass笔记自动上传辅助类
|
||||
/// </summary>
|
||||
public class DlassNoteUploader
|
||||
{
|
||||
private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA";
|
||||
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
|
||||
private const int BATCH_SIZE = 10; // 批量上传大小
|
||||
private const int MAX_RETRY_COUNT = 3; // 最大重试次数
|
||||
private const string QUEUE_FILE_NAME = "DlassUploadQueue.json";
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列项
|
||||
/// </summary>
|
||||
private class UploadQueueItemData
|
||||
{
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("retry_count")]
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
[JsonProperty("added_time")]
|
||||
public DateTime AddedTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列项
|
||||
/// </summary>
|
||||
private class UploadQueueItem
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public int RetryCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列
|
||||
/// </summary>
|
||||
private static readonly ConcurrentQueue<UploadQueueItem> _uploadQueue = new ConcurrentQueue<UploadQueueItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 队列处理锁,防止并发处理
|
||||
/// </summary>
|
||||
private static readonly SemaphoreSlim _queueProcessingLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 队列保存锁,防止并发保存
|
||||
/// </summary>
|
||||
private static readonly SemaphoreSlim _queueSaveLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 是否已初始化队列
|
||||
/// </summary>
|
||||
private static bool _isQueueInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取队列文件路径
|
||||
/// </summary>
|
||||
private static string GetQueueFilePath()
|
||||
{
|
||||
var configsDir = Path.Combine(App.RootPath, "Configs");
|
||||
if (!Directory.Exists(configsDir))
|
||||
{
|
||||
Directory.CreateDirectory(configsDir);
|
||||
}
|
||||
return Path.Combine(configsDir, QUEUE_FILE_NAME);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化上传队列
|
||||
/// </summary>
|
||||
public static void InitializeQueue()
|
||||
{
|
||||
if (_isQueueInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
if (!File.Exists(queueFilePath))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = File.ReadAllText(queueFilePath);
|
||||
if (string.IsNullOrWhiteSpace(jsonContent))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var queueData = JsonConvert.DeserializeObject<List<UploadQueueItemData>>(jsonContent);
|
||||
if (queueData == null || queueData.Count == 0)
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int restoredCount = 0;
|
||||
int skippedCount = 0;
|
||||
|
||||
foreach (var item in queueData)
|
||||
{
|
||||
// 验证文件是否存在
|
||||
if (!File.Exists(item.FilePath))
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 验证文件格式和大小
|
||||
var fileExtension = Path.GetExtension(item.FilePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(item.FilePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 恢复队列项
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount
|
||||
});
|
||||
restoredCount++;
|
||||
}
|
||||
|
||||
_isQueueInitialized = true;
|
||||
|
||||
if (restoredCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"已恢复上传队列:{restoredCount}个文件,跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
// 如果恢复了队列,触发处理
|
||||
_ = ProcessUploadQueueAsync();
|
||||
}
|
||||
else if (skippedCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"队列恢复完成:跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"恢复上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
_isQueueInitialized = true; // 即使出错也标记为已初始化,避免重复尝试
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存队列到文件
|
||||
/// </summary>
|
||||
private static async Task SaveQueueToFileAsync()
|
||||
{
|
||||
if (!await _queueSaveLock.WaitAsync(1000)) // 最多等待1秒
|
||||
{
|
||||
return; // 如果无法获取锁,跳过保存(避免阻塞)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var queueData = new List<UploadQueueItemData>();
|
||||
|
||||
// 将队列转换为可序列化的格式
|
||||
foreach (var item in _uploadQueue)
|
||||
{
|
||||
queueData.Add(new UploadQueueItemData
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount,
|
||||
AddedTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
|
||||
// 如果队列为空,清空文件
|
||||
if (queueData.Count == 0)
|
||||
{
|
||||
ClearQueueFile();
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = JsonConvert.SerializeObject(queueData, Formatting.Indented);
|
||||
|
||||
// 使用临时文件写入,然后替换,确保原子性
|
||||
var tempFilePath = queueFilePath + ".tmp";
|
||||
File.WriteAllText(tempFilePath, jsonContent);
|
||||
|
||||
// 如果原文件存在,先删除
|
||||
if (File.Exists(queueFilePath))
|
||||
{
|
||||
File.Delete(queueFilePath);
|
||||
}
|
||||
|
||||
// 重命名临时文件
|
||||
File.Move(tempFilePath, queueFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueSaveLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空队列文件
|
||||
/// </summary>
|
||||
private static void ClearQueueFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
if (File.Exists(queueFilePath))
|
||||
{
|
||||
File.WriteAllText(queueFilePath, "[]");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清空队列文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传笔记响应模型
|
||||
/// </summary>
|
||||
public class UploadNoteResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
[JsonProperty("note_id")]
|
||||
public int? NoteId { get; set; }
|
||||
|
||||
[JsonProperty("filename")]
|
||||
public string Filename { get; set; }
|
||||
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 白板信息模型(用于查找白板)
|
||||
/// </summary>
|
||||
private class WhiteboardInfo
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("board_id")]
|
||||
public string BoardId { get; set; }
|
||||
|
||||
[JsonProperty("secret_key")]
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
[JsonProperty("class_name")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
[JsonProperty("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 认证响应模型
|
||||
/// </summary>
|
||||
private class AuthWithTokenResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("whiteboards")]
|
||||
public List<WhiteboardInfo> Whiteboards { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步上传笔记文件到Dlass(支持PNG、ICSTK和ZIP格式)
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径(支持PNG、ICSTK和ZIP)</param>
|
||||
/// <returns>是否成功加入队列(不等待实际上传完成)</returns>
|
||||
public static async Task<bool> UploadNoteFileAsync(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否启用自动上传
|
||||
if (MainWindow.Settings?.Dlass?.IsAutoUploadNotes != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 基本验证
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取上传延迟时间(分钟)
|
||||
var delayMinutes = MainWindow.Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
|
||||
// 如果设置了延迟时间,在后台任务中等待后再加入队列
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
EnqueueFile(filePath);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EnqueueFile(filePath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件加入上传队列
|
||||
/// </summary>
|
||||
private static void EnqueueFile(string filePath, int retryCount = 0)
|
||||
{
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = filePath,
|
||||
RetryCount = retryCount
|
||||
});
|
||||
|
||||
// 异步保存队列到文件
|
||||
_ = Task.Run(async () => await SaveQueueToFileAsync());
|
||||
|
||||
// 如果队列达到批量大小,触发批量上传
|
||||
if (_uploadQueue.Count >= BATCH_SIZE)
|
||||
{
|
||||
_ = ProcessUploadQueueAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理上传队列,批量上传文件
|
||||
/// </summary>
|
||||
private static async Task ProcessUploadQueueAsync()
|
||||
{
|
||||
// 使用信号量防止并发处理
|
||||
if (!await _queueProcessingLock.WaitAsync(0))
|
||||
{
|
||||
return; // 已有处理任务在运行
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var filesToUpload = new List<UploadQueueItem>();
|
||||
|
||||
// 从队列中取出最多BATCH_SIZE个文件
|
||||
while (filesToUpload.Count < BATCH_SIZE && _uploadQueue.TryDequeue(out UploadQueueItem item))
|
||||
{
|
||||
// 再次检查文件是否存在
|
||||
if (File.Exists(item.FilePath))
|
||||
{
|
||||
filesToUpload.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToUpload.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取共享的白板信息(同一批次的所有文件共享认证信息)
|
||||
WhiteboardInfo sharedWhiteboard = null;
|
||||
string apiBaseUrl = null;
|
||||
string userToken = null;
|
||||
|
||||
try
|
||||
{
|
||||
var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
|
||||
if (string.IsNullOrEmpty(selectedClassName))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
userToken = MainWindow.Settings?.Dlass?.UserToken;
|
||||
if (string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
|
||||
// 获取白板信息(只获取一次,所有文件共享)
|
||||
using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
|
||||
{
|
||||
var authData = new
|
||||
{
|
||||
app_id = APP_ID,
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
var authResult = await apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
|
||||
|
||||
if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sharedWhiteboard = authResult.Whiteboards
|
||||
.FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
|
||||
|
||||
if (sharedWhiteboard == null || string.IsNullOrEmpty(sharedWhiteboard.BoardId) || string.IsNullOrEmpty(sharedWhiteboard.SecretKey))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"批量上传获取白板信息时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 并发上传所有文件(共享白板信息),并处理失败重试
|
||||
var uploadTasks = filesToUpload.Select(async item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await UploadFileInternalAsync(item.FilePath, sharedWhiteboard, apiBaseUrl, userToken);
|
||||
if (!success)
|
||||
{
|
||||
// 检查是否是可重试的错误
|
||||
if (IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败,将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Event);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 检查是否是可重试的错误(超时、网络错误等)
|
||||
var errorMessage = ex.Message.ToLower();
|
||||
bool isRetryable = errorMessage.Contains("超时") ||
|
||||
errorMessage.Contains("timeout") ||
|
||||
errorMessage.Contains("网络错误") ||
|
||||
errorMessage.Contains("network");
|
||||
|
||||
if (isRetryable && IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败({ex.Message}),将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Event);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
await Task.WhenAll(uploadTasks);
|
||||
|
||||
// 上传完成后保存队列状态
|
||||
await SaveQueueToFileAsync();
|
||||
|
||||
// 如果队列达到批量大小,继续处理
|
||||
if (_uploadQueue.Count >= BATCH_SIZE)
|
||||
{
|
||||
_ = ProcessUploadQueueAsync();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueProcessingLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部上传方法,执行实际上传操作
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="whiteboard">白板信息(如果为null则重新获取)</param>
|
||||
/// <param name="apiBaseUrl">API基础URL(如果为null则从设置获取)</param>
|
||||
/// <param name="userToken">用户Token(如果为null则从设置获取)</param>
|
||||
private static async Task<bool> UploadFileInternalAsync(string filePath, WhiteboardInfo whiteboard = null, string apiBaseUrl = null, string userToken = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次检查文件是否存在(可能在队列等待时被删除)
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件大小(最大10MB,ZIP文件可能更大,允许50MB)
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果白板信息未提供,则重新获取
|
||||
if (whiteboard == null)
|
||||
{
|
||||
var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
|
||||
if (string.IsNullOrEmpty(selectedClassName))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
userToken = userToken ?? MainWindow.Settings?.Dlass?.UserToken;
|
||||
if (string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
apiBaseUrl = apiBaseUrl ?? MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
|
||||
// 创建API客户端并获取白板信息
|
||||
using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
|
||||
{
|
||||
var authData = new
|
||||
{
|
||||
app_id = APP_ID,
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
var authResult = await apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
|
||||
|
||||
if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找匹配班级的白板
|
||||
whiteboard = authResult.Whiteboards
|
||||
.FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
|
||||
|
||||
if (whiteboard == null || string.IsNullOrEmpty(whiteboard.BoardId) || string.IsNullOrEmpty(whiteboard.SecretKey))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取API基础URL和用户Token(如果未提供)
|
||||
apiBaseUrl = apiBaseUrl ?? MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
userToken = userToken ?? MainWindow.Settings?.Dlass?.UserToken;
|
||||
|
||||
// 准备上传参数
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||
var title = fileName;
|
||||
string fileType;
|
||||
string tags;
|
||||
if (fileExtension == ".zip")
|
||||
{
|
||||
fileType = "多页面墨迹压缩包";
|
||||
tags = "自动上传,多页面,zip,压缩包";
|
||||
}
|
||||
else if (fileExtension == ".icstk")
|
||||
{
|
||||
fileType = "墨迹文件";
|
||||
tags = "自动上传,墨迹,icstk";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileType = "笔记";
|
||||
tags = "自动上传,笔记,png";
|
||||
}
|
||||
var description = $"自动上传的{fileType} - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
|
||||
|
||||
// 创建API客户端并上传文件
|
||||
using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
|
||||
{
|
||||
var uploadResult = await apiClient.UploadNoteAsync<UploadNoteResponse>(
|
||||
"/api/whiteboard/upload_note",
|
||||
filePath,
|
||||
whiteboard.BoardId,
|
||||
whiteboard.SecretKey,
|
||||
title,
|
||||
description,
|
||||
tags);
|
||||
|
||||
if (uploadResult != null && uploadResult.Success)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"笔记上传成功:{fileName} -> {uploadResult.FileUrl}", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:服务器响应失败 - {uploadResult?.Message ?? "未知错误"}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录错误信息,抛出异常以便调用方判断是否可重试
|
||||
LogHelper.WriteLogToFile($"上传笔记时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断错误是否可重试(超时、网络错误等)
|
||||
/// </summary>
|
||||
private static bool IsRetryableError(string filePath)
|
||||
{
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false; // 文件不存在,不可重试
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".zip")
|
||||
{
|
||||
return false; // 文件格式错误,不可重试
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
return false; // 文件过大,不可重试
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false; // 无法读取文件信息,不可重试
|
||||
}
|
||||
|
||||
// 其他错误(超时、网络错误等)可以重试
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,16 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
UnregisterHotkey(hotkeyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
HotkeyManager.Current.Remove(hotkeyName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// 创建快捷键信息
|
||||
var hotkeyInfo = new HotkeyInfo
|
||||
@@ -112,9 +122,8 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册全局快捷键 {hotkeyName} 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,813 +0,0 @@
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Windows.Ink;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 多PPT墨迹管理器 - 支持多个PPT窗口分别管理墨迹
|
||||
/// </summary>
|
||||
public class MultiPPTInkManager : IDisposable
|
||||
{
|
||||
#region Properties
|
||||
public bool IsAutoSaveEnabled { get; set; } = true;
|
||||
public string AutoSaveLocation { get; set; } = "";
|
||||
public PPTManager PPTManager { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private readonly Dictionary<string, PPTInkManager> _presentationManagers;
|
||||
private readonly Dictionary<string, PresentationInfo> _presentationInfos;
|
||||
private readonly object _lockObject = new object();
|
||||
private bool _disposed;
|
||||
private string _currentActivePresentationId = "";
|
||||
|
||||
// 墨迹备份机制
|
||||
private readonly Dictionary<string, Dictionary<int, StrokeCollection>> _strokeBackups;
|
||||
private DateTime _lastBackupTime = DateTime.MinValue;
|
||||
private const int BackupIntervalMinutes = 2; // 每2分钟备份一次
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public MultiPPTInkManager()
|
||||
{
|
||||
_presentationManagers = new Dictionary<string, PPTInkManager>();
|
||||
_presentationInfos = new Dictionary<string, PresentationInfo>();
|
||||
_strokeBackups = new Dictionary<string, Dictionary<int, StrokeCollection>>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 初始化新的演示文稿
|
||||
/// </summary>
|
||||
public void InitializePresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
|
||||
// 如果已存在该演示文稿的管理器,先清理
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].Dispose();
|
||||
_presentationManagers.Remove(presentationId);
|
||||
}
|
||||
|
||||
// 创建新的墨迹管理器
|
||||
var inkManager = new PPTInkManager();
|
||||
inkManager.IsAutoSaveEnabled = IsAutoSaveEnabled;
|
||||
inkManager.AutoSaveLocation = AutoSaveLocation;
|
||||
inkManager.InitializePresentation(presentation);
|
||||
|
||||
// 保存管理器和演示文稿信息
|
||||
_presentationManagers[presentationId] = inkManager;
|
||||
_presentationInfos[presentationId] = new PresentationInfo
|
||||
{
|
||||
Id = presentationId,
|
||||
Name = presentation.Name,
|
||||
FullName = presentation.FullName,
|
||||
SlideCount = presentation.Slides.Count,
|
||||
CreatedTime = DateTime.Now,
|
||||
LastAccessTime = DateTime.Now
|
||||
};
|
||||
|
||||
// 设置为当前活跃的演示文稿
|
||||
_currentActivePresentationId = presentationId;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化多PPT墨迹管理失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定的演示文稿
|
||||
/// </summary>
|
||||
public bool SwitchToPresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return false;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
// 如果切换的是不同的演示文稿,先保存当前活跃演示文稿的墨迹
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId) &&
|
||||
_currentActivePresentationId != presentationId)
|
||||
{
|
||||
var currentManager = GetCurrentManager();
|
||||
if (currentManager != null)
|
||||
{
|
||||
// 获取当前活跃的演示文稿并保存墨迹
|
||||
var currentPresentation = GetCurrentActivePresentation();
|
||||
if (currentPresentation != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentManager.SaveAllStrokesToFile(currentPresentation);
|
||||
LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存当前演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_currentActivePresentationId = presentationId;
|
||||
|
||||
// 更新最后访问时间
|
||||
if (_presentationInfos.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationInfos[presentationId].LastAccessTime = DateTime.Now;
|
||||
}
|
||||
|
||||
if (_currentActivePresentationId != presentationId)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不存在,尝试初始化
|
||||
InitializePresentation(presentation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换到演示文稿失败: {ex}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前页面的墨迹
|
||||
/// </summary>
|
||||
public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
if (slideIndex <= 0 || strokes == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
// 保存到管理器
|
||||
manager.SaveCurrentSlideStrokes(slideIndex, strokes);
|
||||
|
||||
// 只有在保存成功后才创建备份
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId))
|
||||
{
|
||||
CreateStrokeBackup(_currentActivePresentationId, slideIndex, strokes);
|
||||
}
|
||||
|
||||
// 检查是否需要执行定期备份
|
||||
CheckAndPerformBackup();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制保存指定页面的墨迹(忽略锁定状态)
|
||||
/// </summary>
|
||||
public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
if (slideIndex <= 0 || strokes == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
manager.ForceSaveSlideStrokes(slideIndex, strokes);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"强制保存页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定页面的墨迹
|
||||
/// </summary>
|
||||
public StrokeCollection LoadSlideStrokes(int slideIndex)
|
||||
{
|
||||
if (slideIndex <= 0) return new StrokeCollection();
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
var strokes = manager.LoadSlideStrokes(slideIndex);
|
||||
|
||||
// 如果从管理器加载失败,尝试从备份恢复
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId))
|
||||
{
|
||||
strokes = RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return strokes ?? new StrokeCollection();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
|
||||
// 尝试从备份恢复
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId))
|
||||
{
|
||||
return RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面并加载墨迹
|
||||
/// </summary>
|
||||
public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
return manager.SwitchToSlide(slideIndex, currentStrokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"无法获取当前墨迹管理器,页面切换失败: {slideIndex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有墨迹到文件
|
||||
/// </summary>
|
||||
public void SaveAllStrokesToFile(Presentation presentation)
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存所有墨迹到文件失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载已保存的墨迹
|
||||
/// </summary>
|
||||
public void LoadSavedStrokes(Presentation presentation)
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].LoadSavedStrokes();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除指定演示文稿的所有墨迹
|
||||
/// </summary>
|
||||
public void ClearPresentationStrokes(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].ClearAllStrokes();
|
||||
LogHelper.WriteLogToFile($"已清除演示文稿墨迹: {presentation.Name}", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有演示文稿的墨迹
|
||||
/// </summary>
|
||||
public void ClearAllStrokes()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var manager in _presentationManagers.Values)
|
||||
{
|
||||
manager?.ClearAllStrokes();
|
||||
}
|
||||
LogHelper.WriteLogToFile("已清除所有演示文稿墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除所有墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 翻页后锁定墨迹写入
|
||||
/// </summary>
|
||||
public void LockInkForSlide(int slideIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
manager.LockInkForSlide(slideIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"锁定墨迹写入失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以写入墨迹
|
||||
/// </summary>
|
||||
public bool CanWriteInk(int currentSlideIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
return manager.CanWriteInk(currentSlideIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查墨迹写入权限失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置当前演示文稿的墨迹锁定状态
|
||||
/// </summary>
|
||||
public void ResetCurrentPresentationLockState()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
manager.ResetLockState();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重置墨迹锁定状态失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除演示文稿管理器
|
||||
/// </summary>
|
||||
public void RemovePresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
// 保存墨迹到文件
|
||||
_presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
|
||||
|
||||
// 释放资源
|
||||
_presentationManagers[presentationId].Dispose();
|
||||
_presentationManagers.Remove(presentationId);
|
||||
}
|
||||
|
||||
if (_presentationInfos.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationInfos.Remove(presentationId);
|
||||
}
|
||||
|
||||
// 如果移除的是当前活跃的演示文稿,重置活跃ID
|
||||
if (_currentActivePresentationId == presentationId)
|
||||
{
|
||||
_currentActivePresentationId = "";
|
||||
}
|
||||
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前管理的演示文稿数量
|
||||
/// </summary>
|
||||
public int GetPresentationCount()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _presentationManagers.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有演示文稿信息
|
||||
/// </summary>
|
||||
public List<PresentationInfo> GetAllPresentationInfos()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _presentationInfos.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理长时间未访问的演示文稿管理器
|
||||
/// </summary>
|
||||
public void CleanupInactivePresentations(TimeSpan inactiveThreshold)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var inactiveIds = new List<string>();
|
||||
var cutoffTime = DateTime.Now - inactiveThreshold;
|
||||
|
||||
foreach (var info in _presentationInfos.Values)
|
||||
{
|
||||
if (info.LastAccessTime < cutoffTime && info.Id != _currentActivePresentationId)
|
||||
{
|
||||
inactiveIds.Add(info.Id);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in inactiveIds)
|
||||
{
|
||||
if (_presentationManagers.ContainsKey(id))
|
||||
{
|
||||
_presentationManagers[id].Dispose();
|
||||
_presentationManagers.Remove(id);
|
||||
}
|
||||
_presentationInfos.Remove(id);
|
||||
|
||||
// 清理备份数据
|
||||
if (_strokeBackups.ContainsKey(id))
|
||||
{
|
||||
_strokeBackups.Remove(id);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已清理非活跃演示文稿: {id}", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理非活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建墨迹备份
|
||||
/// </summary>
|
||||
private void CreateStrokeBackup(string presentationId, int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0) return;
|
||||
|
||||
if (!_strokeBackups.ContainsKey(presentationId))
|
||||
{
|
||||
_strokeBackups[presentationId] = new Dictionary<int, StrokeCollection>();
|
||||
}
|
||||
|
||||
// 释放旧的备份
|
||||
if (_strokeBackups[presentationId].ContainsKey(slideIndex))
|
||||
{
|
||||
_strokeBackups[presentationId][slideIndex] = null;
|
||||
}
|
||||
|
||||
// 创建新的备份
|
||||
_strokeBackups[presentationId][slideIndex] = strokes.Clone();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建墨迹备份失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从备份恢复墨迹
|
||||
/// </summary>
|
||||
private StrokeCollection RestoreStrokeFromBackup(string presentationId, int slideIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_strokeBackups.ContainsKey(presentationId) &&
|
||||
_strokeBackups[presentationId].ContainsKey(slideIndex))
|
||||
{
|
||||
var backup = _strokeBackups[presentationId][slideIndex];
|
||||
if (backup != null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复第{slideIndex}页墨迹", LogHelper.LogType.Trace);
|
||||
return backup.Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并执行定期备份
|
||||
/// </summary>
|
||||
private void CheckAndPerformBackup()
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 检查是否需要执行备份
|
||||
if (now - _lastBackupTime < TimeSpan.FromMinutes(BackupIntervalMinutes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 备份当前活跃演示文稿的所有墨迹
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId) &&
|
||||
_presentationManagers.ContainsKey(_currentActivePresentationId))
|
||||
{
|
||||
var manager = _presentationManagers[_currentActivePresentationId];
|
||||
if (manager != null)
|
||||
{
|
||||
// 这里可以添加更详细的备份逻辑
|
||||
}
|
||||
}
|
||||
|
||||
_lastBackupTime = now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"定期备份检查失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private PPTInkManager GetCurrentManager()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_currentActivePresentationId) ||
|
||||
!_presentationManagers.ContainsKey(_currentActivePresentationId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _presentationManagers[_currentActivePresentationId];
|
||||
}
|
||||
|
||||
private Presentation GetCurrentActivePresentation()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通过PPTManager获取当前活跃的演示文稿
|
||||
return PPTManager?.GetCurrentActivePresentation();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GeneratePresentationId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查COM对象是否仍然有效
|
||||
if (presentation == null)
|
||||
{
|
||||
return $"invalid_{DateTime.Now.Ticks}";
|
||||
}
|
||||
|
||||
var presentationPath = presentation.FullName;
|
||||
var fileHash = GetFileHash(presentationPath);
|
||||
var processId = GetProcessId(presentation);
|
||||
return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}_{processId}";
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
|
||||
{
|
||||
return $"disconnected_{DateTime.Now.Ticks}";
|
||||
}
|
||||
return $"error_{DateTime.Now.Ticks}";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return $"unknown_{DateTime.Now.Ticks}";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFileHash(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return "unknown";
|
||||
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath));
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 所有异常都静默处理,避免日志噪音
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetProcessId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 尝试获取PowerPoint应用程序的进程ID
|
||||
if (presentation.Application != null)
|
||||
{
|
||||
// 通过COM对象获取进程信息
|
||||
var hwnd = presentation.Application.HWND;
|
||||
if (hwnd != 0)
|
||||
{
|
||||
return hwnd.ToString();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
// COM对象已失效,这是正常情况,完全静默处理
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
|
||||
{
|
||||
return "disconnected";
|
||||
}
|
||||
return "error";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
// 释放所有管理器
|
||||
foreach (var manager in _presentationManagers.Values)
|
||||
{
|
||||
manager?.Dispose();
|
||||
}
|
||||
_presentationManagers.Clear();
|
||||
_presentationInfos.Clear();
|
||||
|
||||
// 清理备份数据
|
||||
foreach (var backupDict in _strokeBackups.Values)
|
||||
{
|
||||
foreach (var backup in backupDict.Values)
|
||||
{
|
||||
backup?.Clear();
|
||||
}
|
||||
backupDict.Clear();
|
||||
}
|
||||
_strokeBackups.Clear();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示文稿信息
|
||||
/// </summary>
|
||||
public class PresentationInfo
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
public int SlideCount { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public DateTime LastAccessTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -421,6 +421,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\gesture.png" />
|
||||
<Resource Include="Resources\new-icons\gesture_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\gesture-enabled.png" />
|
||||
|
||||
+135
-10
@@ -6,6 +6,8 @@
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:c="clr-namespace:Ink_Canvas.Converter"
|
||||
xmlns:Controls="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
|
||||
xmlns:controls="clr-namespace:Ink_Canvas.Controls"
|
||||
xmlns:Windows="clr-namespace:Ink_Canvas.Windows"
|
||||
mc:Ignorable="d"
|
||||
AllowsTransparency="True"
|
||||
WindowStyle="None"
|
||||
@@ -2824,17 +2826,11 @@
|
||||
TextWrapping="Wrap" Foreground="#a1a1aa" Margin="0,0,0,8" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,8">
|
||||
<Button Name="BtnUnregisterFileAssociation" Content="取消文件关联"
|
||||
Padding="12,6" Margin="0,0,8,0" Click="BtnUnregisterFileAssociation_Click"
|
||||
Background="#FFDC3545" Foreground="White" BorderBrush="#FFBD2130"
|
||||
FontFamily="Microsoft YaHei UI" FontSize="12" />
|
||||
Padding="15,5" Margin="0,0,8,0" Click="BtnUnregisterFileAssociation_Click" />
|
||||
<Button Name="BtnCheckFileAssociation" Content="检查关联状态"
|
||||
Padding="12,6" Margin="0,0,8,0" Click="BtnCheckFileAssociation_Click"
|
||||
Background="#FF17A2B8" Foreground="White" BorderBrush="#FF138496"
|
||||
FontFamily="Microsoft YaHei UI" FontSize="12" />
|
||||
Padding="15,5" Margin="0,0,8,0" Click="BtnCheckFileAssociation_Click" />
|
||||
<Button Name="BtnRegisterFileAssociation" Content="重新注册关联"
|
||||
Padding="12,6" Click="BtnRegisterFileAssociation_Click"
|
||||
Background="#FF28A745" Foreground="White" BorderBrush="#FF1E7E34"
|
||||
FontFamily="Microsoft YaHei UI" FontSize="12" />
|
||||
Padding="15,5" Click="BtnRegisterFileAssociation_Click" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Name="TextBlockFileAssociationStatus" Text=""
|
||||
Foreground="#a1a1aa" FontSize="12" Margin="0,4,0,0" />
|
||||
@@ -3083,6 +3079,30 @@
|
||||
Toggled="ToggleSwitchAutoSaveStrokesAtScreenshot_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="定时自动保存墨迹" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchEnableAutoSaveStrokes" IsOn="False"
|
||||
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchEnableAutoSaveStrokes_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#a1a1aa" Text="保存间隔" VerticalAlignment="Center"
|
||||
FontSize="12" Margin="0,0,16,0" />
|
||||
<ComboBox Name="ComboBoxAutoSaveStrokesInterval" Width="120"
|
||||
SelectionChanged="ComboBoxAutoSaveStrokesInterval_SelectionChanged">
|
||||
<ComboBoxItem Content="1分钟" Tag="1" />
|
||||
<ComboBoxItem Content="3分钟" Tag="3" />
|
||||
<ComboBoxItem Content="5分钟" Tag="5" />
|
||||
<ComboBoxItem Content="10分钟" Tag="10" />
|
||||
<ComboBoxItem Content="15分钟" Tag="15" />
|
||||
<ComboBoxItem Content="30分钟" Tag="30" />
|
||||
<ComboBoxItem Content="60分钟" Tag="60" />
|
||||
</ComboBox>
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Text="# 开启后将在设定时间间隔自动保存墨迹,仅在画布可见且有墨迹时才会保存" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="墨迹全页面保存" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
@@ -3167,6 +3187,17 @@
|
||||
FontSize="14" Margin="8,0,0,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
|
||||
StrokeThickness="1" Margin="0,8,0,8" />
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0">
|
||||
<Button Name="BtnDlassSettingsManage" Content="Dlass设置管理"
|
||||
HorizontalAlignment="Left"
|
||||
Click="BtnDlassSettingsManage_Click"
|
||||
Padding="15,5"
|
||||
Margin="0,0,0,0"/>
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
|
||||
StrokeThickness="1" Margin="0,8,0,8" />
|
||||
|
||||
@@ -3249,6 +3280,15 @@
|
||||
FontWeight="Bold"
|
||||
Toggled="ToggleSwitchShowRandomAndSingleDraw_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="启用快抽悬浮按钮"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchEnableQuickDraw"
|
||||
IsOn="True" FontFamily="Microsoft YaHei UI"
|
||||
FontWeight="Bold"
|
||||
Toggled="ToggleSwitchEnableQuickDraw_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="直接调用外部点名"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
@@ -3299,6 +3339,62 @@
|
||||
FontFamily="Consolas"
|
||||
Text="{Binding ElementName=RandWindowOnceMaxStudentsSlider, Path=Value, Converter={StaticResource IntNumberToString}}" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<!-- 新点名UI设置 -->
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0"
|
||||
Stroke="#3f3f46" StrokeThickness="1" Margin="0,4,0,4" />
|
||||
<TextBlock Foreground="#fafafa" Text="新点名UI设置"
|
||||
FontSize="16" FontWeight="Bold" Margin="0,10,0,5" />
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="启用新点名UI"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchUseNewRollCallUI"
|
||||
IsOn="True" FontFamily="Microsoft YaHei UI"
|
||||
FontWeight="Bold"
|
||||
Toggled="ToggleSwitchUseNewRollCallUI_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="启用机器学习避免重复"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchEnableMLAvoidance"
|
||||
IsOn="True" FontFamily="Microsoft YaHei UI"
|
||||
FontWeight="Bold"
|
||||
Toggled="ToggleSwitchEnableMLAvoidance_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="避免重复历史记录数量" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<Slider x:Name="MLAvoidanceHistorySlider" Minimum="5"
|
||||
Maximum="50" Width="168" FontFamily="Microsoft YaHei UI"
|
||||
ValueChanged="MLAvoidanceHistorySlider_ValueChanged"
|
||||
FontSize="20" IsSnapToTickEnabled="True" Value="50" TickFrequency="5"
|
||||
TickPlacement="None" AutoToolTipPlacement="None" />
|
||||
<TextBlock VerticalAlignment="Center" Margin="12,0,16,0" FontSize="14"
|
||||
FontFamily="Consolas"
|
||||
Text="{Binding ElementName=MLAvoidanceHistorySlider, Path=Value, Converter={StaticResource IntNumberToString}}" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="避免重复权重" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<Slider x:Name="MLAvoidanceWeightSlider" Minimum="0.1"
|
||||
Maximum="1.0" Width="168" FontFamily="Microsoft YaHei UI"
|
||||
ValueChanged="MLAvoidanceWeightSlider_ValueChanged"
|
||||
FontSize="20" IsSnapToTickEnabled="True" Value="1.0" TickFrequency="0.1"
|
||||
TickPlacement="None" AutoToolTipPlacement="None" />
|
||||
<TextBlock VerticalAlignment="Center" Margin="12,0,16,0" FontSize="14"
|
||||
FontFamily="Consolas"
|
||||
Text="{Binding ElementName=MLAvoidanceWeightSlider, Path=Value, StringFormat={}{0:P0}}" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<TextBlock Text="# 机器学习算法会分析最近的点名历史,智能避免重复选择相同人员"
|
||||
TextWrapping="Wrap" Foreground="#a1a1aa" Margin="0,5,0,0" />
|
||||
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0"
|
||||
Stroke="#3f3f46" StrokeThickness="1" Margin="0,4,0,4" />
|
||||
<TextBlock Foreground="#fafafa" Text="计时器设置"
|
||||
@@ -3862,6 +3958,14 @@
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Canvas>
|
||||
|
||||
<!-- 快抽悬浮按钮 -->
|
||||
<controls:QuickDrawFloatingButtonControl x:Name="QuickDrawFloatingButton"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0,0,0,200"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1001"/>
|
||||
</Grid>
|
||||
|
||||
<Canvas IsHitTestVisible="False">
|
||||
@@ -9369,7 +9473,7 @@
|
||||
|
||||
MouseUp="TwoFingerGestureBorder_MouseUp" Background="Transparent"
|
||||
Orientation="Vertical" HorizontalAlignment="Center" Width="36" Margin="0">
|
||||
<Image x:Name="EnableTwoFingerGestureBtn" Source="/Resources/new-icons/gesture.png"
|
||||
<Image x:Name="EnableTwoFingerGestureBtn" Source="{DynamicResource GestureIcon}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" Width="28" Height="18"
|
||||
Margin="0,3,0,0" />
|
||||
<TextBlock x:Name="gestureiconText" Text="手势" Foreground="{DynamicResource FloatBarForeground}" FontSize="8"
|
||||
@@ -9847,6 +9951,27 @@
|
||||
</ui:SimpleStackPanel>
|
||||
</Border>
|
||||
</Viewbox>
|
||||
|
||||
<Border x:Name="TimerContainer"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="998"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Width="900"
|
||||
Height="500">
|
||||
<Windows:TimerControl x:Name="TimerControl"/>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="MinimizedTimerContainer"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="997"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Margin="20,20,0,0"
|
||||
Width="600"
|
||||
Height="200">
|
||||
<Windows:MinimizedTimerControl x:Name="MinimizedTimerControl"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
+268
-46
@@ -28,6 +28,9 @@ using Application = System.Windows.Application;
|
||||
using Brushes = System.Windows.Media.Brushes;
|
||||
using Button = System.Windows.Controls.Button;
|
||||
using Cursor = System.Windows.Input.Cursor;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using VerticalAlignment = System.Windows.VerticalAlignment;
|
||||
using Cursors = System.Windows.Input.Cursors;
|
||||
using DpiChangedEventArgs = System.Windows.DpiChangedEventArgs;
|
||||
using File = System.IO.File;
|
||||
@@ -60,6 +63,7 @@ namespace Ink_Canvas
|
||||
// 悬浮窗拦截管理器
|
||||
private FloatingWindowInterceptorManager _floatingWindowInterceptorManager;
|
||||
|
||||
|
||||
// 设置面板相关状态
|
||||
private bool userChangedNoFocusModeInSettings;
|
||||
private bool isTemporarilyDisablingNoFocusMode = false;
|
||||
@@ -264,6 +268,74 @@ namespace Ink_Canvas
|
||||
|
||||
// 为滑块控件添加触摸事件支持
|
||||
AddTouchSupportToSliders();
|
||||
|
||||
// 初始化计时器控件事件
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (TimerControl != null)
|
||||
{
|
||||
TimerControl.ShowMinimizedRequested += TimerControl_ShowMinimizedRequested;
|
||||
TimerControl.HideMinimizedRequested += TimerControl_HideMinimizedRequested;
|
||||
}
|
||||
|
||||
if (MinimizedTimerControl != null && TimerControl != null)
|
||||
{
|
||||
MinimizedTimerControl.SetParentControl(TimerControl);
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void TimerControl_ShowMinimizedRequested(object sender, EventArgs e)
|
||||
{
|
||||
var timerContainer = FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
double x = 0, y = 0;
|
||||
|
||||
if (timerContainer.HorizontalAlignment == HorizontalAlignment.Center &&
|
||||
timerContainer.VerticalAlignment == VerticalAlignment.Center)
|
||||
{
|
||||
var timerPoint = timerContainer.TransformToAncestor(this).Transform(new Point(0, 0));
|
||||
x = timerPoint.X;
|
||||
y = timerPoint.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
var timerMargin = timerContainer.Margin;
|
||||
x = double.IsNaN(timerMargin.Left) ? 0 : timerMargin.Left;
|
||||
y = double.IsNaN(timerMargin.Top) ? 0 : timerMargin.Top;
|
||||
}
|
||||
|
||||
minimizedContainer.Margin = new Thickness(x, y, 0, 0);
|
||||
minimizedContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
minimizedContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
timerContainer.Margin = new Thickness(x, y, 0, 0);
|
||||
timerContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
timerContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
timerContainer.Visibility = Visibility.Collapsed;
|
||||
minimizedContainer.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void TimerControl_HideMinimizedRequested(object sender, EventArgs e)
|
||||
{
|
||||
var timerContainer = FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
minimizedContainer.Visibility = Visibility.Collapsed;
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
|
||||
if (TimerControl != null)
|
||||
{
|
||||
TimerControl.UpdateActivityTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -321,13 +393,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (gest.ApplicationGesture == ApplicationGesture.Left)
|
||||
{
|
||||
// 直接发送翻页请求到PPT放映软件
|
||||
SendKeyToPPTSlideShow(false); // 下一页
|
||||
BtnPPTSlidesDown_Click(null, null); // 下一页
|
||||
}
|
||||
if (gest.ApplicationGesture == ApplicationGesture.Right)
|
||||
{
|
||||
// 直接发送翻页请求到PPT放映软件
|
||||
SendKeyToPPTSlideShow(true); // 上一页
|
||||
BtnPPTSlidesUp_Click(null, null); // 上一页
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,6 +470,9 @@ namespace Ink_Canvas
|
||||
LoadSettings(true);
|
||||
AutoBackupManager.Initialize(Settings);
|
||||
|
||||
// 初始化Dlass上传队列(恢复上次的上传队列)
|
||||
DlassNoteUploader.InitializeQueue();
|
||||
|
||||
// 检查保存路径是否可用,不可用则修正
|
||||
try
|
||||
{
|
||||
@@ -530,6 +603,9 @@ namespace Ink_Canvas
|
||||
else
|
||||
RadioCrashNoAction.IsChecked = true;
|
||||
|
||||
// 显示快抽悬浮按钮
|
||||
ShowQuickDrawFloatingButton();
|
||||
|
||||
// 如果当前不是黑板模式,则切换到黑板模式
|
||||
if (currentMode == 0)
|
||||
{
|
||||
@@ -610,6 +686,34 @@ namespace Ink_Canvas
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
// 初始化计时器控件关联
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (TimerControl != null && MinimizedTimerControl != null)
|
||||
{
|
||||
MinimizedTimerControl.SetParentControl(TimerControl);
|
||||
|
||||
TimerControl.ShowMinimizedRequested += (s, args) =>
|
||||
{
|
||||
if (TimerContainer != null && MinimizedTimerContainer != null && MinimizedTimerControl != null)
|
||||
{
|
||||
TimerContainer.Visibility = Visibility.Collapsed;
|
||||
MinimizedTimerContainer.Visibility = Visibility.Visible;
|
||||
MinimizedTimerControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
};
|
||||
|
||||
TimerControl.HideMinimizedRequested += (s, args) =>
|
||||
{
|
||||
if (MinimizedTimerContainer != null && MinimizedTimerControl != null)
|
||||
{
|
||||
MinimizedTimerContainer.Visibility = Visibility.Collapsed;
|
||||
MinimizedTimerControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
};
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
|
||||
@@ -668,6 +772,15 @@ namespace Ink_Canvas
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
LogHelper.WriteLogToFile("Ink Canvas closing", LogHelper.LogType.Event);
|
||||
try
|
||||
{
|
||||
// 快抽按钮现在集成在主窗口中,不需要单独关闭
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"关闭快抽悬浮按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
if (!CloseIsFromButton && Settings.Advanced.IsSecondConfirmWhenShutdownApp)
|
||||
{
|
||||
// 第一个确认对话框
|
||||
@@ -763,6 +876,8 @@ namespace Ink_Canvas
|
||||
// 停止置顶维护定时器
|
||||
StopTopmostMaintenance();
|
||||
|
||||
UninstallKeyboardHook();
|
||||
|
||||
// 从Z-Order管理器中移除主窗口
|
||||
WindowZOrderManager.UnregisterWindow(this);
|
||||
|
||||
@@ -1819,7 +1934,40 @@ namespace Ink_Canvas
|
||||
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentProcessId();
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
|
||||
private const int WH_KEYBOARD_LL = 13;
|
||||
private const int WM_KEYDOWN = 0x0100;
|
||||
private const int WM_KEYUP = 0x0101;
|
||||
private const int WM_SYSKEYDOWN = 0x0104;
|
||||
private const int WM_SYSKEYUP = 0x0105;
|
||||
|
||||
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct KBDLLHOOKSTRUCT
|
||||
{
|
||||
public uint vkCode;
|
||||
public uint scanCode;
|
||||
public uint flags;
|
||||
public uint time;
|
||||
public IntPtr dwExtraInfo;
|
||||
}
|
||||
|
||||
private LowLevelKeyboardProc _keyboardProc;
|
||||
private IntPtr _keyboardHookId = IntPtr.Zero;
|
||||
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
private const int WS_EX_NOACTIVATE = 0x08000000;
|
||||
@@ -1834,8 +1982,70 @@ namespace Ink_Canvas
|
||||
|
||||
// 添加定时器来维护置顶状态
|
||||
private DispatcherTimer topmostMaintenanceTimer;
|
||||
private DispatcherTimer autoSaveStrokesTimer;
|
||||
private bool isTopmostMaintenanceEnabled;
|
||||
|
||||
private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (nCode >= 0)
|
||||
{
|
||||
if (Settings.Advanced.IsNoFocusMode &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
currentMode == 0)
|
||||
{
|
||||
KBDLLHOOKSTRUCT hookStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
|
||||
uint vkCode = hookStruct.vkCode;
|
||||
|
||||
if (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
|
||||
{
|
||||
if (vkCode == 0x22 || vkCode == 0x28 || vkCode == 0x27 ||
|
||||
vkCode == 0x4E || vkCode == 0x20)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
BtnPPTSlidesDown_Click(null, null);
|
||||
}), DispatcherPriority.Normal);
|
||||
return (IntPtr)1;
|
||||
}
|
||||
else if (vkCode == 0x21 || vkCode == 0x26 || vkCode == 0x25 ||
|
||||
vkCode == 0x50)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
BtnPPTSlidesUp_Click(null, null);
|
||||
}), DispatcherPriority.Normal);
|
||||
return (IntPtr)1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return CallNextHookEx(_keyboardHookId, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
private void InstallKeyboardHook()
|
||||
{
|
||||
if (_keyboardHookId == IntPtr.Zero)
|
||||
{
|
||||
_keyboardProc = KeyboardHookProc;
|
||||
_keyboardHookId = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardProc,
|
||||
GetModuleHandle(null), 0);
|
||||
if (_keyboardHookId == IntPtr.Zero)
|
||||
{
|
||||
LogHelper.WriteLogToFile("安装低级键盘钩子失败", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UninstallKeyboardHook()
|
||||
{
|
||||
if (_keyboardHookId != IntPtr.Zero)
|
||||
{
|
||||
UnhookWindowsHookEx(_keyboardHookId);
|
||||
_keyboardHookId = IntPtr.Zero;
|
||||
_keyboardProc = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyNoFocusMode()
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
@@ -1847,10 +2057,12 @@ namespace Ink_Canvas
|
||||
if (shouldBeNoFocus)
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE);
|
||||
InstallKeyboardHook();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_NOACTIVATE);
|
||||
UninstallKeyboardHook();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,6 +2153,28 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
public void PauseTopmostMaintenance()
|
||||
{
|
||||
if (topmostMaintenanceTimer != null && isTopmostMaintenanceEnabled)
|
||||
{
|
||||
topmostMaintenanceTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResumeTopmostMaintenance()
|
||||
{
|
||||
if (Settings.Advanced.IsAlwaysOnTop &&
|
||||
Settings.Advanced.IsNoFocusMode &&
|
||||
!Settings.Advanced.EnableUIAccessTopMost)
|
||||
{
|
||||
if (topmostMaintenanceTimer != null && !isTopmostMaintenanceEnabled)
|
||||
{
|
||||
topmostMaintenanceTimer.Start();
|
||||
isTopmostMaintenanceEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 置顶维护定时器事件
|
||||
/// </summary>
|
||||
@@ -2329,47 +2563,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PPT翻页直接传递
|
||||
/// <summary>
|
||||
/// 直接发送翻页请求到PPT放映软件,让PPT软件处理翻页
|
||||
/// </summary>
|
||||
/// <param name="isPrevious">是否为上一页</param>
|
||||
private void SendKeyToPPTSlideShow(bool isPrevious)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 查找PPT放映窗口并发送按键
|
||||
var pptWindows = Process.GetProcessesByName("POWERPNT");
|
||||
var wpsWindows = Process.GetProcessesByName("wpp");
|
||||
|
||||
foreach (var process in pptWindows.Concat(wpsWindows))
|
||||
{
|
||||
if (process.MainWindowHandle != IntPtr.Zero)
|
||||
{
|
||||
// 激活PPT窗口
|
||||
SetForegroundWindow(process.MainWindowHandle);
|
||||
|
||||
// 发送翻页按键消息
|
||||
int keyCode = isPrevious ? 0x21 : 0x22; // VK_PRIOR : VK_NEXT
|
||||
|
||||
// 发送按键按下和释放消息
|
||||
PostMessage(process.MainWindowHandle, 0x0100, (IntPtr)keyCode, IntPtr.Zero); // WM_KEYDOWN
|
||||
PostMessage(process.MainWindowHandle, 0x0101, (IntPtr)keyCode, IntPtr.Zero); // WM_KEYUP
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 如果直接发送失败,回退到原来的方法
|
||||
if (isPrevious)
|
||||
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
|
||||
else
|
||||
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 初始化文件关联状态显示
|
||||
@@ -2509,7 +2702,9 @@ namespace Ink_Canvas
|
||||
BoardHighlighterWidthSlider,
|
||||
InkWidthSlider,
|
||||
InkAlphaSlider,
|
||||
HighlighterWidthSlider
|
||||
HighlighterWidthSlider,
|
||||
MLAvoidanceHistorySlider,
|
||||
MLAvoidanceWeightSlider
|
||||
};
|
||||
|
||||
foreach (var slider in sliders)
|
||||
@@ -3053,6 +3248,33 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示快抽悬浮按钮
|
||||
/// </summary>
|
||||
private void ShowQuickDrawFloatingButton()
|
||||
{
|
||||
try
|
||||
{
|
||||
var quickDrawButton = FindName("QuickDrawFloatingButton") as Controls.QuickDrawFloatingButtonControl;
|
||||
if (quickDrawButton == null) return;
|
||||
|
||||
// 检查设置是否启用快抽功能
|
||||
if (Settings?.RandSettings?.EnableQuickDraw == true)
|
||||
{
|
||||
quickDrawButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
quickDrawButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示快抽悬浮按钮失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ namespace Ink_Canvas
|
||||
// 刷新图片选中栏图标
|
||||
RefreshImageSelectionIcons();
|
||||
|
||||
// 刷新手势按钮图标
|
||||
RefreshGestureButtonIcon();
|
||||
|
||||
RefreshFloatingBarHighlightColors();
|
||||
|
||||
if (autoSwitchIcon)
|
||||
@@ -109,6 +112,9 @@ namespace Ink_Canvas
|
||||
// 刷新图片选中栏图标
|
||||
RefreshImageSelectionIcons();
|
||||
|
||||
// 刷新手势按钮图标
|
||||
RefreshGestureButtonIcon();
|
||||
|
||||
RefreshFloatingBarHighlightColors();
|
||||
|
||||
if (autoSwitchIcon)
|
||||
@@ -492,6 +498,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新手势按钮图标
|
||||
/// </summary>
|
||||
private void RefreshGestureButtonIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 调用手势按钮颜色和图标更新方法,该方法会根据当前主题和手势状态设置正确的图标
|
||||
CheckEnableTwoFingerGestureBtnColorPrompt();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新其他窗口的主题
|
||||
/// </summary>
|
||||
@@ -515,6 +536,17 @@ namespace Ink_Canvas
|
||||
operatingGuideWindow.RefreshTheme();
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新计时器控件
|
||||
if (TimerControl != null)
|
||||
{
|
||||
TimerControl.RefreshTheme();
|
||||
}
|
||||
|
||||
if (MinimizedTimerControl != null)
|
||||
{
|
||||
MinimizedTimerControl.RefreshTheme();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -424,8 +424,14 @@ namespace Ink_Canvas
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
|
||||
// 始终允许点击"下一页/新页面"按钮(除非已达最大页数)
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = !isMaxPage;
|
||||
if (isLastPage)
|
||||
{
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = !isMaxPage;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = true;
|
||||
}
|
||||
|
||||
// 获取主题颜色资源
|
||||
var iconForegroundBrush = Application.Current.FindResource("IconForeground") as SolidColorBrush;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
@@ -857,4 +857,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,7 @@ namespace Ink_Canvas
|
||||
// 多指书写模式启用时,手势功能被禁用
|
||||
TwoFingerGestureSimpleStackPanel.Opacity = 0.5;
|
||||
TwoFingerGestureSimpleStackPanel.IsHitTestVisible = false;
|
||||
EnableTwoFingerGestureBtn.Source =
|
||||
new BitmapImage(new Uri("/Resources/new-icons/gesture.png", UriKind.Relative));
|
||||
EnableTwoFingerGestureBtn.Source = (BitmapImage)Application.Current.FindResource("GestureIcon");
|
||||
|
||||
// 根据主题设置颜色
|
||||
if (Settings.Appearance.Theme == 1) // 深色主题
|
||||
@@ -117,8 +116,7 @@ namespace Ink_Canvas
|
||||
|
||||
if (hasGestureEnabled)
|
||||
{
|
||||
EnableTwoFingerGestureBtn.Source =
|
||||
new BitmapImage(new Uri("/Resources/new-icons/gesture-enabled.png", UriKind.Relative));
|
||||
EnableTwoFingerGestureBtn.Source = (BitmapImage)Application.Current.FindResource("GestureIconEnabled");
|
||||
|
||||
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(Colors.GhostWhite);
|
||||
@@ -130,8 +128,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableTwoFingerGestureBtn.Source =
|
||||
new BitmapImage(new Uri("/Resources/new-icons/gesture.png", UriKind.Relative));
|
||||
EnableTwoFingerGestureBtn.Source = (BitmapImage)Application.Current.FindResource("GestureIcon");
|
||||
|
||||
// 根据主题设置颜色
|
||||
if (Settings.Appearance.Theme == 1) // 深色主题
|
||||
@@ -176,11 +173,16 @@ namespace Ink_Canvas
|
||||
return;
|
||||
}
|
||||
|
||||
// 在屏幕模式(非放映模式)下,不显示手势按钮
|
||||
if (currentMode == 0)
|
||||
{
|
||||
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
if (GridTransparencyFakeBackground.Background != Brushes.Transparent && isVisible)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (StackPanelCanvasControls.Visibility != Visibility.Visible
|
||||
@@ -740,7 +742,6 @@ namespace Ink_Canvas
|
||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 新增:确保在白板模式下基础浮动栏被隐藏
|
||||
ViewboxFloatingBar.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
@@ -1050,17 +1051,38 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
|
||||
// 参考老计时器的窗口置顶功能:在白板模式下停止窗口置顶
|
||||
if (currentMode == 1) // 白板模式
|
||||
if (Settings.RandSettings?.UseNewStyleUI == true)
|
||||
{
|
||||
Topmost = false;
|
||||
if (TimerContainer != null && TimerControl != null)
|
||||
{
|
||||
TimerContainer.Visibility = Visibility.Visible;
|
||||
if (MinimizedTimerContainer != null)
|
||||
{
|
||||
MinimizedTimerContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
TimerControl.CloseRequested += (s, args) =>
|
||||
{
|
||||
TimerContainer.Visibility = Visibility.Collapsed;
|
||||
if (MinimizedTimerContainer != null)
|
||||
{
|
||||
MinimizedTimerContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var timerWindow = CountdownTimerWindow.CreateTimerWindow();
|
||||
timerWindow.Show();
|
||||
if (currentMode == 1) // 白板模式
|
||||
else
|
||||
{
|
||||
timerWindow.Topmost = true;
|
||||
if (currentMode == 1)
|
||||
{
|
||||
Topmost = false;
|
||||
}
|
||||
|
||||
var timerWindow = CountdownTimerWindow.CreateTimerWindow();
|
||||
timerWindow.Show();
|
||||
if (currentMode == 1)
|
||||
{
|
||||
timerWindow.Topmost = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1085,11 +1107,20 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
|
||||
var randWindow = new RandWindow(Settings);
|
||||
randWindow.Show();
|
||||
// 根据设置决定使用哪个点名窗口
|
||||
if (Settings.RandSettings.UseNewRollCallUI)
|
||||
{
|
||||
// 使用新点名UI - 随机抽模式
|
||||
new NewStyleRollCallWindow(Settings, false).ShowDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用默认的随机点名窗口
|
||||
var randWindow = new RandWindow(Settings);
|
||||
randWindow.Show();
|
||||
|
||||
// 使用延迟确保窗口完全显示后再强制置顶
|
||||
randWindow.Dispatcher.BeginInvoke(new Action(() =>
|
||||
// 使用延迟确保窗口完全显示后再强制置顶
|
||||
randWindow.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1126,6 +1157,7 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"强制置顶RandWindow失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckEraserTypeTab()
|
||||
@@ -1232,14 +1264,30 @@ namespace Ink_Canvas
|
||||
{
|
||||
MessageBox.Show("无法调用外部点名:" + ex.Message);
|
||||
|
||||
// 调用失败时回退到默认的随机点名窗口
|
||||
new RandWindow(Settings, true).ShowDialog();
|
||||
// 调用失败时回退到相应的点名窗口
|
||||
if (Settings.RandSettings.UseNewRollCallUI)
|
||||
{
|
||||
new NewStyleRollCallWindow(Settings, true).ShowDialog(); // 单次抽模式
|
||||
}
|
||||
else
|
||||
{
|
||||
new RandWindow(Settings, true).ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用默认的随机点名窗口
|
||||
new RandWindow(Settings, true).ShowDialog();
|
||||
// 根据设置决定使用哪个点名窗口
|
||||
if (Settings.RandSettings.UseNewRollCallUI)
|
||||
{
|
||||
// 使用新点名UI - 单次抽模式
|
||||
new NewStyleRollCallWindow(Settings, true).ShowDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用默认的随机点名窗口
|
||||
new RandWindow(Settings, true).ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1997,8 +2045,6 @@ namespace Ink_Canvas
|
||||
// 禁用高级橡皮擦系统
|
||||
DisableEraserOverlay();
|
||||
|
||||
ExitMultiTouchModeIfNeeded();
|
||||
|
||||
SetFloatingBarHighlightPosition("pen");
|
||||
|
||||
// 记录当前是否已经是批注模式且是否为高光显示模式
|
||||
@@ -2840,39 +2886,14 @@ namespace Ink_Canvas
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
|
||||
CancelSingleFingerDragMode();
|
||||
|
||||
if (Settings.Canvas.ClearCanvasAndClearTimeMachine) timeMachine.ClearStrokeHistory();
|
||||
|
||||
if (Settings.Gesture.IsEnableMultiTouchMode && ToggleSwitchEnableMultiTouchMode != null && ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
{
|
||||
ReinitializeMultiTouchMode();
|
||||
}
|
||||
CancelSingleFingerDragMode();
|
||||
|
||||
}
|
||||
|
||||
private bool lastIsInMultiTouchMode;
|
||||
|
||||
private void ReinitializeMultiTouchMode()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isInMultiTouchMode)
|
||||
{
|
||||
isInMultiTouchMode = true;
|
||||
}
|
||||
|
||||
inkCanvas.TouchDown -= Main_Grid_TouchDown;
|
||||
inkCanvas.TouchDown += MainWindow_TouchDown;
|
||||
inkCanvas.StylusDown += MainWindow_StylusDown;
|
||||
inkCanvas.StylusMove += MainWindow_StylusMove;
|
||||
inkCanvas.StylusUp += MainWindow_StylusUp;
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelSingleFingerDragMode()
|
||||
{
|
||||
if (ToggleSwitchDrawShapeBorderAutoHide.IsOn) CollapseBorderDrawShape();
|
||||
@@ -2881,8 +2902,6 @@ namespace Ink_Canvas
|
||||
|
||||
if (isSingleFingerDragMode) BtnFingerDragMode_Click(BtnFingerDragMode, null);
|
||||
isLongPressSelected = false;
|
||||
|
||||
ResetTouchStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2895,17 +2914,6 @@ namespace Ink_Canvas
|
||||
// 清空触摸点计数器
|
||||
dec.Clear();
|
||||
|
||||
|
||||
// 重置单指拖动模式状态
|
||||
if (isSingleFingerDragMode)
|
||||
{
|
||||
isSingleFingerDragMode = false;
|
||||
if (BtnFingerDragMode != null)
|
||||
{
|
||||
BtnFingerDragMode.Content = "单指\n拖动";
|
||||
}
|
||||
}
|
||||
|
||||
// 重置手掌擦状态
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
@@ -3060,11 +3068,8 @@ namespace Ink_Canvas
|
||||
SaveStrokes(true);
|
||||
ClearStrokes(true);
|
||||
|
||||
// 总是恢复备份墨迹,不管是否在PPT模式
|
||||
// PPT墨迹和白板墨迹应该分别管理,不应该互相影响
|
||||
RestoreStrokes();
|
||||
|
||||
// 新增:在白板模式下隐藏基础浮动栏
|
||||
ViewboxFloatingBar.Visibility = Visibility.Collapsed;
|
||||
|
||||
BtnSwitch.Content = "屏幕";
|
||||
@@ -3215,7 +3220,6 @@ namespace Ink_Canvas
|
||||
CheckEnableTwoFingerGestureBtnVisibility(false);
|
||||
HideSubPanels("cursor");
|
||||
|
||||
// 新增:在屏幕模式下显示基础浮动栏
|
||||
if (currentMode == 0)
|
||||
{
|
||||
ViewboxFloatingBar.Visibility = Visibility.Visible;
|
||||
@@ -3226,7 +3230,6 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.ShowWithSlideFromLeftAndFade(StackPanelCanvasControls);
|
||||
CheckEnableTwoFingerGestureBtnVisibility(true);
|
||||
|
||||
// 新增:在批注模式下显示基础浮动栏
|
||||
if (currentMode == 0)
|
||||
{
|
||||
ViewboxFloatingBar.Visibility = Visibility.Visible;
|
||||
@@ -3370,7 +3373,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:插入图片方法
|
||||
// 插入图片方法
|
||||
private async void InsertImage_MouseUp_New(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
@@ -3602,8 +3605,6 @@ namespace Ink_Canvas
|
||||
// 检查浮动栏是否处于收起状态
|
||||
if (isFloatingBarFolded || (BorderFloatingBarMainControls != null && BorderFloatingBarMainControls.Visibility == Visibility.Collapsed))
|
||||
{
|
||||
// 在收起状态下,仍然需要设置高光位置,但可能需要调整计算方式
|
||||
// 这里先隐藏高光,等浮动栏展开时再显示
|
||||
FloatingbarSelectionBG.Visibility = Visibility.Hidden;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,49 +7,31 @@ namespace Ink_Canvas
|
||||
{
|
||||
private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
// 只有在PPT放映模式下才响应鼠标滚轮翻页
|
||||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
currentMode != 0 ||
|
||||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible ||
|
||||
PPTManager?.IsInSlideShow != true) return;
|
||||
|
||||
// 直接发送翻页请求到PPT放映软件,不通过软件处理
|
||||
if (BtnPPTSlideShowEnd.Visibility != Visibility.Visible || currentMode != 0) return;
|
||||
if (e.Delta >= 120)
|
||||
{
|
||||
// 上一页 - 发送PageUp键到PPT放映窗口
|
||||
SendKeyToPPTSlideShow(true);
|
||||
BtnPPTSlidesUp_Click(null, null);
|
||||
}
|
||||
else if (e.Delta <= -120)
|
||||
{
|
||||
// 下一页 - 发送PageDown键到PPT放映窗口
|
||||
SendKeyToPPTSlideShow(false);
|
||||
BtnPPTSlidesDown_Click(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void Main_Grid_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
// 只有在PPT放映模式下才响应键盘翻页快捷键
|
||||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
currentMode != 0 ||
|
||||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible ||
|
||||
PPTManager?.IsInSlideShow != true) return;
|
||||
if (BtnPPTSlideShowEnd.Visibility != Visibility.Visible || currentMode != 0) return;
|
||||
|
||||
// 直接发送翻页请求到PPT放映软件,不通过软件处理
|
||||
if (e.Key == Key.Down || e.Key == Key.PageDown || e.Key == Key.Right || e.Key == Key.N ||
|
||||
e.Key == Key.Space)
|
||||
if (e.Key == Key.Down || e.Key == Key.PageDown || e.Key == Key.Right || e.Key == Key.N || e.Key == Key.Space)
|
||||
{
|
||||
e.Handled = true; // 阻止事件继续传播
|
||||
SendKeyToPPTSlideShow(false); // 下一页
|
||||
BtnPPTSlidesDown_Click(null, null);
|
||||
}
|
||||
else if (e.Key == Key.Up || e.Key == Key.PageUp || e.Key == Key.Left || e.Key == Key.P)
|
||||
if (e.Key == Key.Up || e.Key == Key.PageUp || e.Key == Key.Left || e.Key == Key.P)
|
||||
{
|
||||
e.Handled = true; // 阻止事件继续传播
|
||||
SendKeyToPPTSlideShow(true); // 上一页
|
||||
BtnPPTSlidesUp_Click(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
// 保留PPT翻页快捷键处理
|
||||
// 以下方法保留供全局快捷键调用
|
||||
|
||||
private void HotKey_Undo(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
|
||||
@@ -104,7 +104,6 @@ namespace Ink_Canvas
|
||||
|
||||
#region PPT Managers
|
||||
private PPTManager _pptManager;
|
||||
private MultiPPTInkManager _multiPPTInkManager;
|
||||
private PPTInkManager _singlePPTInkManager;
|
||||
private PPTUIManager _pptUIManager;
|
||||
|
||||
@@ -135,19 +134,9 @@ namespace Ink_Canvas
|
||||
_pptManager.PresentationClose += OnPPTPresentationClose;
|
||||
_pptManager.SlideShowStateChanged += OnPPTSlideShowStateChanged;
|
||||
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager = new PPTInkManager();
|
||||
_singlePPTInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
|
||||
_singlePPTInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager = new MultiPPTInkManager();
|
||||
_multiPPTInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
|
||||
_multiPPTInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
|
||||
_multiPPTInkManager.PPTManager = _pptManager;
|
||||
}
|
||||
_singlePPTInkManager = new PPTInkManager();
|
||||
_singlePPTInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
|
||||
_singlePPTInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
|
||||
|
||||
// 初始化UI管理器
|
||||
_pptUIManager = new PPTUIManager(this);
|
||||
@@ -430,12 +419,10 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
_pptManager?.Dispose();
|
||||
_multiPPTInkManager?.Dispose();
|
||||
_singlePPTInkManager?.Dispose();
|
||||
_longPressTimer?.Stop();
|
||||
_longPressTimer = null;
|
||||
_pptManager = null;
|
||||
_multiPPTInkManager = null;
|
||||
_singlePPTInkManager = null;
|
||||
_pptUIManager = null;
|
||||
|
||||
@@ -521,14 +508,7 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event);
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.ClearAllStrokes();
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.ClearAllStrokes();
|
||||
}
|
||||
_singlePPTInkManager?.ClearAllStrokes();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -553,14 +533,7 @@ namespace Ink_Canvas
|
||||
TimeMachineHistories[0] = null;
|
||||
}
|
||||
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.InitializePresentation(pres);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.InitializePresentation(pres);
|
||||
}
|
||||
_singlePPTInkManager?.InitializePresentation(pres);
|
||||
|
||||
// 处理跳转到首页或上次播放页的逻辑
|
||||
HandlePresentationOpenNavigation(pres);
|
||||
@@ -594,15 +567,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
_multiPPTInkManager?.RemovePresentation(pres);
|
||||
}
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
|
||||
_pptUIManager?.UpdateConnectionStatus(false);
|
||||
});
|
||||
@@ -665,17 +630,36 @@ namespace Ink_Canvas
|
||||
|
||||
isStopInkReplay = true;
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
await Application.Current.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
var activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
Presentation activePresentation = null;
|
||||
int currentSlide = 0;
|
||||
int totalSlides = 0;
|
||||
|
||||
if (wn?.View != null && wn.Presentation != null)
|
||||
{
|
||||
activePresentation = wn.Presentation;
|
||||
currentSlide = wn.View.CurrentShowPosition;
|
||||
totalSlides = activePresentation.Slides.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
totalSlides = _pptManager?.SlidesCount ?? 0;
|
||||
}
|
||||
|
||||
if (activePresentation != null)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
if (_singlePPTInkManager != null)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
|
||||
try
|
||||
{
|
||||
_singlePPTInkManager.InitializePresentation(activePresentation);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,8 +675,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 更新UI状态
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
var totalSlides = _pptManager?.SlidesCount ?? 0;
|
||||
_pptUIManager?.UpdateSlideShowStatus(true, currentSlide, totalSlides);
|
||||
|
||||
// 设置浮动栏透明度和边距
|
||||
@@ -740,41 +722,39 @@ namespace Ink_Canvas
|
||||
if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow &&
|
||||
!Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
await Task.Delay(300);
|
||||
// 先进入批注模式,这会显示调色盘
|
||||
PenIcon_Click(null, null);
|
||||
// 然后设置颜色
|
||||
BtnColorRed_Click(null, null);
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
try
|
||||
{
|
||||
try
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
UpdateCurrentToolMode("pen");
|
||||
SetFloatingBarHighlightPosition("pen");
|
||||
if (Settings.Appearance.IsShowQuickColorPalette && QuickColorPalettePanel != null && QuickColorPaletteSingleRowPanel != null)
|
||||
{
|
||||
UpdateCurrentToolMode("pen");
|
||||
SetFloatingBarHighlightPosition("pen");
|
||||
if (Settings.Appearance.IsShowQuickColorPalette && QuickColorPalettePanel != null && QuickColorPaletteSingleRowPanel != null)
|
||||
// 根据显示模式选择显示哪个面板
|
||||
if (Settings.Appearance.QuickColorPaletteDisplayMode == 0)
|
||||
{
|
||||
// 根据显示模式选择显示哪个面板
|
||||
if (Settings.Appearance.QuickColorPaletteDisplayMode == 0)
|
||||
{
|
||||
// 单行显示模式
|
||||
QuickColorPalettePanel.Visibility = Visibility.Collapsed;
|
||||
QuickColorPaletteSingleRowPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 双行显示模式
|
||||
QuickColorPalettePanel.Visibility = Visibility.Visible;
|
||||
QuickColorPaletteSingleRowPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
// 单行显示模式
|
||||
QuickColorPalettePanel.Visibility = Visibility.Collapsed;
|
||||
QuickColorPaletteSingleRowPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 双行显示模式
|
||||
QuickColorPalettePanel.Visibility = Visibility.Visible;
|
||||
QuickColorPaletteSingleRowPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"PPT进入批注模式后同步浮动栏高光状态失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"PPT进入批注模式后同步浮动栏高光状态失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
isEnteredSlideShowEndEvent = false;
|
||||
@@ -807,20 +787,14 @@ namespace Ink_Canvas
|
||||
{
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
if (activePresentation != null)
|
||||
if (wn?.View == null || wn.Presentation == null)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
var totalSlides = _pptManager?.SlidesCount ?? 0;
|
||||
var currentSlide = wn.View.CurrentShowPosition;
|
||||
var activePresentation = wn.Presentation;
|
||||
var totalSlides = activePresentation.Slides.Count;
|
||||
|
||||
// 使用防抖机制处理页面切换
|
||||
HandleSlideSwitchWithDebounce(currentSlide, totalSlides);
|
||||
@@ -869,14 +843,7 @@ namespace Ink_Canvas
|
||||
if (isEnteredSlideShowEndEvent) return;
|
||||
isEnteredSlideShowEndEvent = true;
|
||||
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
}
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
@@ -1135,19 +1102,13 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
StrokeCollection strokes = null;
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
strokes = _singlePPTInkManager?.LoadSlideStrokes(slideIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
strokes = _multiPPTInkManager?.LoadSlideStrokes(slideIndex);
|
||||
}
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
if (strokes != null)
|
||||
StrokeCollection strokes = _singlePPTInkManager?.LoadSlideStrokes(slideIndex);
|
||||
|
||||
if (strokes != null && strokes.Count > 0)
|
||||
{
|
||||
inkCanvas.Strokes.Clear();
|
||||
inkCanvas.Strokes.Add(strokes);
|
||||
}
|
||||
}
|
||||
@@ -1164,19 +1125,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.ResetLockState();
|
||||
}
|
||||
else
|
||||
{
|
||||
var activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
if (activePresentation != null)
|
||||
{
|
||||
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
|
||||
_multiPPTInkManager?.ResetCurrentPresentationLockState();
|
||||
}
|
||||
}
|
||||
_singlePPTInkManager?.ResetLockState();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1278,7 +1227,6 @@ namespace Ink_Canvas
|
||||
// 获取当前页面索引
|
||||
var currentSlideIndex = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
|
||||
|
||||
// 验证页面索引的有效性
|
||||
if (newSlideIndex <= 0)
|
||||
{
|
||||
@@ -1289,48 +1237,23 @@ namespace Ink_Canvas
|
||||
// 如果有当前墨迹且不是第一次切换,先保存到当前页面
|
||||
if (inkCanvas.Strokes.Count > 0 && currentSlideIndex > 0 && currentSlideIndex != newSlideIndex)
|
||||
{
|
||||
bool canWrite = false;
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
canWrite = _singlePPTInkManager?.CanWriteInk(currentSlideIndex) == true;
|
||||
}
|
||||
else
|
||||
{
|
||||
canWrite = _multiPPTInkManager?.CanWriteInk(currentSlideIndex) == true;
|
||||
}
|
||||
bool canWrite = _singlePPTInkManager?.CanWriteInk(currentSlideIndex) == true;
|
||||
|
||||
if (canWrite)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
|
||||
}
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
|
||||
}
|
||||
}
|
||||
else if (inkCanvas.Strokes.Count > 0 && currentSlideIndex <= 0)
|
||||
{
|
||||
}
|
||||
|
||||
StrokeCollection newStrokes = null;
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
StrokeCollection newStrokes = _singlePPTInkManager?.SwitchToSlide(newSlideIndex, null);
|
||||
|
||||
if (newStrokes != null && newStrokes.Count > 0)
|
||||
{
|
||||
newStrokes = _singlePPTInkManager?.SwitchToSlide(newSlideIndex, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
newStrokes = _multiPPTInkManager?.SwitchToSlide(newSlideIndex, null);
|
||||
}
|
||||
if (newStrokes != null)
|
||||
{
|
||||
inkCanvas.Strokes.Clear();
|
||||
inkCanvas.Strokes.Add(newStrokes);
|
||||
}
|
||||
|
||||
// 注意:LockInkForSlide已经在SwitchToSlide中调用,这里不需要重复调用
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1464,14 +1387,7 @@ namespace Ink_Canvas
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
if (currentSlide > 0)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
|
||||
// 保存截图(如果启用)
|
||||
@@ -1511,14 +1427,7 @@ namespace Ink_Canvas
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
if (currentSlide > 0)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
|
||||
// 保存截图(如果启用)
|
||||
@@ -1678,14 +1587,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Forms;
|
||||
@@ -78,7 +79,7 @@ namespace Ink_Canvas
|
||||
|
||||
for (int i = 1; i <= totalSlides; i++)
|
||||
{
|
||||
var slideStrokes = _multiPPTInkManager?.LoadSlideStrokes(i);
|
||||
var slideStrokes = _singlePPTInkManager?.LoadSlideStrokes(i);
|
||||
if (slideStrokes != null && slideStrokes.Count > 0)
|
||||
{
|
||||
allPageStrokes.Add(slideStrokes);
|
||||
@@ -132,6 +133,23 @@ namespace Ink_Canvas
|
||||
var fs = new FileStream(savePathWithName, FileMode.Create);
|
||||
inkCanvas.Strokes.Save(fs);
|
||||
fs.Close();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(savePathWithName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
// 保存元素信息
|
||||
var elementInfos = new List<CanvasElementInfo>();
|
||||
foreach (var child in inkCanvas.Children)
|
||||
@@ -228,6 +246,24 @@ namespace Ink_Canvas
|
||||
// 使用System.IO.Compression.FileSystem来创建ZIP
|
||||
ZipFile.CreateFromDirectory(tempDir, zipFileName);
|
||||
|
||||
// 异步上传ZIP文件到Dlass
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(zipFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
if (newNotice) ShowNotification($"多页面墨迹成功保存至压缩包 {zipFileName}");
|
||||
}
|
||||
finally
|
||||
@@ -310,6 +346,23 @@ namespace Ink_Canvas
|
||||
var fs = new FileStream(savePathWithName, FileMode.Create);
|
||||
inkCanvas.Strokes.Save(fs);
|
||||
fs.Close();
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(imagePathWithName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +581,7 @@ namespace Ink_Canvas
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
// 重置PPT墨迹存储
|
||||
_multiPPTInkManager?.ClearAllStrokes();
|
||||
_singlePPTInkManager?.ClearAllStrokes();
|
||||
|
||||
// 读取所有页面的墨迹文件
|
||||
var files = Directory.GetFiles(tempDir, "page_*.icstk");
|
||||
@@ -542,7 +595,7 @@ namespace Ink_Canvas
|
||||
var strokes = new StrokeCollection(fs);
|
||||
if (strokes.Count > 0)
|
||||
{
|
||||
_multiPPTInkManager?.ForceSaveSlideStrokes(pageNumber, strokes);
|
||||
_singlePPTInkManager?.ForceSaveSlideStrokes(pageNumber, strokes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,7 +605,7 @@ namespace Ink_Canvas
|
||||
if (_pptManager?.IsInSlideShow == true)
|
||||
{
|
||||
int currentSlide = _pptManager.GetCurrentSlideNumber();
|
||||
var currentStrokes = _multiPPTInkManager?.LoadSlideStrokes(currentSlide);
|
||||
var currentStrokes = _singlePPTInkManager?.LoadSlideStrokes(currentSlide);
|
||||
if (currentStrokes != null && currentStrokes.Count > 0)
|
||||
{
|
||||
inkCanvas.Strokes.Add(currentStrokes);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -65,6 +66,22 @@ namespace Ink_Canvas
|
||||
{
|
||||
ShowNotification($"截图成功保存至 {savePath}");
|
||||
}
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(savePath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取日期文件夹路径
|
||||
|
||||
@@ -1846,6 +1846,62 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void BtnDlassSettingsManage_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isOpeningOrHidingSettingsPane) return;
|
||||
HideSubPanels();
|
||||
try
|
||||
{
|
||||
// 检查是否是第一次打开(检查用户是否已设置Token)
|
||||
bool hasToken = !string.IsNullOrEmpty(Settings?.Dlass?.UserToken?.Trim());
|
||||
bool isFirstTime = !hasToken;
|
||||
|
||||
if (isFirstTime)
|
||||
{
|
||||
// 第一次打开,询问用户是否已注册
|
||||
var result = MessageBox.Show(
|
||||
"您是否已经注册了Dlass账号?\n\n" +
|
||||
"• 如果已注册:将直接打开设置管理页面\n" +
|
||||
"• 如果未注册:将打开浏览器跳转到注册页面",
|
||||
"Dlass账号注册",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.No)
|
||||
{
|
||||
// 用户未注册,打开浏览器
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "https://dlass.tech/dashboard",
|
||||
UseShellExecute = true
|
||||
});
|
||||
LogHelper.WriteLogToFile("已打开浏览器跳转到Dlass注册页面", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开浏览器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"无法打开浏览器。请手动访问: https://dlass.tech/dashboard",
|
||||
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
return; // 不打开设置窗口
|
||||
}
|
||||
// 如果用户选择"是",继续打开设置窗口
|
||||
}
|
||||
|
||||
// 打开设置管理窗口
|
||||
var dlassSettingsWindow = new Windows.DlassSettingsWindow();
|
||||
dlassSettingsWindow.Owner = this;
|
||||
dlassSettingsWindow.ShowDialog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开Dlass设置管理窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"打开Dlass设置管理窗口时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSwitchAutoDelSavedFiles_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1877,6 +1933,29 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableAutoSaveStrokes_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Automation.IsEnableAutoSaveStrokes = ToggleSwitchEnableAutoSaveStrokes.IsOn;
|
||||
SaveSettingsToFile();
|
||||
// 更新定时器状态
|
||||
UpdateAutoSaveStrokesTimer();
|
||||
}
|
||||
|
||||
private void ComboBoxAutoSaveStrokesInterval_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded || ComboBoxAutoSaveStrokesInterval.SelectedItem == null) return;
|
||||
|
||||
var selectedItem = ComboBoxAutoSaveStrokesInterval.SelectedItem as System.Windows.Controls.ComboBoxItem;
|
||||
if (selectedItem?.Tag != null && int.TryParse(selectedItem.Tag.ToString(), out int intervalMinutes))
|
||||
{
|
||||
Settings.Automation.AutoSaveStrokesIntervalMinutes = intervalMinutes;
|
||||
SaveSettingsToFile();
|
||||
// 更新定时器间隔
|
||||
UpdateAutoSaveStrokesTimer();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Gesture
|
||||
@@ -2649,8 +2728,8 @@ namespace Ink_Canvas
|
||||
|
||||
if (ToggleSwitchEnableOvertimeRedText.IsOn && !ToggleSwitchEnableOvertimeCountUp.IsOn)
|
||||
{
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = false;
|
||||
return;
|
||||
ToggleSwitchEnableOvertimeCountUp.IsOn = true;
|
||||
Settings.RandSettings.EnableOvertimeCountUp = true;
|
||||
}
|
||||
|
||||
Settings.RandSettings.EnableOvertimeRedText = ToggleSwitchEnableOvertimeRedText.IsOn;
|
||||
@@ -2671,6 +2750,35 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 新点名UI设置事件处理
|
||||
private void ToggleSwitchUseNewRollCallUI_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.UseNewRollCallUI = ToggleSwitchUseNewRollCallUI.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableMLAvoidance_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.EnableMLAvoidance = ToggleSwitchEnableMLAvoidance.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void MLAvoidanceHistorySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.MLAvoidanceHistoryCount = (int)MLAvoidanceHistorySlider.Value;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void MLAvoidanceWeightSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.MLAvoidanceWeight = MLAvoidanceWeightSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ProgressiveReminderVolumeSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2740,6 +2848,20 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableQuickDraw_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 获取开关状态并保存到设置中
|
||||
Settings.RandSettings.EnableQuickDraw = ToggleSwitchEnableQuickDraw.IsOn;
|
||||
|
||||
// 保存设置到文件
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 根据设置状态显示或隐藏快抽悬浮按钮
|
||||
ShowQuickDrawFloatingButton();
|
||||
}
|
||||
|
||||
private void ToggleSwitchExternalCaller_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
@@ -592,7 +592,7 @@ namespace Ink_Canvas
|
||||
ToggleSwitchEnableTwoFingerTranslate.IsOn = false;
|
||||
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = false;
|
||||
Settings.Gesture.IsEnableTwoFingerTranslate = false;
|
||||
if (!isInMultiTouchMode) ToggleSwitchEnableMultiTouchMode.IsOn = true;
|
||||
// if (!isInMultiTouchMode) ToggleSwitchEnableMultiTouchMode.IsOn = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -876,6 +876,7 @@ namespace Ink_Canvas
|
||||
RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency;
|
||||
RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents;
|
||||
ToggleSwitchShowRandomAndSingleDraw.IsOn = Settings.RandSettings.ShowRandomAndSingleDraw;
|
||||
ToggleSwitchEnableQuickDraw.IsOn = Settings.RandSettings.EnableQuickDraw;
|
||||
ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand;
|
||||
ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType;
|
||||
RandomDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed;
|
||||
@@ -886,6 +887,12 @@ namespace Ink_Canvas
|
||||
ToggleSwitchUseNewStyleUI.IsOn = Settings.RandSettings.UseNewStyleUI;
|
||||
ToggleSwitchEnableOvertimeCountUp.IsOn = Settings.RandSettings.EnableOvertimeCountUp;
|
||||
|
||||
// 新点名UI设置
|
||||
ToggleSwitchUseNewRollCallUI.IsOn = Settings.RandSettings.UseNewRollCallUI;
|
||||
ToggleSwitchEnableMLAvoidance.IsOn = Settings.RandSettings.EnableMLAvoidance;
|
||||
MLAvoidanceHistorySlider.Value = Settings.RandSettings.MLAvoidanceHistoryCount;
|
||||
MLAvoidanceWeightSlider.Value = Settings.RandSettings.MLAvoidanceWeight;
|
||||
|
||||
bool canEnableRedText = Settings.RandSettings.EnableOvertimeCountUp && Settings.RandSettings.EnableOvertimeRedText;
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = canEnableRedText;
|
||||
if (!canEnableRedText)
|
||||
@@ -915,6 +922,7 @@ namespace Ink_Canvas
|
||||
ToggleSwitchDisplayRandWindowNamesInputBtn.IsOn = Settings.RandSettings.DisplayRandWindowNamesInputBtn;
|
||||
RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency;
|
||||
RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents;
|
||||
ToggleSwitchEnableQuickDraw.IsOn = Settings.RandSettings.EnableQuickDraw;
|
||||
ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand;
|
||||
ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType;
|
||||
ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI;
|
||||
@@ -1045,6 +1053,23 @@ namespace Ink_Canvas
|
||||
|
||||
ToggleSwitchSaveFullPageStrokes.IsOn = Settings.Automation.IsSaveFullPageStrokes;
|
||||
|
||||
// 加载定时保存墨迹设置
|
||||
ToggleSwitchEnableAutoSaveStrokes.IsOn = Settings.Automation.IsEnableAutoSaveStrokes;
|
||||
// 初始化保存间隔下拉框
|
||||
if (ComboBoxAutoSaveStrokesInterval != null)
|
||||
{
|
||||
int intervalMinutes = Settings.Automation.AutoSaveStrokesIntervalMinutes;
|
||||
if (intervalMinutes < 1) intervalMinutes = 5; // 默认5分钟
|
||||
foreach (System.Windows.Controls.ComboBoxItem item in ComboBoxAutoSaveStrokesInterval.Items)
|
||||
{
|
||||
if (item.Tag != null && int.TryParse(item.Tag.ToString(), out int tagValue) && tagValue == intervalMinutes)
|
||||
{
|
||||
ComboBoxAutoSaveStrokesInterval.SelectedItem = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SideControlMinimumAutomationSlider.Value = Settings.Automation.MinimumAutomationStrokeNumber;
|
||||
|
||||
AutoSavedStrokesLocation.Text = Settings.Automation.AutoSavedStrokesLocation;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -2143,4 +2143,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,39 +236,30 @@ namespace Ink_Canvas
|
||||
// 检查是否启用了直线自动拉直功能
|
||||
if (Settings.Canvas.AutoStraightenLine && IsPotentialStraightLine(e.Stroke))
|
||||
{
|
||||
// Get start and end points of the stroke
|
||||
Point startPoint = e.Stroke.StylusPoints[0].ToPoint();
|
||||
Point endPoint = e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint();
|
||||
Point endpoint1, endpoint2;
|
||||
bool shouldStraighten = TryGetStraightLineEndpoints(e.Stroke, out endpoint1, out endpoint2);
|
||||
|
||||
// 先完成所有直线判定,再考虑端点吸附
|
||||
// 读取实际的灵敏度设置值
|
||||
double sensitivity = Settings.InkToShape.LineStraightenSensitivity;
|
||||
Debug.WriteLine($"当前灵敏度值: {sensitivity}");
|
||||
|
||||
// 判断是否应该拉直线条
|
||||
bool shouldStraighten = ShouldStraightenLine(e.Stroke);
|
||||
|
||||
// 输出一些调试信息,帮助理解灵敏度设置的效果
|
||||
Debug.WriteLine($"LineStraightenSensitivity: {Settings.InkToShape.LineStraightenSensitivity}, ShouldStraighten: {shouldStraighten}");
|
||||
|
||||
// 只有当确定要拉直线条时,才检查端点吸附
|
||||
if (shouldStraighten && Settings.Canvas.LineEndpointSnapping)
|
||||
{
|
||||
// 只有在启用了形状识别(矩形或三角形)时才执行端点吸附
|
||||
if (Settings.InkToShape.IsInkToShapeRectangle || Settings.InkToShape.IsInkToShapeTriangle)
|
||||
{
|
||||
Point[] snappedPoints = GetSnappedEndpoints(startPoint, endPoint);
|
||||
if (snappedPoints != null)
|
||||
{
|
||||
startPoint = snappedPoints[0];
|
||||
endPoint = snappedPoints[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果确定要拉直,则创建直线
|
||||
if (shouldStraighten)
|
||||
{
|
||||
Point startPoint = endpoint1;
|
||||
Point endPoint = endpoint2;
|
||||
|
||||
// 只有当确定要拉直线条时,才检查端点吸附
|
||||
if (Settings.Canvas.LineEndpointSnapping)
|
||||
{
|
||||
// 只有在启用了形状识别(矩形或三角形)时才执行端点吸附
|
||||
if (Settings.InkToShape.IsInkToShapeRectangle || Settings.InkToShape.IsInkToShapeTriangle)
|
||||
{
|
||||
Point[] snappedPoints = GetSnappedEndpoints(startPoint, endPoint);
|
||||
if (snappedPoints != null)
|
||||
{
|
||||
startPoint = snappedPoints[0];
|
||||
endPoint = snappedPoints[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建直线
|
||||
StylusPointCollection straightLinePoints = CreateStraightLine(startPoint, endPoint);
|
||||
Stroke straightStroke = new Stroke(straightLinePoints)
|
||||
{
|
||||
@@ -857,7 +848,7 @@ namespace Ink_Canvas
|
||||
// 获取用户设置的灵敏度值,确保使用正确的设置
|
||||
double sensitivity = Settings.InkToShape.LineStraightenSensitivity;
|
||||
|
||||
// 输出当前灵敏度值(调试用)
|
||||
// 输出当前灵敏度值
|
||||
Debug.WriteLine($"IsPotentialStraightLine - sensitivity: {sensitivity}, length: {lineLength}");
|
||||
|
||||
// 将灵敏度转换为阈值:灵敏度0.05-2.0映射到阈值0.01-0.4
|
||||
@@ -884,11 +875,9 @@ namespace Ink_Canvas
|
||||
// 使用相对偏差:偏差与线长的比例,并使用灵敏度进行调整
|
||||
double quickRelativeThreshold = lineLength * quickThreshold;
|
||||
|
||||
// 记录检测到的偏差(调试用)
|
||||
// 记录检测到的偏差
|
||||
Debug.WriteLine($"Deviations: q={quarterDeviation}, m={midDeviation}, tq={threeQuarterDeviation}, threshold={quickRelativeThreshold}");
|
||||
|
||||
// 修复后的逻辑:灵敏度越大,容许的偏差越大
|
||||
// 如果任一点偏离太大,直接排除(使用统一的判断标准)
|
||||
if (quarterDeviation > quickRelativeThreshold ||
|
||||
midDeviation > quickRelativeThreshold ||
|
||||
threeQuarterDeviation > quickRelativeThreshold)
|
||||
@@ -901,7 +890,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查墨迹是否为复杂形状(如一团墨迹、涂鸦等)
|
||||
/// 检查墨迹是否为复杂形状
|
||||
/// </summary>
|
||||
private bool IsComplexShape(Stroke stroke)
|
||||
{
|
||||
@@ -1177,291 +1166,144 @@ namespace Ink_Canvas
|
||||
lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength;
|
||||
}
|
||||
|
||||
// New method: Determines if a stroke should be straightened into a line
|
||||
private bool TryGetStraightLineEndpoints(Stroke stroke, out Point endpoint1, out Point endpoint2)
|
||||
{
|
||||
endpoint1 = new Point();
|
||||
endpoint2 = new Point();
|
||||
|
||||
var points = stroke.StylusPoints.Select(p => p.ToPoint()).ToList();
|
||||
if (points.Count < 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用总最小二乘法(TLS/PCA)进行直线拟合
|
||||
int n = points.Count - 8;
|
||||
List<Point> filteredPoints = new List<Point>();
|
||||
|
||||
// 收集过滤后的点(跳过前 4 个和后 4 个点,用于计算直线方向)
|
||||
for (int i = 4; i < n + 4; i++)
|
||||
{
|
||||
filteredPoints.Add(points[i]);
|
||||
}
|
||||
|
||||
// 计算中心点(使用过滤后的点)
|
||||
double centerX = 0, centerY = 0;
|
||||
foreach (Point p in filteredPoints)
|
||||
{
|
||||
centerX += p.X;
|
||||
centerY += p.Y;
|
||||
}
|
||||
centerX /= filteredPoints.Count;
|
||||
centerY /= filteredPoints.Count;
|
||||
|
||||
// 计算协方差矩阵(使用过滤后的点)
|
||||
double covXX = 0, covYY = 0, covXY = 0;
|
||||
foreach (Point p in filteredPoints)
|
||||
{
|
||||
double dx = p.X - centerX;
|
||||
double dy = p.Y - centerY;
|
||||
covXX += dx * dx;
|
||||
covYY += dy * dy;
|
||||
covXY += dx * dy;
|
||||
}
|
||||
|
||||
// 计算特征值和特征向量
|
||||
double trace = covXX + covYY;
|
||||
double determinant = covXX * covYY - covXY * covXY;
|
||||
double discriminant = Math.Sqrt(trace * trace - 4 * determinant);
|
||||
|
||||
double eigenvalue1 = (trace + discriminant) / 2;
|
||||
double eigenvalue2 = (trace - discriminant) / 2;
|
||||
|
||||
// 最大特征值对应的特征向量即为直线方向
|
||||
double directionX, directionY;
|
||||
if (Math.Abs(covXY) > 1e-10)
|
||||
{
|
||||
directionX = covXY;
|
||||
directionY = eigenvalue1 - covXX;
|
||||
// 归一化
|
||||
double length = Math.Sqrt(directionX * directionX + directionY * directionY);
|
||||
directionX /= length;
|
||||
directionY /= length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果协方差为 0,则是水平或垂直直线
|
||||
directionX = (covXX >= covYY) ? 1 : 0;
|
||||
directionY = (covXX >= covYY) ? 0 : 1;
|
||||
}
|
||||
|
||||
// 计算解释方差比例(拟合优度)
|
||||
double totalVariance = eigenvalue1 + eigenvalue2;
|
||||
double explainedVarianceRatio = (totalVariance > 1e-10) ?
|
||||
Math.Max(eigenvalue1, eigenvalue2) / totalVariance : 1d;
|
||||
|
||||
// 使用所有点计算端点
|
||||
double minProjection = double.MaxValue;
|
||||
double maxProjection = double.MinValue;
|
||||
|
||||
// 计算所有点在直线方向上的投影
|
||||
foreach (Point p in points)
|
||||
{
|
||||
// 相对于过滤点中心的投影
|
||||
double projection = (p.X - centerX) * directionX + (p.Y - centerY) * directionY;
|
||||
minProjection = Math.Min(minProjection, projection);
|
||||
maxProjection = Math.Max(maxProjection, projection);
|
||||
}
|
||||
|
||||
// 计算端点坐标
|
||||
endpoint1 = new Point(
|
||||
centerX + minProjection * directionX,
|
||||
centerY + minProjection * directionY
|
||||
);
|
||||
|
||||
endpoint2 = new Point(
|
||||
centerX + maxProjection * directionX,
|
||||
centerY + maxProjection * directionY
|
||||
);
|
||||
|
||||
// 使用解释方差比例作为判断条件
|
||||
double threshold = 0.998 + Settings.InkToShape.LineNormalizationThreshold / 500;
|
||||
return explainedVarianceRatio > threshold;
|
||||
}
|
||||
|
||||
// New method: Determines if a stroke should be straightened into a line
|
||||
private bool ShouldStraightenLine(Stroke stroke)
|
||||
{
|
||||
// 分辨率自适应阈值
|
||||
Point start = stroke.StylusPoints.First().ToPoint();
|
||||
Point end = stroke.StylusPoints.Last().ToPoint();
|
||||
double maxDeviation = 0;
|
||||
double lineLength = GetDistance(start, end);
|
||||
// 分辨率自适应阈值
|
||||
double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale();
|
||||
// 如果线条太短,不进行拉直处理,使用自适应阈值
|
||||
|
||||
// 如果线条太短,不进行拉直处理
|
||||
if (lineLength < adaptiveThreshold)
|
||||
{
|
||||
Debug.WriteLine($"线条太短: {lineLength} < {adaptiveThreshold}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 新增:再次检查复杂度(双重保险)
|
||||
// 检查复杂度
|
||||
if (IsComplexShape(stroke))
|
||||
{
|
||||
Debug.WriteLine("拒绝拉直:检测到复杂形状");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 新增:检查线条的直线度评分
|
||||
double straightnessScore = CalculateStraightnessScore(stroke);
|
||||
double minStraightnessThreshold = 0.7; // 最低直线度要求
|
||||
|
||||
if (straightnessScore < minStraightnessThreshold)
|
||||
Point endpoint1, endpoint2;
|
||||
bool shouldStraighten = TryGetStraightLineEndpoints(stroke, out endpoint1, out endpoint2);
|
||||
|
||||
if (shouldStraighten)
|
||||
{
|
||||
Debug.WriteLine($"拒绝拉直:直线度评分过低 {straightnessScore:F3} < {minStraightnessThreshold}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取用户设置的灵敏度值,确保使用正确的值进行后续判断
|
||||
double sensitivity = Settings.InkToShape.LineStraightenSensitivity;
|
||||
|
||||
// 输出详细的调试信息
|
||||
Debug.WriteLine($"ShouldStraightenLine - sensitivity: {sensitivity}, length: {lineLength}");
|
||||
|
||||
// 临时:显示调试消息框
|
||||
// MessageBox.Show($"灵敏度值: {sensitivity}", "调试信息");
|
||||
|
||||
// 计算点与直线的偏差
|
||||
double totalDeviation = 0;
|
||||
int pointCount = 0;
|
||||
|
||||
// 检查是否启用了高精度直线拉直
|
||||
bool useHighPrecision = Settings.Canvas.HighPrecisionLineStraighten;
|
||||
|
||||
if (useHighPrecision)
|
||||
{
|
||||
Debug.WriteLine("使用高精度直线拉直模式");
|
||||
|
||||
// 高精度模式:每隔10像素取一个计数点
|
||||
double strokeLength = 0;
|
||||
double sampleInterval = 10.0; // 10像素间隔
|
||||
|
||||
// 计算笔画的总长度,用于后续采样
|
||||
for (int i = 1; i < stroke.StylusPoints.Count; i++)
|
||||
{
|
||||
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
|
||||
Point p2 = stroke.StylusPoints[i].ToPoint();
|
||||
strokeLength += GetDistance(p1, p2);
|
||||
}
|
||||
|
||||
// 如果笔画太短,直接使用所有点
|
||||
if (strokeLength < sampleInterval * 5)
|
||||
{
|
||||
foreach (StylusPoint sp in stroke.StylusPoints)
|
||||
{
|
||||
Point p = sp.ToPoint();
|
||||
double deviation = DistanceFromLineToPoint(start, end, p);
|
||||
maxDeviation = Math.Max(maxDeviation, deviation);
|
||||
totalDeviation += deviation;
|
||||
pointCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用等距采样点
|
||||
double currentLength = 0;
|
||||
double nextSampleAt = 0;
|
||||
|
||||
// 总是包含起点
|
||||
Point lastPoint = start;
|
||||
double deviation = DistanceFromLineToPoint(start, end, lastPoint);
|
||||
maxDeviation = Math.Max(maxDeviation, deviation);
|
||||
totalDeviation += deviation;
|
||||
pointCount++;
|
||||
|
||||
// 采样中间点
|
||||
for (int i = 1; i < stroke.StylusPoints.Count; i++)
|
||||
{
|
||||
Point currentPoint = stroke.StylusPoints[i].ToPoint();
|
||||
double segmentLength = GetDistance(lastPoint, currentPoint);
|
||||
|
||||
// 如果这段线段跨越了下一个采样点
|
||||
while (currentLength + segmentLength >= nextSampleAt)
|
||||
{
|
||||
// 计算采样点在线段上的位置
|
||||
double t = (nextSampleAt - currentLength) / segmentLength;
|
||||
Point samplePoint = new Point(
|
||||
lastPoint.X + t * (currentPoint.X - lastPoint.X),
|
||||
lastPoint.Y + t * (currentPoint.Y - lastPoint.Y)
|
||||
);
|
||||
|
||||
// 计算采样点的偏差
|
||||
deviation = DistanceFromLineToPoint(start, end, samplePoint);
|
||||
maxDeviation = Math.Max(maxDeviation, deviation);
|
||||
totalDeviation += deviation;
|
||||
pointCount++;
|
||||
|
||||
// 设置下一个采样点位置
|
||||
nextSampleAt += sampleInterval;
|
||||
|
||||
// 防止无限循环
|
||||
if (nextSampleAt > strokeLength) break;
|
||||
}
|
||||
|
||||
currentLength += segmentLength;
|
||||
lastPoint = currentPoint;
|
||||
}
|
||||
|
||||
// 总是包含终点
|
||||
deviation = DistanceFromLineToPoint(start, end, end);
|
||||
maxDeviation = Math.Max(maxDeviation, deviation);
|
||||
totalDeviation += deviation;
|
||||
pointCount++;
|
||||
}
|
||||
Debug.WriteLine($"接受拉直:判断为直线,解释方差比例满足阈值");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 原始模式:使用所有点
|
||||
foreach (StylusPoint sp in stroke.StylusPoints)
|
||||
{
|
||||
Point p = sp.ToPoint();
|
||||
double deviation = DistanceFromLineToPoint(start, end, p);
|
||||
maxDeviation = Math.Max(maxDeviation, deviation);
|
||||
totalDeviation += deviation;
|
||||
pointCount++;
|
||||
}
|
||||
Debug.WriteLine($"拒绝拉直:判断不满足直线条件");
|
||||
}
|
||||
|
||||
// 计算平均偏差
|
||||
double avgDeviation = totalDeviation / pointCount;
|
||||
|
||||
// 更详细的调试信息
|
||||
Debug.WriteLine($"Max deviation: {maxDeviation}, Avg: {avgDeviation}, Threshold: {sensitivity * lineLength}, Points: {pointCount}");
|
||||
|
||||
// 支持更广泛的灵敏度范围 (0.05-2.0)
|
||||
|
||||
// 移除特殊的高灵敏度模式,使用统一的阈值计算逻辑
|
||||
|
||||
// 检查点分布的一致性 - 如果有些点偏离很大而其他点很接近直线,表明线条有明显弯曲
|
||||
double deviationVariance = 0;
|
||||
|
||||
// 使用相同的高精度/原始模式来计算方差
|
||||
if (useHighPrecision)
|
||||
{
|
||||
// 高精度模式:重新采样计算方差
|
||||
double strokeLength = 0;
|
||||
double sampleInterval = 10.0; // 10像素间隔
|
||||
|
||||
// 计算笔画的总长度,用于后续采样
|
||||
for (int i = 1; i < stroke.StylusPoints.Count; i++)
|
||||
{
|
||||
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
|
||||
Point p2 = stroke.StylusPoints[i].ToPoint();
|
||||
strokeLength += GetDistance(p1, p2);
|
||||
}
|
||||
|
||||
// 如果笔画太短,直接使用所有点
|
||||
if (strokeLength < sampleInterval * 5)
|
||||
{
|
||||
foreach (StylusPoint sp in stroke.StylusPoints)
|
||||
{
|
||||
Point p = sp.ToPoint();
|
||||
double deviation = DistanceFromLineToPoint(start, end, p);
|
||||
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用等距采样点
|
||||
double currentLength = 0;
|
||||
double nextSampleAt = 0;
|
||||
Point lastPoint = start;
|
||||
|
||||
// 起点方差
|
||||
double deviation = DistanceFromLineToPoint(start, end, lastPoint);
|
||||
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
|
||||
|
||||
// 采样中间点
|
||||
for (int i = 1; i < stroke.StylusPoints.Count; i++)
|
||||
{
|
||||
Point currentPoint = stroke.StylusPoints[i].ToPoint();
|
||||
double segmentLength = GetDistance(lastPoint, currentPoint);
|
||||
|
||||
// 如果这段线段跨越了下一个采样点
|
||||
while (currentLength + segmentLength >= nextSampleAt)
|
||||
{
|
||||
// 计算采样点在线段上的位置
|
||||
double t = (nextSampleAt - currentLength) / segmentLength;
|
||||
Point samplePoint = new Point(
|
||||
lastPoint.X + t * (currentPoint.X - lastPoint.X),
|
||||
lastPoint.Y + t * (currentPoint.Y - lastPoint.Y)
|
||||
);
|
||||
|
||||
// 计算采样点的方差
|
||||
deviation = DistanceFromLineToPoint(start, end, samplePoint);
|
||||
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
|
||||
|
||||
// 设置下一个采样点位置
|
||||
nextSampleAt += sampleInterval;
|
||||
|
||||
// 防止无限循环
|
||||
if (nextSampleAt > strokeLength) break;
|
||||
}
|
||||
|
||||
currentLength += segmentLength;
|
||||
lastPoint = currentPoint;
|
||||
}
|
||||
|
||||
// 终点方差
|
||||
deviation = DistanceFromLineToPoint(start, end, end);
|
||||
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 原始模式:使用所有点计算方差
|
||||
foreach (StylusPoint sp in stroke.StylusPoints)
|
||||
{
|
||||
Point p = sp.ToPoint();
|
||||
double deviation = DistanceFromLineToPoint(start, end, p);
|
||||
deviationVariance += Math.Pow(deviation - avgDeviation, 2);
|
||||
}
|
||||
}
|
||||
|
||||
deviationVariance /= pointCount;
|
||||
|
||||
// 输出更多调试信息
|
||||
Debug.WriteLine($"Deviation variance: {deviationVariance}, Threshold: {sensitivity * lineLength * 0.05}");
|
||||
|
||||
// 修复灵敏度逻辑:灵敏度越大,容许的偏差越大,更容易将线条识别为直线
|
||||
// 将灵敏度转换为阈值:灵敏度0.05-1.0映射到阈值0.01-0.2
|
||||
double threshold = Math.Max(0.01, sensitivity * 0.2); // 确保最小阈值为0.01
|
||||
|
||||
if ((maxDeviation / lineLength) > threshold)
|
||||
{
|
||||
Debug.WriteLine($"拒绝拉直:最大偏差过大 {maxDeviation / lineLength:F3} > {threshold:F3}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果偏差方差大,说明线条弯曲不均匀
|
||||
// 灵敏度越大,容许的偏差方差越大
|
||||
double varianceThreshold = threshold * lineLength * 0.25; // 调整方差阈值比例
|
||||
if (deviationVariance > varianceThreshold)
|
||||
{
|
||||
Debug.WriteLine($"拒绝拉直:偏差方差过大 {deviationVariance:F3} > {varianceThreshold:F3}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查中点偏离情况 - 针对弧形线条特别有效
|
||||
if (stroke.StylusPoints.Count > 10)
|
||||
{
|
||||
int midIndex = stroke.StylusPoints.Count / 2;
|
||||
Point midPoint = stroke.StylusPoints[midIndex].ToPoint();
|
||||
double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
|
||||
|
||||
// 输出中点偏差信息
|
||||
double midThreshold = lineLength * threshold * 0.8;
|
||||
Debug.WriteLine($"Mid deviation: {midDeviation:F3}, Threshold: {midThreshold:F3}");
|
||||
|
||||
// 如果中点偏离过大,不拉直
|
||||
// 使用调整后的阈值,灵敏度越大,容许的中点偏离越大
|
||||
if (midDeviation > midThreshold)
|
||||
{
|
||||
Debug.WriteLine($"拒绝拉直:中点偏差过大 {midDeviation:F3} > {midThreshold:F3}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine($"接受拉直:直线度评分 = {straightnessScore:F3}");
|
||||
return true;
|
||||
return shouldStraighten;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -148,6 +149,55 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"程序启动时NTP同步失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化定时保存墨迹定时器
|
||||
InitAutoSaveStrokesTimer();
|
||||
}
|
||||
|
||||
// 初始化定时保存墨迹定时器
|
||||
private void InitAutoSaveStrokesTimer()
|
||||
{
|
||||
if (autoSaveStrokesTimer == null)
|
||||
{
|
||||
autoSaveStrokesTimer = new DispatcherTimer();
|
||||
autoSaveStrokesTimer.Tick += AutoSaveStrokesTimer_Tick;
|
||||
}
|
||||
|
||||
// 根据设置更新定时器间隔和启动状态
|
||||
UpdateAutoSaveStrokesTimer();
|
||||
}
|
||||
|
||||
// 更新定时保存墨迹定时器状态
|
||||
private void UpdateAutoSaveStrokesTimer()
|
||||
{
|
||||
if (autoSaveStrokesTimer == null) return;
|
||||
|
||||
autoSaveStrokesTimer.Stop();
|
||||
|
||||
if (Settings.Automation.IsEnableAutoSaveStrokes)
|
||||
{
|
||||
int intervalMinutes = Settings.Automation.AutoSaveStrokesIntervalMinutes;
|
||||
if (intervalMinutes < 1) intervalMinutes = 1; // 最小间隔1分钟
|
||||
autoSaveStrokesTimer.Interval = TimeSpan.FromMinutes(intervalMinutes);
|
||||
autoSaveStrokesTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// 定时保存墨迹定时器事件处理
|
||||
private void AutoSaveStrokesTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有在画布可见且有墨迹时才保存
|
||||
if (inkCanvas.Visibility == Visibility.Visible && inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
// 静默保存
|
||||
SaveInkCanvasStrokes(false, false);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// NTP同步定时器事件处理
|
||||
|
||||
@@ -391,12 +391,20 @@ namespace Ink_Canvas
|
||||
{
|
||||
var stroke = GetStrokeVisual(e.StylusDevice.Id).Stroke;
|
||||
|
||||
inkCanvas.Strokes.Add(stroke);
|
||||
await Task.Delay(5);
|
||||
inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id));
|
||||
if (stroke != null)
|
||||
{
|
||||
inkCanvas.Strokes.Add(stroke);
|
||||
await Task.Delay(5);
|
||||
inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id));
|
||||
|
||||
inkCanvas_StrokeCollected(inkCanvas,
|
||||
new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||
inkCanvas_StrokeCollected(inkCanvas,
|
||||
new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(5);
|
||||
inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -799,58 +807,6 @@ namespace Ink_Canvas
|
||||
isMultiTouchTimerActive = false;
|
||||
}
|
||||
|
||||
|
||||
// 当手掌擦激活且所有触摸点都抬起时,恢复原编辑模式
|
||||
if (isPalmEraserActive && dec.Count == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Palm eraser recovery triggered - Touch points remaining: {dec.Count}");
|
||||
|
||||
// 恢复高光状态
|
||||
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
|
||||
|
||||
// 恢复编辑模式
|
||||
try
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
// 根据之前的状态恢复
|
||||
switch (palmEraserLastEditingMode)
|
||||
{
|
||||
case InkCanvasEditingMode.Ink:
|
||||
PenIcon_Click(null, null);
|
||||
break;
|
||||
case InkCanvasEditingMode.Select:
|
||||
SymbolIconSelect_MouseUp(null, null);
|
||||
break;
|
||||
default:
|
||||
inkCanvas.EditingMode = palmEraserLastEditingMode;
|
||||
break;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"Palm eraser recovered to mode: {palmEraserLastEditingMode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 如果恢复失败,强制切换到批注模式
|
||||
LogHelper.WriteLogToFile($"Palm eraser recovery failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 重置手掌擦状态
|
||||
isPalmEraserActive = false;
|
||||
|
||||
// 禁用橡皮擦覆盖层
|
||||
DisableEraserOverlay();
|
||||
if (Settings.Canvas.IsShowCursor)
|
||||
{
|
||||
inkCanvas.ForceCursor = true;
|
||||
inkCanvas.UseCustomCursor = true;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("Palm eraser state reset completed");
|
||||
}
|
||||
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
isTouchDown = false;
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.15.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.15.0")]
|
||||
[assembly: AssemblyVersion("1.7.18.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.0")]
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- 新点名窗口主题资源 -->
|
||||
|
||||
<!-- 浅色主题资源 -->
|
||||
<SolidColorBrush x:Key="NewRollCallWindowBackgroundLight" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowBorderBrushLight" Color="#E4E4E7"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowTitleForegroundLight" Color="#18181B"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowDigitForegroundLight" Color="#18181B"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowButtonBackgroundLight" Color="#F4F4F5"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowButtonForegroundLight" Color="#18181B"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowPrimaryButtonBackgroundLight" Color="#4CAF50"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowPrimaryButtonForegroundLight" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowSecondaryTextForegroundLight" Color="#71717A"/>
|
||||
|
||||
<!-- 深色主题资源 -->
|
||||
<SolidColorBrush x:Key="NewRollCallWindowBackgroundDark" Color="#1f1f1f"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowBorderBrushDark" Color="#E0E0E0"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowTitleForegroundDark" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowDigitForegroundDark" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowButtonBackgroundDark" Color="#2a2a2a"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowButtonForegroundDark" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowPrimaryButtonBackgroundDark" Color="#4CAF50"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowPrimaryButtonForegroundDark" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowSecondaryTextForegroundDark" Color="#9ca3af"/>
|
||||
|
||||
<!-- 默认资源绑定 -->
|
||||
<SolidColorBrush x:Key="NewRollCallWindowBackground" Color="#1f1f1f"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowBorderBrush" Color="#E0E0E0"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowTitleForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowDigitForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowButtonBackground" Color="#2a2a2a"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowButtonForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowPrimaryButtonBackground" Color="#4CAF50"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowPrimaryButtonForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="NewRollCallWindowSecondaryTextForeground" Color="#9ca3af"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -29,6 +29,8 @@ namespace Ink_Canvas
|
||||
public ModeSettings ModeSettings { get; set; } = new ModeSettings();
|
||||
[JsonProperty("camera")]
|
||||
public CameraSettings Camera { get; set; } = new CameraSettings();
|
||||
[JsonProperty("dlass")]
|
||||
public DlassSettings Dlass { get; set; } = new DlassSettings();
|
||||
}
|
||||
|
||||
public class Canvas
|
||||
@@ -121,7 +123,7 @@ namespace Ink_Canvas
|
||||
[JsonIgnore]
|
||||
public bool IsEnableTwoFingerGestureTranslateOrRotation => IsEnableTwoFingerTranslate || IsEnableTwoFingerRotation;
|
||||
[JsonProperty("isEnableMultiTouchMode")]
|
||||
public bool IsEnableMultiTouchMode { get; set; } = true;
|
||||
public bool IsEnableMultiTouchMode { get; set; } = false;
|
||||
[JsonProperty("isEnableTwoFingerZoom")]
|
||||
public bool IsEnableTwoFingerZoom { get; set; } = true;
|
||||
[JsonProperty("isEnableTwoFingerTranslate")]
|
||||
@@ -462,6 +464,12 @@ namespace Ink_Canvas
|
||||
[JsonProperty("isAutoEnterAnnotationAfterKillHite")]
|
||||
public bool IsAutoEnterAnnotationAfterKillHite { get; set; }
|
||||
|
||||
[JsonProperty("isEnableAutoSaveStrokes")]
|
||||
public bool IsEnableAutoSaveStrokes { get; set; } = true;
|
||||
|
||||
[JsonProperty("autoSaveStrokesIntervalMinutes")]
|
||||
public int AutoSaveStrokesIntervalMinutes { get; set; } = 5;
|
||||
|
||||
[JsonProperty("floatingWindowInterceptor")]
|
||||
public FloatingWindowInterceptorSettings FloatingWindowInterceptor { get; set; } = new FloatingWindowInterceptorSettings();
|
||||
}
|
||||
@@ -611,7 +619,9 @@ namespace Ink_Canvas
|
||||
[JsonProperty("isInkToShapeRounded")]
|
||||
public bool IsInkToShapeRounded { get; set; } = true;
|
||||
[JsonProperty("lineStraightenSensitivity")]
|
||||
public double LineStraightenSensitivity { get; set; } = 0.20; // 直线检测灵敏度,值越小越严格(0.05-2.0)
|
||||
public double LineStraightenSensitivity { get; set; } = 0.20;
|
||||
[JsonProperty("lineNormalizationThreshold")]
|
||||
public double LineNormalizationThreshold { get; set; } = 0.5;
|
||||
}
|
||||
|
||||
public class RandSettings
|
||||
@@ -650,6 +660,16 @@ namespace Ink_Canvas
|
||||
public double ProgressiveReminderVolume { get; set; } = 1.0;
|
||||
[JsonProperty("progressiveReminderSoundPath")]
|
||||
public string ProgressiveReminderSoundPath { get; set; } = "";
|
||||
[JsonProperty("useNewRollCallUI")]
|
||||
public bool UseNewRollCallUI { get; set; } = true;
|
||||
[JsonProperty("enableMLAvoidance")]
|
||||
public bool EnableMLAvoidance { get; set; } = true;
|
||||
[JsonProperty("mlAvoidanceHistoryCount")]
|
||||
public int MLAvoidanceHistoryCount { get; set; } = 50;
|
||||
[JsonProperty("mlAvoidanceWeight")]
|
||||
public double MLAvoidanceWeight { get; set; } = 1.0;
|
||||
[JsonProperty("enableQuickDraw")]
|
||||
public bool EnableQuickDraw { get; set; } = true;
|
||||
}
|
||||
|
||||
public class CustomPickNameBackground
|
||||
@@ -708,4 +728,25 @@ namespace Ink_Canvas
|
||||
[JsonProperty("selectedCameraIndex")]
|
||||
public int SelectedCameraIndex { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class DlassSettings
|
||||
{
|
||||
[JsonProperty("userToken")]
|
||||
public string UserToken { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("savedTokens")]
|
||||
public List<string> SavedTokens { get; set; } = new List<string>();
|
||||
|
||||
[JsonProperty("selectedClassName")]
|
||||
public string SelectedClassName { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("apiBaseUrl")]
|
||||
public string ApiBaseUrl { get; set; } = "https://dlass.tech";
|
||||
|
||||
[JsonProperty("isAutoUploadNotes")]
|
||||
public bool IsAutoUploadNotes { get; set; } = false;
|
||||
|
||||
[JsonProperty("autoUploadDelayMinutes")]
|
||||
public int AutoUploadDelayMinutes { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@
|
||||
<SolidColorBrush x:Key="RandWindowBackground" Color="#1f1f1f"/>
|
||||
<SolidColorBrush x:Key="RandWindowBorderBrush" Color="#0066BF"/>
|
||||
<SolidColorBrush x:Key="RandWindowTextForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="RandWindowButtonBackground" Color="#FBFBFD"/>
|
||||
<SolidColorBrush x:Key="RandWindowButtonForeground" Color="Black"/>
|
||||
<SolidColorBrush x:Key="RandWindowButtonBackground" Color="#3F3F46"/>
|
||||
<SolidColorBrush x:Key="RandWindowButtonForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="RandWindowPrimaryButtonBackground" Color="#0066BF"/>
|
||||
<SolidColorBrush x:Key="RandWindowPrimaryButtonForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="RandWindowSecondaryButtonBackground" Color="#00B894"/>
|
||||
@@ -145,4 +145,8 @@
|
||||
<BitmapImage x:Key="HandMoveIcon" UriSource="/Resources/new-icons/hand-move_white.png"/>
|
||||
<BitmapImage x:Key="ZoomIcon" UriSource="/Resources/new-icons/zoom_white.png"/>
|
||||
<BitmapImage x:Key="RotateIcon" UriSource="/Resources/new-icons/rotate_white.png"/>
|
||||
|
||||
<!-- 浮动栏手势按钮图标资源 - 深色主题 -->
|
||||
<BitmapImage x:Key="GestureIcon" UriSource="/Resources/new-icons/gesture_white.png"/>
|
||||
<BitmapImage x:Key="GestureIconEnabled" UriSource="/Resources/new-icons/gesture-enabled.png"/>
|
||||
</ResourceDictionary>
|
||||
@@ -145,4 +145,8 @@
|
||||
<BitmapImage x:Key="HandMoveIcon" UriSource="/Resources/new-icons/hand-move.png"/>
|
||||
<BitmapImage x:Key="ZoomIcon" UriSource="/Resources/new-icons/zoom.png"/>
|
||||
<BitmapImage x:Key="RotateIcon" UriSource="/Resources/new-icons/rotate.png"/>
|
||||
|
||||
<!-- 浮动栏手势按钮图标资源 - 浅色主题 -->
|
||||
<BitmapImage x:Key="GestureIcon" UriSource="/Resources/new-icons/gesture.png"/>
|
||||
<BitmapImage x:Key="GestureIconEnabled" UriSource="/Resources/new-icons/gesture-enabled.png"/>
|
||||
</ResourceDictionary>
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:processbars="clr-namespace:Ink_Canvas.ProcessBars"
|
||||
ui:ThemeManager.RequestedTheme="Light" Topmost="True" Background="Transparent"
|
||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True"
|
||||
ResizeMode="CanMinimize"
|
||||
Loaded="Window_Loaded" Closing="Window_Closing" WindowStartupLocation="CenterScreen"
|
||||
Title="Ink Canvas 画板 - 计时器" Height="700" Width="1100">
|
||||
<Border Background="{DynamicResource TimerWindowBackground}" CornerRadius="10" BorderThickness="1" BorderBrush="{DynamicResource TimerWindowBorderBrush}" Margin="60">
|
||||
|
||||
@@ -29,14 +29,7 @@ namespace Ink_Canvas
|
||||
|
||||
public static Window CreateTimerWindow()
|
||||
{
|
||||
if (MainWindow.Settings.RandSettings?.UseNewStyleUI == true)
|
||||
{
|
||||
return new NewStyleTimerWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new CountdownTimerWindow();
|
||||
}
|
||||
return new CountdownTimerWindow();
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
|
||||
@@ -0,0 +1,494 @@
|
||||
<Window x:Class="Ink_Canvas.Windows.DlassSettingsWindow"
|
||||
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"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:controls="clr-namespace:Ink_Canvas.Windows.Controls"
|
||||
mc:Ignorable="d"
|
||||
WindowStyle="None"
|
||||
Title="Dlass设置管理" Height="600" Width="900"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Controls/WinUI3CloseButton.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<SolidColorBrush x:Key="WindowBackground" Color="#1e1e1e"/>
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="#3f3f46"/>
|
||||
<SolidColorBrush x:Key="TextForeground" Color="#fafafa"/>
|
||||
<SolidColorBrush x:Key="TextSecondary" Color="#a1a1aa"/>
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#3b82f6"/>
|
||||
<SolidColorBrush x:Key="TitleForeground" Color="#fafafa"/>
|
||||
<SolidColorBrush x:Key="NewTimerWindowButtonForeground" Color="White"/>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Border Background="{StaticResource WindowBackground}"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="15"
|
||||
Margin="10"
|
||||
x:Name="MainBorder"
|
||||
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<Grid>
|
||||
<controls:WinUI3CloseButton x:Name="BtnClose"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="0,0,0,0" Cursor="Hand" Click="BtnClose_Click"
|
||||
Content="✕"/>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<Grid Grid.Row="0"
|
||||
Height="50"
|
||||
Background="{StaticResource WindowBackground}"
|
||||
x:Name="TitleBar"
|
||||
VerticalAlignment="Top"
|
||||
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown"
|
||||
Margin="0,0,46,0">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="22,0,0,0">
|
||||
<!-- 设置图标 -->
|
||||
<Path Data="M12 15.5A3.5 3.5 0 0 1 8.5 12A3.5 3.5 0 0 1 12 8.5A3.5 3.5 0 0 1 15.5 12A3.5 3.5 0 0 1 12 15.5M19.43 12.97C19.47 12.65 19.5 12.33 19.5 12C19.5 11.67 19.47 11.34 19.43 11.03L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.54 5.05 19.27 4.96 19.05 5.05L16.56 6.05C16.04 5.65 15.5 5.32 14.87 5.07L14.5 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.5 2.42L9.13 5.07C8.5 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.73 4.96 4.46 5.05 4.35 5.27L2.35 8.73C2.23 8.95 2.27 9.22 2.46 9.37L4.57 11.03C4.53 11.34 4.5 11.67 4.5 12C4.5 12.33 4.53 12.65 4.57 12.97L2.46 14.63C2.27 14.78 2.23 15.05 2.35 15.27L4.35 18.73C4.46 18.95 4.73 19.03 4.95 18.95L7.44 17.95C7.96 18.34 8.5 18.68 9.13 18.93L9.5 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.5 21.58L14.87 18.93C15.5 18.67 16.04 18.34 16.56 17.95L19.05 18.95C19.27 19.03 19.54 18.95 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.97Z"
|
||||
Stroke="{StaticResource TitleForeground}"
|
||||
StrokeThickness="1.5"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="24" Height="24"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<!-- 标题文字 -->
|
||||
<TextBlock Text="Dlass设置管理"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
Foreground="{StaticResource TitleForeground}"
|
||||
x:Name="TitleText"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{StaticResource WindowBackground}"
|
||||
Padding="20,10,20,20">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<ui:SimpleStackPanel Spacing="16">
|
||||
<!-- 内容区域 -->
|
||||
<Border BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="20"
|
||||
Background="#27272a">
|
||||
<ui:SimpleStackPanel Spacing="16">
|
||||
<TextBlock Text="Dlass设置管理"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}"/>
|
||||
|
||||
<TextBlock Text="管理您的Dlass服务端连接和设置。"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<Line HorizontalAlignment="Stretch"
|
||||
X1="0" Y1="0" X2="1" Y2="0"
|
||||
Stroke="{StaticResource BorderBrush}"
|
||||
StrokeThickness="1"
|
||||
Margin="0,2,0,2"/>
|
||||
|
||||
<!-- 用户Token设置 -->
|
||||
<TextBlock Text="用户Token"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
Margin="0,0,0,1"/>
|
||||
|
||||
<TextBlock Text="设置您的用户Token以访问Dlass服务端功能。您可以从Dlass平台获取您的用户Token。"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,2"/>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Vertical" Spacing="4">
|
||||
<ComboBox x:Name="CmbSavedTokens"
|
||||
FontSize="14"
|
||||
Padding="12,8"
|
||||
Background="#18181b"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
MinHeight="36"
|
||||
IsEditable="False"
|
||||
IsReadOnly="True"
|
||||
SelectionChanged="CmbSavedTokens_SelectionChanged">
|
||||
</ComboBox>
|
||||
|
||||
<TextBox x:Name="TxtNewToken"
|
||||
FontSize="14"
|
||||
Padding="12,4"
|
||||
Background="#18181b"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="False"
|
||||
MaxLength="500"
|
||||
MinHeight="36"
|
||||
Tag="输入新的Token">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ScrollViewer x:Name="PART_ContentHost" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button x:Name="BtnSaveToken"
|
||||
Content="保存Token"
|
||||
Padding="12,6"
|
||||
FontSize="13"
|
||||
Background="{StaticResource AccentColor}"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Cursor="Hand"
|
||||
Click="BtnSaveToken_Click">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#2563eb"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#1d4ed8"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<Button x:Name="BtnClearToken"
|
||||
Content="清除Token"
|
||||
Padding="12,6"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
Cursor="Hand"
|
||||
Click="BtnClearToken_Click">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#27272a"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<Button x:Name="BtnTestToken"
|
||||
Content="测试连接"
|
||||
Padding="12,6"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
Cursor="Hand"
|
||||
Click="BtnTestToken_Click">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#27272a"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<TextBlock x:Name="TxtTokenStatus"
|
||||
Text=""
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
Margin="0,4,0,0"/>
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<!-- 连接状态 -->
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="12" Margin="0,4,0,0">
|
||||
<TextBlock Text="连接状态:"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
VerticalAlignment="Center"
|
||||
Width="100"/>
|
||||
<TextBlock x:Name="TxtConnectionStatus"
|
||||
Text="未连接"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
VerticalAlignment="Center"/>
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<Line HorizontalAlignment="Stretch"
|
||||
X1="0" Y1="0" X2="1" Y2="0"
|
||||
Stroke="{StaticResource BorderBrush}"
|
||||
StrokeThickness="1"
|
||||
Margin="0,2,0,1"/>
|
||||
|
||||
<!-- 班级选择 -->
|
||||
<TextBlock Text="班级选择"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
Margin="0,0,0,1"/>
|
||||
|
||||
<TextBlock Text="连接成功后,将自动加载可用班级列表。"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,2"/>
|
||||
|
||||
<ComboBox x:Name="CmbClassSelection"
|
||||
FontSize="14"
|
||||
Padding="12,8"
|
||||
Background="#18181b"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
MinHeight="36"
|
||||
IsEditable="False"
|
||||
IsReadOnly="True"
|
||||
SelectionChanged="CmbClassSelection_SelectionChanged">
|
||||
</ComboBox>
|
||||
|
||||
<Line HorizontalAlignment="Stretch"
|
||||
X1="0" Y1="0" X2="1" Y2="0"
|
||||
Stroke="{StaticResource BorderBrush}"
|
||||
StrokeThickness="1"
|
||||
Margin="0,2,0,1"/>
|
||||
|
||||
<!-- 自动上传设置 -->
|
||||
<TextBlock Text="自动上传设置"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
Margin="0,0,0,1"/>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Vertical" Spacing="4">
|
||||
<ui:ToggleSwitch x:Name="ToggleSwitchAutoUploadNotes"
|
||||
Header="自动上传笔记"
|
||||
FontSize="14"
|
||||
Foreground="White"
|
||||
Toggled="ToggleSwitchAutoUploadNotes_Toggled">
|
||||
</ui:ToggleSwitch>
|
||||
|
||||
<TextBlock Text="启用后,保存的PNG截图和ICSTK墨迹文件将自动上传到所选班级的白板。"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,2"/>
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="12"
|
||||
IsEnabled="{Binding ElementName=ToggleSwitchAutoUploadNotes, Path=IsOn}">
|
||||
<TextBlock Text="上传延迟时间:"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
VerticalAlignment="Center"
|
||||
Width="120"/>
|
||||
<TextBox x:Name="TxtUploadDelayMinutes"
|
||||
FontSize="14"
|
||||
Background="#18181b"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
MinWidth="100"
|
||||
Height="32"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
TextChanged="TxtUploadDelayMinutes_TextChanged"
|
||||
PreviewTextInput="TxtUploadDelayMinutes_PreviewTextInput">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="6">
|
||||
<ScrollViewer x:Name="PART_ContentHost"
|
||||
Margin="12,0"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
<TextBlock Text="分钟"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,0"/>
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<TextBlock Text="设置上传延迟时间(0-60分钟),可以在保存后等待一段时间再上传。"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,2,0,0"/>
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<Line HorizontalAlignment="Stretch"
|
||||
X1="0" Y1="0" X2="1" Y2="0"
|
||||
Stroke="{StaticResource BorderBrush}"
|
||||
StrokeThickness="1"
|
||||
Margin="0,2,0,1"/>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="12" Margin="0,8,0,0">
|
||||
<Button Content="保存"
|
||||
Padding="16,8"
|
||||
FontSize="14"
|
||||
Background="{StaticResource AccentColor}"
|
||||
Foreground="White"
|
||||
BorderThickness="0"
|
||||
Cursor="Hand"
|
||||
Click="BtnSave_Click">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#2563eb"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#1d4ed8"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<Button Content="取消"
|
||||
Padding="16,8"
|
||||
FontSize="14"
|
||||
Background="Transparent"
|
||||
Foreground="{StaticResource TextForeground}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
Cursor="Hand"
|
||||
Click="BtnCancel_Click">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#27272a"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</ui:SimpleStackPanel>
|
||||
</ui:SimpleStackPanel>
|
||||
</Border>
|
||||
</ui:SimpleStackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
|
||||
@@ -0,0 +1,709 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
/// <summary>
|
||||
/// DlassSettingsWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class DlassSettingsWindow : Window
|
||||
{
|
||||
private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA";
|
||||
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
|
||||
|
||||
private DlassApiClient _apiClient;
|
||||
private List<WhiteboardInfo> _currentWhiteboards = new List<WhiteboardInfo>();
|
||||
private UserInfo _currentUser;
|
||||
|
||||
public DlassSettingsWindow(MainWindow mainWindow = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 初始化班级下拉框
|
||||
CmbClassSelection.Items.Clear();
|
||||
CmbClassSelection.Items.Add("(等待连接)");
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
CmbClassSelection.IsEnabled = false;
|
||||
|
||||
// 加载保存的token
|
||||
LoadUserToken();
|
||||
|
||||
// 加载自动上传设置
|
||||
LoadAutoUploadSettings();
|
||||
|
||||
// 初始化API客户端(优先使用用户token)
|
||||
InitializeApiClient();
|
||||
|
||||
// 窗口关闭时释放资源
|
||||
Closed += (s, e) => _apiClient?.Dispose();
|
||||
|
||||
// 测试连接
|
||||
_ = TestConnectionAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化API客户端
|
||||
/// </summary>
|
||||
private void InitializeApiClient()
|
||||
{
|
||||
var userToken = GetUserToken();
|
||||
var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(apiBaseUrl) || apiBaseUrl.Contains("api.dlass.tech"))
|
||||
{
|
||||
apiBaseUrl = "https://dlass.tech";
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.ApiBaseUrl = apiBaseUrl;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
_apiClient = new DlassApiClient(APP_ID, APP_SECRET, baseUrl: apiBaseUrl, userToken: userToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
_apiClient = new DlassApiClient(APP_ID, APP_SECRET, baseUrl: apiBaseUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户token
|
||||
/// </summary>
|
||||
private string GetUserToken()
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
return MainWindow.Settings.Dlass.UserToken ?? string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取保存的Token列表
|
||||
/// </summary>
|
||||
private List<string> GetSavedTokens()
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
return MainWindow.Settings.Dlass.SavedTokens ?? new List<string>();
|
||||
}
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载用户token到UI
|
||||
/// </summary>
|
||||
private void LoadUserToken()
|
||||
{
|
||||
var savedTokens = GetSavedTokens();
|
||||
var currentToken = GetUserToken();
|
||||
|
||||
CmbSavedTokens.Items.Clear();
|
||||
if (savedTokens.Count > 0)
|
||||
{
|
||||
foreach (var token in savedTokens)
|
||||
{
|
||||
CmbSavedTokens.Items.Add(token);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(currentToken))
|
||||
{
|
||||
var index = savedTokens.IndexOf(currentToken);
|
||||
if (index >= 0)
|
||||
{
|
||||
CmbSavedTokens.SelectedIndex = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
CmbSavedTokens.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
else if (CmbSavedTokens.Items.Count > 0)
|
||||
{
|
||||
CmbSavedTokens.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CmbSavedTokens.Items.Add("(无保存的Token)");
|
||||
CmbSavedTokens.SelectedIndex = 0;
|
||||
CmbSavedTokens.IsEnabled = false;
|
||||
}
|
||||
|
||||
TxtNewToken.Text = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(currentToken))
|
||||
{
|
||||
TxtTokenStatus.Text = "已选择Token";
|
||||
TxtTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94));
|
||||
}
|
||||
else
|
||||
{
|
||||
TxtTokenStatus.Text = "未设置Token";
|
||||
TxtTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(161, 161, 170));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存用户token
|
||||
/// </summary>
|
||||
private void SaveUserToken(string token)
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.UserToken = token ?? string.Empty;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加Token到保存列表
|
||||
/// </summary>
|
||||
private void AddTokenToList(string token)
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
if (MainWindow.Settings.Dlass.SavedTokens == null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.SavedTokens = new List<string>();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(token) && !MainWindow.Settings.Dlass.SavedTokens.Contains(token))
|
||||
{
|
||||
MainWindow.Settings.Dlass.SavedTokens.Add(token);
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从列表删除Token
|
||||
/// </summary>
|
||||
private void RemoveTokenFromList(string token)
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null && MainWindow.Settings.Dlass.SavedTokens != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.SavedTokens.Remove(token);
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载班级列表到下拉框
|
||||
/// </summary>
|
||||
private void LoadClasses(List<WhiteboardInfo> whiteboards, UserInfo user = null)
|
||||
{
|
||||
CmbClassSelection.Items.Clear();
|
||||
|
||||
if (whiteboards != null && whiteboards.Count > 0)
|
||||
{
|
||||
var teacherName = user?.Username ?? "未知教师";
|
||||
var classGroups = whiteboards
|
||||
.Where(w => !string.IsNullOrEmpty(w.ClassName))
|
||||
.GroupBy(w => w.ClassName)
|
||||
.OrderBy(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var group in classGroups)
|
||||
{
|
||||
var className = group.Key;
|
||||
var displayText = $"{teacherName} - {className}";
|
||||
CmbClassSelection.Items.Add(new ClassSelectionItem
|
||||
{
|
||||
DisplayText = displayText,
|
||||
ClassName = className,
|
||||
TeacherName = teacherName
|
||||
});
|
||||
}
|
||||
|
||||
var savedClassName = MainWindow.Settings?.Dlass?.SelectedClassName ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(savedClassName))
|
||||
{
|
||||
var savedItem = CmbClassSelection.Items.Cast<ClassSelectionItem>()
|
||||
.FirstOrDefault(item => item.ClassName == savedClassName);
|
||||
if (savedItem != null)
|
||||
{
|
||||
CmbClassSelection.SelectedItem = savedItem;
|
||||
}
|
||||
else if (CmbClassSelection.Items.Count > 0)
|
||||
{
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
else if (CmbClassSelection.Items.Count > 0)
|
||||
{
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
CmbClassSelection.IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CmbClassSelection.Items.Add("(无可用班级)");
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
CmbClassSelection.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 班级选择改变事件
|
||||
/// </summary>
|
||||
private void CmbClassSelection_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CmbClassSelection.SelectedItem is ClassSelectionItem selectedItem)
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.SelectedClassName = selectedItem.ClassName;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"选择班级时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载自动上传设置
|
||||
/// </summary>
|
||||
private void LoadAutoUploadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
ToggleSwitchAutoUploadNotes.IsOn = MainWindow.Settings.Dlass.IsAutoUploadNotes;
|
||||
var delayMinutes = MainWindow.Settings.Dlass.AutoUploadDelayMinutes;
|
||||
if (delayMinutes < 0 || delayMinutes > 60)
|
||||
{
|
||||
delayMinutes = 0;
|
||||
}
|
||||
TxtUploadDelayMinutes.Text = delayMinutes.ToString();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载自动上传设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动上传开关切换事件
|
||||
/// </summary>
|
||||
private void ToggleSwitchAutoUploadNotes_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.IsAutoUploadNotes = ToggleSwitchAutoUploadNotes.IsOn;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存自动上传设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传延迟时间输入框文本改变事件
|
||||
/// </summary>
|
||||
private void TxtUploadDelayMinutes_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null && int.TryParse(TxtUploadDelayMinutes.Text, out int delayMinutes))
|
||||
{
|
||||
// 限制范围在0-60分钟
|
||||
if (delayMinutes < 0)
|
||||
{
|
||||
delayMinutes = 0;
|
||||
TxtUploadDelayMinutes.Text = "0";
|
||||
}
|
||||
else if (delayMinutes > 60)
|
||||
{
|
||||
delayMinutes = 60;
|
||||
TxtUploadDelayMinutes.Text = "60";
|
||||
}
|
||||
|
||||
MainWindow.Settings.Dlass.AutoUploadDelayMinutes = delayMinutes;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(TxtUploadDelayMinutes.Text))
|
||||
{
|
||||
// 空文本时设置为0
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.AutoUploadDelayMinutes = 0;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存上传延迟时间时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传延迟时间输入框预览文本输入事件(只允许数字)
|
||||
/// </summary>
|
||||
private void TxtUploadDelayMinutes_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
Regex regex = new Regex("[^0-9]+");
|
||||
e.Handled = regex.IsMatch(e.Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标题栏拖动事件
|
||||
/// </summary>
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ButtonState == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下拉框选择改变事件
|
||||
/// </summary>
|
||||
private void CmbSavedTokens_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CmbSavedTokens.SelectedItem != null && CmbSavedTokens.SelectedItem.ToString() != "(无保存的Token)")
|
||||
{
|
||||
var selectedToken = CmbSavedTokens.SelectedItem.ToString();
|
||||
SaveUserToken(selectedToken);
|
||||
|
||||
_apiClient?.Dispose();
|
||||
InitializeApiClient();
|
||||
|
||||
TxtTokenStatus.Text = "已选择Token";
|
||||
TxtTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94));
|
||||
|
||||
_ = TestConnectionAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"选择Token时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存Token按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnSaveToken_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = TxtNewToken.Text?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
MessageBox.Show("请输入新的用户Token", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
AddTokenToList(token);
|
||||
SaveUserToken(token);
|
||||
|
||||
_apiClient?.Dispose();
|
||||
InitializeApiClient();
|
||||
|
||||
LoadUserToken();
|
||||
|
||||
MessageBox.Show("Token已成功保存并已选择", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
_ = TestConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存Token时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"保存Token时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除Token按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnClearToken_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CmbSavedTokens.SelectedItem == null || CmbSavedTokens.SelectedItem.ToString() == "(无保存的Token)")
|
||||
{
|
||||
MessageBox.Show("请先选择一个Token", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedToken = CmbSavedTokens.SelectedItem.ToString();
|
||||
var result = MessageBox.Show($"确定要删除已选中的Token吗?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
RemoveTokenFromList(selectedToken);
|
||||
|
||||
if (GetUserToken() == selectedToken)
|
||||
{
|
||||
SaveUserToken(string.Empty);
|
||||
}
|
||||
|
||||
_apiClient?.Dispose();
|
||||
InitializeApiClient();
|
||||
|
||||
LoadUserToken();
|
||||
|
||||
CmbClassSelection.Items.Clear();
|
||||
CmbClassSelection.Items.Add("(等待连接)");
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
CmbClassSelection.IsEnabled = false;
|
||||
_currentWhiteboards.Clear();
|
||||
_currentUser = null;
|
||||
|
||||
TxtConnectionStatus.Text = "未连接";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(161, 161, 170));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"删除Token时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"删除Token时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试Token连接按钮点击事件
|
||||
/// </summary>
|
||||
private async void BtnTestToken_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await TestConnectionAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存按钮点击事件
|
||||
/// </summary>
|
||||
private async void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 根据实际API文档实现保存逻辑
|
||||
// 示例:保存设置到服务器
|
||||
// var settings = new { ... };
|
||||
// await _apiClient.PostAsync<ApiResponse>("/api/settings", settings);
|
||||
|
||||
MessageBox.Show("设置已保存", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"保存设置时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试API连接
|
||||
/// </summary>
|
||||
private async Task TestConnectionAsync()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
TxtConnectionStatus.Text = "测试中...";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(161, 161, 170)); // 灰色
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var userToken = GetUserToken();
|
||||
if (string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
TxtConnectionStatus.Text = "未设置Token";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(239, 68, 68)); // 红色
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据文档,使用 auth-with-token 接口验证token
|
||||
// 此接口需要POST请求,包含app_id, app_secret和user_token
|
||||
try
|
||||
{
|
||||
var authData = new
|
||||
{
|
||||
app_id = APP_ID,
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
var result = await _apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
|
||||
|
||||
if (result != null && result.Success)
|
||||
{
|
||||
var whiteboards = result.Whiteboards ?? new List<WhiteboardInfo>();
|
||||
_currentWhiteboards = whiteboards;
|
||||
_currentUser = result.User;
|
||||
var whiteboardCount = whiteboards.Count;
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
TxtConnectionStatus.Text = $"已连接 (找到 {whiteboardCount} 个白板)";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94));
|
||||
|
||||
// 加载班级列表
|
||||
LoadClasses(whiteboards, result.User);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("认证响应失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (userToken.Length < 10)
|
||||
{
|
||||
throw new Exception("Token格式可能不正确(长度过短,至少需要10个字符)");
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"Token验证失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Dlass API连接测试失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
TxtConnectionStatus.Text = "连接失败";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(239, 68, 68));
|
||||
|
||||
// 清空班级列表
|
||||
CmbClassSelection.Items.Clear();
|
||||
CmbClassSelection.Items.Add("(无可用班级)");
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
CmbClassSelection.IsEnabled = false;
|
||||
_currentWhiteboards.Clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region API响应模型
|
||||
|
||||
/// <summary>
|
||||
/// auth-with-token接口响应模型
|
||||
/// </summary>
|
||||
public class AuthWithTokenResponse
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("whiteboards")]
|
||||
public List<WhiteboardInfo> Whiteboards { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("user")]
|
||||
public UserInfo User { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 白板信息模型
|
||||
/// </summary>
|
||||
public class WhiteboardInfo
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("board_id")]
|
||||
public string BoardId { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("secret_key")]
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("class_name")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("is_online")]
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("last_heartbeat")]
|
||||
public string LastHeartbeat { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息模型
|
||||
/// </summary>
|
||||
public class UserInfo
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("email")]
|
||||
public string Email { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 班级选择项
|
||||
/// </summary>
|
||||
public class ClassSelectionItem
|
||||
{
|
||||
public string DisplayText { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string TeacherName { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return DisplayText;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Window x:Class="Ink_Canvas.FullscreenTimerWindow"
|
||||
<Window x:Class="Ink_Canvas.Windows.FullscreenTimerWindow"
|
||||
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"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Windows"
|
||||
Topmost="True" Background="Black"
|
||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="False"
|
||||
WindowState="Maximized" WindowStartupLocation="Manual"
|
||||
|
||||
@@ -1,43 +1,115 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using System.Timers;
|
||||
|
||||
namespace Ink_Canvas
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
/// <summary>
|
||||
/// 全屏计时器窗口
|
||||
/// </summary>
|
||||
public partial class FullscreenTimerWindow : Window
|
||||
{
|
||||
private NewStyleTimerWindow parentWindow;
|
||||
private TimerControl parentControl;
|
||||
private System.Timers.Timer updateTimer;
|
||||
private Visibility previousTimerContainerVisibility = Visibility.Visible;
|
||||
|
||||
public FullscreenTimerWindow(NewStyleTimerWindow parent)
|
||||
public FullscreenTimerWindow(TimerControl parent)
|
||||
{
|
||||
InitializeComponent();
|
||||
parentWindow = parent;
|
||||
parentControl = parent;
|
||||
|
||||
// 设置窗口位置和大小
|
||||
this.Left = 0;
|
||||
this.Top = 0;
|
||||
this.Width = SystemParameters.PrimaryScreenWidth;
|
||||
this.Height = SystemParameters.PrimaryScreenHeight;
|
||||
|
||||
// 启动更新定时器
|
||||
updateTimer = new System.Timers.Timer(100);
|
||||
updateTimer.Elapsed += UpdateTimer_Elapsed;
|
||||
updateTimer.Start();
|
||||
|
||||
parentWindow.TimerCompleted += ParentWindow_TimerCompleted;
|
||||
parentControl.TimerCompleted += ParentWindow_TimerCompleted;
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
mainWindow.PauseTopmostMaintenance();
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
previousTimerContainerVisibility = timerContainer.Visibility;
|
||||
timerContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保窗口置顶
|
||||
Loaded += FullscreenTimerWindow_Loaded;
|
||||
}
|
||||
|
||||
private void FullscreenTimerWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 使用延迟确保窗口完全加载后再应用置顶
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ApplyTopmost();
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
#region Win32 API 声明和置顶管理
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
private const int WS_EX_TOPMOST = 0x00000008;
|
||||
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||
private const uint SWP_NOMOVE = 0x0002;
|
||||
private const uint SWP_NOSIZE = 0x0001;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
private const uint SWP_SHOWWINDOW = 0x0040;
|
||||
|
||||
/// <summary>
|
||||
/// 应用全屏窗口置顶
|
||||
/// </summary>
|
||||
private void ApplyTopmost()
|
||||
{
|
||||
try
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
|
||||
// 设置WPF的Topmost属性
|
||||
Topmost = true;
|
||||
|
||||
// 使用Win32 API强制置顶
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
|
||||
|
||||
// 使用SetWindowPos确保窗口在最顶层
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"应用全屏窗口置顶失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (parentWindow != null)
|
||||
if (parentControl != null)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
@@ -54,16 +126,16 @@ namespace Ink_Canvas
|
||||
|
||||
private bool ShouldCloseWindow()
|
||||
{
|
||||
if (parentWindow == null) return true;
|
||||
if (parentControl == null) return true;
|
||||
|
||||
if (MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true)
|
||||
{
|
||||
if (parentWindow.IsTimerRunning)
|
||||
if (parentControl.IsTimerRunning)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var remainingTime = parentWindow.GetRemainingTime();
|
||||
var remainingTime = parentControl.GetRemainingTime();
|
||||
if (remainingTime.HasValue && remainingTime.Value.TotalSeconds < 0)
|
||||
{
|
||||
return false;
|
||||
@@ -73,16 +145,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
return !parentWindow.IsTimerRunning;
|
||||
return !parentControl.IsTimerRunning;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimeDisplay()
|
||||
{
|
||||
if (parentWindow == null) return;
|
||||
if (parentControl == null) return;
|
||||
|
||||
// 获取剩余时间
|
||||
var remainingTime = parentWindow.GetRemainingTime();
|
||||
var remainingTime = parentControl.GetRemainingTime();
|
||||
if (remainingTime.HasValue)
|
||||
{
|
||||
var timeSpan = remainingTime.Value;
|
||||
@@ -93,10 +164,10 @@ namespace Ink_Canvas
|
||||
|
||||
if (isOvertimeMode)
|
||||
{
|
||||
var totalTimeSpan = parentWindow.GetTotalTimeSpan();
|
||||
var totalTimeSpan = parentControl.GetTotalTimeSpan();
|
||||
if (totalTimeSpan.HasValue)
|
||||
{
|
||||
var elapsedTime = parentWindow.GetElapsedTime();
|
||||
var elapsedTime = parentControl.GetElapsedTime();
|
||||
if (elapsedTime.HasValue)
|
||||
{
|
||||
var overtimeSpan = elapsedTime.Value - totalTimeSpan.Value;
|
||||
@@ -125,15 +196,12 @@ namespace Ink_Canvas
|
||||
seconds = timeSpan.Seconds;
|
||||
}
|
||||
|
||||
// 更新小时显示
|
||||
SetDigitDisplay("FullHour1Display", hours / 10, shouldShowRed);
|
||||
SetDigitDisplay("FullHour2Display", hours % 10, shouldShowRed);
|
||||
SetDigitDisplay("FullHour1Display", Math.Abs(hours / 10) % 10, shouldShowRed);
|
||||
SetDigitDisplay("FullHour2Display", (hours % 10 + 10) % 10, shouldShowRed);
|
||||
|
||||
// 更新分钟显示
|
||||
SetDigitDisplay("FullMinute1Display", minutes / 10, shouldShowRed);
|
||||
SetDigitDisplay("FullMinute2Display", minutes % 10, shouldShowRed);
|
||||
|
||||
// 更新秒显示
|
||||
SetDigitDisplay("FullSecond1Display", seconds / 10, shouldShowRed);
|
||||
SetDigitDisplay("FullSecond2Display", seconds % 10, shouldShowRed);
|
||||
|
||||
@@ -224,23 +292,32 @@ namespace Ink_Canvas
|
||||
|
||||
private void ExitFullscreen()
|
||||
{
|
||||
// 恢复主窗口
|
||||
if (parentWindow != null)
|
||||
{
|
||||
// 清除全屏模式标志
|
||||
parentWindow.SetFullscreenMode(false);
|
||||
parentWindow.Show();
|
||||
parentWindow.Activate();
|
||||
parentWindow.WindowState = WindowState.Normal;
|
||||
}
|
||||
this.Close();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
if (parentWindow != null)
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
parentWindow.TimerCompleted -= ParentWindow_TimerCompleted;
|
||||
mainWindow.ResumeTopmostMaintenance();
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null && previousTimerContainerVisibility == Visibility.Visible)
|
||||
{
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
|
||||
// 重置5秒最小化计时
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.UpdateActivityTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.TimerCompleted -= ParentWindow_TimerCompleted;
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
|
||||
+14
-17
@@ -1,23 +1,17 @@
|
||||
<Window x:Class="Ink_Canvas.MinimizedTimerWindow"
|
||||
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"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
Topmost="True" Background="Transparent"
|
||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True"
|
||||
WindowStartupLocation="Manual" Title="计时器" Height="200" Width="600"
|
||||
MouseLeftButtonDown="Window_MouseLeftButtonDown" MouseLeftButtonUp="Window_MouseLeftButtonUp"
|
||||
MouseEnter="Window_MouseEnter" MouseLeave="Window_MouseLeave" MouseMove="Window_MouseMove">
|
||||
|
||||
<Window.Resources>
|
||||
<UserControl x:Class="Ink_Canvas.Windows.MinimizedTimerControl"
|
||||
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"
|
||||
d:DesignHeight="200" d:DesignWidth="600">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="DigitResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border x:Name="MainBorder" Background="{DynamicResource NewTimerWindowBackground}"
|
||||
CornerRadius="15"
|
||||
@@ -25,7 +19,9 @@
|
||||
BorderBrush="{DynamicResource NewTimerWindowBorderBrush}"
|
||||
Margin="0"
|
||||
UseLayoutRounding="True"
|
||||
SnapsToDevicePixels="True">
|
||||
SnapsToDevicePixels="True"
|
||||
MouseLeftButtonDown="MainBorder_MouseLeftButtonDown"
|
||||
Cursor="Hand">
|
||||
<Grid>
|
||||
<!-- 时间显示 -->
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20,20,20,20">
|
||||
@@ -122,4 +118,5 @@
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Microsoft.Win32;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
/// <summary>
|
||||
/// 最小化计时器窗口
|
||||
/// </summary>
|
||||
public partial class MinimizedTimerControl : UserControl
|
||||
{
|
||||
private TimerControl parentControl;
|
||||
private System.Timers.Timer updateTimer;
|
||||
|
||||
public MinimizedTimerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
updateTimer = new System.Timers.Timer(100);
|
||||
updateTimer.Elapsed += UpdateTimer_Elapsed;
|
||||
updateTimer.Start();
|
||||
|
||||
ApplyTheme();
|
||||
|
||||
// 监听主题变化事件
|
||||
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
|
||||
Unloaded += MinimizedTimerControl_Unloaded;
|
||||
}
|
||||
|
||||
private void MinimizedTimerControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 取消订阅主题变化事件
|
||||
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.TimerCompleted -= ParentControl_TimerCompleted;
|
||||
}
|
||||
|
||||
if (updateTimer != null)
|
||||
{
|
||||
updateTimer.Stop();
|
||||
updateTimer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
// 当主题变化时,重新应用主题
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
RefreshTheme();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新主题
|
||||
/// </summary>
|
||||
public void RefreshTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 重新应用主题
|
||||
ApplyTheme();
|
||||
|
||||
// 强制刷新UI
|
||||
InvalidateVisual();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"刷新最小化计时器窗口主题出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetParentControl(TimerControl parent)
|
||||
{
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.TimerCompleted -= ParentControl_TimerCompleted;
|
||||
}
|
||||
|
||||
parentControl = parent;
|
||||
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.TimerCompleted += ParentControl_TimerCompleted;
|
||||
UpdateTimeDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (parentControl != null)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (this.Visibility != Visibility.Visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldHide())
|
||||
{
|
||||
this.Visibility = Visibility.Collapsed;
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateTimeDisplay();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldHide()
|
||||
{
|
||||
if (parentControl == null) return true;
|
||||
|
||||
if (parentControl.IsFullscreenWindowOpen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true)
|
||||
{
|
||||
if (parentControl.IsTimerRunning)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var remainingTime = parentControl.GetRemainingTime();
|
||||
if (remainingTime.HasValue && remainingTime.Value.TotalSeconds < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !parentControl.IsTimerRunning;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimeDisplay()
|
||||
{
|
||||
if (parentControl == null) return;
|
||||
|
||||
var remainingTime = parentControl.GetRemainingTime();
|
||||
if (remainingTime.HasValue)
|
||||
{
|
||||
var timeSpan = remainingTime.Value;
|
||||
bool isOvertimeMode = timeSpan.TotalSeconds < 0;
|
||||
bool shouldShowRed = isOvertimeMode && MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true;
|
||||
|
||||
int hours, minutes, seconds;
|
||||
|
||||
if (isOvertimeMode)
|
||||
{
|
||||
var totalTimeSpan = parentControl.GetTotalTimeSpan();
|
||||
if (totalTimeSpan.HasValue)
|
||||
{
|
||||
var elapsedTime = parentControl.GetElapsedTime();
|
||||
if (elapsedTime.HasValue)
|
||||
{
|
||||
var overtimeSpan = elapsedTime.Value - totalTimeSpan.Value;
|
||||
hours = (int)overtimeSpan.TotalHours;
|
||||
minutes = overtimeSpan.Minutes;
|
||||
seconds = overtimeSpan.Seconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
seconds = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
seconds = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hours = (int)timeSpan.TotalHours;
|
||||
minutes = timeSpan.Minutes;
|
||||
seconds = timeSpan.Seconds;
|
||||
}
|
||||
|
||||
SetDigitDisplay("MinHour1Display", Math.Abs(hours / 10) % 10, shouldShowRed);
|
||||
SetDigitDisplay("MinHour2Display", (hours % 10 + 10) % 10, shouldShowRed);
|
||||
|
||||
SetDigitDisplay("MinMinute1Display", minutes / 10, shouldShowRed);
|
||||
SetDigitDisplay("MinMinute2Display", minutes % 10, shouldShowRed);
|
||||
|
||||
SetDigitDisplay("MinSecond1Display", seconds / 10, shouldShowRed);
|
||||
SetDigitDisplay("MinSecond2Display", seconds % 10, shouldShowRed);
|
||||
|
||||
SetColonDisplay(shouldShowRed);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParentControl_TimerCompleted(object sender, EventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
});
|
||||
}
|
||||
|
||||
private void SetDigitDisplay(string pathName, int digit, bool isRed = false)
|
||||
{
|
||||
var path = this.FindName(pathName) as Path;
|
||||
if (path != null)
|
||||
{
|
||||
string resourceKey = $"Digit{digit}";
|
||||
var geometry = this.FindResource(resourceKey) as Geometry;
|
||||
if (geometry != null)
|
||||
{
|
||||
path.Data = geometry;
|
||||
}
|
||||
|
||||
if (isRed)
|
||||
{
|
||||
path.Fill = Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.TryFindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
path.Fill = defaultBrush;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLightTheme = IsLightTheme();
|
||||
path.Fill = isLightTheme ? Brushes.Black : Brushes.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetColonDisplay(bool isRed = false)
|
||||
{
|
||||
var colon1 = this.FindName("MinColon1Display") as TextBlock;
|
||||
var colon2 = this.FindName("MinColon2Display") as TextBlock;
|
||||
|
||||
if (colon1 != null)
|
||||
{
|
||||
if (isRed)
|
||||
{
|
||||
colon1.Foreground = Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.TryFindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
colon1.Foreground = defaultBrush;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLightTheme = IsLightTheme();
|
||||
colon1.Foreground = isLightTheme ? Brushes.Black : Brushes.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (colon2 != null)
|
||||
{
|
||||
if (isRed)
|
||||
{
|
||||
colon2.Foreground = Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.TryFindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
colon2.Foreground = defaultBrush;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLightTheme = IsLightTheme();
|
||||
colon2.Foreground = isLightTheme ? Brushes.Black : Brushes.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings != null)
|
||||
{
|
||||
ApplyTheme(MainWindow.Settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLightTheme = IsLightTheme();
|
||||
if (!isLightTheme)
|
||||
{
|
||||
SetDarkThemeBorder();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"应用主题时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTheme(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (settings.Appearance.Theme == 0) // 浅色主题
|
||||
{
|
||||
ThemeManager.SetRequestedTheme(this, ElementTheme.Light);
|
||||
}
|
||||
else if (settings.Appearance.Theme == 1) // 深色主题
|
||||
{
|
||||
ThemeManager.SetRequestedTheme(this, ElementTheme.Dark);
|
||||
SetDarkThemeBorder();
|
||||
}
|
||||
else // 跟随系统主题
|
||||
{
|
||||
bool isSystemLight = IsSystemThemeLight();
|
||||
if (isSystemLight)
|
||||
{
|
||||
ThemeManager.SetRequestedTheme(this, ElementTheme.Light);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThemeManager.SetRequestedTheme(this, ElementTheme.Dark);
|
||||
SetDarkThemeBorder();
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数字和冒号显示的颜色
|
||||
if (parentControl != null)
|
||||
{
|
||||
UpdateTimeDisplay();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"应用最小化计时器窗口主题出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
try
|
||||
{
|
||||
var registryKey = Microsoft.Win32.Registry.CurrentUser;
|
||||
var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||
if (themeKey != null)
|
||||
{
|
||||
var value = themeKey.GetValue("AppsUseLightTheme");
|
||||
if (value != null)
|
||||
{
|
||||
light = (int)value == 1;
|
||||
}
|
||||
themeKey.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果读取注册表失败,默认为浅色主题
|
||||
light = true;
|
||||
}
|
||||
return light;
|
||||
}
|
||||
|
||||
private bool IsLightTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var currentModeField = mainWindow.GetType().GetField("currentMode",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (currentModeField != null)
|
||||
{
|
||||
var currentMode = currentModeField.GetValue(mainWindow);
|
||||
return currentMode?.ToString() == "Light";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetDarkThemeBorder()
|
||||
{
|
||||
try
|
||||
{
|
||||
var border = this.FindName("MainBorder") as Border;
|
||||
if (border != null)
|
||||
{
|
||||
border.BorderBrush = new SolidColorBrush(Color.FromRgb(64, 64, 64));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.StopTimer();
|
||||
}
|
||||
Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private bool isDragging = false;
|
||||
private bool isDragStarted = false;
|
||||
private Point dragStartPoint;
|
||||
private Point containerStartPosition;
|
||||
private const double DragThreshold = 5.0; // 拖动阈值,像素
|
||||
|
||||
private void MainBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount == 2)
|
||||
{
|
||||
// 双击:恢复主窗口
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.UpdateActivityTime();
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
minimizedContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.ClickCount == 1)
|
||||
{
|
||||
// 单击:准备拖动或点击
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (minimizedContainer != null)
|
||||
{
|
||||
var point = e.GetPosition(minimizedContainer);
|
||||
var mainWindowPoint = minimizedContainer.TransformToAncestor(mainWindow).Transform(point);
|
||||
|
||||
// 初始化拖动状态,但不立即开始拖动
|
||||
isDragging = false;
|
||||
isDragStarted = false;
|
||||
dragStartPoint = mainWindowPoint;
|
||||
|
||||
var margin = minimizedContainer.Margin;
|
||||
containerStartPosition = new Point(margin.Left, margin.Top);
|
||||
|
||||
if (double.IsNaN(containerStartPosition.X) || containerStartPosition.X < 0) containerStartPosition.X = 0;
|
||||
if (double.IsNaN(containerStartPosition.Y) || containerStartPosition.Y < 0) containerStartPosition.Y = 0;
|
||||
|
||||
// 捕获鼠标并订阅事件,等待判断是拖动还是点击
|
||||
minimizedContainer.CaptureMouse();
|
||||
minimizedContainer.MouseMove += MinimizedContainer_MouseMove;
|
||||
minimizedContainer.MouseLeftButtonUp += MinimizedContainer_MouseLeftButtonUp;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MinimizedContainer_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (minimizedContainer == null) return;
|
||||
|
||||
var currentPoint = e.GetPosition(mainWindow);
|
||||
var deltaX = currentPoint.X - dragStartPoint.X;
|
||||
var deltaY = currentPoint.Y - dragStartPoint.Y;
|
||||
var distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
// 如果移动距离超过阈值,开始拖动
|
||||
if (!isDragStarted && distance > DragThreshold)
|
||||
{
|
||||
isDragStarted = true;
|
||||
isDragging = true;
|
||||
}
|
||||
|
||||
// 如果已经开始拖动,更新位置
|
||||
if (isDragging)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
|
||||
var newX = containerStartPosition.X + deltaX;
|
||||
var newY = containerStartPosition.Y + deltaY;
|
||||
|
||||
if (newX < 0) newX = 0;
|
||||
if (newY < 0) newY = 0;
|
||||
|
||||
minimizedContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
minimizedContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
minimizedContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
if (timerContainer != null)
|
||||
{
|
||||
timerContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
timerContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
timerContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MinimizedContainer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (minimizedContainer != null)
|
||||
{
|
||||
minimizedContainer.ReleaseMouseCapture();
|
||||
minimizedContainer.MouseMove -= MinimizedContainer_MouseMove;
|
||||
minimizedContainer.MouseLeftButtonUp -= MinimizedContainer_MouseLeftButtonUp;
|
||||
}
|
||||
|
||||
// 如果没有开始拖动(移动距离小于阈值),则视为单击,恢复主窗口
|
||||
if (!isDragStarted)
|
||||
{
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.UpdateActivityTime();
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
minimizedContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isDragging = false;
|
||||
isDragStarted = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,392 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 最小化计时器窗口
|
||||
/// </summary>
|
||||
public partial class MinimizedTimerWindow : Window
|
||||
{
|
||||
private NewStyleTimerWindow parentWindow;
|
||||
private System.Timers.Timer updateTimer;
|
||||
private bool isMouseOver = false;
|
||||
private bool isDragging = false;
|
||||
private Point lastMousePosition;
|
||||
|
||||
public MinimizedTimerWindow(NewStyleTimerWindow parent)
|
||||
{
|
||||
InitializeComponent();
|
||||
parentWindow = parent;
|
||||
|
||||
// 设置窗口位置
|
||||
this.Left = parent.Left;
|
||||
this.Top = parent.Top;
|
||||
|
||||
// 启动更新定时器
|
||||
updateTimer = new System.Timers.Timer(100); // 100ms更新一次
|
||||
updateTimer.Elapsed += UpdateTimer_Elapsed;
|
||||
updateTimer.Start();
|
||||
|
||||
parentWindow.TimerCompleted += ParentWindow_TimerCompleted;
|
||||
|
||||
// 应用主题
|
||||
ApplyTheme();
|
||||
}
|
||||
|
||||
private void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (parentWindow != null)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (ShouldCloseWindow())
|
||||
{
|
||||
this.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateTimeDisplay();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldCloseWindow()
|
||||
{
|
||||
if (parentWindow == null) return true;
|
||||
|
||||
if (MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true)
|
||||
{
|
||||
if (parentWindow.IsTimerRunning)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var remainingTime = parentWindow.GetRemainingTime();
|
||||
if (remainingTime.HasValue && remainingTime.Value.TotalSeconds < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !parentWindow.IsTimerRunning;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimeDisplay()
|
||||
{
|
||||
if (parentWindow == null) return;
|
||||
|
||||
// 获取剩余时间
|
||||
var remainingTime = parentWindow.GetRemainingTime();
|
||||
if (remainingTime.HasValue)
|
||||
{
|
||||
var timeSpan = remainingTime.Value;
|
||||
bool isOvertimeMode = timeSpan.TotalSeconds < 0;
|
||||
bool shouldShowRed = isOvertimeMode && MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true;
|
||||
|
||||
int hours, minutes, seconds;
|
||||
|
||||
if (isOvertimeMode)
|
||||
{
|
||||
var totalTimeSpan = parentWindow.GetTotalTimeSpan();
|
||||
if (totalTimeSpan.HasValue)
|
||||
{
|
||||
var elapsedTime = parentWindow.GetElapsedTime();
|
||||
if (elapsedTime.HasValue)
|
||||
{
|
||||
var overtimeSpan = elapsedTime.Value - totalTimeSpan.Value;
|
||||
hours = (int)overtimeSpan.TotalHours;
|
||||
minutes = overtimeSpan.Minutes;
|
||||
seconds = overtimeSpan.Seconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
seconds = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
seconds = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hours = (int)timeSpan.TotalHours;
|
||||
minutes = timeSpan.Minutes;
|
||||
seconds = timeSpan.Seconds;
|
||||
}
|
||||
|
||||
// 更新小时显示
|
||||
SetDigitDisplay("MinHour1Display", hours / 10, shouldShowRed);
|
||||
SetDigitDisplay("MinHour2Display", hours % 10, shouldShowRed);
|
||||
|
||||
// 更新分钟显示
|
||||
SetDigitDisplay("MinMinute1Display", minutes / 10, shouldShowRed);
|
||||
SetDigitDisplay("MinMinute2Display", minutes % 10, shouldShowRed);
|
||||
|
||||
// 更新秒显示
|
||||
SetDigitDisplay("MinSecond1Display", seconds / 10, shouldShowRed);
|
||||
SetDigitDisplay("MinSecond2Display", seconds % 10, shouldShowRed);
|
||||
|
||||
SetColonDisplay(shouldShowRed);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParentWindow_TimerCompleted(object sender, EventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
this.Close();
|
||||
});
|
||||
}
|
||||
|
||||
private void SetDigitDisplay(string pathName, int digit, bool isRed = false)
|
||||
{
|
||||
var path = this.FindName(pathName) as Path;
|
||||
if (path != null)
|
||||
{
|
||||
string resourceKey = $"Digit{digit}";
|
||||
var geometry = this.FindResource(resourceKey) as Geometry;
|
||||
if (geometry != null)
|
||||
{
|
||||
path.Data = geometry;
|
||||
}
|
||||
|
||||
// 设置颜色
|
||||
if (isRed)
|
||||
{
|
||||
path.Fill = Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
path.Fill = defaultBrush;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLightTheme = IsLightTheme();
|
||||
path.Fill = isLightTheme ? Brushes.Black : Brushes.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置最小化窗口冒号显示颜色
|
||||
/// </summary>
|
||||
/// <param name="isRed">是否显示为红色</param>
|
||||
private void SetColonDisplay(bool isRed = false)
|
||||
{
|
||||
var colon1 = this.FindName("MinColon1Display") as TextBlock;
|
||||
var colon2 = this.FindName("MinColon2Display") as TextBlock;
|
||||
|
||||
if (colon1 != null)
|
||||
{
|
||||
if (isRed)
|
||||
{
|
||||
colon1.Foreground = Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
colon1.Foreground = defaultBrush;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLightTheme = IsLightTheme();
|
||||
colon1.Foreground = isLightTheme ? Brushes.Black : Brushes.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (colon2 != null)
|
||||
{
|
||||
if (isRed)
|
||||
{
|
||||
colon2.Foreground = Brushes.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
colon2.Foreground = defaultBrush;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isLightTheme = IsLightTheme();
|
||||
colon2.Foreground = isLightTheme ? Brushes.Black : Brushes.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
bool isLightTheme = IsLightTheme();
|
||||
if (!isLightTheme)
|
||||
{
|
||||
SetDarkThemeBorder();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"应用主题时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsLightTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var currentModeField = mainWindow.GetType().GetField("currentMode",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (currentModeField != null)
|
||||
{
|
||||
var currentMode = currentModeField.GetValue(mainWindow);
|
||||
return currentMode?.ToString() == "Light";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果获取主题失败,默认使用浅色主题
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 设置深色主题下的灰色边框
|
||||
private void SetDarkThemeBorder()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 找到Border元素并设置灰色边框
|
||||
var border = this.FindName("MainBorder") as Border;
|
||||
if (border != null)
|
||||
{
|
||||
border.BorderBrush = new SolidColorBrush(Color.FromRgb(64, 64, 64));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 记录点击时间
|
||||
lastClickTime = DateTime.Now;
|
||||
// 开始拖动
|
||||
isDragging = true;
|
||||
lastMousePosition = e.GetPosition(this);
|
||||
this.CaptureMouse();
|
||||
}
|
||||
|
||||
private void Window_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (isDragging)
|
||||
{
|
||||
var currentPosition = e.GetPosition(this);
|
||||
var deltaX = currentPosition.X - lastMousePosition.X;
|
||||
var deltaY = currentPosition.Y - lastMousePosition.Y;
|
||||
|
||||
this.Left += deltaX;
|
||||
this.Top += deltaY;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (isDragging)
|
||||
{
|
||||
isDragging = false;
|
||||
this.ReleaseMouseCapture();
|
||||
|
||||
// 如果点击时间很短,认为是单击,恢复主窗口
|
||||
var clickDuration = DateTime.Now - lastClickTime;
|
||||
if (clickDuration.TotalMilliseconds < 200) // 200ms内认为是单击
|
||||
{
|
||||
// 恢复主窗口
|
||||
if (parentWindow != null)
|
||||
{
|
||||
parentWindow.Show();
|
||||
parentWindow.Activate();
|
||||
parentWindow.WindowState = WindowState.Normal;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime lastClickTime = DateTime.Now;
|
||||
|
||||
private void Window_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
isMouseOver = true;
|
||||
// 鼠标进入时显示关闭按钮
|
||||
if (CloseButton != null)
|
||||
{
|
||||
CloseButton.Opacity = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
isMouseOver = false;
|
||||
// 鼠标离开时隐藏关闭按钮
|
||||
if (CloseButton != null)
|
||||
{
|
||||
CloseButton.Opacity = 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 停止计时器并关闭窗口
|
||||
if (parentWindow != null)
|
||||
{
|
||||
parentWindow.StopTimer();
|
||||
}
|
||||
this.Close();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
if (parentWindow != null)
|
||||
{
|
||||
parentWindow.TimerCompleted -= ParentWindow_TimerCompleted;
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
if (updateTimer != null)
|
||||
{
|
||||
updateTimer.Stop();
|
||||
updateTimer.Dispose();
|
||||
}
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,40 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
mc:Ignorable="d" FontFamily="Microsoft YaHei UI" ui:WindowHelper.UseModernWindowStyle="True"
|
||||
ui:ThemeManager.RequestedTheme="Light" WindowStartupLocation="CenterScreen"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" Topmost="True"
|
||||
Title="Ink Canvas 抽奖 - 名单导入" Height="500" Width="400"
|
||||
Loaded="Window_Loaded" Closing="Window_Closing">
|
||||
<Grid>
|
||||
<Label Content="请在下方输入名单,每行一人(建议直接粘贴表格姓名列)" Margin="10"/>
|
||||
<TextBox Name="TextBoxNames" FontFamily="Microsoft YaHei UI" VerticalScrollBarVisibility="Auto" AcceptsReturn="True" Margin="10,40,10,50" />
|
||||
<Button Margin="10" VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
||||
Content="关闭" FontFamily="Microsoft YaHei UI"
|
||||
Width="100" Click="Button_Click"/>
|
||||
<Window.Resources>
|
||||
<!-- 主题资源 -->
|
||||
<SolidColorBrush x:Key="NamesInputWindowBackground" Color="White"/>
|
||||
<SolidColorBrush x:Key="NamesInputWindowForeground" Color="Black"/>
|
||||
<SolidColorBrush x:Key="NamesInputWindowButtonBackground" Color="#F4F4F5"/>
|
||||
<SolidColorBrush x:Key="NamesInputWindowButtonForeground" Color="Black"/>
|
||||
<SolidColorBrush x:Key="NamesInputWindowBorderBrush" Color="#E4E4E7"/>
|
||||
</Window.Resources>
|
||||
<Grid Background="{DynamicResource NamesInputWindowBackground}">
|
||||
<Label Content="请在下方输入名单,每行一人(建议直接粘贴表格姓名列)"
|
||||
Margin="10"
|
||||
Foreground="{DynamicResource NamesInputWindowForeground}"
|
||||
FontFamily="Microsoft YaHei UI"/>
|
||||
<TextBox Name="TextBoxNames"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
AcceptsReturn="True"
|
||||
Margin="10,40,10,50"
|
||||
Background="{DynamicResource NamesInputWindowBackground}"
|
||||
Foreground="{DynamicResource NamesInputWindowForeground}"
|
||||
BorderBrush="{DynamicResource NamesInputWindowBorderBrush}"/>
|
||||
<Button Margin="10"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
Content="关闭"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
Width="100"
|
||||
Click="Button_Click"
|
||||
Background="{DynamicResource NamesInputWindowButtonBackground}"
|
||||
Foreground="{DynamicResource NamesInputWindowButtonForeground}"
|
||||
BorderBrush="{DynamicResource NamesInputWindowBorderBrush}"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -14,6 +17,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
InitializeComponent();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
|
||||
ApplyTheme();
|
||||
}
|
||||
|
||||
string originText = "";
|
||||
@@ -43,5 +47,111 @@ namespace Ink_Canvas
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings != null)
|
||||
{
|
||||
ApplyTheme(MainWindow.Settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用名单导入窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTheme(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (settings.Appearance.Theme == 0) // 浅色主题
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
|
||||
ApplyThemeResources("Light");
|
||||
}
|
||||
else if (settings.Appearance.Theme == 1) // 深色主题
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
|
||||
ApplyThemeResources("Dark");
|
||||
}
|
||||
else // 跟随系统主题
|
||||
{
|
||||
bool isSystemLight = IsSystemThemeLight();
|
||||
if (isSystemLight)
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
|
||||
ApplyThemeResources("Light");
|
||||
}
|
||||
else
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
|
||||
ApplyThemeResources("Dark");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用名单导入窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyThemeResources(string theme)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resources = this.Resources;
|
||||
|
||||
if (theme == "Light")
|
||||
{
|
||||
// 应用浅色主题资源
|
||||
resources["NamesInputWindowBackground"] = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
resources["NamesInputWindowForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
resources["NamesInputWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(244, 244, 245));
|
||||
resources["NamesInputWindowButtonForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
resources["NamesInputWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(228, 228, 231));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 应用深色主题资源 - 与新计时器窗口统一
|
||||
resources["NamesInputWindowBackground"] = new SolidColorBrush(Color.FromRgb(31, 31, 31)); // #1f1f1f
|
||||
resources["NamesInputWindowForeground"] = new SolidColorBrush(Colors.White);
|
||||
resources["NamesInputWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(42, 42, 42)); // #2a2a2a
|
||||
resources["NamesInputWindowButtonForeground"] = new SolidColorBrush(Colors.White);
|
||||
resources["NamesInputWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(224, 224, 224)); // #E0E0E0
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用名单导入窗口主题资源出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
try
|
||||
{
|
||||
var registryKey = Microsoft.Win32.Registry.CurrentUser;
|
||||
var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||
if (themeKey != null)
|
||||
{
|
||||
var value = themeKey.GetValue("AppsUseLightTheme");
|
||||
if (value != null)
|
||||
{
|
||||
light = (int)value == 1;
|
||||
}
|
||||
themeKey.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果无法读取注册表,默认使用浅色主题
|
||||
light = true;
|
||||
}
|
||||
return light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,541 @@
|
||||
<Window x:Class="Ink_Canvas.NewStyleRollCallWindow"
|
||||
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"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:controls="clr-namespace:Ink_Canvas.Windows.Controls"
|
||||
Topmost="True" Background="Transparent"
|
||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True"
|
||||
Loaded="RollCallWindow_Loaded" Closing="Window_Closing" WindowStartupLocation="CenterScreen"
|
||||
MouseMove="Window_MouseMove" MouseEnter="Window_MouseEnter"
|
||||
Title="Ink Canvas 画板 - 点名" Height="500" Width="800">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Controls/WinUI3CloseButton.xaml" />
|
||||
<ResourceDictionary Source="../Resources/NewRollCallWindowResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Border Background="{DynamicResource NewRollCallWindowBackground}" CornerRadius="15" BorderThickness="1" BorderBrush="{DynamicResource NewRollCallWindowBorderBrush}" Margin="10" x:Name="MainBorder" MouseLeftButtonDown="WindowDragMove">
|
||||
<Grid>
|
||||
<controls:WinUI3CloseButton x:Name="CloseButton"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="0,0,0,0" Cursor="Hand" Click="CloseButton_Click"
|
||||
Content="✕"/>
|
||||
<!-- 主要内容区域 -->
|
||||
<Grid>
|
||||
<!-- 使用Viewbox自动缩放内容 -->
|
||||
<Viewbox x:Name="MainViewController" Margin="20,20,20,20">
|
||||
<Grid Height="550" Width="1000">
|
||||
<!-- 顶部标题栏 -->
|
||||
<Grid Height="50" Background="{DynamicResource NewRollCallWindowBackground}" x:Name="TitleBar" VerticalAlignment="Top" MouseLeftButtonDown="WindowDragMove" Margin="0,0,450,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="22,0,0,0">
|
||||
<!-- 点名图标 -->
|
||||
<Path Data="M5 7C5 8.06087 5.42143 9.07828 6.17157 9.82843C6.92172 10.5786 7.93913 11 9 11C10.0609 11 11.0783 10.5786 11.8284 9.82843C12.5786 9.07828 13 8.06087 13 7C13 5.93913 12.5786 4.92172 11.8284 4.17157C11.0783 3.42143 10.0609 3 9 3C7.93913 3 6.92172 3.42143 6.17157 4.17157C5.42143 4.92172 5 5.93913 5 7Z M3 21V19C3 17.9391 3.42143 16.9217 4.17157 16.1716C4.92172 15.4214 5.93913 15 7 15H11C12.0609 15 13.0783 15.4214 13.8284 16.1716C14.5786 16.9217 15 17.9391 15 19V21 M16 3.13C16.8604 3.35031 17.623 3.85071 18.1676 4.55232C18.7122 5.25392 19.0078 6.11683 19.0078 7.005C19.0078 7.89318 18.7122 8.75608 18.1676 9.45769C17.623 10.1593 16.8604 10.6597 16 10.88 M21 21V19C20.9949 18.1172 20.6979 17.2608 20.1553 16.5644C19.6126 15.868 18.8548 15.3707 18 15.15"
|
||||
Stroke="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="24" Height="24"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<!-- 点名文字 -->
|
||||
<TextBlock Text="点名" FontSize="28" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowTitleForeground}" x:Name="TitleText"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 主要内容区域 - 分为左右两部分 -->
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center"
|
||||
x:Name="MainContentGrid" Margin="0,-25,0,25">
|
||||
<!-- 左侧:结果显示区域 -->
|
||||
<Grid HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
x:Name="MainDisplayGrid" Width="500" Margin="-10,0,0,0">
|
||||
<!-- 结果显示区域 -->
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<!-- 主结果显示 -->
|
||||
<TextBlock x:Name="MainResultDisplay" Text="点击开始点名" FontSize="48" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" Margin="0,0,0,20"/>
|
||||
|
||||
<!-- 多结果显示区域 - 支持最多20个结果 -->
|
||||
<ScrollViewer x:Name="MultiResultScrollViewer" MaxHeight="200" VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled" Visibility="Collapsed">
|
||||
<StackPanel x:Name="MultiResultPanel" Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 第1行:结果1-5 -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,8">
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result1Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result2Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result3Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result4Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result5Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</StackPanel>
|
||||
<!-- 第2行:结果6-10 -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,8">
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result6Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result7Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result8Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result9Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result10Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</StackPanel>
|
||||
<!-- 第3行:结果11-15 -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,8">
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result11Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result12Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result13Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result14Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result15Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</StackPanel>
|
||||
<!-- 第4行:结果16-20 -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result16Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result17Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result18Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" Margin="0,0,12,0" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result19Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
<Viewbox Width="60" Height="36" Stretch="Uniform" ClipToBounds="True">
|
||||
<TextBlock x:Name="Result20Display" Text="" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" />
|
||||
</Viewbox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- 点名状态显示 -->
|
||||
<TextBlock x:Name="StatusDisplay" Text="准备就绪" FontSize="16"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowSecondaryTextForeground}"
|
||||
TextAlignment="Center" Margin="0,10,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<Border Width="2" Background="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
Opacity="0.3" HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||
Margin="500,0,0,0"/>
|
||||
|
||||
<!-- 右侧:控制选项区域 -->
|
||||
<Grid HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
x:Name="ControlOptionsGrid" Width="450" Margin="520,0,0,0" Height="400">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 人数控制区域 -->
|
||||
<Grid Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<TextBlock Text="点名人数" FontSize="16" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button x:Name="CountMinusBtn" Width="40" Height="40" Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
BorderThickness="0" Click="CountMinus_Click" Cursor="Hand" Margin="0,0,15,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M5 12l14 0"
|
||||
Stroke="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
|
||||
<TextBlock x:Name="CountDisplay" Text="1" FontSize="24" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
Width="60" TextAlignment="Center"/>
|
||||
|
||||
|
||||
<Button x:Name="CountPlusBtn" Width="40" Height="40" Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
BorderThickness="0" Click="CountPlus_Click" Cursor="Hand" Margin="15,0,0,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M12 5l0 14 M5 12l14 0"
|
||||
Stroke="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 点名模式选择 -->
|
||||
<Grid Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<TextBlock Text="点名模式" FontSize="16" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||
<Border Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
CornerRadius="8"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#616161"
|
||||
Width="300" Height="40">
|
||||
<Grid>
|
||||
<!-- 背景指示器 -->
|
||||
<Border x:Name="SegmentedIndicator"
|
||||
Background="{DynamicResource NewRollCallWindowPrimaryButtonBackground}"
|
||||
CornerRadius="7.5,0,0,7.5"
|
||||
Width="100" Height="38"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,0"/>
|
||||
|
||||
<!-- 按钮容器 -->
|
||||
<Grid>
|
||||
<Button x:Name="RandomModeBtn"
|
||||
Width="100" Height="40"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="RandomMode_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock x:Name="RandomModeText" Text="随机点名" FontSize="16" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"/>
|
||||
</Button>
|
||||
<Button x:Name="SequentialModeBtn"
|
||||
Width="100" Height="40"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="SequentialMode_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock x:Name="SequentialModeText" Text="顺序点名" FontSize="16" FontWeight="Normal"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}" Opacity="0.6"/>
|
||||
</Button>
|
||||
<Button x:Name="GroupModeBtn"
|
||||
Width="100" Height="40"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="GroupMode_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Right">
|
||||
<TextBlock x:Name="GroupModeText" Text="分组点名" FontSize="16" FontWeight="Normal"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}" Opacity="0.6"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 外部点名区域 -->
|
||||
<Grid Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<TextBlock Text="外部点名" FontSize="16" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||
|
||||
<!-- 外部点名模式选择 -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="10"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 外部点名模式Tab -->
|
||||
<Border Grid.Column="0"
|
||||
Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
CornerRadius="8"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#616161"
|
||||
Width="100" Height="40">
|
||||
<Grid>
|
||||
<!-- 背景指示器 -->
|
||||
<Border x:Name="ExternalCallerModeIndicator"
|
||||
Background="{DynamicResource NewRollCallWindowPrimaryButtonBackground}"
|
||||
CornerRadius="7.5"
|
||||
Width="100" Height="38"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,0"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
<!-- 按钮容器 -->
|
||||
<Button x:Name="ExternalCallerModeBtn"
|
||||
Width="100" Height="40"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="ExternalCallerMode_Click"
|
||||
Cursor="Hand">
|
||||
<TextBlock x:Name="ExternalCallerModeText" Text="外部点名" FontSize="16" FontWeight="Normal"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}" Opacity="0.6"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 外部点名类型下拉框 -->
|
||||
<ComboBox x:Name="ExternalCallerTypeComboBox"
|
||||
Grid.Column="2"
|
||||
Width="160" Height="40"
|
||||
IsEditable="False"
|
||||
IsReadOnly="True"
|
||||
SelectedIndex="0"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
SelectionChanged="ExternalCallerTypeComboBox_SelectionChanged">
|
||||
<ComboBoxItem Content="ClassIsland" IsSelected="True"/>
|
||||
<ComboBoxItem Content="SecRandom"/>
|
||||
<ComboBoxItem Content="NamePicker"/>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 名单管理区域 -->
|
||||
<Grid Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<TextBlock Text="名单管理" FontSize="16" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button x:Name="ImportListBtn" Width="90" Height="40" Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
BorderThickness="0" Click="ImportList_Click" Cursor="Hand" Margin="0,0,10,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="8">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<TextBlock Text="导入名单" FontSize="14"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"/>
|
||||
</Button>
|
||||
<Button x:Name="ClearListBtn" Width="90" Height="40" Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
BorderThickness="0" Click="ClearList_Click" Cursor="Hand" Margin="0,0,10,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="8">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<TextBlock Text="清空名单" FontSize="14"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"/>
|
||||
</Button>
|
||||
<Button x:Name="ViewHistoryBtn" Width="90" Height="40" Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
BorderThickness="0" Click="ViewHistory_Click" Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="8">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<TextBlock Text="查看历史" FontSize="14"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 名单统计信息 -->
|
||||
<Grid Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<TextBlock x:Name="ListCountDisplay" Text="名单人数: 0" FontSize="14"
|
||||
Foreground="{DynamicResource NewRollCallWindowSecondaryTextForeground}"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部控制按钮区域 -->
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<!-- 开始点名按钮 -->
|
||||
<Button x:Name="StartRollCallBtn" Width="140" Height="60" Background="{DynamicResource NewRollCallWindowPrimaryButtonBackground}"
|
||||
BorderThickness="0" Click="StartRollCall_Click" Cursor="Hand" Margin="0,0,20,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="30">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Path x:Name="StartRollCallBtnIcon" Data="M5 7C5 8.06087 5.42143 9.07828 6.17157 9.82843C6.92172 10.5786 7.93913 11 9 11C10.0609 11 11.0783 10.5786 11.8284 9.82843C12.5786 9.07828 13 8.06087 13 7C13 5.93913 12.5786 4.92172 11.8284 4.17157C11.0783 3.42143 10.0609 3 9 3C7.93913 3 6.92172 3.42143 6.17157 4.17157C5.42143 4.92172 5 5.93913 5 7Z M3 21V19C3 17.9391 3.42143 16.9217 4.17157 16.1716C4.92172 15.4214 5.93913 15 7 15H11C12.0609 15 13.0783 15.4214 13.8284 16.1716C14.5786 16.9217 15 17.9391 15 19V21 M16 3.13C16.8604 3.35031 17.623 3.85071 18.1676 4.55232C18.7122 5.25392 19.0078 6.11683 19.0078 7.005C19.0078 7.89318 18.7122 8.75608 18.1676 9.45769C17.623 10.1593 16.8604 10.6597 16 10.88 M21 21V19C20.9949 18.1172 20.6979 17.2608 20.1553 16.5644C19.6126 15.868 18.8548 15.3707 18 15.15"
|
||||
Stroke="{DynamicResource NewRollCallWindowPrimaryButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<TextBlock x:Name="StartRollCallBtnText" Text="开始点名" FontSize="18" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowPrimaryButtonForeground}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- 停止点名按钮 -->
|
||||
<Button x:Name="StopRollCallBtn" Width="100" Height="60" Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
BorderThickness="0" Click="StopRollCall_Click" Cursor="Hand" Margin="0,0,20,0" Visibility="Collapsed">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="30">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Path Data="M6 6l12 12 M18 6l-12 12"
|
||||
Stroke="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<TextBlock Text="停止" FontSize="18" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- 重置按钮 -->
|
||||
<Button x:Name="ResetBtn" Width="140" Height="60" Background="{DynamicResource NewRollCallWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Reset_Click" Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="30">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Path Data="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4 M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"
|
||||
Stroke="{DynamicResource NewRollCallWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<TextBlock Text="重置" FontSize="18" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewRollCallWindowButtonForeground}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,303 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 快抽窗口
|
||||
/// </summary>
|
||||
public partial class QuickDrawWindow : Window
|
||||
{
|
||||
private Random random = new Random();
|
||||
private int autoCloseWaitTime = 2500; // 自动关闭等待时间(毫秒)
|
||||
private List<string> nameList = new List<string>(); // 名单列表
|
||||
|
||||
public QuickDrawWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.Focusable = false;
|
||||
this.ShowInTaskbar = false;
|
||||
InitializeSettings();
|
||||
LoadNamesFromFile();
|
||||
StartQuickDraw();
|
||||
}
|
||||
|
||||
private void InitializeSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.RandSettings != null)
|
||||
{
|
||||
autoCloseWaitTime = (int)MainWindow.Settings.RandSettings.RandWindowOnceCloseLatency * 1000;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化快抽窗口设置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadNamesFromFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
string namesFilePath = App.RootPath + "Names.txt";
|
||||
if (File.Exists(namesFilePath))
|
||||
{
|
||||
string content = File.ReadAllText(namesFilePath);
|
||||
nameList = content.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(name => name.Trim())
|
||||
.Where(name => !string.IsNullOrEmpty(name))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
nameList.Clear();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载名单文件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
nameList.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartQuickDraw()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 延迟100ms后开始抽选动画
|
||||
new System.Threading.Thread(() =>
|
||||
{
|
||||
System.Threading.Thread.Sleep(100);
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
StartQuickDrawAnimation();
|
||||
});
|
||||
}).Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"开始快抽失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快抽动画
|
||||
/// </summary>
|
||||
private void StartQuickDrawAnimation()
|
||||
{
|
||||
const int animationTimes = 100; // 动画次数
|
||||
const int sleepTime = 5; // 每次动画间隔(毫秒)
|
||||
|
||||
new System.Threading.Thread(() =>
|
||||
{
|
||||
if (nameList.Count > 0)
|
||||
{
|
||||
// 有名单时,从名单中抽选
|
||||
StartNameDrawAnimation(animationTimes, sleepTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有名单时,从1-60数字中抽选
|
||||
StartNumberDrawAnimation(animationTimes, sleepTime);
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 名单抽选动画
|
||||
/// </summary>
|
||||
private void StartNameDrawAnimation(int animationTimes, int sleepTime)
|
||||
{
|
||||
List<string> usedNames = new List<string>();
|
||||
|
||||
for (int i = 0; i < animationTimes; i++)
|
||||
{
|
||||
// 随机选择一个名字进行动画显示,避免立即重复
|
||||
string randomName;
|
||||
do
|
||||
{
|
||||
randomName = nameList[random.Next(0, nameList.Count)];
|
||||
} while (usedNames.Count > 0 && usedNames[usedNames.Count - 1] == randomName);
|
||||
|
||||
usedNames.Add(randomName);
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainResultDisplay.Text = randomName;
|
||||
});
|
||||
|
||||
System.Threading.Thread.Sleep(sleepTime);
|
||||
}
|
||||
|
||||
// 动画结束,显示最终结果
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 使用降重抽选方法选择最终名字
|
||||
var selectedNames = NewStyleRollCallWindow.SelectNamesWithML(nameList, 1, random);
|
||||
string finalName = selectedNames.Count > 0 ? selectedNames[0] : nameList[random.Next(0, nameList.Count)];
|
||||
MainResultDisplay.Text = finalName;
|
||||
|
||||
// 更新历史记录
|
||||
NewStyleRollCallWindow.UpdateRollCallHistory(new List<string> { finalName });
|
||||
});
|
||||
|
||||
// 显示结果后,等待一段时间让用户看到结果,然后关闭窗口
|
||||
new System.Threading.Thread(() =>
|
||||
{
|
||||
System.Threading.Thread.Sleep(autoCloseWaitTime);
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Close();
|
||||
});
|
||||
}).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数字抽选动画
|
||||
/// </summary>
|
||||
private void StartNumberDrawAnimation(int animationTimes, int sleepTime)
|
||||
{
|
||||
List<int> usedNumbers = new List<int>();
|
||||
|
||||
for (int i = 0; i < animationTimes; i++)
|
||||
{
|
||||
// 随机选择一个数字进行动画显示,避免立即重复
|
||||
int randomNumber;
|
||||
do
|
||||
{
|
||||
randomNumber = random.Next(1, 61); // 1-60
|
||||
} while (usedNumbers.Count > 0 && usedNumbers[usedNumbers.Count - 1] == randomNumber);
|
||||
|
||||
usedNumbers.Add(randomNumber);
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainResultDisplay.Text = randomNumber.ToString();
|
||||
});
|
||||
|
||||
System.Threading.Thread.Sleep(sleepTime);
|
||||
}
|
||||
|
||||
// 动画结束,显示最终结果
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 使用降重抽选方法选择最终数字
|
||||
var numberList = Enumerable.Range(1, 60).Select(n => n.ToString()).ToList();
|
||||
var selectedNumbers = NewStyleRollCallWindow.SelectNamesWithML(numberList, 1, random);
|
||||
string finalNumber = selectedNumbers.Count > 0 ? selectedNumbers[0] : random.Next(1, 61).ToString();
|
||||
MainResultDisplay.Text = finalNumber;
|
||||
|
||||
// 更新历史记录
|
||||
NewStyleRollCallWindow.UpdateRollCallHistory(new List<string> { finalNumber });
|
||||
});
|
||||
|
||||
// 显示结果后,等待一段时间让用户看到结果,然后关闭窗口
|
||||
new System.Threading.Thread(() =>
|
||||
{
|
||||
System.Threading.Thread.Sleep(autoCloseWaitTime);
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Close();
|
||||
});
|
||||
}).Start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void WindowDragMove(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
DragMove();
|
||||
}
|
||||
|
||||
|
||||
#region Win32 API 声明和置顶管理
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
private const int WS_EX_TOPMOST = 0x00000008;
|
||||
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||
private const uint SWP_NOMOVE = 0x0002;
|
||||
private const uint SWP_NOSIZE = 0x0001;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
private const uint SWP_SHOWWINDOW = 0x0040;
|
||||
private const uint SWP_NOOWNERZORDER = 0x0200;
|
||||
|
||||
/// <summary>
|
||||
/// 应用快抽窗口置顶
|
||||
/// </summary>
|
||||
private void ApplyQuickDrawWindowTopmost()
|
||||
{
|
||||
try
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
|
||||
// 设置WPF的Topmost属性
|
||||
Topmost = true;
|
||||
|
||||
// 使用Win32 API强制置顶
|
||||
// 1. 设置窗口样式为置顶
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
|
||||
|
||||
// 2. 使用SetWindowPos确保窗口在最顶层
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
|
||||
LogHelper.WriteLogToFile("快抽窗口已应用置顶", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用快抽窗口置顶失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口加载事件处理,确保置顶
|
||||
/// </summary>
|
||||
private void QuickDrawWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MainWindow mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
mainWindow.PauseTopmostMaintenance();
|
||||
}
|
||||
|
||||
// 使用延迟确保窗口完全加载后再应用置顶
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ApplyQuickDrawWindowTopmost();
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
MainWindow mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
mainWindow.ResumeTopmostMaintenance();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<Window x:Class="Ink_Canvas.QuickDrawWindow"
|
||||
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"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
Topmost="True" Background="Transparent"
|
||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True"
|
||||
Loaded="QuickDrawWindow_Loaded" Closing="Window_Closing" WindowStartupLocation="CenterScreen"
|
||||
Title="快抽窗口" Height="200" Width="400" Focusable="False" ShowInTaskbar="False">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<!-- 快抽窗口资源 -->
|
||||
<SolidColorBrush x:Key="QuickDrawWindowBackground" Color="#1f1f1f"/>
|
||||
<SolidColorBrush x:Key="QuickDrawWindowBorderBrush" Color="#E0E0E0"/>
|
||||
<SolidColorBrush x:Key="QuickDrawWindowTitleForeground" Color="White"/>
|
||||
<SolidColorBrush x:Key="QuickDrawWindowDigitForeground" Color="White"/>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Border Background="{DynamicResource QuickDrawWindowBackground}"
|
||||
CornerRadius="15"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource QuickDrawWindowBorderBrush}"
|
||||
Margin="10"
|
||||
x:Name="MainBorder"
|
||||
MouseLeftButtonDown="WindowDragMove">
|
||||
<Grid>
|
||||
<!-- 主要内容区域 -->
|
||||
<Grid>
|
||||
<!-- 顶部标题栏 -->
|
||||
<Grid Height="50" Background="{DynamicResource QuickDrawWindowBackground}"
|
||||
x:Name="TitleBar"
|
||||
VerticalAlignment="Top"
|
||||
MouseLeftButtonDown="WindowDragMove"
|
||||
Margin="10,8,40,0">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="22,0,0,0">
|
||||
<!-- 快抽图标 -->
|
||||
<Path Data="M5 7C5 8.06087 5.42143 9.07828 6.17157 9.82843C6.92172 10.5786 7.93913 11 9 11C10.0609 11 11.0783 10.5786 11.8284 9.82843C12.5786 9.07828 13 8.06087 13 7C13 5.93913 12.5786 4.92172 11.8284 4.17157C11.0783 3.42143 10.0609 3 9 3C7.93913 3 6.92172 3.42143 6.17157 4.17157C5.42143 4.92172 5 5.93913 5 7Z M3 21V19C3 17.9391 3.42143 16.9217 4.17157 16.1716C4.92172 15.4214 5.93913 15 7 15H11C12.0609 15 13.0783 15.4214 13.8284 16.1716C14.5786 16.9217 15 17.9391 15 19V21 M16 3.13C16.8604 3.35031 17.623 3.85071 18.1676 4.55232C18.7122 5.25392 19.0078 6.11683 19.0078 7.005C19.0078 7.89318 18.7122 8.75608 18.1676 9.45769C17.623 10.1593 16.8604 10.6597 16 10.88 M21 21V19C20.9949 18.1172 20.6979 17.2608 20.1553 16.5644C19.6126 15.868 18.8548 15.3707 18 15.15"
|
||||
Stroke="{DynamicResource QuickDrawWindowTitleForeground}"
|
||||
StrokeThickness="2"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="24" Height="24"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<!-- 快抽文字 -->
|
||||
<TextBlock Text="快抽" FontSize="20" FontWeight="Bold"
|
||||
Foreground="{DynamicResource QuickDrawWindowTitleForeground}"
|
||||
x:Name="TitleText"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<Grid Margin="20,60,20,20">
|
||||
<!-- 结果显示区域 -->
|
||||
<Grid x:Name="ResultGrid" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<!-- 主结果显示 -->
|
||||
<TextBlock x:Name="MainResultDisplay"
|
||||
Text="准备抽选..."
|
||||
FontSize="48"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource QuickDrawWindowDigitForeground}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,46 @@
|
||||
<Window x:Class="Ink_Canvas.RollCallHistoryWindow"
|
||||
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"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
mc:Ignorable="d" FontFamily="Microsoft YaHei UI" ui:WindowHelper.UseModernWindowStyle="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" Topmost="True"
|
||||
Title="Ink Canvas 抽奖 - 点名历史记录" Height="500" Width="400"
|
||||
Loaded="Window_Loaded">
|
||||
<Window.Resources>
|
||||
<!-- 主题资源 -->
|
||||
<SolidColorBrush x:Key="RollCallHistoryWindowBackground" Color="White"/>
|
||||
<SolidColorBrush x:Key="RollCallHistoryWindowForeground" Color="Black"/>
|
||||
<SolidColorBrush x:Key="RollCallHistoryWindowButtonBackground" Color="#F4F4F5"/>
|
||||
<SolidColorBrush x:Key="RollCallHistoryWindowButtonForeground" Color="Black"/>
|
||||
<SolidColorBrush x:Key="RollCallHistoryWindowBorderBrush" Color="#E4E4E7"/>
|
||||
</Window.Resources>
|
||||
<Grid Background="{DynamicResource RollCallHistoryWindowBackground}">
|
||||
<Label Content="点名历史记录"
|
||||
Margin="10"
|
||||
Foreground="{DynamicResource RollCallHistoryWindowForeground}"
|
||||
FontFamily="Microsoft YaHei UI"/>
|
||||
<TextBox Name="TextBoxHistory"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
Margin="10,40,10,50"
|
||||
Background="{DynamicResource RollCallHistoryWindowBackground}"
|
||||
Foreground="{DynamicResource RollCallHistoryWindowForeground}"
|
||||
BorderBrush="{DynamicResource RollCallHistoryWindowBorderBrush}"/>
|
||||
<Button Margin="10"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
Content="关闭"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
Width="100"
|
||||
Click="Button_Click"
|
||||
Background="{DynamicResource RollCallHistoryWindowButtonBackground}"
|
||||
Foreground="{DynamicResource RollCallHistoryWindowButtonForeground}"
|
||||
BorderBrush="{DynamicResource RollCallHistoryWindowBorderBrush}"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Newtonsoft.Json;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for RollCallHistoryWindow.xaml
|
||||
/// </summary>
|
||||
public partial class RollCallHistoryWindow : Window
|
||||
{
|
||||
public RollCallHistoryWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
|
||||
ApplyTheme();
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LoadHistory();
|
||||
}
|
||||
|
||||
private void LoadHistory()
|
||||
{
|
||||
try
|
||||
{
|
||||
string configsFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs");
|
||||
string historyJsonPath = System.IO.Path.Combine(configsFolder, "RollCallHistory.json");
|
||||
|
||||
if (!File.Exists(historyJsonPath))
|
||||
{
|
||||
TextBoxHistory.Text = "暂无历史记录";
|
||||
return;
|
||||
}
|
||||
|
||||
string jsonContent = File.ReadAllText(historyJsonPath);
|
||||
var historyData = JsonConvert.DeserializeObject<RollCallHistoryData>(jsonContent);
|
||||
|
||||
if (historyData == null || historyData.History == null || historyData.History.Count == 0)
|
||||
{
|
||||
TextBoxHistory.Text = "暂无历史记录";
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算每个名字的总累计抽选次数(用于统计信息)
|
||||
var nameCountDict = new System.Collections.Generic.Dictionary<string, int>();
|
||||
if (historyData.NameFrequency != null && historyData.NameFrequency.Count > 0)
|
||||
{
|
||||
// 使用已保存的频率统计
|
||||
foreach (var kvp in historyData.NameFrequency)
|
||||
{
|
||||
nameCountDict[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有频率统计,从历史记录中计算
|
||||
foreach (var name in historyData.History)
|
||||
{
|
||||
if (nameCountDict.ContainsKey(name))
|
||||
nameCountDict[name]++;
|
||||
else
|
||||
nameCountDict[name] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算历史记录中每条记录出现时的累计次数(按时间正序)
|
||||
var historyWithCount = new System.Collections.Generic.List<System.Tuple<string, int>>();
|
||||
var runningCount = new System.Collections.Generic.Dictionary<string, int>();
|
||||
foreach (var name in historyData.History)
|
||||
{
|
||||
if (runningCount.ContainsKey(name))
|
||||
runningCount[name]++;
|
||||
else
|
||||
runningCount[name] = 1;
|
||||
historyWithCount.Add(new System.Tuple<string, int>(name, runningCount[name]));
|
||||
}
|
||||
|
||||
// 按时间倒序显示(最新的在上方)
|
||||
historyWithCount.Reverse();
|
||||
|
||||
// 显示历史记录,每行显示:名字 (累计X次)
|
||||
var historyLines = new System.Collections.Generic.List<string>();
|
||||
foreach (var item in historyWithCount)
|
||||
{
|
||||
historyLines.Add($"{item.Item1} (最近累计{item.Item2}次)");
|
||||
}
|
||||
|
||||
// 显示统计信息
|
||||
int totalCount = historyData.History.Count;
|
||||
string lastUpdate = historyData.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// 计算累计统计信息
|
||||
var statsLines = new System.Collections.Generic.List<string>();
|
||||
statsLines.Add($"");
|
||||
statsLines.Add($"");
|
||||
statsLines.Add($"累计抽选次数统计:");
|
||||
|
||||
// 按累计次数降序排序显示
|
||||
var sortedStats = nameCountDict.OrderByDescending(kvp => kvp.Value).ToList();
|
||||
foreach (var kvp in sortedStats)
|
||||
{
|
||||
statsLines.Add($" {kvp.Key}: {kvp.Value}次");
|
||||
}
|
||||
|
||||
statsLines.Add($"");
|
||||
statsLines.Add($"共 {totalCount} 条记录,最后更新:{lastUpdate}");
|
||||
|
||||
// 组合历史记录和统计信息
|
||||
TextBoxHistory.Text = string.Join(Environment.NewLine, historyLines) +
|
||||
Environment.NewLine +
|
||||
string.Join(Environment.NewLine, statsLines);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TextBoxHistory.Text = $"加载历史记录失败: {ex.Message}";
|
||||
LogHelper.WriteLogToFile($"加载点名历史记录失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings != null)
|
||||
{
|
||||
ApplyTheme(MainWindow.Settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用历史记录窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTheme(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (settings.Appearance.Theme == 0) // 浅色主题
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
|
||||
ApplyThemeResources("Light");
|
||||
}
|
||||
else if (settings.Appearance.Theme == 1) // 深色主题
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
|
||||
ApplyThemeResources("Dark");
|
||||
}
|
||||
else // 跟随系统主题
|
||||
{
|
||||
bool isSystemLight = IsSystemThemeLight();
|
||||
if (isSystemLight)
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
|
||||
ApplyThemeResources("Light");
|
||||
}
|
||||
else
|
||||
{
|
||||
iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
|
||||
ApplyThemeResources("Dark");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用历史记录窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyThemeResources(string theme)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resources = this.Resources;
|
||||
|
||||
if (theme == "Light")
|
||||
{
|
||||
// 应用浅色主题资源
|
||||
resources["RollCallHistoryWindowBackground"] = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
resources["RollCallHistoryWindowForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
resources["RollCallHistoryWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(244, 244, 245));
|
||||
resources["RollCallHistoryWindowButtonForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
resources["RollCallHistoryWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(228, 228, 231));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 应用深色主题资源
|
||||
resources["RollCallHistoryWindowBackground"] = new SolidColorBrush(Color.FromRgb(31, 31, 31)); // #1f1f1f
|
||||
resources["RollCallHistoryWindowForeground"] = new SolidColorBrush(Colors.White);
|
||||
resources["RollCallHistoryWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(42, 42, 42)); // #2a2a2a
|
||||
resources["RollCallHistoryWindowButtonForeground"] = new SolidColorBrush(Colors.White);
|
||||
resources["RollCallHistoryWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(224, 224, 224)); // #E0E0E0
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用历史记录窗口主题资源出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
try
|
||||
{
|
||||
var registryKey = Microsoft.Win32.Registry.CurrentUser;
|
||||
var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||
if (themeKey != null)
|
||||
{
|
||||
var value = themeKey.GetValue("AppsUseLightTheme");
|
||||
if (value != null)
|
||||
{
|
||||
light = (int)value == 1;
|
||||
}
|
||||
themeKey.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
light = true;
|
||||
}
|
||||
return light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,9 +393,9 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
case 1: // 跟随四季
|
||||
var month = DateTime.Now.Month;
|
||||
if (month >= 3 && month <= 5) return GetImageNameByStyle(2); // 春季
|
||||
if (month >= 6 && month <= 8) return GetImageNameByStyle(3); // 夏季
|
||||
if (month >= 9 && month <= 11) return GetImageNameByStyle(4); // 秋季
|
||||
if (month >= 2 && month <= 4) return GetImageNameByStyle(2); // 春季
|
||||
if (month >= 5 && month <= 7) return GetImageNameByStyle(3); // 夏季
|
||||
if (month >= 8 && month <= 10) return GetImageNameByStyle(4); // 秋季
|
||||
return GetImageNameByStyle(5); // 冬季
|
||||
|
||||
case 2: // 春季
|
||||
|
||||
+483
-303
@@ -1,31 +1,35 @@
|
||||
<Window x:Class="Ink_Canvas.NewStyleTimerWindow"
|
||||
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"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:controls="clr-namespace:Ink_Canvas.Windows.Controls"
|
||||
Topmost="True" Background="Transparent"
|
||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True"
|
||||
Loaded="Window_Loaded" Closing="Window_Closing" WindowStartupLocation="CenterScreen"
|
||||
MouseMove="Window_MouseMove" MouseEnter="Window_MouseEnter"
|
||||
Title="Ink Canvas 画板 - 计时器" Height="450" Width="900">
|
||||
|
||||
<Window.Resources>
|
||||
<UserControl x:Class="Ink_Canvas.Windows.TimerControl"
|
||||
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"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:controls="clr-namespace:Ink_Canvas.Windows.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="900">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Controls/WinUI3CloseButton.xaml" />
|
||||
<ResourceDictionary Source="DigitResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border Background="{DynamicResource NewTimerWindowBackground}" CornerRadius="15" BorderThickness="1" BorderBrush="{DynamicResource NewTimerWindowBorderBrush}" Margin="10" x:Name="MainBorder" MouseLeftButtonDown="WindowDragMove">
|
||||
<Border Background="{DynamicResource NewTimerWindowBackground}"
|
||||
CornerRadius="15"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource NewTimerWindowBorderBrush}"
|
||||
Margin="10"
|
||||
x:Name="MainBorder"
|
||||
MouseLeftButtonDown="MainBorder_MouseLeftButtonDown">
|
||||
<Grid>
|
||||
<controls:WinUI3CloseButton x:Name="CloseButton"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="0,0,0,0" Cursor="Hand" Click="CloseButton_Click"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,0,0,0"
|
||||
Cursor="Hand"
|
||||
Click="CloseButton_Click"
|
||||
Content="✕"/>
|
||||
<!-- 主要内容区域 -->
|
||||
<Grid>
|
||||
@@ -33,8 +37,16 @@
|
||||
<Viewbox x:Name="MainViewController" Margin="20,20,20,20">
|
||||
<Grid Height="400" Width="900">
|
||||
<!-- 顶部标题栏 -->
|
||||
<Grid Height="50" Background="{DynamicResource NewTimerWindowBackground}" x:Name="TitleBar" VerticalAlignment="Top" MouseLeftButtonDown="WindowDragMove" Margin="0,0,450,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="22,0,0,0">
|
||||
<Grid Height="50"
|
||||
Background="{DynamicResource NewTimerWindowBackground}"
|
||||
x:Name="TitleBar"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,0,450,0"
|
||||
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="22,0,0,0">
|
||||
<!-- 计时器图标 -->
|
||||
<Path Data="M5 13a7 7 0 1 0 14 0a7 7 0 0 0 -14 0z M14.5 10.5l-2.5 2.5 M17 8l1 -1 M14 3h-4"
|
||||
Stroke="{DynamicResource NewTimerWindowTitleForeground}"
|
||||
@@ -45,35 +57,56 @@
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<!-- 计时文字 -->
|
||||
<TextBlock Text="计时" FontSize="28" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewTimerWindowTitleForeground}" x:Name="TitleText"/>
|
||||
<TextBlock Text="计时"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewTimerWindowTitleForeground}"
|
||||
x:Name="TitleText"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 主要内容区域 - 分为左右两部分 -->
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center"
|
||||
x:Name="MainContentGrid" Margin="0,-25,0,25">
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
x:Name="MainContentGrid"
|
||||
Margin="0,-25,0,25">
|
||||
<!-- 左侧:6位数字显示区域 -->
|
||||
<Grid HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
x:Name="MainDisplayGrid" Width="500" Margin="-10,0,0,0">
|
||||
<!-- 6位数字水平排列 -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
|
||||
x:Name="DigitsPanel">
|
||||
<!-- 小时组(十位和个位) -->
|
||||
<Grid Margin="0,0,20,0" HorizontalAlignment="Center" Width="120">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<!-- 第1位数字(小时十位) -->
|
||||
<Grid Margin="0,0,15,0">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit1PlusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit1Plus_Click" Cursor="Hand" Margin="0,0,0,15" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Grid HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
x:Name="MainDisplayGrid"
|
||||
Width="500"
|
||||
Margin="-10,0,0,0">
|
||||
<!-- 6位数字水平排列 -->
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
x:Name="DigitsPanel">
|
||||
<!-- 小时组(十位和个位) -->
|
||||
<Grid Margin="0,0,20,0"
|
||||
HorizontalAlignment="Center"
|
||||
Width="120">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 第1位数字(小时十位) -->
|
||||
<Grid Margin="0,0,15,0">
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit1PlusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit1Plus_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,0,15"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M12 5l0 14 M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -83,25 +116,33 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit1Display" Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit1MinusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit1Minus_Click" Cursor="Hand" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit1Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit1MinusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit1Minus_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -111,23 +152,31 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 第2位数字(小时个位) -->
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit2PlusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit2Plus_Click" Cursor="Hand" Margin="0,0,0,15" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 第2位数字(小时个位) -->
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit2PlusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit2Plus_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,0,15"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M12 5l0 14 M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -137,25 +186,33 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit2Display" Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit2MinusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit2Minus_Click" Cursor="Hand" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit2Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit2MinusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit2Minus_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -165,34 +222,48 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 冒号分隔符 -->
|
||||
<TextBlock x:Name="Colon1Display" Text=":" FontSize="48" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
<TextBlock x:Name="Colon1Display"
|
||||
Text=":"
|
||||
FontSize="48"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Margin="-20,0,0,0"/>
|
||||
|
||||
<!-- 分钟组(十位和个位) -->
|
||||
<Grid Margin="0,0,20,0" HorizontalAlignment="Center" Width="120">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<!-- 第3位数字(分钟十位) -->
|
||||
<Grid Margin="0,0,15,0">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit3PlusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit3Plus_Click" Cursor="Hand" Margin="0,0,0,15" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<!-- 分钟组(十位和个位) -->
|
||||
<Grid Margin="0,0,20,0"
|
||||
HorizontalAlignment="Center"
|
||||
Width="120">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 第3位数字(分钟十位) -->
|
||||
<Grid Margin="0,0,15,0">
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit3PlusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit3Plus_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,0,15"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M12 5l0 14 M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -202,25 +273,33 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit3Display" Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit3MinusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit3Minus_Click" Cursor="Hand" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit3Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit3MinusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit3Minus_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -230,23 +309,31 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 第4位数字(分钟个位) -->
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit4PlusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit4Plus_Click" Cursor="Hand" Margin="0,0,0,15" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 第4位数字(分钟个位) -->
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit4PlusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit4Plus_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,0,15"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M12 5l0 14 M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -256,25 +343,33 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit4Display" Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit4MinusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit4Minus_Click" Cursor="Hand" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit4Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit4MinusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit4Minus_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -284,34 +379,46 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 冒号分隔符 -->
|
||||
<TextBlock x:Name="Colon2Display" Text=":" FontSize="48" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
<TextBlock x:Name="Colon2Display"
|
||||
Text=":"
|
||||
FontSize="48"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Margin="-20,0,0,0"/>
|
||||
|
||||
<!-- 秒组(十位和个位) -->
|
||||
<Grid Width="120">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<!-- 第5位数字(秒十位) -->
|
||||
<Grid Margin="0,0,15,0">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit5PlusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit5Plus_Click" Cursor="Hand" Margin="0,0,0,15" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<!-- 秒组(十位和个位) -->
|
||||
<Grid Width="120">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 第5位数字(秒十位) -->
|
||||
<Grid Margin="0,0,15,0">
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit5PlusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit5Plus_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,0,15"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M12 5l0 14 M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -321,25 +428,33 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit5Display" Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit5MinusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit5Minus_Click" Cursor="Hand" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit5Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit5MinusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit5Minus_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -349,23 +464,31 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 第6位数字(秒个位) -->
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit6PlusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit6Plus_Click" Cursor="Hand" Margin="0,0,0,15" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 第6位数字(秒个位) -->
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 上方+按钮 -->
|
||||
<Button x:Name="Digit6PlusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit6Plus_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,0,15"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M12 5l0 14 M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -375,25 +498,33 @@
|
||||
Fill="Transparent"
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"/>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit6Display" Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit6MinusBtn" Width="40" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Digit6Minus_Click" Cursor="Hand" HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- 数字显示 -->
|
||||
<Path x:Name="Digit6Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="{DynamicResource NewTimerWindowDigitForeground}"
|
||||
Width="48" Height="48"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,15"/>
|
||||
<!-- 下方-按钮 -->
|
||||
<Button x:Name="Digit6MinusBtn"
|
||||
Width="40" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Digit6Minus_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Center">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="15">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path Data="M5 12l14 0"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -410,16 +541,22 @@
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<Border Width="2" Background="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
Opacity="0.3" HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||
<Border Width="2"
|
||||
Background="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
Opacity="0.3"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="465,0,0,0"/>
|
||||
|
||||
<!-- 右侧:快捷选项区域 -->
|
||||
<Grid HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
x:Name="QuickOptionsGrid" Width="400" Margin="470,0,0,0" Height="200">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<Grid HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
x:Name="QuickOptionsGrid"
|
||||
Width="400"
|
||||
Margin="470,0,0,0"
|
||||
Height="200">
|
||||
<StackPanel Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- Segmented Control -->
|
||||
<Grid Margin="0,0,0,20">
|
||||
<Border Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
@@ -436,7 +573,6 @@
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,0,0"/>
|
||||
|
||||
<!-- 按钮容器 -->
|
||||
<Grid>
|
||||
<Button x:Name="CommonTabBtn"
|
||||
@@ -446,7 +582,10 @@
|
||||
Click="CommonTab_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock x:Name="CommonTabText" Text="常用" FontSize="16" FontWeight="Bold"
|
||||
<TextBlock x:Name="CommonTabText"
|
||||
Text="常用"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewTimerWindowButtonForeground}"/>
|
||||
</Button>
|
||||
<Button x:Name="RecentTabBtn"
|
||||
@@ -456,14 +595,17 @@
|
||||
Click="RecentTab_Click"
|
||||
Cursor="Hand"
|
||||
HorizontalAlignment="Right">
|
||||
<TextBlock x:Name="RecentTabText" Text="最近" FontSize="16" FontWeight="Normal"
|
||||
Foreground="{DynamicResource NewTimerWindowButtonForeground}" Opacity="0.6"/>
|
||||
<TextBlock x:Name="RecentTabText"
|
||||
Text="最近"
|
||||
FontSize="16"
|
||||
FontWeight="Normal"
|
||||
Foreground="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
Opacity="0.6"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- 常用计时区域 -->
|
||||
<Grid x:Name="CommonTimersGrid" Visibility="Visible">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
@@ -618,9 +760,8 @@
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 最近计时区域 -->
|
||||
<Grid x:Name="RecentTimersGrid" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center">
|
||||
@@ -662,7 +803,7 @@
|
||||
<TextBlock x:Name="RecentTimer3Text" Text="--:--" FontSize="16" FontWeight="Bold"
|
||||
Foreground="{DynamicResource NewTimerWindowButtonForeground}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 第二排:最近3个 -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
@@ -708,21 +849,39 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部控制按钮区域 -->
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Grid HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0,0,0,20">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<!-- 全屏按钮 -->
|
||||
<Button x:Name="FullscreenBtn" Width="80" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Fullscreen_Click" Cursor="Hand" Margin="0,0,30,0">
|
||||
<Button x:Name="FullscreenBtn"
|
||||
Width="80" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Fullscreen_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,30,0"
|
||||
IsEnabled="False">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="8">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="8"
|
||||
Opacity="1">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.5"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<Path Data="M4 8v-2a2 2 0 0 1 2 -2h2 M4 16v2a2 2 0 0 0 2 2h2 M16 4h2a2 2 0 0 1 2 2v2 M16 20h2a2 2 0 0 0 2 -2v-2"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -731,37 +890,55 @@
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,5,0"/>
|
||||
<TextBlock Text="全屏" FontSize="16" Foreground="{DynamicResource NewTimerWindowButtonForeground}"/>
|
||||
<TextBlock Text="全屏"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource NewTimerWindowButtonForeground}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- 开始/暂停按钮 -->
|
||||
<Button x:Name="StartPauseBtn" Width="80" Height="80" Background="{DynamicResource NewTimerWindowPrimaryButtonBackground}"
|
||||
BorderThickness="0" Click="StartPause_Click" Cursor="Hand" Margin="0,0,30,0">
|
||||
<Button x:Name="StartPauseBtn"
|
||||
Width="80" Height="80"
|
||||
Background="{DynamicResource NewTimerWindowPrimaryButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="StartPause_Click"
|
||||
Cursor="Hand"
|
||||
Margin="0,0,30,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="40">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="40">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Path x:Name="StartPauseIcon" Data="M6.5 4.00004V20C6.49995 20.178 6.54737 20.3527 6.63738 20.5062C6.72739 20.6597 6.85672 20.7864 7.01202 20.8732C7.16733 20.96 7.34299 21.0038 7.52088 21.0001C7.69878 20.9964 7.87245 20.9453 8.024 20.852L21.024 12.852C21.1696 12.7626 21.2898 12.6373 21.3733 12.4881C21.4567 12.339 21.5005 12.1709 21.5005 12C21.5005 11.8291 21.4567 11.6611 21.3733 11.512C21.2898 11.3628 21.1696 11.2375 21.024 11.148L8.024 3.14804C7.87245 3.0548 7.69878 3.00369 7.52088 2.99997C7.34299 2.99626 7.16733 3.04007 7.01202 3.1269C6.85672 3.21372 6.72739 3.34042 6.63738 3.4939C6.54737 3.64739 6.49995 3.82211 6.5 4.00004Z"
|
||||
Fill="{DynamicResource NewTimerWindowPrimaryButtonForeground}"
|
||||
Width="24" Height="24" Margin="2,0,0,0"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="Fill"/>
|
||||
<Path x:Name="StartPauseIcon"
|
||||
Data="M6.5 4.00004V20C6.49995 20.178 6.54737 20.3527 6.63738 20.5062C6.72739 20.6597 6.85672 20.7864 7.01202 20.8732C7.16733 20.96 7.34299 21.0038 7.52088 21.0001C7.69878 20.9964 7.87245 20.9453 8.024 20.852L21.024 12.852C21.1696 12.7626 21.2898 12.6373 21.3733 12.4881C21.4567 12.339 21.5005 12.1709 21.5005 12C21.5005 11.8291 21.4567 11.6611 21.3733 11.512C21.2898 11.3628 21.1696 11.2375 21.024 11.148L8.024 3.14804C7.87245 3.0548 7.69878 3.00369 7.52088 2.99997C7.34299 2.99626 7.16733 3.04007 7.01202 3.1269C6.85672 3.21372 6.72739 3.34042 6.63738 3.4939C6.54737 3.64739 6.49995 3.82211 6.5 4.00004Z"
|
||||
Fill="{DynamicResource NewTimerWindowPrimaryButtonForeground}"
|
||||
Width="24" Height="24"
|
||||
Margin="2,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Fill"/>
|
||||
</Button>
|
||||
|
||||
<!-- 重置按钮 -->
|
||||
<Button x:Name="ResetBtn" Width="80" Height="40" Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0" Click="Reset_Click" Cursor="Hand">
|
||||
<Button x:Name="ResetBtn"
|
||||
Width="80" Height="40"
|
||||
Background="{DynamicResource NewTimerWindowButtonBackground}"
|
||||
BorderThickness="0"
|
||||
Click="Reset_Click"
|
||||
Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="8">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="8">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<Path Data="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4 M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"
|
||||
Stroke="{DynamicResource NewTimerWindowButtonForeground}"
|
||||
StrokeThickness="2"
|
||||
@@ -770,7 +947,9 @@
|
||||
Width="20" Height="20"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,5,0"/>
|
||||
<TextBlock Text="重置" FontSize="16" Foreground="{DynamicResource NewTimerWindowButtonForeground}"/>
|
||||
<TextBlock Text="重置"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource NewTimerWindowButtonForeground}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@@ -780,4 +959,5 @@
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
</UserControl>
|
||||
|
||||
+352
-229
@@ -6,14 +6,12 @@ using System.Timers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Newtonsoft.Json;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas
|
||||
using Microsoft.Win32;
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
/// <summary>
|
||||
/// 最近计时记录数据模型
|
||||
@@ -31,12 +29,11 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 新计时器UI风格的倒计时器窗口
|
||||
/// </summary>
|
||||
public partial class NewStyleTimerWindow : Window
|
||||
public partial class TimerControl : UserControl
|
||||
{
|
||||
public NewStyleTimerWindow()
|
||||
public TimerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
|
||||
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
timer.Interval = 50;
|
||||
@@ -49,10 +46,69 @@ namespace Ink_Canvas
|
||||
hideTimer = new Timer(1000); // 每秒检查一次
|
||||
hideTimer.Elapsed += HideTimer_Elapsed;
|
||||
lastActivityTime = DateTime.Now;
|
||||
|
||||
// 添加窗口加载事件处理,确保置顶
|
||||
Loaded += TimerWindow_Loaded;
|
||||
|
||||
// 监听主题变化事件
|
||||
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
|
||||
// 监听卸载事件,清理资源
|
||||
Unloaded += TimerControl_Unloaded;
|
||||
}
|
||||
|
||||
private void TimerControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 取消订阅主题变化事件
|
||||
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
// 当主题变化时,重新应用主题
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
RefreshTheme();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新主题(供外部调用)
|
||||
/// </summary>
|
||||
public void RefreshTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 重新应用主题
|
||||
ApplyTheme();
|
||||
|
||||
// 强制刷新UI
|
||||
InvalidateVisual();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新计时器窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#region 事件定义
|
||||
/// <summary>
|
||||
/// 计时器完成事件
|
||||
/// </summary>
|
||||
public event EventHandler TimerCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 关闭事件 - 通知主窗口隐藏容器
|
||||
/// </summary>
|
||||
public event EventHandler CloseRequested;
|
||||
|
||||
/// <summary>
|
||||
/// 显示最小化视图事件
|
||||
/// </summary>
|
||||
public event EventHandler ShowMinimizedRequested;
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏最小化视图事件
|
||||
/// </summary>
|
||||
public event EventHandler HideMinimizedRequested;
|
||||
#endregion
|
||||
|
||||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
@@ -116,6 +172,12 @@ namespace Ink_Canvas
|
||||
StartPauseIcon.Data = Geometry.Parse(PlayIconData);
|
||||
PlayTimerSound();
|
||||
|
||||
// 禁用全屏按钮
|
||||
if (FullscreenBtn != null)
|
||||
{
|
||||
FullscreenBtn.IsEnabled = false;
|
||||
}
|
||||
|
||||
TimerCompleted?.Invoke(this, EventArgs.Empty);
|
||||
HandleTimerCompletion();
|
||||
}
|
||||
@@ -127,15 +189,23 @@ namespace Ink_Canvas
|
||||
int displayHours = totalHours;
|
||||
|
||||
if (displayHours > 99) displayHours = 99;
|
||||
if (displayHours < 0) displayHours = 0;
|
||||
|
||||
bool shouldShowRed = MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true;
|
||||
|
||||
SetDigitDisplay("Digit1Display", displayHours / 10, shouldShowRed);
|
||||
SetDigitDisplay("Digit2Display", displayHours % 10, shouldShowRed);
|
||||
SetDigitDisplay("Digit3Display", overtimeSpan.Minutes / 10, shouldShowRed);
|
||||
SetDigitDisplay("Digit4Display", overtimeSpan.Minutes % 10, shouldShowRed);
|
||||
SetDigitDisplay("Digit5Display", overtimeSpan.Seconds / 10, shouldShowRed);
|
||||
SetDigitDisplay("Digit6Display", overtimeSpan.Seconds % 10, shouldShowRed);
|
||||
int hoursTens = Math.Max(0, Math.Min(9, Math.Abs(displayHours / 10) % 10));
|
||||
int hoursOnes = Math.Max(0, Math.Min(9, (displayHours % 10 + 10) % 10));
|
||||
int minutesTens = Math.Max(0, Math.Min(9, Math.Abs(overtimeSpan.Minutes) / 10));
|
||||
int minutesOnes = Math.Max(0, Math.Min(9, Math.Abs(overtimeSpan.Minutes) % 10));
|
||||
int secondsTens = Math.Max(0, Math.Min(9, Math.Abs(overtimeSpan.Seconds) / 10));
|
||||
int secondsOnes = Math.Max(0, Math.Min(9, Math.Abs(overtimeSpan.Seconds) % 10));
|
||||
|
||||
SetDigitDisplay("Digit1Display", hoursTens, shouldShowRed);
|
||||
SetDigitDisplay("Digit2Display", hoursOnes, shouldShowRed);
|
||||
SetDigitDisplay("Digit3Display", minutesTens, shouldShowRed);
|
||||
SetDigitDisplay("Digit4Display", minutesOnes, shouldShowRed);
|
||||
SetDigitDisplay("Digit5Display", secondsTens, shouldShowRed);
|
||||
SetDigitDisplay("Digit6Display", secondsOnes, shouldShowRed);
|
||||
|
||||
SetColonDisplay(shouldShowRed);
|
||||
}
|
||||
@@ -160,11 +230,7 @@ namespace Ink_Canvas
|
||||
|
||||
Timer timer = new Timer();
|
||||
private Timer hideTimer;
|
||||
private MinimizedTimerWindow minimizedWindow;
|
||||
private DateTime lastActivityTime;
|
||||
private bool isFullscreenMode = false;
|
||||
private FullscreenTimerWindow fullscreenWindow;
|
||||
public event EventHandler TimerCompleted;
|
||||
private DateTime lastActivityTime;
|
||||
public TimeSpan? GetTotalTimeSpan()
|
||||
{
|
||||
return new TimeSpan(hour, minute, second);
|
||||
@@ -270,6 +336,9 @@ namespace Ink_Canvas
|
||||
SetDarkThemeBorder();
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数字和冒号显示的颜色
|
||||
UpdateDigitDisplays();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -314,47 +383,6 @@ namespace Ink_Canvas
|
||||
SetColonDisplay(false);
|
||||
}
|
||||
|
||||
private void HideTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 只有在计时器运行时且不在全屏模式下才检查自动隐藏
|
||||
if (isTimerRunning && !isPaused && !isFullscreenMode)
|
||||
{
|
||||
var timeSinceLastActivity = DateTime.Now - lastActivityTime;
|
||||
if (timeSinceLastActivity.TotalSeconds >= 5)
|
||||
{
|
||||
ShowMinimizedWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowMinimizedWindow()
|
||||
{
|
||||
if (minimizedWindow == null || !minimizedWindow.IsVisible)
|
||||
{
|
||||
minimizedWindow = new MinimizedTimerWindow(this);
|
||||
minimizedWindow.Show();
|
||||
|
||||
// 确保最小化窗口也置顶
|
||||
minimizedWindow.Topmost = true;
|
||||
|
||||
// 隐藏主窗口
|
||||
this.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateActivityTime()
|
||||
{
|
||||
lastActivityTime = DateTime.Now;
|
||||
}
|
||||
|
||||
public void SetFullscreenMode(bool isFullscreen)
|
||||
{
|
||||
isFullscreenMode = isFullscreen;
|
||||
}
|
||||
|
||||
// 更新剩余时间
|
||||
private void UpdateRemainingTime()
|
||||
{
|
||||
@@ -430,10 +458,12 @@ namespace Ink_Canvas
|
||||
if (isPaused) return null;
|
||||
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
var totalSeconds = hour * 3600 + minute * 60 + second;
|
||||
var remaining = totalSeconds - elapsed.TotalSeconds;
|
||||
var totalTimeSpan = new TimeSpan(hour, minute, second);
|
||||
var leftTimeSpan = totalTimeSpan - elapsed;
|
||||
|
||||
return TimeSpan.FromSeconds(remaining);
|
||||
if (leftTimeSpan.Milliseconds > 0) leftTimeSpan += new TimeSpan(0, 0, 1);
|
||||
|
||||
return leftTimeSpan;
|
||||
}
|
||||
|
||||
public void StopTimer()
|
||||
@@ -443,15 +473,6 @@ namespace Ink_Canvas
|
||||
StartPauseIcon.Data = Geometry.Parse(PlayIconData);
|
||||
}
|
||||
|
||||
private void Window_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
}
|
||||
|
||||
private void Window_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据数字值设置SVG数字显示
|
||||
@@ -464,6 +485,8 @@ namespace Ink_Canvas
|
||||
var path = this.FindName(pathName) as System.Windows.Shapes.Path;
|
||||
if (path != null)
|
||||
{
|
||||
digit = Math.Max(0, Math.Min(9, digit));
|
||||
|
||||
string resourceKey = $"Digit{digit}";
|
||||
var geometry = this.FindResource(resourceKey) as Geometry;
|
||||
if (geometry != null)
|
||||
@@ -477,7 +500,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
var defaultBrush = this.TryFindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
path.Fill = defaultBrush;
|
||||
@@ -507,7 +530,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
var defaultBrush = this.TryFindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
colon1.Foreground = defaultBrush;
|
||||
@@ -527,7 +550,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
var defaultBrush = this.TryFindResource("NewTimerWindowDigitForeground") as Brush;
|
||||
if (defaultBrush != null)
|
||||
{
|
||||
colon2.Foreground = defaultBrush;
|
||||
@@ -544,6 +567,7 @@ namespace Ink_Canvas
|
||||
private void Digit1Plus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentHour = hour;
|
||||
int hourTens = currentHour / 10;
|
||||
int hourOnes = currentHour % 10;
|
||||
@@ -558,6 +582,7 @@ namespace Ink_Canvas
|
||||
private void Digit1Minus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentHour = hour;
|
||||
int hourTens = currentHour / 10;
|
||||
int hourOnes = currentHour % 10;
|
||||
@@ -573,6 +598,7 @@ namespace Ink_Canvas
|
||||
private void Digit2Plus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentHour = hour;
|
||||
int hourTens = currentHour / 10;
|
||||
int hourOnes = currentHour % 10;
|
||||
@@ -592,6 +618,7 @@ namespace Ink_Canvas
|
||||
private void Digit2Minus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentHour = hour;
|
||||
int hourTens = currentHour / 10;
|
||||
int hourOnes = currentHour % 10;
|
||||
@@ -612,6 +639,7 @@ namespace Ink_Canvas
|
||||
private void Digit3Plus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentMinute = minute;
|
||||
int minuteTens = currentMinute / 10;
|
||||
int minuteOnes = currentMinute % 10;
|
||||
@@ -626,6 +654,7 @@ namespace Ink_Canvas
|
||||
private void Digit3Minus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentMinute = minute;
|
||||
int minuteTens = currentMinute / 10;
|
||||
int minuteOnes = currentMinute % 10;
|
||||
@@ -641,6 +670,7 @@ namespace Ink_Canvas
|
||||
private void Digit4Plus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentMinute = minute;
|
||||
int minuteTens = currentMinute / 10;
|
||||
int minuteOnes = currentMinute % 10;
|
||||
@@ -660,6 +690,7 @@ namespace Ink_Canvas
|
||||
private void Digit4Minus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentMinute = minute;
|
||||
int minuteTens = currentMinute / 10;
|
||||
int minuteOnes = currentMinute % 10;
|
||||
@@ -680,6 +711,7 @@ namespace Ink_Canvas
|
||||
private void Digit5Plus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentSecond = second;
|
||||
int secondTens = currentSecond / 10;
|
||||
int secondOnes = currentSecond % 10;
|
||||
@@ -694,6 +726,7 @@ namespace Ink_Canvas
|
||||
private void Digit5Minus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentSecond = second;
|
||||
int secondTens = currentSecond / 10;
|
||||
int secondOnes = currentSecond % 10;
|
||||
@@ -709,6 +742,7 @@ namespace Ink_Canvas
|
||||
private void Digit6Plus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentSecond = second;
|
||||
int secondTens = currentSecond / 10;
|
||||
int secondOnes = currentSecond % 10;
|
||||
@@ -728,6 +762,7 @@ namespace Ink_Canvas
|
||||
private void Digit6Minus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning) return;
|
||||
UpdateActivityTime();
|
||||
int currentSecond = second;
|
||||
int secondTens = currentSecond / 10;
|
||||
int secondOnes = currentSecond % 10;
|
||||
@@ -750,6 +785,7 @@ namespace Ink_Canvas
|
||||
|
||||
private void StartPause_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
if (isPaused && isTimerRunning)
|
||||
{
|
||||
// 继续计时
|
||||
@@ -786,34 +822,49 @@ namespace Ink_Canvas
|
||||
// 启动隐藏定时器
|
||||
hideTimer.Start();
|
||||
|
||||
// 确保计时器窗口置顶
|
||||
ApplyTimerWindowTopmost();
|
||||
|
||||
// 保存到最近计时记录
|
||||
SaveRecentTimer();
|
||||
|
||||
// 启用全屏按钮
|
||||
if (FullscreenBtn != null)
|
||||
{
|
||||
FullscreenBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isTimerRunning)
|
||||
UpdateActivityTime();
|
||||
|
||||
if (isTimerRunning)
|
||||
{
|
||||
UpdateDigitDisplays();
|
||||
isOvertimeMode = false;
|
||||
}
|
||||
else if (isTimerRunning && isPaused)
|
||||
{
|
||||
UpdateDigitDisplays();
|
||||
StartPauseIcon.Data = Geometry.Parse(PlayIconData);
|
||||
isTimerRunning = false;
|
||||
// 停止计时器
|
||||
timer.Stop();
|
||||
isTimerRunning = false;
|
||||
isPaused = false;
|
||||
isOvertimeMode = false;
|
||||
|
||||
if (hideTimer != null)
|
||||
{
|
||||
hideTimer.Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
UpdateDigitDisplays();
|
||||
SetColonDisplay(false);
|
||||
|
||||
if (StartPauseIcon != null)
|
||||
{
|
||||
startTime = DateTime.Now;
|
||||
Timer_Elapsed(timer, null);
|
||||
StartPauseIcon.Data = Geometry.Parse(PlayIconData);
|
||||
}
|
||||
|
||||
isOvertimeMode = false;
|
||||
hasPlayedProgressiveReminder = false;
|
||||
|
||||
// 禁用全屏按钮
|
||||
if (FullscreenBtn != null)
|
||||
{
|
||||
FullscreenBtn.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,29 +934,15 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 窗口加载时的初始化
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
isTimerRunning = false;
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void WindowDragMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
DragMove();
|
||||
StopTimer();
|
||||
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void CommonTab_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
CommonTimersGrid.Visibility = Visibility.Visible;
|
||||
RecentTimersGrid.Visibility = Visibility.Collapsed;
|
||||
|
||||
@@ -940,6 +977,7 @@ namespace Ink_Canvas
|
||||
|
||||
private void RecentTab_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
CommonTimersGrid.Visibility = Visibility.Collapsed;
|
||||
RecentTimersGrid.Visibility = Visibility.Visible;
|
||||
|
||||
@@ -976,36 +1014,42 @@ namespace Ink_Canvas
|
||||
private void Common5Min_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning && !isPaused) return;
|
||||
UpdateActivityTime();
|
||||
SetQuickTime(0, 5, 0);
|
||||
}
|
||||
|
||||
private void Common10Min_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning && !isPaused) return;
|
||||
UpdateActivityTime();
|
||||
SetQuickTime(0, 10, 0);
|
||||
}
|
||||
|
||||
private void Common15Min_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning && !isPaused) return;
|
||||
UpdateActivityTime();
|
||||
SetQuickTime(0, 15, 0);
|
||||
}
|
||||
|
||||
private void Common30Min_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning && !isPaused) return;
|
||||
UpdateActivityTime();
|
||||
SetQuickTime(0, 30, 0);
|
||||
}
|
||||
|
||||
private void Common45Min_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning && !isPaused) return;
|
||||
UpdateActivityTime();
|
||||
SetQuickTime(0, 45, 0);
|
||||
}
|
||||
|
||||
private void Common60Min_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isTimerRunning && !isPaused) return;
|
||||
UpdateActivityTime();
|
||||
SetQuickTime(1, 0, 0);
|
||||
}
|
||||
|
||||
@@ -1013,36 +1057,42 @@ namespace Ink_Canvas
|
||||
private void RecentTimer1_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((isTimerRunning && !isPaused) || recentTimer1 == "--:--") return;
|
||||
UpdateActivityTime();
|
||||
ApplyRecentTimer(recentTimer1);
|
||||
}
|
||||
|
||||
private void RecentTimer2_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((isTimerRunning && !isPaused) || recentTimer2 == "--:--") return;
|
||||
UpdateActivityTime();
|
||||
ApplyRecentTimer(recentTimer2);
|
||||
}
|
||||
|
||||
private void RecentTimer3_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((isTimerRunning && !isPaused) || recentTimer3 == "--:--") return;
|
||||
UpdateActivityTime();
|
||||
ApplyRecentTimer(recentTimer3);
|
||||
}
|
||||
|
||||
private void RecentTimer4_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((isTimerRunning && !isPaused) || recentTimer4 == "--:--") return;
|
||||
UpdateActivityTime();
|
||||
ApplyRecentTimer(recentTimer4);
|
||||
}
|
||||
|
||||
private void RecentTimer5_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((isTimerRunning && !isPaused) || recentTimer5 == "--:--") return;
|
||||
UpdateActivityTime();
|
||||
ApplyRecentTimer(recentTimer5);
|
||||
}
|
||||
|
||||
private void RecentTimer6_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if ((isTimerRunning && !isPaused) || recentTimer6 == "--:--") return;
|
||||
UpdateActivityTime();
|
||||
ApplyRecentTimer(recentTimer6);
|
||||
}
|
||||
|
||||
@@ -1182,16 +1232,22 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
RecentTimer1Text.Text = recentTimer1;
|
||||
RecentTimer2Text.Text = recentTimer2;
|
||||
RecentTimer3Text.Text = recentTimer3;
|
||||
RecentTimer4Text.Text = recentTimer4;
|
||||
RecentTimer5Text.Text = recentTimer5;
|
||||
RecentTimer6Text.Text = recentTimer6;
|
||||
var timer1Text = this.FindName("RecentTimer1Text") as TextBlock;
|
||||
var timer2Text = this.FindName("RecentTimer2Text") as TextBlock;
|
||||
var timer3Text = this.FindName("RecentTimer3Text") as TextBlock;
|
||||
var timer4Text = this.FindName("RecentTimer4Text") as TextBlock;
|
||||
var timer5Text = this.FindName("RecentTimer5Text") as TextBlock;
|
||||
var timer6Text = this.FindName("RecentTimer6Text") as TextBlock;
|
||||
|
||||
if (timer1Text != null) timer1Text.Text = recentTimer1;
|
||||
if (timer2Text != null) timer2Text.Text = recentTimer2;
|
||||
if (timer3Text != null) timer3Text.Text = recentTimer3;
|
||||
if (timer4Text != null) timer4Text.Text = recentTimer4;
|
||||
if (timer5Text != null) timer5Text.Text = recentTimer5;
|
||||
if (timer6Text != null) timer6Text.Text = recentTimer6;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果UI元素还未初始化,忽略错误
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1297,135 +1353,202 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private FullscreenTimerWindow fullscreenWindow;
|
||||
|
||||
public bool IsFullscreenWindowOpen => fullscreenWindow != null && fullscreenWindow.IsVisible;
|
||||
|
||||
private void Fullscreen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ShowFullscreenTimer();
|
||||
if (fullscreenWindow != null && fullscreenWindow.IsVisible)
|
||||
{
|
||||
fullscreenWindow.Close();
|
||||
fullscreenWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTimerRunning && !isPaused)
|
||||
{
|
||||
fullscreenWindow = new FullscreenTimerWindow(this);
|
||||
fullscreenWindow.Closed += (s, args) => { fullscreenWindow = null; };
|
||||
fullscreenWindow.Show();
|
||||
HideMinimizedRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowFullscreenTimer()
|
||||
|
||||
private void MainBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 设置全屏模式标志
|
||||
isFullscreenMode = true;
|
||||
UpdateActivityTime();
|
||||
if (e.ClickCount == 1)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
var point = e.GetPosition(timerContainer);
|
||||
var mainWindowPoint = timerContainer.TransformToAncestor(mainWindow).Transform(point);
|
||||
DragTimerContainer(mainWindow, mainWindowPoint, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
if (e.ClickCount == 1)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
var point = e.GetPosition(timerContainer);
|
||||
var mainWindowPoint = timerContainer.TransformToAncestor(mainWindow).Transform(point);
|
||||
DragTimerContainer(mainWindow, mainWindowPoint, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool isDragging = false;
|
||||
private Point dragStartPoint;
|
||||
private Point containerStartPosition;
|
||||
|
||||
private void DragTimerContainer(MainWindow mainWindow, Point startPoint, MouseButtonEventArgs e)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer == null) return;
|
||||
|
||||
// 创建全屏计时器窗口
|
||||
fullscreenWindow = new FullscreenTimerWindow(this);
|
||||
fullscreenWindow.Show();
|
||||
isDragging = true;
|
||||
dragStartPoint = startPoint;
|
||||
|
||||
// 确保全屏窗口也置顶
|
||||
fullscreenWindow.Topmost = true;
|
||||
if (timerContainer.HorizontalAlignment == HorizontalAlignment.Center ||
|
||||
timerContainer.VerticalAlignment == VerticalAlignment.Center)
|
||||
{
|
||||
var timerPoint = timerContainer.TransformToAncestor(mainWindow).Transform(new Point(0, 0));
|
||||
containerStartPosition = new Point(timerPoint.X, timerPoint.Y);
|
||||
|
||||
timerContainer.Margin = new Thickness(containerStartPosition.X, containerStartPosition.Y, 0, 0);
|
||||
timerContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
timerContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
var margin = timerContainer.Margin;
|
||||
containerStartPosition = new Point(margin.Left, margin.Top);
|
||||
|
||||
if (double.IsNaN(containerStartPosition.X) || containerStartPosition.X < 0) containerStartPosition.X = 0;
|
||||
if (double.IsNaN(containerStartPosition.Y) || containerStartPosition.Y < 0) containerStartPosition.Y = 0;
|
||||
}
|
||||
|
||||
// 隐藏主窗口
|
||||
this.Hide();
|
||||
timerContainer.CaptureMouse();
|
||||
timerContainer.MouseMove += TimerContainer_MouseMove;
|
||||
timerContainer.MouseLeftButtonUp += TimerContainer_MouseLeftButtonUp;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void TimerContainer_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isDragging) return;
|
||||
|
||||
UpdateActivityTime();
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (timerContainer == null) return;
|
||||
|
||||
var currentPoint = e.GetPosition(mainWindow);
|
||||
var deltaX = currentPoint.X - dragStartPoint.X;
|
||||
var deltaY = currentPoint.Y - dragStartPoint.Y;
|
||||
|
||||
var newX = containerStartPosition.X + deltaX;
|
||||
var newY = containerStartPosition.Y + deltaY;
|
||||
|
||||
if (newX < 0) newX = 0;
|
||||
if (newY < 0) newY = 0;
|
||||
|
||||
timerContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
timerContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
timerContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
if (minimizedContainer != null && minimizedContainer.Visibility == Visibility.Visible)
|
||||
{
|
||||
minimizedContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
minimizedContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
minimizedContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
}
|
||||
|
||||
private void TimerContainer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!isDragging) return;
|
||||
|
||||
isDragging = false;
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
timerContainer.ReleaseMouseCapture();
|
||||
timerContainer.MouseMove -= TimerContainer_MouseMove;
|
||||
timerContainer.MouseLeftButtonUp -= TimerContainer_MouseLeftButtonUp;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTimerCompletion()
|
||||
{
|
||||
if (minimizedWindow != null)
|
||||
{
|
||||
minimizedWindow.Close();
|
||||
minimizedWindow = null;
|
||||
this.Show();
|
||||
this.Activate();
|
||||
this.WindowState = WindowState.Normal;
|
||||
// 重新应用置顶
|
||||
ApplyTimerWindowTopmost();
|
||||
}
|
||||
else if (fullscreenWindow != null)
|
||||
{
|
||||
fullscreenWindow.Close();
|
||||
fullscreenWindow = null;
|
||||
isFullscreenMode = false;
|
||||
this.Show();
|
||||
this.Activate();
|
||||
this.WindowState = WindowState.Normal;
|
||||
// 重新应用置顶
|
||||
ApplyTimerWindowTopmost();
|
||||
}
|
||||
}
|
||||
|
||||
#region Win32 API 声明和置顶管理
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsIconic(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentProcessId();
|
||||
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
private const int WS_EX_TOPMOST = 0x00000008;
|
||||
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
|
||||
private const uint SWP_NOMOVE = 0x0002;
|
||||
private const uint SWP_NOSIZE = 0x0001;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
private const uint SWP_SHOWWINDOW = 0x0040;
|
||||
private const uint SWP_NOOWNERZORDER = 0x0200;
|
||||
|
||||
/// <summary>
|
||||
/// 应用计时器窗口置顶
|
||||
/// </summary>
|
||||
private void ApplyTimerWindowTopmost()
|
||||
|
||||
private void HideTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
if (!isTimerRunning || isPaused) return;
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
|
||||
// 强制激活窗口
|
||||
Activate();
|
||||
Focus();
|
||||
|
||||
// 设置WPF的Topmost属性
|
||||
Topmost = true;
|
||||
|
||||
// 使用Win32 API强制置顶
|
||||
// 1. 设置窗口样式为置顶
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
|
||||
|
||||
// 2. 使用SetWindowPos确保窗口在最顶层
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
|
||||
LogHelper.WriteLogToFile("计时器窗口已应用置顶", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用计时器窗口置顶失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
var timeSinceLastActivity = DateTime.Now - lastActivityTime;
|
||||
|
||||
if (timeSinceLastActivity.TotalSeconds >= 5)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null && timerContainer.Visibility == Visibility.Visible)
|
||||
{
|
||||
ShowMinimizedRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口加载事件处理,确保置顶
|
||||
/// </summary>
|
||||
private void TimerWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
|
||||
public void UpdateActivityTime()
|
||||
{
|
||||
// 使用延迟确保窗口完全加载后再应用置顶
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
lastActivityTime = DateTime.Now;
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
ApplyTimerWindowTopmost();
|
||||
}), DispatcherPriority.Loaded);
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
if (timerContainer.Visibility == Visibility.Collapsed && minimizedContainer.Visibility == Visibility.Visible)
|
||||
{
|
||||
HideMinimizedRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -20,21 +20,17 @@
|
||||
|
||||
</div>
|
||||
|
||||
## 🤔 发生了什么?
|
||||
## 💫 软件说明
|
||||
|
||||
|
||||
由于众所周知的原因,[DotteringDoge471](https://github.com/DotteringDoge471) 不再积极负责 InkCanvasForClass 旧时代版本的开发与维护工作,而刚好 [CJKmkp](https://github.com/CJKmkp) 又维护了这个社区版本的 icc,经过沟通后就顺理成章地成为了 icc 的官方版本。该分支版本 **目前还在开发之中** ,可能会有潜在的问题/ Bug 出现,请在出现 Bug 后与开发者或与 [DotteringDoge471](https://github.com/DotteringDoge471) 上报,方便我们迅速诊断并解决问题。
|
||||
|
||||
> ⚠️ 请注意:[DotteringDoge471](https://github.com/DotteringDoge471) 不积极负责 **本社区版本** 的开发与维护工作,仅会在有空的时候对本项目开发新功能或修复 Bug。因此,任何问题反馈/Bug反馈/建议等,请优先找本项目主要维护者 [CJKmkp](https://github.com/CJKmkp) 反馈或在 GitHub 仓库内提出 Issue!
|
||||
|
||||
使用该版本 InkCanvasForClass,意味着您同意自行承担任何可能存在的问题与风险。建议不要在公众场合(例如公开课,录播课,线上直播课,大型会议)使用未经广泛测试和优化的 Beta 版本,对使用 Beta 版本而带来的任何问题和风险(例如:被班主任批斗,被校长处罚,崩溃而导致的场面混乱,全球海平面上升等),**将由使用者自行承担**,[CJKmkp](https://github.com/CJKmkp) 和 [DotteringDoge471](https://github.com/DotteringDoge471) 及其项目的所有维护者不提供任何担保。
|
||||
使用该版本 InkCanvasForClass,意味着您同意自行承担任何可能存在的问题与风险。建议不要在公众场合(例如公开课,录播课,线上直播课,大型会议)使用未经广泛测试和优化的 Beta 版本,对使用 Beta 版本而带来的任何问题和风险(例如:被班主任批斗,被校长处罚,崩溃而导致的场面混乱,全球海平面上升等),**将由使用者自行承担**,[CJKmkp](https://github.com/CJKmkp) 及其项目的所有维护者不提供任何担保。
|
||||
|
||||
♥️ **本项目版权归 [CJKmkp](https://github.com/CJKmkp) 所有。[CJKmkp](https://github.com/CJKmkp) 拥有最终解释权。**
|
||||
|
||||
**智教联盟 InkCanvasForClass Community Edition 板块:** [forum.smart-teach.cn/t/icc-ce](https://forum.smart-teach.cn/t/icc-ce) ,我们会在此处发布版本更新日志,同时,您也可以在遵守论坛对应管理规则与InkCanvasForClass Community Edition 板块管理条约的情况下,在该板块内提问或发表自己的使用体验。
|
||||
|
||||
## ⚠️ 使用须知
|
||||
在使用和分发本软件前,请务必了解相关开源协议。本软件基于 https://github.com/Awesome-Iwb/icc-0610fix 修改而来,而 icc-0610fix 基于 https://github.com/ChangSakura/Ink-Canvas 修改,ica 则基于 https://github.com/WXRIW/Ink-Canvas 修改,增加了包括但不限于隐藏到侧边栏等功能,更改了相关UI和软件操作逻辑。对于墨迹书写功能以及 ica 独有功能的相关问题反馈,建议优先查阅 https://github.com/WXRIW/Ink-Canvas/issues 。**使用前建议戴上大脑使用。**
|
||||
|
||||
在使用和分发本软件前,请务必了解相关开源协议。本软件基于 <https://github.com/InkCanvasForClass/icc-20240610-stable> 修改而来,而 icc-20240610-stable 基于 <https://github.com/ChangSakura/Ink-Canvas> 修改,ica 则基于 <https://github.com/WXRIW/Ink-Canvas> 修改,增加了包括但不限于隐藏到侧边栏等功能,更改了相关UI和软件操作逻辑。对于墨迹书写功能以及 ica 独有功能的相关问题反馈,建议优先查阅 <https://github.com/WXRIW/Ink-Canvas/issues> 。**使用前建议戴上大脑使用。**
|
||||
|
||||
# 💬 提示
|
||||
- 对于新功能的有效意见和合理建议,开发者会适时回复并进行开发。本软件并非商业性质软件或由营利性机构驱动,请不要催促开发者,耐心等待能让功能少些Bug,更加稳定。
|
||||
@@ -62,13 +58,8 @@
|
||||
|
||||
如果仍无法运行,请[安装 `Microsoft Office`](https://www.coolhub.top/archives/11)。
|
||||
|
||||
### 程序能在 Wine 环境中运行吗?
|
||||
不能,但是你可以期待 icc-gtk4,是正在开发的仅支持 Linux 平台的 icc 移植版本。
|
||||
|
||||
## ✏️ 贡献指南
|
||||
|
||||
请前往 InkCanvasForClass/dubious-notes
|
||||
|
||||
**请注意,在贡献代码时,_务必_ 将所有代码提交到 _beta_ 分支,以保证beta版本总是新于main版本。**
|
||||
|
||||
## TODO LIST
|
||||
@@ -105,13 +96,12 @@
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
## 🤝 感谢
|
||||
感谢 [DotteringDoge471](https://github.com/DotteringDoge471) 创造了 `InkCanvasForClass`!
|
||||
|
||||
感谢 [yuwenhui2020](https://github.com/yuwenhui2020) 为 `Ink Canvas 使用说明` 做出的贡献!
|
||||
感谢 [CN-Ironegg](https://github.com/CN-Ironegg)、[jiajiaxd](https://github.com/jiajiaxd)、[Kengwang](https://github.com/kengwang)、[Raspberry Kan](https://github.com/Raspberry-Monster)、[clover-yan](https://github.com/clover-yan)、[STBBRD](https://github.com/STBBRD)、[ChangSakura](https://github.com/WuChanging)、[DotteringDoge471](https://github.com/DotteringDoge471) 为本项目贡献代码!
|
||||
感谢 [CN-Ironegg](https://github.com/CN-Ironegg)、[jiajiaxd](https://github.com/jiajiaxd)、[Kengwang](https://github.com/kengwang)、[Raspberry Kan](https://github.com/Raspberry-Monster)、[clover-yan](https://github.com/clover-yan)、[STBBRD](https://github.com/STBBRD)、[ChangSakura](https://github.com/WuChanging) 为本项目贡献代码!
|
||||
|
||||
## License
|
||||
GPLv3
|
||||
|
||||
## 项目引用
|
||||
[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker)
|
||||
[Awesome-Iwb/iwbicons-gallery](https://github.com/awesome-iwb/awesome-iwb/wiki/iwbicons-gallery)「本项目部分图标来自 Awesome Iwb 的 IwbIcons 图标库,由 Douxiba 制作。」
|
||||
|
||||
@@ -100,3 +100,6 @@ ICC CE 1.7.X.X更新日志
|
||||
99. 修复仅调色盘状态下浮动栏不居中
|
||||
100. 修复希沃白板查杀与思锐希沃启动器导致的重复启动
|
||||
101. 新增UIA窗口置顶
|
||||
102. 新增新点名窗口
|
||||
103. 新增点名快抽
|
||||
104. 新增墨迹自动保存
|
||||
|
||||
Reference in New Issue
Block a user