Merge pull request #195 from InkCanvasForClass/beta

ICC CE 1.7.11.0
This commit is contained in:
CJK_mkp
2025-09-13 23:05:18 +08:00
committed by GitHub
67 changed files with 6102 additions and 927 deletions
-9
View File
@@ -19,15 +19,6 @@
"code"
]
},
{
"login": "Hydro11451",
"name": "Hydrogen",
"avatar_url": "https://avatars.githubusercontent.com/u/214308559?v=4",
"profile": "http://hydro11451.qzz.io",
"contributions": [
"code"
]
},
{
"login": "CreeperAWA",
"name": "CreeperAWA",
+2 -2
View File
@@ -39,14 +39,14 @@ body:
2.
3.
validations:
required: false
required: true
- type: textarea
id: expected
attributes:
label: 期望结果 | Expected Behavior
description: 你期望的正确行为或结果 | What did you expect to happen?
validations:
required: false
required: true
- type: textarea
id: extra
attributes:
+1 -1
View File
@@ -20,7 +20,7 @@ body:
label: 需求动机 | Motivation
description: 为什么需要这个功能?| Why do you need this feature?
validations:
required: false
required: true
- type: textarea
id: design
attributes:
+1 -1
View File
@@ -1 +1 @@
1.7.10.0
1.7.11.0
+1 -1
View File
@@ -32,7 +32,7 @@
</MenuItem.Icon>
</MenuItem>
<Separator Margin="0,3" />
<MenuItem>
<MenuItem Name="DisableAllHotkeysMenuItem" Click="DisableAllHotkeysMenuItem_Clicked">
<MenuItem.Header>
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="禁用所有快捷键" />
+53 -7
View File
@@ -33,7 +33,9 @@ namespace Ink_Canvas
public static string[] StartArgs;
public static string RootPath = Environment.GetEnvironmentVariable("APPDATA") + "\\Ink Canvas\\";
// 新增:标记是否通过--board参数启动
public static bool StartWithBoardMode = false;
// 新增:保存看门狗进程对象
private static Process watchdogProcess;
// 新增:标记是否为软件内主动退出
@@ -292,6 +294,27 @@ namespace Ink_Canvas
{
string reason = e.Reason == SessionEndReasons.Logoff ? "用户注销" : "系统关机";
WriteCrashLog($"系统会话即将结束: {reason}");
// 清理PowerPoint进程守护
try
{
// 获取主窗口实例并清理PowerPoint进程守护
var mainWindow = Current.MainWindow as MainWindow;
if (mainWindow != null)
{
// 通过反射调用StopPowerPointProcessMonitoring方法
var method = mainWindow.GetType().GetMethod("StopPowerPointProcessMonitoring",
BindingFlags.NonPublic | BindingFlags.Instance);
method?.Invoke(mainWindow, null);
WriteCrashLog("PowerPoint进程守护已在系统关机时清理");
}
}
catch (Exception ex)
{
WriteCrashLog($"清理PowerPoint进程守护失败: {ex.Message}");
}
DeviceIdentifier.SaveUsageStatsOnShutdown();
}
@@ -408,7 +431,7 @@ namespace Ink_Canvas
try
{
// 优先从 Settings.json 直接读取
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.json");
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
if (File.Exists(settingsPath))
{
var json = File.ReadAllText(settingsPath);
@@ -474,6 +497,14 @@ namespace Ink_Canvas
// 检查是否为最终应用启动(更新后的应用)
bool isFinalApp = e.Args.Contains("--final-app");
bool skipMutexCheck = e.Args.Contains("--skip-mutex-check");
// 检查是否通过--board参数启动
bool hasBoardArg = e.Args.Contains("--board");
if (hasBoardArg)
{
StartWithBoardMode = true;
LogHelper.WriteLogToFile("App | 检测到--board参数,将直接进入白板模式");
}
// 记录最终应用启动状态
if (isFinalApp)
@@ -633,13 +664,13 @@ namespace Ink_Canvas
if (!ret && !e.Args.Contains("-m")) //-m multiple
{
LogHelper.NewLog("Detected existing instance");
// 检查是否有.icstk文件参数
string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(e.Args);
if (!string.IsNullOrEmpty(icstkFile))
{
LogHelper.WriteLogToFile($"检测到已运行实例,尝试通过IPC发送文件: {icstkFile}", LogHelper.LogType.Event);
// 尝试通过IPC发送文件路径给已运行实例
if (FileAssociationManager.TrySendFileToExistingInstance(icstkFile))
{
@@ -650,11 +681,26 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile("通过IPC发送文件路径失败", LogHelper.LogType.Warning);
}
}
// 检查是否有--board参数
else if (hasBoardArg)
{
LogHelper.WriteLogToFile("检测到已运行实例且有--board参数,尝试通过IPC发送白板模式命令", LogHelper.LogType.Event);
// 尝试通过IPC发送白板模式命令给已运行实例
if (FileAssociationManager.TrySendBoardModeCommandToExistingInstance())
{
LogHelper.WriteLogToFile("白板模式命令已通过IPC发送给已运行实例", LogHelper.LogType.Event);
}
else
{
LogHelper.WriteLogToFile("通过IPC发送白板模式命令失败", LogHelper.LogType.Warning);
}
}
else
{
LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event);
}
LogHelper.NewLog("Ink Canvas automatically closed");
IsAppExitByUser = true; // 多开时标记为用户主动退出
// 写入退出信号,确保看门狗不会重启
@@ -783,7 +829,7 @@ namespace Ink_Canvas
}
// 备份路径更改为软件根目录下的saves/RegistryBackups文件夹
string backupPath = Path.Combine(RootPath, "saves", "RegistryBackups");
string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups");
LogHelper.WriteLogToFile($"备份路径: {backupPath}");
if (!Directory.Exists(backupPath))
@@ -1297,7 +1343,7 @@ namespace Ink_Canvas
try
{
// 准备备份目录
string backupPath = Path.Combine(RootPath, "saves", "RegistryBackups");
string backupPath = Path.Combine(RootPath, "Saves", "RegistryBackups");
if (!Directory.Exists(backupPath))
{
Directory.CreateDirectory(backupPath);
+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.10.0")]
[assembly: AssemblyFileVersion("1.7.10.0")]
[assembly: AssemblyVersion("1.7.11.0")]
[assembly: AssemblyFileVersion("1.7.11.0")]
@@ -0,0 +1,368 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Ink_Canvas.Helpers;
namespace Ink_Canvas
{
/// <summary>
/// 悬浮窗拦截管理器
/// </summary>
public class FloatingWindowInterceptorManager : IDisposable
{
#region
private FloatingWindowInterceptor _interceptor;
private bool _isInitialized;
private bool _disposed;
private FloatingWindowInterceptorSettings _settings;
#endregion
#region
public event EventHandler<FloatingWindowInterceptor.WindowInterceptedEventArgs> WindowIntercepted;
public event EventHandler<FloatingWindowInterceptor.WindowRestoredEventArgs> WindowRestored;
#endregion
#region
public bool IsEnabled => _interceptor != null && _settings != null && _settings.IsEnabled;
public bool IsRunning => _interceptor != null && _interceptor.IsRunning;
#endregion
#region
/// <summary>
/// 初始化拦截器
/// </summary>
public void Initialize(FloatingWindowInterceptorSettings settings)
{
if (_isInitialized) return;
try
{
_settings = settings ?? new FloatingWindowInterceptorSettings();
_interceptor = new FloatingWindowInterceptor();
// 订阅事件
_interceptor.WindowIntercepted += OnWindowIntercepted;
_interceptor.WindowRestored += OnWindowRestored;
// 应用配置
ApplySettings();
_isInitialized = true;
// 如果设置了自动启动,则启动拦截器
if (_settings.AutoStart && _settings.IsEnabled)
{
Start();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 启动拦截器
/// </summary>
public void Start()
{
if (!_isInitialized || _settings == null) return;
if (_interceptor == null) return;
try
{
_interceptor.Start(_settings.ScanIntervalMs);
LogHelper.WriteLogToFile("悬浮窗拦截器已启动", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"启动悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 停止拦截器
/// </summary>
public void Stop()
{
if (_interceptor == null) return;
try
{
_interceptor.Stop();
LogHelper.WriteLogToFile("悬浮窗拦截器已停止", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"停止悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 设置拦截规则
/// </summary>
public void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
{
if (_interceptor == null || _settings == null) return;
try
{
_interceptor.SetInterceptRule(type, enabled);
// 更新设置
var ruleName = type.ToString();
if (_settings.InterceptRules.ContainsKey(ruleName))
{
_settings.InterceptRules[ruleName] = enabled;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"设置拦截规则失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 获取拦截规则
/// </summary>
public FloatingWindowInterceptor.InterceptRule GetInterceptRule(FloatingWindowInterceptor.InterceptType type)
{
return _interceptor?.GetInterceptRule(type);
}
/// <summary>
/// 获取所有拦截规则
/// </summary>
public Dictionary<FloatingWindowInterceptor.InterceptType, FloatingWindowInterceptor.InterceptRule> GetAllRules()
{
return _interceptor?.GetAllRules() ?? new Dictionary<FloatingWindowInterceptor.InterceptType, FloatingWindowInterceptor.InterceptRule>();
}
/// <summary>
/// 手动扫描一次
/// </summary>
public void ScanOnce()
{
if (_interceptor == null) return;
try
{
_interceptor.ScanOnce();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"手动扫描失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 恢复所有被拦截的窗口
/// </summary>
public void RestoreAllWindows()
{
if (_interceptor == null) return;
try
{
_interceptor.RestoreAllWindows();
LogHelper.WriteLogToFile("已恢复所有被拦截的窗口", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"恢复窗口失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 应用设置
/// </summary>
public void ApplySettings()
{
if (_interceptor == null || _settings == null) return;
try
{
// 应用拦截规则设置
foreach (var kvp in _settings.InterceptRules)
{
if (Enum.TryParse<FloatingWindowInterceptor.InterceptType>(kvp.Key, out var type))
{
_interceptor.SetInterceptRule(type, kvp.Value);
}
}
// 如果启用了拦截器,则启动
if (_settings.IsEnabled && !IsRunning)
{
Start();
}
// 如果禁用了拦截器,则停止
else if (!_settings.IsEnabled && IsRunning)
{
Stop();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"应用设置失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新扫描间隔
/// </summary>
public void UpdateScanInterval(int intervalMs)
{
if (_interceptor == null || _settings == null) return;
try
{
_settings.ScanIntervalMs = intervalMs;
// 如果正在运行,重启以应用新间隔
if (IsRunning)
{
Stop();
Start();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新扫描间隔失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 获取拦截统计信息
/// </summary>
public InterceptStatistics GetStatistics()
{
if (_interceptor == null || _settings == null) return new InterceptStatistics();
try
{
var rules = GetAllRules();
var enabledRules = rules.Count(r => r.Value.IsEnabled);
var totalRules = rules.Count;
return new InterceptStatistics
{
TotalRules = totalRules,
EnabledRules = enabledRules,
IsRunning = IsRunning,
ScanIntervalMs = _settings.ScanIntervalMs
};
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"获取统计信息失败: {ex.Message}", LogHelper.LogType.Error);
return new InterceptStatistics();
}
}
#endregion
#region
private void OnWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e)
{
try
{
// 记录日志
LogHelper.WriteLogToFile($"拦截窗口: {e.WindowTitle} ({e.InterceptType})", LogHelper.LogType.Event);
// 显示通知(如果启用)
if (_settings != null && _settings.ShowNotifications)
{
ShowNotification($"已拦截悬浮窗: {e.Rule.Description}");
}
// 触发事件
WindowIntercepted?.Invoke(this, e);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口拦截事件失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void OnWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e)
{
try
{
// 记录日志
LogHelper.WriteLogToFile($"恢复窗口: {e.InterceptType}", LogHelper.LogType.Event);
// 触发事件
WindowRestored?.Invoke(this, e);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口恢复事件失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void ShowNotification(string message)
{
try
{
// 这里可以集成系统通知或自定义通知
// 暂时使用调试输出
System.Diagnostics.Debug.WriteLine($"通知: {message}");
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"显示通知失败: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region
public class InterceptStatistics
{
public int TotalRules { get; set; }
public int EnabledRules { get; set; }
public bool IsRunning { get; set; }
public int ScanIntervalMs { get; set; }
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
try
{
Stop();
_interceptor?.Dispose();
_interceptor = null;
_isInitialized = false;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"释放悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
}
finally
{
_disposed = true;
}
}
#endregion
}
}
+315
View File
@@ -0,0 +1,315 @@
using AForge.Video;
using AForge.Video.DirectShow;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace Ink_Canvas.Helpers
{
public class CameraService : IDisposable
{
private VideoCaptureDevice _videoSource;
private bool _isCapturing;
private Bitmap _currentFrame;
private readonly object _frameLock = new object();
private Dispatcher _dispatcher;
public event EventHandler<Bitmap> FrameReceived;
public event EventHandler<string> ErrorOccurred;
public bool IsCapturing => _isCapturing;
public List<FilterInfo> AvailableCameras { get; private set; }
public FilterInfo CurrentCamera { get; private set; }
public CameraService()
{
_dispatcher = Dispatcher.CurrentDispatcher;
AvailableCameras = new List<FilterInfo>();
RefreshCameraList();
}
/// <summary>
/// 刷新可用摄像头列表
/// </summary>
public void RefreshCameraList()
{
try
{
AvailableCameras.Clear();
var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
foreach (FilterInfo device in videoDevices)
{
AvailableCameras.Add(device);
}
LogHelper.WriteLogToFile($"发现 {AvailableCameras.Count} 个摄像头设备");
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error);
ErrorOccurred?.Invoke(this, $"刷新摄像头列表失败: {ex.Message}");
}
}
/// <summary>
/// 开始摄像头预览
/// </summary>
/// <param name="cameraIndex">摄像头索引</param>
public bool StartPreview(int cameraIndex = 0)
{
try
{
if (AvailableCameras.Count == 0)
{
RefreshCameraList();
if (AvailableCameras.Count == 0)
{
ErrorOccurred?.Invoke(this, "未找到可用的摄像头设备");
return false;
}
}
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
{
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
return false;
}
// 停止当前预览
StopPreview();
CurrentCamera = AvailableCameras[cameraIndex];
_videoSource = new VideoCaptureDevice(CurrentCamera.MonikerString);
// 设置视频源事件处理
_videoSource.NewFrame += VideoSource_NewFrame;
// 启动视频源
_videoSource.Start();
_isCapturing = true;
LogHelper.WriteLogToFile($"开始摄像头预览: {CurrentCamera.Name}");
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"启动摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
ErrorOccurred?.Invoke(this, $"启动摄像头预览失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 停止摄像头预览
/// </summary>
public void StopPreview()
{
try
{
if (_videoSource != null && _videoSource.IsRunning)
{
_videoSource.SignalToStop();
_videoSource.WaitForStop();
_videoSource.NewFrame -= VideoSource_NewFrame;
_videoSource = null;
}
_isCapturing = false;
LogHelper.WriteLogToFile("摄像头预览已停止");
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"停止摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 切换到指定摄像头
/// </summary>
/// <param name="cameraIndex">摄像头索引</param>
public bool SwitchCamera(int cameraIndex)
{
try
{
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
{
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
return false;
}
return StartPreview(cameraIndex);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
ErrorOccurred?.Invoke(this, $"切换摄像头失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 获取当前帧的BitmapSource(WPF格式),直接返回可用的WPF位图
/// </summary>
public BitmapSource GetCurrentFrameAsBitmapSource()
{
lock (_frameLock)
{
if (_currentFrame == null)
return null;
try
{
// 验证位图有效性
if (_currentFrame.Width <= 0 || _currentFrame.Height <= 0)
return null;
// 使用更安全的方法转换位图
var bitmapData = _currentFrame.LockBits(
new Rectangle(0, 0, _currentFrame.Width, _currentFrame.Height),
ImageLockMode.ReadOnly,
_currentFrame.PixelFormat);
try
{
// 根据像素格式选择合适的WPF像素格式
System.Windows.Media.PixelFormat wpfPixelFormat;
switch (_currentFrame.PixelFormat)
{
case PixelFormat.Format24bppRgb:
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24;
break;
case PixelFormat.Format32bppArgb:
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgra32;
break;
case PixelFormat.Format32bppRgb:
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr32;
break;
default:
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24;
break;
}
var bitmapSource = BitmapSource.Create(
bitmapData.Width,
bitmapData.Height,
_currentFrame.HorizontalResolution,
_currentFrame.VerticalResolution,
wpfPixelFormat,
null,
bitmapData.Scan0,
bitmapData.Stride * bitmapData.Height,
bitmapData.Stride);
bitmapSource.Freeze();
return bitmapSource;
}
finally
{
_currentFrame.UnlockBits(bitmapData);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"转换帧为BitmapSource失败: {ex.Message}", LogHelper.LogType.Error);
return null;
}
}
}
/// <summary>
/// 视频源新帧事件处理
/// </summary>
private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
try
{
lock (_frameLock)
{
// 释放之前的帧
_currentFrame?.Dispose();
// 创建新的位图,避免Clone的问题
var sourceFrame = eventArgs.Frame;
if (sourceFrame != null)
{
try
{
var width = sourceFrame.Width;
var height = sourceFrame.Height;
if (width > 0 && height > 0)
{
_currentFrame = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (var graphics = Graphics.FromImage(_currentFrame))
{
graphics.DrawImage(sourceFrame, 0, 0);
}
}
else
{
_currentFrame = null;
}
}
catch (Exception frameEx)
{
LogHelper.WriteLogToFile($"处理源帧失败: {frameEx.Message}", LogHelper.LogType.Error);
_currentFrame = null;
}
}
else
{
_currentFrame = null;
}
}
// 在UI线程中触发事件
_dispatcher.BeginInvoke(new Action(() =>
{
FrameReceived?.Invoke(this, _currentFrame);
}));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理新帧失败: {ex.Message}", LogHelper.LogType.Error);
ErrorOccurred?.Invoke(this, $"处理新帧失败: {ex.Message}");
}
}
/// <summary>
/// 获取摄像头名称列表
/// </summary>
public List<string> GetCameraNames()
{
return AvailableCameras.Select(camera => camera.Name).ToList();
}
/// <summary>
/// 检查是否有可用摄像头
/// </summary>
public bool HasAvailableCameras()
{
if (AvailableCameras.Count == 0)
{
RefreshCameraList();
}
return AvailableCameras.Count > 0;
}
public void Dispose()
{
StopPreview();
lock (_frameLock)
{
_currentFrame?.Dispose();
}
}
}
}
+2 -2
View File
@@ -17,7 +17,7 @@ namespace Ink_Canvas.Helpers
// 文件路径策略
private static readonly string DeviceIdFilePath = Path.Combine(App.RootPath, "device_id.dat");
private static readonly string UsageStatsFilePath = Path.Combine(App.RootPath, "usage_stats.enc");
private static readonly string UsageStatsBackupPath = Path.Combine(App.RootPath, "saves", "usage_stats_backup.enc");
private static readonly string UsageStatsBackupPath = Path.Combine(App.RootPath, "Saves", "usage_stats_backup.enc");
private static readonly string DeviceId;
private static readonly object fileLock = new object();
@@ -1092,7 +1092,7 @@ namespace Ink_Canvas.Helpers
{
int versionDiff = CalculateVersionGenerationDifference(localVersion, updateVersion);
LogHelper.WriteLogToFile($"DeviceIdentifier | 无法获取版本发布时间,使用版本号差异判断 - 本地版本: {localVersion}, 远程版本: {updateVersion}, 代数差异: {versionDiff}");
// 当版本号代数差异大于3时自动更新
if (versionDiff > 3)
{
+141 -38
View File
@@ -1,12 +1,12 @@
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using Microsoft.Win32;
using System.Threading;
using System.Text;
using System.Threading;
using System.Windows;
namespace Ink_Canvas.Helpers
{
@@ -19,11 +19,12 @@ namespace Ink_Canvas.Helpers
private const string FileTypeName = "InkCanvasStrokesFile";
private const string AppName = "Ink Canvas";
private const string AppDescription = "Ink Canvas Strokes File";
// IPC相关常量
private const string IpcMutexName = "InkCanvasFileAssociationIpc";
private const string IpcEventName = "InkCanvasFileAssociationEvent";
private const string IpcFilePrefix = "InkCanvasFileAssociation_";
private const string IpcBoardModePrefix = "InkCanvasBoardMode_";
private const int IpcTimeout = 5000; // 5秒超时
/// <summary>
@@ -34,19 +35,19 @@ namespace Ink_Canvas.Helpers
try
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
// 注册文件类型
using (RegistryKey fileTypeKey = Registry.ClassesRoot.CreateSubKey(FileTypeName))
{
fileTypeKey.SetValue("", AppDescription);
fileTypeKey.SetValue("FriendlyTypeName", AppDescription);
// 设置默认图标
using (RegistryKey defaultIconKey = fileTypeKey.CreateSubKey("DefaultIcon"))
{
defaultIconKey.SetValue("", $"\"{exePath}\",0");
}
// 设置打开命令
using (RegistryKey shellKey = fileTypeKey.CreateSubKey("shell"))
using (RegistryKey openKey = shellKey.CreateSubKey("open"))
@@ -55,16 +56,16 @@ namespace Ink_Canvas.Helpers
commandKey.SetValue("", $"\"{exePath}\" \"%1\"");
}
}
// 注册文件扩展名
using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(FileExtension))
{
extensionKey.SetValue("", FileTypeName);
}
// 刷新系统文件关联缓存
RefreshSystemFileAssociations();
LogHelper.WriteLogToFile($"成功注册{FileExtension}文件关联", LogHelper.LogType.Event);
return true;
}
@@ -94,13 +95,13 @@ namespace Ink_Canvas.Helpers
{
// 删除文件扩展名关联
Registry.ClassesRoot.DeleteSubKeyTree(FileExtension, false);
// 删除文件类型定义
Registry.ClassesRoot.DeleteSubKeyTree(FileTypeName, false);
// 刷新系统文件关联缓存
RefreshSystemFileAssociations();
LogHelper.WriteLogToFile($"成功注销{FileExtension}文件关联", LogHelper.LogType.Event);
return true;
}
@@ -121,21 +122,21 @@ namespace Ink_Canvas.Helpers
using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(FileExtension))
{
if (extensionKey == null) return false;
string fileType = extensionKey.GetValue("") as string;
if (string.IsNullOrEmpty(fileType)) return false;
using (RegistryKey fileTypeKey = Registry.ClassesRoot.OpenSubKey(fileType))
{
if (fileTypeKey == null) return false;
using (RegistryKey shellKey = fileTypeKey.OpenSubKey("shell\\open\\command"))
{
if (shellKey == null) return false;
string command = shellKey.GetValue("") as string;
if (string.IsNullOrEmpty(command)) return false;
// 检查命令是否指向当前应用程序
string currentExePath = Process.GetCurrentProcess().MainModule.FileName;
return command.Contains(currentExePath);
@@ -147,7 +148,7 @@ namespace Ink_Canvas.Helpers
{
LogHelper.WriteLogToFile($"检查文件关联状态时出错: {ex.Message}", LogHelper.LogType.Error);
}
return false;
}
@@ -184,11 +185,11 @@ namespace Ink_Canvas.Helpers
public static string GetIcstkFileFromArgs(string[] args)
{
if (args == null || args.Length == 0) return null;
foreach (string arg in args)
{
if (string.IsNullOrEmpty(arg)) continue;
// 检查是否为.icstk文件
if (Path.GetExtension(arg).Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
{
@@ -204,7 +205,7 @@ namespace Ink_Canvas.Helpers
}
}
}
return null;
}
@@ -218,24 +219,24 @@ namespace Ink_Canvas.Helpers
try
{
LogHelper.WriteLogToFile($"尝试通过IPC发送文件路径给已运行实例: {filePath}", LogHelper.LogType.Event);
// 创建IPC文件
string tempDir = Path.GetTempPath();
string ipcFileName = IpcFilePrefix + Guid.NewGuid().ToString("N") + ".tmp";
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
// 写入文件路径到IPC文件
File.WriteAllText(ipcFilePath, filePath, Encoding.UTF8);
// 创建事件通知已运行实例
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
{
ipcEvent.Set();
}
// 等待一段时间让已运行实例处理文件
Thread.Sleep(1000);
// 清理IPC文件
try
{
@@ -248,7 +249,7 @@ namespace Ink_Canvas.Helpers
{
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
}
LogHelper.WriteLogToFile("IPC文件路径发送完成", LogHelper.LogType.Event);
return true;
}
@@ -259,6 +260,56 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 尝试通过IPC将白板模式命令发送给已运行的实例
/// </summary>
/// <returns>是否成功发送</returns>
public static bool TrySendBoardModeCommandToExistingInstance()
{
try
{
LogHelper.WriteLogToFile("尝试通过IPC发送白板模式命令给已运行实例", LogHelper.LogType.Event);
// 创建IPC文件
string tempDir = Path.GetTempPath();
string ipcFileName = IpcBoardModePrefix + Guid.NewGuid().ToString("N") + ".tmp";
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
// 写入白板模式命令到IPC文件
File.WriteAllText(ipcFilePath, "BOARD_MODE", Encoding.UTF8);
// 创建事件通知已运行实例
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
{
ipcEvent.Set();
}
// 等待一段时间让已运行实例处理命令
Thread.Sleep(1000);
// 清理IPC文件
try
{
if (File.Exists(ipcFilePath))
{
File.Delete(ipcFilePath);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
}
LogHelper.WriteLogToFile("IPC白板模式命令发送完成", LogHelper.LogType.Event);
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"通过IPC发送白板模式命令失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 启动IPC监听器,等待其他实例发送文件路径
/// </summary>
@@ -271,7 +322,7 @@ namespace Ink_Canvas.Helpers
try
{
LogHelper.WriteLogToFile("启动IPC监听器", LogHelper.LogType.Event);
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
{
while (true)
@@ -281,11 +332,11 @@ namespace Ink_Canvas.Helpers
{
// 处理IPC文件
ProcessIpcFiles();
// 重置事件
ipcEvent.Reset();
}
// 检查应用是否还在运行
if (Application.Current == null || Application.Current.Dispatcher == null)
{
@@ -299,7 +350,7 @@ namespace Ink_Canvas.Helpers
LogHelper.WriteLogToFile($"IPC监听器出错: {ex.Message}", LogHelper.LogType.Error);
}
});
ipcThread.IsBackground = true;
ipcThread.Start();
}
@@ -317,19 +368,20 @@ namespace Ink_Canvas.Helpers
try
{
string tempDir = Path.GetTempPath();
string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp");
// 处理文件路径IPC文件
string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp");
foreach (string ipcFile in ipcFiles)
{
try
{
// 读取文件路径
string filePath = File.ReadAllText(ipcFile, Encoding.UTF8);
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
{
LogHelper.WriteLogToFile($"IPC接收到文件路径: {filePath}", LogHelper.LogType.Event);
// 在UI线程中处理文件打开
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
@@ -348,14 +400,65 @@ namespace Ink_Canvas.Helpers
}
}));
}
// 删除IPC文件
File.Delete(ipcFile);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
// 尝试删除损坏的IPC文件
try
{
if (File.Exists(ipcFile))
{
File.Delete(ipcFile);
}
}
catch { }
}
}
// 处理白板模式命令IPC文件
string[] boardModeFiles = Directory.GetFiles(tempDir, IpcBoardModePrefix + "*.tmp");
foreach (string ipcFile in boardModeFiles)
{
try
{
// 读取命令内容
string command = File.ReadAllText(ipcFile, Encoding.UTF8);
if (command == "BOARD_MODE")
{
LogHelper.WriteLogToFile("IPC接收到白板模式命令", LogHelper.LogType.Event);
// 在UI线程中处理白板模式切换
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// 获取主窗口并切换到白板模式
if (Application.Current.MainWindow is MainWindow mainWindow)
{
mainWindow.SwitchToBoardMode();
mainWindow.ShowNotification("已切换到白板模式");
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"IPC处理白板模式切换失败: {ex.Message}", LogHelper.LogType.Error);
}
}));
}
// 删除IPC文件
File.Delete(ipcFile);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理白板模式IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
// 尝试删除损坏的IPC文件
try
{
@@ -377,4 +480,4 @@ namespace Ink_Canvas.Helpers
[DllImport("shell32.dll")]
private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
}
}
}
@@ -0,0 +1,762 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 悬浮窗拦截器 - 检测和隐藏指定的悬浮窗
/// </summary>
public class FloatingWindowInterceptor : IDisposable
{
#region Windows API Declarations
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll")]
private static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out ForegroundWindowInfo.RECT lpRect);
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
private static extern int GetProcessImageFileName(IntPtr hProcess, StringBuilder lpImageFileName, int nSize);
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
private const int SW_MINIMIZE = 6;
private const int SW_RESTORE = 9;
private const int GWL_EXSTYLE = -20;
private const uint WS_EX_TOOLWINDOW = 0x00000080;
private const uint WS_EX_APPWINDOW = 0x00040000;
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_NOZORDER = 0x0004;
private const uint SWP_HIDEWINDOW = 0x0080;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
private const uint PROCESS_VM_READ = 0x0010;
#endregion
#region
/// <summary>
/// 拦截规则类型
/// </summary>
public enum InterceptType
{
/// <summary>
/// 希沃白板3 桌面悬浮窗
/// </summary>
SeewoWhiteboard3Floating,
/// <summary>
/// 希沃白板5 桌面悬浮窗
/// </summary>
SeewoWhiteboard5Floating,
/// <summary>
/// 希沃白板5C 桌面悬浮窗
/// </summary>
SeewoWhiteboard5CFloating,
/// <summary>
/// 希沃品课教师端 桌面悬浮窗
/// </summary>
SeewoPincoSideBarFloating,
/// <summary>
/// 希沃品课教师端 画笔悬浮窗(包括PPT控件)
/// </summary>
SeewoPincoDrawingFloating,
/// <summary>
/// 希沃PPT小工具
/// </summary>
SeewoPPTFloating,
/// <summary>
/// AiClass 桌面悬浮窗
/// </summary>
AiClassFloating,
/// <summary>
/// 鸿合屏幕书写
/// </summary>
HiteAnnotationFloating,
/// <summary>
/// 畅言智慧课堂 桌面悬浮窗
/// </summary>
ChangYanFloating,
/// <summary>
/// 畅言智慧课堂 PPT悬浮窗
/// </summary>
ChangYanPptFloating,
/// <summary>
/// 天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)
/// </summary>
IntelligentClassFloating,
/// <summary>
/// 希沃桌面 画笔悬浮窗
/// </summary>
SeewoDesktopAnnotationFloating,
/// <summary>
/// 希沃桌面 侧栏悬浮窗
/// </summary>
SeewoDesktopSideBarFloating
}
/// <summary>
/// 拦截规则
/// </summary>
public class InterceptRule
{
public InterceptType Type { get; set; }
public string ProcessName { get; set; }
public string WindowTitlePattern { get; set; }
public string ClassNamePattern { get; set; }
public bool IsEnabled { get; set; }
public bool RequiresAdmin { get; set; }
public string Description { get; set; }
}
#endregion
#region
private readonly Dictionary<InterceptType, InterceptRule> _interceptRules;
private readonly Dictionary<IntPtr, InterceptType> _interceptedWindows;
private readonly Timer _scanTimer;
private readonly Dispatcher _dispatcher;
private bool _isRunning;
private bool _disposed;
#endregion
#region
public bool IsRunning => _isRunning;
#endregion
#region
public event EventHandler<WindowInterceptedEventArgs> WindowIntercepted;
public event EventHandler<WindowRestoredEventArgs> WindowRestored;
#endregion
#region
public FloatingWindowInterceptor()
{
_interceptRules = new Dictionary<InterceptType, InterceptRule>();
_interceptedWindows = new Dictionary<IntPtr, InterceptType>();
_dispatcher = Dispatcher.CurrentDispatcher;
InitializeRules();
_scanTimer = new Timer(ScanForWindows, null, Timeout.Infinite, Timeout.Infinite);
}
#endregion
#region
private void InitializeRules()
{
// 希沃白板3 桌面悬浮窗
_interceptRules[InterceptType.SeewoWhiteboard3Floating] = new InterceptRule
{
Type = InterceptType.SeewoWhiteboard3Floating,
ProcessName = "EasiNote",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板3 桌面悬浮窗"
};
// 希沃白板5 桌面悬浮窗
_interceptRules[InterceptType.SeewoWhiteboard5Floating] = new InterceptRule
{
Type = InterceptType.SeewoWhiteboard5Floating,
ProcessName = "EasiNote",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板5 桌面悬浮窗"
};
// 希沃白板5C 桌面悬浮窗
_interceptRules[InterceptType.SeewoWhiteboard5CFloating] = new InterceptRule
{
Type = InterceptType.SeewoWhiteboard5CFloating,
ProcessName = "EasiNote5C",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃白板5C 桌面悬浮窗"
};
// 希沃品课教师端 桌面悬浮窗
_interceptRules[InterceptType.SeewoPincoSideBarFloating] = new InterceptRule
{
Type = InterceptType.SeewoPincoSideBarFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃品课教师端 桌面悬浮窗"
};
// 希沃品课教师端 画笔悬浮窗
_interceptRules[InterceptType.SeewoPincoDrawingFloating] = new InterceptRule
{
Type = InterceptType.SeewoPincoDrawingFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃品课教师端 画笔悬浮窗(包括PPT控件)"
};
// 希沃PPT小工具
_interceptRules[InterceptType.SeewoPPTFloating] = new InterceptRule
{
Type = InterceptType.SeewoPPTFloating,
ProcessName = "SeewoPPT",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃PPT小工具"
};
// AiClass 桌面悬浮窗
_interceptRules[InterceptType.AiClassFloating] = new InterceptRule
{
Type = InterceptType.AiClassFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "AiClass 桌面悬浮窗"
};
// 鸿合屏幕书写
_interceptRules[InterceptType.HiteAnnotationFloating] = new InterceptRule
{
Type = InterceptType.HiteAnnotationFloating,
ProcessName = "HiteVision",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "鸿合屏幕书写"
};
// 畅言智慧课堂 桌面悬浮窗
_interceptRules[InterceptType.ChangYanFloating] = new InterceptRule
{
Type = InterceptType.ChangYanFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 桌面悬浮窗"
};
// 畅言智慧课堂 PPT悬浮窗
_interceptRules[InterceptType.ChangYanPptFloating] = new InterceptRule
{
Type = InterceptType.ChangYanPptFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = true,
Description = "畅言智慧课堂 PPT悬浮窗"
};
// 天喻教育云互动课堂 桌面悬浮窗
_interceptRules[InterceptType.IntelligentClassFloating] = new InterceptRule
{
Type = InterceptType.IntelligentClassFloating,
ProcessName = "ClassIn",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "天喻教育云互动课堂 桌面悬浮窗(包括PPT控件)"
};
// 希沃桌面 画笔悬浮窗
_interceptRules[InterceptType.SeewoDesktopAnnotationFloating] = new InterceptRule
{
Type = InterceptType.SeewoDesktopAnnotationFloating,
ProcessName = "Seewo",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = false,
Description = "希沃桌面 画笔悬浮窗"
};
// 希沃桌面 侧栏悬浮窗
_interceptRules[InterceptType.SeewoDesktopSideBarFloating] = new InterceptRule
{
Type = InterceptType.SeewoDesktopSideBarFloating,
ProcessName = "Seewo",
WindowTitlePattern = "",
ClassNamePattern = "",
IsEnabled = true,
RequiresAdmin = true,
Description = "希沃桌面 侧栏悬浮窗"
};
}
#endregion
#region
/// <summary>
/// 启动拦截器
/// </summary>
public void Start(int scanIntervalMs = 5000)
{
if (_isRunning) return;
_isRunning = true;
_scanTimer.Change(0, scanIntervalMs);
}
/// <summary>
/// 停止拦截器
/// </summary>
public void Stop()
{
if (!_isRunning) return;
_isRunning = false;
_scanTimer.Change(Timeout.Infinite, Timeout.Infinite);
// 恢复所有被拦截的窗口
RestoreAllWindows();
}
/// <summary>
/// 设置拦截规则
/// </summary>
public void SetInterceptRule(InterceptType type, bool enabled)
{
if (_interceptRules.ContainsKey(type))
{
_interceptRules[type].IsEnabled = enabled;
}
}
/// <summary>
/// 获取拦截规则
/// </summary>
public InterceptRule GetInterceptRule(InterceptType type)
{
return _interceptRules.ContainsKey(type) ? _interceptRules[type] : null;
}
/// <summary>
/// 获取所有拦截规则
/// </summary>
public Dictionary<InterceptType, InterceptRule> GetAllRules()
{
return new Dictionary<InterceptType, InterceptRule>(_interceptRules);
}
/// <summary>
/// 手动扫描一次
/// </summary>
public void ScanOnce()
{
ScanForWindows(null);
}
/// <summary>
/// 恢复所有被拦截的窗口
/// </summary>
public void RestoreAllWindows()
{
var windowsToRestore = new List<IntPtr>(_interceptedWindows.Keys);
foreach (var hWnd in windowsToRestore)
{
RestoreWindow(hWnd);
}
}
/// <summary>
/// 恢复指定窗口
/// </summary>
public bool RestoreWindow(IntPtr hWnd)
{
if (!_interceptedWindows.ContainsKey(hWnd)) return false;
if (IsWindow(hWnd))
{
ShowWindow(hWnd, SW_RESTORE);
_interceptedWindows.Remove(hWnd);
WindowRestored?.Invoke(this, new WindowRestoredEventArgs
{
WindowHandle = hWnd,
InterceptType = _interceptedWindows[hWnd]
});
return true;
}
_interceptedWindows.Remove(hWnd);
return false;
}
#endregion
#region
private void ScanForWindows(object state)
{
if (!_isRunning) return;
try
{
EnumWindows(EnumWindowsCallback, IntPtr.Zero);
}
catch (Exception ex)
{
// 记录错误但不中断扫描
LogHelper.WriteLogToFile($"扫描窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
}
}
private bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
{
try
{
// 基本检查
if (!IsWindow(hWnd) || !IsWindowVisible(hWnd)) return true;
// 检查是否已经被拦截
if (_interceptedWindows.ContainsKey(hWnd)) return true;
// 获取窗口信息
var windowInfo = GetWindowInfo(hWnd);
if (windowInfo == null) return true;
// 检查窗口样式,过滤掉系统窗口和主窗口
var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
var style = GetWindowLong(hWnd, -16); // GWL_STYLE
// 跳过工具窗口
if ((exStyle & WS_EX_TOOLWINDOW) != 0)
{
return true;
}
// 跳过主窗口(有标题栏和系统菜单的窗口)
const uint WS_CAPTION = 0x00C00000;
const uint WS_SYSMENU = 0x00080000;
if ((style & WS_CAPTION) != 0 && (style & WS_SYSMENU) != 0)
{
return true;
}
// 检查窗口大小,跳过过大的窗口
var rect = new ForegroundWindowInfo.RECT();
GetWindowRect(hWnd, out rect);
var width = rect.Right - rect.Left;
var height = rect.Bottom - rect.Top;
if (width > 600 || height > 400)
{
return true;
}
// 检查是否匹配拦截规则
foreach (var rule in _interceptRules.Values)
{
if (!rule.IsEnabled) continue;
if (MatchesRule(windowInfo, rule))
{
InterceptWindow(hWnd, rule);
break;
}
}
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
return true;
}
}
private WindowInfo GetWindowInfo(IntPtr hWnd)
{
try
{
// 获取进程ID
GetWindowThreadProcessId(hWnd, out uint processId);
if (processId == 0) return null;
// 获取进程信息
var process = Process.GetProcessById((int)processId);
if (process == null) return null;
// 获取窗口标题
var titleBuilder = new StringBuilder(256);
GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity);
// 获取窗口类名
var classBuilder = new StringBuilder(256);
GetClassName(hWnd, classBuilder, classBuilder.Capacity);
return new WindowInfo
{
Handle = hWnd,
ProcessId = processId,
ProcessName = process.ProcessName,
WindowTitle = titleBuilder.ToString(),
ClassName = classBuilder.ToString(),
Process = process
};
}
catch
{
return null;
}
}
private bool MatchesRule(WindowInfo windowInfo, InterceptRule rule)
{
try
{
// 检查进程名(如果指定了进程名)
if (!string.IsNullOrEmpty(rule.ProcessName))
{
if (!windowInfo.ProcessName.ToLower().Contains(rule.ProcessName.ToLower()))
{
return false;
}
}
// 检查窗口标题(如果指定了模式)
if (!string.IsNullOrEmpty(rule.WindowTitlePattern))
{
if (!windowInfo.WindowTitle.ToLower().Contains(rule.WindowTitlePattern.ToLower()))
{
return false;
}
}
// 检查类名(如果指定了模式)
if (!string.IsNullOrEmpty(rule.ClassNamePattern))
{
if (!windowInfo.ClassName.ToLower().Contains(rule.ClassNamePattern.ToLower()))
{
return false;
}
}
// 如果所有检查都通过,就认为是目标窗口
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"匹配规则时发生错误: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
private void InterceptWindow(IntPtr hWnd, InterceptRule rule)
{
try
{
// 使用多种方法隐藏窗口
// 方法1:移动到屏幕外
SetWindowPos(hWnd, IntPtr.Zero, -2000, -2000, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_HIDEWINDOW);
// 方法2:最小化窗口
ShowWindow(hWnd, SW_MINIMIZE);
// 方法3:隐藏窗口
ShowWindow(hWnd, SW_HIDE);
// 记录拦截的窗口
_interceptedWindows[hWnd] = rule.Type;
// 触发事件
WindowIntercepted?.Invoke(this, new WindowInterceptedEventArgs
{
WindowHandle = hWnd,
InterceptType = rule.Type,
Rule = rule,
WindowTitle = GetWindowTitle(hWnd)
});
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"拦截窗口时发生错误: {ex.Message}", LogHelper.LogType.Error);
}
}
private string GetWindowTitle(IntPtr hWnd)
{
try
{
var titleBuilder = new StringBuilder(256);
GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity);
return titleBuilder.ToString();
}
catch
{
return "Unknown";
}
}
private bool IsMainWindow(IntPtr hWnd)
{
try
{
// 检查是否有父窗口
var parent = GetWindow(hWnd, 4); // GW_OWNER
if (parent != IntPtr.Zero) return false;
// 检查窗口样式
var style = GetWindowLong(hWnd, -16); // GWL_STYLE
var exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
// 主窗口通常有 WS_CAPTION 和 WS_SYSMENU
const uint WS_CAPTION = 0x00C00000;
const uint WS_SYSMENU = 0x00080000;
if ((style & WS_CAPTION) != 0 && (style & WS_SYSMENU) != 0)
{
return true; // 这可能是主窗口
}
// 检查窗口大小,主窗口通常比较大
var rect = new ForegroundWindowInfo.RECT();
GetWindowRect(hWnd, out rect);
var width = rect.Right - rect.Left;
var height = rect.Bottom - rect.Top;
// 如果窗口很大,可能是主窗口
if (width > 800 && height > 600)
{
return true;
}
return false;
}
catch
{
return false;
}
}
#endregion
#region
private class WindowInfo
{
public IntPtr Handle { get; set; }
public uint ProcessId { get; set; }
public string ProcessName { get; set; }
public string WindowTitle { get; set; }
public string ClassName { get; set; }
public Process Process { get; set; }
}
#endregion
#region
public class WindowInterceptedEventArgs : EventArgs
{
public IntPtr WindowHandle { get; set; }
public InterceptType InterceptType { get; set; }
public InterceptRule Rule { get; set; }
public string WindowTitle { get; set; }
}
public class WindowRestoredEventArgs : EventArgs
{
public IntPtr WindowHandle { get; set; }
public InterceptType InterceptType { get; set; }
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
Stop();
_scanTimer?.Dispose();
// 恢复所有被拦截的窗口
RestoreAllWindows();
_disposed = true;
}
#endregion
}
}
+102 -20
View File
@@ -21,7 +21,7 @@ namespace Ink_Canvas.Helpers
private bool _hotkeysShouldBeRegistered = true; // 启动时注册热键
// 配置文件路径
private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "HotkeyConfig.json");
private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "Configs", "HotkeyConfig.json");
#endregion
#region Constructor
@@ -30,6 +30,9 @@ namespace Ink_Canvas.Helpers
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
_registeredHotkeys = new Dictionary<string, HotkeyInfo>();
_hotkeysShouldBeRegistered = true; // 启动时注册热键
// 启动时确保配置文件存在
EnsureConfigFileExists();
}
#endregion
@@ -280,6 +283,17 @@ namespace Ink_Canvas.Helpers
return;
}
// 如果配置文件不存在,先创建默认配置文件
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning);
LogHelper.WriteLogToFile("配置文件不存在,创建默认配置文件并注册默认快捷键");
CreateDefaultConfigFile();
RegisterDefaultHotkeys();
_hotkeysShouldBeRegistered = true;
return;
}
// 尝试从配置文件加载
if (LoadHotkeysFromConfigFile())
{
@@ -289,19 +303,9 @@ namespace Ink_Canvas.Helpers
}
else
{
// 如果配置文件存在加载失败,使用默认快捷键
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile("配置文件不存在,注册默认快捷键");
RegisterDefaultHotkeys();
_hotkeysShouldBeRegistered = true;
}
else
{
LogHelper.WriteLogToFile("配置文件存在但加载失败,回退到默认快捷键", LogHelper.LogType.Warning);
RegisterDefaultHotkeys();
_hotkeysShouldBeRegistered = true;
}
LogHelper.WriteLogToFile("配置文件存在加载失败,回退到默认快捷键", LogHelper.LogType.Warning);
RegisterDefaultHotkeys();
_hotkeysShouldBeRegistered = true;
}
}
catch (Exception ex)
@@ -353,9 +357,6 @@ namespace Ink_Canvas.Helpers
}
else
{
LogHelper.WriteLogToFile("快捷键注册功能已经启用,重新加载快捷键设置");
// 即使已经启用,也要重新加载快捷键设置以确保快捷键正常工作
LoadHotkeysFromSettings();
}
}
catch (Exception ex)
@@ -375,7 +376,6 @@ namespace Ink_Canvas.Helpers
if (_hotkeysShouldBeRegistered)
{
_hotkeysShouldBeRegistered = false;
LogHelper.WriteLogToFile("禁用快捷键注册功能");
// 注销所有快捷键
UnregisterAllHotkeys();
@@ -413,14 +413,12 @@ namespace Ink_Canvas.Helpers
{
// 鼠标模式下禁用快捷键,让键盘操作放行
DisableHotkeyRegistration();
LogHelper.WriteLogToFile("切换到鼠标模式,禁用快捷键以放行键盘操作");
}
}
else
{
// 非鼠标模式下启用快捷键
EnableHotkeyRegistration();
LogHelper.WriteLogToFile("切换到非鼠标模式,启用快捷键");
}
}
catch (Exception ex)
@@ -505,6 +503,90 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 确保配置文件存在,如果不存在则创建
/// </summary>
private void EnsureConfigFileExists()
{
try
{
// 如果配置文件不存在,创建默认配置文件
if (!File.Exists(HotkeyConfigFile))
{
LogHelper.WriteLogToFile($"快捷键配置文件不存在,创建默认配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event);
CreateDefaultConfigFile();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"确保快捷键配置文件存在时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 创建默认的快捷键配置文件
/// </summary>
private void CreateDefaultConfigFile()
{
try
{
// 确保配置目录存在
string configDir = Path.GetDirectoryName(HotkeyConfigFile);
if (!Directory.Exists(configDir))
{
Directory.CreateDirectory(configDir);
}
// 创建默认配置对象
var config = new HotkeyConfig
{
Version = "1.0",
LastModified = DateTime.Now,
Hotkeys = new List<HotkeyConfigItem>()
};
// 添加默认快捷键配置
config.Hotkeys.AddRange(new[]
{
new HotkeyConfigItem { Name = "Undo", Key = Key.Z, Modifiers = ModifierKeys.Control },
new HotkeyConfigItem { Name = "Redo", Key = Key.Y, Modifiers = ModifierKeys.Control },
new HotkeyConfigItem { Name = "Clear", Key = Key.E, Modifiers = ModifierKeys.Control },
new HotkeyConfigItem { Name = "Paste", Key = Key.V, Modifiers = ModifierKeys.Control },
new HotkeyConfigItem { Name = "SelectTool", Key = Key.S, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "DrawTool", Key = Key.D, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "EraserTool", Key = Key.E, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "BlackboardTool", Key = Key.B, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "QuitDrawTool", Key = Key.Q, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Pen1", Key = Key.D1, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Pen2", Key = Key.D2, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Pen3", Key = Key.D3, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Pen4", Key = Key.D4, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Pen5", Key = Key.D5, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "DrawLine", Key = Key.L, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Screenshot", Key = Key.C, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Hide", Key = Key.V, Modifiers = ModifierKeys.Alt },
new HotkeyConfigItem { Name = "Exit", Key = Key.Escape, Modifiers = ModifierKeys.None }
});
// 序列化为JSON
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
};
string jsonContent = JsonConvert.SerializeObject(config, settings);
// 写入配置文件
File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
LogHelper.WriteLogToFile($"已创建默认快捷键配置文件: {HotkeyConfigFile}", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"创建默认快捷键配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 从配置文件加载快捷键设置
/// </summary>
+19 -13
View File
@@ -71,37 +71,43 @@ namespace Ink_Canvas.Helpers
}
/// <summary>
/// 应用质量设置
/// 应用质量设置
/// </summary>
public void ApplyQualitySettings()
{
switch (Quality)
{
case SmoothingQuality.Performance:
SmoothingStrength = 0.2;
ResampleInterval = 4.0;
InterpolationSteps = 6;
SmoothingStrength = 0.15;
ResampleInterval = 5.0;
InterpolationSteps = 4;
UseAdaptiveInterpolation = false;
CurveTension = 0.2;
CurveTension = 0.15;
MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2);
UseHardwareAcceleration = true;
UseAsyncProcessing = true;
break;
case SmoothingQuality.Balanced:
SmoothingStrength = 0.4;
ResampleInterval = 2.5;
InterpolationSteps = 12;
SmoothingStrength = 0.3;
ResampleInterval = 3.0;
InterpolationSteps = 8;
UseAdaptiveInterpolation = true;
CurveTension = 0.3;
CurveTension = 0.25;
MaxConcurrentTasks = Environment.ProcessorCount;
UseHardwareAcceleration = true;
UseAsyncProcessing = true;
break;
case SmoothingQuality.Quality:
SmoothingStrength = 0.6;
ResampleInterval = 1.5;
InterpolationSteps = 20;
SmoothingStrength = 0.5;
ResampleInterval = 2.0;
InterpolationSteps = 15;
UseAdaptiveInterpolation = true;
CurveTension = 0.4;
CurveTension = 0.35;
MaxConcurrentTasks = Environment.ProcessorCount;
UseHardwareAcceleration = true;
UseAsyncProcessing = true;
break;
}
}
+666
View File
@@ -0,0 +1,666 @@
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;
using System.Text;
using System.Windows.Ink;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// 多PPT墨迹管理器 - 支持多个PPT窗口分别管理墨迹
/// </summary>
public class MultiPPTInkManager : IDisposable
{
#region Properties
public bool IsAutoSaveEnabled { get; set; } = true;
public string AutoSaveLocation { get; set; } = "";
public PPTManager PPTManager { get; set; }
#endregion
#region Private Fields
private readonly Dictionary<string, PPTInkManager> _presentationManagers;
private readonly Dictionary<string, PresentationInfo> _presentationInfos;
private readonly object _lockObject = new object();
private bool _disposed;
private string _currentActivePresentationId = "";
#endregion
#region Constructor
public MultiPPTInkManager()
{
_presentationManagers = new Dictionary<string, PPTInkManager>();
_presentationInfos = new Dictionary<string, PresentationInfo>();
}
#endregion
#region Public Methods
/// <summary>
/// 初始化新的演示文稿
/// </summary>
public void InitializePresentation(Presentation presentation)
{
if (presentation == null) return;
lock (_lockObject)
{
try
{
var presentationId = GeneratePresentationId(presentation);
// 如果已存在该演示文稿的管理器,先清理
if (_presentationManagers.ContainsKey(presentationId))
{
_presentationManagers[presentationId].Dispose();
_presentationManagers.Remove(presentationId);
}
// 创建新的墨迹管理器
var inkManager = new PPTInkManager();
inkManager.IsAutoSaveEnabled = IsAutoSaveEnabled;
inkManager.AutoSaveLocation = AutoSaveLocation;
inkManager.InitializePresentation(presentation);
// 保存管理器和演示文稿信息
_presentationManagers[presentationId] = inkManager;
_presentationInfos[presentationId] = new PresentationInfo
{
Id = presentationId,
Name = presentation.Name,
FullName = presentation.FullName,
SlideCount = presentation.Slides.Count,
CreatedTime = DateTime.Now,
LastAccessTime = DateTime.Now
};
// 设置为当前活跃的演示文稿
_currentActivePresentationId = presentationId;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化多PPT墨迹管理失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 切换到指定的演示文稿
/// </summary>
public bool SwitchToPresentation(Presentation presentation)
{
if (presentation == null) return false;
lock (_lockObject)
{
try
{
var presentationId = GeneratePresentationId(presentation);
if (_presentationManagers.ContainsKey(presentationId))
{
// 如果切换的是不同的演示文稿,先保存当前活跃演示文稿的墨迹
if (!string.IsNullOrEmpty(_currentActivePresentationId) &&
_currentActivePresentationId != presentationId)
{
var currentManager = GetCurrentManager();
if (currentManager != null)
{
// 获取当前活跃的演示文稿并保存墨迹
var currentPresentation = GetCurrentActivePresentation();
if (currentPresentation != null)
{
currentManager.SaveAllStrokesToFile(currentPresentation);
LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace);
}
}
}
_currentActivePresentationId = presentationId;
// 更新最后访问时间
if (_presentationInfos.ContainsKey(presentationId))
{
_presentationInfos[presentationId].LastAccessTime = DateTime.Now;
}
if (_currentActivePresentationId != presentationId)
{
LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace);
}
return true;
}
else
{
// 如果不存在,尝试初始化
InitializePresentation(presentation);
return true;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换到演示文稿失败: {ex}", LogHelper.LogType.Error);
return false;
}
}
}
/// <summary>
/// 保存当前页面的墨迹
/// </summary>
public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
{
if (slideIndex <= 0 || strokes == null) return;
lock (_lockObject)
{
try
{
var manager = GetCurrentManager();
if (manager != null)
{
manager.SaveCurrentSlideStrokes(slideIndex, strokes);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 强制保存指定页面的墨迹(忽略锁定状态)
/// </summary>
public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
{
if (slideIndex <= 0 || strokes == null) return;
lock (_lockObject)
{
try
{
var manager = GetCurrentManager();
if (manager != null)
{
manager.ForceSaveSlideStrokes(slideIndex, strokes);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"强制保存页面墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 加载指定页面的墨迹
/// </summary>
public StrokeCollection LoadSlideStrokes(int slideIndex)
{
if (slideIndex <= 0) return new StrokeCollection();
lock (_lockObject)
{
try
{
var manager = GetCurrentManager();
if (manager != null)
{
return manager.LoadSlideStrokes(slideIndex);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载页面墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
return new StrokeCollection();
}
/// <summary>
/// 切换到指定页面并加载墨迹
/// </summary>
public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
{
lock (_lockObject)
{
try
{
var manager = GetCurrentManager();
if (manager != null)
{
return manager.SwitchToSlide(slideIndex, currentStrokes);
}
else
{
LogHelper.WriteLogToFile($"无法获取当前墨迹管理器,页面切换失败: {slideIndex}", LogHelper.LogType.Warning);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
return new StrokeCollection();
}
/// <summary>
/// 保存所有墨迹到文件
/// </summary>
public void SaveAllStrokesToFile(Presentation presentation)
{
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
lock (_lockObject)
{
try
{
var presentationId = GeneratePresentationId(presentation);
if (_presentationManagers.ContainsKey(presentationId))
{
_presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"保存所有墨迹到文件失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 从文件加载已保存的墨迹
/// </summary>
public void LoadSavedStrokes(Presentation presentation)
{
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
lock (_lockObject)
{
try
{
var presentationId = GeneratePresentationId(presentation);
if (_presentationManagers.ContainsKey(presentationId))
{
_presentationManagers[presentationId].LoadSavedStrokes();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 清除指定演示文稿的所有墨迹
/// </summary>
public void ClearPresentationStrokes(Presentation presentation)
{
if (presentation == null) return;
lock (_lockObject)
{
try
{
var presentationId = GeneratePresentationId(presentation);
if (_presentationManagers.ContainsKey(presentationId))
{
_presentationManagers[presentationId].ClearAllStrokes();
LogHelper.WriteLogToFile($"已清除演示文稿墨迹: {presentation.Name}", LogHelper.LogType.Trace);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清除演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 清除所有演示文稿的墨迹
/// </summary>
public void ClearAllStrokes()
{
lock (_lockObject)
{
try
{
foreach (var manager in _presentationManagers.Values)
{
manager?.ClearAllStrokes();
}
LogHelper.WriteLogToFile("已清除所有演示文稿墨迹", LogHelper.LogType.Trace);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清除所有墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 翻页后锁定墨迹写入
/// </summary>
public void LockInkForSlide(int slideIndex)
{
lock (_lockObject)
{
try
{
var manager = GetCurrentManager();
if (manager != null)
{
manager.LockInkForSlide(slideIndex);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"锁定墨迹写入失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 检查是否可以写入墨迹
/// </summary>
public bool CanWriteInk(int currentSlideIndex)
{
lock (_lockObject)
{
try
{
var manager = GetCurrentManager();
if (manager != null)
{
return manager.CanWriteInk(currentSlideIndex);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"检查墨迹写入权限失败: {ex}", LogHelper.LogType.Error);
}
}
return false;
}
/// <summary>
/// 重置当前演示文稿的墨迹锁定状态
/// </summary>
public void ResetCurrentPresentationLockState()
{
lock (_lockObject)
{
try
{
var manager = GetCurrentManager();
if (manager != null)
{
manager.ResetLockState();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"重置墨迹锁定状态失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 移除演示文稿管理器
/// </summary>
public void RemovePresentation(Presentation presentation)
{
if (presentation == null) return;
lock (_lockObject)
{
try
{
var presentationId = GeneratePresentationId(presentation);
if (_presentationManagers.ContainsKey(presentationId))
{
// 保存墨迹到文件
_presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
// 释放资源
_presentationManagers[presentationId].Dispose();
_presentationManagers.Remove(presentationId);
}
if (_presentationInfos.ContainsKey(presentationId))
{
_presentationInfos.Remove(presentationId);
}
// 如果移除的是当前活跃的演示文稿,重置活跃ID
if (_currentActivePresentationId == presentationId)
{
_currentActivePresentationId = "";
}
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
{
}
}
catch (Exception)
{
}
}
}
/// <summary>
/// 获取当前管理的演示文稿数量
/// </summary>
public int GetPresentationCount()
{
lock (_lockObject)
{
return _presentationManagers.Count;
}
}
/// <summary>
/// 获取所有演示文稿信息
/// </summary>
public List<PresentationInfo> GetAllPresentationInfos()
{
lock (_lockObject)
{
return _presentationInfos.Values.ToList();
}
}
/// <summary>
/// 清理长时间未访问的演示文稿管理器
/// </summary>
public void CleanupInactivePresentations(TimeSpan inactiveThreshold)
{
lock (_lockObject)
{
try
{
var inactiveIds = new List<string>();
var cutoffTime = DateTime.Now - inactiveThreshold;
foreach (var info in _presentationInfos.Values)
{
if (info.LastAccessTime < cutoffTime && info.Id != _currentActivePresentationId)
{
inactiveIds.Add(info.Id);
}
}
foreach (var id in inactiveIds)
{
if (_presentationManagers.ContainsKey(id))
{
_presentationManagers[id].Dispose();
_presentationManagers.Remove(id);
}
_presentationInfos.Remove(id);
LogHelper.WriteLogToFile($"已清理非活跃演示文稿: {id}", LogHelper.LogType.Trace);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理非活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
}
}
}
#endregion
#region Private Methods
private PPTInkManager GetCurrentManager()
{
if (string.IsNullOrEmpty(_currentActivePresentationId) ||
!_presentationManagers.ContainsKey(_currentActivePresentationId))
{
return null;
}
return _presentationManagers[_currentActivePresentationId];
}
private Presentation GetCurrentActivePresentation()
{
try
{
// 通过PPTManager获取当前活跃的演示文稿
return PPTManager?.GetCurrentActivePresentation();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
return null;
}
}
private string GeneratePresentationId(Presentation presentation)
{
try
{
// 检查COM对象是否仍然有效
if (presentation == null)
{
return $"invalid_{DateTime.Now.Ticks}";
}
var presentationPath = presentation.FullName;
var fileHash = GetFileHash(presentationPath);
var processId = GetProcessId(presentation);
return $"{presentation.Name}_{fileHash}_{processId}";
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
{
return $"disconnected_{DateTime.Now.Ticks}";
}
return $"error_{DateTime.Now.Ticks}";
}
catch (Exception)
{
return $"unknown_{DateTime.Now.Ticks}";
}
}
private string GetFileHash(string filePath)
{
try
{
if (string.IsNullOrEmpty(filePath)) return "unknown";
using (var md5 = MD5.Create())
{
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath));
return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8);
}
}
catch (Exception)
{
// 所有异常都静默处理,避免日志噪音
return "error";
}
}
private string GetProcessId(Presentation presentation)
{
try
{
// 尝试获取PowerPoint应用程序的进程ID
if (presentation.Application != null)
{
// 通过COM对象获取进程信息
var hwnd = presentation.Application.HWND;
if (hwnd != 0)
{
return hwnd.ToString();
}
}
return "unknown";
}
catch (COMException comEx)
{
// COM对象已失效,这是正常情况,完全静默处理
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
{
return "disconnected";
}
return "error";
}
catch (Exception)
{
return "error";
}
}
#endregion
#region Dispose
public void Dispose()
{
if (!_disposed)
{
lock (_lockObject)
{
foreach (var manager in _presentationManagers.Values)
{
manager?.Dispose();
}
_presentationManagers.Clear();
_presentationInfos.Clear();
}
_disposed = true;
}
}
#endregion
}
/// <summary>
/// 演示文稿信息
/// </summary>
public class PresentationInfo
{
public string Id { get; set; }
public string Name { get; set; }
public string FullName { get; set; }
public int SlideCount { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime LastAccessTime { get; set; }
}
}
+35 -4
View File
@@ -25,10 +25,14 @@ namespace Ink_Canvas.Helpers
}
/// <summary>
/// 用于显示笔迹的类
/// 用于显示笔迹的类
/// </summary>
public class StrokeVisual : DrawingVisual
{
private bool _needsRedraw = true;
private int _lastPointCount = 0;
private const int REDRAW_THRESHOLD = 3;
/// <summary>
/// 创建显示笔迹的类
/// </summary>
@@ -49,15 +53,20 @@ namespace Ink_Canvas.Helpers
public StrokeVisual(DrawingAttributes drawingAttributes)
{
_drawingAttributes = drawingAttributes;
// 启用硬件加速
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality);
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
RenderOptions.SetCachingHint(this, CachingHint.Cache);
}
/// <summary>
/// 设置或获取显示的笔迹
/// 设置或获取显示的笔迹
/// </summary>
public Stroke Stroke { set; get; }
/// <summary>
/// 在笔迹中添加点
/// 在笔迹中添加点
/// </summary>
/// <param name="point"></param>
public void Add(StylusPoint point)
@@ -66,28 +75,50 @@ namespace Ink_Canvas.Helpers
{
var collection = new StylusPointCollection { point };
Stroke = new Stroke(collection) { DrawingAttributes = _drawingAttributes };
_lastPointCount = 1;
}
else
{
Stroke.StylusPoints.Add(point);
_lastPointCount++;
}
// 标记需要重绘
_needsRedraw = true;
}
/// <summary>
/// 重新画出笔迹
/// 重新画出笔迹
/// </summary>
public void Redraw()
{
if (!_needsRedraw || Stroke == null) return;
if (_lastPointCount % REDRAW_THRESHOLD != 0 && _lastPointCount > REDRAW_THRESHOLD)
{
return;
}
try
{
using (var dc = RenderOpen())
{
Stroke.Draw(dc);
}
_needsRedraw = false;
}
catch { }
}
/// <summary>
/// 强制重绘
/// </summary>
public void ForceRedraw()
{
_needsRedraw = true;
Redraw();
}
private readonly DrawingAttributes _drawingAttributes;
public static implicit operator Stroke(StrokeVisual v)
+120 -26
View File
@@ -1,6 +1,7 @@
using Microsoft.Office.Interop.PowerPoint;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Ink;
@@ -29,6 +30,11 @@ namespace Ink_Canvas.Helpers
private DateTime _inkLockUntil = DateTime.MinValue;
private int _lockedSlideIndex = -1;
private const int InkLockMilliseconds = 500;
// 添加快速切换保护机制
private DateTime _lastSwitchTime = DateTime.MinValue;
private int _lastSwitchSlideIndex = -1;
private const int MinSwitchIntervalMs = 100; // 最小切换间隔100毫秒
#endregion
#region Constructor
@@ -66,7 +72,20 @@ namespace Ink_Canvas.Helpers
_currentPresentationId = GeneratePresentationId(presentation);
// 重新初始化内存流数组
var slideCount = presentation.Slides.Count;
int slideCount = 0;
try
{
slideCount = presentation.Slides.Count;
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
if (hr == 0x80048010)
{
return;
}
throw;
}
_memoryStreams = new MemoryStream[slideCount + 2];
// 如果启用自动保存,尝试加载已保存的墨迹
@@ -75,7 +94,6 @@ namespace Ink_Canvas.Helpers
LoadSavedStrokes();
}
LogHelper.WriteLogToFile($"已初始化演示文稿墨迹管理: {presentation.Name}, 幻灯片数量: {slideCount}", LogHelper.LogType.Trace);
}
catch (Exception ex)
{
@@ -95,10 +113,13 @@ namespace Ink_Canvas.Helpers
{
try
{
// 检查墨迹锁定
// 检查墨迹锁定
if (!CanWriteInk(slideIndex))
{
LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning);
if (DateTime.Now < _inkLockUntil)
{
LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning);
}
return;
}
@@ -112,7 +133,9 @@ namespace Ink_Canvas.Helpers
_memoryStreams[slideIndex]?.Dispose();
_memoryStreams[slideIndex] = ms;
LogHelper.WriteLogToFile($"已保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace);
if (ms.Length > 0)
{
}
}
}
catch (Exception ex)
@@ -122,6 +145,37 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 强制保存指定页面的墨迹(忽略锁定状态)
/// </summary>
public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
{
if (slideIndex <= 0 || strokes == null) return;
lock (_lockObject)
{
try
{
if (slideIndex < _memoryStreams.Length)
{
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);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"强制保存第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
}
}
}
/// <summary>
/// 加载指定页面的墨迹
/// </summary>
@@ -137,7 +191,6 @@ namespace Ink_Canvas.Helpers
{
_memoryStreams[slideIndex].Position = 0;
var strokes = new StrokeCollection(_memoryStreams[slideIndex]);
LogHelper.WriteLogToFile($"已加载第{slideIndex}页墨迹,笔画数量: {strokes.Count}", LogHelper.LogType.Trace);
return strokes;
}
}
@@ -159,26 +212,29 @@ namespace Ink_Canvas.Helpers
{
try
{
// 如果有当前墨迹,先保存到正确的页面
if (currentStrokes != null && currentStrokes.Count > 0)
// 检查快速切换保护
var now = DateTime.Now;
if (now - _lastSwitchTime < TimeSpan.FromMilliseconds(MinSwitchIntervalMs) &&
_lastSwitchSlideIndex == slideIndex)
{
// 确定要保存的页面索引
int saveToSlideIndex = _lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex;
// 确保页面索引有效
if (saveToSlideIndex > 0 && saveToSlideIndex < _memoryStreams.Length)
{
SaveCurrentSlideStrokes(saveToSlideIndex, currentStrokes);
LogHelper.WriteLogToFile($"已保存第{saveToSlideIndex}页墨迹,墨迹数量: {currentStrokes.Count}", LogHelper.LogType.Trace);
}
LogHelper.WriteLogToFile($"快速切换保护:忽略重复的页面切换请求 {slideIndex}", LogHelper.LogType.Warning);
return LoadSlideStrokes(slideIndex);
}
// 设置墨迹锁定
LockInkForSlide(slideIndex);
// 加载新页面的墨迹
var newStrokes = LoadSlideStrokes(slideIndex);
LogHelper.WriteLogToFile($"已切换到第{slideIndex}页,加载墨迹数量: {newStrokes.Count}", LogHelper.LogType.Trace);
// 更新切换记录
_lastSwitchTime = now;
_lastSwitchSlideIndex = slideIndex;
if (newStrokes.Count > 0)
{
}
return newStrokes;
}
@@ -219,7 +275,23 @@ namespace Ink_Canvas.Helpers
// 保存所有页面的墨迹
int savedCount = 0;
for (int i = 1; i <= presentation.Slides.Count && i < _memoryStreams.Length; i++)
int slideCount = 0;
try
{
slideCount = presentation.Slides.Count;
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
if (hr == 0x80048010)
{
return;
}
throw;
}
for (int i = 1; i <= slideCount && i < _memoryStreams.Length; i++)
{
if (_memoryStreams[i] != null)
{
@@ -235,7 +307,6 @@ namespace Ink_Canvas.Helpers
File.WriteAllBytes(filePath, srcBuf);
savedCount++;
LogHelper.WriteLogToFile($"已保存第{i}页墨迹,大小: {byteLength} bytes", LogHelper.LogType.Trace);
}
else
{
@@ -254,7 +325,6 @@ namespace Ink_Canvas.Helpers
}
}
LogHelper.WriteLogToFile($"已保存{savedCount}页墨迹到文件", LogHelper.LogType.Event);
}
catch (Exception ex)
{
@@ -301,7 +371,6 @@ namespace Ink_Canvas.Helpers
}
}
LogHelper.WriteLogToFile($"已从文件加载{loadedCount}页墨迹", LogHelper.LogType.Event);
}
catch (Exception ex)
{
@@ -349,9 +418,34 @@ namespace Ink_Canvas.Helpers
/// </summary>
public bool CanWriteInk(int currentSlideIndex)
{
if (DateTime.Now < _inkLockUntil) return false;
if (currentSlideIndex != _lockedSlideIndex && _lockedSlideIndex > 0) return false;
return true;
// 如果锁定时间已过,允许写入
if (DateTime.Now >= _inkLockUntil)
{
return true;
}
// 如果当前页面与锁定页面相同,允许写入(用户在当前页面绘制)
if (currentSlideIndex == _lockedSlideIndex)
{
return true;
}
// 只有在快速切换且页面不同时才锁定
return false;
}
/// <summary>
/// 重置墨迹锁定状态
/// </summary>
public void ResetLockState()
{
lock (_lockObject)
{
_inkLockUntil = DateTime.MinValue;
_lockedSlideIndex = -1;
_lastSwitchTime = DateTime.MinValue;
_lastSwitchSlideIndex = -1;
}
}
#endregion
@@ -362,7 +456,7 @@ namespace Ink_Canvas.Helpers
{
var presentationPath = presentation.FullName;
var fileHash = GetFileHash(presentationPath);
return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}";
return $"{presentation.Name}_{fileHash}";
}
catch (Exception ex)
{
+130 -37
View File
@@ -70,17 +70,17 @@ namespace Ink_Canvas.Helpers
try
{
if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false;
// 检查是否有放映窗口
var slideShowWindows = PPTApplication.SlideShowWindows;
if (slideShowWindows == null || slideShowWindows.Count == 0) return false;
// 验证放映窗口是否真正有效
try
{
var slideShowWindow = slideShowWindows[1];
if (slideShowWindow == null) return false;
// 尝试访问放映窗口的属性来验证其有效性
var _ = slideShowWindow.View;
return true;
@@ -249,7 +249,6 @@ namespace Ink_Canvas.Helpers
if (!currentSlideShowState)
{
LogHelper.WriteLogToFile("检测到PPT放映已结束", LogHelper.LogType.Trace);
}
}
}
@@ -277,16 +276,6 @@ namespace Ink_Canvas.Helpers
catch (COMException ex)
{
var hr = (uint)ex.HResult;
// 忽略常见的PowerPoint连接错误:
// 0x800401E3: 操作无法使用
// 0x80004005: 未指定错误
// 0x800706B5: RPC服务器不可用
// 0x8001010E: 应用程序调用一个已为另一线程整理的接口
// 0x800401F3: 无效的类字符串(PowerPoint未安装或COM组件未注册)
if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3)
{
LogHelper.WriteLogToFile($"连接PowerPoint失败: {ex}", LogHelper.LogType.Warning);
}
return null;
}
catch (InvalidCastException)
@@ -294,9 +283,8 @@ namespace Ink_Canvas.Helpers
// COM对象类型转换失败
return null;
}
catch (Exception ex)
catch (Exception)
{
LogHelper.WriteLogToFile($"连接PowerPoint时发生意外错误: {ex}", LogHelper.LogType.Warning);
return null;
}
}
@@ -422,12 +410,7 @@ namespace Ink_Canvas.Helpers
}
catch (COMException comEx)
{
// COM对象已经被释放或在错误的线程中,忽略这些错误
var hr = (uint)comEx.HResult;
if (hr != 0x8001010E && hr != 0x80004005 && hr != 0x800706B5)
{
LogHelper.WriteLogToFile($"取消PPT事件注册COM异常: {comEx}", LogHelper.LogType.Warning);
}
}
catch (InvalidCastException)
{
@@ -437,10 +420,53 @@ namespace Ink_Canvas.Helpers
}, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(1));
}
}
catch (Exception ex)
catch (Exception)
{
}
// 释放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)
{
// 记录但不抛出异常,确保清理过程能够继续
LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.GetType().Name} - {ex.Message}", LogHelper.LogType.Warning);
}
// 清理引用
@@ -479,7 +505,7 @@ namespace Ink_Canvas.Helpers
{
CurrentPresentation = activePresentation;
CurrentSlides = CurrentPresentation.Slides;
// 验证页数读取是否成功
try
{
@@ -487,7 +513,6 @@ namespace Ink_Canvas.Helpers
if (slideCount > 0)
{
SlidesCount = slideCount;
LogHelper.WriteLogToFile($"成功读取PPT页数: {slideCount}", LogHelper.LogType.Trace);
}
else
{
@@ -603,7 +628,6 @@ namespace Ink_Canvas.Helpers
try
{
PresentationClose?.Invoke(pres);
LogHelper.WriteLogToFile($"演示文稿已关闭: {pres?.Name}", LogHelper.LogType.Event);
// 重新启动连接检查
_connectionCheckTimer?.Start();
@@ -620,7 +644,6 @@ namespace Ink_Canvas.Helpers
{
UpdateCurrentPresentationInfo();
SlideShowBegin?.Invoke(wn);
LogHelper.WriteLogToFile("幻灯片放映开始", LogHelper.LogType.Event);
}
catch (Exception ex)
{
@@ -634,7 +657,6 @@ namespace Ink_Canvas.Helpers
{
UpdateCurrentPresentationInfo();
SlideShowNextSlide?.Invoke(wn);
LogHelper.WriteLogToFile($"幻灯片切换到第{wn?.View?.CurrentShowPosition}页", LogHelper.LogType.Trace);
}
catch (Exception ex)
{
@@ -653,7 +675,6 @@ namespace Ink_Canvas.Helpers
}
SlideShowEnd?.Invoke(pres);
LogHelper.WriteLogToFile("幻灯片放映结束", LogHelper.LogType.Event);
}
catch (Exception ex)
{
@@ -825,14 +846,86 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 获取当前活跃的演示文稿(用于多窗口墨迹分离)
/// </summary>
public Presentation GetCurrentActivePresentation()
{
try
{
if (!IsConnected || PPTApplication == null) return null;
if (!Marshal.IsComObject(PPTApplication)) return null;
// 如果在放映模式,获取放映窗口的演示文稿
if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0)
{
var slideShowWindow = PPTApplication.SlideShowWindows[1];
if (slideShowWindow?.View != null)
{
return (Presentation)slideShowWindow.View.Slide.Parent;
}
}
// 如果不在放映模式,获取活动窗口的演示文稿
if (PPTApplication.ActiveWindow?.Presentation != null)
{
return PPTApplication.ActiveWindow.Presentation;
}
// 如果没有活动窗口,返回当前演示文稿
return CurrentPresentation;
}
catch (COMException comEx)
{
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005)
{
// COM对象已失效,触发断开连接
DisconnectFromPPT();
}
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {comEx.Message}", LogHelper.LogType.Warning);
return CurrentPresentation;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
return CurrentPresentation;
}
}
/// <summary>
/// 获取当前幻灯片编号
/// </summary>
public int GetCurrentSlideNumber()
{
try
{
if (!IsConnected || !IsInSlideShow || PPTApplication == null) return 0;
if (!IsConnected || PPTApplication == null) return 0;
if (!Marshal.IsComObject(PPTApplication)) return 0;
return PPTApplication.SlideShowWindows[1]?.View?.CurrentShowPosition ?? 0;
// 如果在放映模式,获取放映窗口的当前幻灯片编号
if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0)
{
var slideShowWindow = PPTApplication.SlideShowWindows[1];
if (slideShowWindow?.View != null)
{
return slideShowWindow.View.CurrentShowPosition;
}
}
// 如果不在放映模式,获取活动窗口的当前幻灯片编号
if (PPTApplication.ActiveWindow?.Selection?.SlideRange?.SlideNumber > 0)
{
return PPTApplication.ActiveWindow.Selection.SlideRange.SlideNumber;
}
// 如果CurrentSlide存在,尝试获取其编号
if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide))
{
return CurrentSlide.SlideNumber;
}
return 0;
}
catch (COMException comEx)
{
@@ -1042,7 +1135,7 @@ namespace Ink_Canvas.Helpers
_wpsProcessCheckTimer.Dispose();
}
// 优化:增加检查间隔到2秒,减少性能开销
// 增加检查间隔到2秒,减少性能开销
_wpsProcessCheckTimer = new Timer(2000);
_wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed;
_wpsProcessCheckTimer.Start();
@@ -1076,7 +1169,7 @@ namespace Ink_Canvas.Helpers
}
// 检查前台WPS窗口是否存在(优化版)
// 检查前台WPS窗口是否存在
bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized();
if (isForegroundWpsWindowActive)
@@ -1088,7 +1181,7 @@ namespace Ink_Canvas.Helpers
return;
}
// 优化:多重验证确保准确性
// 多重验证确保准确性
if (!PerformMultipleWpsWindowChecks())
{
LogHelper.WriteLogToFile("多重验证显示WPS窗口仍然存在,跳过查杀", LogHelper.LogType.Trace);
@@ -1109,7 +1202,7 @@ namespace Ink_Canvas.Helpers
}
/// <summary>
/// 优化版的前台WPS窗口检测,减少性能开销
/// 前台WPS窗口检测
/// </summary>
private bool IsForegroundWpsWindowStillActiveOptimized()
{
@@ -1135,7 +1228,7 @@ namespace Ink_Canvas.Helpers
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"优化版WPS窗口检测失败: {ex}", LogHelper.LogType.Error);
LogHelper.WriteLogToFile($"WPS窗口检测失败: {ex}", LogHelper.LogType.Error);
return false;
}
}
+1 -7
View File
@@ -86,7 +86,6 @@ namespace Ink_Canvas.Helpers
{
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
LogHelper.WriteLogToFile($"更新PPT页码显示: {currentSlide}/{totalSlides}", LogHelper.LogType.Trace);
}
else
{
@@ -127,7 +126,6 @@ namespace Ink_Canvas.Helpers
{
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
LogHelper.WriteLogToFile($"更新PPT页码显示: {currentSlide}/{totalSlides}", LogHelper.LogType.Trace);
}
else
{
@@ -157,7 +155,6 @@ namespace Ink_Canvas.Helpers
{
// 如果不在放映模式,隐藏所有导航面板
HideAllNavigationPanels();
LogHelper.WriteLogToFile("PPT放映状态变化:隐藏导航面板", LogHelper.LogType.Trace);
}
}
catch (Exception ex)
@@ -181,7 +178,7 @@ namespace Ink_Canvas.Helpers
bool isInSlideShow = _mainWindow.PPTManager?.IsInSlideShow == true;
int slidesCount = _mainWindow.PPTManager?.SlidesCount ?? 0;
bool hasValidPageCount = slidesCount > 0;
bool shouldShowButtons = ShowPPTButton &&
_mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
isInSlideShow &&
@@ -190,12 +187,9 @@ namespace Ink_Canvas.Helpers
if (!shouldShowButtons)
{
HideAllNavigationPanels();
LogHelper.WriteLogToFile($"隐藏PPT导航面板 - 放映状态: {isInSlideShow}, 页数: {slidesCount}, 按钮设置: {ShowPPTButton}", LogHelper.LogType.Trace);
return;
}
LogHelper.WriteLogToFile($"显示PPT导航面板 - 放映状态: {isInSlideShow}, 页数: {slidesCount}", LogHelper.LogType.Trace);
// 设置侧边按钮位置
_mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2);
_mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2);
@@ -238,4 +238,4 @@ namespace Ink_Canvas.Helpers.Plugins
#endregion
}
}
}
+1 -1
View File
@@ -293,4 +293,4 @@ namespace Ink_Canvas.Helpers.Plugins
#endregion
}
}
}
+1 -2
View File
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
@@ -212,4 +211,4 @@ namespace Ink_Canvas.Helpers.Plugins
#endregion
}
}
}
@@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
+1 -3
View File
@@ -1,5 +1,3 @@
using System;
namespace Ink_Canvas.Helpers.Plugins
{
/// <summary>
@@ -151,4 +149,4 @@ namespace Ink_Canvas.Helpers.Plugins
#endregion
}
}
}
+2 -4
View File
@@ -20,8 +20,8 @@ namespace Ink_Canvas.Helpers.Plugins
public class PluginManager
{
private static readonly string PluginsDirectory = Path.Combine(App.RootPath, "Plugins");
private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "PluginConfig.json");
private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "PluginConfig.json.bak");
private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "Configs", "PluginConfig.json");
private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "Configs", "PluginConfig.json.bak");
private static PluginManager _instance;
private static SemaphoreSlim _configLock = new SemaphoreSlim(1, 1);
@@ -79,8 +79,6 @@ namespace Ink_Canvas.Helpers.Plugins
Directory.CreateDirectory(PluginsDirectory);
}
// 加载插件配置
LoadConfig();
// 初始化自动保存计时器(3秒)
_autoSaveTimer = new Timer(3000);
+1 -1
View File
@@ -156,7 +156,7 @@ namespace Ink_Canvas.Helpers
// 使用更直接的方法:先激活窗口,再置顶
window.Activate();
window.Focus();
// 设置窗口为置顶
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
+9 -2
View File
@@ -24,8 +24,12 @@
<BootstrapperEnabled>false</BootstrapperEnabled>
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<UseWPF>true</UseWPF>
<Configurations>Release;x86 Debug;Debug</Configurations>
<Platforms>AnyCPU;x86</Platforms>
<Configurations>Debug;Release;x86 Debug</Configurations>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>embedded</DebugType>
<OutputPath>bin\$(Configuration)\</OutputPath>
<Prefer32Bit>True</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|AnyCPU'">
<DebugType>embedded</DebugType>
@@ -154,6 +158,8 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
<PackageReference Include="OSVersionExt" Version="3.0.0" />
<PackageReference Include="AForge.Video" Version="2.2.5" />
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
@@ -248,6 +254,7 @@
<Resource Include="Resources\DeveloperAvatars\kengwang.png" />
<Resource Include="Resources\DeveloperAvatars\STBBRD.png" />
<Resource Include="Resources\DeveloperAvatars\WXRIW.png" />
<Resource Include="Resources\DeveloperAvatars\PrefacedCorg.jpg" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Icons-png\down.png" />
+307 -12
View File
@@ -291,7 +291,7 @@
</Image>
</Button>
<!-- 新增:个性化设置 -->
<!-- 个性化设置 -->
<Button Width="40" Height="40" Margin="0,5,0,0" Style="{StaticResource NavButton}"
Click="NavTheme_Click" Tag="theme" ToolTip="个性化设置">
<Image Width="24" Height="24">
@@ -306,7 +306,7 @@
</Image>
</Button>
<!-- 新增:快捷键设置 -->
<!-- 快捷键设置 -->
<Button Width="40" Height="40" Margin="0,5,0,0" Style="{StaticResource NavButton}"
Click="NavShortcuts_Click" Tag="shortcuts" ToolTip="快捷键设置">
<Image Width="24" Height="24">
@@ -538,7 +538,7 @@
</GroupBox.Header>
<ui:SimpleStackPanel Spacing="6" Margin="0,10,0,0">
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" Foreground="#fafafa">
选择软件运行模式。仅PPT模式下,软件将完全隐藏,仅在PPT放映时出现。
选择软件运行模式。仅PPT模式下,软件将完全隐藏,仅在PPT放映时出现。(实验性功能,可能不稳定。)
</TextBlock>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
<TextBlock Text="正常模式" VerticalAlignment="Center" Foreground="#fafafa"
@@ -821,7 +821,7 @@
</ui:SimpleStackPanel>
</GroupBox>
<!-- 新增:崩溃后操作设置 -->
<!-- 崩溃后操作设置 -->
<GroupBox>
<GroupBox.Header>
<TextBlock Margin="0,12,0,0" Text="崩溃后操作" FontWeight="Bold" Foreground="#fafafa"
@@ -1958,6 +1958,8 @@
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchNotifyPreviousPage_Toggled" />
</ui:SimpleStackPanel>
<TextBlock Text="#开启后会记录上次播放的页数,点击“是”后会在进入放映状态时自动跳转"
TextWrapping="Wrap" Foreground="#a1a1aa" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="进入放映时回到首页" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
@@ -2598,6 +2600,15 @@
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchAutoFoldInPPTSlideShow_Toggled" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="软件退出后保持收纳模式"
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent=""
Name="ToggleSwitchKeepFoldAfterSoftwareExit"
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchKeepFoldAfterSoftwareExit_Toggled" />
</ui:SimpleStackPanel>
<TextBlock Text="# 开启后,执行自动收纳的软件在软件退出后不退出收纳模式,保持收纳状态" TextWrapping="Wrap" Foreground="#a1a1aa" />
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
StrokeThickness="1" Margin="0,4,0,4" />
<TextBlock Margin="0,0,0,8" Text="自动查杀" FontWeight="Bold" Foreground="#fafafa"
@@ -2734,6 +2745,226 @@
</ui:SimpleStackPanel>
<TextBlock Name="TextBlockFileAssociationStatus" Text=""
Foreground="#a1a1aa" FontSize="12" Margin="0,4,0,0" />
<!-- 悬浮窗拦截功能 -->
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
StrokeThickness="1" Margin="0,8,0,8" />
<TextBlock Text="悬浮窗拦截" FontWeight="Bold" Foreground="#fafafa"
FontSize="16" Margin="0,8,0,8" />
<TextBlock Text="自动检测并隐藏教育软件的悬浮窗,保持桌面整洁"
TextWrapping="Wrap" Foreground="#a1a1aa" Margin="0,0,0,8" />
<!-- 主控制开关 -->
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,8">
<TextBlock Foreground="#fafafa" Text="启用悬浮窗拦截" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent=""
Name="ToggleSwitchFloatingWindowInterceptorEnabled" IsOn="False"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchFloatingWindowInterceptorEnabled_Toggled" />
</ui:SimpleStackPanel>
<!-- 拦截规则网格 -->
<Grid Name="FloatingWindowInterceptorGrid" Margin="4,0" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- 希沃白板3 -->
<ui:SimpleStackPanel Grid.Row="0" Grid.Column="0" Orientation="Vertical">
<Image Source="/Resources/Icons-png/EasiNote3.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃白板3" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoWhiteboard3Floating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoWhiteboard3Floating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 希沃白板5 -->
<ui:SimpleStackPanel Grid.Row="0" Grid.Column="1" Orientation="Vertical">
<Image Source="/Resources/Icons-png/EasiNote.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃白板5" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoWhiteboard5Floating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoWhiteboard5Floating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 希沃白板5C -->
<ui:SimpleStackPanel Grid.Row="0" Grid.Column="2" Orientation="Vertical">
<Image Source="/Resources/Icons-png/EasiNote5C.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃白板5C" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoWhiteboard5CFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoWhiteboard5CFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 希沃品课 -->
<ui:SimpleStackPanel Grid.Row="0" Grid.Column="3" Orientation="Vertical">
<Image Source="/Resources/Icons-png/SeewoPinco.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃品课" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoPincoSideBarFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoPincoSideBarFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 希沃品课画笔 -->
<ui:SimpleStackPanel Grid.Row="1" Grid.Column="0" Orientation="Vertical">
<Image Source="/Resources/Icons-png/SeewoPinco.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃品课画笔" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoPincoDrawingFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoPincoDrawingFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 希沃PPT小工具 -->
<ui:SimpleStackPanel Grid.Row="1" Grid.Column="1" Orientation="Vertical">
<Image Source="/Resources/Icons-png/PPTTools.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃PPT小工具" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoPPTFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoPPTFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- AiClass -->
<ui:SimpleStackPanel Grid.Row="1" Grid.Column="2" Orientation="Vertical">
<Image Source="/Resources/Icons-png/AiClass.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="AiClass" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchAiClassFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchAiClassFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 鸿合屏幕书写 -->
<ui:SimpleStackPanel Grid.Row="1" Grid.Column="3" Orientation="Vertical">
<Image Source="/Resources/Icons-png/HiteAnnotation.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="鸿合屏幕书写" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchHiteAnnotationFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchHiteAnnotationFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 畅言智慧课堂 -->
<ui:SimpleStackPanel Grid.Row="2" Grid.Column="0" Orientation="Vertical">
<Image Source="/Resources/Icons-png/ChangYan.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="畅言智慧课堂" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchChangYanFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchChangYanFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 畅言PPT -->
<ui:SimpleStackPanel Grid.Row="2" Grid.Column="1" Orientation="Vertical">
<Image Source="/Resources/Icons-png/ChangYanPPT.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="畅言PPT" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchChangYanPptFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchChangYanPptFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 天喻教育云 -->
<ui:SimpleStackPanel Grid.Row="2" Grid.Column="2" Orientation="Vertical">
<Image Source="/Resources/Icons-png/IntelligentClass.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="天喻教育云" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchIntelligentClassFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchIntelligentClassFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 希沃桌面画笔 -->
<ui:SimpleStackPanel Grid.Row="2" Grid.Column="3" Orientation="Vertical">
<Image Source="/Resources/Icons-png/SeewoDesktop.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃桌面画笔" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoDesktopAnnotationFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoDesktopAnnotationFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
<!-- 希沃桌面侧栏 -->
<ui:SimpleStackPanel Grid.Row="3" Grid.Column="0" Orientation="Vertical">
<Image Source="/Resources/Icons-png/SeewoDesktop.png" Margin="0,0,0,4"
Width="42" Height="42" HorizontalAlignment="Center" />
<TextBlock Foreground="#fafafa" Text="希沃桌面侧栏" HorizontalAlignment="Center"
FontSize="14" Margin="0,0,0,4" />
<ui:ToggleSwitch OnContent="" OffContent="" MinWidth="0"
HorizontalAlignment="Center"
Name="ToggleSwitchSeewoDesktopSideBarFloating" IsOn="True"
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchSeewoDesktopSideBarFloating_Toggled"
Width="40" />
</ui:SimpleStackPanel>
</Grid>
<!-- 状态信息 -->
<TextBlock Name="TextBlockFloatingWindowInterceptorStatus" Text="拦截器未启动"
Foreground="#a1a1aa" FontSize="12" Margin="0,4,0,0" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="清屏时自动截图" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
@@ -2917,13 +3148,24 @@
Toggled="ToggleSwitchShowRandomAndSingleDraw_Toggled" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="直接调用Ci点名"
<TextBlock Foreground="#fafafa" Text="直接调用外部点名"
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent=""
Name="ToggleSwitchDirectCallCiRand"
Name="ToggleSwitchExternalCaller"
IsOn="False" FontFamily="Microsoft YaHei UI"
FontWeight="Bold"
Toggled="ToggleSwitchDirectCallCiRand_Toggled" />
Toggled="ToggleSwitchExternalCaller_Toggled" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0">
<TextBlock Foreground="#fafafa" Text="点名类型"
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
<ComboBox Name="ComboBoxExternalCallerType" Width="150" Height="30"
IsEditable="False" IsReadOnly="True" SelectedIndex="0"
SelectionChanged="ComboBoxExternalCallerType_SelectionChanged">
<ComboBoxItem>ClassIsland点名</ComboBoxItem>
<ComboBoxItem>SecRandom点名</ComboBoxItem>
<ComboBoxItem>NamePicker点名</ComboBoxItem>
</ComboBox>
</ui:SimpleStackPanel>
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0"
Stroke="#3f3f46" StrokeThickness="1" Margin="0,4,0,4" />
@@ -3295,6 +3537,21 @@
Alan-CRL
</TextBlock>
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Grid.Column="3" Grid.Row="2" Orientation="Vertical"
Margin="0,16,0,0">
<Image Source="/Resources/DeveloperAvatars/PrefacedCorg.jpg"
VerticalAlignment="Center" HorizontalAlignment="Center"
RenderOptions.BitmapScalingMode="HighQuality" Width="48"
Margin="0,0,0,6" Height="48">
<Image.Clip>
<EllipseGeometry Center="24,24" RadiusX="24" RadiusY="24" />
</Image.Clip>
</Image>
<TextBlock Margin="0,0,0,2" VerticalAlignment="Center"
HorizontalAlignment="Center" FontSize="12" Foreground="White">
PrefacedCorg
</TextBlock>
</ui:SimpleStackPanel>
</Grid>
<ui:SimpleStackPanel Spacing="3" Orientation="Vertical">
<TextBlock
@@ -3397,16 +3654,54 @@
<Grid Visibility="{Binding ElementName=inkCanvas, Path=Visibility}">
<Grid Name="GridInkCanvasSelectionCover"
MouseDown="GridInkCanvasSelectionCover_MouseDown"
MouseMove="GridInkCanvasSelectionCover_MouseMove"
MouseUp="GridInkCanvasSelectionCover_MouseUp"
IsManipulationEnabled="True"
ManipulationStarting="GridInkCanvasSelectionCover_ManipulationStarting"
ManipulationCompleted="GridInkCanvasSelectionCover_ManipulationCompleted"
ManipulationDelta="GridInkCanvasSelectionCover_ManipulationDelta"
PreviewTouchDown="GridInkCanvasSelectionCover_PreviewTouchDown"
PreviewTouchUp="GridInkCanvasSelectionCover_PreviewTouchUp"
TouchDown="GridInkCanvasSelectionCover_TouchDown"
TouchUp="GridInkCanvasSelectionCover_TouchUp"
Background="#01FFFFFF" Opacity="0.01" Visibility="Visible" Margin="1,0,-1,0" />
PreviewTouchDown="GridInkCanvasSelectionCover_PreviewTouchDown"
PreviewTouchUp="GridInkCanvasSelectionCover_PreviewTouchUp"
PreviewTouchMove="GridInkCanvasSelectionCover_PreviewTouchMove"
TouchDown="GridInkCanvasSelectionCover_TouchDown"
TouchUp="GridInkCanvasSelectionCover_TouchUp"
TouchMove="GridInkCanvasSelectionCover_TouchMove"
Background="#01FFFFFF" Opacity="0.01" Visibility="Visible" Margin="1,0,-1,0">
<!-- 选择框 -->
<Rectangle Name="SelectionRectangle"
Stroke="Blue"
StrokeThickness="2"
StrokeDashArray="5,5"
Fill="Transparent"
Visibility="Collapsed"
IsHitTestVisible="False"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<!-- 选择点容器 -->
<Canvas Name="SelectionHandlesCanvas" Visibility="Collapsed">
<!-- 四个角选择点 -->
<Rectangle Name="TopLeftHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeNWSE" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
<Rectangle Name="TopRightHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeNESW" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
<Rectangle Name="BottomLeftHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeNESW" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
<Rectangle Name="BottomRightHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeNWSE" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
<!-- 四个边选择点 -->
<Rectangle Name="TopHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeNS" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
<Rectangle Name="BottomHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeNS" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
<Rectangle Name="LeftHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeWE" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
<Rectangle Name="RightHandle" Width="8" Height="8" Fill="LightBlue" Stroke="Blue" StrokeThickness="1"
Cursor="SizeWE" MouseDown="SelectionHandle_MouseDown" MouseMove="SelectionHandle_MouseMove" MouseUp="SelectionHandle_MouseUp" />
</Canvas>
</Grid>
<Border Name="BorderStrokeSelectionControl"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="800,900,0,0"
CornerRadius="5" Height="80"
+93 -34
View File
@@ -31,14 +31,14 @@ using Cursors = System.Windows.Input.Cursors;
using DpiChangedEventArgs = System.Windows.DpiChangedEventArgs;
using File = System.IO.File;
using GroupBox = System.Windows.Controls.GroupBox;
using MessageBox = System.Windows.MessageBox;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
using Point = System.Windows.Point;
namespace Ink_Canvas
{
public partial class MainWindow : Window
{
// 新增:每一页一个Canvas对象
// 每一页一个Canvas对象
private List<System.Windows.Controls.Canvas> whiteboardPages = new List<System.Windows.Controls.Canvas>();
private int currentPageIndex;
private System.Windows.Controls.Canvas currentCanvas;
@@ -50,6 +50,12 @@ namespace Ink_Canvas
// 墨迹渐隐管理器
private InkFadeManager _inkFadeManager;
// 悬浮窗拦截管理器
private FloatingWindowInterceptorManager _floatingWindowInterceptorManager;
// 设置面板相关状态
private bool userChangedNoFocusModeInSettings;
#region Window Initialization
@@ -247,7 +253,7 @@ namespace Ink_Canvas
// 为浮动栏按钮添加触摸事件支持
AddTouchSupportToFloatingBarButtons();
// 为滑块控件添加触摸事件支持
AddTouchSupportToSliders();
}
@@ -301,7 +307,7 @@ namespace Ink_Canvas
foreach (var gest in gestures)
//Trace.WriteLine(string.Format("Gesture: {0}, Confidence: {1}", gest.ApplicationGesture, gest.RecognitionConfidence));
// 只有在PPT放映模式下才响应翻页手势
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
PPTManager?.IsInSlideShow == true)
{
@@ -377,7 +383,7 @@ namespace Ink_Canvas
#region Definations and Loading
public static Settings Settings = new Settings();
public static string settingsFileName = "Settings.json";
public static string settingsFileName = Path.Combine("Configs", "Settings.json");
private bool isLoaded;
private bool forcePointEraser;
@@ -411,7 +417,7 @@ namespace Ink_Canvas
}
if (needFix)
{
string newPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "saves");
string newPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Saves");
Settings.Automation.AutoSavedStrokesLocation = newPath;
if (!Directory.Exists(newPath))
Directory.CreateDirectory(newPath);
@@ -531,6 +537,9 @@ namespace Ink_Canvas
// 初始化剪贴板监控
InitializeClipboardMonitoring();
// 初始化悬浮窗拦截管理器
InitializeFloatingWindowInterceptor();
// 初始化全局快捷键管理器
InitializeGlobalHotkeyManager();
@@ -545,6 +554,17 @@ namespace Ink_Canvas
// 检查模式设置并应用
CheckMainWindowVisibility();
// 检查是否通过--board参数启动,如果是则自动切换到白板模式
if (App.StartWithBoardMode)
{
LogHelper.WriteLogToFile("检测到--board参数,自动切换到白板模式", LogHelper.LogType.Event);
// 延迟执行,确保UI已完全加载
Dispatcher.BeginInvoke(new Action(() =>
{
SwitchToBoardMode();
}), DispatcherPriority.Loaded);
}
}
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
@@ -1032,12 +1052,16 @@ namespace Ink_Canvas
// 如果当前有选中的元素,取消选中状态
if (currentSelectedElement != null)
{
// 保存当前编辑模式
var previousEditingMode = inkCanvas.EditingMode;
// 取消选中元素
UnselectElement(currentSelectedElement);
// 恢复编辑模式
inkCanvas.EditingMode = previousEditingMode;
currentSelectedElement = null;
// 重置为选择模式,确保用户可以继续选择其他元素
SetCurrentToolMode(InkCanvasEditingMode.Select);
// 更新模式缓存
UpdateCurrentToolMode("select");
// 刷新浮动栏高光显示
SetFloatingBarHighlightPosition("select");
}
}
}
@@ -1929,6 +1953,12 @@ namespace Ink_Canvas
{
ApplyAlwaysOnTop();
}
// 如果当前在设置面板中,标记用户已修改无焦点模式设置
if (BorderSettings.Visibility == Visibility.Visible)
{
userChangedNoFocusModeInSettings = true;
}
}
private void ToggleSwitchAlwaysOnTop_Toggled(object sender, RoutedEventArgs e)
@@ -2029,14 +2059,14 @@ namespace Ink_Canvas
MessageBox.Show("快捷键管理器尚未初始化,请稍后重试。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
// 暂时隐藏设置面板
BorderSettings.Visibility = Visibility.Hidden;
BorderSettingsMask.Visibility = Visibility.Hidden;
// 创建快捷键设置窗口
var hotkeySettingsWindow = new HotkeySettingsWindow(this, _globalHotkeyManager);
// 设置窗口关闭事件,用于在快捷键设置窗口关闭后恢复设置面板
hotkeySettingsWindow.Closed += (s, e) =>
{
@@ -2044,7 +2074,7 @@ namespace Ink_Canvas
BorderSettings.Visibility = Visibility.Visible;
BorderSettingsMask.Visibility = Visibility.Visible;
};
// 显示快捷键设置窗口
hotkeySettingsWindow.ShowDialog();
}
@@ -2053,7 +2083,7 @@ namespace Ink_Canvas
// 确保在发生错误时也恢复设置面板显示
BorderSettings.Visibility = Visibility.Visible;
BorderSettingsMask.Visibility = Visibility.Visible;
LogHelper.WriteLogToFile($"打开快捷键设置窗口时出错: {ex.Message}", LogHelper.LogType.Error);
MessageBox.Show($"打开快捷键设置窗口时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
@@ -2154,13 +2184,13 @@ namespace Ink_Canvas
var toggle = sender as ToggleSwitch;
Settings.PowerPointSettings.ShowGestureButtonInSlideShow = toggle != null && toggle.IsOn;
SaveSettingsToFile();
// 如果当前在PPT放映模式,需要立即更新手势按钮的显示状态
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
{
UpdateGestureButtonVisibilityInPPTMode();
}
LogHelper.WriteLogToFile($"PPT放映模式显示手势按钮已{(Settings.PowerPointSettings.ShowGestureButtonInSlideShow ? "" : "")}", LogHelper.LogType.Event);
}
catch (Exception ex)
@@ -2178,7 +2208,7 @@ namespace Ink_Canvas
{
if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow)
{
// 如果启用了PPT放映模式显示手势按钮,则显示手势按钮(在PPT模式下不依赖手势功能是否启用)
// 如果启用了PPT放映模式显示手势按钮,则检查是否在批注模式下显示手势按钮
CheckEnableTwoFingerGestureBtnVisibility(true);
}
else
@@ -2272,11 +2302,11 @@ namespace Ink_Canvas
{
// 检查启动参数中是否有.icstk文件
string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(App.StartArgs);
if (!string.IsNullOrEmpty(icstkFile))
{
LogHelper.WriteLogToFile($"检测到命令行参数中的.icstk文件: {icstkFile}", LogHelper.LogType.Event);
// 延迟执行,确保UI已完全加载
Dispatcher.BeginInvoke(new Action(() =>
{
@@ -2322,10 +2352,15 @@ namespace Ink_Canvas
_globalHotkeyManager.UpdateHotkeyStateForToolMode(isMouseMode);
}
// 在PPT放映模式下,工具模式切换时需要更新手势按钮的显示状态
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
{
UpdateGestureButtonVisibilityInPPTMode();
}
// 执行额外的操作(如果有)
additionalActions?.Invoke();
LogHelper.WriteLogToFile($"工具模式已切换到: {newMode}, 鼠标模式: {isMouseMode}", LogHelper.LogType.Trace);
}
catch (Exception ex)
{
@@ -2416,17 +2451,17 @@ namespace Ink_Canvas
if (slider == null) return;
// 捕获触摸设备
if (e.RoutedEvent == UIElement.TouchDownEvent)
if (e.RoutedEvent == TouchDownEvent)
{
slider.CaptureTouch(e.TouchDevice);
}
// 计算触摸位置对应的滑块值
var touchPoint = e.GetTouchPoint(slider);
// 使用更精确的位置计算方法
UpdateSliderValueFromPositionImproved(slider, touchPoint.Position);
e.Handled = true;
}
@@ -2439,7 +2474,7 @@ namespace Ink_Canvas
// 释放触摸捕获
slider.ReleaseTouchCapture(e.TouchDevice);
e.Handled = true;
}
@@ -2451,7 +2486,7 @@ namespace Ink_Canvas
if (slider == null) return;
// 捕获手写笔设备
if (e.RoutedEvent == UIElement.StylusDownEvent)
if (e.RoutedEvent == StylusDownEvent)
{
slider.CaptureStylus();
}
@@ -2462,7 +2497,7 @@ namespace Ink_Canvas
{
UpdateSliderValueFromPositionImproved(slider, stylusPoint[0].ToPoint());
}
e.Handled = true;
}
@@ -2475,7 +2510,7 @@ namespace Ink_Canvas
// 释放手写笔捕获
slider.ReleaseStylusCapture();
e.Handled = true;
}
@@ -2501,9 +2536,9 @@ namespace Ink_Canvas
// 获取轨道的实际边界
var trackBounds = track.TransformToAncestor(slider).TransformBounds(new Rect(0, 0, track.ActualWidth, track.ActualHeight));
double relativePosition = 0;
if (slider.Orientation == System.Windows.Controls.Orientation.Horizontal)
{
// 水平滑块
@@ -2527,7 +2562,7 @@ namespace Ink_Canvas
// 计算新的滑块值
var newValue = slider.Minimum + relativePosition * (slider.Maximum - slider.Minimum);
// 如果启用了吸附到刻度,则调整到最近的刻度
if (slider.IsSnapToTickEnabled && slider.TickFrequency > 0)
{
@@ -2560,7 +2595,7 @@ namespace Ink_Canvas
{
// 使用更简单直接的方法计算滑块值
double relativePosition = 0;
if (slider.Orientation == System.Windows.Controls.Orientation.Horizontal)
{
// 水平滑块 - 使用滑块的实际宽度
@@ -2590,7 +2625,7 @@ namespace Ink_Canvas
// 计算新的滑块值
var newValue = slider.Minimum + relativePosition * (slider.Maximum - slider.Minimum);
// 如果启用了吸附到刻度,则调整到最近的刻度
if (slider.IsSnapToTickEnabled && slider.TickFrequency > 0)
{
@@ -2619,11 +2654,14 @@ namespace Ink_Canvas
{
try
{
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
var toggle = sender as ToggleSwitch;
if (toggle != null)
{
Settings.ModeSettings.IsPPTOnlyMode = toggle.IsOn;
// 保存设置到文件
SaveSettingsToFile();
// 如果切换到仅PPT模式,立即隐藏主窗口
if (Settings.ModeSettings.IsPPTOnlyMode)
{
@@ -2681,6 +2719,27 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 切换到白板模式(用于--board参数和IPC命令)
/// 调用浮动栏上的白板功能
/// </summary>
public void SwitchToBoardMode()
{
try
{
LogHelper.WriteLogToFile("开始切换到白板模式", LogHelper.LogType.Event);
// 调用浮动栏上的白板功能
ImageBlackboard_MouseUp(null, null);
LogHelper.WriteLogToFile("已成功切换到白板模式", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换到白板模式时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
}
}
+7 -8
View File
@@ -260,7 +260,7 @@ namespace Ink_Canvas
}
// 只有在PPT放映模式下且页数有效时才显示翻页按钮
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
PPTManager?.IsInSlideShow == true &&
PPTManager?.SlidesCount > 0)
@@ -280,25 +280,24 @@ namespace Ink_Canvas
RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
LogHelper.WriteLogToFile($"从收纳模式恢复时隐藏PPT翻页按钮 - 放映状态: {PPTManager?.IsInSlideShow}, 页数: {PPTManager?.SlidesCount}", LogHelper.LogType.Trace);
}
// 新增:只在屏幕模式下显示浮动栏
// 新只在屏幕模式下显示浮动栏
if (currentMode == 0)
{
// 强制更新布局以确保ActualWidth正确
ViewboxFloatingBar.UpdateLayout();
// 等待一小段时间让布局完全更新
Task.Delay(50);
// 再次强制更新布局
ViewboxFloatingBar.UpdateLayout();
// 强制重新测量和排列
ViewboxFloatingBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
ViewboxFloatingBar.Arrange(new Rect(ViewboxFloatingBar.DesiredSize));
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
ViewboxFloatingBarMarginAnimation(60);
else
@@ -314,7 +313,7 @@ namespace Ink_Canvas
{
// 等待UI完全更新
await Task.Delay(100);
// 获取当前选中的模式并重新设置高光位置
string selectedToolMode = GetCurrentSelectedMode();
if (!string.IsNullOrEmpty(selectedToolMode))
@@ -205,6 +205,11 @@ namespace Ink_Canvas
// 提交到历史记录
timeMachine.CommitElementInsertHistory(image);
// 插入图片后切换到选择模式并刷新浮动栏高光显示
SetCurrentToolMode(InkCanvasEditingMode.Select);
UpdateCurrentToolMode("select");
HideSubPanels("select");
ShowNotification("图片已从剪贴板粘贴");
}
catch (Exception ex)
+30 -20
View File
@@ -68,6 +68,11 @@ namespace Ink_Canvas
};
timeMachine.CommitElementInsertHistory(image);
// 插入图片后切换到选择模式并刷新浮动栏高光显示
SetCurrentToolMode(InkCanvasEditingMode.Select);
UpdateCurrentToolMode("select");
HideSubPanels("select");
}
}
}
@@ -191,7 +196,16 @@ namespace Ink_Canvas
{
if (sender is FrameworkElement element)
{
// 使用触摸拖动的完整实现
// 检查是否是双指手势
if (e.Manipulators.Count() >= 2)
{
// 双指手势时,不处理单个元素的手势,让画布级别的手势处理
// 这样可以实现图片与墨迹的同步移动
e.Handled = false;
return;
}
// 单指手势时,使用触摸拖动的完整实现
ApplyTouchManipulationTransform(element, e);
// 如果是图片元素,更新工具栏位置
@@ -276,25 +290,19 @@ namespace Ink_Canvas
BorderImageSelectionControl.Visibility = Visibility.Visible;
}
// 隐藏笔画选择工具栏
if (BorderStrokeSelectionControl != null)
{
BorderStrokeSelectionControl.Visibility = Visibility.Collapsed;
}
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
// 不需要直接设置BorderStrokeSelectionControl.Visibility
}
else
{
// 显示笔画选择工具栏
if (BorderStrokeSelectionControl != null)
{
BorderStrokeSelectionControl.Visibility = Visibility.Visible;
}
// 隐藏图片选择工具栏
if (BorderImageSelectionControl != null)
{
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
}
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
// 不需要直接设置BorderStrokeSelectionControl.Visibility
}
// 确保选择框不显示,避免蓝色边框
@@ -308,8 +316,8 @@ namespace Ink_Canvas
{
// 清除当前选择
inkCanvas.Select(new StrokeCollection());
// 设置编辑模式为非选择模式
inkCanvas.EditingMode = InkCanvasEditingMode.None;
// 保持选择模式,这样用户可以直接点击墨迹来选择
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
}
}
@@ -318,16 +326,14 @@ namespace Ink_Canvas
{
// 去除选中效果
// 隐藏所有选择工具栏
// 隐藏图片选择工具栏
if (BorderImageSelectionControl != null)
{
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
}
if (BorderStrokeSelectionControl != null)
{
BorderStrokeSelectionControl.Visibility = Visibility.Collapsed;
}
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
// 不需要直接设置BorderStrokeSelectionControl.Visibility
// 确保选择框隐藏
if (GridInkCanvasSelectionCover != null)
@@ -335,7 +341,11 @@ namespace Ink_Canvas
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
}
// 确保InkCanvas处于选择模式,这样用户可以直接点击墨迹来选择
if (inkCanvas != null)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
}
}
// 应用矩阵变换到元素
+8
View File
@@ -750,6 +750,10 @@ namespace Ink_Canvas
{
overlay.CaptureMouse();
StartAdvancedEraserOperation(sender);
// 处理单点擦除
var position = e.GetPosition((UIElement)FindName("inkCanvas"));
UpdateAdvancedEraserPosition(sender, position);
}
};
@@ -787,6 +791,10 @@ namespace Ink_Canvas
overlay.CaptureStylus();
}
StartAdvancedEraserOperation(sender);
// 处理单点擦除
var position = e.GetPosition((UIElement)FindName("inkCanvas"));
UpdateAdvancedEraserPosition(sender, position);
}
};
+99 -46
View File
@@ -2,7 +2,6 @@ using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -79,6 +78,7 @@ namespace Ink_Canvas
{
if (ToggleSwitchEnableMultiTouchMode.IsOn)
{
// 多指书写模式启用时,手势功能被禁用
TwoFingerGestureSimpleStackPanel.Opacity = 0.5;
TwoFingerGestureSimpleStackPanel.IsHitTestVisible = false;
EnableTwoFingerGestureBtn.Source =
@@ -94,9 +94,14 @@ namespace Ink_Canvas
}
else
{
// 多指书写模式禁用时,根据实际手势功能状态显示
TwoFingerGestureSimpleStackPanel.Opacity = 1;
TwoFingerGestureSimpleStackPanel.IsHitTestVisible = true;
if (Settings.Gesture.IsEnableTwoFingerGesture)
// 检查是否有任何手势功能启用
bool hasGestureEnabled = Settings.Gesture.IsEnableTwoFingerGesture;
if (hasGestureEnabled)
{
EnableTwoFingerGestureBtn.Source =
new BitmapImage(new Uri("/Resources/new-icons/gesture-enabled.png", UriKind.Relative));
@@ -130,11 +135,11 @@ namespace Ink_Canvas
/// </summary>
private void CheckEnableTwoFingerGestureBtnVisibility(bool isVisible)
{
// 在PPT模式下根据设置决定是否显示手势按钮
if (currentMode == 0 || BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
// 在PPT放映模式下根据设置决定是否显示手势按钮
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
{
// 如果启用了PPT放映模式显示手势按钮,则显示手势按钮(在PPT模式下不依赖手势功能是否启用)
if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow && isVisible)
// 如果启用了PPT放映模式显示手势按钮,且当前处于批注模式,则显示手势按钮
if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow && isVisible && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
{
EnableTwoFingerGestureBorder.Visibility = Visibility.Visible;
}
@@ -145,6 +150,13 @@ namespace Ink_Canvas
return;
}
// 在屏幕模式(非放映模式)下,不显示手势按钮
if (currentMode == 0)
{
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
return;
}
if (StackPanelCanvasControls.Visibility != Visibility.Visible
|| BorderFloatingBarMainControls.Visibility != Visibility.Visible)
{
@@ -358,11 +370,15 @@ namespace Ink_Canvas
{
BorderSettings.Visibility = Visibility.Collapsed;
isOpeningOrHidingSettingsPane = false;
// 在设置面板完全关闭后,根据当前设置恢复无焦点模式
if (Settings.Advanced.IsNoFocusMode)
// 在设置面板完全关闭后,根据情况恢复无焦点模式状态
if (!userChangedNoFocusModeInSettings && wasNoFocusModeBeforeSettings)
{
// 如果用户没有在设置中修改无焦点模式,则恢复之前的状态
Settings.Advanced.IsNoFocusMode = true;
ToggleSwitchNoFocusMode.IsOn = true; // 同步更新设置面板中的开关状态
ApplyNoFocusMode();
}
// 如果用户在设置中修改了无焦点模式,则保持用户的修改
};
BorderSettings.Visibility = Visibility.Visible;
@@ -668,7 +684,7 @@ namespace Ink_Canvas
HideSubPanelsImmediately();
// 只有在PPT放映模式下且页数有效时才显示翻页按钮
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
PPTManager?.IsInSlideShow == true &&
PPTManager?.SlidesCount > 0)
@@ -688,14 +704,12 @@ namespace Ink_Canvas
RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
LogHelper.WriteLogToFile($"隐藏PPT翻页按钮 - 放映状态: {PPTManager?.IsInSlideShow}, 页数: {PPTManager?.SlidesCount}", LogHelper.LogType.Trace);
}
// 使用PPT UI管理器来正确更新翻页按钮显示状态,确保遵循用户设置
if (_pptUIManager != null)
{
_pptUIManager.UpdateNavigationPanelsVisibility();
LogHelper.WriteLogToFile($"使用PPT UI管理器更新翻页按钮显示状态", LogHelper.LogType.Trace);
}
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
@@ -827,10 +841,10 @@ namespace Ink_Canvas
if (sender == SymbolIconSelect && lastBorderMouseDownObject != SymbolIconSelect) return;
BtnSelect_Click(null, null);
// 更新模式缓存
UpdateCurrentToolMode("select");
HideSubPanels("select");
}
@@ -967,7 +981,7 @@ namespace Ink_Canvas
var randWindow = new RandWindow(Settings);
randWindow.Show();
// 使用延迟确保窗口完全显示后再强制置顶
randWindow.Dispatcher.BeginInvoke(new Action(() =>
{
@@ -976,10 +990,10 @@ namespace Ink_Canvas
// 强制激活窗口
randWindow.Activate();
randWindow.Focus();
// 设置置顶
randWindow.Topmost = true;
// 使用Win32 API强制置顶
var hwnd = new WindowInteropHelper(randWindow).Handle;
if (hwnd != IntPtr.Zero)
@@ -1080,20 +1094,37 @@ namespace Ink_Canvas
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
// 检查是否启用了直接调用ClassIsland点名功能
// 检查是否启用了外部点名功能
if (Settings.RandSettings.DirectCallCiRand)
{
try
{
string protocol = "";
switch (Settings.RandSettings.ExternalCallerType)
{
case 0: // ClassIsland点名
protocol = "classisland://plugins/IslandCaller/Simple/1";
break;
case 1: // SecRandom点名
protocol = "secrandom://direct_extraction";
break;
case 2: // NamePicker点名
protocol = "namepicker://";
break;
default:
protocol = "classisland://plugins/IslandCaller/Simple/1";
break;
}
Process.Start(new ProcessStartInfo
{
FileName = "classisland://plugins/IslandCaller/Simple/1",
FileName = protocol,
UseShellExecute = true
});
}
catch (Exception ex)
{
MessageBox.Show("无法调用ClassIsland点名:" + ex.Message);
MessageBox.Show("无法调用外部点名:" + ex.Message);
// 调用失败时回退到默认的随机点名窗口
new RandWindow(Settings, true).ShowDialog();
@@ -1404,19 +1435,19 @@ namespace Ink_Canvas
// 计算浮动栏位置,考虑快捷调色盘的显示状态
// 使用更可靠的方法获取浮动栏宽度
double baseWidth = ViewboxFloatingBar.ActualWidth;
// 如果ActualWidth为0,尝试使用DesiredSize
if (baseWidth <= 0)
{
baseWidth = ViewboxFloatingBar.DesiredSize.Width;
}
// 如果仍然为0,使用RenderSize
if (baseWidth <= 0)
{
baseWidth = ViewboxFloatingBar.RenderSize.Width;
}
// 如果所有方法都失败,使用一个基于内容的估算值
if (baseWidth <= 0)
{
@@ -1424,9 +1455,9 @@ namespace Ink_Canvas
baseWidth = 200; // 最小宽度
LogHelper.WriteLogToFile($"浮动栏宽度无法获取,使用估算值: {baseWidth}");
}
double floatingBarWidth = baseWidth * ViewboxFloatingBarScaleTransform.ScaleX;
// 如果快捷调色盘显示,确保有足够空间
if ((QuickColorPalettePanel != null && QuickColorPalettePanel.Visibility == Visibility.Visible) ||
@@ -1549,19 +1580,19 @@ namespace Ink_Canvas
// 计算浮动栏位置,考虑快捷调色盘的显示状态
// 使用更可靠的方法获取浮动栏宽度
double baseWidth = ViewboxFloatingBar.ActualWidth;
// 如果ActualWidth为0,尝试使用DesiredSize
if (baseWidth <= 0)
{
baseWidth = ViewboxFloatingBar.DesiredSize.Width;
}
// 如果仍然为0,使用RenderSize
if (baseWidth <= 0)
{
baseWidth = ViewboxFloatingBar.RenderSize.Width;
}
// 如果所有方法都失败,使用一个基于内容的估算值
if (baseWidth <= 0)
{
@@ -1569,9 +1600,9 @@ namespace Ink_Canvas
baseWidth = 200; // 最小宽度
LogHelper.WriteLogToFile($"浮动栏宽度无法获取,使用估算值: {baseWidth}");
}
double floatingBarWidth = baseWidth * ViewboxFloatingBarScaleTransform.ScaleX;
// 如果快捷调色盘显示,确保有足够空间
if ((QuickColorPalettePanel != null && QuickColorPalettePanel.Visibility == Visibility.Visible) ||
@@ -1655,19 +1686,19 @@ namespace Ink_Canvas
// 计算浮动栏位置,考虑快捷调色盘的显示状态
// 使用更可靠的方法获取浮动栏宽度
double baseWidth = ViewboxFloatingBar.ActualWidth;
// 如果ActualWidth为0,尝试使用DesiredSize
if (baseWidth <= 0)
{
baseWidth = ViewboxFloatingBar.DesiredSize.Width;
}
// 如果仍然为0,使用RenderSize
if (baseWidth <= 0)
{
baseWidth = ViewboxFloatingBar.RenderSize.Width;
}
// 如果所有方法都失败,使用一个基于内容的估算值
if (baseWidth <= 0)
{
@@ -1675,9 +1706,9 @@ namespace Ink_Canvas
baseWidth = 200; // 最小宽度
LogHelper.WriteLogToFile($"浮动栏宽度无法获取,使用估算值: {baseWidth}");
}
double floatingBarWidth = baseWidth * ViewboxFloatingBarScaleTransform.ScaleX;
// 如果快捷调色盘显示,确保有足够空间
if ((QuickColorPalettePanel != null && QuickColorPalettePanel.Visibility == Visibility.Visible) ||
@@ -1735,7 +1766,7 @@ namespace Ink_Canvas
// 使用集中化的工具模式切换方法,确保快捷键状态正确更新
// 鼠标模式下应该禁用快捷键以放行键盘操作
SetCurrentToolMode(InkCanvasEditingMode.None);
// 更新模式缓存,确保后续的模式检测正确
UpdateCurrentToolMode("cursor");
@@ -1844,6 +1875,13 @@ namespace Ink_Canvas
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
if (sender == Pen_Icon && lastBorderMouseDownObject != Pen_Icon) return;
// 如果当前有选中的图片元素,先取消选中
if (currentSelectedElement != null)
{
UnselectElement(currentSelectedElement);
currentSelectedElement = null;
}
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
@@ -1867,7 +1905,7 @@ namespace Ink_Canvas
{
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.Ink);
// 更新模式缓存
UpdateCurrentToolMode("pen");
@@ -1904,7 +1942,7 @@ namespace Ink_Canvas
CheckEnableTwoFingerGestureBtnVisibility(true);
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.Ink);
// 更新模式缓存
UpdateCurrentToolMode("pen");
@@ -2029,7 +2067,7 @@ namespace Ink_Canvas
}
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.Ink);
// 更新模式缓存
UpdateCurrentToolMode("pen");
@@ -2093,10 +2131,10 @@ namespace Ink_Canvas
// 使用新的高级橡皮擦系统
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.EraseByPoint);
// 更新模式缓存
UpdateCurrentToolMode("eraser");
ApplyAdvancedEraserShape(); // 使用新的橡皮擦形状应用方法
SetCursorBasedOnEditingMode(inkCanvas);
HideSubPanels("eraser"); // 高亮橡皮按钮
@@ -2137,10 +2175,10 @@ namespace Ink_Canvas
// 使用新的高级橡皮擦系统
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.EraseByPoint);
// 更新模式缓存
UpdateCurrentToolMode("eraser");
ApplyAdvancedEraserShape(); // 使用新的橡皮擦形状应用方法
SetCursorBasedOnEditingMode(inkCanvas);
HideSubPanels("eraser"); // 高亮橡皮按钮
@@ -2179,10 +2217,10 @@ namespace Ink_Canvas
inkCanvas.EraserShape = new EllipseStylusShape(5, 5);
// 使用集中化的工具模式切换方法
SetCurrentToolMode(InkCanvasEditingMode.EraseByStroke);
// 更新模式缓存
UpdateCurrentToolMode("eraserByStrokes");
drawingShapeMode = 0;
// 修复:切换到线擦时,保存当前的笔类型状态,而不是强制重置
@@ -2599,6 +2637,7 @@ namespace Ink_Canvas
{
// 临时禁用无焦点模式以避免下拉选项被遮挡
wasNoFocusModeBeforeSettings = Settings.Advanced.IsNoFocusMode;
userChangedNoFocusModeInSettings = false; // 重置用户修改标志
if (wasNoFocusModeBeforeSettings)
{
Settings.Advanced.IsNoFocusMode = false;
@@ -3092,6 +3131,11 @@ namespace Ink_Canvas
}
timeMachine.CommitElementInsertHistory(image);
// 插入图片后切换到选择模式并刷新浮动栏高光显示
SetCurrentToolMode(InkCanvasEditingMode.Select);
UpdateCurrentToolMode("select");
HideSubPanels("select");
}
}
}
@@ -3158,6 +3202,11 @@ namespace Ink_Canvas
}
timeMachine.CommitElementInsertHistory(image);
// 插入图片后切换到选择模式并刷新浮动栏高光显示
SetCurrentToolMode(InkCanvasEditingMode.Select);
UpdateCurrentToolMode("select");
HideSubPanels("select");
}
}
}
@@ -3224,6 +3273,11 @@ namespace Ink_Canvas
}
timeMachine.CommitElementInsertHistory(image);
// 插入图片后切换到选择模式并刷新浮动栏高光显示
SetCurrentToolMode(InkCanvasEditingMode.Select);
UpdateCurrentToolMode("select");
HideSubPanels("select");
}
}
}
@@ -3510,7 +3564,6 @@ namespace Ink_Canvas
private void UpdateCurrentToolMode(string mode)
{
_currentToolMode = mode;
LogHelper.WriteLogToFile($"更新工具模式缓存: {mode}", LogHelper.LogType.Trace);
}
#endregion
@@ -0,0 +1,328 @@
using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern.Controls;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace Ink_Canvas
{
public partial class MainWindow : Window
{
#region
/// <summary>
/// 初始化悬浮窗拦截管理器
/// </summary>
private void InitializeFloatingWindowInterceptor()
{
try
{
_floatingWindowInterceptorManager = new FloatingWindowInterceptorManager();
// 订阅事件
_floatingWindowInterceptorManager.WindowIntercepted += OnFloatingWindowIntercepted;
_floatingWindowInterceptorManager.WindowRestored += OnFloatingWindowRestored;
// 初始化拦截器
_floatingWindowInterceptorManager.Initialize(Settings.Automation.FloatingWindowInterceptor);
// 加载UI状态
LoadFloatingWindowInterceptorUI();
LogHelper.WriteLogToFile("悬浮窗拦截管理器初始化完成", LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化悬浮窗拦截管理器失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 加载悬浮窗拦截UI状态
/// </summary>
private void LoadFloatingWindowInterceptorUI()
{
try
{
if (!isLoaded) return;
// 设置主开关状态
ToggleSwitchFloatingWindowInterceptorEnabled.IsOn = Settings.Automation.FloatingWindowInterceptor.IsEnabled;
// 设置各个拦截规则的状态
foreach (var kvp in Settings.Automation.FloatingWindowInterceptor.InterceptRules)
{
var toggleName = $"ToggleSwitch{kvp.Key}";
var toggle = FindName(toggleName) as ToggleSwitch;
if (toggle != null)
{
toggle.IsOn = kvp.Value;
}
}
// 更新UI可见性
UpdateFloatingWindowInterceptorUI();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"加载悬浮窗拦截UI状态失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新悬浮窗拦截UI
/// </summary>
private void UpdateFloatingWindowInterceptorUI()
{
try
{
var isEnabled = Settings.Automation.FloatingWindowInterceptor.IsEnabled;
FloatingWindowInterceptorGrid.Visibility = isEnabled ? Visibility.Visible : Visibility.Collapsed;
// 计算启用的规则数量
var enabledRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Where(kvp => kvp.Value).Count();
var totalRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Count;
// 更新状态文本
if (_floatingWindowInterceptorManager != null)
{
var stats = _floatingWindowInterceptorManager.GetStatistics();
TextBlockFloatingWindowInterceptorStatus.Text = stats.IsRunning
? $"拦截器运行中 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则"
: $"拦截器未启动 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则";
}
else
{
TextBlockFloatingWindowInterceptorStatus.Text = $"拦截器未初始化 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则";
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新悬浮窗拦截UI失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 窗口被拦截事件处理
/// </summary>
private void OnFloatingWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e)
{
try
{
// 在UI线程中更新状态
Dispatcher.BeginInvoke(new Action(() =>
{
UpdateFloatingWindowInterceptorUI();
}));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口拦截事件失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 窗口被恢复事件处理
/// </summary>
private void OnFloatingWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e)
{
try
{
// 在UI线程中更新状态
Dispatcher.BeginInvoke(new Action(() =>
{
UpdateFloatingWindowInterceptorUI();
}));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理窗口恢复事件失败: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
#region
/// <summary>
/// 主开关切换事件
/// </summary>
private void ToggleSwitchFloatingWindowInterceptorEnabled_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
try
{
Settings.Automation.FloatingWindowInterceptor.IsEnabled = ToggleSwitchFloatingWindowInterceptorEnabled.IsOn;
if (_floatingWindowInterceptorManager != null)
{
if (Settings.Automation.FloatingWindowInterceptor.IsEnabled)
{
_floatingWindowInterceptorManager.Start();
}
else
{
_floatingWindowInterceptorManager.Stop();
}
}
UpdateFloatingWindowInterceptorUI();
SaveSettingsToFile();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换悬浮窗拦截主开关失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 希沃白板3拦截开关
/// </summary>
private void ToggleSwitchSeewoWhiteboard3Floating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard3Floating, ToggleSwitchSeewoWhiteboard3Floating.IsOn);
}
/// <summary>
/// 希沃白板5拦截开关
/// </summary>
private void ToggleSwitchSeewoWhiteboard5Floating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5Floating, ToggleSwitchSeewoWhiteboard5Floating.IsOn);
}
/// <summary>
/// 希沃白板5C拦截开关
/// </summary>
private void ToggleSwitchSeewoWhiteboard5CFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5CFloating, ToggleSwitchSeewoWhiteboard5CFloating.IsOn);
}
/// <summary>
/// 希沃品课侧栏拦截开关
/// </summary>
private void ToggleSwitchSeewoPincoSideBarFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoSideBarFloating, ToggleSwitchSeewoPincoSideBarFloating.IsOn);
}
/// <summary>
/// 希沃品课画笔拦截开关
/// </summary>
private void ToggleSwitchSeewoPincoDrawingFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoDrawingFloating, ToggleSwitchSeewoPincoDrawingFloating.IsOn);
}
/// <summary>
/// 希沃PPT小工具拦截开关
/// </summary>
private void ToggleSwitchSeewoPPTFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPPTFloating, ToggleSwitchSeewoPPTFloating.IsOn);
}
/// <summary>
/// AiClass拦截开关
/// </summary>
private void ToggleSwitchAiClassFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.AiClassFloating, ToggleSwitchAiClassFloating.IsOn);
}
/// <summary>
/// 鸿合屏幕书写拦截开关
/// </summary>
private void ToggleSwitchHiteAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.HiteAnnotationFloating, ToggleSwitchHiteAnnotationFloating.IsOn);
}
/// <summary>
/// 畅言智慧课堂拦截开关
/// </summary>
private void ToggleSwitchChangYanFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanFloating, ToggleSwitchChangYanFloating.IsOn);
}
/// <summary>
/// 畅言PPT拦截开关
/// </summary>
private void ToggleSwitchChangYanPptFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanPptFloating, ToggleSwitchChangYanPptFloating.IsOn);
}
/// <summary>
/// 天喻教育云拦截开关
/// </summary>
private void ToggleSwitchIntelligentClassFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.IntelligentClassFloating, ToggleSwitchIntelligentClassFloating.IsOn);
}
/// <summary>
/// 希沃桌面画笔拦截开关
/// </summary>
private void ToggleSwitchSeewoDesktopAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopAnnotationFloating, ToggleSwitchSeewoDesktopAnnotationFloating.IsOn);
}
/// <summary>
/// 希沃桌面侧栏拦截开关
/// </summary>
private void ToggleSwitchSeewoDesktopSideBarFloating_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopSideBarFloating, ToggleSwitchSeewoDesktopSideBarFloating.IsOn);
}
/// <summary>
/// 设置拦截规则
/// </summary>
private void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
{
try
{
if (_floatingWindowInterceptorManager != null)
{
_floatingWindowInterceptorManager.SetInterceptRule(type, enabled);
}
// 更新设置
var ruleName = type.ToString();
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(ruleName))
{
Settings.Automation.FloatingWindowInterceptor.InterceptRules[ruleName] = enabled;
}
// 更新UI显示
UpdateFloatingWindowInterceptorUI();
SaveSettingsToFile();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"设置拦截规则失败: {ex.Message}", LogHelper.LogType.Error);
}
}
#endregion
}
}
+4 -4
View File
@@ -8,8 +8,8 @@ namespace Ink_Canvas
private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
{
// 只有在PPT放映模式下才响应鼠标滚轮翻页
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
currentMode != 0 ||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
currentMode != 0 ||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible ||
PPTManager?.IsInSlideShow != true) return;
@@ -29,8 +29,8 @@ namespace Ink_Canvas
private void Main_Grid_PreviewKeyDown(object sender, KeyEventArgs e)
{
// 只有在PPT放映模式下才响应键盘翻页快捷键
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
currentMode != 0 ||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
currentMode != 0 ||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible ||
PPTManager?.IsInSlideShow != true) return;
+341 -36
View File
@@ -27,11 +27,15 @@ namespace Ink_Canvas
{
public Rectangle Area;
public List<Point> Path;
public Bitmap CameraImage;
public BitmapSource CameraBitmapSource;
public ScreenshotResult(Rectangle area, List<Point> path = null)
public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null, BitmapSource cameraBitmapSource = null)
{
Area = area;
Path = path;
CameraImage = cameraImage;
CameraBitmapSource = cameraBitmapSource;
}
}
@@ -55,34 +59,48 @@ namespace Ink_Canvas
// 恢复窗口显示
Visibility = originalVisibility;
if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
if (screenshotResult.HasValue)
{
// 截取选定区域
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
// 检查是否是摄像头截图
if (screenshotResult.Value.CameraBitmapSource != null)
{
if (originalBitmap != null)
// 摄像头截图(使用BitmapSource
await InsertBitmapSourceToCanvas(screenshotResult.Value.CameraBitmapSource);
}
else if (screenshotResult.Value.CameraImage != null)
{
// 摄像头截图(使用Bitmap
await InsertScreenshotToCanvas(screenshotResult.Value.CameraImage);
}
else if (screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
{
// 屏幕截图
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
{
Bitmap finalBitmap = originalBitmap;
bool needDisposeFinalBitmap = false;
try
if (originalBitmap != null)
{
// 如果有路径信息,应用形状遮罩
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
Bitmap finalBitmap = originalBitmap;
bool needDisposeFinalBitmap = false;
try
{
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
// 如果有路径信息,应用形状遮罩
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
{
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
}
// 将截图转换为WPF Image并插入到画布
await InsertScreenshotToCanvas(finalBitmap);
}
// 将截图转换为WPF Image并插入到画布
await InsertScreenshotToCanvas(finalBitmap);
}
finally
{
// 如果创建了新的位图,需要释放它
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
finally
{
finalBitmap.Dispose();
// 如果创建了新的位图,需要释放它
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
{
finalBitmap.Dispose();
}
}
}
}
@@ -100,6 +118,46 @@ namespace Ink_Canvas
}
}
// 直接全屏截图并插入到画布
private async Task CaptureFullScreenAndInsert()
{
try
{
// 隐藏主窗口以避免截图包含窗口本身
var originalVisibility = Visibility;
Visibility = Visibility.Hidden;
// 等待窗口隐藏
await Task.Delay(200);
// 获取虚拟屏幕边界
var virtualScreen = SystemInformation.VirtualScreen;
var fullScreenArea = new Rectangle(virtualScreen.X, virtualScreen.Y, virtualScreen.Width, virtualScreen.Height);
// 截取全屏
using (var fullScreenBitmap = CaptureScreenArea(fullScreenArea))
{
if (fullScreenBitmap != null)
{
// 将截图转换为WPF Image并插入到画布
await InsertScreenshotToCanvas(fullScreenBitmap);
}
else
{
ShowNotification("全屏截图失败");
}
}
// 恢复窗口显示
Visibility = originalVisibility;
}
catch (Exception ex)
{
ShowNotification($"全屏截图失败: {ex.Message}");
Visibility = Visibility.Visible;
}
}
// 显示截图区域选择器
private async Task<ScreenshotResult?> ShowScreenshotSelector()
{
@@ -112,10 +170,31 @@ namespace Ink_Canvas
var selectorWindow = new ScreenshotSelectorWindow();
if (selectorWindow.ShowDialog() == true)
{
result = new ScreenshotResult(
selectorWindow.SelectedArea.Value,
selectorWindow.SelectedPath
);
// 检查是否是摄像头截图
if (selectorWindow.CameraBitmapSource != null)
{
result = new ScreenshotResult(
Rectangle.Empty, // 摄像头截图不需要区域
null, // 摄像头截图不需要路径
null, // 不再使用Bitmap
selectorWindow.CameraBitmapSource // 摄像头BitmapSource
);
}
else if (selectorWindow.CameraImage != null)
{
result = new ScreenshotResult(
Rectangle.Empty, // 摄像头截图不需要区域
null, // 摄像头截图不需要路径
selectorWindow.CameraImage // 摄像头图像
);
}
else
{
result = new ScreenshotResult(
selectorWindow.SelectedArea.Value,
selectorWindow.SelectedPath
);
}
}
});
}
@@ -173,8 +252,21 @@ namespace Ink_Canvas
{
try
{
// 验证位图有效性
if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
{
ShowNotification("无效的截图");
return;
}
// 将Bitmap转换为WPF BitmapSource
var bitmapSource = ConvertBitmapToBitmapSource(bitmap);
if (bitmapSource == null)
{
ShowNotification("转换截图失败");
return;
}
// 创建WPF Image控件
var image = new Image
@@ -216,6 +308,11 @@ namespace Ink_Canvas
// 提交历史记录
timeMachine.CommitElementInsertHistory(image);
// 插入图片后切换到选择模式并刷新浮动栏高光显示
SetCurrentToolMode(InkCanvasEditingMode.Select);
UpdateCurrentToolMode("select");
HideSubPanels("select");
ShowNotification("截图已插入到画布");
}
catch (Exception ex)
@@ -223,6 +320,69 @@ namespace Ink_Canvas
ShowNotification($"插入截图失败: {ex.Message}");
LogHelper.WriteLogToFile($"插入截图失败: {ex.Message}", LogHelper.LogType.Error);
}
finally
{
bitmap?.Dispose();
}
}
// 将BitmapSource插入到画布(用于摄像头截图)
private async Task InsertBitmapSourceToCanvas(BitmapSource bitmapSource)
{
try
{
// 创建WPF Image控件
var image = new Image
{
Source = bitmapSource,
Stretch = Stretch.Uniform
};
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
// 生成唯一名称
string timestamp = "camera_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
image.Name = timestamp;
// 初始化TransformGroup
InitializeScreenshotTransform(image);
// 设置截图属性,避免被InkCanvas选择系统处理
image.IsHitTestVisible = true;
image.Focusable = false;
// 初始化InkCanvas选择设置
InitializeInkCanvasSelectionSettings();
// 等待图片加载完成后再进行居中处理
image.Loaded += (sender, e) =>
{
// 确保在UI线程中执行
Dispatcher.BeginInvoke(new Action(() =>
{
CenterAndScaleScreenshot(image);
// 绑定事件处理器
BindScreenshotEvents(image);
}), DispatcherPriority.Loaded);
};
// 添加到画布
inkCanvas.Children.Add(image);
// 提交历史记录
timeMachine.CommitElementInsertHistory(image);
// 插入图片后切换到选择模式并刷新浮动栏高光显示
SetCurrentToolMode(InkCanvasEditingMode.Select);
UpdateCurrentToolMode("select");
HideSubPanels("select");
ShowNotification("摄像头截图已插入到画布");
}
catch (Exception ex)
{
ShowNotification($"插入摄像头截图失败: {ex.Message}");
LogHelper.WriteLogToFile($"插入摄像头截图失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 初始化截图的TransformGroup
@@ -433,24 +593,169 @@ namespace Ink_Canvas
{
try
{
using (var memory = new MemoryStream())
// 验证位图有效性
if (bitmap == null)
return null;
// 验证位图尺寸
if (bitmap.Width <= 0 || bitmap.Height <= 0)
return null;
// 使用更安全的方法转换位图
var bitmapData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly,
bitmap.PixelFormat);
try
{
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
// 根据像素格式选择合适的WPF像素格式
System.Windows.Media.PixelFormat wpfPixelFormat;
switch (bitmap.PixelFormat)
{
case PixelFormat.Format24bppRgb:
wpfPixelFormat = PixelFormats.Bgr24;
break;
case PixelFormat.Format32bppArgb:
wpfPixelFormat = PixelFormats.Bgra32;
break;
case PixelFormat.Format32bppRgb:
wpfPixelFormat = PixelFormats.Bgr32;
break;
default:
wpfPixelFormat = PixelFormats.Bgr24;
break;
}
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = memory;
bitmapImage.EndInit();
bitmapImage.Freeze();
var bitmapSource = BitmapSource.Create(
bitmapData.Width,
bitmapData.Height,
bitmap.HorizontalResolution,
bitmap.VerticalResolution,
wpfPixelFormat,
null,
bitmapData.Scan0,
bitmapData.Stride * bitmapData.Height,
bitmapData.Stride);
return bitmapImage;
bitmapSource.Freeze();
return bitmapSource;
}
finally
{
bitmap.UnlockBits(bitmapData);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"转换位图失败: {ex.Message}", LogHelper.LogType.Error);
// 尝试使用备用方法:内存流转换
try
{
return ConvertBitmapToBitmapSourceFallback(bitmap);
}
catch (Exception fallbackEx)
{
LogHelper.WriteLogToFile($"备用转换方法也失败: {fallbackEx.Message}", LogHelper.LogType.Error);
// 最后尝试:使用最简单的转换方法
try
{
return ConvertBitmapToBitmapSourceSimple(bitmap);
}
catch (Exception simpleEx)
{
LogHelper.WriteLogToFile($"简单转换方法也失败: {simpleEx.Message}", LogHelper.LogType.Error);
throw;
}
}
}
}
// 备用的位图转换方法(使用内存流)
private BitmapSource ConvertBitmapToBitmapSourceFallback(Bitmap bitmap)
{
try
{
// 验证位图有效性
if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
return null;
// 创建一个新的位图,确保格式正确
using (var convertedBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb))
{
using (var graphics = Graphics.FromImage(convertedBitmap))
{
graphics.DrawImage(bitmap, 0, 0);
}
using (var memory = new MemoryStream())
{
convertedBitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = memory;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"备用转换方法失败: {ex.Message}", LogHelper.LogType.Error);
throw;
}
}
// 最简单的位图转换方法
private BitmapSource ConvertBitmapToBitmapSourceSimple(Bitmap bitmap)
{
try
{
if (bitmap == null)
return null;
// 使用最基础的方法:直接保存为PNG然后加载
var tempFile = Path.GetTempFileName() + ".png";
try
{
bitmap.Save(tempFile, ImageFormat.Png);
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(tempFile);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
finally
{
// 清理临时文件
try
{
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}
catch (Exception deleteEx)
{
LogHelper.WriteLogToFile($"删除临时文件失败: {deleteEx.Message}", LogHelper.LogType.Warning);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"简单转换方法失败: {ex.Message}", LogHelper.LogType.Error);
throw;
}
}
+195 -76
View File
@@ -3,7 +3,6 @@ using iNKORE.UI.WPF.Modern;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.PowerPoint;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
@@ -11,11 +10,12 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using Application = System.Windows.Application;
using File = System.IO.File;
using MessageBox = System.Windows.MessageBox;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
using MouseButtonEventArgs = System.Windows.Input.MouseButtonEventArgs;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
@@ -88,12 +88,22 @@ namespace Ink_Canvas
// PowerPoint应用程序守护相关字段
private DispatcherTimer _powerPointProcessMonitorTimer;
private const int ProcessMonitorInterval = 5000; // 应用程序监控间隔(毫秒)
private const int ProcessMonitorInterval = 1000; // 应用程序监控间隔(毫秒)
// 上次播放位置相关字段
private int _lastPlaybackPage = 0;
private bool _shouldNavigateToLastPage = false;
// 页面切换防抖机制
private DateTime _lastSlideSwitchTime = DateTime.MinValue;
private int _pendingSlideIndex = -1;
private System.Timers.Timer _slideSwitchDebounceTimer;
private const int SlideSwitchDebounceMs = 150; // 防抖延迟150毫秒
#endregion
#region PPT Managers
private PPTManager _pptManager;
private PPTInkManager _pptInkManager;
private MultiPPTInkManager _multiPPTInkManager;
private PPTUIManager _pptUIManager;
/// <summary>
@@ -123,10 +133,11 @@ namespace Ink_Canvas
_pptManager.PresentationClose += OnPPTPresentationClose;
_pptManager.SlideShowStateChanged += OnPPTSlideShowStateChanged;
// 初始化墨迹管理器
_pptInkManager = new PPTInkManager();
_pptInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
_pptInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
// 初始化多PPT墨迹管理器
_multiPPTInkManager = new MultiPPTInkManager();
_multiPPTInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
_multiPPTInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
_multiPPTInkManager.PPTManager = _pptManager;
// 初始化UI管理器
_pptUIManager = new PPTUIManager(this);
@@ -230,12 +241,12 @@ namespace Ink_Canvas
// 创建新的PowerPoint应用程序实例
pptApplication = new Microsoft.Office.Interop.PowerPoint.Application();
// 设置为不可见,作为后台进程
pptApplication.Visible = Microsoft.Office.Core.MsoTriState.msoFalse;
pptApplication.Visible = MsoTriState.msoFalse;
// 设置应用程序属性
pptApplication.WindowState = Microsoft.Office.Interop.PowerPoint.PpWindowState.ppWindowMinimized;
pptApplication.WindowState = PpWindowState.ppWindowMinimized;
// 直接设置PPTManager的PPTApplication属性,绕过COM注册问题
Task.Delay(1000).ContinueWith(_ =>
@@ -278,9 +289,9 @@ namespace Ink_Canvas
// 使用反射调用PPTManager的ConnectToPPT方法
var pptManagerType = _pptManager.GetType();
var connectMethod = pptManagerType.GetMethod("ConnectToPPT",
var connectMethod = pptManagerType.GetMethod("ConnectToPPT",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (connectMethod != null)
{
connectMethod.Invoke(_pptManager, new object[] { app });
@@ -289,9 +300,9 @@ namespace Ink_Canvas
else
{
// 如果无法通过反射调用,尝试直接设置属性
var pptApplicationProperty = pptManagerType.GetProperty("PPTApplication",
var pptApplicationProperty = pptManagerType.GetProperty("PPTApplication",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (pptApplicationProperty != null && pptApplicationProperty.CanWrite)
{
pptApplicationProperty.SetValue(_pptManager, app);
@@ -318,7 +329,7 @@ namespace Ink_Canvas
{
if (pptApplication == null) return false;
if (!Marshal.IsComObject(pptApplication)) return false;
// 尝试访问一个简单的属性来验证连接是否有效
var _ = pptApplication.Name;
return true;
@@ -363,12 +374,12 @@ namespace Ink_Canvas
// 退出PowerPoint应用程序
pptApplication.Quit();
// 释放COM对象
Marshal.ReleaseComObject(pptApplication);
pptApplication = null;
}
LogHelper.WriteLogToFile("PowerPoint应用程序已关闭", LogHelper.LogType.Event);
}
catch (Exception ex)
@@ -409,11 +420,11 @@ namespace Ink_Canvas
try
{
_pptManager?.Dispose();
_pptInkManager?.Dispose();
_multiPPTInkManager?.Dispose();
_longPressTimer?.Stop();
_longPressTimer = null;
_pptManager = null;
_pptInkManager = null;
_multiPPTInkManager = null;
_pptUIManager = null;
// 清理PowerPoint进程守护
@@ -448,6 +459,8 @@ namespace Ink_Canvas
if (!Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) return;
_isLongPressNext = isNext;
// 重置定时器间隔为初始延迟时间,确保每次长按检测都从正确的延迟开始
_longPressTimer.Interval = TimeSpan.FromMilliseconds(LongPressDelay);
_longPressTimer?.Start();
}
@@ -497,7 +510,7 @@ namespace Ink_Canvas
{
LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event);
// 清理墨迹管理器
_pptInkManager?.ClearAllStrokes();
_multiPPTInkManager?.ClearAllStrokes();
}
});
}
@@ -522,8 +535,8 @@ namespace Ink_Canvas
TimeMachineHistories[0] = null;
}
// 初始化墨迹管理器
_pptInkManager?.InitializePresentation(pres);
// 初始化多PPT墨迹管理器
_multiPPTInkManager?.InitializePresentation(pres);
// 处理跳转到首页或上次播放页的逻辑
HandlePresentationOpenNavigation(pres);
@@ -558,17 +571,24 @@ namespace Ink_Canvas
Application.Current.Dispatcher.InvokeAsync(() =>
{
// 保存所有墨迹
_pptInkManager?.SaveAllStrokesToFile(pres);
_multiPPTInkManager?.SaveAllStrokesToFile(pres);
// 清理墨迹管理器
_pptInkManager?.ClearAllStrokes();
// 移除演示文稿管理器
_multiPPTInkManager?.RemovePresentation(pres);
_pptUIManager?.UpdateConnectionStatus(false);
});
}
catch (Exception ex)
catch (COMException comEx)
{
// COM对象已失效,这是正常情况,完全静默处理
var hr = (uint)comEx.HResult;
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
{
}
}
catch (Exception)
{
LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error);
}
}
@@ -583,7 +603,6 @@ namespace Ink_Canvas
if (!isInSlideShow)
{
LogHelper.WriteLogToFile("PPT放映状态变化:退出放映模式", LogHelper.LogType.Trace);
}
// 检查主窗口可见性(用于仅PPT模式)
@@ -612,11 +631,23 @@ namespace Ink_Canvas
await Application.Current.Dispatcher.InvokeAsync(() =>
{
// 处理跳转到首页
// 获取当前活跃的演示文稿并切换到对应的墨迹管理器
var activePresentation = _pptManager?.GetCurrentActivePresentation();
if (activePresentation != null)
{
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
}
// 处理跳转到首页或上次播放位置
if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter)
{
_pptManager?.TryNavigateToSlide(1);
}
else if (_shouldNavigateToLastPage && _lastPlaybackPage > 0)
{
_pptManager?.TryNavigateToSlide(_lastPlaybackPage);
_shouldNavigateToLastPage = false; // 重置标志位
}
// 更新UI状态
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
@@ -649,7 +680,7 @@ namespace Ink_Canvas
// 在PPT模式下根据设置决定是否隐藏手势面板和手势按钮
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
// 根据设置决定是否在PPT放映模式下显示手势按钮
if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow)
{
@@ -667,7 +698,24 @@ namespace Ink_Canvas
if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow &&
!Settings.Automation.IsAutoFoldInPPTSlideShow)
{
BtnColorRed_Click(null, null);
Dispatcher.BeginInvoke(new Action(() =>
{
try
{
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
{
UpdateCurrentToolMode("pen");
SetFloatingBarHighlightPosition("pen");
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"PPT进入批注模式后同步浮动栏高光状态失败: {ex.Message}", LogHelper.LogType.Error);
}
}), DispatcherPriority.Loaded);
}
isEnteredSlideShowEndEvent = false;
@@ -699,16 +747,19 @@ namespace Ink_Canvas
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
// 获取当前活跃的演示文稿并确保切换到正确的墨迹管理器
var activePresentation = _pptManager?.GetCurrentActivePresentation();
if (activePresentation != null)
{
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
}
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
var totalSlides = _pptManager?.SlidesCount ?? 0;
// 保存上一页墨迹并加载当前页墨迹
SwitchSlideInk(currentSlide);
// 使用防抖机制处理页面切换
HandleSlideSwitchWithDebounce(currentSlide, totalSlides);
// 更新UI
_pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides);
LogHelper.WriteLogToFile($"幻灯片切换到第{currentSlide}页", LogHelper.LogType.Trace);
});
}
catch (Exception ex)
@@ -735,7 +786,7 @@ namespace Ink_Canvas
isEnteredSlideShowEndEvent = true;
// 保存所有墨迹
_pptInkManager?.SaveAllStrokesToFile(pres);
_multiPPTInkManager?.SaveAllStrokesToFile(pres);
await Application.Current.Dispatcher.InvokeAsync(() =>
{
@@ -767,6 +818,9 @@ namespace Ink_Canvas
// 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上)
TimeMachineHistories[0] = null;
// 重置墨迹管理器的锁定状态,防止下次放映时墨迹显示错误
ResetInkManagerLockState();
// 退出PPT模式时恢复手势面板和手势按钮的显示状态
if (Settings.Gesture.IsEnableTwoFingerGesture && ToggleSwitchEnableMultiTouchMode.IsOn)
{
@@ -791,6 +845,7 @@ namespace Ink_Canvas
if (GridTransparencyFakeBackground.Background != Brushes.Transparent)
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
SetCurrentToolMode(InkCanvasEditingMode.None);
}
catch (Exception ex)
{
@@ -798,7 +853,7 @@ namespace Ink_Canvas
}
});
await Task.Delay(150);
await Task.Delay(100);
await Application.Current.Dispatcher.InvokeAsync(() =>
{
// 强制重新计算浮动栏位置,确保在退出PPT模式后正确复位
@@ -850,9 +905,10 @@ namespace Ink_Canvas
if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0)
{
_lastPlaybackPage = page;
new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () =>
{
_pptManager?.TryNavigateToSlide(page);
_shouldNavigateToLastPage = true;
}).ShowDialog();
}
}
@@ -971,7 +1027,7 @@ namespace Ink_Canvas
{
try
{
var strokes = _pptInkManager?.LoadSlideStrokes(slideIndex);
var strokes = _multiPPTInkManager?.LoadSlideStrokes(slideIndex);
if (strokes != null)
{
inkCanvas.Strokes.Clear();
@@ -984,11 +1040,95 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 重置墨迹管理器的锁定状态,防止墨迹显示错误
/// </summary>
private void ResetInkManagerLockState()
{
try
{
// 获取当前活跃的演示文稿
var activePresentation = _pptManager?.GetCurrentActivePresentation();
if (activePresentation != null)
{
// 切换到对应的墨迹管理器
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
// 重置锁定状态
_multiPPTInkManager?.ResetCurrentPresentationLockState();
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"重置墨迹管理器锁定状态失败: {ex}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 使用防抖机制处理页面切换
/// </summary>
private void HandleSlideSwitchWithDebounce(int currentSlide, int totalSlides)
{
try
{
var now = DateTime.Now;
// 如果距离上次切换时间太短,使用防抖机制
if (now - _lastSlideSwitchTime < TimeSpan.FromMilliseconds(SlideSwitchDebounceMs))
{
_pendingSlideIndex = currentSlide;
// 停止之前的定时器
_slideSwitchDebounceTimer?.Stop();
// 创建新的定时器
_slideSwitchDebounceTimer = new System.Timers.Timer(SlideSwitchDebounceMs);
_slideSwitchDebounceTimer.Elapsed += (sender, e) =>
{
Application.Current.Dispatcher.Invoke(() =>
{
if (_pendingSlideIndex > 0)
{
SwitchSlideInk(_pendingSlideIndex);
_pptUIManager?.UpdateCurrentSlideNumber(_pendingSlideIndex, totalSlides);
_pendingSlideIndex = -1;
}
});
_slideSwitchDebounceTimer?.Stop();
};
_slideSwitchDebounceTimer.Start();
}
else
{
// 直接处理页面切换
SwitchSlideInk(currentSlide);
_pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides);
}
_lastSlideSwitchTime = now;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理页面切换防抖失败: {ex}", LogHelper.LogType.Error);
}
}
private void SwitchSlideInk(int newSlideIndex)
{
try
{
var newStrokes = _pptInkManager?.SwitchToSlide(newSlideIndex, inkCanvas.Strokes);
// 获取当前页面索引
var currentSlideIndex = _pptManager?.GetCurrentSlideNumber() ?? 0;
// 如果有当前墨迹且不是第一次切换,先保存到当前页面
if (inkCanvas.Strokes.Count > 0 && currentSlideIndex > 0 && currentSlideIndex != newSlideIndex)
{
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
LogHelper.WriteLogToFile($"切换前保存第{currentSlideIndex}页墨迹,墨迹数量: {inkCanvas.Strokes.Count}", LogHelper.LogType.Trace);
}
// 切换到新页面并加载墨迹
var newStrokes = _multiPPTInkManager?.SwitchToSlide(newSlideIndex, null);
if (newStrokes != null)
{
inkCanvas.Strokes.Clear();
@@ -996,7 +1136,7 @@ namespace Ink_Canvas
}
// 设置墨迹锁定
_pptInkManager?.LockInkForSlide(newSlideIndex);
_multiPPTInkManager?.LockInkForSlide(newSlideIndex);
}
catch (Exception ex)
{
@@ -1065,24 +1205,23 @@ namespace Ink_Canvas
private void ToggleSwitchPowerPointEnhancement_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.PowerPointSettings.EnablePowerPointEnhancement = ToggleSwitchPowerPointEnhancement.IsOn;
// 与WPS支持互斥
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
{
Settings.PowerPointSettings.IsSupportWPS = false;
ToggleSwitchSupportWPS.IsOn = false;
// 更新PPT管理器的WPS支持设置
if (_pptManager != null)
{
_pptManager.IsSupportWPS = false;
}
}
SaveSettingsToFile();
// 启动或停止PowerPoint进程守护
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
{
@@ -1099,8 +1238,7 @@ namespace Ink_Canvas
if (!isLoaded) return;
Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn;
// 与PowerPoint联动增强互斥
if (Settings.PowerPointSettings.IsSupportWPS)
{
Settings.PowerPointSettings.EnablePowerPointEnhancement = false;
@@ -1122,24 +1260,6 @@ namespace Ink_Canvas
public static bool IsShowingRestoreHiddenSlidesWindow;
private static bool IsShowingAutoplaySlidesWindow;
private void BtnPPTSlidesUp_Click(object sender, RoutedEventArgs e)
{
Application.Current.Dispatcher.Invoke(() =>
@@ -1150,7 +1270,7 @@ namespace Ink_Canvas
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
if (currentSlide > 0)
{
_pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
}
// 保存截图(如果启用)
@@ -1165,7 +1285,6 @@ namespace Ink_Canvas
if (_pptManager?.TryNavigatePrevious() == true)
{
// 翻页成功,等待事件处理墨迹切换
LogHelper.WriteLogToFile("成功切换到上一页", LogHelper.LogType.Trace);
}
else
{
@@ -1191,7 +1310,7 @@ namespace Ink_Canvas
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
if (currentSlide > 0)
{
_pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
}
// 保存截图(如果启用)
@@ -1206,7 +1325,6 @@ namespace Ink_Canvas
if (_pptManager?.TryNavigateNext() == true)
{
// 翻页成功,等待事件处理墨迹切换
LogHelper.WriteLogToFile("成功切换到下一页", LogHelper.LogType.Trace);
}
else
{
@@ -1352,7 +1470,7 @@ namespace Ink_Canvas
{
Application.Current.Dispatcher.Invoke(() =>
{
_pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
timeMachine.ClearStrokeHistory();
});
}
@@ -1360,7 +1478,6 @@ namespace Ink_Canvas
// 结束放映
if (_pptManager?.TryEndSlideShow() == true)
{
LogHelper.WriteLogToFile("成功结束幻灯片放映", LogHelper.LogType.Event);
}
else
{
@@ -1376,6 +1493,8 @@ namespace Ink_Canvas
}
HideSubPanels("cursor");
SetCurrentToolMode(InkCanvasEditingMode.None);
await Task.Delay(150);
ViewboxFloatingBarMarginAnimation(100, true);
}
@@ -78,7 +78,7 @@ namespace Ink_Canvas
for (int i = 1; i <= totalSlides; i++)
{
var slideStrokes = _pptInkManager?.LoadSlideStrokes(i);
var slideStrokes = _multiPPTInkManager?.LoadSlideStrokes(i);
if (slideStrokes != null && slideStrokes.Count > 0)
{
allPageStrokes.Add(slideStrokes);
@@ -528,7 +528,7 @@ namespace Ink_Canvas
timeMachine.ClearStrokeHistory();
// 重置PPT墨迹存储
_pptInkManager?.ClearAllStrokes();
_multiPPTInkManager?.ClearAllStrokes();
// 读取所有页面的墨迹文件
var files = Directory.GetFiles(tempDir, "page_*.icstk");
@@ -542,7 +542,7 @@ namespace Ink_Canvas
var strokes = new StrokeCollection(fs);
if (strokes.Count > 0)
{
_pptInkManager?.SaveCurrentSlideStrokes(pageNumber, strokes);
_multiPPTInkManager?.ForceSaveSlideStrokes(pageNumber, strokes);
}
}
}
@@ -552,7 +552,7 @@ namespace Ink_Canvas
if (_pptManager?.IsInSlideShow == true)
{
int currentSlide = _pptManager.GetCurrentSlideNumber();
var currentStrokes = _pptInkManager?.LoadSlideStrokes(currentSlide);
var currentStrokes = _multiPPTInkManager?.LoadSlideStrokes(currentSlide);
if (currentStrokes != null && currentStrokes.Count > 0)
{
inkCanvas.Strokes.Add(currentStrokes);
+426 -29
View File
@@ -1,3 +1,4 @@
using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern.Controls;
using System;
using System.Collections.Generic;
@@ -7,6 +8,7 @@ using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Point = System.Windows.Point;
namespace Ink_Canvas
@@ -254,18 +256,101 @@ namespace Ink_Canvas
#endregion
private bool isGridInkCanvasSelectionCoverMouseDown;
private bool isStrokeDragging = false;
private Point strokeDragStartPoint;
private StrokeCollection StrokesSelectionClone = new StrokeCollection();
// 选择框和选择点相关变量
private bool isResizing = false;
private string currentResizeHandle = "";
private Point resizeStartPoint;
private Rect originalSelectionBounds;
private void GridInkCanvasSelectionCover_MouseDown(object sender, MouseButtonEventArgs e)
{
isGridInkCanvasSelectionCoverMouseDown = true;
// 检查是否有选中的墨迹
if (inkCanvas.GetSelectedStrokes().Count > 0)
{
// 获取鼠标点击位置
var clickPoint = e.GetPosition(inkCanvas);
var selectionBounds = inkCanvas.GetSelectionBounds();
// 检查点击位置是否在选择框边界内
if (clickPoint.X >= selectionBounds.Left &&
clickPoint.X <= selectionBounds.Right &&
clickPoint.Y >= selectionBounds.Top &&
clickPoint.Y <= selectionBounds.Bottom)
{
// 只有在选择框边界内才允许拖动
isStrokeDragging = true;
strokeDragStartPoint = clickPoint;
GridInkCanvasSelectionCover.CaptureMouse();
GridInkCanvasSelectionCover.Cursor = Cursors.SizeAll;
}
else
{
// 点击在选择框外,取消选择
inkCanvas.Select(new StrokeCollection());
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
}
}
}
private void GridInkCanvasSelectionCover_MouseMove(object sender, MouseEventArgs e)
{
if (!isGridInkCanvasSelectionCoverMouseDown) return;
// 如果正在拖动墨迹,执行拖动操作
if (isStrokeDragging && GridInkCanvasSelectionCover.IsMouseCaptured)
{
var currentPoint = e.GetPosition(inkCanvas);
var delta = currentPoint - strokeDragStartPoint;
// 创建变换矩阵
var matrix = new Matrix();
matrix.Translate(delta.X, delta.Y);
// 对选中的墨迹应用变换
var selectedStrokes = inkCanvas.GetSelectedStrokes();
foreach (var stroke in selectedStrokes)
{
stroke.Transform(matrix, false);
}
// 更新选中栏位置
updateBorderStrokeSelectionControlLocation();
// 更新起始点
strokeDragStartPoint = currentPoint;
}
else if (inkCanvas.GetSelectedStrokes().Count > 0)
{
// 当鼠标在选中区域移动时,更新墨迹选中栏位置
updateBorderStrokeSelectionControlLocation();
}
}
private void GridInkCanvasSelectionCover_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!isGridInkCanvasSelectionCoverMouseDown) return;
// 结束墨迹拖动
if (isStrokeDragging)
{
isStrokeDragging = false;
GridInkCanvasSelectionCover.ReleaseMouseCapture();
GridInkCanvasSelectionCover.Cursor = Cursors.Arrow;
}
isGridInkCanvasSelectionCoverMouseDown = false;
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
// 只有在没有选中墨迹时才隐藏选中栏
if (inkCanvas.GetSelectedStrokes().Count == 0)
{
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
}
}
private void BtnSelect_Click(object sender, RoutedEventArgs e)
@@ -306,7 +391,30 @@ namespace Ink_Canvas
{
if (isProgramChangeStrokeSelection) return;
// 检查是否有图片元素被选中
// 优先检查墨迹选择状态
if (inkCanvas.GetSelectedStrokes().Count > 0)
{
// 有墨迹被选中,清除图片选择状态
if (currentSelectedElement != null)
{
currentSelectedElement = null;
// 隐藏图片选择工具栏
if (BorderImageSelectionControl != null)
{
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
}
}
// 显示墨迹选择栏和选择框
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
BorderStrokeSelectionClone.Background = Brushes.Transparent;
isStrokeSelectionCloneOn = false;
updateBorderStrokeSelectionControlLocation();
UpdateSelectionDisplay();
return;
}
// 检查是否有图片元素被选中(通过InkCanvas的选中元素)
var selectedElements = inkCanvas.GetSelectedElements();
bool hasImageElement = selectedElements.Any(element => element is Image);
@@ -314,20 +422,21 @@ namespace Ink_Canvas
if (hasImageElement)
{
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
HideSelectionDisplay();
return;
}
if (inkCanvas.GetSelectedStrokes().Count == 0)
// 检查是否有图片元素被选中(通过currentSelectedElement
if (currentSelectedElement != null && currentSelectedElement is Image)
{
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
HideSelectionDisplay();
return;
}
else
{
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
BorderStrokeSelectionClone.Background = Brushes.Transparent;
isStrokeSelectionCloneOn = false;
updateBorderStrokeSelectionControlLocation();
}
// 没有选中任何内容,隐藏选择框
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
HideSelectionDisplay();
}
@@ -336,7 +445,7 @@ namespace Ink_Canvas
{
var borderLeft = (inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Right -
BorderStrokeSelectionControlWidth) / 2;
var borderTop = inkCanvas.GetSelectionBounds().Bottom + 1;
var borderTop = inkCanvas.GetSelectionBounds().Bottom + 10; // 在墨迹下方10像素处显示
if (borderLeft < 0) borderLeft = 0;
if (borderTop < 0) borderTop = 0;
if (Width - borderLeft < BorderStrokeSelectionControlWidth || double.IsNaN(borderLeft))
@@ -344,7 +453,14 @@ namespace Ink_Canvas
if (Height - borderTop < BorderStrokeSelectionControlHeight || double.IsNaN(borderTop))
borderTop = Height - BorderStrokeSelectionControlHeight;
if (borderTop > 60) borderTop -= 60;
// 确保墨迹选中栏始终显示在墨迹下方
// 如果选中栏会超出屏幕底部,则显示在墨迹上方
if (borderTop + BorderStrokeSelectionControlHeight > Height)
{
borderTop = inkCanvas.GetSelectionBounds().Top - BorderStrokeSelectionControlHeight - 10;
if (borderTop < 0) borderTop = 10; // 如果上方也没有空间,则显示在顶部
}
BorderStrokeSelectionControl.Margin = new Thickness(borderLeft, borderTop, 0, 0);
}
@@ -408,25 +524,19 @@ namespace Ink_Canvas
strokes = StrokesSelectionClone;
else if (Settings.Gesture.IsEnableTwoFingerRotationOnSelection)
m.RotateAt(rotate, center.X, center.Y); // 旋转
// 应用变换到选中的墨迹
foreach (var stroke in strokes)
{
stroke.Transform(m, false);
try
{
stroke.DrawingAttributes.Width *= md.Scale.X;
stroke.DrawingAttributes.Height *= md.Scale.Y;
}
catch
{
}
}
updateBorderStrokeSelectionControlLocation();
}
}
catch
catch (Exception ex)
{
LogHelper.WriteLogToFile($"墨迹ManipulationDelta错误: {ex.Message}", LogHelper.LogType.Error);
}
}
@@ -438,6 +548,78 @@ namespace Ink_Canvas
{
}
private void GridInkCanvasSelectionCover_TouchMove(object sender, TouchEventArgs e)
{
// 处理触摸移动事件 - 用于拖动选中的墨迹
if (inkCanvas.GetSelectedStrokes().Count > 0 && dec.Count == 1)
{
var currentTouchPoint = e.GetTouchPoint(inkCanvas).Position;
// 检查是否有有效的起始触摸点
if (lastTouchPointOnGridInkCanvasCover != new Point(0, 0))
{
var delta = currentTouchPoint - lastTouchPointOnGridInkCanvasCover;
// 只有当移动距离足够大时才进行拖动(避免微小移动造成的抖动)
if (Math.Abs(delta.X) > 1 || Math.Abs(delta.Y) > 1)
{
// 创建变换矩阵
var matrix = new Matrix();
matrix.Translate(delta.X, delta.Y);
// 对选中的墨迹应用变换
var selectedStrokes = inkCanvas.GetSelectedStrokes();
foreach (var stroke in selectedStrokes)
{
stroke.Transform(matrix, false);
}
// 更新选中栏位置
updateBorderStrokeSelectionControlLocation();
// 更新最后触摸点
lastTouchPointOnGridInkCanvasCover = currentTouchPoint;
}
}
}
}
private void GridInkCanvasSelectionCover_PreviewTouchMove(object sender, TouchEventArgs e)
{
// 预览触摸移动事件 - 用于更精确的触摸处理
if (inkCanvas.GetSelectedStrokes().Count > 0 && dec.Count == 1)
{
var currentTouchPoint = e.GetTouchPoint(inkCanvas).Position;
// 检查是否有有效的起始触摸点
if (lastTouchPointOnGridInkCanvasCover != new Point(0, 0))
{
var delta = currentTouchPoint - lastTouchPointOnGridInkCanvasCover;
// 只有当移动距离足够大时才进行拖动(避免微小移动造成的抖动)
if (Math.Abs(delta.X) > 1 || Math.Abs(delta.Y) > 1)
{
// 创建变换矩阵
var matrix = new Matrix();
matrix.Translate(delta.X, delta.Y);
// 对选中的墨迹应用变换
var selectedStrokes = inkCanvas.GetSelectedStrokes();
foreach (var stroke in selectedStrokes)
{
stroke.Transform(matrix, false);
}
// 更新选中栏位置
updateBorderStrokeSelectionControlLocation();
// 更新最后触摸点
lastTouchPointOnGridInkCanvasCover = currentTouchPoint;
}
}
}
}
private Point lastTouchPointOnGridInkCanvasCover = new Point(0, 0);
private void GridInkCanvasSelectionCover_PreviewTouchDown(object sender, TouchEventArgs e)
@@ -448,7 +630,32 @@ namespace Ink_Canvas
{
var touchPoint = e.GetTouchPoint(null);
centerPoint = touchPoint.Position;
lastTouchPointOnGridInkCanvasCover = touchPoint.Position;
lastTouchPointOnGridInkCanvasCover = e.GetTouchPoint(inkCanvas).Position;
// 检查是否有选中的墨迹
if (inkCanvas.GetSelectedStrokes().Count > 0)
{
// 获取触摸点位置
var touchPosition = e.GetTouchPoint(inkCanvas).Position;
var selectionBounds = inkCanvas.GetSelectionBounds();
// 检查触摸位置是否在选择框边界内
if (touchPosition.X >= selectionBounds.Left &&
touchPosition.X <= selectionBounds.Right &&
touchPosition.Y >= selectionBounds.Top &&
touchPosition.Y <= selectionBounds.Bottom)
{
// 只有在选择框边界内才允许拖动
// 触摸拖动状态已通过TouchMove事件处理
}
else
{
// 触摸在选择框外,取消选择
inkCanvas.Select(new StrokeCollection());
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
return;
}
}
if (isStrokeSelectionCloneOn)
{
@@ -474,13 +681,32 @@ namespace Ink_Canvas
{
dec.Remove(e.TouchDevice.Id);
if (dec.Count >= 1) return;
// 重置触摸状态
lastTouchPointOnGridInkCanvasCover = new Point(0, 0);
isProgramChangeStrokeSelection = false;
if (lastTouchPointOnGridInkCanvasCover == e.GetTouchPoint(null).Position)
// 检查是否有点击(没有移动)
var currentTouchPoint = e.GetTouchPoint(null).Position;
if (Math.Abs(currentTouchPoint.X - centerPoint.X) < 5 && Math.Abs(currentTouchPoint.Y - centerPoint.Y) < 5)
{
if (!(lastTouchPointOnGridInkCanvasCover.X < inkCanvas.GetSelectionBounds().Left) &&
!(lastTouchPointOnGridInkCanvasCover.Y < inkCanvas.GetSelectionBounds().Top) &&
!(lastTouchPointOnGridInkCanvasCover.X > inkCanvas.GetSelectionBounds().Right) &&
!(lastTouchPointOnGridInkCanvasCover.Y > inkCanvas.GetSelectionBounds().Bottom)) return;
// 点击在选择框内,保持选择状态
if (inkCanvas.GetSelectedStrokes().Count > 0)
{
var selectionBounds = inkCanvas.GetSelectionBounds();
if (currentTouchPoint.X >= selectionBounds.Left &&
currentTouchPoint.X <= selectionBounds.Right &&
currentTouchPoint.Y >= selectionBounds.Top &&
currentTouchPoint.Y <= selectionBounds.Bottom)
{
// 点击在选择框内,保持选择
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
StrokesSelectionClone = new StrokeCollection();
return;
}
}
// 点击在选择框外,取消选择
inkCanvas.Select(new StrokeCollection());
StrokesSelectionClone = new StrokeCollection();
}
@@ -494,6 +720,7 @@ namespace Ink_Canvas
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
StrokesSelectionClone = new StrokeCollection();
}
}
private void LassoSelect_Click(object sender, RoutedEventArgs e)
@@ -558,7 +785,177 @@ namespace Ink_Canvas
return new Rect(0, 0, 0, 0);
}
#endregion
#region Selection Display and Resize Handles
private void UpdateSelectionDisplay()
{
if (inkCanvas.GetSelectedStrokes().Count == 0)
{
HideSelectionDisplay();
return;
}
var selectionBounds = inkCanvas.GetSelectionBounds();
// 更新选择框
SelectionRectangle.Visibility = Visibility.Visible;
SelectionRectangle.Margin = new Thickness(selectionBounds.Left, selectionBounds.Top, 0, 0);
SelectionRectangle.Width = selectionBounds.Width;
SelectionRectangle.Height = selectionBounds.Height;
// 更新选择点位置
UpdateSelectionHandles(selectionBounds);
SelectionHandlesCanvas.Visibility = Visibility.Visible;
}
private void HideSelectionDisplay()
{
SelectionRectangle.Visibility = Visibility.Collapsed;
SelectionHandlesCanvas.Visibility = Visibility.Collapsed;
}
private void UpdateSelectionHandles(Rect bounds)
{
// 四个角选择点
TopLeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Top - 4, 0, 0);
TopRightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Top - 4, 0, 0);
BottomLeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Bottom - 4, 0, 0);
BottomRightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Bottom - 4, 0, 0);
// 四个边选择点
TopHandle.Margin = new Thickness(bounds.Left + bounds.Width / 2 - 4, bounds.Top - 4, 0, 0);
BottomHandle.Margin = new Thickness(bounds.Left + bounds.Width / 2 - 4, bounds.Bottom - 4, 0, 0);
LeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Top + bounds.Height / 2 - 4, 0, 0);
RightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Top + bounds.Height / 2 - 4, 0, 0);
}
private void SelectionHandle_MouseDown(object sender, MouseButtonEventArgs e)
{
if (sender is Rectangle handle)
{
isResizing = true;
currentResizeHandle = handle.Name;
resizeStartPoint = e.GetPosition(inkCanvas);
originalSelectionBounds = inkCanvas.GetSelectionBounds();
handle.CaptureMouse();
e.Handled = true;
}
}
private void SelectionHandle_MouseMove(object sender, MouseEventArgs e)
{
if (!isResizing || !(sender is Rectangle handle)) return;
var currentPoint = e.GetPosition(inkCanvas);
var delta = new Point(currentPoint.X - resizeStartPoint.X, currentPoint.Y - resizeStartPoint.Y);
var newBounds = CalculateNewBounds(originalSelectionBounds, delta, currentResizeHandle);
// 应用新的边界到选中的墨迹
ApplyBoundsToStrokes(newBounds);
// 更新选择框显示
UpdateSelectionDisplay();
}
private void SelectionHandle_MouseUp(object sender, MouseButtonEventArgs e)
{
if (sender is Rectangle handle)
{
isResizing = false;
currentResizeHandle = "";
handle.ReleaseMouseCapture();
e.Handled = true;
}
}
private Rect CalculateNewBounds(Rect originalBounds, Point delta, string handleName)
{
var newBounds = originalBounds;
double newWidth = originalBounds.Width;
double newHeight = originalBounds.Height;
double newX = originalBounds.X;
double newY = originalBounds.Y;
switch (handleName)
{
case "TopLeftHandle":
newX = originalBounds.X + delta.X;
newY = originalBounds.Y + delta.Y;
newWidth = originalBounds.Width - delta.X;
newHeight = originalBounds.Height - delta.Y;
break;
case "TopRightHandle":
newY = originalBounds.Y + delta.Y;
newWidth = originalBounds.Width + delta.X;
newHeight = originalBounds.Height - delta.Y;
break;
case "BottomLeftHandle":
newX = originalBounds.X + delta.X;
newWidth = originalBounds.Width - delta.X;
newHeight = originalBounds.Height + delta.Y;
break;
case "BottomRightHandle":
newWidth = originalBounds.Width + delta.X;
newHeight = originalBounds.Height + delta.Y;
break;
case "TopHandle":
newY = originalBounds.Y + delta.Y;
newHeight = originalBounds.Height - delta.Y;
break;
case "BottomHandle":
newHeight = originalBounds.Height + delta.Y;
break;
case "LeftHandle":
newX = originalBounds.X + delta.X;
newWidth = originalBounds.Width - delta.X;
break;
case "RightHandle":
newWidth = originalBounds.Width + delta.X;
break;
}
// 确保最小尺寸和正值
if (newWidth < 10) newWidth = 10;
if (newHeight < 10) newHeight = 10;
// 创建新的Rect,确保所有值都是有效的
newBounds = new Rect(newX, newY, newWidth, newHeight);
return newBounds;
}
private void ApplyBoundsToStrokes(Rect newBounds)
{
var selectedStrokes = inkCanvas.GetSelectedStrokes();
if (selectedStrokes.Count == 0) return;
var originalBounds = inkCanvas.GetSelectionBounds();
// 计算缩放比例
var scaleX = newBounds.Width / originalBounds.Width;
var scaleY = newBounds.Height / originalBounds.Height;
// 计算平移量
var translateX = newBounds.X - originalBounds.X;
var translateY = newBounds.Y - originalBounds.Y;
// 创建变换矩阵
var matrix = new Matrix();
matrix.Translate(translateX, translateY);
matrix.ScaleAt(scaleX, scaleY, originalBounds.X + originalBounds.Width / 2, originalBounds.Y + originalBounds.Height / 2);
// 应用变换到选中的墨迹
foreach (var stroke in selectedStrokes)
{
stroke.Transform(matrix, false);
}
}
#endregion
}
}
#endregion
+36 -8
View File
@@ -18,7 +18,7 @@ using Application = System.Windows.Application;
using CheckBox = System.Windows.Controls.CheckBox;
using ComboBox = System.Windows.Controls.ComboBox;
using File = System.IO.File;
using MessageBox = System.Windows.MessageBox;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using OperatingSystem = OSVersionExtension.OperatingSystem;
using RadioButton = System.Windows.Controls.RadioButton;
@@ -197,23 +197,23 @@ namespace Ink_Canvas
val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1;
ViewboxFloatingBarScaleTransform.ScaleY =
val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1;
// 等待UI更新后再重新计算浮动栏位置,确保居中计算准确
Dispatcher.BeginInvoke(new Action(async () =>
{
// 强制更新布局以确保ActualWidth正确
ViewboxFloatingBar.UpdateLayout();
// 等待一小段时间让布局完全更新
await Task.Delay(100);
// 再次强制更新布局
ViewboxFloatingBar.UpdateLayout();
// 强制重新测量和排列
ViewboxFloatingBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
ViewboxFloatingBar.Arrange(new Rect(ViewboxFloatingBar.DesiredSize));
// auto align - 新增:只在屏幕模式下重新计算浮动栏位置
if (currentMode == 0)
{
@@ -1814,7 +1814,11 @@ namespace Ink_Canvas
// 先设为None再设回原来的模式,避免可能的事件冲突
inkCanvas.EditingMode = InkCanvasEditingMode.None;
// 保存非笔画元素(如图片)
var preservedElements = PreserveNonStrokeElements();
inkCanvas.Children.Clear();
// 恢复非笔画元素
RestoreNonStrokeElements(preservedElements);
isInMultiTouchMode = true;
// 恢复到之前的编辑状态
@@ -2412,12 +2416,23 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchDirectCallCiRand_Toggled(object sender, RoutedEventArgs e)
private void ToggleSwitchExternalCaller_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
// 获取开关状态并保存到设置中
Settings.RandSettings.DirectCallCiRand = ToggleSwitchDirectCallCiRand.IsOn;
Settings.RandSettings.DirectCallCiRand = ToggleSwitchExternalCaller.IsOn;
// 保存设置到文件
SaveSettingsToFile();
}
private void ComboBoxExternalCallerType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!isLoaded) return;
// 获取下拉框选择并保存到设置中
Settings.RandSettings.ExternalCallerType = ComboBoxExternalCallerType.SelectedIndex;
// 保存设置到文件
SaveSettingsToFile();
@@ -2747,6 +2762,12 @@ namespace Ink_Canvas
var text = JsonConvert.SerializeObject(Settings, Formatting.Indented);
try
{
string configsDir = Path.Combine(App.RootPath, "Configs");
if (!Directory.Exists(configsDir))
{
Directory.CreateDirectory(configsDir);
}
File.WriteAllText(App.RootPath + settingsFileName, text);
}
catch { }
@@ -2961,6 +2982,13 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchKeepFoldAfterSoftwareExit_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Automation.KeepFoldAfterSoftwareExit = ToggleSwitchKeepFoldAfterSoftwareExit.IsOn;
SaveSettingsToFile();
}
private void ToggleSwitchAlwaysGoToFirstPageOnReenter_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
+15 -3
View File
@@ -256,7 +256,7 @@ namespace Ink_Canvas
_taskbar.Visibility = Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed;
ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityValue;
// 初始化浮动栏透明度滑块值
ViewboxFloatingBarOpacityValueSlider.Value = Settings.Appearance.ViewboxFloatingBarOpacityValue;
ViewboxFloatingBarOpacityInPPTValueSlider.Value = Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue;
@@ -763,7 +763,8 @@ namespace Ink_Canvas
RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency;
RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents;
ToggleSwitchShowRandomAndSingleDraw.IsOn = Settings.RandSettings.ShowRandomAndSingleDraw;
ToggleSwitchDirectCallCiRand.IsOn = Settings.RandSettings.DirectCallCiRand;
ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand;
ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType;
RandomDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed;
SingleDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed;
@@ -783,13 +784,22 @@ namespace Ink_Canvas
ToggleSwitchDisplayRandWindowNamesInputBtn.IsOn = Settings.RandSettings.DisplayRandWindowNamesInputBtn;
RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency;
RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents;
ToggleSwitchDirectCallCiRand.IsOn = Settings.RandSettings.DirectCallCiRand;
ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand;
ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType;
}
// ModeSettings
if (Settings.ModeSettings != null)
{
ToggleSwitchMode.IsOn = Settings.ModeSettings.IsPPTOnlyMode;
// 根据加载的配置状态执行相应的窗口显示/隐藏逻辑
if (isStartup && Settings.ModeSettings.IsPPTOnlyMode)
{
// 启动时如果是仅PPT模式,隐藏主窗口
Hide();
LogHelper.WriteLogToFile("启动时检测到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event);
}
}
else
{
@@ -848,6 +858,8 @@ namespace Ink_Canvas
ToggleSwitchAutoFoldAfterPPTSlideShow.IsOn = Settings.Automation.IsAutoFoldAfterPPTSlideShow;
ToggleSwitchKeepFoldAfterSoftwareExit.IsOn = Settings.Automation.KeepFoldAfterSoftwareExit;
if (Settings.Automation.IsAutoKillEasiNote || Settings.Automation.IsAutoKillPptService ||
Settings.Automation.IsAutoKillHiteAnnotation || Settings.Automation.IsAutoKillInkCanvas
|| Settings.Automation.IsAutoKillICA || Settings.Automation.IsAutoKillIDT ||
+10 -3
View File
@@ -10,7 +10,7 @@ using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using MessageBox = System.Windows.MessageBox;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
using Point = System.Windows.Point;
namespace Ink_Canvas
@@ -107,10 +107,10 @@ namespace Ink_Canvas
else if (sender == ImageDrawArrow || sender == BoardImageDrawArrow)
drawingShapeMode = 2;
else if (sender == ImageDrawParallelLine || sender == BoardImageDrawParallelLine) drawingShapeMode = 15;
// 更新模式缓存
UpdateCurrentToolMode("shape");
isLongPressSelected = true;
if (isSingleFingerDragMode) BtnFingerDragMode_Click(BtnFingerDragMode, null);
}
@@ -118,6 +118,13 @@ namespace Ink_Canvas
private void BtnPen_Click(object sender, RoutedEventArgs e)
{
// 如果当前有选中的图片元素,先取消选中
if (currentSelectedElement != null)
{
UnselectElement(currentSelectedElement);
currentSelectedElement = null;
}
// 禁用高级橡皮擦系统
DisableAdvancedEraserSystem();
+120 -23
View File
@@ -65,8 +65,11 @@ namespace Ink_Canvas
private Timer timerDisplayTime = new Timer();
private Timer timerDisplayDate = new Timer();
private Timer timerNtpSync = new Timer();
private TimeViewModel nowTimeVM = new TimeViewModel();
private DateTime cachedNetworkTime = DateTime.Now;
private DateTime lastNtpSyncTime = DateTime.MinValue;
private async Task<DateTime> GetNetworkTimeAsync()
{
@@ -117,35 +120,61 @@ namespace Ink_Canvas
timerDisplayDate.Elapsed += TimerDisplayDate_Elapsed;
timerDisplayDate.Interval = 1000 * 60 * 60 * 1;
timerDisplayDate.Start();
timerNtpSync.Elapsed += async (s, e) => await TimerNtpSync_ElapsedAsync();
timerNtpSync.Interval = 1000 * 60 * 60 * 2; // 每2小时同步一次
timerNtpSync.Start();
timerKillProcess.Start();
nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd");
nowTimeVM.nowTime = DateTime.Now.ToString("tt hh'时'mm'分'ss'秒'");
}
// 修改TimerDisplayTime_ElapsedAsync方法中的时间格式,实现校验制
// NTP同步定时器事件处理
private async Task TimerNtpSync_ElapsedAsync()
{
try
{
DateTime networkTime = await GetNetworkTimeAsync();
cachedNetworkTime = networkTime;
lastNtpSyncTime = DateTime.Now;
}
catch
{
// NTP同步失败时,保持使用本地时间
cachedNetworkTime = DateTime.Now;
}
}
// 修改TimerDisplayTime_ElapsedAsync方法,使用缓存的网络时间
private async Task TimerDisplayTime_ElapsedAsync()
{
DateTime localTime = DateTime.Now;
DateTime displayTime = localTime; // 默认使用本地时间
try
// 如果还没有进行过NTP同步,或者距离上次同步超过2小时,则进行一次同步
if (lastNtpSyncTime == DateTime.MinValue ||
(DateTime.Now - lastNtpSyncTime).TotalHours >= 2)
{
DateTime networkTime = await GetNetworkTimeAsync();
// 计算时间差
TimeSpan timeDifference = networkTime - localTime;
double timeDifferenceMinutes = Math.Abs(timeDifference.TotalMinutes);
// 如果网络时间与本地时间相差不超过1分钟,则使用本地时间
// 否则使用网络时间
displayTime = timeDifferenceMinutes <= 1.0 ? localTime : networkTime;
try
{
DateTime networkTime = await GetNetworkTimeAsync();
cachedNetworkTime = networkTime;
lastNtpSyncTime = DateTime.Now;
}
catch
{
// 网络时间获取失败时,使用本地时间
cachedNetworkTime = localTime;
}
}
catch
{
// 网络时间获取失败时,使用本地时间
displayTime = localTime;
}
// 使用缓存的网络时间进行显示
TimeSpan timeDifference = cachedNetworkTime - localTime;
double timeDifferenceMinutes = Math.Abs(timeDifference.TotalMinutes);
// 如果网络时间与本地时间相差不超过3分钟,则使用本地时间
// 否则使用网络时间
displayTime = timeDifferenceMinutes <= 3.0 ? localTime : cachedNetworkTime;
// 只更新时间,日期由原有逻辑定时更新即可
Dispatcher.Invoke(() =>
{
@@ -240,8 +269,18 @@ namespace Ink_Canvas
ShowNotification("“鸿合屏幕书写”已自动关闭");
if (Settings.Automation.IsAutoKillHiteAnnotation && Settings.Automation.IsAutoEnterAnnotationAfterKillHite)
{
// 自动进入批注状态
PenIcon_Click(null, null);
// 检查是否处于收纳状态,如果是则先展开浮动栏
if (isFloatingBarFolded)
{
// 先展开浮动栏,然后进入批注状态
// UnFoldFloatingBar 方法内部会根据设置自动进入批注模式
UnFoldFloatingBar(null);
}
else
{
// 如果已经展开,直接进入批注状态
PenIcon_Click(null, null);
}
}
});
}
@@ -302,6 +341,44 @@ namespace Ink_Canvas
{
var windowTitle = ForegroundWindowInfo.WindowTitle();
var windowRect = ForegroundWindowInfo.WindowRect();
var windowProcessName = ForegroundWindowInfo.ProcessName();
// 检测希沃白板五的批注面板
// 希沃白板五的批注面板通常具有以下特征:
// 1. 窗口标题为空或包含特定关键词
// 2. 窗口高度较小(批注工具栏)
// 3. 窗口宽度适中(工具栏宽度)
if (windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher")
{
// 检测希沃白板五的批注工具栏
// 批注工具栏通常高度在50-200像素之间,宽度在200-800像素之间
if (windowRect.Height >= 50 && windowRect.Height <= 200 &&
windowRect.Width >= 200 && windowRect.Width <= 800)
{
return true;
}
// 检测希沃白板五的二级菜单面板
// 二级菜单面板通常高度在100-400像素之间,宽度在150-400像素之间
if (windowRect.Height >= 100 && windowRect.Height <= 400 &&
windowRect.Width >= 150 && windowRect.Width <= 400)
{
return true;
}
}
// 检测鸿合软件的批注面板
if (windowProcessName == "HiteCamera" || windowProcessName == "HiteTouchPro" || windowProcessName == "HiteLightBoard")
{
// 鸿合软件的批注面板特征
if (windowRect.Height >= 50 && windowRect.Height <= 300 &&
windowRect.Width >= 200 && windowRect.Width <= 600)
{
return true;
}
}
// 原有的检测逻辑(保持向后兼容)
return windowTitle.Length == 0 && windowRect.Height < 500;
}
@@ -369,7 +446,17 @@ namespace Ink_Canvas
}
else if (Settings.Automation.IsAutoFoldInSeewoPincoTeacher && (windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher"))
{
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
// 检测到希沃白板五的批注窗口时保持收纳状态
if (IsAnnotationWindow())
{
// 批注窗口打开时,如果当前是展开状态则收纳
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
}
else
{
// 非批注窗口时正常处理
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
}
// HiteCamera
}
else if (Settings.Automation.IsAutoFoldInHiteCamera && windowProcessName == "HiteCamera" &&
@@ -491,8 +578,18 @@ namespace Ink_Canvas
}
else
{
if (isFloatingBarFolded && !foldFloatingBarByUser) UnFoldFloatingBar_MouseUp(new object(), null);
unfoldFloatingBarByUser = false;
// 检查是否启用了软件退出后保持收纳模式
if (Settings.Automation.KeepFoldAfterSoftwareExit)
{
// 如果启用了保持收纳模式,则不自动展开浮动栏
unfoldFloatingBarByUser = false;
}
else
{
// 原有的逻辑:软件退出后自动展开浮动栏
if (isFloatingBarFolded && !foldFloatingBarByUser) UnFoldFloatingBar_MouseUp(new object(), null);
unfoldFloatingBarByUser = false;
}
}
}
catch { }
+238 -27
View File
@@ -8,6 +8,7 @@ using System.Windows.Controls;
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;
@@ -30,7 +31,7 @@ namespace Ink_Canvas
{
var preservedElements = new List<UIElement>();
// 遍历inkCanvas的所有子元素
// 遍历inkCanvas的所有子元素,创建副本而不是直接引用
for (int i = inkCanvas.Children.Count - 1; i >= 0; i--)
{
var child = inkCanvas.Children[i];
@@ -39,13 +40,118 @@ namespace Ink_Canvas
if (child is Image || child is MediaElement ||
(child is Border border && border.Name != "AdvancedEraserOverlay"))
{
preservedElements.Add(child);
// 创建元素的深拷贝,避免直接引用导致的问题
var clonedElement = CloneUIElement(child);
if (clonedElement != null)
{
preservedElements.Add(clonedElement);
}
}
}
return preservedElements;
}
/// <summary>
/// 克隆UI元素,创建深拷贝
/// </summary>
private UIElement CloneUIElement(UIElement originalElement)
{
try
{
if (originalElement is Image originalImage)
{
var clonedImage = new Image();
// 复制图片源
if (originalImage.Source is BitmapSource bitmapSource)
{
clonedImage.Source = bitmapSource;
}
// 复制属性
clonedImage.Width = originalImage.Width;
clonedImage.Height = originalImage.Height;
clonedImage.Stretch = originalImage.Stretch;
clonedImage.StretchDirection = originalImage.StretchDirection;
clonedImage.Name = originalImage.Name;
clonedImage.IsHitTestVisible = originalImage.IsHitTestVisible;
clonedImage.Focusable = originalImage.Focusable;
clonedImage.Cursor = originalImage.Cursor;
clonedImage.IsManipulationEnabled = originalImage.IsManipulationEnabled;
// 复制位置
InkCanvas.SetLeft(clonedImage, InkCanvas.GetLeft(originalImage));
InkCanvas.SetTop(clonedImage, InkCanvas.GetTop(originalImage));
// 复制变换
if (originalImage.RenderTransform != null)
{
clonedImage.RenderTransform = originalImage.RenderTransform.Clone();
}
return clonedImage;
}
else if (originalElement is MediaElement originalMedia)
{
var clonedMedia = new MediaElement();
// 复制媒体属性
clonedMedia.Source = originalMedia.Source;
clonedMedia.Width = originalMedia.Width;
clonedMedia.Height = originalMedia.Height;
clonedMedia.Name = originalMedia.Name;
clonedMedia.IsHitTestVisible = originalMedia.IsHitTestVisible;
clonedMedia.Focusable = originalMedia.Focusable;
// 复制位置
InkCanvas.SetLeft(clonedMedia, InkCanvas.GetLeft(originalMedia));
InkCanvas.SetTop(clonedMedia, InkCanvas.GetTop(originalMedia));
// 复制变换
if (originalMedia.RenderTransform != null)
{
clonedMedia.RenderTransform = originalMedia.RenderTransform.Clone();
}
return clonedMedia;
}
else if (originalElement is Border originalBorder)
{
var clonedBorder = new Border();
// 复制边框属性
clonedBorder.Width = originalBorder.Width;
clonedBorder.Height = originalBorder.Height;
clonedBorder.Name = originalBorder.Name;
clonedBorder.IsHitTestVisible = originalBorder.IsHitTestVisible;
clonedBorder.Focusable = originalBorder.Focusable;
clonedBorder.Background = originalBorder.Background;
clonedBorder.BorderBrush = originalBorder.BorderBrush;
clonedBorder.BorderThickness = originalBorder.BorderThickness;
clonedBorder.CornerRadius = originalBorder.CornerRadius;
// 复制位置
InkCanvas.SetLeft(clonedBorder, InkCanvas.GetLeft(originalBorder));
InkCanvas.SetTop(clonedBorder, InkCanvas.GetTop(originalBorder));
// 复制变换
if (originalBorder.RenderTransform != null)
{
clonedBorder.RenderTransform = originalBorder.RenderTransform.Clone();
}
return clonedBorder;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"克隆UI元素失败: {ex.Message}", LogHelper.LogType.Error);
}
return null;
}
/// <summary>
/// 恢复之前保存的非笔画元素到画布
/// </summary>
@@ -55,11 +161,15 @@ namespace Ink_Canvas
foreach (var element in preservedElements)
{
// 确保元素没有父容器再添加到inkCanvas
if (element is FrameworkElement fe && fe.Parent == null)
try
{
// 由于现在使用的是克隆的元素,不需要检查Parent属性
inkCanvas.Children.Add(element);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"恢复非笔画元素失败: {ex.Message}", LogHelper.LogType.Error);
}
}
}
@@ -163,13 +273,11 @@ namespace Ink_Canvas
return;
}
LogHelper.WriteLogToFile($"MainWindow_StylusDown 被调用,笔尾状态: {e.StylusDevice.Inverted}, 当前 drawingShapeMode: {drawingShapeMode}, 当前 EditingMode: {inkCanvas.EditingMode}");
// 新增:根据是否为笔尾自动切换橡皮擦/画笔模式
if (e.StylusDevice.Inverted)
{
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
LogHelper.WriteLogToFile("检测到笔尾,设置 EditingMode 为 EraseByPoint");
}
else
{
@@ -178,14 +286,12 @@ namespace Ink_Canvas
{
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
inkCanvas.EditingMode = InkCanvasEditingMode.None;
LogHelper.WriteLogToFile("几何绘制模式,设置 EditingMode 为 None");
return;
}
// 修复:保持当前的线擦模式,不要强制切换到Ink模式
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
{
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
LogHelper.WriteLogToFile("设置 EditingMode 为 Ink");
}
else
{
@@ -231,17 +337,11 @@ namespace Ink_Canvas
{
try
{
LogHelper.WriteLogToFile($"MainWindow_StylusUp 被调用,EditingMode: {inkCanvas.EditingMode}, EnableInkFade: {Settings.Canvas.EnableInkFade}");
var stroke = GetStrokeVisual(e.StylusDevice.Id).Stroke;
LogHelper.WriteLogToFile($"获取到墨迹,StylusPoints数量: {stroke.StylusPoints.Count}");
// 正常模式:添加到画布并参与墨迹纠正
// 墨迹渐隐功能现在在 StrokeCollected 事件中统一处理所有输入方式
LogHelper.WriteLogToFile("StylusUp: 添加墨迹到画布");
inkCanvas.Strokes.Add(stroke);
await Task.Delay(5); // 避免渲染墨迹完成前预览墨迹被删除导致墨迹闪烁
await Task.Delay(5);
inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id));
inkCanvas_StrokeCollected(inkCanvas,
@@ -369,7 +469,8 @@ namespace Ink_Canvas
}
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
{
// 套索选状态下return保证套索选可用
// 套索选状态下不直接return允许触摸事件继续处理
dec.Add(e.TouchDevice.Id);
return;
}
// 修复:几何绘制模式下完全禁止触摸轨迹收集
@@ -502,7 +603,7 @@ namespace Ink_Canvas
// 计算触摸面积(使用设备提供的Size)
double touchArea = size.Width * size.Height;
// 计算宽高比(使用Bounds确保准确性)
double aspectRatio = Math.Min(bounds.Width, bounds.Height) / Math.Max(bounds.Width, bounds.Height);
@@ -510,7 +611,7 @@ namespace Ink_Canvas
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; // 避免过于细长的触摸(可能是手指)
@@ -613,7 +714,7 @@ namespace Ink_Canvas
if (isPalmEraserActive && palmEraserTouchIds.Count == 0)
{
LogHelper.WriteLogToFile($"Palm eraser recovery triggered - Touch points remaining: {palmEraserTouchIds.Count}, dec.Count: {dec.Count}");
// 恢复高光状态
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
@@ -777,10 +878,10 @@ namespace Ink_Canvas
if (isPalmEraserActive)
{
LogHelper.WriteLogToFile("Palm eraser force recovery - all touch points cleared");
// 恢复高光状态
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
// 恢复编辑模式
try
{
@@ -806,7 +907,7 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile($"Palm eraser force recovery failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
}
// 如果手掌擦还在激活状态但触摸点已清空,强制重置状态
isPalmEraserActive = false;
palmEraserTouchDownHandled = false;
@@ -816,10 +917,10 @@ namespace Ink_Canvas
ViewboxFloatingBar.IsHitTestVisible = true;
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
// 停止恢复定时器
StopPalmEraserRecoveryTimer();
LogHelper.WriteLogToFile("Palm eraser force recovery completed");
}
}
@@ -864,7 +965,34 @@ namespace Ink_Canvas
return;
// 三指及以上禁止缩放
bool disableScale = dec.Count >= 3;
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
// 修复:允许单指拖动选中的墨迹,即使禁用了多指手势
if (isInMultiTouchMode) return;
// 如果是单指拖动选中的墨迹,允许处理
if (dec.Count == 1 && inkCanvas.GetSelectedStrokes().Count > 0)
{
var md = e.DeltaManipulation;
var trans = md.Translation; // 获得位移矢量
if (trans.X != 0 || trans.Y != 0)
{
var m = new Matrix();
m.Translate(trans.X, trans.Y); // 移动
var strokes = inkCanvas.GetSelectedStrokes();
foreach (var stroke in strokes)
{
stroke.Transform(m, false);
}
// 更新选择框位置
updateBorderStrokeSelectionControlLocation();
}
return;
}
if (!Settings.Gesture.IsEnableTwoFingerGesture) return;
if ((dec.Count >= 2 && (Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode ||
StackPanelPPTControls.Visibility != Visibility.Visible ||
StackPanelPPTButtons.Visibility == Visibility.Collapsed)) ||
@@ -938,12 +1066,15 @@ namespace Ink_Canvas
catch { }
}
;
// 同时变换画布上的图片元素
TransformCanvasImages(m);
}
else
{
foreach (var stroke in inkCanvas.Strokes) stroke.Transform(m, false);
;
// 同时变换画布上的图片元素
TransformCanvasImages(m);
}
foreach (var circle in circles)
@@ -961,6 +1092,86 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 变换画布上的图片元素,使其与墨迹同步移动
/// </summary>
private void TransformCanvasImages(Matrix matrix)
{
try
{
// 遍历inkCanvas的所有子元素,找到图片元素
for (int i = inkCanvas.Children.Count - 1; i >= 0; i--)
{
var child = inkCanvas.Children[i];
if (child is Image image)
{
// 应用矩阵变换到图片
ApplyMatrixTransformToImage(image, matrix);
}
else if (child is MediaElement mediaElement)
{
// 对媒体元素也应用变换
ApplyMatrixTransformToMediaElement(mediaElement, matrix);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"变换画布图片失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 对图片应用矩阵变换
/// </summary>
private void ApplyMatrixTransformToImage(Image image, Matrix matrix)
{
try
{
// 获取图片的RenderTransform,如果不存在则创建新的TransformGroup
TransformGroup transformGroup = image.RenderTransform as TransformGroup;
if (transformGroup == null)
{
transformGroup = new TransformGroup();
image.RenderTransform = transformGroup;
}
// 创建新的MatrixTransform并添加到变换组
var matrixTransform = new MatrixTransform(matrix);
transformGroup.Children.Add(matrixTransform);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"应用图片变换失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 对媒体元素应用矩阵变换
/// </summary>
private void ApplyMatrixTransformToMediaElement(MediaElement mediaElement, Matrix matrix)
{
try
{
// 获取媒体元素的RenderTransform,如果不存在则创建新的TransformGroup
TransformGroup transformGroup = mediaElement.RenderTransform as TransformGroup;
if (transformGroup == null)
{
transformGroup = new TransformGroup();
mediaElement.RenderTransform = transformGroup;
}
// 创建新的MatrixTransform并添加到变换组
var matrixTransform = new MatrixTransform(matrix);
transformGroup.Children.Add(matrixTransform);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"应用媒体元素变换失败: {ex.Message}", LogHelper.LogType.Error);
}
}
// 退出多指书写模式,恢复InkCanvas的TouchDown事件绑定
private void ExitMultiTouchModeIfNeeded()
{
+55
View File
@@ -201,5 +201,60 @@ namespace Ink_Canvas
}
}
private void DisableAllHotkeysMenuItem_Clicked(object sender, RoutedEventArgs e)
{
var mainWin = (MainWindow)Current.MainWindow;
if (mainWin.IsLoaded)
{
try
{
// 获取全局快捷键管理器
var hotkeyManagerField = typeof(MainWindow).GetField("_globalHotkeyManager",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var hotkeyManager = hotkeyManagerField?.GetValue(mainWin) as GlobalHotkeyManager;
if (hotkeyManager != null)
{
// 禁用所有快捷键
hotkeyManager.DisableHotkeyRegistration();
// 更新菜单项文本和状态
var menuItem = sender as MenuItem;
if (menuItem != null)
{
var headerPanel = menuItem.Header as SimpleStackPanel;
if (headerPanel != null)
{
var textBlock = headerPanel.Children[0] as TextBlock;
if (textBlock != null)
{
if (textBlock.Text == "禁用所有快捷键")
{
textBlock.Text = "启用所有快捷键";
LogHelper.WriteLogToFile("已禁用所有快捷键", LogHelper.LogType.Event);
}
else
{
textBlock.Text = "禁用所有快捷键";
// 重新启用快捷键
hotkeyManager.EnableHotkeyRegistration();
LogHelper.WriteLogToFile("已启用所有快捷键", LogHelper.LogType.Event);
}
}
}
}
}
else
{
LogHelper.WriteLogToFile("无法获取全局快捷键管理器", LogHelper.LogType.Error);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"禁用/启用快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
}
}
+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.10.0")]
[assembly: AssemblyFileVersion("1.7.10.0")]
[assembly: AssemblyVersion("1.7.11.0")]
[assembly: AssemblyFileVersion("1.7.11.0")]
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+19 -17
View File
@@ -1,27 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows;
using System.Windows.Media;
namespace Ink_Canvas.Resources.ICCConfiguration {
public enum InitialPositionTypes {
namespace Ink_Canvas.Resources.ICCConfiguration
{
public enum InitialPositionTypes
{
TopLeft, TopRight, BottomLeft, BottomRight, TopCenter, BottomCenter, Custom
}
public enum ElementCornerRadiusTypes {
public enum ElementCornerRadiusTypes
{
SuperEllipse, Circle, Custom, None
}
public class NearSnapAreaSize {
public double[] TopLeft { get; set; } = {24,24};
public double[] TopRight { get; set; } = {24,24};
public double[] BottomLeft { get; set; } = {24,24};
public double[] BottomRight { get; set; } = {24,24};
public class NearSnapAreaSize
{
public double[] TopLeft { get; set; } = { 24, 24 };
public double[] TopRight { get; set; } = { 24, 24 };
public double[] BottomLeft { get; set; } = { 24, 24 };
public double[] BottomRight { get; set; } = { 24, 24 };
public double TopCenter { get; set; } = 24;
public double BottomCenter { get; set; } = 24;
}
public class ICCFloatingBarConfiguration {
public class ICCFloatingBarConfiguration
{
public bool SemiTransparent { get; set; } = false;
public bool NearSnap { get; set; } = true;
public InitialPositionTypes InitialPosition { get; set; } = InitialPositionTypes.BottomCenter;
@@ -37,7 +37,8 @@ namespace Ink_Canvas.Resources.ICCConfiguration {
public double MovingLimitationNoSnap { get; set; } = 12;
public double MovingLimitationSnapped { get; set; } = 24;
public NearSnapAreaSize NearSnapAreaSize { get; set; } = new NearSnapAreaSize() {
public NearSnapAreaSize NearSnapAreaSize { get; set; } = new NearSnapAreaSize()
{
TopLeft = new double[] { 24, 24 },
TopRight = new double[] { 24, 24 },
BottomLeft = new double[] { 24, 24 },
@@ -55,7 +56,8 @@ namespace Ink_Canvas.Resources.ICCConfiguration {
};
}
public class ICCConfiguration {
public class ICCConfiguration
{
public ICCFloatingBarConfiguration FloatingBar { get; set; } = new ICCFloatingBarConfiguration();
}
}
+42 -1
View File
@@ -440,7 +440,7 @@ namespace Ink_Canvas
public int MinimumAutomationStrokeNumber { get; set; }
[JsonProperty("autoSavedStrokesLocation")]
public string AutoSavedStrokesLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "saves");
public string AutoSavedStrokesLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Saves");
[JsonProperty("autoDelSavedFiles")]
public bool AutoDelSavedFiles;
@@ -448,11 +448,50 @@ namespace Ink_Canvas
[JsonProperty("autoDelSavedFilesDaysThreshold")]
public int AutoDelSavedFilesDaysThreshold = 15;
[JsonProperty("keepFoldAfterSoftwareExit")]
public bool KeepFoldAfterSoftwareExit { get; set; } = false;
[JsonProperty("isSaveFullPageStrokes")]
public bool IsSaveFullPageStrokes;
[JsonProperty("isAutoEnterAnnotationAfterKillHite")]
public bool IsAutoEnterAnnotationAfterKillHite { get; set; }
[JsonProperty("floatingWindowInterceptor")]
public FloatingWindowInterceptorSettings FloatingWindowInterceptor { get; set; } = new FloatingWindowInterceptorSettings();
}
public class FloatingWindowInterceptorSettings
{
[JsonProperty("isEnabled")]
public bool IsEnabled { get; set; } = false;
[JsonProperty("scanIntervalMs")]
public int ScanIntervalMs { get; set; } = 1000;
[JsonProperty("interceptRules")]
public Dictionary<string, bool> InterceptRules { get; set; } = new Dictionary<string, bool>
{
{ "SeewoWhiteboard3Floating", true },
{ "SeewoWhiteboard5Floating", true },
{ "SeewoWhiteboard5CFloating", true },
{ "SeewoPincoSideBarFloating", true },
{ "SeewoPincoDrawingFloating", true },
{ "SeewoPPTFloating", true },
{ "AiClassFloating", true },
{ "HiteAnnotationFloating", true },
{ "ChangYanFloating", true },
{ "ChangYanPptFloating", true },
{ "IntelligentClassFloating", true },
{ "SeewoDesktopAnnotationFloating", true },
{ "SeewoDesktopSideBarFloating", true }
};
[JsonProperty("autoStart")]
public bool AutoStart { get; set; } = false;
[JsonProperty("showNotifications")]
public bool ShowNotifications { get; set; } = true;
}
public class Advanced
@@ -545,6 +584,8 @@ namespace Ink_Canvas
public bool ShowRandomAndSingleDraw { get; set; } = true;
[JsonProperty("directCallCiRand")]
public bool DirectCallCiRand { get; set; }
[JsonProperty("externalCallerType")]
public int ExternalCallerType { get; set; } = 0;
[JsonProperty("selectedBackgroundIndex")]
public int SelectedBackgroundIndex { get; set; }
[JsonProperty("customPickNameBackgrounds")]
@@ -222,7 +222,6 @@ namespace Ink_Canvas
BtnStartCover.Visibility = Visibility.Collapsed;
BorderStopTime.Visibility = Visibility.Collapsed;
TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F"));
return;
}
else if (isTimerRunning && isPaused)
{
@@ -359,7 +358,8 @@ namespace Ink_Canvas
// Set to center
double dpiScaleX = 1, dpiScaleY = 1;
PresentationSource source = PresentationSource.FromVisual(this);
if (source != null) {
if (source != null)
{
dpiScaleX = source.CompositionTarget.TransformToDevice.M11;
dpiScaleY = source.CompositionTarget.TransformToDevice.M22;
}
+12 -12
View File
@@ -42,7 +42,7 @@ namespace Ink_Canvas.Windows
// 加载当前快捷键(包括配置文件中的)
LoadCurrentHotkeys();
SetupEventHandlers();
// 初始化鼠标模式快捷键设置
InitializeMouseModeSettings();
}
@@ -66,11 +66,11 @@ namespace Ink_Canvas.Windows
try
{
// 设置窗口启动位置为屏幕中心
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
// 确保窗口在显示时获得焦点
this.ShowInTaskbar = true;
ShowInTaskbar = true;
LogHelper.WriteLogToFile("快捷键设置窗口属性已设置");
}
catch (Exception ex)
@@ -274,10 +274,10 @@ namespace Ink_Canvas.Windows
{
// 设置开关的初始状态
ToggleSwitchEnableHotkeysInMouseMode.IsOn = MainWindow.Settings.Appearance.EnableHotkeysInMouseMode;
// 绑定开关变化事件
ToggleSwitchEnableHotkeysInMouseMode.Toggled += OnMouseModeHotkeyToggleChanged;
LogHelper.WriteLogToFile($"鼠标模式快捷键设置已初始化: {MainWindow.Settings.Appearance.EnableHotkeysInMouseMode}");
}
catch (Exception ex)
@@ -295,16 +295,16 @@ namespace Ink_Canvas.Windows
{
// 更新设置
MainWindow.Settings.Appearance.EnableHotkeysInMouseMode = ToggleSwitchEnableHotkeysInMouseMode.IsOn;
// 立即保存设置
MainWindow.SaveSettingsToFile();
// 如果快捷键管理器存在,立即更新快捷键状态
if (_hotkeyManager != null)
{
// 检查当前是否处于鼠标模式
bool isCurrentlyMouseMode = _mainWindow.inkCanvas.EditingMode == InkCanvasEditingMode.None;
// 如果当前处于鼠标模式且关闭了开关,立即禁用快捷键
if (isCurrentlyMouseMode && !ToggleSwitchEnableHotkeysInMouseMode.IsOn)
{
@@ -317,7 +317,7 @@ namespace Ink_Canvas.Windows
_hotkeyManager.UpdateHotkeyStateForToolMode(isCurrentlyMouseMode);
}
}
LogHelper.WriteLogToFile($"鼠标模式快捷键设置已更新: {MainWindow.Settings.Appearance.EnableHotkeysInMouseMode}", LogHelper.LogType.Event);
}
catch (Exception ex)
@@ -540,7 +540,7 @@ namespace Ink_Canvas.Windows
/// <summary>
/// 标题栏拖拽事件
/// </summary>
private void TitleBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
@@ -25,17 +25,22 @@ namespace Ink_Canvas
if (e.LeftButton == MouseButtonState.Pressed) DragMove();
}
private void BtnFullscreen_MouseUp(object sender, MouseButtonEventArgs e) {
if (WindowState == WindowState.Normal) {
private void BtnFullscreen_MouseUp(object sender, MouseButtonEventArgs e)
{
if (WindowState == WindowState.Normal)
{
WindowState = WindowState.Maximized;
SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.BackToWindow;
} else {
}
else
{
WindowState = WindowState.Normal;
SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.FullScreen;
}
}
private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e) {
private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
{
e.Handled = true;
}
}
+1 -1
View File
@@ -6,10 +6,10 @@
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:local="clr-namespace:Ink_Canvas.Windows"
mc:Ignorable="d"
WindowStyle="None"
Title="插件管理" Height="550" Width="800"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResize"
WindowStyle="None"
AllowsTransparency="True"
Background="#F9F9F9">
@@ -12,7 +12,7 @@ using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using MessageBox = System.Windows.MessageBox;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
namespace Ink_Canvas.Windows
{
+4 -4
View File
@@ -98,7 +98,7 @@
<Border x:Name="BorderBtnRand" MouseUp="BorderBtnRand_MouseUp" Background="#0066BF" Height="70" Width="200" CornerRadius="35">
<ui:SimpleStackPanel Margin="3,0" Spacing="20" Orientation="Horizontal" HorizontalAlignment="Center">
<Viewbox Margin="0,20">
<ui:FontIcon Name="FontIconStart" Glyph="&#xE779;" Foreground="White"/>
<ui:SymbolIcon Name="SymbolIconStart" Symbol="Contact" Foreground="White"/>
</Viewbox>
<TextBlock Text="开抽" Foreground="White" FontSize="32" Margin="-1,-1,4,0" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
@@ -113,7 +113,7 @@
<Border x:Name="BorderBtnExternalCaller" MouseUp="BorderBtnExternalCaller_MouseUp" Background="#00B894" Height="50" Width="120" CornerRadius="25">
<ui:SimpleStackPanel Margin="3,0" Spacing="8" Orientation="Horizontal" HorizontalAlignment="Center">
<Viewbox Margin="0,10">
<ui:FontIcon Glyph="&#xE909;" Foreground="White"/>
<ui:SymbolIcon Symbol="Globe" Foreground="White"/>
</Viewbox>
<TextBlock Text="外部点名" Foreground="White" FontSize="16" Margin="-1,-1,4,0" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
@@ -127,7 +127,7 @@
</Border.Effect>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Viewbox Margin="15,12">
<ui:FontIcon Glyph="&#xE716;" Foreground="Black"/>
<ui:SymbolIcon Symbol="People" Foreground="Black"/>
</Viewbox>
<TextBlock Margin="-5,12,15,12" Name="TextBlockPeopleCount" Text="点击此处以导入名单" FontSize="16" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ui:SimpleStackPanel>
@@ -137,7 +137,7 @@
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
</Border.Effect>
<Viewbox Margin="14">
<ui:FontIcon Glyph="&#xE8BB;" Foreground="White"/>
<ui:SymbolIcon Symbol="Clear" Foreground="White"/>
</Viewbox>
</Border>
</Canvas>
+11 -10
View File
@@ -1,4 +1,5 @@
using Ink_Canvas.Helpers;
using iNKORE.UI.WPF.Modern.Controls;
using Microsoft.VisualBasic;
using System;
using System.Collections.Generic;
@@ -26,7 +27,7 @@ namespace Ink_Canvas
{
InitializeComponent();
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
BorderBtnHelp.Visibility = !settings.RandSettings.DisplayRandWindowNamesInputBtn ? Visibility.Collapsed : Visibility.Visible;
BorderBtnHelp.Visibility = settings.RandSettings.DisplayRandWindowNamesInputBtn == false ? Visibility.Collapsed : Visibility.Visible;
RandMaxPeopleOneTime = settings.RandSettings.RandWindowOnceMaxStudents;
RandDoneAutoCloseWaitTime = (int)settings.RandSettings.RandWindowOnceCloseLatency * 1000;
@@ -35,10 +36,10 @@ namespace Ink_Canvas
// 设置窗口为置顶
Topmost = true;
// 添加窗口关闭事件处理
Closed += RandWindow_Closed;
// 添加窗口显示事件处理,确保置顶
Loaded += RandWindow_Loaded;
}
@@ -80,7 +81,7 @@ namespace Ink_Canvas
isAutoClose = IsAutoClose;
PeopleControlPane.Opacity = 0.4;
PeopleControlPane.IsHitTestVisible = false;
BorderBtnHelp.Visibility = !settings.RandSettings.DisplayRandWindowNamesInputBtn ? Visibility.Collapsed : Visibility.Visible;
BorderBtnHelp.Visibility = settings.RandSettings.DisplayRandWindowNamesInputBtn == false ? Visibility.Collapsed : Visibility.Visible;
RandMaxPeopleOneTime = settings.RandSettings.RandWindowOnceMaxStudents;
RandDoneAutoCloseWaitTime = (int)settings.RandSettings.RandWindowOnceCloseLatency * 1000;
@@ -89,10 +90,10 @@ namespace Ink_Canvas
// 设置窗口为置顶
Topmost = true;
// 添加窗口关闭事件处理
Closed += RandWindow_Closed;
// 添加窗口显示事件处理,确保置顶
Loaded += RandWindow_Loaded;
@@ -120,7 +121,7 @@ namespace Ink_Canvas
if (RandMaxPeopleOneTime != -1 && TotalCount >= RandMaxPeopleOneTime) return;
TotalCount++;
LabelNumberCount.Text = TotalCount.ToString();
FontIconStart.Glyph = "&#xE779;";
SymbolIconStart.Symbol = Symbol.People;
BorderBtnAdd.Opacity = 1;
BorderBtnMinus.Opacity = 1;
}
@@ -132,7 +133,7 @@ namespace Ink_Canvas
LabelNumberCount.Text = TotalCount.ToString();
if (TotalCount == 1)
{
FontIconStart.Glyph = "&#xE779;";
SymbolIconStart.Symbol = Symbol.Contact;
}
}
@@ -371,10 +372,10 @@ namespace Ink_Canvas
// 强制激活窗口
Activate();
Focus();
// 设置置顶
Topmost = true;
// 使用Win32 API强制置顶
var hwnd = new WindowInteropHelper(this).Handle;
if (hwnd != IntPtr.Zero)
@@ -19,8 +19,6 @@
<Rectangle Name="OverlayRectangle"
Fill="Black"
Opacity="0.3" />
<!-- 透明选择区域遮罩 - 使用正确的几何体操作 -->
<Rectangle Name="TransparentSelectionMask"
Fill="Black"
Opacity="0.3"
@@ -103,7 +101,7 @@
Content="矩形模式"
Margin="4,0"
Padding="12,6"
Background="#2563eb"
Background="#6b7280"
Foreground="White"
BorderThickness="0"
FontWeight="Medium"
@@ -117,6 +115,24 @@
BorderThickness="0"
FontWeight="Medium"
Click="FreehandModeButton_Click" />
<Button Name="FullScreenButton"
Content="全屏截图"
Margin="4,0"
Padding="12,6"
Background="#6b7280"
Foreground="White"
BorderThickness="0"
FontWeight="Medium"
Click="FullScreenButton_Click" />
<Button Name="CameraModeButton"
Content="摄像头截图"
Margin="4,0"
Padding="12,6"
Background="#6b7280"
Foreground="White"
BorderThickness="0"
FontWeight="Medium"
Click="CameraModeButton_Click" />
<!-- 分隔线 -->
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"
@@ -145,24 +161,70 @@
</StackPanel>
</Border>
<!-- 提示文字 -->
<Border Background="#1a1a1a"
Opacity="0.9"
CornerRadius="6"
Padding="16,10"
<!-- 摄像头预览区域 -->
<Border Name="CameraPreviewBorder"
Background="#1a1a1a"
Opacity="0.95"
CornerRadius="8"
Padding="8"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="0,80,0,0"
VerticalAlignment="Center"
Width="640"
Height="480"
Visibility="Collapsed"
Panel.ZIndex="1000">
<TextBlock Name="HintText"
Text="拖拽鼠标选择矩形区域,或使用自由绘制模式"
Foreground="White"
FontSize="14"
FontWeight="Medium" />
<Grid>
<!-- 摄像头预览画面 -->
<Image Name="CameraPreviewImage"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<!-- 摄像头控制面板 -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,8">
<!-- 摄像头选择下拉框 -->
<ComboBox Name="CameraSelectionComboBox"
Width="200"
Margin="4,0"
Background="#2d2d2d"
Foreground="White"
BorderBrush="#404040"
SelectionChanged="CameraSelectionComboBox_SelectionChanged" />
<!-- 切换摄像头按钮 -->
<Button Name="SwitchCameraButton"
Content="切换摄像头"
Margin="4,0"
Padding="8,4"
Background="#3b82f6"
Foreground="White"
BorderThickness="0"
FontWeight="Medium"
Click="SwitchCameraButton_Click" />
</StackPanel>
<!-- 摄像头状态指示 -->
<Border Background="#1a1a1a"
Opacity="0.9"
CornerRadius="4"
Padding="8,4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="8">
<TextBlock Name="CameraStatusText"
Foreground="White"
FontSize="12"
FontWeight="Medium"
Text="摄像头未连接" />
</Border>
</Grid>
</Border>
<!-- 调整模式提示 -->
<Border Name="AdjustModeHint"
<!-- 统一提示文字区域 -->
<Border Name="HintTextBorder"
Background="#1a1a1a"
Opacity="0.9"
CornerRadius="6"
@@ -170,9 +232,9 @@
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,140"
Visibility="Collapsed"
Panel.ZIndex="1000">
<TextBlock Text="拖拽控制点调整选择区域,或拖拽边框移动位置"
<TextBlock Name="HintText"
Text="拖拽鼠标选择矩形区域,或使用自由绘制模式"
Foreground="White"
FontSize="14"
FontWeight="Medium" />
@@ -7,9 +7,12 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Drawing;
using Ink_Canvas.Helpers;
using Brushes = System.Windows.Media.Brushes;
using Color = System.Windows.Media.Color;
using DrawingRectangle = System.Drawing.Rectangle;
using WpfPoint = System.Windows.Point;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using WpfCanvas = System.Windows.Controls.Canvas;
@@ -22,13 +25,16 @@ namespace Ink_Canvas
private bool _isFreehandMode;
private bool _isAdjusting;
private bool _isMoving;
private Point _startPoint;
private Point _currentPoint;
private Point _lastMousePosition;
private List<Point> _freehandPoints;
private bool _isCameraMode;
private WpfPoint _startPoint;
private WpfPoint _currentPoint;
private WpfPoint _lastMousePosition;
private List<WpfPoint> _freehandPoints;
private Polyline _freehandPolyline;
private Rect _currentSelection;
private ControlPointType _activeControlPoint = ControlPointType.None;
private CameraService _cameraService;
private Bitmap _capturedCameraImage = null;
// 控制点类型枚举
private enum ControlPointType
@@ -40,7 +46,9 @@ namespace Ink_Canvas
}
public DrawingRectangle? SelectedArea { get; private set; }
public List<Point> SelectedPath { get; private set; }
public List<WpfPoint> SelectedPath { get; private set; }
public Bitmap CameraImage { get; private set; }
public System.Windows.Media.Imaging.BitmapSource CameraBitmapSource { get; private set; }
public ScreenshotSelectorWindow()
{
@@ -55,12 +63,18 @@ namespace Ink_Canvas
// 绑定控制点事件
BindControlPointEvents();
// 初始化按钮状态
InitializeButtonStates();
// 初始化摄像头服务
InitializeCameraService();
// 隐藏提示文字的定时器
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(5);
timer.Tick += (s, e) =>
{
HintText.Visibility = Visibility.Collapsed;
HintTextBorder.Visibility = Visibility.Collapsed;
timer.Stop();
};
timer.Start();
@@ -68,7 +82,7 @@ namespace Ink_Canvas
private void InitializeFreehandMode()
{
_freehandPoints = new List<Point>();
_freehandPoints = new List<WpfPoint>();
_freehandPolyline = new Polyline
{
Stroke = Brushes.White,
@@ -78,10 +92,185 @@ namespace Ink_Canvas
SelectionCanvas.Children.Add(_freehandPolyline);
}
private void InitializeButtonStates()
{
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
}
private void InitializeCameraService()
{
try
{
_cameraService = new CameraService();
_cameraService.FrameReceived += CameraService_FrameReceived;
_cameraService.ErrorOccurred += CameraService_ErrorOccurred;
// 初始化摄像头选择下拉框
RefreshCameraComboBox();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"初始化摄像头服务失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void RefreshCameraComboBox()
{
try
{
CameraSelectionComboBox.Items.Clear();
if (_cameraService.HasAvailableCameras())
{
var cameraNames = _cameraService.GetCameraNames();
foreach (var name in cameraNames)
{
CameraSelectionComboBox.Items.Add(name);
}
if (cameraNames.Count > 0)
{
CameraSelectionComboBox.SelectedIndex = 0;
}
}
else
{
CameraSelectionComboBox.Items.Add("未找到摄像头设备");
CameraSelectionComboBox.SelectedIndex = 0;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void CameraService_FrameReceived(object sender, Bitmap frame)
{
try
{
Dispatcher.BeginInvoke(new Action(() =>
{
if (_isCameraMode && CameraPreviewImage != null && frame != null)
{
try
{
// 验证帧的有效性
if (frame.Width <= 0 || frame.Height <= 0)
return;
// 创建新的位图,避免Clone的问题
var clonedFrame = new Bitmap(frame.Width, frame.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
using (var graphics = Graphics.FromImage(clonedFrame))
{
graphics.DrawImage(frame, 0, 0);
}
var bitmapSource = ConvertBitmapToBitmapSource(clonedFrame);
if (bitmapSource != null)
{
CameraPreviewImage.Source = bitmapSource;
CameraStatusText.Text = "摄像头已连接";
}
// 释放临时位图
clonedFrame.Dispose();
}
catch (Exception frameEx)
{
LogHelper.WriteLogToFile($"处理摄像头帧时出错: {frameEx.Message}", LogHelper.LogType.Error);
}
}
}));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理摄像头帧失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void CameraService_ErrorOccurred(object sender, string error)
{
try
{
Dispatcher.BeginInvoke(new Action(() =>
{
CameraStatusText.Text = $"摄像头错误: {error}";
}));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理摄像头错误失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private System.Windows.Media.Imaging.BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap)
{
try
{
// 验证位图有效性
if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
return null;
// 使用更安全的方法转换位图
var bitmapData = bitmap.LockBits(
new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
bitmap.PixelFormat);
try
{
// 根据像素格式选择合适的WPF像素格式
PixelFormat wpfPixelFormat;
switch (bitmap.PixelFormat)
{
case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
wpfPixelFormat = PixelFormats.Bgr24;
break;
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
wpfPixelFormat = PixelFormats.Bgra32;
break;
case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
wpfPixelFormat = PixelFormats.Bgr32;
break;
default:
wpfPixelFormat = PixelFormats.Bgr24;
break;
}
var bitmapSource = System.Windows.Media.Imaging.BitmapSource.Create(
bitmapData.Width,
bitmapData.Height,
bitmap.HorizontalResolution,
bitmap.VerticalResolution,
wpfPixelFormat,
null,
bitmapData.Scan0,
bitmapData.Stride * bitmapData.Height,
bitmapData.Stride);
bitmapSource.Freeze();
return bitmapSource;
}
finally
{
bitmap.UnlockBits(bitmapData);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"转换位图失败: {ex.Message}", LogHelper.LogType.Error);
return null;
}
}
private void BindControlPointEvents()
{
// 绑定所有控制点的鼠标事件
var controlPoints = new[]
var controlPoints = new[]
{
TopLeftControl, TopRightControl, BottomLeftControl, BottomRightControl,
TopControl, BottomControl, LeftControl, RightControl
@@ -92,11 +281,11 @@ namespace Ink_Canvas
control.MouseLeftButtonDown += ControlPoint_MouseLeftButtonDown;
control.MouseLeftButtonUp += ControlPoint_MouseLeftButtonUp;
control.MouseMove += ControlPoint_MouseMove;
// 确保控制点能够接收鼠标事件
control.IsHitTestVisible = true;
control.Focusable = false;
// 设置控制点的Z-index,确保它们在最上层
WpfCanvas.SetZIndex(control, 1003);
}
@@ -142,24 +331,127 @@ namespace Ink_Canvas
{
// 重置所有选择状态
ResetSelectionState();
_isFreehandMode = false;
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
HintText.Text = "拖拽鼠标选择矩形区域";
HintText.Visibility = Visibility.Visible;
HintTextBorder.Visibility = Visibility.Visible;
}
private void FreehandModeButton_Click(object sender, RoutedEventArgs e)
{
// 重置所有选择状态
ResetSelectionState();
_isFreehandMode = true;
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
HintText.Text = "按住鼠标左键绘制任意形状,松开直接截图";
HintText.Visibility = Visibility.Visible;
HintTextBorder.Visibility = Visibility.Visible;
}
private void FullScreenButton_Click(object sender, RoutedEventArgs e)
{
// 重置所有选择状态
ResetSelectionState();
// 设置全屏截图模式
_isFreehandMode = false;
_isCameraMode = false;
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
// 隐藏摄像头预览
CameraPreviewBorder.Visibility = Visibility.Collapsed;
// 直接执行全屏截图
PerformFullScreenCapture();
}
private void CameraModeButton_Click(object sender, RoutedEventArgs e)
{
try
{
// 重置所有选择状态
ResetSelectionState();
// 设置摄像头模式
_isFreehandMode = false;
_isCameraMode = true;
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
// 显示摄像头预览
CameraPreviewBorder.Visibility = Visibility.Visible;
HintText.Text = "摄像头预览模式,点击确认截图按钮进行截图";
HintTextBorder.Visibility = Visibility.Visible;
// 启动摄像头预览
if (_cameraService != null && _cameraService.HasAvailableCameras())
{
var selectedIndex = CameraSelectionComboBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < _cameraService.GetCameraNames().Count)
{
_cameraService.StartPreview(selectedIndex);
}
}
else
{
CameraStatusText.Text = "未找到摄像头设备";
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"启动摄像头模式失败: {ex.Message}", LogHelper.LogType.Error);
CameraStatusText.Text = $"启动摄像头失败: {ex.Message}";
}
}
private void CameraSelectionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
if (_isCameraMode && _cameraService != null)
{
var selectedIndex = CameraSelectionComboBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < _cameraService.GetCameraNames().Count)
{
_cameraService.SwitchCamera(selectedIndex);
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void SwitchCameraButton_Click(object sender, RoutedEventArgs e)
{
try
{
if (_cameraService != null && _cameraService.HasAvailableCameras())
{
var cameraNames = _cameraService.GetCameraNames();
if (cameraNames.Count > 1)
{
var currentIndex = CameraSelectionComboBox.SelectedIndex;
var nextIndex = (currentIndex + 1) % cameraNames.Count;
CameraSelectionComboBox.SelectedIndex = nextIndex;
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
}
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
@@ -169,10 +461,54 @@ namespace Ink_Canvas
{
return;
}
// 在摄像头模式下,执行摄像头截图
if (_isCameraMode)
{
ConfirmCameraCapture();
return;
}
ConfirmSelection();
}
private void ConfirmCameraCapture()
{
try
{
if (_cameraService != null && _cameraService.IsCapturing)
{
// 直接获取BitmapSource,避免Bitmap传递问题
var bitmapSource = _cameraService.GetCurrentFrameAsBitmapSource();
if (bitmapSource != null)
{
// 保存BitmapSource而不是Bitmap
CameraBitmapSource = bitmapSource;
// 停止摄像头预览
_cameraService.StopPreview();
// 设置结果并关闭窗口
DialogResult = true;
Close();
}
else
{
CameraStatusText.Text = "无法获取摄像头画面";
}
}
else
{
CameraStatusText.Text = "摄像头未启动";
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"摄像头截图失败: {ex.Message}", LogHelper.LogType.Error);
CameraStatusText.Text = $"摄像头截图失败: {ex.Message}";
}
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
CancelSelection();
@@ -183,10 +519,10 @@ namespace Ink_Canvas
// 检查是否点击了UI元素,如果是则不处理选择
var hitElement = e.Source as FrameworkElement;
if (hitElement != null && (
hitElement is Ellipse ||
hitElement is System.Windows.Controls.Button ||
hitElement is Border ||
hitElement is TextBlock ||
hitElement is Ellipse ||
hitElement is System.Windows.Controls.Button ||
hitElement is Border ||
hitElement is TextBlock ||
hitElement is StackPanel ||
hitElement is Separator ||
hitElement.Name == "SizeInfoBorder" ||
@@ -213,7 +549,7 @@ namespace Ink_Canvas
_currentPoint = _startPoint;
// 隐藏提示文字
HintText.Visibility = Visibility.Collapsed;
HintTextBorder.Visibility = Visibility.Collapsed;
if (_isFreehandMode)
{
@@ -222,7 +558,7 @@ namespace Ink_Canvas
_freehandPolyline.Points.Clear();
_freehandPoints.Add(_startPoint);
_freehandPolyline.Points.Add(_startPoint);
// 确保自由绘制路径可见
_freehandPolyline.Visibility = Visibility.Visible;
}
@@ -259,7 +595,7 @@ namespace Ink_Canvas
// 自由绘制模式:添加点到路径
_freehandPoints.Add(_currentPoint);
_freehandPolyline.Points.Add(_currentPoint);
// 确保自由绘制路径可见
_freehandPolyline.Visibility = Visibility.Visible;
}
@@ -290,8 +626,8 @@ namespace Ink_Canvas
if (_freehandPoints.Count > 1) // 只要有点就可以截图
{
// 创建路径的副本,避免修改原始列表
var pathPoints = new List<Point>(_freehandPoints);
var pathPoints = new List<WpfPoint>(_freehandPoints);
// 简化路径处理,不强制闭合
// 如果路径没有闭合,自动添加起始点
if (pathPoints.Count > 0)
@@ -301,13 +637,13 @@ namespace Ink_Canvas
// 优化路径:移除重复点和过于接近的点,提高路径质量
var optimizedPath = OptimizePath(pathPoints);
// 保存选择的路径
SelectedPath = optimizedPath;
// 计算边界矩形用于截图
var bounds = CalculatePathBounds(optimizedPath);
// 确保边界矩形有效
if (bounds.Width >= 0 && bounds.Height >= 0)
{
@@ -325,7 +661,7 @@ namespace Ink_Canvas
return;
}
}
// 如果自由绘制失败,清除路径并继续
_freehandPoints.Clear();
_freehandPolyline.Points.Clear();
@@ -341,7 +677,8 @@ namespace Ink_Canvas
_currentSelection = rect;
_isAdjusting = true;
ShowControlPoints();
AdjustModeHint.Visibility = Visibility.Visible;
HintText.Text = "拖拽控制点调整选择区域,或拖拽边框移动位置";
HintTextBorder.Visibility = Visibility.Visible;
}
else
{
@@ -363,7 +700,7 @@ namespace Ink_Canvas
_isMoving = true;
_lastMousePosition = e.GetPosition(this);
// 确定当前控制点类型
var ellipse = sender as Ellipse;
if (ellipse == TopLeftControl) _activeControlPoint = ControlPointType.TopLeft;
@@ -486,26 +823,26 @@ namespace Ink_Canvas
// 更新角控制点位置
WpfCanvas.SetLeft(TopLeftControl, rect.Left - 4);
WpfCanvas.SetTop(TopLeftControl, rect.Top - 4);
WpfCanvas.SetLeft(TopRightControl, rect.Right - 4);
WpfCanvas.SetTop(TopRightControl, rect.Top - 4);
WpfCanvas.SetLeft(BottomLeftControl, rect.Left - 4);
WpfCanvas.SetTop(BottomLeftControl, rect.Bottom - 4);
WpfCanvas.SetLeft(BottomRightControl, rect.Right - 4);
WpfCanvas.SetTop(BottomRightControl, rect.Bottom - 4);
// 更新边控制点位置
WpfCanvas.SetLeft(TopControl, rect.Left + rect.Width / 2 - 4);
WpfCanvas.SetTop(TopControl, rect.Top - 4);
WpfCanvas.SetLeft(BottomControl, rect.Left + rect.Width / 2 - 4);
WpfCanvas.SetTop(BottomControl, rect.Bottom - 4);
WpfCanvas.SetLeft(LeftControl, rect.Left - 4);
WpfCanvas.SetTop(LeftControl, rect.Top + rect.Height / 2 - 4);
WpfCanvas.SetLeft(RightControl, rect.Right - 4);
WpfCanvas.SetTop(RightControl, rect.Top + rect.Height / 2 - 4);
}
@@ -519,7 +856,7 @@ namespace Ink_Canvas
WpfCanvas.SetTop(SelectionRectangle, rect.Y);
SelectionRectangle.Width = rect.Width;
SelectionRectangle.Height = rect.Height;
// 在选择过程中,禁用选择矩形的鼠标事件,避免干扰选择操作
if (_isSelecting)
{
@@ -547,7 +884,7 @@ namespace Ink_Canvas
{
// 更新选择区域的几何体
SelectionClipGeometry.Rect = selectionRect;
// 显示透明遮罩,隐藏原始遮罩
TransparentSelectionMask.Visibility = Visibility.Visible;
OverlayRectangle.Visibility = Visibility.Collapsed;
@@ -569,7 +906,7 @@ namespace Ink_Canvas
WpfCanvas.SetTop(SelectionRectangle, rect.Y);
SelectionRectangle.Width = rect.Width;
SelectionRectangle.Height = rect.Height;
// 确保选择矩形在调整模式下能够接收鼠标事件
if (_isAdjusting)
{
@@ -612,7 +949,7 @@ namespace Ink_Canvas
{
return;
}
if (_isAdjusting)
{
// 转换为屏幕坐标,考虑DPI缩放
@@ -633,13 +970,20 @@ namespace Ink_Canvas
private void CancelSelection()
{
// 停止摄像头预览
if (_cameraService != null)
{
_cameraService.StopPreview();
}
SelectedArea = null;
SelectedPath = null;
CameraImage = null;
DialogResult = false;
Close();
}
private Rect CalculatePathBounds(List<Point> points)
private Rect CalculatePathBounds(List<WpfPoint> points)
{
if (points == null || points.Count == 0)
return new Rect();
@@ -660,12 +1004,12 @@ namespace Ink_Canvas
return new Rect(minX, minY, maxX - minX, maxY - minY);
}
private List<Point> OptimizePath(List<Point> points)
private List<WpfPoint> OptimizePath(List<WpfPoint> points)
{
if (points == null || points.Count < 3)
return points;
var optimized = new List<Point>();
var optimized = new List<WpfPoint>();
optimized.Add(points[0]);
for (int i = 1; i < points.Count - 1; i++)
@@ -688,7 +1032,7 @@ namespace Ink_Canvas
return optimized;
}
private double DistanceToLine(Point point, Point lineStart, Point lineEnd)
private double DistanceToLine(WpfPoint point, WpfPoint lineStart, WpfPoint lineEnd)
{
var A = point.X - lineStart.X;
var B = point.Y - lineStart.Y;
@@ -733,7 +1077,7 @@ namespace Ink_Canvas
_isMoving = true;
_activeControlPoint = ControlPointType.Move;
_lastMousePosition = e.GetPosition(this);
// 捕获鼠标到选择矩形
SelectionRectangle.CaptureMouse();
e.Handled = true;
@@ -786,47 +1130,113 @@ namespace Ink_Canvas
}
}
private void PerformFullScreenCapture()
{
try
{
// 获取虚拟屏幕边界
var virtualScreen = SystemInformation.VirtualScreen;
// 设置全屏截图区域
SelectedArea = new DrawingRectangle(virtualScreen.X, virtualScreen.Y, virtualScreen.Width, virtualScreen.Height);
SelectedPath = null; // 全屏截图不需要路径
// 直接关闭窗口并返回结果
DialogResult = true;
Close();
}
catch (Exception ex)
{
// 如果全屏截图失败,记录错误并关闭窗口
System.Diagnostics.Debug.WriteLine($"全屏截图失败: {ex.Message}");
DialogResult = false;
Close();
}
}
private void ResetSelectionState()
{
// 重置所有选择相关的状态
_isSelecting = false;
_isAdjusting = false;
_isMoving = false;
_isCameraMode = false;
_activeControlPoint = ControlPointType.None;
// 停止摄像头预览
if (_cameraService != null)
{
_cameraService.StopPreview();
}
// 清除自由绘制的内容
_freehandPoints.Clear();
_freehandPolyline.Points.Clear();
_freehandPolyline.Visibility = Visibility.Collapsed;
// 清除矩形选择的内容
SelectionRectangle.Visibility = Visibility.Collapsed;
SelectionRectangle.IsHitTestVisible = false;
ControlPointsCanvas.Visibility = Visibility.Collapsed;
SizeInfoBorder.Visibility = Visibility.Collapsed;
SelectionPath.Visibility = Visibility.Collapsed;
AdjustModeHint.Visibility = Visibility.Collapsed;
HintTextBorder.Visibility = Visibility.Collapsed;
// 隐藏摄像头预览
CameraPreviewBorder.Visibility = Visibility.Collapsed;
// 重置遮罩
TransparentSelectionMask.Visibility = Visibility.Collapsed;
OverlayRectangle.Visibility = Visibility.Visible;
// 释放鼠标捕获
if (IsMouseCaptured)
{
ReleaseMouseCapture();
}
// 释放选择矩形的鼠标捕获
if (SelectionRectangle.IsMouseCaptured)
{
SelectionRectangle.ReleaseMouseCapture();
}
// 重置选择区域
_currentSelection = new Rect();
SelectedArea = null;
SelectedPath = null;
CameraImage = null;
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
}
protected override void OnClosed(EventArgs e)
{
try
{
// 清理摄像头资源
if (_cameraService != null)
{
_cameraService.StopPreview();
_cameraService.Dispose();
_cameraService = null;
}
// 清理摄像头图像
_capturedCameraImage?.Dispose();
CameraImage?.Dispose();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理资源失败: {ex.Message}", LogHelper.LogType.Error);
}
finally
{
base.OnClosed(e);
}
}
}
}
@@ -1,50 +1,51 @@
using OSVersionExtension;
using iNKORE.UI.WPF.Helpers;
using OSVersionExtension;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using iNKORE.UI.WPF.Helpers;
using static Ink_Canvas.Windows.SettingsWindow;
namespace Ink_Canvas.Windows.SettingsViews {
namespace Ink_Canvas.Windows.SettingsViews
{
/// <summary>
/// AboutPanel.xaml 的交互逻辑
/// </summary>
public partial class AboutPanel : UserControl {
public AboutPanel() {
public partial class AboutPanel : UserControl
{
public AboutPanel()
{
InitializeComponent();
// 关于页面图片横幅
if (File.Exists(App.RootPath + "icc-about-illustrations.png")) {
try {
if (File.Exists(App.RootPath + "icc-about-illustrations.png"))
{
try
{
CopyrightBannerImage.Visibility = Visibility.Visible;
CopyrightBannerImage.Source =
new BitmapImage(new Uri($"file://{App.RootPath + "icc-about-illustrations.png"}"));
}
catch { }
} else {
}
else
{
CopyrightBannerImage.Visibility = Visibility.Collapsed;
}
// 关于页面构建时间
var buildTime = FileBuildTimeHelper.GetBuildDateTime(System.Reflection.Assembly.GetExecutingAssembly());
if (buildTime != null) {
var buildTime = FileBuildTimeHelper.GetBuildDateTime(Assembly.GetExecutingAssembly());
if (buildTime != null)
{
var bt = ((DateTimeOffset)buildTime).LocalDateTime;
var m = bt.Month.ToString().PadLeft(2, '0');
var d = bt.Day.ToString().PadLeft(2, '0');
@@ -59,7 +60,8 @@ namespace Ink_Canvas.Windows.SettingsViews {
AboutSystemVersion.Text = $"{OSVersion.GetOperatingSystem()} {OSVersion.GetOSVersion().Version}";
// 关于页面触摸设备
var _t_touch = new Thread(() => {
var _t_touch = new Thread(() =>
{
var touchcount = TouchTabletDetectHelper.GetTouchTabletDevices().Count;
var support = TouchTabletDetectHelper.IsTouchEnabled();
Dispatcher.BeginInvoke(() =>
@@ -68,8 +70,9 @@ namespace Ink_Canvas.Windows.SettingsViews {
_t_touch.Start();
}
public static class TouchTabletDetectHelper {
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static class TouchTabletDetectHelper
{
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
public static bool IsTouchEnabled()
@@ -84,9 +87,9 @@ namespace Ink_Canvas.Windows.SettingsViews {
{
public USBDeviceInfo(string deviceID, string pnpDeviceID, string description)
{
this.DeviceID = deviceID;
this.PnpDeviceID = pnpDeviceID;
this.Description = description;
DeviceID = deviceID;
PnpDeviceID = pnpDeviceID;
Description = description;
}
public string DeviceID { get; private set; }
public string PnpDeviceID { get; private set; }
@@ -99,9 +102,10 @@ namespace Ink_Canvas.Windows.SettingsViews {
ManagementObjectCollection collection;
using (var searcher = new ManagementObjectSearcher(@"Select * From Win32_PnPEntity"))
collection = searcher.Get();
collection = searcher.Get();
foreach (var device in collection) {
foreach (var device in collection)
{
var name = new StringBuilder((string)device.GetPropertyValue("Name")).ToString();
if (!name.Contains("Pentablet")) continue;
devices.Add(new USBDeviceInfo(
@@ -116,7 +120,8 @@ namespace Ink_Canvas.Windows.SettingsViews {
}
}
public static class FileBuildTimeHelper {
public static class FileBuildTimeHelper
{
public struct _IMAGE_FILE_HEADER
{
public ushort Machine;
@@ -153,46 +158,55 @@ namespace Ink_Canvas.Windows.SettingsViews {
pinnedBuffer.Free();
}
}
else
else
{
return null;
}
}
}
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e) {
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
if (scrollViewer.VerticalOffset >= 10) {
if (scrollViewer.VerticalOffset >= 10)
{
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
} else {
}
else
{
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
}
}
private void ScrollBar_Scroll(object sender, RoutedEventArgs e) {
private void ScrollBar_Scroll(object sender, RoutedEventArgs e)
{
var scrollbar = (ScrollBar)sender;
var scrollviewer = scrollbar.FindAscendant<ScrollViewer>();
if (scrollviewer != null) scrollviewer.ScrollToVerticalOffset(scrollbar.Track.Value);
}
private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e) {
private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e)
{
var border = (Border)sender;
if (border.Child is Track track) {
if (border.Child is Track track)
{
track.Width = 16;
track.Margin = new Thickness(0, 0, -2, 0);
var scrollbar = track.FindAscendant<ScrollBar>();
if (scrollbar != null) scrollbar.Width = 16;
var grid = track.FindAscendant<Grid>();
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) {
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder)
{
backgroundBorder.Width = 8;
backgroundBorder.CornerRadius = new CornerRadius(4);
backgroundBorder.Opacity = 1;
}
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ;
if (thumb != null) {
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb);
if (thumb != null)
{
var _thumb = thumb as Border;
_thumb.CornerRadius = new CornerRadius(4);
_thumb.Width = 8;
@@ -202,23 +216,27 @@ namespace Ink_Canvas.Windows.SettingsViews {
}
}
private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e) {
private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e)
{
var border = (Border)sender;
border.Background = new SolidColorBrush(Colors.Transparent);
border.CornerRadius = new CornerRadius(0);
if (border.Child is Track track) {
if (border.Child is Track track)
{
track.Width = 6;
track.Margin = new Thickness(0, 0, 0, 0);
var scrollbar = track.FindAscendant<ScrollBar>();
if (scrollbar != null) scrollbar.Width = 6;
var grid = track.FindAscendant<Grid>();
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) {
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder)
{
backgroundBorder.Width = 3;
backgroundBorder.CornerRadius = new CornerRadius(1.5);
backgroundBorder.Opacity = 0;
}
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ;
if (thumb != null) {
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb);
if (thumb != null)
{
var _thumb = thumb as Border;
_thumb.CornerRadius = new CornerRadius(1.5);
_thumb.Width = 3;
@@ -228,15 +246,17 @@ namespace Ink_Canvas.Windows.SettingsViews {
}
}
private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e) {
private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e)
{
var thumb = (Thumb)sender;
var border = thumb.Template.FindName("ScrollbarThumbEx",thumb);
var border = thumb.Template.FindName("ScrollbarThumbEx", thumb);
((Border)border).Background = new SolidColorBrush(Color.FromRgb(95, 95, 95));
}
private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e) {
private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e)
{
var thumb = (Thumb)sender;
var border = thumb.Template.FindName("ScrollbarThumbEx",thumb);
var border = thumb.Template.FindName("ScrollbarThumbEx", thumb);
((Border)border).Background = new SolidColorBrush(Color.FromRgb(138, 138, 138));
}
}
@@ -1,24 +1,16 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Ink_Canvas.Windows.SettingsViews {
public partial class AppearancePanel : UserControl {
public AppearancePanel() {
namespace Ink_Canvas.Windows.SettingsViews
{
public partial class AppearancePanel : UserControl
{
public AppearancePanel()
{
InitializeComponent();
BaseView.SettingsPanels.Add(new SettingsViewPanel() {
BaseView.SettingsPanels.Add(new SettingsViewPanel()
{
Title = "新版设置测试",
Items = new ObservableCollection<SettingsItem>(new SettingsItem[] {
new SettingsItem() {
@@ -67,7 +59,8 @@ namespace Ink_Canvas.Windows.SettingsViews {
},
})
});
BaseView.SettingsPanels[0].Items[5].OnToggleSwitchToggled += (sender, args) => {
BaseView.SettingsPanels[0].Items[5].OnToggleSwitchToggled += (sender, args) =>
{
var item = (SettingsItem)sender;
BaseView.SettingsPanels[0].Items[4].ToggleSwitchEnabled = item.ToggleSwitchToggled;
};
@@ -1,105 +1,120 @@
using System;
using System.Collections.Generic;
using iNKORE.UI.WPF.DragDrop;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using iNKORE.UI.WPF.DragDrop;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Menu;
namespace Ink_Canvas.Windows.SettingsViews {
public class FloatingBarItem {
namespace Ink_Canvas.Windows.SettingsViews
{
public class FloatingBarItem
{
public DrawingImage IconSource { get; set; }
}
public partial class FloatingBarDnDSettingsPanel : UserControl {
public class BarItemsDropTarget : IDropTarget {
public partial class FloatingBarDnDSettingsPanel : UserControl
{
public class BarItemsDropTarget : IDropTarget
{
public ObservableCollection<FloatingBarItem> BarItems { get; set; } =
new ObservableCollection<FloatingBarItem>();
void IDropTarget.DragOver(IDropInfo info) {
void IDropTarget.DragOver(IDropInfo info)
{
info.Effects = DragDropEffects.Move;
info.DropTargetAdorner = DropTargetAdorners.Insert;
}
void IDropTarget.Drop(IDropInfo info) {
if (info.Data is FloatingBarItem draggedItem) {
void IDropTarget.Drop(IDropInfo info)
{
if (info.Data is FloatingBarItem draggedItem)
{
var targetCollection = info.TargetCollection as ObservableCollection<FloatingBarItem>;
var sourceCollection = info.DragInfo.SourceCollection as ObservableCollection<FloatingBarItem>;
Trace.WriteLine(info.InsertIndex);
// 在同一个 ObservableCollection 中移动
if (targetCollection.Equals(sourceCollection)) {
if (info.InsertIndex == 0) {
targetCollection.Move(targetCollection.IndexOf(info.Data as FloatingBarItem),0);
} else if (info.InsertIndex == targetCollection.Count) {
if (targetCollection.Equals(sourceCollection))
{
if (info.InsertIndex == 0)
{
targetCollection.Move(targetCollection.IndexOf(info.Data as FloatingBarItem), 0);
}
else if (info.InsertIndex == targetCollection.Count)
{
targetCollection.Remove(info.Data as FloatingBarItem);
targetCollection.Add(info.Data as FloatingBarItem);
} else if ((info.InsertIndex - targetCollection.IndexOf(info.Data as FloatingBarItem) == 1 &&
info.InsertPosition == RelativeInsertPosition.AfterTargetItem) ||
(info.InsertIndex - targetCollection.IndexOf(info.Data as FloatingBarItem) == 0 &&
info.InsertPosition == RelativeInsertPosition.BeforeTargetItem)) { } else {
targetCollection.Move(targetCollection.IndexOf(info.Data as FloatingBarItem),info.InsertIndex - 1);
}
} else { // 跨 ObservableCollection 移动
else if ((info.InsertIndex - targetCollection.IndexOf(info.Data as FloatingBarItem) == 1 &&
info.InsertPosition == RelativeInsertPosition.AfterTargetItem) ||
(info.InsertIndex - targetCollection.IndexOf(info.Data as FloatingBarItem) == 0 &&
info.InsertPosition == RelativeInsertPosition.BeforeTargetItem)) { }
else
{
targetCollection.Move(targetCollection.IndexOf(info.Data as FloatingBarItem), info.InsertIndex - 1);
}
}
else
{ // 跨 ObservableCollection 移动
sourceCollection.Remove(info.Data as FloatingBarItem);
targetCollection.Insert(info.InsertIndex, info.Data as FloatingBarItem);
}
}
}
void IDropTarget.DragEnter(IDropInfo info) {
void IDropTarget.DragEnter(IDropInfo info)
{
}
void IDropTarget.DragLeave(IDropInfo info) {
void IDropTarget.DragLeave(IDropInfo info)
{
}
}
public class BarDrawerItemsDropTarget : IDropTarget {
public class BarDrawerItemsDropTarget : IDropTarget
{
public ObservableCollection<FloatingBarItem> BarDrawerItems { get; set; } =
new ObservableCollection<FloatingBarItem>();
void IDropTarget.DragOver(IDropInfo info) {
void IDropTarget.DragOver(IDropInfo info)
{
info.Effects = DragDropEffects.Move;
info.DropTargetAdorner = DropTargetAdorners.Insert;
}
void IDropTarget.Drop(IDropInfo info) {
if (info.Data is FloatingBarItem draggedItem) {
void IDropTarget.Drop(IDropInfo info)
{
if (info.Data is FloatingBarItem draggedItem)
{
var targetCollection = info.TargetCollection as ObservableCollection<FloatingBarItem>;
var sourceCollection = info.DragInfo.SourceCollection as ObservableCollection<FloatingBarItem>;
// 在同一个 ObservableCollection 中移动
if (targetCollection.Equals(sourceCollection)) {
if (targetCollection.Equals(sourceCollection))
{
targetCollection.Insert(info.InsertIndex, info.Data as FloatingBarItem);
} else { // 跨 ObservableCollection 移动
}
else
{ // 跨 ObservableCollection 移动
sourceCollection.Remove(info.Data as FloatingBarItem);
targetCollection.Insert(info.InsertIndex, info.Data as FloatingBarItem);
}
}
}
void IDropTarget.DragEnter(IDropInfo info) {
void IDropTarget.DragEnter(IDropInfo info)
{
}
void IDropTarget.DragLeave(IDropInfo info) {
void IDropTarget.DragLeave(IDropInfo info)
{
}
@@ -108,19 +123,23 @@ namespace Ink_Canvas.Windows.SettingsViews {
public BarItemsDropTarget barItems { get; set; } = new BarItemsDropTarget();
public BarDrawerItemsDropTarget barDrawerItems { get; set; } = new BarDrawerItemsDropTarget();
public FloatingBarDnDSettingsPanel() {
public FloatingBarDnDSettingsPanel()
{
InitializeComponent();
ToolbarItemsControl.DataContext = barItems;
ToolbarDrawerItemsControl.DataContext = barDrawerItems;
barItems.BarItems.Add(new FloatingBarItem() {
barItems.BarItems.Add(new FloatingBarItem()
{
IconSource = FindResource("EraserIcon") as DrawingImage,
});
barDrawerItems.BarDrawerItems.Add(new FloatingBarItem() {
barDrawerItems.BarDrawerItems.Add(new FloatingBarItem()
{
IconSource = FindResource("CursorIcon") as DrawingImage,
});
barDrawerItems.BarDrawerItems.Add(new FloatingBarItem() {
barDrawerItems.BarDrawerItems.Add(new FloatingBarItem()
{
IconSource = FindResource("PenIcon") as DrawingImage,
});
}
@@ -1,27 +1,19 @@
using System;
using System.Collections.Generic;
using iNKORE.UI.WPF.Helpers;
using iNKORE.UI.WPF.Modern.Controls;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using iNKORE.UI.WPF.Helpers;
using iNKORE.UI.WPF.Modern.Controls;
namespace Ink_Canvas.Windows.SettingsViews {
namespace Ink_Canvas.Windows.SettingsViews
{
public class SettingsViewPanel {
public class SettingsViewPanel
{
public string Title { get; set; }
public Visibility _TitleVisibility => String.IsNullOrWhiteSpace(Title) ? Visibility.Collapsed : Visibility.Visible;
public Thickness _PanelMargin =>
@@ -29,14 +21,16 @@ namespace Ink_Canvas.Windows.SettingsViews {
public ObservableCollection<SettingsItem> Items { get; set; } = new ObservableCollection<SettingsItem>() { };
}
public enum SettingsItemType {
public enum SettingsItemType
{
Plain, // 只显示Title和Description
SingleToggleSwtich,
ToggleSwitchWithArrowButton,
SelectionButtons,
}
public class SettingsItem : INotifyPropertyChanged {
public class SettingsItem : INotifyPropertyChanged
{
public string Title { get; set; }
public string Description { get; set; }
public SettingsItemType Type { get; set; } = SettingsItemType.Plain;
@@ -46,10 +40,13 @@ namespace Ink_Canvas.Windows.SettingsViews {
public Visibility _ToggleSwitchVisibility =>
Type == SettingsItemType.SingleToggleSwtich || Type == SettingsItemType.ToggleSwitchWithArrowButton ? Visibility.Visible : Visibility.Collapsed;
private bool _toggleSwitchToggled;
public bool ToggleSwitchToggled {
public bool ToggleSwitchToggled
{
get => _toggleSwitchToggled;
set {
if (_toggleSwitchToggled != value) {
set
{
if (_toggleSwitchToggled != value)
{
_toggleSwitchToggled = value;
OnPropertyChanged(nameof(ToggleSwitchToggled)); // 通知绑定控件属性变化
OnToggleSwitchToggled?.Invoke(this, EventArgs.Empty); // 触发事件
@@ -58,15 +55,19 @@ namespace Ink_Canvas.Windows.SettingsViews {
}
public event EventHandler OnToggleSwitchToggled;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private SolidColorBrush _toggleSwitchBackground = new SolidColorBrush(Color.FromRgb(53, 132, 228));
public SolidColorBrush ToggleSwitchBackground {
public SolidColorBrush ToggleSwitchBackground
{
get => _toggleSwitchBackground;
set {
if (_toggleSwitchBackground != value) {
set
{
if (_toggleSwitchBackground != value)
{
_toggleSwitchBackground = value;
OnPropertyChanged(nameof(ToggleSwitchBackground)); // 通知绑定控件属性变化
}
@@ -74,19 +75,24 @@ namespace Ink_Canvas.Windows.SettingsViews {
}
private bool _toggleSwitchEnabled = true;
public bool ToggleSwitchEnabled {
public bool ToggleSwitchEnabled
{
get => _toggleSwitchEnabled;
set {
if (_toggleSwitchEnabled != value) {
set
{
if (_toggleSwitchEnabled != value)
{
_toggleSwitchEnabled = value;
OnPropertyChanged(nameof(ToggleSwitchEnabled)); // 通知绑定控件属性变化
}
}
}
}
public partial class SettingsBaseView : UserControl {
public SettingsBaseView() {
public partial class SettingsBaseView : UserControl
{
public SettingsBaseView()
{
InitializeComponent();
SettingsViewBaseItemsControl.ItemsSource = SettingsPanels;
}
@@ -94,39 +100,48 @@ namespace Ink_Canvas.Windows.SettingsViews {
public ObservableCollection<SettingsViewPanel> SettingsPanels { get; set; } =
new ObservableCollection<SettingsViewPanel>() { };
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e) {
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
if (scrollViewer.VerticalOffset >= 10) {
if (scrollViewer.VerticalOffset >= 10)
{
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
} else {
}
else
{
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
}
}
private void ScrollBar_Scroll(object sender, RoutedEventArgs e) {
private void ScrollBar_Scroll(object sender, RoutedEventArgs e)
{
var scrollbar = (ScrollBar)sender;
var scrollviewer = scrollbar.FindAscendant<ScrollViewer>();
if (scrollviewer != null) scrollviewer.ScrollToVerticalOffset(scrollbar.Track.Value);
}
private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e) {
private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e)
{
var border = (Border)sender;
if (border.Child is Track track) {
if (border.Child is Track track)
{
track.Width = 16;
track.Margin = new Thickness(0, 0, -2, 0);
var scrollbar = track.FindAscendant<ScrollBar>();
if (scrollbar != null) scrollbar.Width = 16;
var grid = track.FindAscendant<Grid>();
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) {
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder)
{
backgroundBorder.Width = 8;
backgroundBorder.CornerRadius = new CornerRadius(4);
backgroundBorder.Opacity = 1;
}
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ;
if (thumb != null) {
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb);
if (thumb != null)
{
var _thumb = thumb as Border;
_thumb.CornerRadius = new CornerRadius(4);
_thumb.Width = 8;
@@ -136,29 +151,34 @@ namespace Ink_Canvas.Windows.SettingsViews {
}
}
private void ToggleSwitch_OnToggled(object sender, RoutedEventArgs e) {
private void ToggleSwitch_OnToggled(object sender, RoutedEventArgs e)
{
var toggleswitch = sender as ToggleSwitch;
var item = toggleswitch.Tag as SettingsItem;
item.ToggleSwitchToggled = toggleswitch.IsOn;
}
private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e) {
private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e)
{
var border = (Border)sender;
border.Background = new SolidColorBrush(Colors.Transparent);
border.CornerRadius = new CornerRadius(0);
if (border.Child is Track track) {
if (border.Child is Track track)
{
track.Width = 6;
track.Margin = new Thickness(0, 0, 0, 0);
var scrollbar = track.FindAscendant<ScrollBar>();
if (scrollbar != null) scrollbar.Width = 6;
var grid = track.FindAscendant<Grid>();
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) {
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder)
{
backgroundBorder.Width = 3;
backgroundBorder.CornerRadius = new CornerRadius(1.5);
backgroundBorder.Opacity = 0;
}
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ;
if (thumb != null) {
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb);
if (thumb != null)
{
var _thumb = thumb as Border;
_thumb.CornerRadius = new CornerRadius(1.5);
_thumb.Width = 3;
@@ -168,15 +188,17 @@ namespace Ink_Canvas.Windows.SettingsViews {
}
}
private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e) {
private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e)
{
var thumb = (Thumb)sender;
var border = thumb.Template.FindName("ScrollbarThumbEx",thumb);
var border = thumb.Template.FindName("ScrollbarThumbEx", thumb);
((Border)border).Background = new SolidColorBrush(Color.FromRgb(95, 95, 95));
}
private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e) {
private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e)
{
var thumb = (Thumb)sender;
var border = thumb.Template.FindName("ScrollbarThumbEx",thumb);
var border = thumb.Template.FindName("ScrollbarThumbEx", thumb);
((Border)border).Background = new SolidColorBrush(Color.FromRgb(138, 138, 138));
}
}
@@ -1,95 +1,92 @@
using System;
using iNKORE.UI.WPF.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Ink_Canvas.Windows.SettingsViews;
using iNKORE.UI.WPF.Helpers;
using iNKORE.UI.WPF.Modern.Controls;
using OSVersionExtension;
namespace Ink_Canvas.Windows {
public partial class SettingsWindow : Window {
namespace Ink_Canvas.Windows
{
public partial class SettingsWindow : Window
{
public SettingsWindow() {
public SettingsWindow()
{
InitializeComponent();
// 初始化侧边栏项目
SidebarItemsControl.ItemsSource = SidebarItems;
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "启动时行为",
Name = "StartupItem",
IconSource = FindResource("StartupIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "画板和墨迹",
Name = "CanvasAndInkItem",
IconSource = FindResource("CanvasAndInkIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "手势操作",
Name = "GesturesItem",
IconSource = FindResource("GesturesIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "墨迹纠正",
Name = "InkRecognitionItem",
IconSource = FindResource("InkRecognitionIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Separator
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "个性化设置",
Name = "ThemeItem",
IconSource = FindResource("AppearanceIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "快捷键设置",
Name = "ShortcutsItem",
IconSource = FindResource("AppearanceIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "崩溃处理",
Name = "CrashActionItem",
IconSource = FindResource("AppearanceIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Separator
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "PowerPoint 支持",
Name = "PowerPointItem",
@@ -97,48 +94,56 @@ namespace Ink_Canvas.Windows {
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "自动化行为",
Name = "AutomationItem",
IconSource = FindResource("AutomationIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "随机点名",
Name = "LuckyRandomItem",
IconSource = FindResource("LuckyRandomIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Separator
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "存储空间",
Name = "StorageItem",
IconSource = FindResource("StorageIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "截图和屏幕捕捉",
Name = "SnapshotItem",
IconSource = FindResource("SnapshotIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Separator
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "高级选项",
Name = "AdvancedItem",
IconSource = FindResource("AdvancedIcon") as DrawingImage,
Selected = false,
});
SidebarItems.Add(new SidebarItem() {
SidebarItems.Add(new SidebarItem()
{
Type = SidebarItemType.Item,
Title = "关于 InkCanvasForClass",
Name = "AboutItem",
@@ -218,33 +223,38 @@ namespace Ink_Canvas.Windows {
_selectedSidebarItemName = "CanvasAndInkItem";
UpdateSidebarItemsSelection();
// 为自定义滑块控件添加触摸支持
AddTouchSupportToCustomSliders();
}
public enum SidebarItemType {
public enum SidebarItemType
{
Item,
Separator
}
public class SidebarItem {
public class SidebarItem
{
public SidebarItemType Type { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public ImageSource IconSource { get; set; }
public bool Selected { get; set; }
public Visibility _spVisibility {
get => this.Type == SidebarItemType.Separator ? Visibility.Visible : Visibility.Collapsed;
public Visibility _spVisibility
{
get => Type == SidebarItemType.Separator ? Visibility.Visible : Visibility.Collapsed;
}
public Visibility _siVisibility {
get => this.Type == SidebarItemType.Item ? Visibility.Visible : Visibility.Collapsed;
public Visibility _siVisibility
{
get => Type == SidebarItemType.Item ? Visibility.Visible : Visibility.Collapsed;
}
public SolidColorBrush _siBackground {
get => this.Selected
public SolidColorBrush _siBackground
{
get => Selected
? new SolidColorBrush(Color.FromRgb(217, 217, 217))
: new SolidColorBrush(Colors.Transparent);
}
@@ -258,10 +268,13 @@ namespace Ink_Canvas.Windows {
public string[] SettingsPaneTitles;
public string[] SettingsPaneNames;
public void UpdateSidebarItemsSelection() {
foreach (var si in SidebarItems) {
public void UpdateSidebarItemsSelection()
{
foreach (var si in SidebarItems)
{
si.Selected = si.Name == _selectedSidebarItemName;
if (si.Selected && SettingsWindowTitle != null) {
if (si.Selected && SettingsWindowTitle != null)
{
SettingsWindowTitle.Text = si.Title;
}
}
@@ -281,45 +294,57 @@ namespace Ink_Canvas.Windows {
if (StoragePane != null) StoragePane.Visibility = _selectedSidebarItemName == "StorageItem" ? Visibility.Visible : Visibility.Collapsed;
if (SnapshotPane != null) SnapshotPane.Visibility = _selectedSidebarItemName == "SnapshotItem" ? Visibility.Visible : Visibility.Collapsed;
if (AdvancedPane != null) AdvancedPane.Visibility = _selectedSidebarItemName == "AdvancedItem" ? Visibility.Visible : Visibility.Collapsed;
if (SettingsPaneScrollViewers != null) {
foreach (var sv in SettingsPaneScrollViewers) {
if (sv != null) {
if (SettingsPaneScrollViewers != null)
{
foreach (var sv in SettingsPaneScrollViewers)
{
if (sv != null)
{
sv.ScrollToTop();
}
}
}
}
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e) {
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
if (scrollViewer.VerticalOffset >= 10) {
if (scrollViewer.VerticalOffset >= 10)
{
DropShadowEffectTopBar.Opacity = 0.25;
} else {
}
else
{
DropShadowEffectTopBar.Opacity = 0;
}
}
private void ScrollBar_Scroll(object sender, RoutedEventArgs e) {
private void ScrollBar_Scroll(object sender, RoutedEventArgs e)
{
var scrollbar = (ScrollBar)sender;
var scrollviewer = scrollbar.FindAscendant<ScrollViewer>();
if (scrollviewer != null) scrollviewer.ScrollToVerticalOffset(scrollbar.Track.Value);
}
private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e) {
private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e)
{
var border = (Border)sender;
if (border.Child is Track track) {
if (border.Child is Track track)
{
track.Width = 16;
track.Margin = new Thickness(0, 0, -2, 0);
var scrollbar = track.FindAscendant<ScrollBar>();
if (scrollbar != null) scrollbar.Width = 16;
var grid = track.FindAscendant<Grid>();
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) {
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder)
{
backgroundBorder.Width = 8;
backgroundBorder.CornerRadius = new CornerRadius(4);
backgroundBorder.Opacity = 1;
}
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ;
if (thumb != null) {
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb);
if (thumb != null)
{
var _thumb = thumb as Border;
_thumb.CornerRadius = new CornerRadius(4);
_thumb.Width = 8;
@@ -329,23 +354,27 @@ namespace Ink_Canvas.Windows {
}
}
private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e) {
private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e)
{
var border = (Border)sender;
border.Background = new SolidColorBrush(Colors.Transparent);
border.CornerRadius = new CornerRadius(0);
if (border.Child is Track track) {
if (border.Child is Track track)
{
track.Width = 6;
track.Margin = new Thickness(0, 0, 0, 0);
var scrollbar = track.FindAscendant<ScrollBar>();
if (scrollbar != null) scrollbar.Width = 6;
var grid = track.FindAscendant<Grid>();
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) {
if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder)
{
backgroundBorder.Width = 3;
backgroundBorder.CornerRadius = new CornerRadius(1.5);
backgroundBorder.Opacity = 0;
}
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ;
if (thumb != null) {
var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb);
if (thumb != null)
{
var _thumb = thumb as Border;
_thumb.CornerRadius = new CornerRadius(1.5);
_thumb.Width = 3;
@@ -355,76 +384,90 @@ namespace Ink_Canvas.Windows {
}
}
private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e) {
private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e)
{
var thumb = (Thumb)sender;
var border = thumb.Template.FindName("ScrollbarThumbEx",thumb);
var border = thumb.Template.FindName("ScrollbarThumbEx", thumb);
((Border)border).Background = new SolidColorBrush(Color.FromRgb(95, 95, 95));
}
private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e) {
private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e)
{
var thumb = (Thumb)sender;
var border = thumb.Template.FindName("ScrollbarThumbEx",thumb);
var border = thumb.Template.FindName("ScrollbarThumbEx", thumb);
((Border)border).Background = new SolidColorBrush(Color.FromRgb(138, 138, 138));
}
private Border _sidebarItemMouseDownBorder = null;
private void SidebarItem_MouseDown(object sender, MouseButtonEventArgs e) {
private void SidebarItem_MouseDown(object sender, MouseButtonEventArgs e)
{
if (_sidebarItemMouseDownBorder != null || _sidebarItemMouseDownBorder == sender) return;
_sidebarItemMouseDownBorder = (Border)sender;
var bd = sender as Border;
if (bd.FindDescendantByName("MouseFeedbackBorder") is Border feedbackBd) feedbackBd.Opacity = 0.12;
}
private void SidebarItem_MouseUp(object sender, MouseButtonEventArgs e) {
private void SidebarItem_MouseUp(object sender, MouseButtonEventArgs e)
{
if (_sidebarItemMouseDownBorder == null || _sidebarItemMouseDownBorder != sender) return;
if (_sidebarItemMouseDownBorder.Tag is SidebarItem data) _selectedSidebarItemName = data.Name;
SidebarItem_MouseLeave(sender, null);
UpdateSidebarItemsSelection();
}
private void SidebarItem_MouseLeave(object sender, MouseEventArgs e) {
private void SidebarItem_MouseLeave(object sender, MouseEventArgs e)
{
if (_sidebarItemMouseDownBorder == null || _sidebarItemMouseDownBorder != sender) return;
if (_sidebarItemMouseDownBorder.FindDescendantByName("MouseFeedbackBorder") is Border feedbackBd) feedbackBd.Opacity = 0;
_sidebarItemMouseDownBorder = null;
}
private void CloseButton_Click(object sender, MouseButtonEventArgs e) {
private void CloseButton_Click(object sender, MouseButtonEventArgs e)
{
Close();
}
private void SearchButton_Click(object sender, MouseButtonEventArgs e) {
private void SearchButton_Click(object sender, MouseButtonEventArgs e)
{
// 搜索功能 - 可以显示搜索框或搜索对话框
}
private void MenuButton_Click(object sender, MouseButtonEventArgs e) {
private void MenuButton_Click(object sender, MouseButtonEventArgs e)
{
// 菜单功能 - 可以显示上下文菜单或选项菜单
}
private void ToggleSwitch_Click(object sender, MouseButtonEventArgs e) {
private void ToggleSwitch_Click(object sender, MouseButtonEventArgs e)
{
var border = sender as Border;
if (border != null) {
if (border != null)
{
// 切换开关状态
bool isOn = border.Background.ToString() == "#FF3584E4";
border.Background = isOn ? new SolidColorBrush(Color.FromRgb(225, 225, 225)) : new SolidColorBrush(Color.FromRgb(53, 132, 228));
// 切换内部圆点的位置
var innerBorder = border.Child as Border;
if (innerBorder != null) {
if (innerBorder != null)
{
innerBorder.HorizontalAlignment = isOn ? HorizontalAlignment.Left : HorizontalAlignment.Right;
}
// 根据Tag处理不同的设置项
string tag = border.Tag?.ToString();
if (!string.IsNullOrEmpty(tag)) {
if (!string.IsNullOrEmpty(tag))
{
HandleSettingChange(tag, !isOn);
}
}
}
private void HandleSettingChange(string settingName, bool value) {
private void HandleSettingChange(string settingName, bool value)
{
// 根据设置名称处理不同的设置项
switch (settingName) {
switch (settingName)
{
case "UseObviousCursor":
// 处理使用更加明显的画笔光标设置
break;
@@ -512,18 +555,22 @@ namespace Ink_Canvas.Windows {
}
}
private void OptionButton_Click(object sender, MouseButtonEventArgs e) {
private void OptionButton_Click(object sender, MouseButtonEventArgs e)
{
var border = sender as Border;
if (border != null) {
if (border != null)
{
string tag = border.Tag?.ToString();
if (!string.IsNullOrEmpty(tag)) {
if (!string.IsNullOrEmpty(tag))
{
// 清除同组其他按钮的选中状态
ClearOtherOptionsInGroup(border, tag);
// 设置当前按钮为选中状态
border.Background = new SolidColorBrush(Color.FromRgb(225, 225, 225));
var textBlock = border.Child as TextBlock;
if (textBlock != null) {
if (textBlock != null)
{
textBlock.FontWeight = FontWeights.Bold;
}
@@ -533,21 +580,27 @@ namespace Ink_Canvas.Windows {
}
}
private void ClearOtherOptionsInGroup(Border currentBorder, string currentTag) {
private void ClearOtherOptionsInGroup(Border currentBorder, string currentTag)
{
// 获取当前按钮所在的父容器
var parent = currentBorder.Parent as StackPanel;
if (parent != null) {
if (parent != null)
{
// 获取组名(Tag中下划线前的部分)
string groupName = currentTag.Split('_')[0];
// 清除同组其他按钮的选中状态
foreach (var child in parent.Children) {
if (child is Border border && border != currentBorder) {
foreach (var child in parent.Children)
{
if (child is Border border && border != currentBorder)
{
string childTag = border.Tag?.ToString();
if (!string.IsNullOrEmpty(childTag) && childTag.StartsWith(groupName + "_")) {
if (!string.IsNullOrEmpty(childTag) && childTag.StartsWith(groupName + "_"))
{
border.Background = new SolidColorBrush(Colors.Transparent);
var textBlock = border.Child as TextBlock;
if (textBlock != null) {
if (textBlock != null)
{
textBlock.FontWeight = FontWeights.Normal;
}
}
@@ -556,14 +609,17 @@ namespace Ink_Canvas.Windows {
}
}
private void HandleOptionChange(string optionTag) {
private void HandleOptionChange(string optionTag)
{
// 根据选项标签处理不同的选项变化
string[] parts = optionTag.Split('_');
if (parts.Length >= 2) {
if (parts.Length >= 2)
{
string group = parts[0];
string value = parts[1];
switch (group) {
switch (group)
{
case "EraserSize":
// 处理板擦橡皮大小设置
break;
@@ -622,7 +678,7 @@ namespace Ink_Canvas.Windows {
catch (Exception ex)
{
// 记录错误但不影响程序运行
System.Diagnostics.Debug.WriteLine($"添加自定义滑块触摸支持时出错: {ex.Message}");
Debug.WriteLine($"添加自定义滑块触摸支持时出错: {ex.Message}");
}
}
@@ -636,7 +692,7 @@ namespace Ink_Canvas.Windows {
// 查找面板中的所有自定义滑块控件
var customSliders = FindCustomSlidersInPanel(pane);
foreach (var slider in customSliders)
{
AddTouchSupportToCustomSlider(slider);
@@ -651,11 +707,11 @@ namespace Ink_Canvas.Windows {
private List<CustomSliderInfo> FindCustomSlidersInPanel(DependencyObject panel)
{
var customSliders = new List<CustomSliderInfo>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(panel); i++)
{
var child = VisualTreeHelper.GetChild(panel, i);
// 检查是否是自定义滑块控件(包含GnomeSliderThumb图片的Grid
if (child is Grid grid)
{
@@ -665,11 +721,11 @@ namespace Ink_Canvas.Windows {
customSliders.Add(customSlider);
}
}
// 递归查找子元素
customSliders.AddRange(FindCustomSlidersInPanel(child));
}
return customSliders;
}
@@ -684,7 +740,7 @@ namespace Ink_Canvas.Windows {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(grid); i++)
{
var child = VisualTreeHelper.GetChild(grid, i);
if (child is Image image && image.Source != null)
{
var sourceName = image.Source.ToString();
@@ -698,12 +754,12 @@ namespace Ink_Canvas.Windows {
TrackBorder = FindTrackBorderInGrid(grid),
ValueBorder = FindValueBorderInGrid(grid)
};
return customSlider;
}
}
}
return null;
}
@@ -717,7 +773,7 @@ namespace Ink_Canvas.Windows {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(grid); i++)
{
var child = VisualTreeHelper.GetChild(grid, i);
if (child is Border border && border.Background != null)
{
var brush = border.Background as SolidColorBrush;
@@ -727,7 +783,7 @@ namespace Ink_Canvas.Windows {
}
}
}
return null;
}
@@ -741,7 +797,7 @@ namespace Ink_Canvas.Windows {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(grid); i++)
{
var child = VisualTreeHelper.GetChild(grid, i);
if (child is Border border && border.Background != null)
{
var brush = border.Background as SolidColorBrush;
@@ -751,7 +807,7 @@ namespace Ink_Canvas.Windows {
}
}
}
return null;
}
@@ -897,7 +953,7 @@ namespace Ink_Canvas.Windows {
// 考虑拇指大小,计算有效轨道长度
var thumbSize = 21; // 根据XAML中的Width="21"
var effectiveWidth = trackWidth - thumbSize;
// 计算相对位置(0-1之间),考虑拇指大小
var adjustedX = position.X - thumbSize / 2;
var relativePosition = Math.Max(0, Math.Min(1, adjustedX / effectiveWidth));
@@ -919,7 +975,7 @@ namespace Ink_Canvas.Windows {
{
var valueWidth = relativePosition * trackWidth;
customSlider.ValueBorder.Width = Math.Max(0, valueWidth);
// 调整值显示Border的位置
var valueMargin = customSlider.ValueBorder.Margin;
customSlider.ValueBorder.Margin = new Thickness(0, valueMargin.Top, trackWidth - valueWidth, valueMargin.Bottom);
@@ -930,7 +986,7 @@ namespace Ink_Canvas.Windows {
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新自定义滑块值时出错: {ex.Message}");
Debug.WriteLine($"更新自定义滑块值时出错: {ex.Message}");
}
}
-1
View File
@@ -85,7 +85,6 @@
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CJKmkp"><img src="https://avatars.githubusercontent.com/u/113243675?v=4?s=100" width="100px;" alt="CJK_mkp"/><br /><sub><b>CJK_mkp</b></sub></a><br /><a href="#maintenance-CJKmkp" title="Maintenance">🚧</a> <a href="#doc-CJKmkp" title="Documentation">📖</a> <a href="#code-CJKmkp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hydro11451.qzz.io"><img src="https://avatars.githubusercontent.com/u/214308559?v=4?s=100" width="100px;" alt="Hydrogen"/><br /><sub><b>Hydrogen</b></sub></a><br /><a href="#code-Hydro11451" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CreeperAWA"><img src="https://avatars.githubusercontent.com/u/134939494?v=4?s=100" width="100px;" alt="CreeperAWA"/><br /><sub><b>CreeperAWA</b></sub></a><br /><a href="#code-CreeperAWA" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/2-2-3-trimethylpentane"><img src="https://avatars.githubusercontent.com/u/141403762?v=4?s=100" width="100px;" alt="2,2,3-三甲基戊烷"/><br /><sub><b>2,2,3-三甲基戊烷</b></sub></a><br /><a href="#blog-2-2-3-trimethylpentane" title="Blogposts">📝</a> <a href="#doc-2-2-3-trimethylpentane" title="Documentation">📖</a> <a href="#design-2-2-3-trimethylpentane" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alan-CRL"><img src="https://avatars.githubusercontent.com/u/92425617?v=4?s=100" width="100px;" alt="Alan-CRL"/><br /><sub><b>Alan-CRL</b></sub></a><br /><a href="#code-Alan-CRL" title="Code">💻</a> <a href="#infra-Alan-CRL" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#doc-Alan-CRL" title="Documentation">📖</a> <a href="#financial-Alan-CRL" title="Financial">💵</a></td>
+11 -1
View File
@@ -71,4 +71,14 @@ ICC CE 1.7.X.X更新日志
70. 修复白板时间显示问题
71. 修复长按翻页
72. 优化墨迹渐隐动画
73. 新增软件模式切换
73. 新增软件模式切换
74. 优化多PPT的墨迹显示
75. 修复插入图片无法使用手势
76. 新增全屏截图
77. 新增摄像头截图
78. 修复记忆并提示上次放映页数无效
79. 新增参数可直接进入白板(--brode)
80. 修复切换模式后无法持续生效
81. 优化UI
82. 修复插入图片后使用多指书写导致图片消失
83. 优化PowerPoint联动增强