@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 @@
|
||||
1.7.10.0
|
||||
1.7.11.0
|
||||
|
||||
+1
-1
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,4 +293,4 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
@@ -151,4 +149,4 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
// 应用矩阵变换到元素
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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();
|
||||
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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="" 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="" 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="" 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="" Foreground="White"/>
|
||||
<ui:SymbolIcon Symbol="Clear" Foreground="White"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
</Canvas>
|
||||
|
||||
@@ -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 = "";
|
||||
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 = "";
|
||||
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;
|
||||
};
|
||||
|
||||
+66
-47
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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
@@ -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联动增强
|
||||
Reference in New Issue
Block a user