Merge pull request #233 from InkCanvasForClass/beta

ICC CE 1.7.11.5
This commit is contained in:
CJK_mkp
2025-10-01 15:17:17 +08:00
committed by GitHub
66 changed files with 6339 additions and 1936 deletions
+2 -2
View File
@@ -26,10 +26,10 @@ jobs:
- name: Build the Solution
run: |
msbuild -t:restore /p:GitFlow="Github Action"
msbuild /p:platform="Any CPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
msbuild /p:platform="AnyCPU" /p:configuration="Debug" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
- name: Upload to artifact
uses: actions/upload-artifact@v4.5.0
with:
name: InkCanvasForClass
path: "Ink Canvas/bin/Any CPU/Release/net472/"
path: "Ink Canvas/bin/Debug/net472"
+3 -1
View File
@@ -1,4 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">WARNING</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe</s:String>
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/BuildTool/MsbuildVersion/@EntryValue">1114112</s:Int64></wpf:ResourceDictionary>
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/BuildTool/MsbuildVersion/@EntryValue">1114112</s:Int64>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=InkCanvasForClass_002FProperties_002FResources/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>
-1
View File
@@ -237,7 +237,6 @@
<ResourceDictionary Source="Resources/SeewoImageDictionary.xaml"/>
<ResourceDictionary Source="Resources/DrawShapeImageDictionary.xaml"/>
<ResourceDictionary Source="Resources/IconImageDictionary.xaml"/>
<ResourceDictionary Source="Resources/Styles/Light.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
+2 -2
View File
@@ -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.11.0")]
[assembly: AssemblyFileVersion("1.7.11.0")]
[assembly: AssemblyVersion("1.7.11.4")]
[assembly: AssemblyFileVersion("1.7.11.4")]
+31 -1
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Ink_Canvas.Helpers;
namespace Ink_Canvas
@@ -124,6 +123,37 @@ namespace Ink_Canvas
{
_settings.InterceptRules[ruleName] = enabled;
}
// 获取规则信息以处理父子关系
var rule = _interceptor.GetInterceptRule(type);
if (rule != null)
{
// 如果是父规则,更新所有子规则的设置
if (rule.ChildTypes.Count > 0)
{
foreach (var childType in rule.ChildTypes)
{
var childRuleName = childType.ToString();
if (_settings.InterceptRules.ContainsKey(childRuleName))
{
_settings.InterceptRules[childRuleName] = enabled;
}
}
}
// 如果是子规则,更新父规则的设置
else if (rule.ParentType.HasValue)
{
var parentRule = _interceptor.GetInterceptRule(rule.ParentType.Value);
if (parentRule != null)
{
var parentRuleName = rule.ParentType.Value.ToString();
if (_settings.InterceptRules.ContainsKey(parentRuleName))
{
_settings.InterceptRules[parentRuleName] = parentRule.IsEnabled;
}
}
}
}
}
catch (Exception ex)
{
+385 -30
View File
@@ -95,18 +95,22 @@ namespace Ink_Canvas.Helpers
// 使用改进的贝塞尔曲线拟合
var smoothedPoints = ApplyImprovedBezierSmoothing(originalPoints);
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 原始点数={originalPoints.Length}, 平滑后点数={smoothedPoints.Length}");
cancellationToken.ThrowIfCancellationRequested();
// 严格控制点数,避免产生过多点
if (smoothedPoints.Length > originalPoints.Length * 2)
// 放宽点数限制
if (smoothedPoints.Length > originalPoints.Length * 3.0)
{
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 点数过多,进行重采样");
// 如果点数增加太多,进行重采样
smoothedPoints = ResampleEquidistantOptimized(smoothedPoints, ResampleInterval);
}
// 最终检查:确保点数不会过多
if (smoothedPoints.Length > originalPoints.Length * 1.5)
// 进一步放宽最终检查
if (smoothedPoints.Length > originalPoints.Length * 2.5)
{
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 重采样后点数仍然过多,返回原始笔画");
// 如果仍然太多点,使用原始笔画
return stroke;
}
@@ -117,6 +121,7 @@ namespace Ink_Canvas.Helpers
DrawingAttributes = stroke.DrawingAttributes.Clone()
};
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 成功创建平滑笔画");
return smoothedStroke;
}
@@ -125,33 +130,42 @@ namespace Ink_Canvas.Helpers
/// </summary>
private StylusPoint[] ApplyImprovedBezierSmoothing(StylusPoint[] points)
{
if (points.Length < 4) return points;
if (points.Length < 6) return points; // 5次贝塞尔需要6个点
var result = new List<StylusPoint>();
// 添加第一个点
result.Add(points[0]);
// 使用非重叠的窗口进行贝塞尔曲线拟合
for (int i = 0; i < points.Length - 3; i += 3) // 每次移动3个点,避免重叠
// 使用5次贝塞尔曲线,每次移动1个点确保连续性
for (int i = 0; i < points.Length - 5; i++)
{
var p0 = points[i];
var p1 = points[Math.Min(i + 1, points.Length - 1)];
var p2 = points[Math.Min(i + 2, points.Length - 1)];
var p3 = points[Math.Min(i + 3, points.Length - 1)];
var p1 = points[i + 1];
var p2 = points[i + 2];
var p3 = points[i + 3];
var p4 = points[i + 4];
var p5 = points[i + 5];
// 计算改进的控制点
var controlPoints = CalculateImprovedControlPoints(p0, p1, p2, p3);
// 计算5次贝塞尔的控制点
var controlPoints = CalculateQuinticControlPoints(p0, p1, p2, p3, p4, p5);
// 限制插值步数,避免点数爆炸
int steps = Math.Min(UseAdaptiveInterpolation ?
CalculateAdaptiveSteps(p0, p1, p2, p3) : InterpolationSteps, 16);
// 生成贝塞尔曲线点,但跳过第一个点避免重复
for (int j = 1; j <= steps; j++)
// 生成插值点
if (i == 0)
{
double t = (double)j / steps;
var bezierPoint = CubicBezierWithControlPoints(controlPoints, t, p0, p3);
// 第一个窗口:生成更多插值点
for (int j = 1; j <= 4; j++)
{
double t = (double)j / 5;
var bezierPoint = CalculateQuinticBezierPoint(p0, controlPoints, p5, t);
result.Add(bezierPoint);
}
}
else
{
// 后续窗口:只生成最后一个插值点,避免重复
double t = 4.0 / 5.0; // 只取最后一个插值点
var bezierPoint = CalculateQuinticBezierPoint(p0, controlPoints, p5, t);
result.Add(bezierPoint);
}
}
@@ -159,8 +173,209 @@ namespace Ink_Canvas.Helpers
// 添加最后一个点
result.Add(points[points.Length - 1]);
// 去重和优化点数
return RemoveDuplicatePoints(result.ToArray());
System.Diagnostics.Debug.WriteLine($"ApplyImprovedBezierSmoothing: 原始点数={points.Length}, 生成点数={result.Count}");
// 使用更宽松的去重
return RemoveDuplicatePointsLoose(result.ToArray());
}
/// <summary>
/// 5次贝塞尔曲线控制点计算
/// </summary>
private (Point cp1, Point cp2, Point cp3, Point cp4) CalculateQuinticControlPoints(
StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, StylusPoint p4, StylusPoint p5)
{
// 计算控制点距离(基于相邻点距离)
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
double dist2 = Math.Sqrt((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y));
double dist3 = Math.Sqrt((p4.X - p3.X) * (p4.X - p3.X) + (p4.Y - p3.Y) * (p4.Y - p3.Y));
double dist4 = Math.Sqrt((p5.X - p4.X) * (p5.X - p4.X) + (p5.Y - p4.Y) * (p5.Y - p4.Y));
// 使用更小的控制点距离,产生超平滑的曲线
double controlDist1 = dist1 * 0.15;
double controlDist2 = dist2 * 0.15;
double controlDist3 = dist3 * 0.15;
double controlDist4 = dist4 * 0.15;
// 计算控制点方向 - 使用更平滑的方向计算
double dir1X = p2.X - p0.X;
double dir1Y = p2.Y - p0.Y;
double dir2X = p3.X - p1.X;
double dir2Y = p3.Y - p1.Y;
double dir3X = p4.X - p2.X;
double dir3Y = p4.Y - p2.Y;
double dir4X = p5.X - p3.X;
double dir4Y = p5.Y - p3.Y;
// 归一化方向
NormalizeVector(ref dir1X, ref dir1Y);
NormalizeVector(ref dir2X, ref dir2Y);
NormalizeVector(ref dir3X, ref dir3Y);
NormalizeVector(ref dir4X, ref dir4Y);
// 计算控制点
var cp1 = new Point(p1.X + dir1X * controlDist1, p1.Y + dir1Y * controlDist1);
var cp2 = new Point(p2.X + dir2X * controlDist2, p2.Y + dir2Y * controlDist2);
var cp3 = new Point(p3.X - dir3X * controlDist3, p3.Y - dir3Y * controlDist3);
var cp4 = new Point(p4.X - dir4X * controlDist4, p4.Y - dir4Y * controlDist4);
return (cp1, cp2, cp3, cp4);
}
/// <summary>
/// 归一化向量
/// </summary>
private void NormalizeVector(ref double x, ref double y)
{
double length = Math.Sqrt(x * x + y * y);
if (length > 0)
{
x /= length;
y /= length;
}
}
/// <summary>
/// 5次贝塞尔曲线点计算
/// </summary>
private StylusPoint CalculateQuinticBezierPoint(StylusPoint p0, (Point cp1, Point cp2, Point cp3, Point cp4) controlPoints, StylusPoint p5, double t)
{
double oneMinusT = 1 - t;
double oneMinusT2 = oneMinusT * oneMinusT;
double oneMinusT3 = oneMinusT2 * oneMinusT;
double oneMinusT4 = oneMinusT3 * oneMinusT;
double oneMinusT5 = oneMinusT4 * oneMinusT;
double t2 = t * t;
double t3 = t2 * t;
double t4 = t3 * t;
double t5 = t4 * t;
// 5次贝塞尔曲线公式
double x = oneMinusT5 * p0.X +
5 * oneMinusT4 * t * controlPoints.cp1.X +
10 * oneMinusT3 * t2 * controlPoints.cp2.X +
10 * oneMinusT2 * t3 * controlPoints.cp3.X +
5 * oneMinusT * t4 * controlPoints.cp4.X +
t5 * p5.X;
double y = oneMinusT5 * p0.Y +
5 * oneMinusT4 * t * controlPoints.cp1.Y +
10 * oneMinusT3 * t2 * controlPoints.cp2.Y +
10 * oneMinusT2 * t3 * controlPoints.cp3.Y +
5 * oneMinusT * t4 * controlPoints.cp4.Y +
t5 * p5.Y;
// 压力插值 - 使用线性插值
float pressure = (float)((1 - t) * p0.PressureFactor + t * p5.PressureFactor);
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
}
/// <summary>
/// 简化的控制点计算
/// </summary>
private (Point cp1, Point cp2) CalculateSimpleControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
{
// 计算控制点距离(基于线段长度)
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
double dist2 = Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
// 使用更小的控制点距离,产生更平滑的曲线
double controlDist1 = dist1 * 0.2; // 进一步减少控制点影响
double controlDist2 = dist2 * 0.2;
// 计算控制点方向 - 使用更平滑的方向计算
double dir1X = p2.X - p0.X; // 使用更远的点计算方向
double dir1Y = p2.Y - p0.Y;
double dir2X = p3.X - p1.X;
double dir2Y = p3.Y - p1.Y;
// 归一化方向
double len1 = Math.Sqrt(dir1X * dir1X + dir1Y * dir1Y);
double len2 = Math.Sqrt(dir2X * dir2X + dir2Y * dir2Y);
if (len1 > 0)
{
dir1X /= len1;
dir1Y /= len1;
}
if (len2 > 0)
{
dir2X /= len2;
dir2Y /= len2;
}
// 计算控制点
var cp1 = new Point(
p1.X + dir1X * controlDist1,
p1.Y + dir1Y * controlDist1
);
var cp2 = new Point(
p2.X - dir2X * controlDist2,
p2.Y - dir2Y * controlDist2
);
return (cp1, cp2);
}
/// <summary>
/// 宽松的去重算法
/// </summary>
private StylusPoint[] RemoveDuplicatePointsLoose(StylusPoint[] points)
{
if (points.Length < 2) return points;
var result = new List<StylusPoint>();
result.Add(points[0]);
double minDistance = 0.1; // 非常小的距离阈值,几乎不去重
for (int i = 1; i < points.Length; i++)
{
var lastPoint = result[result.Count - 1];
var currentPoint = points[i];
// 计算距离
double distance = Math.Sqrt(
(currentPoint.X - lastPoint.X) * (currentPoint.X - lastPoint.X) +
(currentPoint.Y - lastPoint.Y) * (currentPoint.Y - lastPoint.Y));
// 如果距离足够大,添加这个点
if (distance >= minDistance)
{
result.Add(currentPoint);
}
}
System.Diagnostics.Debug.WriteLine($"RemoveDuplicatePointsLoose: 输入点数={points.Length}, 输出点数={result.Count}");
return result.ToArray();
}
/// <summary>
/// 计算贝塞尔曲线上的点
/// </summary>
private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t)
{
double x = Math.Pow(1 - t, 3) * p0.X +
3 * Math.Pow(1 - t, 2) * t * cp1.X +
3 * (1 - t) * Math.Pow(t, 2) * cp2.X +
Math.Pow(t, 3) * p3.X;
double y = Math.Pow(1 - t, 3) * p0.Y +
3 * Math.Pow(1 - t, 2) * t * cp1.Y +
3 * (1 - t) * Math.Pow(t, 2) * cp2.Y +
Math.Pow(t, 3) * p3.Y;
// 压力插值
float pressure = (float)(Math.Pow(1 - t, 3) * p0.PressureFactor +
3 * Math.Pow(1 - t, 2) * t * p0.PressureFactor +
3 * (1 - t) * Math.Pow(t, 2) * p3.PressureFactor +
Math.Pow(t, 3) * p3.PressureFactor);
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
}
/// <summary>
@@ -251,7 +466,7 @@ namespace Ink_Canvas.Helpers
var result = new List<StylusPoint>();
result.Add(points[0]);
double minDistance = ResampleInterval * 0.5; // 最小距离阈值
double minDistance = 0.3; // 进一步减少最小距离阈值,保留更多平滑点
for (int i = 1; i < points.Length; i++)
{
@@ -270,6 +485,7 @@ namespace Ink_Canvas.Helpers
}
}
System.Diagnostics.Debug.WriteLine($"RemoveDuplicatePoints: 输入点数={points.Length}, 输出点数={result.Count}");
return result.ToArray();
}
@@ -516,23 +732,31 @@ namespace Ink_Canvas.Helpers
/// </summary>
public class AdvancedBezierSmoothing
{
public double SmoothingStrength { get; set; } = 0.3;
public double ResampleInterval { get; set; } = 3.0;
public int InterpolationSteps { get; set; } = 8;
public double SmoothingStrength { get; set; } = 0.6;
public double ResampleInterval { get; set; } = 2.0;
public int InterpolationSteps { get; set; } = 12;
public Stroke SmoothStroke(Stroke stroke)
{
if (stroke == null || stroke.StylusPoints.Count < 3)
{
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 笔画点数不足,跳过平滑 (点数: {stroke?.StylusPoints.Count ?? 0})");
return stroke;
}
var originalPoints = stroke.StylusPoints.ToList();
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 开始平滑处理,原始点数: {stroke.StylusPoints.Count}");
// 简化处理:只进行轻度平滑
var smoothedPoints = ApplyLightExponentialSmoothing(originalPoints, 0.2); // 很轻的平滑
var originalPoints = stroke.StylusPoints.ToArray();
// 使用真正的贝塞尔曲线平滑
var smoothedPoints = ApplyCubicBezierSmoothing(originalPoints);
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 平滑完成,平滑后点数: {smoothedPoints.Length}");
// 检查点数是否合理
if (smoothedPoints.Count > originalPoints.Count * 1.5)
if (smoothedPoints.Length > originalPoints.Length * 10.0)
{
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 点数增加过多,返回原始笔画 (原始:{originalPoints.Length}, 平滑后:{smoothedPoints.Length})");
return stroke; // 如果点数增加太多,返回原始笔画
}
@@ -540,9 +764,140 @@ namespace Ink_Canvas.Helpers
{
DrawingAttributes = stroke.DrawingAttributes.Clone()
};
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 创建平滑笔画成功");
return smoothedStroke;
}
/// <summary>
/// 三次贝塞尔曲线平滑
/// </summary>
private StylusPoint[] ApplyCubicBezierSmoothing(StylusPoint[] points)
{
if (points.Length < 4) return points;
var result = new List<StylusPoint>();
result.Add(points[0]);
// 使用更保守的窗口大小和插值
int windowSize = Math.Min(4, points.Length);
int stepSize = Math.Max(1, points.Length / 10); // 根据点数动态调整步长
for (int i = 0; i <= points.Length - windowSize; i += stepSize)
{
if (i + windowSize - 1 >= points.Length) break;
var p0 = points[i];
var p1 = points[Math.Min(i + 1, points.Length - 1)];
var p2 = points[Math.Min(i + 2, points.Length - 1)];
var p3 = points[Math.Min(i + windowSize - 1, points.Length - 1)];
// 计算控制点
var controlPoints = CalculateControlPoints(p0, p1, p2, p3);
// 只生成2-3个插值点
int steps = 2;
// 生成贝塞尔曲线点
for (int j = 1; j <= steps; j++)
{
double t = (double)j / steps;
var bezierPoint = CalculateBezierPoint(p0, controlPoints.cp1, controlPoints.cp2, p3, t);
result.Add(bezierPoint);
}
}
result.Add(points[points.Length - 1]);
// 去重处理
return RemoveDuplicatePoints(result.ToArray());
}
/// <summary>
/// 去除重复点
/// </summary>
private StylusPoint[] RemoveDuplicatePoints(StylusPoint[] points)
{
if (points.Length <= 1) return points;
var result = new List<StylusPoint> { points[0] };
double minDistance = 1.0; // 最小距离阈值
for (int i = 1; i < points.Length; i++)
{
var lastPoint = result[result.Count - 1];
var currentPoint = points[i];
double distance = Math.Sqrt(Math.Pow(currentPoint.X - lastPoint.X, 2) +
Math.Pow(currentPoint.Y - lastPoint.Y, 2));
if (distance > minDistance)
{
result.Add(currentPoint);
}
}
return result.ToArray();
}
/// <summary>
/// 计算控制点
/// </summary>
private (Point cp1, Point cp2) CalculateControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
{
// 计算切线方向
var tangent1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
var tangent2 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
// 归一化切线
if (tangent1.Length > 0) tangent1.Normalize();
if (tangent2.Length > 0) tangent2.Normalize();
// 计算控制点距离
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
double dist2 = Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
double controlDist1 = dist1 * SmoothingStrength;
double controlDist2 = dist2 * SmoothingStrength;
// 计算控制点
var cp1 = new Point(
p1.X + tangent1.X * controlDist1,
p1.Y + tangent1.Y * controlDist1
);
var cp2 = new Point(
p2.X - tangent2.X * controlDist2,
p2.Y - tangent2.Y * controlDist2
);
return (cp1, cp2);
}
/// <summary>
/// 计算贝塞尔曲线上的点
/// </summary>
private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t)
{
double x = Math.Pow(1 - t, 3) * p0.X +
3 * Math.Pow(1 - t, 2) * t * cp1.X +
3 * (1 - t) * Math.Pow(t, 2) * cp2.X +
Math.Pow(t, 3) * p3.X;
double y = Math.Pow(1 - t, 3) * p0.Y +
3 * Math.Pow(1 - t, 2) * t * cp1.Y +
3 * (1 - t) * Math.Pow(t, 2) * cp2.Y +
Math.Pow(t, 3) * p3.Y;
// 压力插值
float pressure = (float)(Math.Pow(1 - t, 3) * p0.PressureFactor +
3 * Math.Pow(1 - t, 2) * t * p0.PressureFactor +
3 * (1 - t) * Math.Pow(t, 2) * p3.PressureFactor +
Math.Pow(t, 3) * p3.PressureFactor);
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
}
/// <summary>
/// 轻度指数平滑
/// </summary>
+216
View File
@@ -0,0 +1,216 @@
using Ink_Canvas.Helpers;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 自动备份管理器
/// 负责管理配置文件的自动备份功能
/// </summary>
public static class AutoBackupManager
{
private static readonly string BackupDir = Path.Combine(App.RootPath, "Backups");
private static readonly string SettingsFile = Path.Combine(App.RootPath, "Configs", "Settings.json");
private static readonly string BackupPrefix = "Settings_AutoBackup_";
/// <summary>
/// 检查是否需要执行自动备份
/// </summary>
/// <param name="settings">设置对象</param>
/// <returns>如果需要备份返回true,否则返回false</returns>
public static bool ShouldPerformAutoBackup(Settings settings)
{
try
{
// 如果自动备份功能未启用,不执行备份
if (!settings.Advanced.IsAutoBackupEnabled)
{
return false;
}
// 如果从未备份过,需要创建首次备份
if (settings.Advanced.LastAutoBackupTime == DateTime.MinValue)
{
return true;
}
// 检查是否已超过备份间隔
var daysSinceLastBackup = (DateTime.Now - settings.Advanced.LastAutoBackupTime).TotalDays;
return daysSinceLastBackup >= settings.Advanced.AutoBackupIntervalDays;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"检查自动备份条件时出错: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 执行自动备份
/// </summary>
/// <param name="settings">设置对象</param>
/// <returns>备份是否成功</returns>
public static bool PerformAutoBackup(Settings settings)
{
try
{
// 确保备份目录存在
if (!Directory.Exists(BackupDir))
{
Directory.CreateDirectory(BackupDir);
}
// 检查主配置文件是否存在
if (!File.Exists(SettingsFile))
{
LogHelper.WriteLogToFile("主配置文件不存在,跳过自动备份", LogHelper.LogType.Warning);
return false;
}
// 创建备份文件名(使用当前日期时间)
string backupFileName = $"{BackupPrefix}{DateTime.Now:yyyyMMdd_HHmmss}.json";
string backupPath = Path.Combine(BackupDir, backupFileName);
// 复制主配置文件到备份位置
File.Copy(SettingsFile, backupPath, true);
// 更新最后备份时间
settings.Advanced.LastAutoBackupTime = DateTime.Now;
MainWindow.SaveSettingsToFile();
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"执行自动备份时出错: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 尝试从备份恢复配置文件
/// </summary>
/// <returns>恢复是否成功</returns>
public static bool TryRestoreFromBackup()
{
try
{
// 确保备份目录存在
if (!Directory.Exists(BackupDir))
{
LogHelper.WriteLogToFile("备份目录不存在,无法从备份恢复", LogHelper.LogType.Warning);
return false;
}
// 查找最新的备份文件
var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json")
.OrderByDescending(f => File.GetCreationTime(f))
.ToArray();
if (backupFiles.Length == 0)
{
LogHelper.WriteLogToFile("没有找到可用的备份文件", LogHelper.LogType.Warning);
return false;
}
// 尝试使用最新的备份文件
string latestBackup = backupFiles[0];
// 验证备份文件是否有效
try
{
string backupJson = File.ReadAllText(latestBackup);
var testSettings = JsonConvert.DeserializeObject<Settings>(backupJson);
if (testSettings == null)
{
LogHelper.WriteLogToFile("备份文件内容无效,无法恢复", LogHelper.LogType.Error);
return false;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"备份文件验证失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
// 备份当前损坏的配置文件(如果存在)
if (File.Exists(SettingsFile))
{
string corruptedBackup = Path.Combine(BackupDir, $"Settings_Corrupted_{DateTime.Now:yyyyMMdd_HHmmss}.json");
File.Copy(SettingsFile, corruptedBackup, true);
}
// 从备份恢复配置文件
File.Copy(latestBackup, SettingsFile, true);
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"从备份恢复配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 清理过期的备份文件
/// 保留最近30天的备份文件
/// </summary>
public static void CleanupOldBackups()
{
try
{
if (!Directory.Exists(BackupDir))
{
return;
}
var cutoffDate = DateTime.Now.AddDays(-30);
var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json");
int deletedCount = 0;
foreach (var file in backupFiles)
{
if (File.GetCreationTime(file) < cutoffDate)
{
File.Delete(file);
deletedCount++;
}
}
if (deletedCount > 0)
{
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理过期备份文件时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 初始化自动备份功能
/// 在应用程序启动时调用
/// </summary>
/// <param name="settings">设置对象</param>
public static void Initialize(Settings settings)
{
try
{
// 检查是否需要执行自动备份
if (ShouldPerformAutoBackup(settings))
{
PerformAutoBackup(settings);
}
// 清理过期备份
CleanupOldBackups();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化自动备份功能时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
}
-2
View File
@@ -47,7 +47,6 @@ namespace Ink_Canvas.Helpers
AvailableCameras.Add(device);
}
LogHelper.WriteLogToFile($"发现 {AvailableCameras.Count} 个摄像头设备");
}
catch (Exception ex)
{
@@ -120,7 +119,6 @@ namespace Ink_Canvas.Helpers
}
_isCapturing = false;
LogHelper.WriteLogToFile("摄像头预览已停止");
}
catch (Exception ex)
{
-1
View File
@@ -1065,7 +1065,6 @@ namespace Ink_Canvas.Helpers
// 如果不是自动更新(即版本修复),则应用不同的策略
if (!isAutoUpdate)
{
// 版本修复:立即允许,不受分级策略影响
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复 - 版本: {updateVersion}, 类型: {updateType}, 结果: 允许");
return true;
}
+574 -47
View File
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace Ink_Canvas.Helpers
@@ -19,6 +19,9 @@ namespace Ink_Canvas.Helpers
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
@@ -110,6 +113,10 @@ namespace Ink_Canvas.Helpers
/// </summary>
SeewoPincoDrawingFloating,
/// <summary>
/// 希沃品课教师端 桌面画板
/// </summary>
SeewoPincoBoardService,
/// <summary>
/// 希沃PPT小工具
/// </summary>
SeewoPPTFloating,
@@ -122,18 +129,66 @@ namespace Ink_Canvas.Helpers
/// </summary>
HiteAnnotationFloating,
/// <summary>
/// 畅言智慧课堂 桌面悬浮窗
/// 畅言智慧课堂 主栏悬浮窗
/// </summary>
ChangYanFloating,
/// <summary>
/// 畅言智慧课堂 画笔设置
/// </summary>
ChangYanBrushSettings,
/// <summary>
/// 畅言智慧课堂 滑动清除
/// </summary>
ChangYanSwipeClear,
/// <summary>
/// 畅言智慧课堂 互动
/// </summary>
ChangYanInteraction,
/// <summary>
/// 畅言智慧课堂 学科应用
/// </summary>
ChangYanSubjectApp,
/// <summary>
/// 畅言智慧课堂 管控
/// </summary>
ChangYanControl,
/// <summary>
/// 畅言智慧课堂 通用工具
/// </summary>
ChangYanCommonTools,
/// <summary>
/// 畅言智慧课堂 场景工具栏
/// </summary>
ChangYanSceneToolbar,
/// <summary>
/// 畅言智慧课堂 绘制窗口
/// </summary>
ChangYanDrawWindow,
/// <summary>
/// 畅言智慧课堂 PPT悬浮窗
/// </summary>
ChangYanPptFloating,
/// <summary>
/// 畅言智慧课堂 PPT页面控制
/// </summary>
ChangYanPptPageControl,
/// <summary>
/// 畅言智慧课堂 PPT返回
/// </summary>
ChangYanPptGoBack,
/// <summary>
/// 畅言智慧课堂 PPT预览
/// </summary>
ChangYanPptPreview,
/// <summary>
/// 天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)
/// </summary>
IntelligentClassFloating,
/// <summary>
/// 天喻教育云互动课堂 PPT悬浮窗
/// </summary>
IntelligentClassPptFloating,
/// <summary>
/// 希沃桌面 画笔悬浮窗
/// </summary>
SeewoDesktopAnnotationFloating,
@@ -155,6 +210,8 @@ namespace Ink_Canvas.Helpers
public bool IsEnabled { get; set; }
public bool RequiresAdmin { get; set; }
public string Description { get; set; }
public InterceptType? ParentType { get; set; }
public List<InterceptType> ChildTypes { get; set; } = new List<InterceptType>();
}
#endregion
@@ -167,6 +224,14 @@ namespace Ink_Canvas.Helpers
private readonly Dispatcher _dispatcher;
private bool _isRunning;
private bool _disposed;
// 性能优化字段
private readonly Dictionary<IntPtr, DateTime> _lastScanTime = new Dictionary<IntPtr, DateTime>();
private readonly HashSet<IntPtr> _knownWindows = new HashSet<IntPtr>();
private readonly Dictionary<string, DateTime> _processLastScanTime = new Dictionary<string, DateTime>();
private int _consecutiveEmptyScans = 0;
private DateTime _lastSuccessfulScan = DateTime.Now;
private readonly object _scanLock = new object();
#endregion
@@ -206,8 +271,8 @@ namespace Ink_Canvas.Helpers
{
Type = InterceptType.SeewoWhiteboard3Floating,
ProcessName = "EasiNote",
WindowTitlePattern = "",
ClassNamePattern = "",
WindowTitlePattern = "Note",
ClassNamePattern = "HwndWrapper[EasiNote.exe;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板3 桌面悬浮窗"
@@ -219,7 +284,7 @@ namespace Ink_Canvas.Helpers
Type = InterceptType.SeewoWhiteboard5Floating,
ProcessName = "EasiNote",
WindowTitlePattern = "",
ClassNamePattern = "",
ClassNamePattern = "HwndWrapper[EasiNote;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板5 桌面悬浮窗"
@@ -231,43 +296,61 @@ namespace Ink_Canvas.Helpers
Type = InterceptType.SeewoWhiteboard5CFloating,
ProcessName = "EasiNote5C",
WindowTitlePattern = "",
ClassNamePattern = "",
ClassNamePattern = "HwndWrapper[EasiNote5C;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板5C 桌面悬浮窗"
};
// 希沃品课教师端 桌面悬浮窗
// 希沃品课教师端 桌面悬浮窗(父规则)
_interceptRules[InterceptType.SeewoPincoSideBarFloating] = new InterceptRule
{
Type = InterceptType.SeewoPincoSideBarFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
WindowTitlePattern = "希沃品课——appBar",
ClassNamePattern = "Chrome_WidgetWin_1",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃品课教师端 桌面悬浮窗"
Description = "希沃品课教师端 桌面悬浮窗",
ParentType = null,
ChildTypes = new List<InterceptType> { InterceptType.SeewoPincoDrawingFloating, InterceptType.SeewoPincoBoardService }
};
// 希沃品课教师端 画笔悬浮窗
// 希沃品课教师端 画笔悬浮窗(子规则)
_interceptRules[InterceptType.SeewoPincoDrawingFloating] = new InterceptRule
{
Type = InterceptType.SeewoPincoDrawingFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
WindowTitlePattern = "希沃品课——integration",
ClassNamePattern = "Chrome_WidgetWin_1",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)"
Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)",
ParentType = InterceptType.SeewoPincoSideBarFloating,
ChildTypes = new List<InterceptType>()
};
// 希沃品课教师端 桌面画板(子规则)
_interceptRules[InterceptType.SeewoPincoBoardService] = new InterceptRule
{
Type = InterceptType.SeewoPincoBoardService,
ProcessName = "BoardService",
WindowTitlePattern = "",
ClassNamePattern = "HwndWrapper[BoardService;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃品课教师端 桌面画板",
ParentType = InterceptType.SeewoPincoSideBarFloating,
ChildTypes = new List<InterceptType>()
};
// 希沃PPT小工具
_interceptRules[InterceptType.SeewoPPTFloating] = new InterceptRule
{
Type = InterceptType.SeewoPPTFloating,
ProcessName = "SeewoPPT",
ProcessName = "PPTService",
WindowTitlePattern = "",
ClassNamePattern = "",
ClassNamePattern = "HwndWrapper[PPTService.exe;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃PPT小工具"
@@ -278,8 +361,8 @@ namespace Ink_Canvas.Helpers
{
Type = InterceptType.AiClassFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
WindowTitlePattern = "TransparentWindow",
ClassNamePattern = "UIWndTransparent",
IsEnabled = true,
RequiresAdmin = false,
Description = "AiClass 桌面悬浮窗"
@@ -290,23 +373,147 @@ namespace Ink_Canvas.Helpers
{
Type = InterceptType.HiteAnnotationFloating,
ProcessName = "HiteVision",
WindowTitlePattern = "",
ClassNamePattern = "",
WindowTitlePattern = "HiteAnnotation",
ClassNamePattern = "Qt5QWindowToolSaveBits",
IsEnabled = true,
RequiresAdmin = false,
Description = "鸿合屏幕书写"
};
// 畅言智慧课堂 桌面悬浮窗
// 畅言智慧课堂 主栏悬浮窗(父规则)
_interceptRules[InterceptType.ChangYanFloating] = new InterceptRule
{
Type = InterceptType.ChangYanFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
WindowTitlePattern = "ifly",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 桌面悬浮窗"
Description = "畅言智慧课堂 主栏悬浮窗",
ParentType = null,
ChildTypes = new List<InterceptType>
{
InterceptType.ChangYanBrushSettings,
InterceptType.ChangYanSwipeClear,
InterceptType.ChangYanInteraction,
InterceptType.ChangYanSubjectApp,
InterceptType.ChangYanControl,
InterceptType.ChangYanCommonTools,
InterceptType.ChangYanSceneToolbar,
InterceptType.ChangYanDrawWindow
}
};
// 畅言智慧课堂 画笔设置(子规则)
_interceptRules[InterceptType.ChangYanBrushSettings] = new InterceptRule
{
Type = InterceptType.ChangYanBrushSettings,
ProcessName = "ClassIn",
WindowTitlePattern = "画笔设置",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 画笔设置",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 滑动清除(子规则)
_interceptRules[InterceptType.ChangYanSwipeClear] = new InterceptRule
{
Type = InterceptType.ChangYanSwipeClear,
ProcessName = "ClassIn",
WindowTitlePattern = "滑动清除",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 滑动清除",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 互动(子规则)
_interceptRules[InterceptType.ChangYanInteraction] = new InterceptRule
{
Type = InterceptType.ChangYanInteraction,
ProcessName = "ClassIn",
WindowTitlePattern = "互动",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 互动",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 学科应用(子规则)
_interceptRules[InterceptType.ChangYanSubjectApp] = new InterceptRule
{
Type = InterceptType.ChangYanSubjectApp,
ProcessName = "ClassIn",
WindowTitlePattern = "学科应用",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 学科应用",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 管控(子规则)
_interceptRules[InterceptType.ChangYanControl] = new InterceptRule
{
Type = InterceptType.ChangYanControl,
ProcessName = "ClassIn",
WindowTitlePattern = "管控",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 管控",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 通用工具(子规则)
_interceptRules[InterceptType.ChangYanCommonTools] = new InterceptRule
{
Type = InterceptType.ChangYanCommonTools,
ProcessName = "ClassIn",
WindowTitlePattern = "通用工具",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 通用工具",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 场景工具栏(子规则)
_interceptRules[InterceptType.ChangYanSceneToolbar] = new InterceptRule
{
Type = InterceptType.ChangYanSceneToolbar,
ProcessName = "ClassIn",
WindowTitlePattern = "SceneToolbar",
ClassNamePattern = "Qt5QWindowOwnDCIcon",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 场景工具栏",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 绘制窗口(子规则)
_interceptRules[InterceptType.ChangYanDrawWindow] = new InterceptRule
{
Type = InterceptType.ChangYanDrawWindow,
ProcessName = "ClassIn",
WindowTitlePattern = "DrawWindow",
ClassNamePattern = "Qt5QWindowToolSaveBits",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 绘制窗口",
ParentType = InterceptType.ChangYanFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 PPT悬浮窗
@@ -314,32 +521,92 @@ namespace Ink_Canvas.Helpers
{
Type = InterceptType.ChangYanPptFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
WindowTitlePattern = "Exch",
ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 PPT悬浮窗"
Description = "畅言智慧课堂 PPT悬浮窗",
ParentType = null,
ChildTypes = new List<InterceptType> { InterceptType.ChangYanPptPageControl, InterceptType.ChangYanPptGoBack, InterceptType.ChangYanPptPreview }
};
// 天喻教育云互动课堂 桌面悬浮窗
// 畅言智慧课堂 PPT页面控制(子规则)
_interceptRules[InterceptType.ChangYanPptPageControl] = new InterceptRule
{
Type = InterceptType.ChangYanPptPageControl,
ProcessName = "ClassIn",
WindowTitlePattern = "PageCtl",
ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 PPT页面控制",
ParentType = InterceptType.ChangYanPptFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 PPT返回(子规则)
_interceptRules[InterceptType.ChangYanPptGoBack] = new InterceptRule
{
Type = InterceptType.ChangYanPptGoBack,
ProcessName = "ClassIn",
WindowTitlePattern = "Goback",
ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 PPT返回",
ParentType = InterceptType.ChangYanPptFloating,
ChildTypes = new List<InterceptType>()
};
// 畅言智慧课堂 PPT预览(子规则)
_interceptRules[InterceptType.ChangYanPptPreview] = new InterceptRule
{
Type = InterceptType.ChangYanPptPreview,
ProcessName = "ClassIn",
WindowTitlePattern = "Preview",
ClassNamePattern = "Qt5QWindowToolSaveBitsOwnDC",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 PPT预览",
ParentType = InterceptType.ChangYanPptFloating,
ChildTypes = new List<InterceptType>()
};
// 天喻教育云互动课堂 桌面悬浮窗(父规则)
_interceptRules[InterceptType.IntelligentClassFloating] = new InterceptRule
{
Type = InterceptType.IntelligentClassFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
ProcessName = "IntelligentClassApp",
WindowTitlePattern = "桌面小工具 - 互动课堂",
ClassNamePattern = "HwndWrapper[IntelligentClassApp.exe;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)"
Description = "天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)",
ParentType = null,
ChildTypes = new List<InterceptType> { InterceptType.IntelligentClassPptFloating }
};
// 天喻教育云互动课堂 PPT悬浮窗(子规则)
_interceptRules[InterceptType.IntelligentClassPptFloating] = new InterceptRule
{
Type = InterceptType.IntelligentClassPptFloating,
ProcessName = "IntelligentClass",
WindowTitlePattern = "",
ClassNamePattern = "HwndWrapper[IntelligentClass.Office.PowerPoint.vsto|vstolocal;VSTA_Main;",
IsEnabled = true,
RequiresAdmin = false,
Description = "天喻教育云互动课堂 PPT悬浮窗",
ParentType = InterceptType.IntelligentClassFloating,
ChildTypes = new List<InterceptType>()
};
// 希沃桌面 画笔悬浮窗
_interceptRules[InterceptType.SeewoDesktopAnnotationFloating] = new InterceptRule
{
Type = InterceptType.SeewoDesktopAnnotationFloating,
ProcessName = "Seewo",
ProcessName = "DesktopAnnotation",
WindowTitlePattern = "",
ClassNamePattern = "",
ClassNamePattern = "HwndWrapper[DesktopAnnotation.exe;;",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃桌面 画笔悬浮窗"
@@ -349,9 +616,9 @@ namespace Ink_Canvas.Helpers
_interceptRules[InterceptType.SeewoDesktopSideBarFloating] = new InterceptRule
{
Type = InterceptType.SeewoDesktopSideBarFloating,
ProcessName = "Seewo",
WindowTitlePattern = "",
ClassNamePattern = "",
ProcessName = "ResidentSideBar",
WindowTitlePattern = "ResidentSideBar",
ClassNamePattern = "HwndWrapper[ResidentSideBar.exe;;",
IsEnabled = true,
RequiresAdmin = true,
Description = "希沃桌面 侧栏悬浮窗"
@@ -371,7 +638,7 @@ namespace Ink_Canvas.Helpers
if (_isRunning) return;
_isRunning = true;
_scanTimer.Change(0, scanIntervalMs);
_scanTimer.Change(0, Math.Max(scanIntervalMs, 2000));
}
/// <summary>
@@ -395,7 +662,51 @@ namespace Ink_Canvas.Helpers
{
if (_interceptRules.ContainsKey(type))
{
_interceptRules[type].IsEnabled = enabled;
var rule = _interceptRules[type];
rule.IsEnabled = enabled;
// 如果是父规则被禁用,则禁用所有子规则
if (!enabled && rule.ChildTypes.Count > 0)
{
foreach (var childType in rule.ChildTypes)
{
if (_interceptRules.ContainsKey(childType))
{
_interceptRules[childType].IsEnabled = false;
}
}
}
// 如果是父规则被启用,则启用所有子规则
else if (enabled && rule.ChildTypes.Count > 0)
{
foreach (var childType in rule.ChildTypes)
{
if (_interceptRules.ContainsKey(childType))
{
_interceptRules[childType].IsEnabled = true;
}
}
}
// 如果是子规则被禁用,检查是否需要禁用父规则
else if (!enabled && rule.ParentType.HasValue)
{
var parentRule = _interceptRules[rule.ParentType.Value];
// 检查是否还有其他启用的子规则
bool hasEnabledChildren = parentRule.ChildTypes.Any(childType =>
_interceptRules.ContainsKey(childType) && _interceptRules[childType].IsEnabled);
// 如果没有启用的子规则,则禁用父规则
if (!hasEnabledChildren)
{
parentRule.IsEnabled = false;
}
}
// 如果是子规则被启用,则启用父规则
else if (enabled && rule.ParentType.HasValue)
{
var parentRule = _interceptRules[rule.ParentType.Value];
parentRule.IsEnabled = true;
}
}
}
@@ -468,18 +779,106 @@ namespace Ink_Canvas.Helpers
{
if (!_isRunning) return;
try
lock (_scanLock)
{
EnumWindows(EnumWindowsCallback, IntPtr.Zero);
}
catch (Exception ex)
{
// 记录错误但不中断扫描
LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
try
{
var scanStartTime = DateTime.Now;
var windowsFound = 0;
var windowsIntercepted = 0;
// 清理过期的缓存
CleanupExpiredCache();
// 使用优化的扫描策略
if (_consecutiveEmptyScans > 3)
{
// 如果连续多次扫描没有发现新窗口,使用快速扫描模式
PerformQuickScan(ref windowsFound, ref windowsIntercepted);
}
else
{
// 正常扫描模式
PerformFullScan(ref windowsFound, ref windowsIntercepted);
}
// 更新扫描统计
UpdateScanStatistics(windowsFound, windowsIntercepted, scanStartTime);
// 动态调整扫描间隔
AdjustScanInterval();
}
catch (Exception ex)
{
// 记录错误但不中断扫描
LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
_consecutiveEmptyScans++;
}
}
}
private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
/// <summary>
/// 执行快速扫描 - 只检查已知进程
/// </summary>
private void PerformQuickScan(ref int windowsFound, ref int windowsIntercepted)
{
var targetProcesses = new HashSet<string>();
var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 };
// 收集所有启用的规则对应的进程名
foreach (var rule in _interceptRules.Values)
{
if (rule.IsEnabled && !string.IsNullOrEmpty(rule.ProcessName))
{
targetProcesses.Add(rule.ProcessName.ToLower());
}
}
// 只扫描目标进程的窗口
foreach (var processName in targetProcesses)
{
try
{
var processes = Process.GetProcessesByName(processName);
foreach (var process in processes)
{
if (process.MainWindowHandle != IntPtr.Zero)
{
ProcessWindow(process.MainWindowHandle, scanData);
}
}
}
catch
{
// 忽略进程访问错误
}
}
windowsFound = scanData.WindowsFound;
windowsIntercepted = scanData.WindowsIntercepted;
}
/// <summary>
/// 执行完整扫描
/// </summary>
private void PerformFullScan(ref int windowsFound, ref int windowsIntercepted)
{
var scanData = new ScanData { WindowsFound = 0, WindowsIntercepted = 0 };
EnumWindows((hWnd, lParam) =>
{
ProcessWindow(hWnd, scanData);
return true;
}, IntPtr.Zero);
windowsFound = scanData.WindowsFound;
windowsIntercepted = scanData.WindowsIntercepted;
}
/// <summary>
/// 处理单个窗口
/// </summary>
private bool ProcessWindow(IntPtr hWnd, ScanData scanData)
{
try
{
@@ -489,10 +888,35 @@ namespace Ink_Canvas.Helpers
// 检查是否已经被拦截
if (_interceptedWindows.ContainsKey(hWnd)) return true;
// 检查缓存,避免重复处理
if (_knownWindows.Contains(hWnd))
{
var lastScan = _lastScanTime.ContainsKey(hWnd) ? _lastScanTime[hWnd] : DateTime.MinValue;
if (DateTime.Now - lastScan < TimeSpan.FromSeconds(30)) // 30秒内不重复处理
{
return true;
}
}
scanData.WindowsFound++;
_knownWindows.Add(hWnd);
_lastScanTime[hWnd] = DateTime.Now;
// 获取窗口信息
var windowInfo = GetWindowInfo(hWnd);
if (windowInfo == null) return true;
// 检查进程缓存
if (_processLastScanTime.ContainsKey(windowInfo.ProcessName))
{
var lastProcessScan = _processLastScanTime[windowInfo.ProcessName];
if (DateTime.Now - lastProcessScan < TimeSpan.FromSeconds(10)) // 10秒内不重复扫描同一进程
{
return true;
}
}
_processLastScanTime[windowInfo.ProcessName] = DateTime.Now;
// 检查窗口样式,过滤掉系统窗口和主窗口
var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
var style = GetWindowLong(hWnd, -16); // GWL_STYLE
@@ -530,6 +954,7 @@ namespace Ink_Canvas.Helpers
if (MatchesRule(windowInfo, rule))
{
InterceptWindow(hWnd, rule);
scanData.WindowsIntercepted++;
break;
}
}
@@ -543,6 +968,102 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 清理过期缓存
/// </summary>
private void CleanupExpiredCache()
{
var now = DateTime.Now;
var expiredWindows = new List<IntPtr>();
var expiredProcesses = new List<string>();
// 清理窗口缓存
foreach (var kvp in _lastScanTime)
{
if (now - kvp.Value > TimeSpan.FromMinutes(5))
{
expiredWindows.Add(kvp.Key);
}
}
foreach (var hWnd in expiredWindows)
{
_lastScanTime.Remove(hWnd);
_knownWindows.Remove(hWnd);
}
// 清理进程缓存
foreach (var kvp in _processLastScanTime)
{
if (now - kvp.Value > TimeSpan.FromMinutes(2))
{
expiredProcesses.Add(kvp.Key);
}
}
foreach (var processName in expiredProcesses)
{
_processLastScanTime.Remove(processName);
}
}
/// <summary>
/// 更新扫描统计
/// </summary>
private void UpdateScanStatistics(int windowsFound, int windowsIntercepted, DateTime scanStartTime)
{
var scanDuration = DateTime.Now - scanStartTime;
if (windowsFound == 0)
{
_consecutiveEmptyScans++;
}
else
{
_consecutiveEmptyScans = 0;
_lastSuccessfulScan = DateTime.Now;
}
}
/// <summary>
/// 动态调整扫描间隔
/// </summary>
private void AdjustScanInterval()
{
if (!_isRunning) return;
int newInterval;
if (_consecutiveEmptyScans > 5)
{
// 连续多次空扫描,增加间隔到30秒
newInterval = 30000;
}
else if (_consecutiveEmptyScans > 3)
{
// 连续多次空扫描,增加间隔到15秒
newInterval = 15000;
}
else if (_consecutiveEmptyScans > 1)
{
// 连续空扫描,增加间隔到10秒
newInterval = 10000;
}
else
{
// 正常扫描,使用5秒间隔
newInterval = 5000;
}
// 更新定时器间隔
_scanTimer.Change(newInterval, newInterval);
}
private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
{
// 这个方法现在由ProcessWindow替代,保留用于兼容性
return true;
}
private WindowInfo GetWindowInfo(IntPtr hWnd)
{
try
@@ -722,6 +1243,12 @@ namespace Ink_Canvas.Helpers
public Process Process { get; set; }
}
private class ScanData
{
public int WindowsFound { get; set; }
public int WindowsIntercepted { get; set; }
}
#endregion
#region
+474 -17
View File
@@ -5,7 +5,9 @@ using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Forms;
namespace Ink_Canvas.Helpers
{
@@ -19,6 +21,16 @@ namespace Ink_Canvas.Helpers
private readonly MainWindow _mainWindow;
private bool _isDisposed;
private bool _hotkeysShouldBeRegistered = true; // 启动时注册热键
// 多屏幕支持相关字段
private Screen _currentScreen;
private bool _isMultiScreenMode = false;
private bool _enableScreenSpecificHotkeys = true; // 是否启用基于屏幕的热键注册
// 智能热键管理相关字段
private bool _isWindowFocused = false;
private bool _isMouseOverWindow = false;
private System.Windows.Threading.DispatcherTimer _mousePositionTimer;
// 配置文件路径
private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "Configs", "HotkeyConfig.json");
@@ -31,6 +43,9 @@ namespace Ink_Canvas.Helpers
_registeredHotkeys = new Dictionary<string, HotkeyInfo>();
_hotkeysShouldBeRegistered = true; // 启动时注册热键
// 初始化多屏幕支持
InitializeMultiScreenSupport();
// 启动时确保配置文件存在
EnsureConfigFileExists();
}
@@ -52,6 +67,12 @@ namespace Ink_Canvas.Helpers
if (_isDisposed)
return false;
// 检查是否应该注册热键(基于屏幕和模式)
if (!ShouldRegisterHotkeys())
{
return false;
}
// 如果快捷键已存在,先注销
if (_registeredHotkeys.ContainsKey(hotkeyName))
{
@@ -85,7 +106,10 @@ namespace Ink_Canvas.Helpers
});
_registeredHotkeys[hotkeyName] = hotkeyInfo;
// 成功注册全局快捷键
// 记录注册信息
var screenInfo = _isMultiScreenMode ? $" (屏幕: {_currentScreen?.DeviceName})" : "";
return true;
}
catch (Exception ex)
@@ -179,7 +203,6 @@ namespace Ink_Canvas.Helpers
{
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile("快捷键配置文件不存在");
return new List<HotkeyInfo>();
}
@@ -212,7 +235,6 @@ namespace Ink_Canvas.Helpers
});
}
LogHelper.WriteLogToFile($"从配置文件读取到 {hotkeyList.Count} 个快捷键信息");
return hotkeyList;
}
catch (Exception ex)
@@ -287,7 +309,6 @@ namespace Ink_Canvas.Helpers
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning);
LogHelper.WriteLogToFile("配置文件不存在,创建默认配置文件并注册默认快捷键");
CreateDefaultConfigFile();
RegisterDefaultHotkeys();
_hotkeysShouldBeRegistered = true;
@@ -299,7 +320,6 @@ namespace Ink_Canvas.Helpers
{
// 成功从配置文件加载快捷键设置
_hotkeysShouldBeRegistered = true;
LogHelper.WriteLogToFile("成功从配置文件加载快捷键设置");
}
else
{
@@ -322,11 +342,9 @@ namespace Ink_Canvas.Helpers
{
try
{
LogHelper.WriteLogToFile("开始保存快捷键配置到配置文件", LogHelper.LogType.Event);
if (SaveHotkeysToConfigFile())
{
LogHelper.WriteLogToFile("快捷键配置已成功保存到配置文件", LogHelper.LogType.Event);
}
else
{
@@ -350,10 +368,18 @@ namespace Ink_Canvas.Helpers
if (!_hotkeysShouldBeRegistered)
{
_hotkeysShouldBeRegistered = true;
LogHelper.WriteLogToFile("启用快捷键注册功能");
// 立即加载快捷键设置
LoadHotkeysFromSettings();
// 启动鼠标位置监控定时器
if (_isMultiScreenMode && _enableScreenSpecificHotkeys && _mousePositionTimer != null)
{
_mousePositionTimer.Start();
}
// 根据上下文决定是否立即加载快捷键
if (ShouldEnableHotkeysBasedOnContext())
{
LoadHotkeysFromSettings();
}
}
else
{
@@ -377,12 +403,17 @@ namespace Ink_Canvas.Helpers
{
_hotkeysShouldBeRegistered = false;
// 停止鼠标位置监控定时器
if (_mousePositionTimer != null && _mousePositionTimer.IsEnabled)
{
_mousePositionTimer.Stop();
}
// 注销所有快捷键
UnregisterAllHotkeys();
}
else
{
LogHelper.WriteLogToFile("快捷键注册功能已经禁用");
}
}
catch (Exception ex)
@@ -407,7 +438,6 @@ namespace Ink_Canvas.Helpers
{
// 如果设置允许,则在鼠标模式下也启用快捷键
EnableHotkeyRegistration();
LogHelper.WriteLogToFile("切换到鼠标模式,但根据设置保持快捷键启用");
}
else
{
@@ -455,7 +485,6 @@ namespace Ink_Canvas.Helpers
if (success)
{
LogHelper.WriteLogToFile($"成功更新快捷键 {hotkeyName}: {modifiers}+{key}", LogHelper.LogType.Event);
// 自动保存配置
SaveHotkeysToSettings();
}
@@ -468,9 +497,418 @@ namespace Ink_Canvas.Helpers
return false;
}
}
/// <summary>
/// 启用基于屏幕的热键注册
/// </summary>
public void EnableScreenSpecificHotkeys()
{
try
{
_enableScreenSpecificHotkeys = true;
// 如果当前在多屏幕环境下,刷新热键注册
if (_isMultiScreenMode)
{
RefreshHotkeysForCurrentScreen();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"启用基于屏幕的热键注册时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 禁用基于屏幕的热键注册
/// </summary>
public void DisableScreenSpecificHotkeys()
{
try
{
_enableScreenSpecificHotkeys = false;
// 重新注册热键(全局模式)
if (_hotkeysShouldBeRegistered)
{
RefreshHotkeysForCurrentScreen();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"禁用基于屏幕的热键注册时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 获取当前屏幕信息
/// </summary>
/// <returns>当前屏幕信息</returns>
public string GetCurrentScreenInfo()
{
try
{
if (_isMultiScreenMode && _currentScreen != null)
{
return $"多屏幕环境 - 当前屏幕: {_currentScreen.DeviceName} ({_currentScreen.Bounds.Width}x{_currentScreen.Bounds.Height})";
}
else
{
return "单屏幕环境";
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"获取当前屏幕信息时出错: {ex.Message}", LogHelper.LogType.Error);
return "无法获取屏幕信息";
}
}
/// <summary>
/// 检查是否启用了基于屏幕的热键注册
/// </summary>
/// <returns>是否启用</returns>
public bool IsScreenSpecificHotkeysEnabled()
{
return _enableScreenSpecificHotkeys && _isMultiScreenMode;
}
/// <summary>
/// 手动刷新当前屏幕的热键注册
/// </summary>
public void RefreshCurrentScreenHotkeys()
{
try
{
RefreshHotkeysForCurrentScreen();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"刷新当前屏幕热键时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region Private Helper Methods
/// <summary>
/// 初始化多屏幕支持
/// </summary>
private void InitializeMultiScreenSupport()
{
try
{
// 检测是否有多个屏幕
_isMultiScreenMode = ScreenDetectionHelper.HasMultipleScreens();
if (_isMultiScreenMode)
{
// 获取当前窗口所在的屏幕
_currentScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
// 监听窗口位置变化事件
_mainWindow.LocationChanged += OnWindowLocationChanged;
// 初始化智能热键管理
InitializeSmartHotkeyManagement();
}
else
{
_currentScreen = ScreenDetectionHelper.GetPrimaryScreen();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化多屏幕支持时出错: {ex.Message}", LogHelper.LogType.Error);
_isMultiScreenMode = false;
_currentScreen = ScreenDetectionHelper.GetPrimaryScreen();
}
}
/// <summary>
/// 初始化智能热键管理
/// </summary>
private void InitializeSmartHotkeyManagement()
{
try
{
// 监听窗口焦点事件
_mainWindow.GotFocus += OnWindowGotFocus;
_mainWindow.LostFocus += OnWindowLostFocus;
// 监听鼠标进入/离开事件
_mainWindow.MouseEnter += OnMouseEnterWindow;
_mainWindow.MouseLeave += OnMouseLeaveWindow;
// 初始化鼠标位置监控定时器
_mousePositionTimer = new System.Windows.Threading.DispatcherTimer();
_mousePositionTimer.Interval = TimeSpan.FromMilliseconds(500); // 每500ms检查一次
_mousePositionTimer.Tick += OnMousePositionTimerTick;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化热键管理时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 窗口位置变化事件处理
/// </summary>
private void OnWindowLocationChanged(object sender, EventArgs e)
{
try
{
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
return;
var newScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
if (newScreen != null && newScreen != _currentScreen)
{
_currentScreen = newScreen;
// 重新注册热键以适应新屏幕
RefreshHotkeysForCurrentScreen();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口位置变化时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 为当前屏幕刷新热键注册
/// </summary>
private void RefreshHotkeysForCurrentScreen()
{
try
{
if (!_hotkeysShouldBeRegistered)
return;
// 注销所有现有热键
UnregisterAllHotkeys();
// 重新注册热键
LoadHotkeysFromSettings();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"刷新当前屏幕热键时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 窗口获得焦点事件处理
/// </summary>
private void OnWindowGotFocus(object sender, RoutedEventArgs e)
{
try
{
_isWindowFocused = true;
UpdateHotkeyStateBasedOnContext();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口获得焦点事件时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 窗口失去焦点事件处理
/// </summary>
private void OnWindowLostFocus(object sender, RoutedEventArgs e)
{
try
{
_isWindowFocused = false;
UpdateHotkeyStateBasedOnContext();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口失去焦点事件时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 鼠标进入窗口事件处理
/// </summary>
private void OnMouseEnterWindow(object sender, System.Windows.Input.MouseEventArgs e)
{
try
{
_isMouseOverWindow = true;
UpdateHotkeyStateBasedOnContext();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理鼠标进入窗口事件时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 鼠标离开窗口事件处理
/// </summary>
private void OnMouseLeaveWindow(object sender, System.Windows.Input.MouseEventArgs e)
{
try
{
_isMouseOverWindow = false;
UpdateHotkeyStateBasedOnContext();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理鼠标离开窗口事件时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 鼠标位置定时器事件处理
/// </summary>
private void OnMousePositionTimerTick(object sender, EventArgs e)
{
try
{
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
return;
// 检查鼠标是否在当前窗口所在的屏幕上
var mousePosition = Control.MousePosition;
var currentScreen = Screen.FromPoint(mousePosition);
// 无论屏幕是否变化,都检查热键状态
// 这样可以确保热键状态始终与当前上下文保持一致
bool shouldEnableHotkeys = ShouldEnableHotkeysBasedOnContext();
bool currentlyHasHotkeys = _registeredHotkeys.Count > 0;
if (shouldEnableHotkeys && !currentlyHasHotkeys)
{
UpdateHotkeyStateBasedOnContext();
}
else if (!shouldEnableHotkeys && currentlyHasHotkeys)
{
UpdateHotkeyStateBasedOnContext();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理鼠标位置定时器事件时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 根据上下文更新热键状态
/// </summary>
private void UpdateHotkeyStateBasedOnContext()
{
try
{
if (!_hotkeysShouldBeRegistered)
return;
bool shouldEnableHotkeys = ShouldEnableHotkeysBasedOnContext();
if (shouldEnableHotkeys)
{
// 如果热键未注册,则注册
if (_registeredHotkeys.Count == 0)
{
LoadHotkeysFromSettings();
}
}
else
{
// 如果热键已注册,则注销(与鼠标模式禁用保持一致)
if (_registeredHotkeys.Count > 0)
{
UnregisterAllHotkeys();
// 注意:这里不设置 _hotkeysShouldBeRegistered = false
// 因为我们需要保持热键系统的启用状态,只是暂时注销热键
// 这样当上下文变化时,热键可以重新注册
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"根据上下文更新热键状态时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 检查是否应该注册热键(基于屏幕和模式)
/// </summary>
/// <returns>是否应该注册热键</returns>
private bool ShouldRegisterHotkeys()
{
try
{
// 如果禁用热键注册,则不注册
if (!_hotkeysShouldBeRegistered)
return false;
// 如果启用基于屏幕的热键注册
if (_enableScreenSpecificHotkeys && _isMultiScreenMode)
{
return ShouldEnableHotkeysBasedOnContext();
}
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"检查是否应该注册热键时出错: {ex.Message}", LogHelper.LogType.Error);
return true; // 出错时默认注册
}
}
/// <summary>
/// 根据上下文检查是否应该启用热键
/// </summary>
/// <returns>是否应该启用热键</returns>
private bool ShouldEnableHotkeysBasedOnContext()
{
try
{
// 策略1:鼠标在窗口上时启用热键(最高优先级)
if (_isMouseOverWindow)
{
return true;
}
// 策略2:在多屏幕环境下,检查鼠标是否在当前窗口所在的屏幕上
if (_isMultiScreenMode)
{
var mousePosition = Control.MousePosition;
var mouseScreen = Screen.FromPoint(mousePosition);
if (mouseScreen == _currentScreen)
{
return true;
}
else
{
return false;
}
}
// 策略3:单屏幕环境下,窗口有焦点时启用热键
if (_isWindowFocused)
{
return true;
}
// 默认情况:禁用热键
return false;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"检查是否应该启用热键时出错: {ex.Message}", LogHelper.LogType.Error);
return true; // 出错时默认启用
}
}
/// <summary>
/// 切换到指定笔类型
/// </summary>
@@ -513,7 +951,6 @@ namespace Ink_Canvas.Helpers
// 如果配置文件不存在,创建默认配置文件
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile($"快捷键配置文件不存在,创建默认配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event);
CreateDefaultConfigFile();
}
}
@@ -579,7 +1016,6 @@ namespace Ink_Canvas.Helpers
// 写入配置文件
File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
LogHelper.WriteLogToFile($"已创建默认快捷键配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event);
}
catch (Exception ex)
{
@@ -643,7 +1079,6 @@ namespace Ink_Canvas.Helpers
}
}
LogHelper.WriteLogToFile($"成功加载 {successCount}/{config.Hotkeys.Count} 个快捷键配置", LogHelper.LogType.Event);
if (successCount > 0)
{
_hotkeysShouldBeRegistered = true;
@@ -702,7 +1137,6 @@ namespace Ink_Canvas.Helpers
// 直接写入原文件,覆盖原有内容
File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
LogHelper.WriteLogToFile($"快捷键配置已保存到: {HotkeyConfigFile}", LogHelper.LogType.Event);
return true;
}
catch (Exception ex)
@@ -908,6 +1342,29 @@ namespace Ink_Canvas.Helpers
{
if (!_isDisposed)
{
// 注销所有快捷键
UnregisterAllHotkeys();
// 停止定时器
if (_mousePositionTimer != null)
{
_mousePositionTimer.Stop();
_mousePositionTimer = null;
}
// 移除事件监听器
if (_mainWindow != null)
{
if (_isMultiScreenMode)
{
_mainWindow.LocationChanged -= OnWindowLocationChanged;
}
_mainWindow.GotFocus -= OnWindowGotFocus;
_mainWindow.LostFocus -= OnWindowLostFocus;
_mainWindow.MouseEnter -= OnMouseEnterWindow;
_mainWindow.MouseLeave -= OnMouseLeaveWindow;
}
_isDisposed = true;
}
+5
View File
@@ -71,6 +71,11 @@ namespace Ink_Canvas.Helpers
try
{
// 确保主窗口的InkCanvas保持Ink编辑模式,防止墨迹渐隐时切换到鼠标模式
if (_mainWindow.inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
{
_mainWindow.inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 记录墨迹的起点和终点
_strokeStartPoints[stroke] = startPoint;
+6 -3
View File
@@ -75,6 +75,9 @@ namespace Ink_Canvas.Helpers
/// </summary>
public void ApplyQualitySettings()
{
// 保存用户设置的异步处理偏好
bool userAsyncPreference = UseAsyncProcessing;
switch (Quality)
{
case SmoothingQuality.Performance:
@@ -85,7 +88,7 @@ namespace Ink_Canvas.Helpers
CurveTension = 0.15;
MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2);
UseHardwareAcceleration = true;
UseAsyncProcessing = true;
UseAsyncProcessing = userAsyncPreference;
break;
case SmoothingQuality.Balanced:
@@ -96,7 +99,7 @@ namespace Ink_Canvas.Helpers
CurveTension = 0.25;
MaxConcurrentTasks = Environment.ProcessorCount;
UseHardwareAcceleration = true;
UseAsyncProcessing = true;
UseAsyncProcessing = userAsyncPreference;
break;
case SmoothingQuality.Quality:
@@ -107,7 +110,7 @@ namespace Ink_Canvas.Helpers
CurveTension = 0.35;
MaxConcurrentTasks = Environment.ProcessorCount;
UseHardwareAcceleration = true;
UseAsyncProcessing = true;
UseAsyncProcessing = userAsyncPreference;
break;
}
}
+157 -10
View File
@@ -1,7 +1,6 @@
using Microsoft.Office.Interop.PowerPoint;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
@@ -27,6 +26,11 @@ namespace Ink_Canvas.Helpers
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
@@ -34,6 +38,7 @@ namespace Ink_Canvas.Helpers
{
_presentationManagers = new Dictionary<string, PPTInkManager>();
_presentationInfos = new Dictionary<string, PresentationInfo>();
_strokeBackups = new Dictionary<string, Dictionary<int, StrokeCollection>>();
}
#endregion
@@ -113,8 +118,15 @@ namespace Ink_Canvas.Helpers
var currentPresentation = GetCurrentActivePresentation();
if (currentPresentation != null)
{
currentManager.SaveAllStrokesToFile(currentPresentation);
LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace);
try
{
currentManager.SaveAllStrokesToFile(currentPresentation);
LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存当前演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
}
}
@@ -127,10 +139,10 @@ namespace Ink_Canvas.Helpers
_presentationInfos[presentationId].LastAccessTime = DateTime.Now;
}
if (_currentActivePresentationId != presentationId)
{
LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace);
}
if (_currentActivePresentationId != presentationId)
{
LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace);
}
return true;
}
else
@@ -162,7 +174,17 @@ namespace Ink_Canvas.Helpers
var manager = GetCurrentManager();
if (manager != null)
{
// 保存到管理器
manager.SaveCurrentSlideStrokes(slideIndex, strokes);
// 只有在保存成功后才创建备份
if (!string.IsNullOrEmpty(_currentActivePresentationId))
{
CreateStrokeBackup(_currentActivePresentationId, slideIndex, strokes);
}
// 检查是否需要执行定期备份
CheckAndPerformBackup();
}
}
catch (Exception ex)
@@ -210,12 +232,29 @@ namespace Ink_Canvas.Helpers
var manager = GetCurrentManager();
if (manager != null)
{
return manager.LoadSlideStrokes(slideIndex);
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);
}
}
}
@@ -512,6 +551,12 @@ namespace Ink_Canvas.Helpers
}
_presentationInfos.Remove(id);
// 清理备份数据
if (_strokeBackups.ContainsKey(id))
{
_strokeBackups.Remove(id);
}
LogHelper.WriteLogToFile($"已清理非活跃演示文稿: {id}", LogHelper.LogType.Trace);
}
}
@@ -521,6 +566,96 @@ namespace Ink_Canvas.Helpers
}
}
}
/// <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
@@ -562,7 +697,7 @@ namespace Ink_Canvas.Helpers
var presentationPath = presentation.FullName;
var fileHash = GetFileHash(presentationPath);
var processId = GetProcessId(presentation);
return $"{presentation.Name}_{fileHash}_{processId}";
return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}_{processId}";
}
catch (COMException comEx)
{
@@ -638,12 +773,24 @@ namespace Ink_Canvas.Helpers
{
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;
}
@@ -663,4 +810,4 @@ namespace Ink_Canvas.Helpers
public DateTime CreatedTime { get; set; }
public DateTime LastAccessTime { get; set; }
}
}
}
+164 -12
View File
@@ -35,6 +35,12 @@ namespace Ink_Canvas.Helpers
private DateTime _lastSwitchTime = DateTime.MinValue;
private int _lastSwitchSlideIndex = -1;
private const int MinSwitchIntervalMs = 100; // 最小切换间隔100毫秒
// 内存管理相关字段
private long _totalMemoryUsage = 0;
private const long MaxMemoryUsageBytes = 100 * 1024 * 1024; // 100MB限制
private DateTime _lastMemoryCleanup = DateTime.MinValue;
private const int MemoryCleanupIntervalMinutes = 5; // 5分钟清理一次
#endregion
#region Constructor
@@ -118,24 +124,34 @@ namespace Ink_Canvas.Helpers
{
if (DateTime.Now < _inkLockUntil)
{
LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning);
}
return;
}
if (slideIndex < _memoryStreams.Length)
{
// 先释放旧的内存流,防止内存泄漏
try
{
_memoryStreams[slideIndex]?.Dispose();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning);
}
// 创建新的内存流
var ms = new MemoryStream();
strokes.Save(ms);
ms.Position = 0;
// 释放旧的内存流
_memoryStreams[slideIndex]?.Dispose();
_memoryStreams[slideIndex] = ms;
if (ms.Length > 0)
{
}
// 检查内存使用情况
CheckAndPerformMemoryCleanup();
}
}
catch (Exception ex)
@@ -158,12 +174,20 @@ namespace Ink_Canvas.Helpers
{
if (slideIndex < _memoryStreams.Length)
{
// 先释放旧的内存流,防止内存泄漏
try
{
_memoryStreams[slideIndex]?.Dispose();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning);
}
// 创建新的内存流
var ms = new MemoryStream();
strokes.Save(ms);
ms.Position = 0;
// 释放旧的内存流
_memoryStreams[slideIndex]?.Dispose();
_memoryStreams[slideIndex] = ms;
LogHelper.WriteLogToFile($"已强制保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace);
@@ -388,13 +412,30 @@ namespace Ink_Canvas.Helpers
{
try
{
for (int i = 0; i < _memoryStreams.Length; i++)
// 安全释放所有内存流
if (_memoryStreams != null)
{
_memoryStreams[i]?.Dispose();
_memoryStreams[i] = null;
for (int i = 0; i < _memoryStreams.Length; i++)
{
try
{
_memoryStreams[i]?.Dispose();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"释放内存流{i}失败: {ex}", LogHelper.LogType.Warning);
}
finally
{
_memoryStreams[i] = null;
}
}
// 重新初始化数组
_memoryStreams = new MemoryStream[_maxSlides + 2];
}
CurrentStrokes.Clear();
CurrentStrokes?.Clear();
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
}
catch (Exception ex)
@@ -430,6 +471,13 @@ namespace Ink_Canvas.Helpers
return true;
}
// 如果当前页面不是锁定页面,但锁定时间很短(小于50ms),允许写入
// 这样可以确保旧页面的墨迹能够及时保存
if (DateTime.Now - (_inkLockUntil.AddMilliseconds(-InkLockMilliseconds)) < TimeSpan.FromMilliseconds(50))
{
return true;
}
// 只有在快速切换且页面不同时才锁定
return false;
}
@@ -447,6 +495,110 @@ namespace Ink_Canvas.Helpers
_lastSwitchSlideIndex = -1;
}
}
/// <summary>
/// 检查并执行内存清理
/// </summary>
private void CheckAndPerformMemoryCleanup()
{
try
{
var now = DateTime.Now;
// 检查是否需要执行内存清理
if (now - _lastMemoryCleanup < TimeSpan.FromMinutes(MemoryCleanupIntervalMinutes))
{
return;
}
// 计算当前内存使用量
long currentMemoryUsage = 0;
if (_memoryStreams != null)
{
for (int i = 0; i < _memoryStreams.Length; i++)
{
if (_memoryStreams[i] != null)
{
currentMemoryUsage += _memoryStreams[i].Length;
}
}
}
_totalMemoryUsage = currentMemoryUsage;
// 如果内存使用量超过限制,执行清理
if (currentMemoryUsage > MaxMemoryUsageBytes)
{
LogHelper.WriteLogToFile($"内存使用量超限 ({currentMemoryUsage / 1024 / 1024}MB),开始清理", LogHelper.LogType.Warning);
// 清理非当前页面的墨迹
CleanupInactiveSlideStrokes();
_lastMemoryCleanup = now;
LogHelper.WriteLogToFile($"内存清理完成,当前使用量: {_totalMemoryUsage / 1024 / 1024}MB", LogHelper.LogType.Trace);
}
else
{
_lastMemoryCleanup = now;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"内存清理检查失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 清理非活跃页面的墨迹
/// </summary>
private void CleanupInactiveSlideStrokes()
{
try
{
if (_memoryStreams == null) return;
int cleanedCount = 0;
long freedMemory = 0;
for (int i = 0; i < _memoryStreams.Length; i++)
{
// 保留当前锁定页面和最近访问的页面
if (i == _lockedSlideIndex || i == _lastSwitchSlideIndex)
{
continue;
}
if (_memoryStreams[i] != null)
{
long memorySize = _memoryStreams[i].Length;
try
{
_memoryStreams[i].Dispose();
freedMemory += memorySize;
cleanedCount++;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理页面{i}墨迹失败: {ex}", LogHelper.LogType.Warning);
}
finally
{
_memoryStreams[i] = null;
}
}
}
if (cleanedCount > 0)
{
LogHelper.WriteLogToFile($"已清理{cleanedCount}个页面的墨迹,释放内存: {freedMemory / 1024}KB", LogHelper.LogType.Trace);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理非活跃页面墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
#endregion
#region Private Methods
@@ -456,7 +608,7 @@ namespace Ink_Canvas.Helpers
{
var presentationPath = presentation.FullName;
var fileHash = GetFileHash(presentationPath);
return $"{presentation.Name}_{fileHash}";
return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}";
}
catch (Exception ex)
{
+53 -52
View File
@@ -93,7 +93,6 @@ namespace Ink_Canvas.Helpers
// COM对象已失效,触发断开连接
DisconnectFromPPT();
}
LogHelper.WriteLogToFile($"验证PPT放映窗口失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
return false;
}
}
@@ -411,63 +410,30 @@ namespace Ink_Canvas.Helpers
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
LogHelper.WriteLogToFile($"取消PPT事件注册时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
}
catch (InvalidCastException)
{
// COM对象类型转换失败,通常是因为对象已经被释放
LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"取消PPT事件注册时发生异常: {ex}", LogHelper.LogType.Warning);
}
}, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(1));
}
}
catch (Exception)
catch (Exception ex)
{
LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex}", LogHelper.LogType.Warning);
}
// 释放COM对象
try
{
if (Marshal.IsComObject(CurrentSlide))
{
Marshal.ReleaseComObject(CurrentSlide);
}
}
catch (Exception)
{
}
try
{
if (Marshal.IsComObject(CurrentSlides))
{
Marshal.ReleaseComObject(CurrentSlides);
}
}
catch (Exception)
{
}
try
{
if (Marshal.IsComObject(CurrentPresentation))
{
Marshal.ReleaseComObject(CurrentPresentation);
}
}
catch (Exception)
{
}
try
{
if (Marshal.IsComObject(PPTApplication))
{
Marshal.ReleaseComObject(PPTApplication);
}
}
catch (Exception)
{
}
// 安全释放COM对象
SafeReleaseComObject(CurrentSlide, "CurrentSlide");
SafeReleaseComObject(CurrentSlides, "CurrentSlides");
SafeReleaseComObject(CurrentPresentation, "CurrentPresentation");
SafeReleaseComObject(PPTApplication, "PPTApplication");
// 清理引用
PPTApplication = null;
@@ -491,6 +457,30 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 安全释放COM对象
/// </summary>
private void SafeReleaseComObject(object comObject, string objectName)
{
try
{
if (comObject != null && Marshal.IsComObject(comObject))
{
int refCount = Marshal.ReleaseComObject(comObject);
LogHelper.WriteLogToFile($"已释放COM对象 {objectName},引用计数: {refCount}", LogHelper.LogType.Trace);
}
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时发生异常: {ex}", LogHelper.LogType.Warning);
}
}
private void UpdateCurrentPresentationInfo()
{
try
@@ -859,10 +849,23 @@ namespace Ink_Canvas.Helpers
// 如果在放映模式,获取放映窗口的演示文稿
if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0)
{
var slideShowWindow = PPTApplication.SlideShowWindows[1];
if (slideShowWindow?.View != null)
try
{
return (Presentation)slideShowWindow.View.Slide.Parent;
var slideShowWindow = PPTApplication.SlideShowWindows[1];
if (slideShowWindow?.View != null)
{
return (Presentation)slideShowWindow.View.Slide.Parent;
}
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
if (hr == 0x80048240) // Integer out of range
{
// 放映窗口已不存在,返回null
return null;
}
throw; // 重新抛出其他COM异常
}
}
@@ -935,12 +938,10 @@ namespace Ink_Canvas.Helpers
// COM对象已失效,触发断开连接
DisconnectFromPPT();
}
LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {comEx.Message}", LogHelper.LogType.Warning);
return 0;
}
catch (Exception ex)
catch (Exception)
{
LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {ex}", LogHelper.LogType.Error);
return 0;
}
}
+2 -3
View File
@@ -92,7 +92,6 @@ namespace Ink_Canvas.Helpers
// 页数无效时清空页码显示
_mainWindow.PPTBtnPageNow.Text = "?";
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
LogHelper.WriteLogToFile($"PPT页数无效,清空页码显示: 当前页={currentSlide}, 总页数={totalSlides}", LogHelper.LogType.Warning);
}
UpdateNavigationPanelsVisibility();
@@ -132,7 +131,6 @@ namespace Ink_Canvas.Helpers
// 页数无效时清空页码显示
_mainWindow.PPTBtnPageNow.Text = "?";
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
LogHelper.WriteLogToFile($"PPT页数无效,清空页码显示: 当前页={currentSlide}, 总页数={totalSlides}", LogHelper.LogType.Warning);
}
}
catch (Exception ex)
@@ -182,7 +180,8 @@ namespace Ink_Canvas.Helpers
bool shouldShowButtons = ShowPPTButton &&
_mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
isInSlideShow &&
hasValidPageCount;
hasValidPageCount &&
!MainWindow.Settings.Automation.IsAutoFoldInPPTSlideShow;
if (!shouldShowButtons)
{
+156
View File
@@ -0,0 +1,156 @@
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using Point = System.Windows.Point;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 屏幕检测帮助类 - 用于检测窗口所在的屏幕和屏幕信息
/// </summary>
public static class ScreenDetectionHelper
{
/// <summary>
/// 获取窗口所在的屏幕
/// </summary>
/// <param name="window">要检测的窗口</param>
/// <returns>窗口所在的屏幕,如果无法检测则返回主屏幕</returns>
public static Screen GetWindowScreen(Window window)
{
try
{
if (window == null)
return Screen.PrimaryScreen;
// 获取窗口的句柄
var hwndSource = PresentationSource.FromVisual(window) as HwndSource;
if (hwndSource == null)
return Screen.PrimaryScreen;
// 获取窗口在屏幕上的位置
var windowRect = GetWindowScreenBounds(window);
// 查找与窗口重叠最多的屏幕
Screen targetScreen = null;
double maxIntersection = 0;
foreach (var screen in Screen.AllScreens)
{
var intersection = Rectangle.Intersect(windowRect, screen.Bounds);
if (intersection.Width * intersection.Height > maxIntersection)
{
maxIntersection = intersection.Width * intersection.Height;
targetScreen = screen;
}
}
return targetScreen ?? Screen.PrimaryScreen;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"检测窗口屏幕时出错: {ex.Message}", LogHelper.LogType.Warning);
return Screen.PrimaryScreen;
}
}
/// <summary>
/// 获取窗口在屏幕坐标系中的边界
/// </summary>
/// <param name="window">要检测的窗口</param>
/// <returns>窗口的屏幕边界</returns>
private static Rectangle GetWindowScreenBounds(Window window)
{
try
{
// 获取窗口左上角在屏幕上的位置
var topLeft = window.PointToScreen(new Point(0, 0));
// 获取窗口右下角在屏幕上的位置
var bottomRight = window.PointToScreen(new Point(window.ActualWidth, window.ActualHeight));
return new Rectangle(
(int)topLeft.X,
(int)topLeft.Y,
(int)(bottomRight.X - topLeft.X),
(int)(bottomRight.Y - topLeft.Y));
}
catch
{
// 如果无法获取精确位置,返回窗口的Left和Top
return new Rectangle(
(int)window.Left,
(int)window.Top,
(int)window.Width,
(int)window.Height);
}
}
/// <summary>
/// 检查是否有多个屏幕
/// </summary>
/// <returns>如果有多个屏幕返回true,否则返回false</returns>
public static bool HasMultipleScreens()
{
try
{
return Screen.AllScreens.Length > 1;
}
catch
{
return false;
}
}
/// <summary>
/// 获取主屏幕
/// </summary>
/// <returns>主屏幕</returns>
public static Screen GetPrimaryScreen()
{
try
{
return Screen.PrimaryScreen;
}
catch
{
return null;
}
}
/// <summary>
/// 获取所有屏幕信息
/// </summary>
/// <returns>所有屏幕的数组</returns>
public static Screen[] GetAllScreens()
{
try
{
return Screen.AllScreens;
}
catch
{
return new Screen[] { Screen.PrimaryScreen };
}
}
/// <summary>
/// 检查窗口是否在主屏幕上
/// </summary>
/// <param name="window">要检查的窗口</param>
/// <returns>如果窗口在主屏幕上返回true,否则返回false</returns>
public static bool IsWindowOnPrimaryScreen(Window window)
{
try
{
var windowScreen = GetWindowScreen(window);
return windowScreen == Screen.PrimaryScreen;
}
catch
{
return true; // 出错时假设在主屏幕
}
}
}
}
+13
View File
@@ -208,6 +208,7 @@
<Resource Include="Resources\DeveloperAvatars\RaspberryKan.jpg" />
<Resource Include="Resources\DeveloperAvatars\wwei.png" />
<Resource Include="Resources\DeveloperAvatars\yuwenhui2020.png" />
<Resource Include="Resources\DeveloperAvatars\CJKmkp.jpg" />
<Resource Include="Resources\icc.ico" />
<Resource Include="Resources\Icons-png\AdmoxBooth.png" />
<Resource Include="Resources\Icons-png\AdmoxWhiteboard.png" />
@@ -492,6 +493,15 @@
<Resource Include="Resources\new-icons\unfold-chevron.png" />
<Resource Include="Resources\new-icons\zoom.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Icons-Fluent\ic_fluent_person_money_24_regular-light.png" />
<Resource Include="Resources\Icons-Fluent\ic_fluent_people_money_24_regular-light.png" />
<Resource Include="Resources\Icons-Fluent\ic_fluent_timer_24_regular-light.png" />
<Resource Include="Resources\new-icons\blackboard-light.png" />
<Resource Include="Resources\new-icons\end-slides-show-light.png" />
<Resource Include="Resources\new-icons\eye-light.png" />
<Resource Include="Resources\new-icons\chevron-left-light.png" />
</ItemGroup>
<ItemGroup>
<Compile Remove="AssemblyInfo.cs" />
</ItemGroup>
@@ -556,6 +566,9 @@
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Icons-png\HiteAnnotation.png" />
<Resource Include="Resources\Icons-png\AiClass.png" />
<Resource Include="Resources\Icons-png\天喻教育云.png" />
<Resource Include="Resources\Icons-png\畅言智慧课堂.png" />
<Resource Include="Resources\PresentationExample\bottombar-dark.png" />
<Resource Include="Resources\PresentationExample\bottombar-white.png" />
<Resource Include="Resources\PresentationExample\page.jpg" />
+432 -229
View File
File diff suppressed because it is too large Load Diff
+153 -16
View File
@@ -357,23 +357,21 @@ namespace Ink_Canvas
if (inkCanvas1.EditingMode == InkCanvasEditingMode.Ink) forcePointEraser = !forcePointEraser;
// 处理高级橡皮擦覆盖层的启用/禁用
var eraserOverlay = FindName("AdvancedEraserOverlay") as Border;
// 处理橡皮擦覆盖层的启用/禁用
var eraserOverlay = FindName("EraserOverlayCanvas") as Canvas;
if (eraserOverlay != null)
{
if (inkCanvas1.EditingMode == InkCanvasEditingMode.EraseByPoint)
{
// 橡皮擦模式下启用覆盖层
eraserOverlay.IsHitTestVisible = true;
Trace.WriteLine("Advanced Eraser: Overlay enabled in eraser mode");
EnableEraserOverlay();
Trace.WriteLine("Eraser: Overlay enabled in eraser mode");
}
else
{
// 其他模式下禁用覆盖层
eraserOverlay.IsHitTestVisible = false;
// 同时禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
Trace.WriteLine("Advanced Eraser: Overlay disabled in non-eraser mode");
DisableEraserOverlay();
Trace.WriteLine("Eraser: Overlay disabled in non-eraser mode");
}
}
}
@@ -392,6 +390,8 @@ namespace Ink_Canvas
loadPenCanvas();
//加载设置
LoadSettings(true);
AutoBackupManager.Initialize(Settings);
// 检查保存路径是否可用,不可用则修正
try
{
@@ -457,8 +457,30 @@ namespace Ink_Canvas
// HasNewUpdateWindow hasNewUpdateWindow = new HasNewUpdateWindow();
if (Environment.Is64BitProcess) GroupBoxInkRecognition.Visibility = Visibility.Collapsed;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
SystemEvents_UserPreferenceChanged(null, null);
// 根据设置应用主题
switch (Settings.Appearance.Theme)
{
case 0: // 浅色主题
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
SetTheme("Light");
break;
case 1: // 深色主题
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
SetTheme("Dark");
break;
case 2: // 跟随系统
if (IsSystemThemeLight())
{
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
SetTheme("Light");
}
else
{
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
SetTheme("Dark");
}
break;
}
//TextBlockVersion.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString();
LogHelper.WriteLogToFile("Ink Canvas Loaded", LogHelper.LogType.Event);
@@ -489,8 +511,8 @@ namespace Ink_Canvas
}
SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged;
// 自动收纳到侧边栏
if (Settings.Startup.IsFoldAtStartup)
// 自动收纳到侧边栏(若通过 --board 进入白板模式则跳过收纳)
if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode)
{
FoldFloatingBar_MouseUp(new object(), null);
}
@@ -530,10 +552,7 @@ namespace Ink_Canvas
ApplyNoFocusMode();
ToggleSwitchAlwaysOnTop.IsOn = Settings.Advanced.IsAlwaysOnTop;
ApplyAlwaysOnTop();
// 初始化UIElement选择系统
// 初始化剪贴板监控
InitializeClipboardMonitoring();
@@ -2741,5 +2760,123 @@ namespace Ink_Canvas
}
#endregion
#region Theme Toggle
/// <summary>
/// 主题下拉框选择变化事件
/// </summary>
private void ComboBoxTheme_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!isLoaded) return;
try
{
System.Windows.Controls.ComboBox comboBox = sender as System.Windows.Controls.ComboBox;
if (comboBox != null)
{
Settings.Appearance.Theme = comboBox.SelectedIndex;
// 应用新主题
ApplyTheme(comboBox.SelectedIndex);
// 保存设置
SaveSettingsToFile();
// 显示通知
string themeName;
switch (comboBox.SelectedIndex)
{
case 0:
themeName = "浅色主题";
break;
case 1:
themeName = "深色主题";
break;
case 2:
themeName = "跟随系统";
break;
default:
themeName = "未知主题";
break;
}
ShowNotification($"已切换到{themeName}");
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换主题时出错: {ex.Message}", LogHelper.LogType.Error);
ShowNotification("主题切换失败");
}
}
/// <summary>
/// 应用指定主题
/// </summary>
/// <param name="themeIndex">主题索引:0-浅色,1-深色,2-跟随系统</param>
private void ApplyTheme(int themeIndex)
{
try
{
switch (themeIndex)
{
case 0: // 浅色主题
SetTheme("Light");
// 浅色主题下设置浮动栏为完全不透明
ViewboxFloatingBar.Opacity = 1.0;
break;
case 1: // 深色主题
SetTheme("Dark");
// 深色主题下设置浮动栏为完全不透明
ViewboxFloatingBar.Opacity = 1.0;
break;
case 2: // 跟随系统
if (IsSystemThemeLight())
{
SetTheme("Light");
ViewboxFloatingBar.Opacity = 1.0;
}
else
{
SetTheme("Dark");
ViewboxFloatingBar.Opacity = 1.0;
}
break;
}
// 强制刷新通知框的颜色资源
RefreshNotificationColors();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"应用主题时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 刷新通知框的颜色资源
/// </summary>
private void RefreshNotificationColors()
{
try
{
// 强制刷新通知框的背景和前景色
var border = GridNotifications.Children.OfType<Border>().FirstOrDefault();
if (border != null)
{
border.Background = (Brush)Application.Current.FindResource("SettingsPageBackground");
border.BorderBrush = new SolidColorBrush(Color.FromRgb(185, 28, 28)); // 保持红色边框
}
TextBlockNotice.Foreground = (Brush)Application.Current.FindResource("SettingsPageForeground");
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"刷新通知框颜色时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
}
}
+1 -3
View File
@@ -271,7 +271,6 @@ namespace Ink_Canvas
if (dopsc[1] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
if (dopsc[2] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
if (dopsc[3] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
LogHelper.WriteLogToFile($"从收纳模式恢复时显示PPT翻页按钮 - 放映状态: {PPTManager?.IsInSlideShow}, 页数: {PPTManager?.SlidesCount}", LogHelper.LogType.Trace);
}
else
{
@@ -306,7 +305,6 @@ namespace Ink_Canvas
SidePannelMarginAnimation(-50, !unfoldFloatingBarByUser);
});
// 修复:在浮动栏展开后,重新设置按钮高亮状态
await Dispatcher.InvokeAsync(async () =>
{
try
@@ -366,4 +364,4 @@ namespace Ink_Canvas
isFloatingBarChangingHideMode = false;
}
}
}
}
+148 -4
View File
@@ -1,6 +1,7 @@
using iNKORE.UI.WPF.Modern;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using Application = System.Windows.Application;
@@ -9,16 +10,34 @@ namespace Ink_Canvas
{
public partial class MainWindow : Window
{
private Color FloatBarForegroundColor = Color.FromRgb(102, 102, 102);
private Color FloatBarForegroundColor;
private void SetTheme(string theme)
{
// 清理现有的主题资源
var resourcesToRemove = new List<ResourceDictionary>();
foreach (var dict in Application.Current.Resources.MergedDictionaries)
{
if (dict.Source != null &&
(dict.Source.ToString().Contains("Light.xaml") ||
dict.Source.ToString().Contains("Dark.xaml")))
{
resourcesToRemove.Add(dict);
}
}
foreach (var dict in resourcesToRemove)
{
Application.Current.Resources.MergedDictionaries.Remove(dict);
}
if (theme == "Light")
{
var rd1 = new ResourceDictionary
{ Source = new Uri("Resources/Styles/Light.xaml", UriKind.Relative) };
Application.Current.Resources.MergedDictionaries.Add(rd1);
// 在主题资源之后添加其他资源
var rd2 = new ResourceDictionary
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
Application.Current.Resources.MergedDictionaries.Add(rd2);
@@ -33,13 +52,20 @@ namespace Ink_Canvas
ThemeManager.SetRequestedTheme(window, ElementTheme.Light);
FloatBarForegroundColor = (Color)Application.Current.FindResource("FloatBarForegroundColor");
InitializeFloatBarForegroundColor();
// 刷新快速面板图标
RefreshQuickPanelIcons();
// 强制刷新UI
window.InvalidateVisual();
}
else if (theme == "Dark")
{
var rd1 = new ResourceDictionary { Source = new Uri("Resources/Styles/Dark.xaml", UriKind.Relative) };
Application.Current.Resources.MergedDictionaries.Add(rd1);
// 在主题资源之后添加其他资源
var rd2 = new ResourceDictionary
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
Application.Current.Resources.MergedDictionaries.Add(rd2);
@@ -54,7 +80,125 @@ namespace Ink_Canvas
ThemeManager.SetRequestedTheme(window, ElementTheme.Dark);
InitializeFloatBarForegroundColor();
// 刷新快速面板图标
RefreshQuickPanelIcons();
// 强制刷新UI
window.InvalidateVisual();
}
}
/// <summary>
/// 初始化FloatBarForegroundColor,从当前主题资源中加载颜色
/// </summary>
private void InitializeFloatBarForegroundColor()
{
try
{
FloatBarForegroundColor = (Color)Application.Current.FindResource("FloatBarForegroundColor");
// 强制刷新浮动工具栏按钮颜色
RefreshFloatingBarButtonColors();
}
catch (Exception)
{
// 如果无法从资源中加载,使用默认颜色
FloatBarForegroundColor = Color.FromRgb(0, 0, 0);
}
}
/// <summary>
/// 刷新快速面板图标
/// </summary>
private void RefreshQuickPanelIcons()
{
try
{
if (LeftUnFoldButtonQuickPanel != null)
{
LeftUnFoldButtonQuickPanel.InvalidateVisual();
}
if (RightUnFoldButtonQuickPanel != null)
{
RightUnFoldButtonQuickPanel.InvalidateVisual();
}
if (LeftSidePanel != null)
{
LeftSidePanel.InvalidateVisual();
}
if (RightSidePanel != null)
{
RightSidePanel.InvalidateVisual();
}
}
catch (Exception)
{
}
}
/// <summary>
/// 刷新浮动工具栏按钮颜色
/// </summary>
private void RefreshFloatingBarButtonColors()
{
try
{
// 选中状态的颜色(蓝底)
var selectedColor = Color.FromRgb(30, 58, 138);
// 根据当前模式设置按钮颜色
switch (_currentToolMode)
{
case "cursor":
CursorIconGeometry.Brush = new SolidColorBrush(selectedColor);
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
break;
case "pen":
case "color":
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
PenIconGeometry.Brush = new SolidColorBrush(selectedColor);
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
break;
case "eraser":
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
CircleEraserIconGeometry.Brush = new SolidColorBrush(selectedColor);
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
break;
case "eraserByStrokes":
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
StrokeEraserIconGeometry.Brush = new SolidColorBrush(selectedColor);
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
break;
case "select":
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
LassoSelectIconGeometry.Brush = new SolidColorBrush(selectedColor);
break;
default:
// 默认情况,所有按钮都使用主题颜色
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
break;
}
}
catch (Exception)
{
}
}
+61 -8
View File
@@ -15,10 +15,10 @@ namespace Ink_Canvas
private StrokeCollection[] strokeCollections = new StrokeCollection[101];
private bool[] whiteboadLastModeIsRedo = new bool[101];
private StrokeCollection lastTouchDownStrokeCollection = new StrokeCollection();
private int CurrentWhiteboardIndex = 1;
private int WhiteboardTotalCount = 1;
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][]; //最多99页,0用来存储非白板时的墨迹以便还原
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][];
private bool[] savedMultiTouchModeStates = new bool[101];
// 保存每页白板图片信息
private void SaveStrokes(bool isBackupMain = false)
@@ -97,6 +97,8 @@ namespace Ink_Canvas
{
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
TimeMachineHistories[0] = timeMachineHistory;
// 保存多指书写模式状态
savedMultiTouchModeStates[0] = isInMultiTouchMode;
timeMachine.ClearStrokeHistory();
@@ -105,6 +107,8 @@ namespace Ink_Canvas
{
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
// 保存多指书写模式状态
savedMultiTouchModeStates[CurrentWhiteboardIndex] = isInMultiTouchMode;
timeMachine.ClearStrokeHistory();
@@ -161,12 +165,16 @@ namespace Ink_Canvas
{
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[0]);
foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item);
// 恢复多指书写模式状态
RestoreMultiTouchModeState(0);
}
else
{
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]);
// 通过时间机器历史恢复所有内容(墨迹和图片)
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
// 恢复多指书写模式状态
RestoreMultiTouchModeState(CurrentWhiteboardIndex);
}
@@ -177,6 +185,45 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 恢复多指书写模式状态
/// </summary>
private void RestoreMultiTouchModeState(int pageIndex)
{
try
{
// 检查是否保存了多指书写模式状态
if (savedMultiTouchModeStates[pageIndex])
{
// 恢复多指书写模式
EnterMultiTouchModeIfNeeded();
// 更新UI状态
if (ToggleSwitchEnableMultiTouchMode != null)
{
ToggleSwitchEnableMultiTouchMode.IsOn = true;
}
LogHelper.WriteLogToFile($"恢复多指书写模式状态 - 页面索引: {pageIndex}", LogHelper.LogType.Info);
}
else
{
// 确保多指书写模式关闭
ExitMultiTouchModeIfNeeded();
// 更新UI状态
if (ToggleSwitchEnableMultiTouchMode != null)
{
ToggleSwitchEnableMultiTouchMode.IsOn = false;
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"恢复多指书写模式状态失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private async void BtnWhiteBoardPageIndex_Click(object sender, EventArgs e)
{
if (sender == BtnLeftPageListWB)
@@ -191,9 +238,12 @@ namespace Ink_Canvas
RefreshBlackBoardSidePageListView();
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderLeftPageListView);
await Task.Delay(1);
ScrollViewToVerticalTop(
(ListViewItem)BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex(
CurrentWhiteboardIndex - 1), BlackBoardLeftSidePageListScrollViewer);
var leftContainer = BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex(
CurrentWhiteboardIndex - 1) as ListViewItem;
if (leftContainer != null)
{
ScrollViewToVerticalTop(leftContainer, BlackBoardLeftSidePageListScrollViewer);
}
}
}
else if (sender == BtnRightPageListWB)
@@ -208,9 +258,12 @@ namespace Ink_Canvas
RefreshBlackBoardSidePageListView();
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderRightPageListView);
await Task.Delay(1);
ScrollViewToVerticalTop(
(ListViewItem)BlackBoardRightSidePageListView.ItemContainerGenerator.ContainerFromIndex(
CurrentWhiteboardIndex - 1), BlackBoardRightSidePageListScrollViewer);
var rightContainer = BlackBoardRightSidePageListView.ItemContainerGenerator.ContainerFromIndex(
CurrentWhiteboardIndex - 1) as ListViewItem;
if (rightContainer != null)
{
ScrollViewToVerticalTop(rightContainer, BlackBoardRightSidePageListScrollViewer);
}
}
}
+1 -2
View File
@@ -716,7 +716,7 @@ namespace Ink_Canvas
//}
//else {
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
DisableEraserOverlay();
forceEraser = true;
forcePointEraser = false;
@@ -726,7 +726,6 @@ namespace Ink_Canvas
SetCurrentToolMode(InkCanvasEditingMode.EraseByStroke);
drawingShapeMode = 0;
// 修复:切换到线擦时,确保重置笔的状态
penType = 0;
drawingAttributes.IsHighlighter = false;
drawingAttributes.StylusTip = StylusTip.Ellipse;
@@ -192,6 +192,8 @@ namespace Ink_Canvas
elementForEvents.MouseWheel += Element_MouseWheel;
// 触摸事件
elementForEvents.TouchDown += Element_TouchDown;
elementForEvents.TouchUp += Element_TouchUp;
elementForEvents.IsManipulationEnabled = true;
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
@@ -11,7 +11,9 @@ using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Path = System.IO.Path;
namespace Ink_Canvas
{
@@ -150,6 +152,19 @@ namespace Ink_Canvas
}
}
// 触摸释放事件
private void Element_TouchUp(object sender, TouchEventArgs e)
{
if (sender is FrameworkElement element)
{
isDragging = false;
element.ReleaseTouchCapture(e.TouchDevice);
element.Cursor = Cursors.Hand;
e.Handled = true;
}
}
// 鼠标移动事件
private void Element_MouseMove(object sender, MouseEventArgs e)
{
@@ -166,6 +181,12 @@ namespace Ink_Canvas
UpdateImageSelectionToolbarPosition(element);
}
// 如果是图片元素,更新选择点位置
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
{
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
}
dragStartPoint = currentPoint;
e.Handled = true;
}
@@ -187,6 +208,40 @@ namespace Ink_Canvas
UpdateImageSelectionToolbarPosition(element);
}
// 如果是图片元素,更新选择点位置
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
{
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
}
e.Handled = true;
}
}
// 触摸按下事件
private void Element_TouchDown(object sender, TouchEventArgs e)
{
if (sender is FrameworkElement element)
{
// 取消之前选中的元素
if (currentSelectedElement != null && currentSelectedElement != element)
{
// 保存当前编辑模式
var previousEditingMode = inkCanvas.EditingMode;
UnselectElement(currentSelectedElement);
// 恢复编辑模式
inkCanvas.EditingMode = previousEditingMode;
}
// 选中当前元素
SelectElement(element);
// 开始拖动
isDragging = true;
dragStartPoint = e.GetTouchPoint(inkCanvas).Position;
element.CaptureTouch(e.TouchDevice);
element.Cursor = Cursors.SizeAll;
e.Handled = true;
}
}
@@ -214,6 +269,12 @@ namespace Ink_Canvas
UpdateImageSelectionToolbarPosition(element);
}
// 如果是图片元素,更新选择点位置
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
{
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
}
e.Handled = true;
}
}
@@ -290,6 +351,9 @@ namespace Ink_Canvas
BorderImageSelectionControl.Visibility = Visibility.Visible;
}
// 显示图片缩放选择点
ShowImageResizeHandles(element);
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
// 不需要直接设置BorderStrokeSelectionControl.Visibility
}
@@ -301,6 +365,9 @@ namespace Ink_Canvas
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
}
// 隐藏图片缩放选择点
HideImageResizeHandles();
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
// 不需要直接设置BorderStrokeSelectionControl.Visibility
}
@@ -332,6 +399,9 @@ namespace Ink_Canvas
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
}
// 隐藏图片缩放选择点
HideImageResizeHandles();
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
// 不需要直接设置BorderStrokeSelectionControl.Visibility
@@ -1334,6 +1404,323 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 克隆墨迹集合
/// </summary>
/// <param name="strokes">要克隆的墨迹集合</param>
/// <returns>克隆后的墨迹集合</returns>
private StrokeCollection CloneStrokes(StrokeCollection strokes)
{
if (strokes == null || strokes.Count == 0) return new StrokeCollection();
try
{
// 创建墨迹集合的副本
var clonedStrokes = strokes.Clone();
// 为每个墨迹添加位置偏移以避免重叠
foreach (var stroke in clonedStrokes)
{
var offsetPoints = new StylusPointCollection();
foreach (var point in stroke.StylusPoints)
{
offsetPoints.Add(new StylusPoint(point.X + 20, point.Y + 20, point.PressureFactor));
}
stroke.StylusPoints = offsetPoints;
}
// 添加到画布
inkCanvas.Strokes.Add(clonedStrokes);
// 提交到时间机器以支持撤销
timeMachine.CommitStrokeUserInputHistory(clonedStrokes);
LogHelper.WriteLogToFile($"墨迹克隆完成: {clonedStrokes.Count} 个墨迹");
return clonedStrokes;
}
catch (Exception ex)
{
// 记录错误但不中断程序
LogHelper.WriteLogToFile($"克隆墨迹时发生错误: {ex.Message}", LogHelper.LogType.Error);
return new StrokeCollection();
}
}
/// <summary>
/// 克隆墨迹集合到新页面
/// </summary>
/// <param name="strokes">要克隆的墨迹集合</param>
private void CloneStrokesToNewBoard(StrokeCollection strokes)
{
if (strokes == null || strokes.Count == 0) return;
try
{
// 创建墨迹集合的副本
var clonedStrokes = strokes.Clone();
// 为每个墨迹添加位置偏移以避免重叠
foreach (var stroke in clonedStrokes)
{
var offsetPoints = new StylusPointCollection();
foreach (var point in stroke.StylusPoints)
{
offsetPoints.Add(new StylusPoint(point.X + 20, point.Y + 20, point.PressureFactor));
}
stroke.StylusPoints = offsetPoints;
}
// 创建新页面
BtnWhiteBoardAdd_Click(null, null);
// 添加到新页面的画布
inkCanvas.Strokes.Add(clonedStrokes);
// 提交到时间机器以支持撤销
timeMachine.CommitStrokeUserInputHistory(clonedStrokes);
LogHelper.WriteLogToFile($"墨迹克隆到新页面完成: {clonedStrokes.Count} 个墨迹");
}
catch (Exception ex)
{
// 记录错误但不中断程序
LogHelper.WriteLogToFile($"克隆墨迹到新页面时发生错误: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region Image Resize Handles
// 图片缩放选择点相关变量
private bool isResizingImage = false;
private Point imageResizeStartPoint;
private string activeResizeHandle = "";
// 显示图片缩放选择点
private void ShowImageResizeHandles(FrameworkElement element)
{
try
{
if (ImageResizeHandlesCanvas == null || element == null) return;
// 获取元素的实际边界
Rect elementBounds = GetElementActualBounds(element);
// 设置选择点位置
UpdateImageResizeHandlesPosition(elementBounds);
// 显示选择点
ImageResizeHandlesCanvas.Visibility = Visibility.Visible;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"显示图片缩放选择点失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 隐藏图片缩放选择点
private void HideImageResizeHandles()
{
try
{
if (ImageResizeHandlesCanvas != null)
{
ImageResizeHandlesCanvas.Visibility = Visibility.Collapsed;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"隐藏图片缩放选择点失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 更新图片缩放选择点位置
private void UpdateImageResizeHandlesPosition(Rect elementBounds)
{
try
{
if (ImageResizeHandlesCanvas == null) return;
ImageResizeHandlesCanvas.Margin = new Thickness(elementBounds.Left, elementBounds.Top, 0, 0);
// 四个角控制点
System.Windows.Controls.Canvas.SetLeft(ImageTopLeftHandle, -4);
System.Windows.Controls.Canvas.SetTop(ImageTopLeftHandle, -4);
System.Windows.Controls.Canvas.SetLeft(ImageTopRightHandle, elementBounds.Width - 4);
System.Windows.Controls.Canvas.SetTop(ImageTopRightHandle, -4);
System.Windows.Controls.Canvas.SetLeft(ImageBottomLeftHandle, -4);
System.Windows.Controls.Canvas.SetTop(ImageBottomLeftHandle, elementBounds.Height - 4);
System.Windows.Controls.Canvas.SetLeft(ImageBottomRightHandle, elementBounds.Width - 4);
System.Windows.Controls.Canvas.SetTop(ImageBottomRightHandle, elementBounds.Height - 4);
// 四个边控制点
System.Windows.Controls.Canvas.SetLeft(ImageTopHandle, elementBounds.Width / 2 - 4);
System.Windows.Controls.Canvas.SetTop(ImageTopHandle, -4);
System.Windows.Controls.Canvas.SetLeft(ImageBottomHandle, elementBounds.Width / 2 - 4);
System.Windows.Controls.Canvas.SetTop(ImageBottomHandle, elementBounds.Height - 4);
System.Windows.Controls.Canvas.SetLeft(ImageLeftHandle, -4);
System.Windows.Controls.Canvas.SetTop(ImageLeftHandle, elementBounds.Height / 2 - 4);
System.Windows.Controls.Canvas.SetLeft(ImageRightHandle, elementBounds.Width - 4);
System.Windows.Controls.Canvas.SetTop(ImageRightHandle, elementBounds.Height / 2 - 4);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新图片缩放选择点位置失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 图片缩放选择点鼠标按下事件
private void ImageResizeHandle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
try
{
if (currentSelectedElement is Image image && sender is Ellipse ellipse)
{
isResizingImage = true;
imageResizeStartPoint = e.GetPosition(inkCanvas);
// 确定是哪个控制点
activeResizeHandle = ellipse.Name;
// 捕获鼠标
ellipse.CaptureMouse();
e.Handled = true;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"图片缩放选择点鼠标按下事件失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 图片缩放选择点鼠标释放事件
private void ImageResizeHandle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
try
{
if (isResizingImage && sender is Ellipse ellipse)
{
isResizingImage = false;
ellipse.ReleaseMouseCapture();
activeResizeHandle = "";
e.Handled = true;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"图片缩放选择点鼠标释放事件失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 图片缩放选择点鼠标移动事件
private void ImageResizeHandle_MouseMove(object sender, MouseEventArgs e)
{
try
{
if (isResizingImage && currentSelectedElement is Image image && sender is Ellipse ellipse)
{
var currentPoint = e.GetPosition(inkCanvas);
ResizeImageByHandle(image, imageResizeStartPoint, currentPoint, activeResizeHandle);
imageResizeStartPoint = currentPoint;
e.Handled = true;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"图片缩放选择点鼠标移动事件失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 根据控制点缩放图片
private void ResizeImageByHandle(Image image, Point startPoint, Point currentPoint, string handleName)
{
try
{
if (image.RenderTransform is TransformGroup transformGroup)
{
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
if (scaleTransform == null || translateTransform == null) return;
// 获取图片的当前边界
Rect currentBounds = GetElementActualBounds(image);
double deltaX = currentPoint.X - startPoint.X;
double deltaY = currentPoint.Y - startPoint.Y;
// 计算缩放比例
double scaleX = 1.0;
double scaleY = 1.0;
double translateX = 0;
double translateY = 0;
switch (handleName)
{
case "ImageTopLeftHandle":
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
translateX = deltaX;
translateY = deltaY;
break;
case "ImageTopRightHandle":
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
translateY = deltaY;
break;
case "ImageBottomLeftHandle":
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
translateX = deltaX;
break;
case "ImageBottomRightHandle":
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
break;
case "ImageTopHandle":
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
translateY = deltaY;
break;
case "ImageBottomHandle":
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
break;
case "ImageLeftHandle":
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
translateX = deltaX;
break;
case "ImageRightHandle":
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
break;
}
// 限制缩放范围
scaleX = Math.Max(0.1, Math.Min(scaleX, 5.0));
scaleY = Math.Max(0.1, Math.Min(scaleY, 5.0));
// 应用缩放
scaleTransform.ScaleX *= scaleX;
scaleTransform.ScaleY *= scaleY;
// 应用平移
translateTransform.X += translateX;
translateTransform.Y += translateY;
// 更新选择点位置
UpdateImageResizeHandlesPosition(GetElementActualBounds(image));
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"根据控制点缩放图片失败: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
}
}
File diff suppressed because it is too large Load Diff
+43 -33
View File
@@ -1,36 +1,46 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingGroup x:Key="EraserDrawingGroup" ClipGeometry="M0,0 V56 H38 V0 H0 Z">
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M38,56z M0,0z M0,4C0,1.79086,1.79086,0,4,0L34,0C36.2091,0,38,1.79086,38,4L38,52C38,54.2091,36.2091,56,34,56L4,56C1.79086,56,0,54.2091,0,52L0,4z" />
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M38,56z M0,0z M34,1L4,1C2.34315,1,1,2.34315,1,4L1,52C1,53.6569,2.34315,55,4,55L34,55C35.6569,55,37,53.6569,37,52L37,4C37,2.34315,35.6569,1,34,1z M4,0C1.79086,0,0,1.79086,0,4L0,52C0,54.2091,1.79086,56,4,56L34,56C36.2091,56,38,54.2091,38,52L38,4C38,1.79086,36.2091,0,34,0L4,0z" />
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M12,19.5C12,18.1193,13.1193,17,14.5,17L14.5,17C15.8807,17,17,18.1193,17,19.5L17,36.5C17,37.8807,15.8807,39,14.5,39L14.5,39C13.1193,39,12,37.8807,12,36.5L12,19.5z" />
<GeometryDrawing Geometry="F0 M38,56z M0,0z M11.5,19.5C11.5,17.8431 12.8431,16.5 14.5,16.5 16.1569,16.5 17.5,17.8431 17.5,19.5L17.5,36.5C17.5,38.1569 16.1569,39.5 14.5,39.5 12.8431,39.5 11.5,38.1569 11.5,36.5L11.5,19.5z M14.5,17.5C13.3954,17.5,12.5,18.3954,12.5,19.5L12.5,36.5C12.5,37.6046 13.3954,38.5 14.5,38.5 15.6046,38.5 16.5,37.6046 16.5,36.5L16.5,19.5C16.5,18.3954,15.6046,17.5,14.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
<GeometryDrawing Geometry="F0 M38,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- 矩形橡皮擦图像资源 -->
<DrawingImage x:Key="RectangleEraserImageSource">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V56 H38 V0 H0 Z">
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M38,56z M0,0z M0,4C0,1.79086,1.79086,0,4,0L34,0C36.2091,0,38,1.79086,38,4L38,52C38,54.2091,36.2091,56,34,56L4,56C1.79086,56,0,54.2091,0,52L0,4z" />
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M38,56z M0,0z M34,1L4,1C2.34315,1,1,2.34315,1,4L1,52C1,53.6569,2.34315,55,4,55L34,55C35.6569,55,37,53.6569,37,52L37,4C37,2.34315,35.6569,1,34,1z M4,0C1.79086,0,0,1.79086,0,4L0,52C0,54.2091,1.79086,56,4,56L34,56C36.2091,56,38,54.2091,38,52L38,4C38,1.79086,36.2091,0,34,0L4,0z" />
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M12,19.5C12,18.1193,13.1193,17,14.5,17L14.5,17C15.8807,17,17,18.1193,17,19.5L17,36.5C17,37.8807,15.8807,39,14.5,39L14.5,39C13.1193,39,12,37.8807,12,36.5L12,19.5z" />
<GeometryDrawing Geometry="F0 M38,56z M0,0z M11.5,19.5C11.5,17.8431 12.8431,16.5 14.5,16.5 16.1569,16.5 17.5,17.8431 17.5,19.5L17.5,36.5C17.5,38.1569 16.1569,39.5 14.5,39.5 12.8431,39.5 11.5,38.1569 11.5,36.5L11.5,19.5z M14.5,17.5C13.3954,17.5,12.5,18.3954,12.5,19.5L12.5,36.5C12.5,37.6046 13.3954,38.5 14.5,38.5 15.6046,38.5 16.5,37.6046 16.5,36.5L16.5,19.5C16.5,18.3954,15.6046,17.5,14.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
<GeometryDrawing Geometry="F0 M38,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingGroup x:Key="EraserCircleDrawingGroup" ClipGeometry="M0,0 V56 H56 V0 H0 Z">
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M56,56z M0,0z M0,28C0,12.536,12.536,0,28,0L28,0C43.464,0,56,12.536,56,28L56,28C56,43.464,43.464,56,28,56L28,56C12.536,56,0,43.464,0,28L0,28z" />
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M56,56z M0,0z M1,28C1,42.9117 13.0883,55 28,55 42.9117,55 55,42.9117 55,28 55,13.0883 42.9117,1 28,1 13.0883,1 1,13.0883 1,28z M28,0C12.536,0 0,12.536 0,28 0,43.464 12.536,56 28,56 43.464,56 56,43.464 56,28 56,12.536 43.464,0 28,0z" />
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
<GeometryDrawing Geometry="F0 M56,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M30,19.5C30,18.1193,31.1193,17,32.5,17L32.5,17C33.8807,17,35,18.1193,35,19.5L35,36.5C35,37.8807,33.8807,39,32.5,39L32.5,39C31.1193,39,30,37.8807,30,36.5L30,19.5z" />
<GeometryDrawing Geometry="F0 M56,56z M0,0z M29.5,19.5C29.5,17.8431 30.8431,16.5 32.5,16.5 34.1569,16.5 35.5,17.8431 35.5,19.5L35.5,36.5C35.5,38.1569 34.1569,39.5 32.5,39.5 30.8431,39.5 29.5,38.1569 29.5,36.5L29.5,19.5z M32.5,17.5C31.3954,17.5,30.5,18.3954,30.5,19.5L30.5,36.5C30.5,37.6046 31.3954,38.5 32.5,38.5 33.6046,38.5 34.5,37.6046 34.5,36.5L34.5,19.5C34.5,18.3954,33.6046,17.5,32.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
<!-- 圆形橡皮擦图像资源 -->
<DrawingImage x:Key="EllipseEraserImageSource">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V56 H56 V0 H0 Z">
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M56,56z M0,0z M0,28C0,12.536,12.536,0,28,0L28,0C43.464,0,56,12.536,56,28L56,28C56,43.464,43.464,56,28,56L28,56C12.536,56,0,43.464,0,28L0,28z" />
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M56,56z M0,0z M1,28C1,42.9117 13.0883,55 28,55 42.9117,55 55,42.9117 55,28 55,13.0883 42.9117,1 28,1 13.0883,1 1,13.0883 1,28z M28,0C12.536,0 0,12.536 0,28 0,43.464 12.536,56 28,56 43.464,56 56,43.464 56,28 56,12.536 43.464,0 28,0z" />
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
<GeometryDrawing Geometry="F0 M56,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M30,19.5C30,18.1193,31.1193,17,32.5,17L32.5,17C33.8807,17,35,18.1193,35,19.5L35,36.5C35,37.8807,33.8807,39,32.5,39L32.5,39C31.1193,39,30,37.8807,30,36.5L30,19.5z" />
<GeometryDrawing Geometry="F0 M56,56z M0,0z M29.5,19.5C29.5,17.8431 30.8431,16.5 32.5,16.5 34.1569,16.5 35.5,17.8431 35.5,19.5L35.5,36.5C35.5,38.1569 34.1569,39.5 32.5,39.5 30.8431,39.5 29.5,38.1569 29.5,36.5L29.5,19.5z M32.5,17.5C31.3954,17.5,30.5,18.3954,30.5,19.5L30.5,36.5C30.5,37.6046 31.3954,38.5 32.5,38.5 33.6046,38.5 34.5,37.6046 34.5,36.5L34.5,19.5C34.5,18.3954,33.6046,17.5,32.5,17.5z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</ResourceDictionary>
+307 -69
View File
@@ -84,13 +84,28 @@ namespace Ink_Canvas
EnableTwoFingerGestureBtn.Source =
new BitmapImage(new Uri("/Resources/new-icons/gesture.png", UriKind.Relative));
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
// 根据主题设置颜色
if (Settings.Appearance.Theme == 1) // 深色主题
{
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
}
else // 浅色主题或跟随系统
{
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
}
BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon);
BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z");
// 强制禁用所有双指手势功能
ForceDisableTwoFingerGestures();
}
else
{
@@ -119,11 +134,23 @@ namespace Ink_Canvas
EnableTwoFingerGestureBtn.Source =
new BitmapImage(new Uri("/Resources/new-icons/gesture.png", UriKind.Relative));
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
// 根据主题设置颜色
if (Settings.Appearance.Theme == 1) // 深色主题
{
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
}
else // 浅色主题或跟随系统
{
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
}
BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon);
BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z");
}
@@ -401,31 +428,50 @@ namespace Ink_Canvas
{
if (mode != "clear")
{
CursorIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(27, 27, 27));
CursorIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedCursorIcon);
PenIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(27, 27, 27));
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedPenIcon);
StrokeEraserIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(27, 27, 27));
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
CursorIconGeometry.Geometry = Geometry.Parse(GetCorrectIcon("cursor", false));
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
PenIconGeometry.Geometry = Geometry.Parse(GetCorrectIcon("pen", false));
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
StrokeEraserIconGeometry.Geometry =
Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserStrokeIcon);
CircleEraserIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(27, 27, 27));
Geometry.Parse(GetCorrectIcon("eraserStroke", false));
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
CircleEraserIconGeometry.Geometry =
Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserCircleIcon);
LassoSelectIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(27, 27, 27));
LassoSelectIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedLassoSelectIcon);
Geometry.Parse(GetCorrectIcon("eraserCircle", false));
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
LassoSelectIconGeometry.Geometry = Geometry.Parse(GetCorrectIcon("lassoSelect", false));
BoardPen.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardSelect.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardEraser.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardSelectGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardPenGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardEraserGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardPenLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardSelectLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardEraserLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardSelect.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
BoardEraser.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
BoardPen.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
// 根据主题设置颜色
if (Settings.Appearance.Theme == 1) // 深色主题
{
BoardPen.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
BoardSelect.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
BoardEraser.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
BoardSelectGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardPenGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardEraserGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardPenLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardSelectLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardEraserLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardSelect.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
BoardEraser.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
BoardPen.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
}
else // 浅色主题或跟随系统
{
BoardPen.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardSelect.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardEraser.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardSelectGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardPenGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardEraserGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardPenLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardSelectLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardEraserLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardSelect.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
BoardEraser.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
BoardPen.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
}
HideFloatingBarHighlight();
}
@@ -436,7 +482,7 @@ namespace Ink_Canvas
case "color":
{
PenIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(30, 58, 138));
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SolidPenIcon);
PenIconGeometry.Geometry = Geometry.Parse(GetCorrectIcon("pen", true));
BoardPen.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardPen.BorderBrush = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardPenGeometry.Brush = new SolidColorBrush(Colors.GhostWhite);
@@ -449,7 +495,7 @@ namespace Ink_Canvas
{
CircleEraserIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(30, 58, 138));
CircleEraserIconGeometry.Geometry =
Geometry.Parse(XamlGraphicsIconGeometries.SolidEraserCircleIcon);
Geometry.Parse(GetCorrectIcon("eraserCircle", true));
BoardEraser.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardEraser.BorderBrush = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardEraserGeometry.Brush = new SolidColorBrush(Colors.GhostWhite);
@@ -462,7 +508,7 @@ namespace Ink_Canvas
{
StrokeEraserIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(30, 58, 138));
StrokeEraserIconGeometry.Geometry =
Geometry.Parse(XamlGraphicsIconGeometries.SolidEraserStrokeIcon);
Geometry.Parse(GetCorrectIcon("eraserStroke", true));
BoardEraser.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardEraser.BorderBrush = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardEraserGeometry.Brush = new SolidColorBrush(Colors.GhostWhite);
@@ -475,7 +521,7 @@ namespace Ink_Canvas
{
LassoSelectIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(30, 58, 138));
LassoSelectIconGeometry.Geometry =
Geometry.Parse(XamlGraphicsIconGeometries.SolidLassoSelectIcon);
Geometry.Parse(GetCorrectIcon("lassoSelect", true));
BoardSelect.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardSelect.BorderBrush = new SolidColorBrush(Color.FromRgb(37, 99, 235));
BoardSelectGeometry.Brush = new SolidColorBrush(Colors.GhostWhite);
@@ -488,11 +534,22 @@ namespace Ink_Canvas
{
CursorIconGeometry.Brush = new SolidColorBrush(Color.FromRgb(30, 58, 138));
CursorIconGeometry.Geometry =
Geometry.Parse(XamlGraphicsIconGeometries.LinedCursorIcon);
BoardPen.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardPen.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
BoardPenGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardPenLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
Geometry.Parse(GetCorrectIcon("cursor", true));
// 根据主题设置颜色
if (Settings.Appearance.Theme == 1) // 深色主题
{
BoardPen.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
BoardPen.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
BoardPenGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
BoardPenLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
}
else // 浅色主题或跟随系统
{
BoardPen.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
BoardPen.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
BoardPenGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
BoardPenLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
}
SetFloatingBarHighlightPosition("cursor");
break;
@@ -695,7 +752,6 @@ namespace Ink_Canvas
if (dopsc[1] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
if (dopsc[2] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
if (dopsc[3] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
LogHelper.WriteLogToFile($"显示PPT翻页按钮 - 放映状态: {PPTManager?.IsInSlideShow}, 页数: {PPTManager?.SlidesCount}", LogHelper.LogType.Trace);
}
else
{
@@ -746,6 +802,36 @@ namespace Ink_Canvas
BtnSwitch_Click(BtnSwitch, null);
if (currentMode == 0)
{
// 根据当前编辑模式正确设置工具模式和高光位置
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
{
UpdateCurrentToolMode("cursor");
SetFloatingBarHighlightPosition("cursor");
}
else if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
{
UpdateCurrentToolMode("pen");
SetFloatingBarHighlightPosition("pen");
}
else if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
{
UpdateCurrentToolMode("eraser");
SetFloatingBarHighlightPosition("eraser");
}
else if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke)
{
UpdateCurrentToolMode("eraserByStrokes");
SetFloatingBarHighlightPosition("eraserByStrokes");
}
else if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
{
UpdateCurrentToolMode("select");
SetFloatingBarHighlightPosition("select");
}
}
if (currentMode == 0 && inkCanvas.Strokes.Count == 0 && BtnPPTSlideShowEnd.Visibility != Visibility.Visible)
CursorIcon_Click(null, null);
@@ -823,6 +909,7 @@ namespace Ink_Canvas
/// <param name="e">RoutedEventArgs</param>
private void EraserPanelSymbolIconDelete_MouseUp(object sender, RoutedEventArgs e)
{
PenIcon_Click(null, null);
SymbolIconDelete_MouseUp(null, null);
}
@@ -1269,6 +1356,15 @@ namespace Ink_Canvas
FloatingbarUIForInkReplay.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.Visibility = Visibility.Visible;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
ResetTouchStates();
});
}).Start();
}
@@ -1291,6 +1387,15 @@ namespace Ink_Canvas
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
AnimationsHelper.HideWithFadeOut(BorderInkReplayToolBox);
isStopInkReplay = true;
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
ResetTouchStates();
}
}
@@ -1761,17 +1866,11 @@ namespace Ink_Canvas
if (sender == Cursor_Icon && lastBorderMouseDownObject != Cursor_Icon) return;
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
// 使用集中化的工具模式切换方法,确保快捷键状态正确更新
// 鼠标模式下应该禁用快捷键以放行键盘操作
DisableEraserOverlay();
SetCurrentToolMode(InkCanvasEditingMode.None);
// 更新模式缓存,确保后续的模式检测正确
UpdateCurrentToolMode("cursor");
// 修复:在浮动栏收起状态下,仍然需要设置按钮高亮状态
// 这样在浮动栏展开时能正确显示高光
SetFloatingBarHighlightPosition("cursor");
// 切换前自动截图保存墨迹
@@ -1831,7 +1930,6 @@ namespace Ink_Canvas
{
SaveStrokes();
RestoreStrokes(true);
LogHelper.WriteLogToFile($"退出白板模式,恢复备份墨迹。当前模式:{(BtnPPTSlideShowEnd.Visibility == Visibility.Visible ? "PPT放映" : "")}", LogHelper.LogType.Trace);
}
if (BtnSwitchTheme.Content.ToString() == "浅色")
@@ -1883,10 +1981,8 @@ namespace Ink_Canvas
}
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
DisableEraserOverlay();
// 修复:从橡皮擦切换到批注模式时,退出多指书写模式
// 这解决了从橡皮擦切换为批注时被锁定为多指书写的问题
ExitMultiTouchModeIfNeeded();
SetFloatingBarHighlightPosition("pen");
@@ -1895,14 +1991,20 @@ namespace Ink_Canvas
bool wasInInkMode = inkCanvas.EditingMode == InkCanvasEditingMode.Ink;
bool wasHighlighter = drawingAttributes.IsHighlighter;
// 禁止几何绘制模式下切换到Ink
if (drawingShapeMode != 0)
if (drawingShapeMode != 0 && !isLongPressSelected)
{
return;
}
if (Pen_Icon.Background == null || StackPanelCanvasControls.Visibility == Visibility.Collapsed)
{
if (isLongPressSelected)
{
drawingShapeMode = 0;
isLongPressSelected = false;
ResetAllShapeButtonsOpacity();
}
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.Ink);
@@ -1964,8 +2066,6 @@ namespace Ink_Canvas
}
}
// 修复:从线擦切换到批注时,保持之前的笔类型状态
// 如果之前是荧光笔模式,则保持荧光笔状态;否则重置为默认笔模式
forceEraser = false;
forcePointEraser = false;
drawingShapeMode = 0;
@@ -1993,7 +2093,6 @@ namespace Ink_Canvas
{
if (wasInInkMode)
{
// 修复:从线擦切换到批注时,确保正确重置状态
if (forceEraser)
{
// 从橡皮擦模式切换过来,保持之前的笔类型状态
@@ -2071,7 +2170,6 @@ namespace Ink_Canvas
// 更新模式缓存
UpdateCurrentToolMode("pen");
// 修复:从线擦切换到批注时,保持之前的笔类型状态
forceEraser = false;
forcePointEraser = false;
drawingShapeMode = 0;
@@ -2098,7 +2196,6 @@ namespace Ink_Canvas
}
// 修复:从线擦切换到批注时,保持之前的笔类型状态
forceEraser = false;
forcePointEraser = false;
drawingShapeMode = 0;
@@ -2125,8 +2222,13 @@ namespace Ink_Canvas
SaveStrokes();
}
if (!isAlreadyEraser)
{
ResetTouchStates();
}
// 启用新的高级橡皮擦系统
EnableAdvancedEraserSystem();
EnableEraserOverlay();
// 使用新的高级橡皮擦系统
// 使用集中化的工具模式切换方法
@@ -2138,10 +2240,7 @@ namespace Ink_Canvas
ApplyAdvancedEraserShape(); // 使用新的橡皮擦形状应用方法
SetCursorBasedOnEditingMode(inkCanvas);
HideSubPanels("eraser"); // 高亮橡皮按钮
// 显示橡皮擦视觉反馈(用于测试)
// 注意:eraserVisualBorder在MW_Eraser.cs中定义,这里无法直接访问
Trace.WriteLine($"Advanced Eraser: Eraser button clicked, current size: {currentEraserSize}, circle: {isCurrentEraserCircle}");
Trace.WriteLine($"Eraser: Eraser button clicked, current size: {eraserWidth}, circle: {isEraserCircleShape}");
if (isAlreadyEraser)
{
@@ -2170,7 +2269,7 @@ namespace Ink_Canvas
drawingShapeMode = 0;
// 启用新的高级橡皮擦系统
EnableAdvancedEraserSystem();
EnableEraserOverlay();
// 使用新的高级橡皮擦系统
// 使用集中化的工具模式切换方法
@@ -2209,7 +2308,7 @@ namespace Ink_Canvas
if (sender == EraserByStrokes_Icon && lastBorderMouseDownObject != EraserByStrokes_Icon) return;
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
DisableEraserOverlay();
forceEraser = true;
forcePointEraser = false;
@@ -2223,7 +2322,6 @@ namespace Ink_Canvas
drawingShapeMode = 0;
// 修复:切换到线擦时,保存当前的笔类型状态,而不是强制重置
// 这样从线擦切换回批注时,可以恢复之前的荧光笔状态
// penType 和 drawingAttributes 的状态将在 PenIcon_Click 中根据 wasHighlighter 来恢复
@@ -2486,7 +2584,7 @@ namespace Ink_Canvas
private void SelectIcon_MouseUp(object sender, RoutedEventArgs e)
{
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
DisableEraserOverlay();
forceEraser = true;
drawingShapeMode = 0;
@@ -2588,6 +2686,19 @@ namespace Ink_Canvas
public void BtnExit_Click(object sender, RoutedEventArgs e)
{
// 如果当前在设置面板中,需要先恢复无焦点模式状态
if (BorderSettings.Visibility == Visibility.Visible)
{
// 如果用户没有在设置中修改无焦点模式,则恢复之前的状态
if (!userChangedNoFocusModeInSettings && wasNoFocusModeBeforeSettings)
{
Settings.Advanced.IsNoFocusMode = true;
ToggleSwitchNoFocusMode.IsOn = true;
ApplyNoFocusMode();
}
SaveSettingsToFile();
}
App.IsAppExitByUser = true;
// 不设置 CloseIsFromButton = true,让它也经过确认流程
Close();
@@ -2595,6 +2706,18 @@ namespace Ink_Canvas
public void BtnRestart_Click(object sender, RoutedEventArgs e)
{
if (BorderSettings.Visibility == Visibility.Visible)
{
// 如果用户没有在设置中修改无焦点模式,则恢复之前的状态
if (!userChangedNoFocusModeInSettings && wasNoFocusModeBeforeSettings)
{
Settings.Advanced.IsNoFocusMode = true;
ToggleSwitchNoFocusMode.IsOn = true;
ApplyNoFocusMode();
}
SaveSettingsToFile();
}
Process.Start(System.Windows.Forms.Application.ExecutablePath, "-m");
App.IsAppExitByUser = true;
// 不设置 CloseIsFromButton = true,让它也经过确认流程
@@ -2711,10 +2834,40 @@ namespace Ink_Canvas
CancelSingleFingerDragMode();
if (Settings.Canvas.ClearCanvasAndClearTimeMachine) timeMachine.ClearStrokeHistory();
// 清空墨迹后模拟用户重新手动开关多指书写功能
SimulateMultiTouchToggle();
}
private bool lastIsInMultiTouchMode;
/// <summary>
/// 模拟用户重新手动开关多指书写功能
/// </summary>
private void SimulateMultiTouchToggle()
{
try
{
// 检查多指书写模式是否启用
if (ToggleSwitchEnableMultiTouchMode != null && ToggleSwitchEnableMultiTouchMode.IsOn)
{
// 先关闭多指书写模式
ToggleSwitchEnableMultiTouchMode.IsOn = false;
// 使用Dispatcher.BeginInvoke确保UI更新完成后再重新开启
Dispatcher.BeginInvoke(new Action(() =>
{
// 重新开启多指书写模式
ToggleSwitchEnableMultiTouchMode.IsOn = true;
}), DispatcherPriority.Background);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"模拟多指书写开关时发生错误: {ex.Message}", LogHelper.LogType.Error);
}
}
private void CancelSingleFingerDragMode()
{
if (ToggleSwitchDrawShapeBorderAutoHide.IsOn) CollapseBorderDrawShape();
@@ -2723,6 +2876,54 @@ namespace Ink_Canvas
if (isSingleFingerDragMode) BtnFingerDragMode_Click(BtnFingerDragMode, null);
isLongPressSelected = false;
ResetTouchStates();
}
/// <summary>
/// 重置所有触摸相关状态,
/// </summary>
private void ResetTouchStates()
{
try
{
// 清空触摸点计数器
dec.Clear();
// 重置单指拖动模式状态
if (isSingleFingerDragMode)
{
isSingleFingerDragMode = false;
if (BtnFingerDragMode != null)
{
BtnFingerDragMode.Content = "单指\n拖动";
}
}
// 重置手掌擦状态
if (isPalmEraserActive)
{
isPalmEraserActive = false;
}
// 确保触摸事件能正常响应
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
// 释放所有触摸捕获
inkCanvas.ReleaseAllTouchCaptures();
// 恢复UI元素的触摸响应
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"重置触摸状态失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void BtnHideControl_Click(object sender, RoutedEventArgs e)
@@ -2806,6 +3007,12 @@ namespace Ink_Canvas
// 新增:在屏幕模式下恢复基础浮动栏的显示
ViewboxFloatingBar.Visibility = Visibility.Visible;
// 新增:退出白板时自动收纳功能
if (Settings.Automation.IsAutoFoldWhenExitWhiteboard && !isFloatingBarFolded)
{
FoldFloatingBar_MouseUp(null, null);
}
if (BtnSwitchTheme.Content.ToString() == "浅色")
{
BtnSwitch.Content = "黑板";
@@ -3122,6 +3329,8 @@ namespace Ink_Canvas
elementForEvents.MouseWheel += Element_MouseWheel;
// 触摸事件
elementForEvents.TouchDown += Element_TouchDown;
elementForEvents.TouchUp += Element_TouchUp;
elementForEvents.IsManipulationEnabled = true;
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
@@ -3193,6 +3402,8 @@ namespace Ink_Canvas
elementForEvents.MouseWheel += Element_MouseWheel;
// 触摸事件
elementForEvents.TouchDown += Element_TouchDown;
elementForEvents.TouchUp += Element_TouchUp;
elementForEvents.IsManipulationEnabled = true;
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
@@ -3264,6 +3475,8 @@ namespace Ink_Canvas
elementForEvents.MouseWheel += Element_MouseWheel;
// 触摸事件
elementForEvents.TouchDown += Element_TouchDown;
elementForEvents.TouchUp += Element_TouchUp;
elementForEvents.IsManipulationEnabled = true;
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
@@ -3716,7 +3929,32 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 强制禁用所有双指手势功能(当多指书写模式启用时)
/// </summary>
private void ForceDisableTwoFingerGestures()
{
// 强制关闭所有双指手势设置
Settings.Gesture.IsEnableTwoFingerTranslate = false;
Settings.Gesture.IsEnableTwoFingerZoom = false;
Settings.Gesture.IsEnableTwoFingerRotation = false;
// 更新UI开关状态
if (ToggleSwitchEnableTwoFingerTranslate != null)
ToggleSwitchEnableTwoFingerTranslate.IsOn = false;
if (ToggleSwitchEnableTwoFingerZoom != null)
ToggleSwitchEnableTwoFingerZoom.IsOn = false;
if (ToggleSwitchEnableTwoFingerRotation != null)
ToggleSwitchEnableTwoFingerRotation.IsOn = false;
// 更新设置窗口中的开关状态
if (BoardToggleSwitchEnableTwoFingerTranslate != null)
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = false;
if (BoardToggleSwitchEnableTwoFingerZoom != null)
BoardToggleSwitchEnableTwoFingerZoom.IsOn = false;
if (BoardToggleSwitchEnableTwoFingerRotation != null)
BoardToggleSwitchEnableTwoFingerRotation.IsOn = false;
}
#endregion
@@ -3,7 +3,6 @@ using iNKORE.UI.WPF.Modern.Controls;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace Ink_Canvas
{
@@ -312,6 +311,37 @@ namespace Ink_Canvas
{
Settings.Automation.FloatingWindowInterceptor.InterceptRules[ruleName] = enabled;
}
// 获取规则信息以处理父子关系
var rule = _floatingWindowInterceptorManager?.GetInterceptRule(type);
if (rule != null)
{
// 如果是父规则,更新所有子规则的设置
if (rule.ChildTypes.Count > 0)
{
foreach (var childType in rule.ChildTypes)
{
var childRuleName = childType.ToString();
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(childRuleName))
{
Settings.Automation.FloatingWindowInterceptor.InterceptRules[childRuleName] = enabled;
}
}
}
// 如果是子规则,更新父规则的设置
else if (rule.ParentType.HasValue)
{
var parentRule = _floatingWindowInterceptorManager?.GetInterceptRule(rule.ParentType.Value);
if (parentRule != null)
{
var parentRuleName = rule.ParentType.Value.ToString();
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(parentRuleName))
{
Settings.Automation.FloatingWindowInterceptor.InterceptRules[parentRuleName] = parentRule.IsEnabled;
}
}
}
}
// 更新UI显示
UpdateFloatingWindowInterceptorUI();
+34
View File
@@ -5,6 +5,9 @@
public static string LinedCursorIcon =
"F1 M24,24z M0,0z M5.72106,15.9716L3.71327,3.00395C3.6389,2.6693 3.65747,2.41831 3.76902,2.25099 3.88057,2.08366 4.0479,2 4.271,2 4.4941,2 4.71711,2.07437 4.94021,2.2231 6.72502,3.39438 9.28149,5.10481 12.6094,7.3544 15.677,9.45526 18.1125,11.1285 19.9159,12.3742 20.1204,12.5229 20.2505,12.6995 20.3062,12.904 20.362,13.1085 20.3249,13.2944 20.1947,13.4618 20.0832,13.6105 19.8973,13.6849 19.637,13.6849L13.3902,13.6849 17.6291,19.7365C17.722,19.8666 17.75,20.0153 17.7128,20.1827 17.6942,20.3314 17.6198,20.4522 17.4897,20.5452L15.5654,21.8838C15.4353,21.9768 15.2865,22.0139 15.1192,21.9953 14.9704,21.9582 14.8496,21.8745 14.7566,21.7444L10.2389,15.2745 7.58956,19.9038C7.45942,20.1269 7.30144,20.2756 7.11552,20.35 6.92961,20.4058 6.75292,20.3872 6.5856,20.2942 6.43686,20.2013 6.34392,20.0339 6.30673,19.7922L6.00007,17.8959C5.88852,17.0779,5.79543,16.4364,5.72106,15.9716z";
public static string SolidCursorIcon =
"F1 M24,24z M0,0z M5.72106,15.9716L3.71327,3.00395C3.6389,2.6693 3.65747,2.41831 3.76902,2.25099 3.88057,2.08366 4.0479,2 4.271,2 4.4941,2 4.71711,2.07437 4.94021,2.2231 6.72502,3.39438 9.28149,5.10481 12.6094,7.3544 15.677,9.45526 18.1125,11.1285 19.9159,12.3742 20.1204,12.5229 20.2505,12.6995 20.3062,12.904 20.362,13.1085 20.3249,13.2944 20.1947,13.4618 20.0832,13.6105 19.8973,13.6849 19.637,13.6849L13.3902,13.6849 17.6291,19.7365C17.722,19.8666 17.75,20.0153 17.7128,20.1827 17.6942,20.3314 17.6198,20.4522 17.4897,20.5452L15.5654,21.8838C15.4353,21.9768 15.2865,22.0139 15.1192,21.9953 14.9704,21.9582 14.8496,21.8745 14.7566,21.7444L10.2389,15.2745 7.58956,19.9038C7.45942,20.1269 7.30144,20.2756 7.11552,20.35 6.92961,20.4058 6.75292,20.3872 6.5856,20.2942 6.43686,20.2013 6.34392,20.0339 6.30673,19.7922L6.00007,17.8959C5.88852,17.0779,5.79543,16.4364,5.72106,15.9716z";
public static string LinedPenIcon =
"F1 M24,24z M0,0z M16.996,2.34419L21.6823,7.00397C21.8941,7.23343 22,7.49819 22,7.79825 22,8.09831 21.8941,8.35425 21.6823,8.56606L10.8271,19.4212 4.57877,13.1994 15.4339,2.34419C15.6457,2.11473 15.9017,2 16.2018,2 16.5195,2 16.7842,2.11473 16.996,2.34419z M9.63571,20.5862L9.50333,20.6391 2.6725,21.9894C2.47834,22.0247 2.31066,21.9718 2.16946,21.8306 2.02825,21.707 1.97529,21.5481 2.01059,21.354L3.38736,14.5232C3.38736,14.4879,3.40502,14.4349,3.44032,14.3643L9.63571,20.5862z";
@@ -37,5 +40,36 @@
public static string EnabledGestureIconBadgeCheck =
"M22.74,18.2234C22.74,20.8888 20.5793,23.0494 17.914,23.0494 15.2487,23.0494 13.088,20.8888 13.088,18.2234 13.088,15.5581 15.2487,13.3975 17.914,13.3975 20.5793,13.3975 22.74,15.5581 22.74,18.2234z M21.1673,15.8009C21.4651,16.0889,21.473,16.5637,21.1851,16.8614L17.5425,20.6282C17.4012,20.7743 17.2066,20.8568 17.0034,20.8568 16.8001,20.8568 16.6055,20.7743 16.4642,20.6282L14.6429,18.7448C14.355,18.447 14.3629,17.9722 14.6607,17.6843 14.9585,17.3963 15.4333,17.4043 15.7212,17.7021L17.0034,19.0279 20.1068,15.8187C20.3947,15.5209,20.8695,15.513,21.1673,15.8009z";
// 老版浮动栏按钮图标
public static string LegacyLinedCursorIcon =
"F0 M24,24z M0,0z M3.85151,2.7073C3.52422,2.57095 3.147,2.64558 2.89629,2.89629 2.64558,3.147 2.57095,3.52422 2.7073,3.85151L9.7773,20.8215C9.91729,21.1575 10.2507,21.3718 10.6145,21.3595 10.9783,21.3473 11.2965,21.1111 11.4135,20.7664L13.4711,14.7085 18.8963,20.1337C19.238,20.4754 19.792,20.4754 20.1337,20.1337 20.4754,19.792 20.4754,19.238 20.1337,18.8963L14.7085,13.4711 20.7664,11.4135C21.1111,11.2965 21.3473,10.9783 21.3595,10.6145 21.3718,10.2507 21.1575,9.91729 20.8215,9.7773L3.85151,2.7073z M10.5017,18.0097L5.13984,5.13984 18.0097,10.5017 12.8136,12.2665C12.5561,12.3539,12.3539,12.5561,12.2665,12.8136L10.5017,18.0097z";
public static string LegacySolidCursorIcon =
"F0 M24,24z M0,0z M2.89629,2.89629C3.147,2.64558,3.52422,2.57095,3.85151,2.7073L20.8215,9.7773C21.1575,9.91729 21.3718,10.2507 21.3595,10.6145 21.3473,10.9783 21.1111,11.2965 20.7664,11.4135L14.7085,13.4711 20.1337,18.8963C20.4754,19.238 20.4754,19.792 20.1337,20.1337 19.792,20.4754 19.238,20.4754 18.8963,20.1337L13.4711,14.7085 11.4135,20.7664C11.2965,21.1111 10.9783,21.3473 10.6145,21.3595 10.2507,21.3718 9.91729,21.1575 9.7773,20.8215L2.7073,3.85151C2.57095,3.52422,2.64558,3.147,2.89629,2.89629z";
public static string LegacyLinedPenIcon =
"F0 M24,24z M0,0z M18.7033,4.39761C18.4948,4.31644 18.2714,4.27922 18.0473,4.28846 17.8233,4.29771 17.6038,4.3532 17.403,4.4512 17.2022,4.54919 17.0246,4.68744 16.8813,4.8568 16.8665,4.87422 16.8511,4.89102 16.8349,4.90716L15.7108,6.03131 17.9591,8.27962 19.0832,7.15546C19.1021,7.13662 19.1218,7.11869 19.1424,7.10176 19.3143,6.96037 19.4543,6.7853 19.5537,6.58793 19.6531,6.39058 19.7099,6.1751 19.7207,5.95519 19.7314,5.73528 19.6959,5.51545 19.6163,5.30962 19.5367,5.10378 19.4147,4.91625 19.2576,4.75914 19.1004,4.60201 18.9117,4.47877 18.7033,4.39761z M16.7944,9.44428L14.5461,7.19597 5.47079,16.2713 4.62767,19.3627 7.7191,18.5196 16.7944,9.44428z M13.9636,5.44913L4.15148,15.2613C4.05014,15.3626,3.977,15.4886,3.93929,15.6269L2.65942,20.3198C2.58166,20.6049 2.66264,20.9098 2.87161,21.1188 3.08059,21.3277 3.38551,21.4087 3.67063,21.331L8.36347,20.0511C8.50174,20.0134,8.62777,19.9402,8.72911,19.8389L20.2217,8.34636C20.5551,8.06468 20.8283,7.71873 21.0247,7.3289 21.2275,6.92628 21.3437,6.48586 21.3658,6.03572 21.3878,5.58559 21.3151,5.13594 21.1525,4.71552 20.99,4.29512 20.7411,3.91338 20.4222,3.59447 20.1033,3.27558 19.7214,3.0265 19.3009,2.86277 18.8804,2.69905 18.4304,2.62417 17.9794,2.64278 17.5285,2.66139 17.0862,2.77308 16.6807,2.97095 16.2862,3.16344 15.9348,3.43348 15.6478,3.76494L13.9636,5.44913z";
public static string LegacySolidPenIcon =
"F1 M24,24z M0,0z M19.3332,2.85933C18.9193,2.69814 18.4762,2.62442 18.0322,2.64274 17.5882,2.66106 17.1527,2.77103 16.7535,2.96583 16.3643,3.15575 16.0177,3.42232 15.7349,3.74956L14.5672,4.91725 19.0731,9.4231 20.2373,8.25888C20.5666,7.98121 20.8364,7.63993 21.0302,7.25528 21.2298,6.85899 21.3442,6.42551 21.3659,5.98249 21.3876,5.53947 21.3161,5.09692 21.1561,4.68313 20.996,4.26934 20.7511,3.89359 20.4372,3.57966 20.1232,3.26574 19.7472,3.02052 19.3332,2.85933z M18.0085,10.4877L13.5026,5.98183 4.14128,15.3432C4.04864,15.4358,3.98179,15.551,3.94732,15.6774L2.65684,20.4091C2.58577,20.6698 2.65979,20.9485 2.8508,21.1395 3.04182,21.3305 3.32054,21.4045 3.58117,21.3335L8.3129,20.043C8.43929,20.0085,8.5545,19.9417,8.64713,19.849L18.0085,10.4877z";
public static string LegacyLinedEraserStrokeIcon =
"F0 M25,24z M0,0z M7.32029,21.36L13.0098,21.36 13.0122,21.36 21.5471,21.36C21.989,21.36 22.3473,21.0017 22.3473,20.5598 22.3473,20.1179 21.989,19.7596 21.5471,19.7596L14.9429,19.7596 21.4352,13.2673C22.7372,12.0786,22.6872,10.1353,21.449,8.89707L16.1515,3.59952C14.9628,2.29751,13.0195,2.34754,11.7813,3.58572L2.68992,12.6771C1.3879,13.8657,1.43793,15.8091,2.67611,17.0473L6.75447,21.1256C6.90453,21.2757,7.10807,21.36,7.32029,21.36z M14.9771,4.68685C14.4571,4.10907,13.5664,4.06392,12.9129,4.71737L6.55503,11.0753 13.9595,18.4797 20.3174,12.1218C20.3273,12.1119 20.3375,12.1022 20.3479,12.0929 20.9257,11.5729 20.9708,10.6822 20.3174,10.0287L15.006,4.71737C14.9961,4.70745,14.9864,4.69727,14.9771,4.68685z M12.8278,19.6114L5.42338,12.2069 3.80776,13.8225C3.79784,13.8324 3.78766,13.8421 3.77724,13.8515 3.19947,14.3715 3.15431,15.2622 3.80776,15.9156L7.65174,19.7596 12.6796,19.7596 12.8278,19.6114z";
public static string LegacySolidEraserStrokeIcon =
"F1 M24,24z M0,0z M11.6199,3.61372C12.8916,2.34202,14.8995,2.2837,16.1307,3.62964L21.3433,8.84225C22.615,10.1139,22.6733,12.1218,21.3274,13.353L15.1877,19.4927 5.46434,9.76928 11.6199,3.61372z M7.33167,21.36C7.08919,21.36 6.86831,21.2676 6.70232,21.116 6.69184,21.1064 6.68155,21.0966 6.67147,21.0865L2.65671,17.0718C1.385,15.8001,1.32668,13.7922,2.67262,12.561L4.14394,11.0897 12.5469,19.4927 21.3367,19.4927C21.8523,19.4927 22.2703,19.9107 22.2703,20.4263 22.2703,20.942 21.8523,21.36 21.3367,21.36L7.33167,21.36z";
public static string LegacyLinedEraserCircleIcon =
"F0 M25,24z M0,0z M2.47995,17.1206L6.56736,21.208C6.57733,21.218 6.58749,21.2277 6.59783,21.237 6.66429,21.2971 6.7405,21.3466 6.82381,21.3829L6.83712,21.3885C6.84698,21.3926 6.85693,21.3965 6.86698,21.4003 6.86818,21.4007 6.86937,21.4011 6.87057,21.4016 6.94576,21.4289 7.02412,21.4451 7.10303,21.45L7.12183,21.451 7.13076,21.4513 7.13345,21.4514 7.15549,21.4517 17.0847,21.4517C17.5973,22.3438 18.5597,22.9445 19.6624,22.9445 21.3031,22.9445 22.6332,21.6144 22.6332,19.9737 22.6332,18.3329 21.3031,17.0028 19.6624,17.0028 18.0839,17.0028 16.793,18.2338 16.6972,19.7882L14.8669,19.7882 21.3224,13.3327C22.6404,12.1289,22.5884,10.1619,21.3367,8.91021L16.0278,3.60138C14.8241,2.28336,12.8571,2.33535,11.6053,3.58706L2.49426,12.6981C1.17625,13.9019,1.22824,15.8689,2.47995,17.1206z M14.8072,4.7316C14.2984,4.16633,13.4255,4.11939,12.7816,4.76332L6.43063,11.1143 13.8094,18.4931 20.1604,12.1421C20.1707,12.1318 20.1813,12.1218 20.1921,12.112 20.7574,11.6033 20.8043,10.7304 20.1604,10.0865L14.8373,4.76332C14.8269,4.75301,14.8169,4.74243,14.8072,4.7316z M3.65621,13.8887C3.6459,13.899 3.63532,13.9091 3.62448,13.9188 3.05922,14.4276 3.01228,15.3004 3.65621,15.9444L7.50001,19.7882 12.752,19.7882 5.25437,12.2906 3.65621,13.8887z";
public static string LegacySolidEraserCircleIcon =
"F1 M24,24z M0,0z M15.0919,19.6686L21.4282,13.3322C22.7462,12.1285,22.6942,10.1616,21.4426,8.90993L16.134,3.60133C14.9303,2.28338,12.9633,2.33537,11.7117,3.58702L5.36097,9.93771 15.0919,19.6686z M6.67201,21.2053C6.82267,21.3569,7.03137,21.4508,7.26201,21.4508L17.1907,21.4508C17.7033,22.3429 18.6657,22.9437 19.7683,22.9437 21.409,22.9437 22.7391,21.6136 22.7391,19.9729 22.7391,18.3322 21.409,17.0022 19.7683,17.0022 18.19,17.0022 16.8991,18.2331 16.8033,19.7874L12.8583,19.7874 4.18476,11.1139 2.60098,12.6977C1.28303,13.9014,1.33502,15.8683,2.58667,17.12L6.67201,21.2053z";
public static string LegacyLinedLassoSelectIcon =
"F0 M24,24z M0,0z M14.4715,12.7092L14.4715,18.7882 15.8291,16.7546C15.9688,16.5453,16.2038,16.4196,16.4554,16.4196L19.0106,16.4196 14.4715,12.7092z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83887,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.7765,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9179 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z M5.97224,14.4745C5.71544,14.4745 5.46916,14.5765 5.28757,14.7581 5.10598,14.9396 5.00397,15.1859 5.00397,15.4427 5.00397,15.6995 5.10598,15.9458 5.28757,16.1274 5.46916,16.309 5.71544,16.411 5.97224,16.411 6.22904,16.411 6.47533,16.309 6.65692,16.1274 6.8385,15.9458 6.94052,15.6995 6.94052,15.4427 6.94052,15.1859 6.8385,14.9396 6.65692,14.7581 6.47533,14.5765 6.22904,14.4745 5.97224,14.4745z";
public static string LegacySolidLassoSelectIcon =
"F1 M24,24z M0,0z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83888,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.77649,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9178 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z";
}
}
+2 -2
View File
@@ -237,7 +237,6 @@ namespace Ink_Canvas
graphics.CopyFromScreen(x, y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
}
LogHelper.WriteLogToFile($"成功截取区域: X={x}, Y={y}, Width={width}, Height={height}");
return bitmap;
}
catch (Exception ex)
@@ -405,6 +404,8 @@ namespace Ink_Canvas
image.MouseWheel += Element_MouseWheel;
// 触摸事件
image.TouchDown += Element_TouchDown;
image.TouchUp += Element_TouchUp;
image.IsManipulationEnabled = true;
image.ManipulationDelta += Element_ManipulationDelta;
image.ManipulationCompleted += Element_ManipulationCompleted;
@@ -492,7 +493,6 @@ namespace Ink_Canvas
InitializeScreenshotTransform(image);
}
LogHelper.WriteLogToFile($"截图居中完成: 位置({centerX}, {centerY}), 尺寸({newWidth}x{newHeight})");
}
catch (Exception ex)
{
+204 -39
View File
@@ -1,4 +1,4 @@
using Ink_Canvas.Helpers;
using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.PowerPoint;
@@ -619,13 +619,22 @@ namespace Ink_Canvas
{
try
{
// 记录进入放映时浮动栏收纳状态
// 始终记录进入放映时浮动栏收纳状态,用于退出时恢复
wasFloatingBarFoldedWhenEnterSlideShow = isFloatingBarFolded;
if (Settings.Automation.IsAutoFoldInPPTSlideShow && !isFloatingBarFolded)
FoldFloatingBar_MouseUp(new object(), null);
else if (isFloatingBarFolded)
await UnFoldFloatingBar(new object());
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
{
if (!isFloatingBarFolded)
FoldFloatingBar_MouseUp(new object(), null);
}
else
{
// 如果关闭了PPT自动收纳功能,但用户当前在收纳模式下,进入PPT时取消收纳以提供更好的使用体验
if (isFloatingBarFolded)
{
await UnFoldFloatingBar(new object());
}
}
isStopInkReplay = true;
@@ -699,6 +708,9 @@ namespace Ink_Canvas
if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow &&
!Settings.Automation.IsAutoFoldInPPTSlideShow)
{
// 先进入批注模式,这会显示调色盘
PenIcon_Click(null, null);
// 然后设置颜色
BtnColorRed_Click(null, null);
Dispatcher.BeginInvoke(new Action(() =>
{
@@ -772,14 +784,35 @@ namespace Ink_Canvas
{
try
{
// 处理浮动栏状态
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && wasFloatingBarFoldedWhenEnterSlideShow)
// 处理浮动栏状态:根据"退出PPT放映后自动恢复浮动栏状态"设置决定是否恢复
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
{
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
if (wasFloatingBarFoldedWhenEnterSlideShow)
{
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
}
else
{
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
}
}
else
{
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
{
if (isFloatingBarFolded)
{
await UnFoldFloatingBar(new object());
}
}
else
{
// 如果两个功能都关闭,确保浮动栏展开
if (isFloatingBarFolded)
{
await UnFoldFloatingBar(new object());
}
}
}
if (isEnteredSlideShowEndEvent) return;
@@ -1064,6 +1097,38 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 重置PPT相关的状态变量,当PPT自动收纳设置变更时调用
/// </summary>
public void ResetPPTStateVariables()
{
try
{
// 重置进入PPT时的浮动栏收纳状态记录
wasFloatingBarFoldedWhenEnterSlideShow = false;
// 重置PPT放映结束事件标志
isEnteredSlideShowEndEvent = false;
// 重置演示文稿黑边状态
isPresentationHaveBlackSpace = false;
// 重置上次播放位置相关字段
_lastPlaybackPage = 0;
_shouldNavigateToLastPage = false;
// 重置页面切换防抖机制
_lastSlideSwitchTime = DateTime.MinValue;
_pendingSlideIndex = -1;
LogHelper.WriteLogToFile("PPT状态变量已重置", LogHelper.LogType.Trace);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"重置PPT状态变量失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 使用防抖机制处理页面切换
/// </summary>
@@ -1117,14 +1182,42 @@ namespace Ink_Canvas
{
try
{
// 检查PPT连接状态
if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true)
{
return;
}
// 获取当前页面索引
var currentSlideIndex = _pptManager?.GetCurrentSlideNumber() ?? 0;
// 验证页面索引的有效性
if (newSlideIndex <= 0)
{
LogHelper.WriteLogToFile($"无效的新页面索引: {newSlideIndex},跳过页面切换", LogHelper.LogType.Warning);
return;
}
// 如果有当前墨迹且不是第一次切换,先保存到当前页面
if (inkCanvas.Strokes.Count > 0 && currentSlideIndex > 0 && currentSlideIndex != newSlideIndex)
{
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
LogHelper.WriteLogToFile($"切换前保存第{currentSlideIndex}页墨迹,墨迹数量: {inkCanvas.Strokes.Count}", LogHelper.LogType.Trace);
// 检查是否可以写入墨迹
bool canWrite = _multiPPTInkManager?.CanWriteInk(currentSlideIndex) == true;
if (canWrite)
{
// 正常保存
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
}
else
{
// 墨迹被锁定,跳过保存以避免墨迹错页
}
}
else if (inkCanvas.Strokes.Count > 0 && currentSlideIndex <= 0)
{
// 无法获取当前页面索引时,不保存墨迹,直接清空
}
// 切换到新页面并加载墨迹
@@ -1135,8 +1228,7 @@ namespace Ink_Canvas
inkCanvas.Strokes.Add(newStrokes);
}
// 设置墨迹锁定
_multiPPTInkManager?.LockInkForSlide(newSlideIndex);
// 注意:LockInkForSlide已经在SwitchToSlide中调用,这里不需要重复调用
}
catch (Exception ex)
{
@@ -1478,6 +1570,7 @@ namespace Ink_Canvas
// 结束放映
if (_pptManager?.TryEndSlideShow() == true)
{
// 如果成功结束放映,等待OnPPTSlideShowEnd事件处理收纳状态恢复
}
else
{
@@ -1490,13 +1583,37 @@ namespace Ink_Canvas
_pptUIManager?.UpdateSidebarExitButtons(false);
LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace);
});
// 手动处理收纳状态恢复,因为OnPPTSlideShowEnd事件可能未触发
await HandleManualSlideShowEnd();
}
HideSubPanels("cursor");
SetCurrentToolMode(InkCanvasEditingMode.None);
await Task.Delay(150);
ViewboxFloatingBarMarginAnimation(100, true);
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
{
if (wasFloatingBarFoldedWhenEnterSlideShow)
{
ViewboxFloatingBarMarginAnimation(-60);
}
else
{
ViewboxFloatingBarMarginAnimation(100, true);
}
}
else
{
if (isFloatingBarFolded)
{
ViewboxFloatingBarMarginAnimation(-60);
}
else
{
ViewboxFloatingBarMarginAnimation(100, true);
}
}
}
catch (Exception ex)
{
@@ -1508,6 +1625,77 @@ namespace Ink_Canvas
_pptUIManager?.UpdateSlideShowStatus(false);
_pptUIManager?.UpdateSidebarExitButtons(false);
});
// 异常情况下也手动处理收纳状态恢复
await HandleManualSlideShowEnd();
// 异常情况下也要根据设置决定浮动栏边距
await Task.Delay(150);
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
{
if (wasFloatingBarFoldedWhenEnterSlideShow)
{
ViewboxFloatingBarMarginAnimation(-60);
}
else
{
ViewboxFloatingBarMarginAnimation(100, true);
}
}
else
{
if (isFloatingBarFolded)
{
ViewboxFloatingBarMarginAnimation(-60);
}
else
{
ViewboxFloatingBarMarginAnimation(100, true);
}
}
}
}
/// <summary>
/// 手动处理PPT放映结束时的收纳状态恢复
/// </summary>
private async Task HandleManualSlideShowEnd()
{
try
{
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
{
if (wasFloatingBarFoldedWhenEnterSlideShow)
{
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
}
else
{
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
}
}
else
{
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
{
if (isFloatingBarFolded)
{
await UnFoldFloatingBar(new object());
}
}
else
{
// 如果两个功能都关闭,确保浮动栏展开
if (isFloatingBarFolded)
{
await UnFoldFloatingBar(new object());
}
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"手动处理PPT放映结束收纳状态恢复失败: {ex}", LogHelper.LogType.Error);
}
}
@@ -1666,28 +1854,5 @@ namespace Ink_Canvas
{
BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
}
}
}
+12 -1
View File
@@ -68,9 +68,20 @@ namespace Ink_Canvas
public static void ScrollViewToVerticalTop(FrameworkElement element, ScrollViewer scrollViewer)
{
if (element == null || scrollViewer == null)
{
return;
}
var scrollViewerOffset = scrollViewer.VerticalOffset;
var point = new Point(0, scrollViewerOffset);
var tarPos = element.TransformToVisual(scrollViewer).Transform(point);
var transform = element.TransformToVisual(scrollViewer);
if (transform == null)
{
return;
}
var tarPos = transform.Transform(point);
scrollViewer.ScrollToVerticalOffset(tarPos.Y);
}
@@ -34,23 +34,24 @@ namespace Ink_Canvas
lastBorderMouseDownObject = sender;
}
private bool isStrokeSelectionCloneOn;
private void BorderStrokeSelectionClone_MouseUp(object sender, MouseButtonEventArgs e)
{
if (lastBorderMouseDownObject != sender) return;
if (isStrokeSelectionCloneOn)
try
{
BorderStrokeSelectionClone.Background = Brushes.Transparent;
isStrokeSelectionCloneOn = false;
var strokes = inkCanvas.GetSelectedStrokes();
if (strokes.Count > 0)
{
// 直接执行克隆操作,与图片克隆保持一致
CloneStrokes(strokes);
LogHelper.WriteLogToFile($"墨迹克隆完成: {strokes.Count} 个墨迹");
}
}
else
catch (Exception ex)
{
BorderStrokeSelectionClone.Background = new SolidColorBrush(StringToColor("#FF1ED760"));
isStrokeSelectionCloneOn = true;
LogHelper.WriteLogToFile($"墨迹克隆失败: {ex.Message}", LogHelper.LogType.Error);
}
}
@@ -60,9 +61,7 @@ namespace Ink_Canvas
var strokes = inkCanvas.GetSelectedStrokes();
inkCanvas.Select(new StrokeCollection());
strokes = strokes.Clone();
BtnWhiteBoardAdd_Click(null, null);
inkCanvas.Strokes.Add(strokes);
CloneStrokesToNewBoard(strokes);
}
private void BorderStrokeSelectionDelete_MouseUp(object sender, MouseButtonEventArgs e)
@@ -408,7 +407,6 @@ namespace Ink_Canvas
// 显示墨迹选择栏和选择框
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
BorderStrokeSelectionClone.Background = Brushes.Transparent;
isStrokeSelectionCloneOn = false;
updateBorderStrokeSelectionControlLocation();
UpdateSelectionDisplay();
return;
@@ -657,23 +655,9 @@ namespace Ink_Canvas
}
}
if (isStrokeSelectionCloneOn)
{
var strokes = inkCanvas.GetSelectedStrokes();
isProgramChangeStrokeSelection = true;
inkCanvas.Select(new StrokeCollection());
StrokesSelectionClone = strokes.Clone();
inkCanvas.Select(strokes);
isProgramChangeStrokeSelection = false;
inkCanvas.Strokes.Add(StrokesSelectionClone);
}
else
{
// 新增:启动套索选择模式
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.Select);
inkCanvas.Select(new StrokeCollection());
}
SetCurrentToolMode(InkCanvasEditingMode.Select);
inkCanvas.Select(new StrokeCollection());
}
}
+235 -5
View File
@@ -1000,11 +1000,23 @@ namespace Ink_Canvas
PPTBtnPreviewRS.Visibility = Visibility.Collapsed;
}
PPTBtnPreviewRSTransform.Y = -(Settings.PowerPointSettings.PPTRSButtonPosition * 0.5);
PPTBtnPreviewLSTransform.Y = -(Settings.PowerPointSettings.PPTLSButtonPosition * 0.5);
// 计算预览区域的缩放比例
double previewScaleY = 182.0 / SystemParameters.PrimaryScreenHeight;
double previewScaleX = 324.0 / SystemParameters.PrimaryScreenWidth;
double sideButtonScaleFactor = 1.9;
double rsPosition = Settings.PowerPointSettings.PPTRSButtonPosition;
double lsPosition = Settings.PowerPointSettings.PPTLSButtonPosition;
PPTBtnPreviewRSTransform.Y = -(rsPosition * 2 * previewScaleY / sideButtonScaleFactor);
PPTBtnPreviewLSTransform.Y = -(lsPosition * 2 * previewScaleY / sideButtonScaleFactor);
PPTBtnPreviewLBTransform.X = -(Settings.PowerPointSettings.PPTLBButtonPosition * 0.5);
PPTBtnPreviewRBTransform.X = -(Settings.PowerPointSettings.PPTRBButtonPosition * 0.5);
double bottomButtonScaleFactor = 1.2;
double leftMarginOffset = 6 * previewScaleX;
PPTBtnPreviewLBTransform.X = leftMarginOffset + (Settings.PowerPointSettings.PPTLBButtonPosition * previewScaleX / bottomButtonScaleFactor);
PPTBtnPreviewRBTransform.X = -(leftMarginOffset + (Settings.PowerPointSettings.PPTRBButtonPosition * previewScaleX / bottomButtonScaleFactor));
}
private void ToggleSwitchShowCursor_Toggled(object sender, RoutedEventArgs e)
@@ -1406,7 +1418,18 @@ namespace Ink_Canvas
private void ToggleSwitchAutoFoldInPPTSlideShow_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
// 记录设置变更前的状态
bool previousState = Settings.Automation.IsAutoFoldInPPTSlideShow;
Settings.Automation.IsAutoFoldInPPTSlideShow = ToggleSwitchAutoFoldInPPTSlideShow.IsOn;
// 如果设置状态发生变化,重置PPT相关状态变量
if (previousState != Settings.Automation.IsAutoFoldInPPTSlideShow)
{
ResetPPTStateVariables();
LogHelper.WriteLogToFile($"PPT自动收纳设置已变更: {Settings.Automation.IsAutoFoldInPPTSlideShow}, 已重置相关状态变量", LogHelper.LogType.Trace);
}
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
{
SettingsPPTInkingAndAutoFoldExplictBorder.Visibility = Visibility.Visible;
@@ -1543,6 +1566,13 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchAutoFoldWhenExitWhiteboard_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Automation.IsAutoFoldWhenExitWhiteboard = ToggleSwitchAutoFoldWhenExitWhiteboard.IsOn;
SaveSettingsToFile();
}
private void ToggleSwitchSaveScreenshotsInDateFolders_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
@@ -1780,6 +1810,18 @@ namespace Ink_Canvas
private void ToggleSwitchEnableTwoFingerZoom_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
// 如果多指书写模式启用,强制禁用双指手势
if (ToggleSwitchEnableMultiTouchMode.IsOn)
{
ToggleSwitchEnableTwoFingerZoom.IsOn = false;
BoardToggleSwitchEnableTwoFingerZoom.IsOn = false;
Settings.Gesture.IsEnableTwoFingerZoom = false;
CheckEnableTwoFingerGestureBtnColorPrompt();
SaveSettingsToFile();
return;
}
if (sender == ToggleSwitchEnableTwoFingerZoom)
BoardToggleSwitchEnableTwoFingerZoom.IsOn = ToggleSwitchEnableTwoFingerZoom.IsOn;
else
@@ -1859,6 +1901,32 @@ namespace Ink_Canvas
}
Settings.Gesture.IsEnableMultiTouchMode = ToggleSwitchEnableMultiTouchMode.IsOn;
// 如果启用多指书写模式,强制禁用所有双指手势
if (ToggleSwitchEnableMultiTouchMode.IsOn)
{
// 强制关闭所有双指手势设置
Settings.Gesture.IsEnableTwoFingerTranslate = false;
Settings.Gesture.IsEnableTwoFingerZoom = false;
Settings.Gesture.IsEnableTwoFingerRotation = false;
// 更新UI开关状态
if (ToggleSwitchEnableTwoFingerTranslate != null)
ToggleSwitchEnableTwoFingerTranslate.IsOn = false;
if (ToggleSwitchEnableTwoFingerZoom != null)
ToggleSwitchEnableTwoFingerZoom.IsOn = false;
if (ToggleSwitchEnableTwoFingerRotation != null)
ToggleSwitchEnableTwoFingerRotation.IsOn = false;
// 更新设置窗口中的开关状态
if (BoardToggleSwitchEnableTwoFingerTranslate != null)
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = false;
if (BoardToggleSwitchEnableTwoFingerZoom != null)
BoardToggleSwitchEnableTwoFingerZoom.IsOn = false;
if (BoardToggleSwitchEnableTwoFingerRotation != null)
BoardToggleSwitchEnableTwoFingerRotation.IsOn = false;
}
CheckEnableTwoFingerGestureBtnColorPrompt();
SaveSettingsToFile();
}
@@ -1866,6 +1934,18 @@ namespace Ink_Canvas
private void ToggleSwitchEnableTwoFingerTranslate_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
// 如果多指书写模式启用,强制禁用双指手势
if (ToggleSwitchEnableMultiTouchMode.IsOn)
{
ToggleSwitchEnableTwoFingerTranslate.IsOn = false;
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = false;
Settings.Gesture.IsEnableTwoFingerTranslate = false;
CheckEnableTwoFingerGestureBtnColorPrompt();
SaveSettingsToFile();
return;
}
if (sender == ToggleSwitchEnableTwoFingerTranslate)
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = ToggleSwitchEnableTwoFingerTranslate.IsOn;
else
@@ -1879,6 +1959,17 @@ namespace Ink_Canvas
{
if (!isLoaded) return;
// 如果多指书写模式启用,强制禁用双指手势
if (ToggleSwitchEnableMultiTouchMode.IsOn)
{
ToggleSwitchEnableTwoFingerRotation.IsOn = false;
BoardToggleSwitchEnableTwoFingerRotation.IsOn = false;
Settings.Gesture.IsEnableTwoFingerRotation = false;
CheckEnableTwoFingerGestureBtnColorPrompt();
SaveSettingsToFile();
return;
}
if (sender == ToggleSwitchEnableTwoFingerRotation)
BoardToggleSwitchEnableTwoFingerRotation.IsOn = ToggleSwitchEnableTwoFingerRotation.IsOn;
else
@@ -1897,6 +1988,7 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
#endregion
#region Reset
@@ -1911,6 +2003,10 @@ namespace Ink_Canvas
Settings.Advanced.TouchMultiplier = 0.3;
Settings.Advanced.NibModeBoundsWidth = 5;
Settings.Advanced.FingerModeBoundsWidth = 20;
Settings.Advanced.NibModeBoundsWidthThresholdValue = 2.5;
Settings.Advanced.FingerModeBoundsWidthThresholdValue = 2.5;
Settings.Advanced.NibModeBoundsWidthEraserSize = 0.8;
Settings.Advanced.FingerModeBoundsWidthEraserSize = 0.8;
Settings.Advanced.EraserBindTouchMultiplier = true;
Settings.Advanced.IsLogEnabled = true;
Settings.Advanced.IsSecondConfirmWhenShutdownApp = false;
@@ -2275,6 +2371,26 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchIsAutoBackupEnabled_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Advanced.IsAutoBackupEnabled = ToggleSwitchIsAutoBackupEnabled.IsOn;
SaveSettingsToFile();
}
private void ComboBoxAutoBackupInterval_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!isLoaded) return;
if (ComboBoxAutoBackupInterval.SelectedItem is ComboBoxItem selectedItem && selectedItem.Tag != null)
{
if (int.TryParse(selectedItem.Tag.ToString(), out int interval))
{
Settings.Advanced.AutoBackupIntervalDays = interval;
SaveSettingsToFile();
}
}
}
private void BtnManualBackup_Click(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
@@ -2400,6 +2516,44 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchUseLegacyTimerUI_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.RandSettings.UseLegacyTimerUI = ToggleSwitchUseLegacyTimerUI.IsOn;
SaveSettingsToFile();
}
private void TimerVolumeSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.RandSettings.TimerVolume = TimerVolumeSlider.Value;
SaveSettingsToFile();
}
private void ButtonSelectCustomTimerSound_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog
{
Title = "选择计时器提醒铃声",
Filter = "音频文件 (*.wav)|*.wav|所有文件 (*.*)|*.*",
DefaultExt = "wav"
};
if (openFileDialog.ShowDialog() == true)
{
Settings.RandSettings.CustomTimerSoundPath = openFileDialog.FileName;
SaveSettingsToFile();
MessageBox.Show("自定义铃声设置成功!", "设置成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private void ButtonResetTimerSound_Click(object sender, RoutedEventArgs e)
{
Settings.RandSettings.CustomTimerSoundPath = "";
SaveSettingsToFile();
MessageBox.Show("已重置为默认铃声!", "重置成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void ToggleSwitchShowRandomAndSingleDraw_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
@@ -2438,10 +2592,87 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
public void UpdateFloatingBarIcons()
{
if (Settings.Appearance.UseLegacyFloatingBarUI)
{
// 使用老版图标
CursorIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedCursorIcon);
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedPenIcon);
StrokeEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedEraserStrokeIcon);
CircleEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedEraserCircleIcon);
LassoSelectIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedLassoSelectIcon);
}
else
{
// 使用新版图标
CursorIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedCursorIcon);
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedPenIcon);
StrokeEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserStrokeIcon);
CircleEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserCircleIcon);
LassoSelectIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedLassoSelectIcon);
}
}
public string GetCorrectIcon(string iconType, bool isSolid = false)
{
if (Settings.Appearance.UseLegacyFloatingBarUI)
{
// 使用老版图标
switch (iconType)
{
case "cursor":
return isSolid ? XamlGraphicsIconGeometries.LegacySolidCursorIcon : XamlGraphicsIconGeometries.LegacyLinedCursorIcon;
case "pen":
return isSolid ? XamlGraphicsIconGeometries.LegacySolidPenIcon : XamlGraphicsIconGeometries.LegacyLinedPenIcon;
case "eraserStroke":
return isSolid ? XamlGraphicsIconGeometries.LegacySolidEraserStrokeIcon : XamlGraphicsIconGeometries.LegacyLinedEraserStrokeIcon;
case "eraserCircle":
return isSolid ? XamlGraphicsIconGeometries.LegacySolidEraserCircleIcon : XamlGraphicsIconGeometries.LegacyLinedEraserCircleIcon;
case "lassoSelect":
return isSolid ? XamlGraphicsIconGeometries.LegacySolidLassoSelectIcon : XamlGraphicsIconGeometries.LegacyLinedLassoSelectIcon;
}
}
else
{
// 使用新版图标
switch (iconType)
{
case "cursor":
return isSolid ? XamlGraphicsIconGeometries.SolidCursorIcon : XamlGraphicsIconGeometries.LinedCursorIcon;
case "pen":
return isSolid ? XamlGraphicsIconGeometries.SolidPenIcon : XamlGraphicsIconGeometries.LinedPenIcon;
case "eraserStroke":
return isSolid ? XamlGraphicsIconGeometries.SolidEraserStrokeIcon : XamlGraphicsIconGeometries.LinedEraserStrokeIcon;
case "eraserCircle":
return isSolid ? XamlGraphicsIconGeometries.SolidEraserCircleIcon : XamlGraphicsIconGeometries.LinedEraserCircleIcon;
case "lassoSelect":
return isSolid ? XamlGraphicsIconGeometries.SolidLassoSelectIcon : XamlGraphicsIconGeometries.LinedLassoSelectIcon;
}
}
return "";
}
#endregion
#region
private void CheckBoxUseLegacyFloatingBarUI_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.UseLegacyFloatingBarUI = CheckBoxUseLegacyFloatingBarUI.IsChecked ?? false;
UpdateFloatingBarIcons();
SaveSettingsToFile();
}
private void CheckBoxUseLegacyFloatingBarUI_Unchecked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.UseLegacyFloatingBarUI = CheckBoxUseLegacyFloatingBarUI.IsChecked ?? false;
UpdateFloatingBarIcons();
SaveSettingsToFile();
}
private void CheckBoxShowShapeButton_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
@@ -2730,7 +2961,6 @@ namespace Ink_Canvas
}
// 重新计算浮动栏位置,因为按钮可见性变化会影响浮动栏宽度
// 修复:移除浮动栏收起状态检查,确保在收起状态下也能正确修正位置
if (currentMode == 0) // 只在屏幕模式下重新计算浮动栏位置
{
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
+112 -5
View File
@@ -1,4 +1,4 @@
using Hardcodet.Wpf.TaskbarNotification;
using Hardcodet.Wpf.TaskbarNotification;
using Ink_Canvas.Helpers;
using Newtonsoft.Json;
using OSVersionExtension;
@@ -28,12 +28,92 @@ namespace Ink_Canvas
{
string text = File.ReadAllText(App.RootPath + settingsFileName);
Settings = JsonConvert.DeserializeObject<Settings>(text);
// 验证设置是否成功加载
if (Settings == null)
{
LogHelper.WriteLogToFile("配置文件解析失败,尝试从备份恢复", LogHelper.LogType.Warning);
if (AutoBackupManager.TryRestoreFromBackup())
{
// 重新尝试加载
text = File.ReadAllText(App.RootPath + settingsFileName);
Settings = JsonConvert.DeserializeObject<Settings>(text);
if (Settings != null)
{
}
}
// 如果仍然失败,使用默认设置
if (Settings == null)
{
LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
BtnResetToSuggestion_Click(null, null);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"配置文件加载失败: {ex.Message}", LogHelper.LogType.Error);
// 尝试从备份恢复
LogHelper.WriteLogToFile("尝试从备份恢复配置文件", LogHelper.LogType.Warning);
if (AutoBackupManager.TryRestoreFromBackup())
{
try
{
string text = File.ReadAllText(App.RootPath + settingsFileName);
Settings = JsonConvert.DeserializeObject<Settings>(text);
if (Settings != null)
{
}
}
catch (Exception restoreEx)
{
LogHelper.WriteLogToFile($"从备份恢复后重新加载失败: {restoreEx.Message}", LogHelper.LogType.Error);
BtnResetToSuggestion_Click(null, null);
}
}
// 如果仍然失败,使用默认设置
if (Settings == null)
{
LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
BtnResetToSuggestion_Click(null, null);
}
}
catch { }
}
else
{
BtnResetToSuggestion_Click(null, null);
LogHelper.WriteLogToFile("配置文件不存在,尝试从备份恢复", LogHelper.LogType.Warning);
if (AutoBackupManager.TryRestoreFromBackup())
{
try
{
string text = File.ReadAllText(App.RootPath + settingsFileName);
Settings = JsonConvert.DeserializeObject<Settings>(text);
if (Settings != null)
{
}
}
catch (Exception restoreEx)
{
LogHelper.WriteLogToFile($"从备份恢复后加载失败: {restoreEx.Message}", LogHelper.LogType.Error);
BtnResetToSuggestion_Click(null, null);
}
}
else
{
// 备份恢复失败(备份目录不存在等),使用默认设置
LogHelper.WriteLogToFile("备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
BtnResetToSuggestion_Click(null, null);
}
// 如果仍然失败,使用默认设置
if (Settings == null)
{
LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
BtnResetToSuggestion_Click(null, null);
}
}
}
catch (Exception ex)
@@ -75,7 +155,7 @@ namespace Ink_Canvas
Settings.Automation.AutoDelSavedFilesDaysThreshold);
}
if (Settings.Startup.IsFoldAtStartup)
if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode)
{
FoldFloatingBar_MouseUp(Fold_Icon, null);
}
@@ -245,6 +325,9 @@ namespace Ink_Canvas
break;
}
// 设置主题下拉框
ComboBoxTheme.SelectedIndex = Settings.Appearance.Theme;
ComboBoxChickenSoupSource.SelectedIndex = Settings.Appearance.ChickenSoupSource;
ToggleSwitchEnableQuickPanel.IsOn = Settings.Appearance.IsShowQuickPanel;
@@ -320,6 +403,7 @@ namespace Ink_Canvas
Settings.Appearance.EnableChickenSoupInWhiteboardMode;
// 浮动栏按钮显示控制开关初始化
CheckBoxUseLegacyFloatingBarUI.IsChecked = Settings.Appearance.UseLegacyFloatingBarUI;
CheckBoxShowShapeButton.IsChecked = Settings.Appearance.IsShowShapeButton;
CheckBoxShowUndoButton.IsChecked = Settings.Appearance.IsShowUndoButton;
CheckBoxShowRedoButton.IsChecked = Settings.Appearance.IsShowRedoButton;
@@ -337,6 +421,9 @@ namespace Ink_Canvas
// 应用浮动栏按钮可见性设置
UpdateFloatingBarButtonsVisibility();
// 更新浮动栏图标
UpdateFloatingBarIcons();
SystemEvents_UserPreferenceChanged(null, null);
}
@@ -711,6 +798,17 @@ namespace Ink_Canvas
ToggleSwitchIsEnableDPIChangeDetection.IsOn = Settings.Advanced.IsEnableDPIChangeDetection;
ToggleSwitchIsEnableAvoidFullScreenHelper.IsOn = Settings.Advanced.IsEnableAvoidFullScreenHelper;
ToggleSwitchIsAutoBackupBeforeUpdate.IsOn = Settings.Advanced.IsAutoBackupBeforeUpdate;
ToggleSwitchIsAutoBackupEnabled.IsOn = Settings.Advanced.IsAutoBackupEnabled;
// 设置备份间隔下拉框
foreach (ComboBoxItem item in ComboBoxAutoBackupInterval.Items)
{
if (item.Tag != null && int.TryParse(item.Tag.ToString(), out int interval) && interval == Settings.Advanced.AutoBackupIntervalDays)
{
ComboBoxAutoBackupInterval.SelectedItem = item;
break;
}
}
if (Settings.Advanced.IsEnableFullScreenHelper)
{
FullScreenHelper.MarkFullscreenWindowTaskbarList(new WindowInteropHelper(this).Handle, true);
@@ -768,6 +866,10 @@ namespace Ink_Canvas
RandomDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed;
SingleDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed;
// 计时器设置
ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI;
TimerVolumeSlider.Value = Settings.RandSettings.TimerVolume;
// 加载自定义点名背景
UpdatePickNameBackgroundsInComboBox();
@@ -786,6 +888,8 @@ namespace Ink_Canvas
RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents;
ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand;
ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType;
ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI;
TimerVolumeSlider.Value = Settings.RandSettings.TimerVolume;
}
// ModeSettings
@@ -907,6 +1011,9 @@ namespace Ink_Canvas
// 加载退出收纳模式自动切换至批注模式设置
ToggleSwitchAutoEnterAnnotationModeWhenExitFoldMode.IsOn = Settings.Automation.IsAutoEnterAnnotationModeWhenExitFoldMode;
// 加载退出白板时自动收纳设置
ToggleSwitchAutoFoldWhenExitWhiteboard.IsOn = Settings.Automation.IsAutoFoldWhenExitWhiteboard;
}
else
{
@@ -975,4 +1082,4 @@ namespace Ink_Canvas
}
}
}
}
}
+180 -67
View File
@@ -10,6 +10,7 @@ using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
using Point = System.Windows.Point;
@@ -74,9 +75,9 @@ namespace Ink_Canvas
ToggleSwitchDrawShapeBorderAutoHide.IsOn = !ToggleSwitchDrawShapeBorderAutoHide.IsOn;
if (ToggleSwitchDrawShapeBorderAutoHide.IsOn)
((FontIcon)sender).Glyph = "&#xE840;";
((SymbolIcon)sender).Symbol = Symbol.Pin;
else
((FontIcon)sender).Glyph = "&#xE77A;";
((SymbolIcon)sender).Symbol = Symbol.UnPin;
}
private object lastMouseDownSender;
@@ -126,10 +127,7 @@ namespace Ink_Canvas
}
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
// 修复:从橡皮擦切换到批注模式时,退出多指书写模式
// 这解决了从橡皮擦切换为批注时被锁定为多指书写的问题
DisableEraserOverlay();
ExitMultiTouchModeIfNeeded();
// 如果当前已是批注模式,再次点击弹出批注子面板
@@ -151,10 +149,12 @@ namespace Ink_Canvas
}
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
// 修复:确保从橡皮擦切换到笔时,多指手势功能能正确恢复
// 更新lastInkCanvasEditingMode以确保多指手势逻辑正确
lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
ResetAllShapeButtonsOpacity();
SetCursorBasedOnEditingMode(inkCanvas);
}
@@ -175,7 +175,6 @@ namespace Ink_Canvas
lastIsInMultiTouchMode = true;
}
// 修复:几何绘制模式下确保不切换到Ink模式,避免触摸轨迹被收集
if (drawingShapeMode != 0)
{
inkCanvas.EditingMode = InkCanvasEditingMode.None;
@@ -344,7 +343,7 @@ namespace Ink_Canvas
await CheckIsDrawingShapesInMultiTouchMode();
EnterShapeDrawingMode(3);
CancelSingleFingerDragMode();
isLongPressSelected = true; // 设置为选中状态,避免抬笔后切换回笔模式
isLongPressSelected = false;
lastMouseDownSender = null;
DrawShapePromptToPen();
}
@@ -490,27 +489,17 @@ namespace Ink_Canvas
SetCursorBasedOnEditingMode(inkCanvas);
}
// 修复:几何绘制模式下完全禁止触摸轨迹收集
if (drawingShapeMode != 0)
{
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
inkCanvas.EditingMode = InkCanvasEditingMode.None;
if (isWaitUntilNextTouchDown && dec.Count > 1) return;
if (dec.Count > 1)
{
// 修复:双曲线绘制时,多指触摸不应该删除第一笔的辅助线
if ((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1)
{
// 第二笔绘制双曲线时,只删除第二笔的临时笔画,保留第一笔的辅助线
try
{
inkCanvas.Strokes.Remove(lastTempStroke);
}
catch { }
return;
}
if (!isTouchDown) return;
if (isWaitUntilNextTouchDown && dec.Count > 1) return;
// 对于多笔图形绘制,允许第二笔绘制,即使dec.Count > 1
if (dec.Count > 1 && !((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1))
{
// 其他情况正常删除临时笔画
try
{
@@ -523,8 +512,18 @@ namespace Ink_Canvas
}
return;
}
// 第二笔绘制双曲线时,只删除第二笔的临时笔画,保留第一笔的辅助线
if ((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1)
{
try
{
inkCanvas.Strokes.Remove(lastTempStroke);
}
catch { }
// 不直接返回,继续执行绘制逻辑
}
// 修复:双曲线绘制时,第二笔应该基于第一笔的起点,而不是触摸实时位置
Point touchPoint = e.GetTouchPoint(inkCanvas).Position;
if ((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1)
{
@@ -585,44 +584,20 @@ namespace Ink_Canvas
{
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
try
{
inkCanvas.Strokes.Remove(lastTempStroke);
}
catch { }
lastTempStroke = stroke;
inkCanvas.Strokes.Add(stroke);
UpdateTempStrokeSafely(stroke);
break;
case 8:
_currentCommitType = CommitReason.ShapeDrawing;
strokes.Add(GenerateDashedLineStrokeCollection(iniP, endP));
try
{
inkCanvas.Strokes.Remove(lastTempStrokeCollection);
}
catch
{
Trace.WriteLine("lastTempStrokeCollection failed.");
}
lastTempStrokeCollection = strokes;
inkCanvas.Strokes.Add(strokes);
UpdateTempStrokeCollectionSafely(strokes);
break;
case 18:
_currentCommitType = CommitReason.ShapeDrawing;
strokes.Add(GenerateDotLineStrokeCollection(iniP, endP));
try
{
inkCanvas.Strokes.Remove(lastTempStrokeCollection);
}
catch
{
Trace.WriteLine("lastTempStrokeCollection failed.");
}
lastTempStrokeCollection = strokes;
inkCanvas.Strokes.Add(strokes);
UpdateTempStrokeCollectionSafely(strokes);
break;
case 2:
_currentCommitType = CommitReason.ShapeDrawing;
@@ -643,14 +618,9 @@ namespace Ink_Canvas
{
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
};
try
{
inkCanvas.Strokes.Remove(lastTempStroke);
}
catch { }
lastTempStroke = stroke;
inkCanvas.Strokes.Add(stroke);
// 优化:使用更安全的临时笔画更新方式,减少闪烁
UpdateTempStrokeSafely(stroke);
break;
case 15:
_currentCommitType = CommitReason.ShapeDrawing;
@@ -1033,7 +1003,6 @@ namespace Ink_Canvas
drawMultiStepShapeSpecialParameter3 = k;
drawMultiStepShapeSpecialStrokeCollection = strokes;
// 修复:第一笔绘制的辅助线应该立即显示在画布上
try
{
inkCanvas.Strokes.Remove(lastTempStrokeCollection);
@@ -1126,7 +1095,6 @@ namespace Ink_Canvas
}
}
// 修复:双曲线绘制完成后,需要将第一笔的辅助线和第二笔的双曲线合并
try
{
// 删除第二笔的临时笔画
@@ -1508,6 +1476,119 @@ namespace Ink_Canvas
private bool isWaitUntilNextTouchDown;
// 添加节流机制,减少更新频率
private DateTime lastUpdateTime = DateTime.MinValue;
private const int UpdateThrottleMs = 16; // 约60fps的更新频率
/// <summary>
/// 安全地更新临时笔画,减少预览闪烁
/// </summary>
/// <param name="newStroke">新的临时笔画</param>
private void UpdateTempStrokeSafely(Stroke newStroke)
{
// 节流机制:限制更新频率
var now = DateTime.Now;
if ((now - lastUpdateTime).TotalMilliseconds < UpdateThrottleMs)
{
return;
}
lastUpdateTime = now;
try
{
// 使用Dispatcher.BeginInvoke确保UI更新在UI线程上执行
Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// 先添加新笔画,再删除旧笔画,减少视觉闪烁
inkCanvas.Strokes.Add(newStroke);
if (lastTempStroke != null && inkCanvas.Strokes.Contains(lastTempStroke))
{
inkCanvas.Strokes.Remove(lastTempStroke);
}
lastTempStroke = newStroke;
}
catch (Exception ex)
{
Debug.WriteLine($"UpdateTempStrokeSafely 失败: {ex.Message}");
// 如果更新失败,确保清理状态
if (lastTempStroke != null && inkCanvas.Strokes.Contains(lastTempStroke))
{
try { inkCanvas.Strokes.Remove(lastTempStroke); } catch { }
}
lastTempStroke = newStroke;
try { inkCanvas.Strokes.Add(newStroke); } catch { }
}
}), DispatcherPriority.Render);
}
catch (Exception ex)
{
Debug.WriteLine($"UpdateTempStrokeSafely Dispatcher 失败: {ex.Message}");
}
}
/// <summary>
/// 安全地更新临时笔画集合,减少预览闪烁
/// </summary>
/// <param name="newStrokeCollection">新的临时笔画集合</param>
private void UpdateTempStrokeCollectionSafely(StrokeCollection newStrokeCollection)
{
// 节流机制:限制更新频率
var now = DateTime.Now;
if ((now - lastUpdateTime).TotalMilliseconds < UpdateThrottleMs)
{
return;
}
lastUpdateTime = now;
try
{
// 使用Dispatcher.BeginInvoke确保UI更新在UI线程上执行
Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// 先添加新笔画集合,再删除旧笔画集合,减少视觉闪烁
inkCanvas.Strokes.Add(newStrokeCollection);
if (lastTempStrokeCollection != null && lastTempStrokeCollection.Count > 0)
{
foreach (var stroke in lastTempStrokeCollection)
{
if (inkCanvas.Strokes.Contains(stroke))
{
inkCanvas.Strokes.Remove(stroke);
}
}
}
lastTempStrokeCollection = newStrokeCollection;
}
catch (Exception ex)
{
Debug.WriteLine($"UpdateTempStrokeCollectionSafely 失败: {ex.Message}");
// 如果更新失败,确保清理状态
if (lastTempStrokeCollection != null && lastTempStrokeCollection.Count > 0)
{
foreach (var stroke in lastTempStrokeCollection)
{
try { inkCanvas.Strokes.Remove(stroke); } catch { }
}
}
lastTempStrokeCollection = newStrokeCollection;
try { inkCanvas.Strokes.Add(newStrokeCollection); } catch { }
}
}), DispatcherPriority.Render);
}
catch (Exception ex)
{
Debug.WriteLine($"UpdateTempStrokeCollectionSafely Dispatcher 失败: {ex.Message}");
}
}
private List<Point> GenerateEllipseGeometry(Point st, Point ed, bool isDrawTop = true,
bool isDrawBottom = true)
{
@@ -1675,6 +1756,7 @@ namespace Ink_Canvas
}
private bool isMouseDown;
private bool isTouchDown;
private void inkCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
@@ -1940,7 +2022,6 @@ namespace Ink_Canvas
private bool NeedUpdateIniP()
{
// 修复:双曲线绘制时,第二笔不应该更新起点,保持第一笔的起点
if (drawingShapeMode == 24 || drawingShapeMode == 25)
{
if (drawMultiStepShapeCurrentStep == 1)
@@ -1987,6 +2068,38 @@ namespace Ink_Canvas
drawingShapeMode = mode;
inkCanvas.EditingMode = InkCanvasEditingMode.None;
SetCursorBasedOnEditingMode(inkCanvas);
ResetAllShapeButtonsOpacity();
}
/// <summary>
/// 重置所有几何绘制按钮的透明度状态
/// </summary>
private void ResetAllShapeButtonsOpacity()
{
try
{
// 重置所有几何绘制按钮的透明度为1(完全不透明)
var buttons = new UIElement[] {
ImageDrawLine, BoardImageDrawLine,
ImageDrawDashedLine, BoardImageDrawDashedLine,
ImageDrawDotLine, BoardImageDrawDotLine,
ImageDrawArrow, BoardImageDrawArrow,
ImageDrawParallelLine, BoardImageDrawParallelLine,
};
foreach (var button in buttons)
{
if (button != null)
{
var dA = new DoubleAnimation(1, 1, new Duration(TimeSpan.FromMilliseconds(0)));
button.BeginAnimation(OpacityProperty, dA);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"重置几何绘制按钮透明度失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
@@ -9,6 +9,7 @@ using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Point = System.Windows.Point;
namespace Ink_Canvas
@@ -63,10 +64,10 @@ namespace Ink_Canvas
var startPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[0].ToPoint() : new Point();
var endPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint() : new Point();
// InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它
if (inkCanvas.Strokes.Contains(e.Stroke))
// 确保InkCanvas保持Ink编辑模式,防止自动切换到鼠标模式
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
{
inkCanvas.Strokes.Remove(e.Stroke);
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 添加到墨迹渐隐管理器
@@ -79,6 +80,30 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile("StrokeCollected: 墨迹渐隐管理器为空,无法添加墨迹", LogHelper.LogType.Error);
}
// 延迟移除墨迹,避免立即移除导致模式切换
// 使用Dispatcher.BeginInvoke确保在UI线程上异步执行
Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// 再次确保InkCanvas保持Ink编辑模式
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 从InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它
if (inkCanvas.Strokes.Contains(e.Stroke))
{
inkCanvas.Strokes.Remove(e.Stroke);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"延迟移除墨迹时出错: {ex}", LogHelper.LogType.Error);
}
}), DispatcherPriority.Background);
// 墨迹渐隐模式下不参与墨迹纠正和其他处理,直接返回
return;
}
@@ -717,16 +742,22 @@ namespace Ink_Canvas
catch { }
// 应用高级贝塞尔曲线平滑(仅在未进行直线拉直时)
Debug.WriteLine($"墨迹平滑检查: UseAdvancedBezierSmoothing={Settings.Canvas.UseAdvancedBezierSmoothing}, wasStraightened={wasStraightened}");
Debug.WriteLine($"异步平滑设置: UseAsyncInkSmoothing={Settings.Canvas.UseAsyncInkSmoothing}, _inkSmoothingManager={_inkSmoothingManager != null}");
if (Settings.Canvas.UseAdvancedBezierSmoothing && !wasStraightened)
{
try
{
Debug.WriteLine($"开始墨迹平滑处理: 原始点数={e.Stroke.StylusPoints.Count}, 直线拉直={wasStraightened}");
// 检查原始笔画是否仍然存在于画布中
if (inkCanvas.Strokes.Contains(e.Stroke))
{
// 使用新的异步墨迹平滑管理器
if (Settings.Canvas.UseAsyncInkSmoothing && _inkSmoothingManager != null)
{
Debug.WriteLine("使用异步墨迹平滑");
// 异步处理
_ = ProcessStrokeAsync(e.Stroke);
}
@@ -746,6 +777,10 @@ namespace Ink_Canvas
}
}
}
else
{
Debug.WriteLine("原始笔画不在画布中,跳过平滑处理");
}
}
catch (Exception ex)
{
@@ -766,17 +801,27 @@ namespace Ink_Canvas
{
try
{
Debug.WriteLine($"异步平滑开始: 原始点数={originalStroke.StylusPoints.Count}");
await _inkSmoothingManager.SmoothStrokeAsync(originalStroke, (original, smoothed) =>
{
Debug.WriteLine($"异步平滑完成: 原始点数={original.StylusPoints.Count}, 平滑后点数={smoothed.StylusPoints.Count}");
Debug.WriteLine($"墨迹比较: smoothed != original = {smoothed != original}");
Debug.WriteLine($"画布包含原始墨迹: {inkCanvas.Strokes.Contains(original)}");
// 在UI线程上执行笔画替换
if (inkCanvas.Strokes.Contains(original) && smoothed != original)
{
Debug.WriteLine("异步替换原始笔画为平滑后的笔画");
SetNewBackupOfStroke();
_currentCommitType = CommitReason.ShapeRecognition;
inkCanvas.Strokes.Remove(original);
inkCanvas.Strokes.Add(smoothed);
_currentCommitType = CommitReason.UserInput;
}
else
{
Debug.WriteLine($"异步平滑后的笔画与原始笔画相同,未进行替换 (contains={inkCanvas.Strokes.Contains(original)}, different={smoothed != original})");
}
});
}
catch (Exception ex)
@@ -815,20 +860,8 @@ namespace Ink_Canvas
// 输出当前灵敏度值(调试用)
Debug.WriteLine($"IsPotentialStraightLine - sensitivity: {sensitivity}, length: {lineLength}");
// 根据灵敏度调整快速检查阈值
double quickThreshold;
// 如果灵敏度超过1.0,使用更宽松的快速检查标准
if (sensitivity > 1.0)
{
// 高灵敏度模式 - 使用更宽松的阈值
quickThreshold = Math.Min(0.2 + (sensitivity - 1.0) * 0.3, 0.5); // 映射到0.2-0.5范围
}
else
{
// 常规灵敏度模式
quickThreshold = Math.Min(sensitivity * 1.5, 0.20);
}
// 将灵敏度转换为阈值:灵敏度0.05-2.0映射到阈值0.01-0.4
double quickThreshold = Math.Max(0.01, sensitivity * 0.2); // 确保最小阈值为0.01
Debug.WriteLine($"使用快速检查阈值: {quickThreshold}");
@@ -854,26 +887,13 @@ namespace Ink_Canvas
// 记录检测到的偏差(调试用)
Debug.WriteLine($"Deviations: q={quarterDeviation}, m={midDeviation}, tq={threeQuarterDeviation}, threshold={quickRelativeThreshold}");
// 如果灵敏度超过1.5,则即使有一个点满足条件也认为可能是直线
if (sensitivity > 1.5)
// 修复后的逻辑:灵敏度越大,容许的偏差越大
// 如果任一点偏离太大,直接排除(使用统一的判断标准)
if (quarterDeviation > quickRelativeThreshold ||
midDeviation > quickRelativeThreshold ||
threeQuarterDeviation > quickRelativeThreshold)
{
// 超高灵敏度模式:只要有一个关键点偏差小,就认为可能是直线
if (quarterDeviation <= quickRelativeThreshold ||
midDeviation <= quickRelativeThreshold ||
threeQuarterDeviation <= quickRelativeThreshold)
{
return true;
}
}
else
{
// 常规判断:如果任一点偏离太大,直接排除
if (quarterDeviation > quickRelativeThreshold ||
midDeviation > quickRelativeThreshold ||
threeQuarterDeviation > quickRelativeThreshold)
{
return false;
}
return false;
}
}
@@ -1308,23 +1328,7 @@ namespace Ink_Canvas
// 支持更广泛的灵敏度范围 (0.05-2.0)
// 如果灵敏度高于1.0,使用更宽松的判断标准
if (sensitivity > 1.0)
{
// 高灵敏度模式 - 允许更大的偏差
double adjustedSensitivity = 0.5 + (sensitivity - 1.0) * 1.5; // 映射到0.5-2.0范围
// 只判断平均偏差和相对偏差
if (maxDeviation / lineLength < adjustedSensitivity && avgDeviation < lineLength * 0.1 * adjustedSensitivity)
{
Debug.WriteLine("接受拉直 (高灵敏度模式)");
return true;
}
Debug.WriteLine("拒绝拉直 (高灵敏度模式)");
return false;
}
// 否则使用常规判断标准
// 移除特殊的高灵敏度模式,使用统一的阈值计算逻辑
// 检查点分布的一致性 - 如果有些点偏离很大而其他点很接近直线,表明线条有明显弯曲
double deviationVariance = 0;
@@ -1417,19 +1421,22 @@ namespace Ink_Canvas
// 输出更多调试信息
Debug.WriteLine($"Deviation variance: {deviationVariance}, Threshold: {sensitivity * lineLength * 0.05}");
// 如果最大偏差超过线长的阈值比例,或者偏差方差较大(表示不均匀弯曲),则不拉直
// 灵敏度越大,容许的偏差越大,更容易将线条识别为直线
if ((maxDeviation / lineLength) > sensitivity)
// 修复灵敏度逻辑:灵敏度越大,容许的偏差越大,更容易将线条识别为直线
// 灵敏度转换为阈值:灵敏度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("拒绝拉直:最大偏差过大");
Debug.WriteLine($"拒绝拉直:最大偏差过大 {maxDeviation / lineLength:F3} > {threshold:F3}");
return false;
}
// 如果偏差方差大,说明线条弯曲不均匀
// 灵敏度越大,容许的偏差方差越大
if (deviationVariance > (sensitivity * lineLength * 0.05))
double varianceThreshold = threshold * lineLength * 0.25; // 调整方差阈值比例
if (deviationVariance > varianceThreshold)
{
Debug.WriteLine("拒绝拉直:偏差方差过大");
Debug.WriteLine($"拒绝拉直:偏差方差过大 {deviationVariance:F3} > {varianceThreshold:F3}");
return false;
}
@@ -1441,13 +1448,14 @@ namespace Ink_Canvas
double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
// 输出中点偏差信息
Debug.WriteLine($"Mid deviation: {midDeviation}, Threshold: {lineLength * sensitivity * 0.8}");
double midThreshold = lineLength * threshold * 0.8;
Debug.WriteLine($"Mid deviation: {midDeviation:F3}, Threshold: {midThreshold:F3}");
// 如果中点偏离过大,不拉直
// 使用灵敏度作为判断基准,灵敏度越大,容许的中点偏离越大
if (midDeviation > (lineLength * sensitivity * 0.8))
// 使用调整后的阈值,灵敏度越大,容许的中点偏离越大
if (midDeviation > midThreshold)
{
Debug.WriteLine("拒绝拉直:中点偏差过大");
Debug.WriteLine($"拒绝拉直:中点偏差过大 {midDeviation:F3} > {midThreshold:F3}");
return false;
}
}
+104 -35
View File
@@ -1,4 +1,4 @@
using Ink_Canvas.Helpers;
using Ink_Canvas.Helpers;
using System;
using System.ComponentModel;
using System.Diagnostics;
@@ -70,6 +70,11 @@ namespace Ink_Canvas
private TimeViewModel nowTimeVM = new TimeViewModel();
private DateTime cachedNetworkTime = DateTime.Now;
private DateTime lastNtpSyncTime = DateTime.MinValue;
private string lastDisplayedTime = "";
private bool useNetworkTime = false;
private TimeSpan networkTimeOffset = TimeSpan.Zero;
private DateTime lastLocalTime = DateTime.Now; // 记录上次的本地时间,用于检测时间跳跃
private bool isNtpSyncing = false; // 防止重复NTP同步的标志
private async Task<DateTime> GetNetworkTimeAsync()
{
@@ -82,7 +87,7 @@ namespace Ink_Canvas
var ipEndPoint = new IPEndPoint(addresses[0], 123);
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
socket.ReceiveTimeout = 2000;
socket.ReceiveTimeout = 5000;
socket.Connect(ipEndPoint);
await Task.Factory.FromAsync(socket.BeginSend(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndSend);
await Task.Factory.FromAsync(socket.BeginReceive(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndReceive);
@@ -94,7 +99,7 @@ namespace Ink_Canvas
var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);
return networkDateTime.ToLocalTime();
}
catch
catch (Exception)
{
return DateTime.Now;
}
@@ -114,7 +119,7 @@ namespace Ink_Canvas
timerCheckAutoUpdateWithSilence.Interval = 1000 * 60 * 10;
WaterMarkTime.DataContext = nowTimeVM;
WaterMarkDate.DataContext = nowTimeVM;
timerDisplayTime.Elapsed += async (s, e) => await TimerDisplayTime_ElapsedAsync();
timerDisplayTime.Elapsed += TimerDisplayTime_Elapsed;
timerDisplayTime.Interval = 1000;
timerDisplayTime.Start();
timerDisplayDate.Elapsed += TimerDisplayDate_Elapsed;
@@ -126,60 +131,124 @@ namespace Ink_Canvas
timerKillProcess.Start();
nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd");
nowTimeVM.nowTime = DateTime.Now.ToString("tt hh'时'mm'分'ss'秒'");
// 程序启动时立即进行一次NTP同步
Task.Run(async () =>
{
try
{
await TimerNtpSync_ElapsedAsync();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"程序启动时NTP同步失败: {ex.Message}", LogHelper.LogType.Error);
}
});
}
// NTP同步定时器事件处理
private async Task TimerNtpSync_ElapsedAsync()
{
// 防止重复同步
if (isNtpSyncing) return;
isNtpSyncing = true;
try
{
DateTime networkTime = await GetNetworkTimeAsync();
// 添加超时机制,最多等待10秒
var timeoutTask = Task.Delay(10000);
var ntpTask = GetNetworkTimeAsync();
var completedTask = await Task.WhenAny(ntpTask, timeoutTask);
if (completedTask == timeoutTask)
{
cachedNetworkTime = DateTime.Now;
lastNtpSyncTime = DateTime.Now;
useNetworkTime = false;
networkTimeOffset = TimeSpan.Zero;
return;
}
DateTime networkTime = await ntpTask;
DateTime localTime = DateTime.Now;
cachedNetworkTime = networkTime;
lastNtpSyncTime = DateTime.Now;
}
catch
lastNtpSyncTime = localTime;
// 计算网络时间与本地时间的偏移量
networkTimeOffset = networkTime - localTime;
// 如果时间差超过3分钟,则使用网络时间
useNetworkTime = Math.Abs(networkTimeOffset.TotalMinutes) > 3.0;
}
catch (Exception ex)
{
// NTP同步失败时,保持使用本地时间
cachedNetworkTime = DateTime.Now;
lastNtpSyncTime = DateTime.Now;
useNetworkTime = false;
networkTimeOffset = TimeSpan.Zero;
LogHelper.WriteLogToFile($"NTP同步失败: {ex.Message}", LogHelper.LogType.Warning);
}
finally
{
isNtpSyncing = false;
}
}
// 修改TimerDisplayTime_ElapsedAsync方法,使用缓存的网络时间
private async Task TimerDisplayTime_ElapsedAsync()
// 优化后的时间显示方法,仅在NTP同步时计算网络时间偏移
private void TimerDisplayTime_Elapsed(object sender, ElapsedEventArgs e)
{
DateTime localTime = DateTime.Now;
DateTime displayTime = localTime; // 默认使用本地时间
// 如果还没有进行过NTP同步,或者距离上次同步超过2小时,则进行一次同步
if (lastNtpSyncTime == DateTime.MinValue ||
(DateTime.Now - lastNtpSyncTime).TotalHours >= 2)
// 检测系统时间是否发生重大跳跃(超过2分钟)
TimeSpan timeJump = localTime - lastLocalTime;
double timeJumpMinutes = Math.Abs(timeJump.TotalMinutes);
if (timeJumpMinutes > 3 && !isNtpSyncing)
{
try
// 系统时间发生重大变化(超过3分钟),立即触发NTP同步
// 使用异步方式触发NTP同步,避免阻塞主线程
Task.Run(async () =>
{
DateTime networkTime = await GetNetworkTimeAsync();
cachedNetworkTime = networkTime;
lastNtpSyncTime = DateTime.Now;
}
catch
{
// 网络时间获取失败时,使用本地时间
cachedNetworkTime = localTime;
}
try
{
await TimerNtpSync_ElapsedAsync();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"时间跳跃触发的NTP同步失败: {ex.Message}", LogHelper.LogType.Error);
}
});
}
lastLocalTime = localTime;
// 如果启用网络时间且偏移量已计算,则应用偏移量
if (useNetworkTime && networkTimeOffset != TimeSpan.Zero)
{
displayTime = localTime + networkTimeOffset;
}
// 使用缓存的网络时间进行显示
TimeSpan timeDifference = cachedNetworkTime - localTime;
double timeDifferenceMinutes = Math.Abs(timeDifference.TotalMinutes);
// 格式化时间字符串
string timeString = displayTime.ToString("tt hh'时'mm'分'ss'秒'");
// 如果网络时间与本地时间相差不超过3分钟,则使用本地时间
// 否则使用网络时间
displayTime = timeDifferenceMinutes <= 3.0 ? localTime : cachedNetworkTime;
// 只更新时间,日期由原有逻辑定时更新即可
Dispatcher.Invoke(() =>
// 只有当时间字符串发生变化时才更新UI,避免不必要的UI刷新
if (timeString != lastDisplayedTime)
{
nowTimeVM.nowTime = displayTime.ToString("tt hh'时'mm'分'ss'秒'");
});
lastDisplayedTime = timeString;
// 使用BeginInvoke异步更新UI,避免阻塞
Dispatcher.BeginInvoke(new Action(() =>
{
nowTimeVM.nowTime = timeString;
}));
}
}
// 修改TimerDisplayDate_Elapsed方法中的日期格式
@@ -748,4 +817,4 @@ namespace Ink_Canvas
}
}
}
}
}
+201 -259
View File
@@ -9,7 +9,6 @@ using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Point = System.Windows.Point;
namespace Ink_Canvas
@@ -23,8 +22,11 @@ namespace Ink_Canvas
private bool isSingleFingerDragMode;
private Point centerPoint = new Point(0, 0);
private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
/// <summary>
private DateTime lastTouchDownTime = DateTime.MinValue;
private const double MULTI_TOUCH_DELAY_MS = 100;
private bool isMultiTouchTimerActive = false;
/// </summary>
/// 保存画布上的非笔画元素(如图片、媒体元素等)
/// </summary>
private List<UIElement> PreserveNonStrokeElements()
@@ -38,7 +40,7 @@ namespace Ink_Canvas
// 保存图片、媒体元素等非笔画相关的UI元素
if (child is Image || child is MediaElement ||
(child is Border border && border.Name != "AdvancedEraserOverlay"))
(child is Border border && border.Name != "EraserOverlayCanvas"))
{
// 创建元素的深拷贝,避免直接引用导致的问题
var clonedElement = CloneUIElement(child);
@@ -241,11 +243,17 @@ namespace Ink_Canvas
HideSubPanels(); // 书写时自动隐藏二级菜单
}
// 修复:几何绘制模式下完全禁止触摸轨迹收集
if (drawingShapeMode != 0)
{
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
inkCanvas.EditingMode = InkCanvasEditingMode.None;
isTouchDown = true;
ViewboxFloatingBar.IsHitTestVisible = false;
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
// 设置起始点
if (NeedUpdateIniP()) iniP = e.GetTouchPoint(inkCanvas).Position;
return;
}
@@ -274,21 +282,26 @@ namespace Ink_Canvas
}
// 新增:根据是否为笔尾自动切换橡皮擦/画笔模式
// 根据是否为笔尾自动切换橡皮擦/画笔模式
if (e.StylusDevice.Inverted)
{
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
}
else
{
// 修复:几何绘制模式下完全禁止触摸轨迹收集
if (drawingShapeMode != 0)
{
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
inkCanvas.EditingMode = InkCanvasEditingMode.None;
isTouchDown = true;
ViewboxFloatingBar.IsHitTestVisible = false;
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
// 设置起始点
if (NeedUpdateIniP()) iniP = e.GetPosition(inkCanvas);
return;
}
// 修复:保持当前的线擦模式,不要强制切换到Ink模式
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
@@ -335,17 +348,56 @@ namespace Ink_Canvas
private async void MainWindow_StylusUp(object sender, StylusEventArgs e)
{
if (drawingShapeMode != 0)
{
// 重置触摸状态
isTouchDown = false;
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// 对于双曲线等需要多步绘制的图形,手写笔抬起时应该进入下一步
if (drawingShapeMode == 24 || drawingShapeMode == 25)
{
if (drawMultiStepShapeCurrentStep == 0)
{
// 第一笔完成,进入第二笔
drawMultiStepShapeCurrentStep = 1;
}
else
{
// 第二笔完成,完成绘制
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
{
RoutedEvent = MouseLeftButtonUpEvent,
Source = inkCanvas
};
inkCanvas_MouseUp(inkCanvas, mouseArgs);
}
}
else
{
// 其他单步绘制的图形,手写笔抬起时完成绘制
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
{
RoutedEvent = MouseLeftButtonUpEvent,
Source = inkCanvas
};
inkCanvas_MouseUp(inkCanvas, mouseArgs);
}
return;
}
try
{
var stroke = GetStrokeVisual(e.StylusDevice.Id).Stroke;
// 正常模式:添加到画布并参与墨迹纠正
inkCanvas.Strokes.Add(stroke);
await Task.Delay(5);
inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id));
inkCanvas_StrokeCollected(inkCanvas,
new InkCanvasStrokeCollectedEventArgs(stroke));
new InkCanvasStrokeCollectedEventArgs(stroke));
}
catch (Exception ex)
{
@@ -384,6 +436,16 @@ namespace Ink_Canvas
{
try
{
if (drawingShapeMode != 0)
{
if (isTouchDown)
{
Point stylusPoint = e.GetPosition(inkCanvas);
MouseTouchMove(stylusPoint);
}
return;
}
if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None) return;
try
{
@@ -473,11 +535,18 @@ namespace Ink_Canvas
dec.Add(e.TouchDevice.Id);
return;
}
// 修复:几何绘制模式下完全禁止触摸轨迹收集
if (drawingShapeMode != 0)
{
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
inkCanvas.EditingMode = InkCanvasEditingMode.None;
// 设置触摸状态,类似鼠标事件处理
isTouchDown = true;
ViewboxFloatingBar.IsHitTestVisible = false;
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
// 设置起始点
if (NeedUpdateIniP()) iniP = e.GetTouchPoint(inkCanvas).Position;
return;
}
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
@@ -500,11 +569,13 @@ namespace Ink_Canvas
private InkCanvasEditingMode palmEraserLastEditingMode = InkCanvasEditingMode.Ink;
private bool palmEraserLastIsHighlighter;
private bool palmEraserWasEnabledBeforeMultiTouch;
private bool palmEraserTouchDownHandled; // 新增:标记手掌擦触摸按下是否已处理
private DateTime palmEraserActivationTime; // 新增:记录手掌擦激活时间
private const int PALM_ERASER_TIMEOUT_MS = 3000; // 修改:减少手掌擦超时时间(3秒)
private DispatcherTimer palmEraserRecoveryTimer; // 新增:手掌擦恢复定时器
private HashSet<int> palmEraserTouchIds = new HashSet<int>(); // 新增:记录参与手掌擦的触摸点ID
public double GetTouchBoundWidth(TouchEventArgs e)
{
var args = e.GetTouchPoint(null).Bounds;
if (!Settings.Advanced.IsQuadIR) return args.Width;
else return Math.Sqrt(args.Width * args.Height); // 四边红外
}
private void inkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
{
@@ -526,18 +597,16 @@ namespace Ink_Canvas
{
return;
}
// 修复:几何绘制模式下完全禁止触摸轨迹收集
if (drawingShapeMode != 0)
{
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹收集
inkCanvas.EditingMode = InkCanvasEditingMode.None;
// 几何绘制模式下不记录触摸点,避免触摸轨迹被收集
SetCursorBasedOnEditingMode(inkCanvas);
inkCanvas.CaptureTouch(e.TouchDevice);
ViewboxFloatingBar.IsHitTestVisible = false;
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
isTouchDown = true;
// 修复:几何绘制模式下,只记录几何绘制的起点,不记录触摸轨迹
if (dec.Count == 0)
{
var inkTouchPoint = e.GetTouchPoint(inkCanvas);
@@ -567,80 +636,67 @@ namespace Ink_Canvas
inkCanvas.CaptureTouch(e.TouchDevice);
ViewboxFloatingBar.IsHitTestVisible = false;
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
lastTouchDownTime = DateTime.Now;
dec.Add(e.TouchDevice.Id);
// Palm Eraser 逻辑 - 优化:改进手掌判定条件,使用设备提供的触摸面积信息
if (Settings.Canvas.EnablePalmEraser && dec.Count >= 2 && !isPalmEraserActive && !palmEraserTouchDownHandled)
// Palm Eraser 逻辑
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive)
{
touchPoint = e.GetTouchPoint(inkCanvas);
var size = touchPoint.Size; // 使用设备提供的触摸面积信息
var bounds = touchPoint.Bounds; // 保留bounds用于宽高比计算
double boundWidth = GetTouchBoundWidth(e);
// 根据敏感度设置调整判定参数
double palmAreaThreshold; // 改为面积阈值
double aspectRatioThreshold;
int minTouchPoints;
switch (Settings.Canvas.PalmEraserSensitivity)
if ((Settings.Advanced.TouchMultiplier != 0 || !Settings.Advanced.IsSpecialScreen)
&& (boundWidth > BoundsWidth))
{
case 0: // 敏感度 - 更严格的判定
palmAreaThreshold = 6400; // 80*80的面积
aspectRatioThreshold = 0.4;
minTouchPoints = 4;
break;
case 1: // 中敏感度 - 平衡的判定
palmAreaThreshold = 3600; // 60*60的面积
aspectRatioThreshold = 0.3;
minTouchPoints = 3;
break;
case 2: // 高敏感度 - 较宽松的判定
default:
palmAreaThreshold = 2500; // 50*50的面积
aspectRatioThreshold = 0.25;
minTouchPoints = 2;
break;
}
// 计算触摸面积(使用设备提供的Size)
double touchArea = size.Width * size.Height;
// 计算宽高比(使用Bounds确保准确性)
double aspectRatio = Math.Min(bounds.Width, bounds.Height) / Math.Max(bounds.Width, bounds.Height);
// 改进的手掌判定条件:使用面积而不是单独的宽高
bool isLargeTouch = touchArea >= palmAreaThreshold;
bool isPalmLikeShape = aspectRatio >= aspectRatioThreshold;
bool hasMultipleTouchPoints = dec.Count >= minTouchPoints;
// 新增:额外的判定条件提高准确性
bool isReasonableSize = size.Width >= 20 && size.Height >= 20 && size.Width <= 200 && size.Height <= 200; // 合理的触摸尺寸范围
bool isNotTooElongated = aspectRatio >= 0.2; // 避免过于细长的触摸(可能是手指)
bool hasEnoughArea = touchArea >= 400; // 最小面积要求,避免小面积误判
if (isLargeTouch && isPalmLikeShape && hasMultipleTouchPoints && isReasonableSize && isNotTooElongated && hasEnoughArea)
{
// 记录当前编辑模式和高光状态
palmEraserLastEditingMode = inkCanvas.EditingMode;
palmEraserLastIsHighlighter = drawingAttributes.IsHighlighter;
// 记录参与手掌擦的触摸点ID
palmEraserTouchIds.Clear();
foreach (int touchId in dec)
// 根据敏感度调整阈值倍数
double thresholdMultiplier;
switch (Settings.Canvas.PalmEraserSensitivity)
{
palmEraserTouchIds.Add(touchId);
case 0: // 低敏感度
thresholdMultiplier = 3.0;
break;
case 1: // 中敏感度
thresholdMultiplier = 2.5;
break;
case 2: // 高敏感度
default:
thresholdMultiplier = 2.0;
break;
}
// 切换为橡皮擦
EraserIcon_Click(null, null);
isPalmEraserActive = true;
palmEraserActivationTime = DateTime.Now; // 记录激活时间
palmEraserTouchDownHandled = true; // 标记已处理
double EraserThresholdValue = Settings.Startup.IsEnableNibMode ?
Settings.Advanced.NibModeBoundsWidthThresholdValue :
Settings.Advanced.FingerModeBoundsWidthThresholdValue;
if (boundWidth > BoundsWidth * EraserThresholdValue * thresholdMultiplier)
{
// 记录当前编辑模式和高光状态
palmEraserLastEditingMode = inkCanvas.EditingMode;
palmEraserLastIsHighlighter = drawingAttributes.IsHighlighter;
// 启动恢复定时器,防止卡死
StartPalmEraserRecoveryTimer();
// 记录日志
LogHelper.WriteLogToFile($"Palm eraser activated - Sensitivity: {Settings.Canvas.PalmEraserSensitivity}, Touch area: {touchArea:F0}, Size: {size.Width}x{size.Height}, Bounds: {bounds.Width}x{bounds.Height}, Aspect ratio: {aspectRatio:F2}, Touch points: {dec.Count}, Reasonable size: {isReasonableSize}, Not elongated: {isNotTooElongated}, Enough area: {hasEnoughArea}");
// 动态调整橡皮大小
boundWidth *= (Settings.Startup.IsEnableNibMode ?
Settings.Advanced.NibModeBoundsWidthEraserSize :
Settings.Advanced.FingerModeBoundsWidthEraserSize);
if (Settings.Advanced.IsSpecialScreen)
boundWidth *= Settings.Advanced.TouchMultiplier;
inkCanvas.EraserShape = new EllipseStylusShape(boundWidth, boundWidth);
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
isPalmEraserActive = true;
// 启用橡皮擦覆盖层显示手掌擦样式
EnableEraserOverlay();
// 更新橡皮擦大小以匹配手掌擦面积
eraserWidth = boundWidth;
UpdateEraserStyle();
// 显示初始橡皮擦反馈位置
touchPoint = e.GetTouchPoint(inkCanvas);
EraserOverlay_PointerDown(sender);
EraserOverlay_PointerMove(sender, touchPoint.Position);
}
}
}
@@ -650,7 +706,6 @@ namespace Ink_Canvas
touchPoint = e.GetTouchPoint(inkCanvas);
centerPoint = touchPoint.Position;
// 修复:只允许在此处赋值iniP,防止TouchMove等其他地方覆盖,保证几何绘制起点一致
if (drawingShapeMode != 0)
{
// 对于双曲线绘制,第一笔时记录起点,第二笔时不更新起点
@@ -679,8 +734,29 @@ namespace Ink_Canvas
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
if (inkCanvas.EditingMode == InkCanvasEditingMode.None ||
inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
var timeSinceLastTouch = (DateTime.Now - lastTouchDownTime).TotalMilliseconds;
if (timeSinceLastTouch < MULTI_TOUCH_DELAY_MS && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
{
if (!isMultiTouchTimerActive)
{
isMultiTouchTimerActive = true;
var remainingTime = MULTI_TOUCH_DELAY_MS - timeSinceLastTouch;
System.Threading.Tasks.Task.Delay((int)remainingTime).ContinueWith(_ =>
{
Dispatcher.Invoke(() =>
{
if (dec.Count > 1 && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
{
inkCanvas.EditingMode = InkCanvasEditingMode.None;
}
isMultiTouchTimerActive = false;
});
});
}
return;
}
lastInkCanvasEditingMode = inkCanvas.EditingMode;
// 修复:几何绘制模式下禁止切回Ink
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
&& drawingShapeMode == 0)
@@ -690,6 +766,17 @@ namespace Ink_Canvas
}
}
private void inkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
{
// 如果手掌擦激活,更新橡皮擦反馈位置
if (isPalmEraserActive)
{
var touchPoint = e.GetTouchPoint(inkCanvas);
EraserOverlay_PointerMove(sender, touchPoint.Position);
}
}
private void inkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
{
// 橡皮状态下不做任何切换,直接return,保证橡皮可持续
@@ -701,24 +788,25 @@ namespace Ink_Canvas
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// Palm Eraser 逻辑:优化状态恢复机制
// Palm Eraser 逻辑
dec.Remove(e.TouchDevice.Id);
// 如果是手掌擦的触摸点,从记录中移除
if (palmEraserTouchIds.Contains(e.TouchDevice.Id))
// 重置多触控点定时器状态
if (dec.Count <= 1)
{
palmEraserTouchIds.Remove(e.TouchDevice.Id);
isMultiTouchTimerActive = false;
}
// 当所有手掌擦触摸点都抬起时,恢复原编辑模式
if (isPalmEraserActive && palmEraserTouchIds.Count == 0)
// 当手掌擦激活且所有触摸点都抬起时,恢复原编辑模式
if (isPalmEraserActive && dec.Count == 0)
{
LogHelper.WriteLogToFile($"Palm eraser recovery triggered - Touch points remaining: {palmEraserTouchIds.Count}, dec.Count: {dec.Count}");
LogHelper.WriteLogToFile($"Palm eraser recovery triggered - Touch points remaining: {dec.Count}");
// 恢复高光状态
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
// 恢复编辑模式 - 优化:改进状态恢复逻辑
// 恢复编辑模式
try
{
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
@@ -749,74 +837,19 @@ namespace Ink_Canvas
// 重置手掌擦状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
palmEraserTouchIds.Clear();
// 停止恢复定时器
StopPalmEraserRecoveryTimer();
// 确保触摸事件能正常响应
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// 禁用橡皮擦覆盖层
DisableEraserOverlay();
LogHelper.WriteLogToFile("Palm eraser state reset completed");
}
// 新增:超时检测 - 如果手掌擦激活时间过长,强制重置状态
if (isPalmEraserActive)
{
var timeSinceActivation = DateTime.Now - palmEraserActivationTime;
if (timeSinceActivation.TotalMilliseconds > PALM_ERASER_TIMEOUT_MS)
{
LogHelper.WriteLogToFile($"Palm eraser timeout detected ({timeSinceActivation.TotalMilliseconds}ms), forcing recovery", LogHelper.LogType.Warning);
// 强制恢复状态
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;
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"Palm eraser timeout recovery failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 重置所有手掌擦状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
palmEraserTouchIds.Clear();
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// 停止恢复定时器
StopPalmEraserRecoveryTimer();
LogHelper.WriteLogToFile("Palm eraser timeout recovery completed");
}
}
// 修复:几何绘制模式下,触摸抬手时应该正确处理,而不是简单模拟鼠标事件
if (drawingShapeMode != 0)
{
isTouchDown = false;
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// 对于双曲线等需要多步绘制的图形,触摸抬手时应该进入下一步
if (drawingShapeMode == 24 || drawingShapeMode == 25)
{
@@ -874,8 +907,7 @@ namespace Ink_Canvas
inkCanvas.EditingMode = lastInkCanvasEditingMode;
}
// 修复:确保手掌擦除后触摸事件能正常响应
if (isPalmEraserActive)
if (isPalmEraserActive)
{
LogHelper.WriteLogToFile("Palm eraser force recovery - all touch points cleared");
@@ -910,16 +942,12 @@ namespace Ink_Canvas
// 如果手掌擦还在激活状态但触摸点已清空,强制重置状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
palmEraserTouchIds.Clear(); // 确保清空触摸点ID
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// 停止恢复定时器
StopPalmEraserRecoveryTimer();
LogHelper.WriteLogToFile("Palm eraser force recovery completed");
}
@@ -947,13 +975,12 @@ namespace Ink_Canvas
private void Main_Grid_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
if (e.Manipulators.Count() != 0) return;
// 修复:几何绘制模式下不自动切换到Ink模式,避免触摸轨迹被收集
if (drawingShapeMode == 0
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
// 修复:确保多指手势完成后正确更新lastInkCanvasEditingMode
lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
}
}
@@ -966,8 +993,13 @@ namespace Ink_Canvas
// 三指及以上禁止缩放
bool disableScale = dec.Count >= 3;
// 修复:允许单指拖动选中的墨迹,即使禁用了多指手势
if (isInMultiTouchMode) return;
if (dec.Count == 0 && (isSingleFingerDragMode || isInMultiTouchMode))
{
ResetTouchStates();
return;
}
// 如果是单指拖动选中的墨迹,允许处理
if (dec.Count == 1 && inkCanvas.GetSelectedStrokes().Count > 0)
@@ -1042,13 +1074,6 @@ namespace Ink_Canvas
break;
}
if (!Settings.Gesture.IsEnableTwoFingerZoom) continue;
try
{
stroke.DrawingAttributes.Width *= md.Scale.X;
stroke.DrawingAttributes.Height *= md.Scale.Y;
}
catch { }
}
}
else
@@ -1182,7 +1207,6 @@ namespace Ink_Canvas
inkCanvas.StylusUp -= MainWindow_StylusUp;
inkCanvas.TouchDown -= MainWindow_TouchDown;
inkCanvas.TouchDown += Main_Grid_TouchDown;
// 修复:几何绘制模式下不自动切换到Ink模式,避免触摸轨迹被收集
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
&& drawingShapeMode == 0)
@@ -1215,7 +1239,6 @@ namespace Ink_Canvas
inkCanvas.StylusUp += MainWindow_StylusUp;
inkCanvas.TouchDown += MainWindow_TouchDown;
inkCanvas.TouchDown -= Main_Grid_TouchDown;
// 修复:几何绘制模式下不自动切换到Ink模式,避免触摸轨迹被收集
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
&& drawingShapeMode == 0)
@@ -1236,87 +1259,6 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 启动手掌擦恢复定时器,防止卡死状态
/// </summary>
private void StartPalmEraserRecoveryTimer()
{
if (palmEraserRecoveryTimer == null)
{
palmEraserRecoveryTimer = new DispatcherTimer();
palmEraserRecoveryTimer.Interval = TimeSpan.FromMilliseconds(1000); // 每秒检查一次
palmEraserRecoveryTimer.Tick += PalmEraserRecoveryTimer_Tick;
}
palmEraserRecoveryTimer.Start();
}
/// <summary>
/// 停止手掌擦恢复定时器
/// </summary>
private void StopPalmEraserRecoveryTimer()
{
if (palmEraserRecoveryTimer != null)
{
palmEraserRecoveryTimer.Stop();
}
}
/// <summary>
/// 手掌擦恢复定时器事件处理
/// </summary>
private void PalmEraserRecoveryTimer_Tick(object sender, EventArgs e)
{
if (!isPalmEraserActive) return;
// 检查是否超时
var timeSinceActivation = DateTime.Now - palmEraserActivationTime;
if (timeSinceActivation.TotalMilliseconds > PALM_ERASER_TIMEOUT_MS)
{
LogHelper.WriteLogToFile($"Palm eraser recovery timer triggered, forcing recovery after {timeSinceActivation.TotalMilliseconds}ms", LogHelper.LogType.Warning);
// 强制恢复状态
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 timer recovery to mode: {palmEraserLastEditingMode}");
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"Palm eraser recovery timer failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 重置所有手掌擦状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
palmEraserTouchIds.Clear();
inkCanvas.IsHitTestVisible = true;
inkCanvas.IsManipulationEnabled = true;
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// 停止定时器
StopPalmEraserRecoveryTimer();
LogHelper.WriteLogToFile("Palm eraser timer recovery completed");
}
}
}
}
+2 -2
View File
@@ -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.11.0")]
[assembly: AssemblyFileVersion("1.7.11.0")]
[assembly: AssemblyVersion("1.7.11.4")]
[assembly: AssemblyFileVersion("1.7.11.4")]
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

+53 -8
View File
@@ -99,7 +99,7 @@ namespace Ink_Canvas
// 墨迹渐隐功能设置
[JsonProperty("enableInkFade")]
public bool EnableInkFade { get; set; } // 是否启用墨迹渐隐功能
public bool EnableInkFade { get; set; } = false;
[JsonProperty("inkFadeTime")]
public int InkFadeTime { get; set; } = 3000; // 墨迹渐隐时间(毫秒)
@@ -209,6 +209,8 @@ namespace Ink_Canvas
public int Theme { get; set; }
// 浮动栏按钮显示控制
[JsonProperty("useLegacyFloatingBarUI")]
public bool UseLegacyFloatingBarUI { get; set; } = false;
[JsonProperty("isShowShapeButton")]
public bool IsShowShapeButton { get; set; } = true;
[JsonProperty("isShowUndoButton")]
@@ -339,6 +341,9 @@ namespace Ink_Canvas
[JsonProperty("isAutoEnterAnnotationModeWhenExitFoldMode")]
public bool IsAutoEnterAnnotationModeWhenExitFoldMode { get; set; }
[JsonProperty("isAutoFoldWhenExitWhiteboard")]
public bool IsAutoFoldWhenExitWhiteboard { get; set; }
[JsonProperty("isAutoFoldInEasiNote")]
public bool IsAutoFoldInEasiNote { get; set; }
@@ -467,7 +472,13 @@ namespace Ink_Canvas
public bool IsEnabled { get; set; } = false;
[JsonProperty("scanIntervalMs")]
public int ScanIntervalMs { get; set; } = 1000;
public int ScanIntervalMs { get; set; } = 5000;
[JsonProperty("autoStart")]
public bool AutoStart { get; set; } = false;
[JsonProperty("showNotifications")]
public bool ShowNotifications { get; set; } = true;
[JsonProperty("interceptRules")]
public Dictionary<string, bool> InterceptRules { get; set; } = new Dictionary<string, bool>
@@ -477,21 +488,28 @@ namespace Ink_Canvas
{ "SeewoWhiteboard5CFloating", true },
{ "SeewoPincoSideBarFloating", true },
{ "SeewoPincoDrawingFloating", true },
{ "SeewoPincoBoardService", true },
{ "SeewoPPTFloating", true },
{ "AiClassFloating", true },
{ "HiteAnnotationFloating", true },
{ "ChangYanFloating", true },
{ "ChangYanBrushSettings", true },
{ "ChangYanSwipeClear", true },
{ "ChangYanInteraction", true },
{ "ChangYanSubjectApp", true },
{ "ChangYanControl", true },
{ "ChangYanCommonTools", true },
{ "ChangYanSceneToolbar", true },
{ "ChangYanDrawWindow", true },
{ "ChangYanPptFloating", true },
{ "ChangYanPptPageControl", true },
{ "ChangYanPptGoBack", true },
{ "ChangYanPptPreview", true },
{ "IntelligentClassFloating", true },
{ "IntelligentClassPptFloating", true },
{ "SeewoDesktopAnnotationFloating", true },
{ "SeewoDesktopSideBarFloating", true }
};
[JsonProperty("autoStart")]
public bool AutoStart { get; set; } = false;
[JsonProperty("showNotifications")]
public bool ShowNotifications { get; set; } = true;
}
public class Advanced
@@ -511,6 +529,18 @@ namespace Ink_Canvas
[JsonProperty("fingerModeBoundsWidth")]
public int FingerModeBoundsWidth { get; set; } = 30;
[JsonProperty("nibModeBoundsWidthThresholdValue")]
public double NibModeBoundsWidthThresholdValue { get; set; } = 2.5;
[JsonProperty("fingerModeBoundsWidthThresholdValue")]
public double FingerModeBoundsWidthThresholdValue { get; set; } = 2.5;
[JsonProperty("nibModeBoundsWidthEraserSize")]
public double NibModeBoundsWidthEraserSize { get; set; } = 0.8;
[JsonProperty("fingerModeBoundsWidthEraserSize")]
public double FingerModeBoundsWidthEraserSize { get; set; } = 0.8;
[JsonProperty("eraserBindTouchMultiplier")]
public bool EraserBindTouchMultiplier { get; set; }
@@ -547,6 +577,15 @@ namespace Ink_Canvas
[JsonProperty("isAutoBackupBeforeUpdate")]
public bool IsAutoBackupBeforeUpdate { get; set; } = true;
[JsonProperty("isAutoBackupEnabled")]
public bool IsAutoBackupEnabled { get; set; } = true;
[JsonProperty("autoBackupIntervalDays")]
public int AutoBackupIntervalDays { get; set; } = 7;
[JsonProperty("lastAutoBackupTime")]
public DateTime LastAutoBackupTime { get; set; } = DateTime.MinValue;
[JsonProperty("isNoFocusMode")]
public bool IsNoFocusMode { get; set; } = true;
@@ -590,6 +629,12 @@ namespace Ink_Canvas
public int SelectedBackgroundIndex { get; set; }
[JsonProperty("customPickNameBackgrounds")]
public List<CustomPickNameBackground> CustomPickNameBackgrounds { get; set; } = new List<CustomPickNameBackground>();
[JsonProperty("useLegacyTimerUI")]
public bool UseLegacyTimerUI { get; set; } = false;
[JsonProperty("timerVolume")]
public double TimerVolume { get; set; } = 1.0;
[JsonProperty("customTimerSoundPath")]
public string CustomTimerSoundPath { get; set; } = "";
}
public class CustomPickNameBackground
+48 -5
View File
@@ -1,7 +1,50 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="FloatBarBackground" Color="Black" Opacity="0.5"/>
<SolidColorBrush x:Key="FloatBarBorderBrush" Color="White" Opacity="0.5"/>
<SolidColorBrush x:Key="FloatBarForeground" Color="White"/>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="FloatBarBackground" Opacity="0.6" Color="Black" />
<SolidColorBrush x:Key="FloatBarBackgroundWithoutOpacity" Opacity="0.95" Color="Black" />
<SolidColorBrush x:Key="FloatBarTitleBackground" Opacity="0.5" Color="#2156c9" />
<SolidColorBrush x:Key="FloatBarBorderBrush" Opacity="0.6" Color="White" />
<SolidColorBrush x:Key="FloatBarForeground" Color="White" />
<Color x:Key="FloatBarForegroundColor">#FFcccccc</Color>
<SolidColorBrush x:Key="SettingsPageForeground" Color="White" />
<SolidColorBrush x:Key="SettingsPageAnnotationForeground" Opacity="0.7" Color="White" />
<SolidColorBrush x:Key="SettingsPageBorderBrush" Opacity="0.8" Color="White" />
<SolidColorBrush x:Key="SettingsPageBackground" Opacity="0.95" Color="#1f1f1f" />
<SolidColorBrush x:Key="IconForeground" Color="White" />
<SolidColorBrush x:Key="TextForeground" Color="White" />
<SolidColorBrush x:Key="RedBrush" Color="IndianRed" />
<SolidColorBrush x:Key="PurpleBrush" Color="MediumPurple" />
<Color x:Key="FloatBarButtonBackgroundKey">Transparent</Color>
<Color x:Key="FloatBarButtonBackgroundPointerOverKey">#2200CDCD</Color>
<Color x:Key="FloatBarButtonButtonBackgroundPressedKey">#4400CDCD</Color>
<!-- 白板模式浮动栏颜色 -->
<SolidColorBrush x:Key="BoardFloatBarBackground" Color="#2a2a2a" />
<SolidColorBrush x:Key="BoardFloatBarBorderBrush" Color="#555555" />
<!-- 白板模式按钮选中状态颜色 -->
<SolidColorBrush x:Key="BoardFloatBarSelectedBackground" Color="#2563eb" />
<SolidColorBrush x:Key="BoardFloatBarSelectedBorderBrush" Color="#2563eb" />
<SolidColorBrush x:Key="BoardFloatBarSelectedForeground" Color="White" />
<!-- 历史回滚窗口主题颜色 -->
<SolidColorBrush x:Key="HistoryWindowPrimaryBrush" Color="#3b82f6"/>
<SolidColorBrush x:Key="HistoryWindowPrimaryHoverBrush" Color="#2563eb"/>
<SolidColorBrush x:Key="HistoryWindowPrimaryPressedBrush" Color="#1d4ed8"/>
<SolidColorBrush x:Key="HistoryWindowCardBackgroundBrush" Color="#2a2a2a"/>
<SolidColorBrush x:Key="HistoryWindowCardBorderBrush" Color="#404040"/>
<SolidColorBrush x:Key="HistoryWindowTextPrimaryBrush" Color="#f9fafb"/>
<SolidColorBrush x:Key="HistoryWindowTextSecondaryBrush" Color="#9ca3af"/>
<!-- 快速面板图标资源 - 深色主题 -->
<BitmapImage x:Key="QuickPanelPersonMoneyIcon" UriSource="/Resources/Icons-Fluent/ic_fluent_person_money_24_regular-light.png"/>
<BitmapImage x:Key="QuickPanelPeopleMoneyIcon" UriSource="/Resources/Icons-Fluent/ic_fluent_people_money_24_regular-light.png"/>
<BitmapImage x:Key="QuickPanelTimerIcon" UriSource="/Resources/Icons-Fluent/ic_fluent_timer_24_regular-light.png"/>
<BitmapImage x:Key="QuickPanelBlackboardIcon" UriSource="/Resources/new-icons/blackboard-light.png"/>
<BitmapImage x:Key="QuickPanelEndSlideshowIcon" UriSource="/Resources/new-icons/end-slides-show-light.png"/>
<BitmapImage x:Key="QuickPanelEyeIcon" UriSource="/Resources/new-icons/eye-light.png"/>
<BitmapImage x:Key="QuickPanelChevronLeftIcon" UriSource="/Resources/new-icons/chevron-left-light.png"/>
</ResourceDictionary>
+49 -6
View File
@@ -1,7 +1,50 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="FloatBarBackground" Color="#fafafa" Opacity="0.95"/>
<SolidColorBrush x:Key="FloatBarBorderBrush" Color="#52525b" Opacity="0.6"/>
<SolidColorBrush x:Key="FloatBarForeground" Color="#18181b"/>
<Color x:Key="FloatBarForegroundColor">#18181b</Color>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="FloatBarBackground" Opacity="1.0" Color="White" />
<SolidColorBrush x:Key="FloatBarBackgroundWithoutOpacity" Opacity="1.0" Color="White" />
<SolidColorBrush x:Key="FloatBarTitleBackground" Opacity="0.25" Color="#588bfc" />
<SolidColorBrush x:Key="FloatBarBorderBrush" Opacity="0.8" Color="Black" />
<SolidColorBrush x:Key="FloatBarForeground" Color="Black" />
<Color x:Key="FloatBarForegroundColor">#FF000000</Color>
<SolidColorBrush x:Key="SettingsPageForeground" Color="Black" />
<SolidColorBrush x:Key="SettingsPageAnnotationForeground" Color="#666666" />
<SolidColorBrush x:Key="SettingsPageBorderBrush" Opacity="0.8" Color="Black" />
<SolidColorBrush x:Key="SettingsPageBackground" Opacity="0.95" Color="White" />
<SolidColorBrush x:Key="IconForeground" Color="#18181b" />
<SolidColorBrush x:Key="TextForeground" Color="Black" />
<SolidColorBrush x:Key="RedBrush" Color="DarkRed" />
<SolidColorBrush x:Key="PurpleBrush" Color="DarkBlue" />
<Color x:Key="FloatBarButtonBackgroundKey">Transparent</Color>
<Color x:Key="FloatBarButtonBackgroundPointerOverKey">#66FFFFFF</Color>
<Color x:Key="FloatBarButtonButtonBackgroundPressedKey">#99FFFFFF</Color>
<!-- 白板模式浮动栏颜色 -->
<SolidColorBrush x:Key="BoardFloatBarBackground" Color="#f4f4f5" />
<SolidColorBrush x:Key="BoardFloatBarBorderBrush" Color="#a1a1aa" />
<!-- 白板模式按钮选中状态颜色 -->
<SolidColorBrush x:Key="BoardFloatBarSelectedBackground" Color="#2563eb" />
<SolidColorBrush x:Key="BoardFloatBarSelectedBorderBrush" Color="#2563eb" />
<SolidColorBrush x:Key="BoardFloatBarSelectedForeground" Color="White" />
<!-- 历史回滚窗口主题颜色 -->
<SolidColorBrush x:Key="HistoryWindowPrimaryBrush" Color="#2563eb"/>
<SolidColorBrush x:Key="HistoryWindowPrimaryHoverBrush" Color="#1d4ed8"/>
<SolidColorBrush x:Key="HistoryWindowPrimaryPressedBrush" Color="#1e40af"/>
<SolidColorBrush x:Key="HistoryWindowCardBackgroundBrush" Color="#f8fafc"/>
<SolidColorBrush x:Key="HistoryWindowCardBorderBrush" Color="#e5e7eb"/>
<SolidColorBrush x:Key="HistoryWindowTextPrimaryBrush" Color="#1f2937"/>
<SolidColorBrush x:Key="HistoryWindowTextSecondaryBrush" Color="#6b7280"/>
<!-- 快速面板图标资源 - 浅色主题 -->
<BitmapImage x:Key="QuickPanelPersonMoneyIcon" UriSource="/Resources/Icons-Fluent/ic_fluent_person_money_24_regular.png"/>
<BitmapImage x:Key="QuickPanelPeopleMoneyIcon" UriSource="/Resources/Icons-Fluent/ic_fluent_people_money_24_regular.png"/>
<BitmapImage x:Key="QuickPanelTimerIcon" UriSource="/Resources/Icons-Fluent/ic_fluent_timer_24_regular.png"/>
<BitmapImage x:Key="QuickPanelBlackboardIcon" UriSource="/Resources/new-icons/blackboard.png"/>
<BitmapImage x:Key="QuickPanelEndSlideshowIcon" UriSource="/Resources/new-icons/end-slides-show.png"/>
<BitmapImage x:Key="QuickPanelEyeIcon" UriSource="/Resources/new-icons/eye.png"/>
<BitmapImage x:Key="QuickPanelChevronLeftIcon" UriSource="/Resources/new-icons/chevron-left.png"/>
</ResourceDictionary>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+13 -12
View File
@@ -44,18 +44,18 @@
<Grid Name="GridAdjustHour" Visibility="Visible" Margin="-29,-30,0,-30" Width="29">
<ui:SimpleStackPanel Spacing="2">
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_1">
<TextBlock Text="+5" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="HourPlus5Text" Text="∧∧" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click">
<TextBlock Text="+1" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="HourPlus1Text" Text="" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Spacing="2" VerticalAlignment="Bottom">
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_2">
<TextBlock Text="-1" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="HourMinus1Text" Text="" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_3">
<TextBlock Text="-5" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="HourMinus5Text" Text="∨∨" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</ui:SimpleStackPanel>
</Grid>
@@ -91,18 +91,18 @@
<Grid Visibility="{Binding ElementName=GridAdjustHour, Path=Visibility}" Margin="-29,-30,0,-30" Width="29">
<ui:SimpleStackPanel Spacing="2">
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_5">
<TextBlock Text="+5" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="MinutePlus5Text" Text="∧∧" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_4">
<TextBlock Text="+1" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="MinutePlus1Text" Text="" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Spacing="2" VerticalAlignment="Bottom">
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_6">
<TextBlock Text="-1" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="MinuteMinus1Text" Text="" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_7">
<TextBlock Text="-5" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="MinuteMinus5Text" Text="∨∨" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</ui:SimpleStackPanel>
<Border x:Name="BtnTimeSetOkay" MouseUp="Grid_MouseUp"
@@ -151,18 +151,18 @@
<Grid Visibility="{Binding ElementName=GridAdjustHour, Path=Visibility}" Margin="-29,-30,0,-30" Width="29">
<ui:SimpleStackPanel Spacing="2">
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_8">
<TextBlock Text="+5" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="SecondPlus5Text" Text="∧∧" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_9">
<TextBlock Text="+1" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="SecondPlus1Text" Text="" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Spacing="2" VerticalAlignment="Bottom">
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_10">
<TextBlock Text="-1" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="SecondMinus1Text" Text="" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button Height="13" Width="{Binding ElementName=GridAdjustHour, Path=ActualWidth}" Click="Button_Click_11">
<TextBlock Text="-5" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="SecondMinus5Text" Text="∨∨" Margin="-10" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</ui:SimpleStackPanel>
</Grid>
@@ -244,3 +244,4 @@
</Grid>
</Border>
</Window>
@@ -1,4 +1,5 @@
using Ink_Canvas.Helpers;
using Ink_Canvas.Resources;
using System;
using System.Media;
using System.Timers;
@@ -6,6 +7,7 @@ using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Ink_Canvas
{
@@ -21,6 +23,7 @@ namespace Ink_Canvas
timer.Elapsed += Timer_Elapsed;
timer.Interval = 50;
InitializeUI();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
@@ -62,13 +65,13 @@ namespace Ink_Canvas
Application.Current.Dispatcher.Invoke(() =>
{
//Play sound
player.Stream = Properties.Resources.TimerDownNotice;
player.Play();
PlayTimerSound();
});
}
}
SoundPlayer player = new SoundPlayer();
MediaPlayer mediaPlayer = new MediaPlayer();
int hour = 0;
int minute = 1;
@@ -80,6 +83,7 @@ namespace Ink_Canvas
bool isTimerRunning = false;
bool isPaused = false;
bool useLegacyUI = false;
Timer timer = new Timer();
@@ -334,6 +338,97 @@ namespace Ink_Canvas
}
}
private void InitializeUI()
{
// 从设置中读取配置
if (MainWindow.Settings.RandSettings != null)
{
useLegacyUI = MainWindow.Settings.RandSettings.UseLegacyTimerUI;
UpdateButtonTexts();
}
}
public void RefreshUI()
{
InitializeUI();
}
private void UpdateButtonTexts()
{
if (useLegacyUI)
{
// 老版UI:使用+5, +1, -1, -5
HourPlus5Text.Text = "+5";
HourPlus1Text.Text = "+1";
HourMinus1Text.Text = "-1";
HourMinus5Text.Text = "-5";
MinutePlus5Text.Text = "+5";
MinutePlus1Text.Text = "+1";
MinuteMinus1Text.Text = "-1";
MinuteMinus5Text.Text = "-5";
SecondPlus5Text.Text = "+5";
SecondPlus1Text.Text = "+1";
SecondMinus1Text.Text = "-1";
SecondMinus5Text.Text = "-5";
}
else
{
// 新版UI:使用箭头符号
HourPlus5Text.Text = "∧∧";
HourPlus1Text.Text = "∧";
HourMinus1Text.Text = "";
HourMinus5Text.Text = "∨∨";
MinutePlus5Text.Text = "∧∧";
MinutePlus1Text.Text = "∧";
MinuteMinus1Text.Text = "";
MinuteMinus5Text.Text = "∨∨";
SecondPlus5Text.Text = "∧∧";
SecondPlus1Text.Text = "∧";
SecondMinus1Text.Text = "";
SecondMinus5Text.Text = "∨∨";
}
}
private void PlayTimerSound()
{
try
{
double volume = MainWindow.Settings.RandSettings?.TimerVolume ?? 1.0;
mediaPlayer.Volume = volume;
if (!string.IsNullOrEmpty(MainWindow.Settings.RandSettings?.CustomTimerSoundPath) &&
System.IO.File.Exists(MainWindow.Settings.RandSettings.CustomTimerSoundPath))
{
// 播放自定义铃声
mediaPlayer.Open(new Uri(MainWindow.Settings.RandSettings.CustomTimerSoundPath));
}
else
{
// 播放默认铃声
string tempPath = System.IO.Path.GetTempFileName() + ".wav";
using (var stream = Properties.Resources.TimerDownNotice)
{
using (var fileStream = new System.IO.FileStream(tempPath, System.IO.FileMode.Create))
{
stream.CopyTo(fileStream);
}
}
mediaPlayer.Open(new Uri(tempPath));
}
mediaPlayer.Play();
}
catch (Exception ex)
{
// 如果播放失败,静默处理
System.Diagnostics.Debug.WriteLine($"播放计时器铃声失败: {ex.Message}");
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
isTimerRunning = false;
+302 -47
View File
@@ -7,27 +7,247 @@
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:mdxam="clr-namespace:MdXaml;assembly=MdXaml"
mc:Ignorable="d"
ui:WindowHelper.UseModernWindowStyle = "True"
ui:WindowHelper.UseModernWindowStyle="False"
ui:WindowHelper.SystemBackdropType="Mica"
ui:TitleBar.Height="36"
Title="InkCanvasForClass CE有新版本可用" Height="600" Width="850" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen">
Title="InkCanvasForClass CE有新版本可用" Height="650" Width="900" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen" WindowStyle="None"
Background="Transparent">
<Window.Template>
<ControlTemplate TargetType="Window">
<Border Background="{DynamicResource SettingsPageBackground}"
CornerRadius="8"
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect Color="#000000" BlurRadius="20" ShadowDepth="0" Opacity="0.1"/>
</Border.Effect>
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Template>
<Window.Resources>
<!-- 主题相关颜色资源 -->
<SolidColorBrush x:Key="UpdateWindowPrimaryBrush" Color="#2563eb"/>
<SolidColorBrush x:Key="UpdateWindowPrimaryHoverBrush" Color="#1d4ed8"/>
<SolidColorBrush x:Key="UpdateWindowPrimaryPressedBrush" Color="#1e40af"/>
<SolidColorBrush x:Key="UpdateWindowCardBackgroundBrush" Color="#f8fafc"/>
<SolidColorBrush x:Key="UpdateWindowCardBorderBrush" Color="#e5e7eb"/>
<SolidColorBrush x:Key="UpdateWindowTextPrimaryBrush" Color="#1f2937"/>
<SolidColorBrush x:Key="UpdateWindowTextSecondaryBrush" Color="#6b7280"/>
<SolidColorBrush x:Key="UpdateWindowCloseButtonBrush" Color="#666666"/>
<!-- 渐变背景定义 -->
<LinearGradientBrush x:Key="HeaderGradient" StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#2563eb" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
<!-- 现代按钮样式 -->
<Style x:Key="ModernPrimaryButton" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource UpdateWindowPrimaryBrush}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="20,10"/>
<Setter Property="Height" Value="40"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="6"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource UpdateWindowPrimaryHoverBrush}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{DynamicResource UpdateWindowPrimaryPressedBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ModernSecondaryButton" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource SettingsPageBackground}"/>
<Setter Property="Foreground" Value="{DynamicResource UpdateWindowTextPrimaryBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource SettingsPageBorderBrush}"/>
<Setter Property="FontWeight" Value="Medium"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="20,10"/>
<Setter Property="Height" Value="40"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="6"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.2"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ModernTertiaryButton" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{DynamicResource UpdateWindowTextSecondaryBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontWeight" Value="Medium"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="20,10"/>
<Setter Property="Height" Value="40"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="6"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.1"/>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="{DynamicResource UpdateWindowTextPrimaryBrush}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.2"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Grid Background="#fafafa" Margin="0,0,0,30">
<Grid Margin="0,0,0,0" Background="Transparent">
<ui:SimpleStackPanel VerticalAlignment="Stretch" Spacing="0">
<!-- 标题栏 -->
<ui:SimpleStackPanel Orientation="Horizontal" Background="#2563eb" Margin="0,0,0,0">
<ui:SimpleStackPanel Orientation="Vertical" Width="735" Margin="24,18,0,12" Spacing="2">
<TextBlock Text="InkCanvasForClass CE新版本来了!" FontSize="24" FontWeight="Bold" Foreground="White" TextAlignment="Left"/>
<TextBlock Text="希望您能喜欢我们的新版本 :-)" FontSize="20" TextAlignment="Left" Foreground="White"/>
</ui:SimpleStackPanel>
<Image Source="/Resources/Icons-fluent/party.png" Width="72" Height="72"/>
</ui:SimpleStackPanel>
<!-- 统一的主容器 -->
<Border Background="{DynamicResource SettingsPageBackground}" CornerRadius="8" Margin="0,0,0,0"
BorderThickness="1" BorderBrush="{DynamicResource SettingsPageBorderBrush}" Padding="0">
<ui:SimpleStackPanel Spacing="0">
<!-- 现代化标题栏 -->
<Border Background="{StaticResource HeaderGradient}" CornerRadius="8,8,0,0" Margin="0,0,0,0"
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
<Grid>
<!-- 标题栏内容 -->
<ui:SimpleStackPanel Orientation="Horizontal" Margin="32,24,80,24" Spacing="16">
<Border Background="White" CornerRadius="16" Padding="16" Margin="0,0,0,0">
<Image Source="/Resources/Icons-fluent/party.png" Width="24" Height="24"/>
</Border>
<ui:SimpleStackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="新版本发布" FontSize="24" FontWeight="Bold" Foreground="{DynamicResource UpdateWindowTextPrimaryBrush}" TextAlignment="Left"/>
<TextBlock Text="InkCanvasForClass CE 为您带来全新体验" FontSize="14" TextAlignment="Left" Foreground="{DynamicResource UpdateWindowTextSecondaryBrush}"/>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
<!-- 更新内容 -->
<Border BorderBrush="#3f3f46" Background="White" BorderThickness="1" CornerRadius="4" Margin="24,16,24,0">
<ui:ScrollViewerEx Margin="0" VerticalScrollBarVisibility="Auto" Height="180" PanningMode="VerticalOnly">
<mdxam:MarkdownScrollViewer x:Name="markdownContent" xml:space="preserve" Foreground="Black" MarkdownStyleName="GithubLike">
<!-- 自定义关闭按钮 -->
<Button x:Name="CloseButton"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,12,12,0"
Width="32" Height="32"
Background="Transparent"
BorderThickness="0"
Click="CloseButton_Click"
Cursor="Hand"
ToolTip="关闭">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="16"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<Path Data="M8,8 L16,16 M16,8 L8,16"
Stroke="{DynamicResource UpdateWindowCloseButtonBrush}"
StrokeThickness="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.2"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Border>
<!-- 内容标题 -->
<Border Background="{DynamicResource UpdateWindowCardBackgroundBrush}" Padding="24,20,24,16">
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="12">
<Border Background="{DynamicResource UpdateWindowPrimaryBrush}" CornerRadius="8" Padding="8">
<TextBlock Text="📝" FontSize="16" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ui:SimpleStackPanel Orientation="Vertical" Spacing="2">
<TextBlock Text="更新内容" FontSize="18" FontWeight="SemiBold" Foreground="{DynamicResource UpdateWindowTextPrimaryBrush}"/>
<TextBlock Text="查看本次更新的详细内容" FontSize="14" Foreground="{DynamicResource UpdateWindowTextSecondaryBrush}"/>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
</Border>
<!-- 内容区域 -->
<ui:ScrollViewerEx Margin="0" VerticalScrollBarVisibility="Auto" Height="200" PanningMode="VerticalOnly">
<mdxam:MarkdownScrollViewer x:Name="markdownContent" xml:space="preserve"
Foreground="{DynamicResource UpdateWindowTextPrimaryBrush}" MarkdownStyleName="GithubLike"
Margin="24,16,24,16">
# InkCanvasForClass v5.0.2更新
你好,旅行者们,本次InkCanvasForClass Community Edition更新带来了如下新功能供您探索:
@@ -42,48 +262,83 @@
8. 带来了基于FitToCurve的笔迹平滑,基于贝塞尔曲线平滑,让墨迹线条更加优美好看。
</mdxam:MarkdownScrollViewer>
</ui:ScrollViewerEx>
</ui:SimpleStackPanel>
</Border>
<!-- 版本信息 -->
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="16" Margin="0,16,0,0">
<TextBlock x:Name="updateVersionInfo" Text="本次更新: 4.9.1 -> 5.9.1" FontWeight="Bold" FontSize="15" TextAlignment="Center"/>
<TextBlock x:Name="updateDateInfo" Text="2024年8月4日发布更新" FontSize="15" TextAlignment="Center"/>
</ui:SimpleStackPanel>
<!-- 现代化版本信息 -->
<Border Background="{DynamicResource UpdateWindowCardBackgroundBrush}" Margin="0,0,0,0" Padding="24,20">
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="24">
<ui:SimpleStackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Center">
<TextBlock Text="版本信息" FontSize="14" FontWeight="Medium" Foreground="{DynamicResource UpdateWindowTextSecondaryBrush}"/>
<TextBlock x:Name="updateVersionInfo" Text="本次更新: 4.9.1 -> 5.9.1"
FontWeight="SemiBold" FontSize="16" Foreground="{DynamicResource UpdateWindowTextPrimaryBrush}" TextAlignment="Center"/>
</ui:SimpleStackPanel>
<Border Width="1" Background="{DynamicResource SettingsPageBorderBrush}" Margin="0,8,0,8"/>
<ui:SimpleStackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Center">
<TextBlock Text="发布日期" FontSize="14" FontWeight="Medium" Foreground="{DynamicResource UpdateWindowTextSecondaryBrush}"/>
<TextBlock x:Name="updateDateInfo" Text="2024年8月4日发布更新"
FontSize="16" Foreground="{DynamicResource UpdateWindowTextPrimaryBrush}" TextAlignment="Center"/>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
</Border>
<!-- 更新按钮组 -->
<Border Background="#f1f5f9" BorderBrush="#e2e8f0" BorderThickness="1" CornerRadius="4" Margin="24,16,24,20">
<ui:SimpleStackPanel Orientation="Vertical" HorizontalAlignment="Center" Spacing="14" Margin="0,16,0,16">
<TextBlock Text="请选择更新方式" FontWeight="Bold" FontSize="16" HorizontalAlignment="Center" Margin="0,0,0,6"/>
<!-- 现代化按钮区域 -->
<Border Background="{DynamicResource UpdateWindowCardBackgroundBrush}" Margin="0,0,0,0" Padding="32,24" CornerRadius="0,0,8,8">
<ui:SimpleStackPanel Orientation="Vertical" HorizontalAlignment="Center" Spacing="16">
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
<Border Background="{DynamicResource UpdateWindowPrimaryBrush}" CornerRadius="8" Padding="8">
<TextBlock Text="⚡" FontSize="16" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="选择更新方式" FontWeight="SemiBold" FontSize="18"
Foreground="{DynamicResource UpdateWindowTextPrimaryBrush}" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
<!-- 立即更新按钮 -->
<Button x:Name="UpdateNowButton" Content="立下载并安装" Foreground="White" FontSize="15" FontWeight="SemiBold"
Padding="20,10" Width="360" Height="48" HorizontalAlignment="Center"
Click="UpdateNowButton_Click" ToolTip="立即下载更新并在完成后安装" Visibility="Visible" IsEnabled="True">
<Button.Resources>
<SolidColorBrush x:Key="{x:Static ui:ThemeKeys.ButtonBackgroundKey}" Color="#15803d"/>
<SolidColorBrush x:Key="{x:Static ui:ThemeKeys.ButtonBackgroundPointerOverKey}" Color="#15803d"/>
<SolidColorBrush x:Key="{x:Static ui:ThemeKeys.ButtonBackgroundPressedKey}" Color="#166534"/>
</Button.Resources>
</Button>
<Button x:Name="UpdateNowButton" Content="立下载并安装"
Style="{StaticResource ModernPrimaryButton}"
Click="UpdateNowButton_Click" ToolTip="立即下载更新并在完成后安装"
Visibility="Visible" IsEnabled="True"/>
<!-- 稍后更新按钮 -->
<Button x:Name="UpdateLaterButton" Content="下载并在软件关闭时安装" Foreground="Black" FontSize="15"
Padding="20,10" Width="360" Height="48" HorizontalAlignment="Center"
Click="UpdateLaterButton_Click" Background="#e2e8f0" BorderBrush="#cbd5e1"
ToolTip="后台下载更新,在软件关闭时自动安装" Visibility="Visible" IsEnabled="True"/>
<Button x:Name="UpdateLaterButton" Content="下载并在软件关闭时安装"
Style="{StaticResource ModernSecondaryButton}"
Click="UpdateLaterButton_Click"
ToolTip="后台下载更新,在软件关闭时自动安装"
Visibility="Visible" IsEnabled="True"/>
<!-- 跳过版本按钮 -->
<Button x:Name="SkipVersionButton" Content="跳过该版本" HorizontalAlignment="Center" Foreground="#71717a"
FontSize="15" Padding="20,10" Width="360" Height="48" Click="SkipVersionButton_Click"
Background="#f8fafc" BorderBrush="#cbd5e1" ToolTip="跳过此版本更新" Visibility="Visible" IsEnabled="True"/>
<Button x:Name="SkipVersionButton" Content="跳过该版本"
Style="{StaticResource ModernTertiaryButton}"
Click="SkipVersionButton_Click"
ToolTip="跳过此版本更新" Visibility="Visible" IsEnabled="True"/>
</ui:SimpleStackPanel>
</Border>
<!-- 下载进度条和状态 -->
<StackPanel x:Name="DownloadProgressPanel" Orientation="Vertical" HorizontalAlignment="Center" Margin="0,10,0,0" Visibility="Collapsed">
<ProgressBar x:Name="DownloadProgressBar" Width="360" Height="18" Minimum="0" Maximum="100" Value="0"/>
<TextBlock x:Name="DownloadProgressText" Text="正在下载..." FontSize="14" Foreground="#2563eb" HorizontalAlignment="Center" Margin="0,6,0,0"/>
</StackPanel>
<!-- 现代化下载进度指示器 -->
<Border x:Name="DownloadProgressPanel" Background="{DynamicResource UpdateWindowCardBackgroundBrush}"
Margin="0,0,0,0" Padding="32,24" Visibility="Collapsed">
<ui:SimpleStackPanel Orientation="Vertical" HorizontalAlignment="Center" Spacing="16">
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
<Border Background="#10b981" CornerRadius="8" Padding="8">
<TextBlock Text="📥" FontSize="16" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="正在下载更新" FontWeight="SemiBold" FontSize="18"
Foreground="{DynamicResource UpdateWindowTextPrimaryBrush}" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
<!-- 现代化进度条 -->
<Border Background="{DynamicResource SettingsPageBackground}" CornerRadius="6" Height="6" Width="400">
<Border x:Name="ProgressFill" Background="{DynamicResource UpdateWindowPrimaryBrush}" CornerRadius="6"
Width="0" HorizontalAlignment="Left">
</Border>
</Border>
<TextBlock x:Name="DownloadProgressText" Text="正在准备下载..."
FontSize="14" Foreground="{DynamicResource UpdateWindowTextSecondaryBrush}" HorizontalAlignment="Center"/>
</ui:SimpleStackPanel>
</Border>
</ui:SimpleStackPanel>
</Grid>
</ScrollViewer>
+167 -3
View File
@@ -1,4 +1,5 @@
using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern;
using iNKORE.UI.WPF.Modern.Controls;
using MdXaml;
using System;
@@ -11,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
@@ -53,6 +55,128 @@ namespace Ink_Canvas
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= build;
}
/// <summary>
/// 应用当前主题设置
/// </summary>
private void ApplyCurrentTheme()
{
try
{
// 根据主窗口的主题设置应用主题
switch (MainWindow.Settings.Appearance.Theme)
{
case 0: // 浅色主题
ThemeManager.SetRequestedTheme(this, ElementTheme.Light);
UpdateColorsForLightTheme();
break;
case 1: // 深色主题
ThemeManager.SetRequestedTheme(this, ElementTheme.Dark);
UpdateColorsForDarkTheme();
break;
case 2: // 跟随系统
if (IsSystemThemeLight())
{
ThemeManager.SetRequestedTheme(this, ElementTheme.Light);
UpdateColorsForLightTheme();
}
else
{
ThemeManager.SetRequestedTheme(this, ElementTheme.Dark);
UpdateColorsForDarkTheme();
}
break;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"应用主题时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新为浅色主题颜色
/// </summary>
private void UpdateColorsForLightTheme()
{
try
{
// 更新主要颜色资源
Resources["UpdateWindowPrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb));
Resources["UpdateWindowPrimaryHoverBrush"] = new SolidColorBrush(Color.FromRgb(0x1d, 0x4e, 0xd8));
Resources["UpdateWindowPrimaryPressedBrush"] = new SolidColorBrush(Color.FromRgb(0x1e, 0x40, 0xaf));
Resources["UpdateWindowCardBackgroundBrush"] = new SolidColorBrush(Color.FromRgb(0xf8, 0xfa, 0xfc));
Resources["UpdateWindowCardBorderBrush"] = new SolidColorBrush(Color.FromRgb(0xe5, 0xe7, 0xeb));
Resources["UpdateWindowTextPrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0x1f, 0x29, 0x37));
Resources["UpdateWindowTextSecondaryBrush"] = new SolidColorBrush(Color.FromRgb(0x6b, 0x72, 0x80));
Resources["UpdateWindowCloseButtonBrush"] = new SolidColorBrush(Color.FromRgb(0x66, 0x66, 0x66));
// 更新渐变背景
var gradient = new LinearGradientBrush();
gradient.StartPoint = new Point(0, 0);
gradient.EndPoint = new Point(1, 1);
gradient.GradientStops.Add(new GradientStop(Color.FromRgb(0x25, 0x63, 0xeb), 0));
gradient.GradientStops.Add(new GradientStop(Colors.White, 1));
Resources["HeaderGradient"] = gradient;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新浅色主题颜色时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新为深色主题颜色
/// </summary>
private void UpdateColorsForDarkTheme()
{
try
{
// 更新主要颜色资源
Resources["UpdateWindowPrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0x3b, 0x82, 0xf6));
Resources["UpdateWindowPrimaryHoverBrush"] = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb));
Resources["UpdateWindowPrimaryPressedBrush"] = new SolidColorBrush(Color.FromRgb(0x1d, 0x4e, 0xd8));
Resources["UpdateWindowCardBackgroundBrush"] = new SolidColorBrush(Color.FromRgb(0x2a, 0x2a, 0x2a));
Resources["UpdateWindowCardBorderBrush"] = new SolidColorBrush(Color.FromRgb(0x40, 0x40, 0x40));
Resources["UpdateWindowTextPrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0xf9, 0xfa, 0xfb));
Resources["UpdateWindowTextSecondaryBrush"] = new SolidColorBrush(Color.FromRgb(0x9c, 0xa3, 0xaf));
Resources["UpdateWindowCloseButtonBrush"] = new SolidColorBrush(Color.FromRgb(0x9c, 0xa3, 0xaf));
// 更新渐变背景
var gradient = new LinearGradientBrush();
gradient.StartPoint = new Point(0, 0);
gradient.EndPoint = new Point(1, 1);
gradient.GradientStops.Add(new GradientStop(Color.FromRgb(0x3b, 0x82, 0xf6), 0));
gradient.GradientStops.Add(new GradientStop(Color.FromRgb(0x1f, 0x1f, 0x1f), 1));
Resources["HeaderGradient"] = gradient;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新深色主题颜色时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 检查系统是否为浅色主题
/// </summary>
private bool IsSystemThemeLight()
{
try
{
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
{
if (key?.GetValue("AppsUseLightTheme") is int value)
{
return value == 1;
}
}
}
catch
{
// 如果无法读取注册表,默认返回true(浅色主题)
}
return true;
}
// 存储更新版本信息
public string CurrentVersion { get; set; }
public string NewVersion { get; set; }
@@ -73,6 +197,9 @@ namespace Ink_Canvas
{
InitializeComponent();
// 应用当前主题
ApplyCurrentTheme();
// 设置版本信息
CurrentVersion = currentVersion;
NewVersion = newVersion;
@@ -143,7 +270,13 @@ namespace Ink_Canvas
UpdateLaterButton.IsEnabled = false;
SkipVersionButton.IsEnabled = false;
DownloadProgressPanel.Visibility = Visibility.Visible;
DownloadProgressBar.Value = 0;
// 重置进度条
var progressFill = FindName("ProgressFill") as Border;
if (progressFill != null)
{
progressFill.Width = 0;
}
DownloadProgressText.Text = "正在准备下载...";
// 启动多线路下载
@@ -156,7 +289,12 @@ namespace Ink_Canvas
{
Dispatcher.Invoke(() =>
{
DownloadProgressBar.Value = percent;
// 更新自定义进度条
progressFill = FindName("ProgressFill") as Border;
if (progressFill != null)
{
progressFill.Width = (percent / 100.0) * 400; // 400是进度条总宽度
}
DownloadProgressText.Text = text;
});
});
@@ -174,7 +312,12 @@ namespace Ink_Canvas
if (downloadSuccess)
{
DownloadProgressBar.Value = 100;
// 设置进度条为100%
progressFill = FindName("ProgressFill") as Border;
if (progressFill != null)
{
progressFill.Width = 400; // 100%完成
}
DownloadProgressText.Text = "下载完成,准备安装...";
await Task.Delay(800);
// 设置结果为立即更新
@@ -215,6 +358,27 @@ namespace Ink_Canvas
Close();
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
LogHelper.WriteLogToFile("AutoUpdate | Close button clicked");
// 设置结果为稍后更新(默认行为)
Result = UpdateResult.UpdateLater;
// 关闭窗口
DialogResult = false;
Close();
}
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// 开始拖动窗口
if (e.LeftButton == MouseButtonState.Pressed)
{
DragMove();
}
}
// 根据屏幕分辨率调整窗口大小
+227 -14
View File
@@ -6,19 +6,232 @@
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:mdxam="clr-namespace:MdXaml;assembly=MdXaml"
mc:Ignorable="d"
Title="历史版本回滚" Height="600" Width="850" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid Background="#fafafa">
<ui:SimpleStackPanel VerticalAlignment="Stretch" Spacing="0">
<TextBlock Text="选择历史版本进行回滚" FontSize="24" FontWeight="Bold" Foreground="#2563eb" Margin="24,24,0,12"/>
<ComboBox x:Name="VersionComboBox" Width="400" Height="36" Margin="24,0,0,0" DisplayMemberPath="Version" SelectionChanged="VersionComboBox_SelectionChanged"/>
<Border BorderBrush="#3f3f46" Background="White" BorderThickness="1" CornerRadius="4" Margin="24,16,24,0" Height="180">
<mdxam:MarkdownScrollViewer x:Name="ReleaseNotesViewer" Foreground="Black" MarkdownStyleName="GithubLike"/>
ui:WindowHelper.UseModernWindowStyle="False"
ui:WindowHelper.SystemBackdropType="Mica"
Title="历史版本回滚" Height="650" Width="900" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen" WindowStyle="None"
Background="Transparent" MinHeight="550" MinWidth="800">
<Window.Template>
<ControlTemplate TargetType="Window">
<Border Background="{DynamicResource SettingsPageBackground}"
CornerRadius="8"
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
BorderThickness="1">
<Border.Effect>
<DropShadowEffect Color="#000000" BlurRadius="20" ShadowDepth="0" Opacity="0.1"/>
</Border.Effect>
<Grid>
<!-- 标题栏 -->
<Border Background="{DynamicResource SettingsPageBackground}" Height="48" VerticalAlignment="Top">
<Grid>
<TextBlock Text="{TemplateBinding Title}"
FontSize="14" FontWeight="SemiBold"
Foreground="{DynamicResource SettingsPageForeground}"
VerticalAlignment="Center"
Margin="16,0,0,0"/>
<!-- 最小化按钮 -->
<Button Name="MinimizeButton"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,8,40,0"
Width="32" Height="32"
Background="Transparent"
BorderThickness="0"
Click="MinimizeButton_Click"
Cursor="Hand"
ToolTip="最小化">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="4"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<TextBlock Text="" FontSize="14"
Foreground="{DynamicResource SettingsPageForeground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#FF2563eb" Opacity="0.2"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
<!-- 关闭按钮 -->
<Button Name="CloseButton"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,8,8,0"
Width="32" Height="32"
Background="Transparent"
BorderThickness="0"
Click="CloseButton_Click"
Cursor="Hand"
ToolTip="关闭">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="4"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<TextBlock Text="×" FontSize="14"
Foreground="{DynamicResource SettingsPageForeground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#ef4444" Opacity="0.1"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#ef4444" Opacity="0.2"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Border>
<!-- 内容区域 -->
<ContentPresenter Margin="0,48,0,0"/>
</Grid>
</Border>
<Button x:Name="RollbackButton" Content="回滚到此版本" Width="360" Height="48" Margin="24,24,0,0" Click="RollbackButton_Click"/>
<StackPanel x:Name="DownloadProgressPanel" Orientation="Vertical" HorizontalAlignment="Center" Margin="0,10,0,0" Visibility="Collapsed">
<ProgressBar x:Name="DownloadProgressBar" Width="360" Height="18" Minimum="0" Maximum="100" Value="0"/>
<TextBlock x:Name="DownloadProgressText" Text="正在下载..." FontSize="14" Foreground="#2563eb" HorizontalAlignment="Center" Margin="0,6,0,0"/>
</StackPanel>
</ControlTemplate>
</Window.Template>
<Window.Resources>
<!-- 主题相关颜色资源 -->
<SolidColorBrush x:Key="PrimaryBrush" Color="#FF2563eb"/>
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#FF1f2937"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#FF6b7280"/>
</Window.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Grid Background="Transparent">
<!-- 主内容区域 -->
<ui:SimpleStackPanel VerticalAlignment="Stretch" Spacing="0" Margin="20">
<!-- 标题区域 -->
<ui:SimpleStackPanel Orientation="Horizontal" Spacing="12" Margin="0,0,0,16">
<Border Background="{DynamicResource PrimaryBrush}" CornerRadius="16" Padding="12" Margin="0,0,0,0">
<ui:SymbolIcon Symbol="Undo" FontSize="20" Foreground="White"
VerticalAlignment="Center"/>
</Border>
<ui:SimpleStackPanel VerticalAlignment="Center" Spacing="4">
<TextBlock Text="历史版本回滚" FontSize="22" FontWeight="Bold"
Foreground="{DynamicResource TextPrimaryBrush}"/>
<TextBlock Text="选择要回滚到的历史版本" FontSize="14"
Foreground="{DynamicResource TextSecondaryBrush}"/>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
<!-- 版本选择卡片 -->
<Border Background="{DynamicResource SettingsPageBackground}"
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
BorderThickness="1"
CornerRadius="12"
Margin="0,0,0,16"
Padding="20">
<Border.Effect>
<DropShadowEffect Color="#000000" BlurRadius="8" ShadowDepth="0" Opacity="0.05"/>
</Border.Effect>
<ui:SimpleStackPanel Spacing="16">
<TextBlock Text="选择版本" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"/>
<ComboBox x:Name="VersionComboBox"
Width="300" Height="40"
DisplayMemberPath="Version"
SelectionChanged="VersionComboBox_SelectionChanged"/>
</ui:SimpleStackPanel>
</Border>
<!-- 发布说明卡片 -->
<Border Background="{DynamicResource SettingsPageBackground}"
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
BorderThickness="1"
CornerRadius="12"
Margin="0,0,0,16"
Padding="20">
<Border.Effect>
<DropShadowEffect Color="#000000" BlurRadius="8" ShadowDepth="0" Opacity="0.05"/>
</Border.Effect>
<ui:SimpleStackPanel Spacing="16">
<TextBlock Text="版本更新说明" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimaryBrush}"/>
<Border Background="{DynamicResource SettingsPageBackground}"
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
BorderThickness="1"
CornerRadius="8"
MinHeight="150" MaxHeight="250">
<Border.Effect>
<DropShadowEffect Color="#000000" BlurRadius="4" ShadowDepth="0" Opacity="0.03"/>
</Border.Effect>
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Padding="16">
<mdxam:MarkdownScrollViewer x:Name="ReleaseNotesViewer"
Foreground="{DynamicResource TextPrimaryBrush}"
MarkdownStyleName="GithubLike"/>
</ScrollViewer>
</Border>
</ui:SimpleStackPanel>
</Border>
<!-- 操作按钮区域 -->
<ui:SimpleStackPanel Spacing="16" Margin="0,0,0,16">
<Button x:Name="RollbackButton"
Content="回滚到此版本"
HorizontalAlignment="Center"
Click="RollbackButton_Click"
Style="{DynamicResource AccentButtonStyle}"
Width="200" Height="48"
FontSize="16" FontWeight="SemiBold"/>
<!-- 下载进度面板 -->
<Border x:Name="DownloadProgressPanel"
Background="{DynamicResource SettingsPageBackground}"
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
BorderThickness="1"
CornerRadius="12"
Visibility="Collapsed"
Margin="0,8,0,0"
Padding="24">
<Border.Effect>
<DropShadowEffect Color="#000000" BlurRadius="8" ShadowDepth="0" Opacity="0.05"/>
</Border.Effect>
<ui:SimpleStackPanel Spacing="12">
<ProgressBar x:Name="DownloadProgressBar"
Width="300" Height="8"
Minimum="0" Maximum="100" Value="0"/>
<TextBlock x:Name="DownloadProgressText"
Text="正在下载..."
FontSize="14"
Foreground="{DynamicResource TextPrimaryBrush}"
HorizontalAlignment="Center"/>
</ui:SimpleStackPanel>
</Border>
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
</Grid>
</Window>
</Grid>
</ScrollViewer>
</Window>
@@ -1,4 +1,5 @@
using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -8,6 +9,7 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
// Added for OrderByDescending
@@ -31,7 +33,116 @@ namespace Ink_Canvas
{
InitializeComponent();
this.channel = channel;
// 应用当前主题
ApplyCurrentTheme();
LoadVersions();
// 添加窗口拖动功能
MouseDown += (sender, e) =>
{
if (e.ChangedButton == MouseButton.Left)
{
DragMove();
}
};
}
/// <summary>
/// 应用当前主题设置
/// </summary>
private void ApplyCurrentTheme()
{
try
{
// 根据主窗口的主题设置应用主题
switch (MainWindow.Settings.Appearance.Theme)
{
case 0: // 浅色主题
ThemeManager.SetRequestedTheme(this, ElementTheme.Light);
UpdateColorsForLightTheme();
break;
case 1: // 深色主题
ThemeManager.SetRequestedTheme(this, ElementTheme.Dark);
UpdateColorsForDarkTheme();
break;
case 2: // 跟随系统
if (IsSystemThemeLight())
{
ThemeManager.SetRequestedTheme(this, ElementTheme.Light);
UpdateColorsForLightTheme();
}
else
{
ThemeManager.SetRequestedTheme(this, ElementTheme.Dark);
UpdateColorsForDarkTheme();
}
break;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"应用主题时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新为浅色主题颜色
/// </summary>
private void UpdateColorsForLightTheme()
{
try
{
// 更新主要颜色资源
Resources["PrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb));
Resources["TextPrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0x1f, 0x29, 0x37));
Resources["TextSecondaryBrush"] = new SolidColorBrush(Color.FromRgb(0x6b, 0x72, 0x80));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新浅色主题颜色时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新为深色主题颜色
/// </summary>
private void UpdateColorsForDarkTheme()
{
try
{
// 更新主要颜色资源
Resources["PrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0x3b, 0x82, 0xf6));
Resources["TextPrimaryBrush"] = new SolidColorBrush(Color.FromRgb(0xf9, 0xfa, 0xfb));
Resources["TextSecondaryBrush"] = new SolidColorBrush(Color.FromRgb(0x9c, 0xa3, 0xaf));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新深色主题颜色时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 检查系统是否为浅色主题
/// </summary>
private bool IsSystemThemeLight()
{
try
{
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
{
if (key?.GetValue("AppsUseLightTheme") is int value)
{
return value == 1;
}
}
}
catch
{
// 如果无法读取注册表,默认返回true(浅色主题)
}
return true;
}
private async void LoadVersions()
@@ -135,6 +246,16 @@ namespace Ink_Canvas
}
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
protected override void OnClosing(CancelEventArgs e)
{
downloadCts?.Cancel();
+4 -4
View File
@@ -23,13 +23,13 @@
## 🤔 发生了什么?
由于众所周知的原因,[DotteringDoge471](https://github.com/DotteringDoge471) 不再积极负责 InkCanvasForClass 旧时代版本的开发与维护工作,而刚好 [CJKmkp](https://github,com/CJK-mkp) 又维护了这个社区版本的 icc,经过沟通后就顺理成章地成为了 icc 的官方版本。该分支版本 **目前还在开发之中** ,可能会有潜在的问题/ Bug 出现,请在出现 Bug 后与开发者或与 [DotteringDoge471](https://github.com/DotteringDoge471) 上报,方便我们迅速诊断并解决问题。
由于众所周知的原因,[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/CJK-mkp) 反馈或在 GitHub 仓库内提出 Issue
> ⚠️ 请注意:[DotteringDoge471](https://github.com/DotteringDoge471) 不积极负责 **本社区版本** 的开发与维护工作,仅会在有空的时候对本项目开发新功能或修复 Bug。因此,任何问题反馈/Bug反馈/建议等,请优先找本项目主要维护者 [CJKmkp](https://github.com/CJKmkp) 反馈或在 GitHub 仓库内提出 Issue
使用该版本 InkCanvasForClass,意味着您同意自行承担任何可能存在的问题与风险。建议不要在公众场合(例如公开课,录播课,线上直播课,大型会议)使用未经广泛测试和优化的 Beta 版本,对使用 Beta 版本而带来的任何问题和风险(例如:被班主任批斗,被校长处罚,崩溃而导致的场面混乱,全球海平面上升等),**将由使用者自行承担**,[CJKmkp](https://github,com/CJK-mkp) 和 [DotteringDoge471](https://github.com/DotteringDoge471) 及其项目的所有维护者不提供任何担保。
使用该版本 InkCanvasForClass,意味着您同意自行承担任何可能存在的问题与风险。建议不要在公众场合(例如公开课,录播课,线上直播课,大型会议)使用未经广泛测试和优化的 Beta 版本,对使用 Beta 版本而带来的任何问题和风险(例如:被班主任批斗,被校长处罚,崩溃而导致的场面混乱,全球海平面上升等),**将由使用者自行承担**,[CJKmkp](https://github.com/CJKmkp) 和 [DotteringDoge471](https://github.com/DotteringDoge471) 及其项目的所有维护者不提供任何担保。
♥️ **本项目版权归 [CJKmkp](https://github,com/CJK-mkp) 所有。[CJKmkp](https://github,com/CJK-mkp) 拥有最终解释权。**
♥️ **本项目版权归 [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 板块管理条约的情况下,在该板块内提问或发表自己的使用体验。