2026-02-19 18:24:55 +08:00
|
|
|
|
using Ink_Canvas.Helpers;
|
2025-08-31 11:43:52 +08:00
|
|
|
|
using System;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
using System.Collections.Generic;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
using System.Windows.Controls;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
using System.Windows.Ink;
|
|
|
|
|
|
using System.Windows.Media;
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
namespace Ink_Canvas
|
|
|
|
|
|
{
|
|
|
|
|
|
public partial class MainWindow : Window
|
|
|
|
|
|
{
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 存储每个白板页面的墨迹集合
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private StrokeCollection[] strokeCollections = new StrokeCollection[101];
|
2026-02-22 10:14:12 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 存储每个白板页面的最后操作模式是否为重做
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private bool[] whiteboadLastModeIsRedo = new bool[101];
|
2026-02-22 10:14:12 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 存储最后一次触摸按下时的墨迹集合
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private StrokeCollection lastTouchDownStrokeCollection = new StrokeCollection();
|
2026-02-22 10:14:12 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 当前白板页面索引
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private int CurrentWhiteboardIndex = 1;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 白板页面总数
|
|
|
|
|
|
/// </summary>
|
2025-05-25 09:29:48 +08:00
|
|
|
|
private int WhiteboardTotalCount = 1;
|
2026-02-22 10:14:12 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 存储每个白板页面的时间机器历史记录
|
|
|
|
|
|
/// </summary>
|
2025-10-03 17:08:46 +08:00
|
|
|
|
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][];
|
2026-02-22 10:14:12 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 存储每个白板页面的多指书写模式状态
|
|
|
|
|
|
/// </summary>
|
2025-10-03 17:08:46 +08:00
|
|
|
|
private bool[] savedMultiTouchModeStates = new bool[101];
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将当前画布上的所有未保存的图片/媒体和墨迹提交到时间机器历史并将导出结果保存为指定页的快照。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="isBackupMain">为 true 时将导出结果保存到主备份槽(索引 0);为 false 时保存到当前白板索引。</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// - 会提交画布上缺失于历史记录的 Image/MediaElement(但跳过 Tag 等于 VideoPresenterLiveFrameTag 的 Image)和缺失的墨迹;
|
|
|
|
|
|
/// - 导出后把结果存入 TimeMachineHistories 的相应索引,并保存当前多指书写模式到 savedMultiTouchModeStates;
|
|
|
|
|
|
/// - 导出后会清除时间机器的临时墨迹历史以释放内存。
|
|
|
|
|
|
/// - 此方法有副作用:修改 TimeMachineHistories、savedMultiTouchModeStates,并通过 timeMachine 的提交方法改变其内部历史状态。
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void SaveStrokes(bool isBackupMain = false)
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
// 确保画布上的所有UI元素都被保存到时间机器历史记录中
|
|
|
|
|
|
var currentHistory = timeMachine.ExportTimeMachineHistory();
|
|
|
|
|
|
var elementsInHistory = new HashSet<UIElement>();
|
|
|
|
|
|
|
|
|
|
|
|
// 收集已经在历史记录中的元素
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (currentHistory != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var h in currentHistory)
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
if (h.CommitType == TimeMachineHistoryType.ElementInsert &&
|
|
|
|
|
|
h.InsertedElement != null &&
|
2025-08-03 16:46:33 +08:00
|
|
|
|
!h.StrokeHasBeenCleared)
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
elementsInHistory.Add(h.InsertedElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查画布上的所有UI元素,确保它们都在历史记录中
|
|
|
|
|
|
var missingElements = 0;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
foreach (UIElement child in inkCanvas.Children)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (child is Image || child is MediaElement)
|
|
|
|
|
|
{
|
2026-02-19 18:24:55 +08:00
|
|
|
|
if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (!elementsInHistory.Contains(child))
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
timeMachine.CommitElementInsertHistory(child);
|
|
|
|
|
|
missingElements++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
|
2025-07-29 17:38:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 确保画布上的所有墨迹都被保存
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (inkCanvas.Strokes.Count > 0)
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
// 检查是否有墨迹没有在时间机器历史记录中
|
|
|
|
|
|
var strokesInHistory = new HashSet<Stroke>();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (currentHistory != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var h in currentHistory)
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
if (h.CommitType == TimeMachineHistoryType.UserInput &&
|
|
|
|
|
|
h.CurrentStroke != null &&
|
2025-08-03 16:46:33 +08:00
|
|
|
|
!h.StrokeHasBeenCleared)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (Stroke stroke in h.CurrentStroke)
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
strokesInHistory.Add(stroke);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收集没有在历史记录中的墨迹
|
|
|
|
|
|
var missingStrokes = new StrokeCollection();
|
2025-08-03 16:46:33 +08:00
|
|
|
|
foreach (Stroke stroke in inkCanvas.Strokes)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!strokesInHistory.Contains(stroke))
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
missingStrokes.Add(stroke);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (missingStrokes.Count > 0)
|
|
|
|
|
|
{
|
2025-07-29 17:38:58 +08:00
|
|
|
|
timeMachine.CommitStrokeUserInputHistory(missingStrokes);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (isBackupMain)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
|
|
|
|
|
TimeMachineHistories[0] = timeMachineHistory;
|
2025-09-20 12:15:21 +08:00
|
|
|
|
// 保存多指书写模式状态
|
|
|
|
|
|
savedMultiTouchModeStates[0] = isInMultiTouchMode;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timeMachine.ClearStrokeHistory();
|
2025-07-29 17:38:58 +08:00
|
|
|
|
|
2025-07-29 17:44:15 +08:00
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
|
|
|
|
|
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
|
2025-09-20 12:15:21 +08:00
|
|
|
|
// 保存多指书写模式状态
|
|
|
|
|
|
savedMultiTouchModeStates[CurrentWhiteboardIndex] = isInMultiTouchMode;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timeMachine.ClearStrokeHistory();
|
2025-07-29 17:38:58 +08:00
|
|
|
|
|
2025-07-29 17:44:15 +08:00
|
|
|
|
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 清除画布上的所有墨迹并执行内存清理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="isErasedByCode">是否由代码触发的清除操作</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// - 根据参数设置当前提交类型
|
|
|
|
|
|
/// - 清除画布上的所有墨迹
|
|
|
|
|
|
/// - 执行轻量级内存清理
|
|
|
|
|
|
/// - 恢复当前提交类型为用户输入
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void ClearStrokes(bool isErasedByCode)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
_currentCommitType = CommitReason.ClearingCanvas;
|
|
|
|
|
|
if (isErasedByCode) _currentCommitType = CommitReason.CodeInput;
|
2025-07-28 15:47:18 +08:00
|
|
|
|
|
2025-05-25 09:29:48 +08:00
|
|
|
|
inkCanvas.Strokes.Clear();
|
2025-07-28 16:04:36 +08:00
|
|
|
|
|
2025-10-18 18:42:29 +08:00
|
|
|
|
// 执行内存清理
|
|
|
|
|
|
PerformLightweightMemoryCleanup();
|
|
|
|
|
|
|
2025-05-25 09:29:48 +08:00
|
|
|
|
_currentCommitType = CommitReason.UserInput;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-18 18:42:29 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 执行内存清理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void PerformLightweightMemoryCleanup()
|
|
|
|
|
|
{
|
|
|
|
|
|
Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
GC.Collect();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 12:26:43 +08:00
|
|
|
|
private static HashSet<UIElement> CollectRemovedElementsFromHistory(TimeMachineHistory[] history)
|
|
|
|
|
|
{
|
|
|
|
|
|
var set = new HashSet<UIElement>();
|
|
|
|
|
|
if (history == null) return set;
|
|
|
|
|
|
foreach (var h in history)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (h.CommitType == TimeMachineHistoryType.ElementInsert && h.StrokeHasBeenCleared && h.InsertedElement != null)
|
|
|
|
|
|
set.Add(h.InsertedElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
return set;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 恢复指定白板页面的墨迹和元素信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="isBackupMain">是否恢复主备份页面</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// - 隐藏图片选择工具栏
|
|
|
|
|
|
/// - 清空当前画布的墨迹和所有内容
|
|
|
|
|
|
/// - 从时间机器历史记录中恢复页面内容
|
|
|
|
|
|
/// - 恢复多指书写模式状态
|
|
|
|
|
|
/// - 包含异常处理
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void RestoreStrokes(bool isBackupMain = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-08-31 08:51:50 +08:00
|
|
|
|
// 隐藏图片选择工具栏
|
|
|
|
|
|
if (currentSelectedElement != null)
|
|
|
|
|
|
{
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 保存当前编辑模式
|
|
|
|
|
|
var previousEditingMode = inkCanvas.EditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
UnselectElement(currentSelectedElement);
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 恢复编辑模式
|
|
|
|
|
|
inkCanvas.EditingMode = previousEditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
currentSelectedElement = null;
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-07-28 15:47:18 +08:00
|
|
|
|
var targetIndex = isBackupMain ? 0 : CurrentWhiteboardIndex;
|
|
|
|
|
|
|
2025-07-29 17:38:58 +08:00
|
|
|
|
// 先清空当前画布的墨迹
|
2025-07-28 15:47:18 +08:00
|
|
|
|
inkCanvas.Strokes.Clear();
|
2025-07-29 17:38:58 +08:00
|
|
|
|
|
2025-07-29 17:44:15 +08:00
|
|
|
|
// 清空当前画布的所有内容(墨迹和图片)
|
|
|
|
|
|
// 这里必须清除图片,因为页面切换时需要完全重置画布状态
|
2025-07-28 15:47:18 +08:00
|
|
|
|
inkCanvas.Children.Clear();
|
|
|
|
|
|
|
2025-07-28 16:04:36 +08:00
|
|
|
|
// 如果历史记录为空,直接返回(新页面或空页面)
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (TimeMachineHistories[targetIndex] == null)
|
|
|
|
|
|
{
|
2025-07-28 16:04:36 +08:00
|
|
|
|
timeMachine.ClearStrokeHistory();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (isBackupMain)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[0]);
|
2026-02-22 12:26:43 +08:00
|
|
|
|
var removed0 = CollectRemovedElementsFromHistory(TimeMachineHistories[0]);
|
|
|
|
|
|
foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item, null, removed0);
|
2025-09-20 12:15:21 +08:00
|
|
|
|
RestoreMultiTouchModeState(0);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]);
|
2026-02-22 12:26:43 +08:00
|
|
|
|
var removed = CollectRemovedElementsFromHistory(TimeMachineHistories[CurrentWhiteboardIndex]);
|
|
|
|
|
|
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item, null, removed);
|
2025-09-20 12:15:21 +08:00
|
|
|
|
RestoreMultiTouchModeState(CurrentWhiteboardIndex);
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-07-29 17:38:58 +08:00
|
|
|
|
|
2025-08-30 21:18:34 +08:00
|
|
|
|
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
catch
|
|
|
|
|
|
{
|
2025-07-29 17:44:15 +08:00
|
|
|
|
// ignored
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-20 12:15:21 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 恢复多指书写模式状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void RestoreMultiTouchModeState(int pageIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查是否保存了多指书写模式状态
|
|
|
|
|
|
if (savedMultiTouchModeStates[pageIndex])
|
|
|
|
|
|
{
|
|
|
|
|
|
// 更新UI状态
|
|
|
|
|
|
if (ToggleSwitchEnableMultiTouchMode != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ToggleSwitchEnableMultiTouchMode.IsOn = true;
|
|
|
|
|
|
}
|
2025-10-03 17:08:46 +08:00
|
|
|
|
|
2025-09-20 12:15:21 +08:00
|
|
|
|
LogHelper.WriteLogToFile($"恢复多指书写模式状态 - 页面索引: {pageIndex}", LogHelper.LogType.Info);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 更新UI状态
|
|
|
|
|
|
if (ToggleSwitchEnableMultiTouchMode != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ToggleSwitchEnableMultiTouchMode.IsOn = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogHelper.WriteLogToFile($"恢复多指书写模式状态失败: {ex.Message}", LogHelper.LogType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理白板页面索引按钮点击事件,显示或隐藏侧边页面列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">事件发送者</param>
|
|
|
|
|
|
/// <param name="e">事件参数</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// - 处理左侧页面列表按钮点击:显示或隐藏左侧页面列表
|
|
|
|
|
|
/// - 处理右侧页面列表按钮点击:显示或隐藏右侧页面列表
|
|
|
|
|
|
/// - 显示页面列表时会刷新列表内容并滚动到当前页面
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private async void BtnWhiteBoardPageIndex_Click(object sender, EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (sender == BtnLeftPageListWB)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (BoardBorderLeftPageListView.Visibility == Visibility.Visible)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView);
|
|
|
|
|
|
RefreshBlackBoardSidePageListView();
|
|
|
|
|
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderLeftPageListView);
|
|
|
|
|
|
await Task.Delay(1);
|
2025-09-30 19:15:03 +08:00
|
|
|
|
var leftContainer = BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
|
|
|
|
|
CurrentWhiteboardIndex - 1) as ListViewItem;
|
|
|
|
|
|
if (leftContainer != null)
|
2025-09-20 12:03:30 +08:00
|
|
|
|
{
|
|
|
|
|
|
ScrollViewToVerticalTop(leftContainer, BlackBoardLeftSidePageListScrollViewer);
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else if (sender == BtnRightPageListWB)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
{
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (BoardBorderRightPageListView.Visibility == Visibility.Visible)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView);
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
|
|
|
|
|
RefreshBlackBoardSidePageListView();
|
|
|
|
|
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderRightPageListView);
|
|
|
|
|
|
await Task.Delay(1);
|
2025-09-30 19:15:03 +08:00
|
|
|
|
var rightContainer = BlackBoardRightSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
|
|
|
|
|
CurrentWhiteboardIndex - 1) as ListViewItem;
|
|
|
|
|
|
if (rightContainer != null)
|
2025-09-20 12:03:30 +08:00
|
|
|
|
{
|
|
|
|
|
|
ScrollViewToVerticalTop(rightContainer, BlackBoardRightSidePageListScrollViewer);
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 切换到前一白板页并在切换过程中保存与恢复画布和相关状态(如果当前已是第一页则不执行任何操作)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 该方法在切换前会取消当前选中元素(同时保留并恢复编辑模式)、调用视频呈现器的离开页前钩子、保存当前页的笔迹与元素、清空画布;切换到前一页后恢复该页内容、调用视频呈现器的页已更改钩子并刷新页面索引显示。
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
if (CurrentWhiteboardIndex <= 1) return;
|
|
|
|
|
|
|
2025-08-31 08:51:50 +08:00
|
|
|
|
// 隐藏图片选择工具栏
|
|
|
|
|
|
if (currentSelectedElement != null)
|
|
|
|
|
|
{
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 保存当前编辑模式
|
|
|
|
|
|
var previousEditingMode = inkCanvas.EditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
UnselectElement(currentSelectedElement);
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 恢复编辑模式
|
|
|
|
|
|
inkCanvas.EditingMode = previousEditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
currentSelectedElement = null;
|
|
|
|
|
|
}
|
2025-07-28 15:47:18 +08:00
|
|
|
|
|
2026-02-19 18:24:55 +08:00
|
|
|
|
VideoPresenter_BeforePageLeave();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
SaveStrokes();
|
|
|
|
|
|
|
|
|
|
|
|
ClearStrokes(true);
|
|
|
|
|
|
CurrentWhiteboardIndex--;
|
|
|
|
|
|
|
|
|
|
|
|
RestoreStrokes();
|
2026-02-19 18:24:55 +08:00
|
|
|
|
VideoPresenter_OnPageChanged();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
UpdateIndexInfoDisplay();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 切换到白板的下一页;在到达最后一页时会新增一页。方法在切页前保存当前页面的笔迹/多媒体状态,在切页后恢复目标页面的内容并更新界面状态。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">触发事件的源对象(通常为按钮)。</param>
|
|
|
|
|
|
/// <param name="e">事件参数。</param>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
2026-02-22 12:20:37 +08:00
|
|
|
|
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber)
|
|
|
|
|
|
CaptureAndEnqueueScreenshotSave(isHideNotification: true);
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (CurrentWhiteboardIndex >= WhiteboardTotalCount)
|
|
|
|
|
|
{
|
2025-08-31 08:51:50 +08:00
|
|
|
|
// 在最后一页时,点击"新页面"按钮直接新增一页
|
2025-05-25 09:29:48 +08:00
|
|
|
|
BtnWhiteBoardAdd_Click(sender, e);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-31 08:51:50 +08:00
|
|
|
|
// 隐藏图片选择工具栏
|
|
|
|
|
|
if (currentSelectedElement != null)
|
|
|
|
|
|
{
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 保存当前编辑模式
|
|
|
|
|
|
var previousEditingMode = inkCanvas.EditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
UnselectElement(currentSelectedElement);
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 恢复编辑模式
|
|
|
|
|
|
inkCanvas.EditingMode = previousEditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
currentSelectedElement = null;
|
|
|
|
|
|
}
|
2025-07-28 15:47:18 +08:00
|
|
|
|
|
2026-02-19 18:24:55 +08:00
|
|
|
|
VideoPresenter_BeforePageLeave();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
SaveStrokes();
|
|
|
|
|
|
|
|
|
|
|
|
ClearStrokes(true);
|
|
|
|
|
|
CurrentWhiteboardIndex++;
|
|
|
|
|
|
|
|
|
|
|
|
RestoreStrokes();
|
2026-02-19 18:24:55 +08:00
|
|
|
|
VideoPresenter_OnPageChanged();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
UpdateIndexInfoDisplay();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 在白板集合中添加一个新页面:在切换前保存并清除当前页面的笔迹与状态,插入新空白页面,恢复并刷新与页面相关的 UI 状态。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// - 在达到最大页面数(99)时不执行任何操作。
|
|
|
|
|
|
/// - 在切换前若启用了自动保存且笔迹数量超过阈值,会保存当前画面截图。
|
|
|
|
|
|
/// - 若有选中元素,会取消选中并恢复编辑模式。
|
|
|
|
|
|
/// - 将当前页面的历史保存到时间轴并清空画布,然后在白板集合中插入一个空白页面(其历史为 null),随后恢复该页面并触发页面变更回调。
|
|
|
|
|
|
/// - 更新页码显示并在达到上限时禁用添加按钮;若侧边页列表可见,则刷新该列表。
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void BtnWhiteBoardAdd_Click(object sender, EventArgs e)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
if (WhiteboardTotalCount >= 99) return;
|
|
|
|
|
|
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
2026-02-22 12:20:37 +08:00
|
|
|
|
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber)
|
|
|
|
|
|
CaptureAndEnqueueScreenshotSave(isHideNotification: true);
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2025-08-31 08:51:50 +08:00
|
|
|
|
// 隐藏图片选择工具栏
|
|
|
|
|
|
if (currentSelectedElement != null)
|
|
|
|
|
|
{
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 保存当前编辑模式
|
|
|
|
|
|
var previousEditingMode = inkCanvas.EditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
UnselectElement(currentSelectedElement);
|
2025-08-31 09:12:30 +08:00
|
|
|
|
// 恢复编辑模式
|
|
|
|
|
|
inkCanvas.EditingMode = previousEditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
currentSelectedElement = null;
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2026-02-19 18:24:55 +08:00
|
|
|
|
VideoPresenter_BeforePageLeave();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
SaveStrokes();
|
|
|
|
|
|
ClearStrokes(true);
|
|
|
|
|
|
|
|
|
|
|
|
WhiteboardTotalCount++;
|
|
|
|
|
|
CurrentWhiteboardIndex++;
|
|
|
|
|
|
|
|
|
|
|
|
if (CurrentWhiteboardIndex != WhiteboardTotalCount)
|
|
|
|
|
|
for (var i = WhiteboardTotalCount; i > CurrentWhiteboardIndex; i--)
|
|
|
|
|
|
TimeMachineHistories[i] = TimeMachineHistories[i - 1];
|
|
|
|
|
|
|
2025-07-28 15:47:18 +08:00
|
|
|
|
// 确保新页面的历史记录为空
|
|
|
|
|
|
TimeMachineHistories[CurrentWhiteboardIndex] = null;
|
|
|
|
|
|
|
2025-07-28 16:04:36 +08:00
|
|
|
|
// 恢复新页面(这会清空画布,因为历史记录为null)
|
|
|
|
|
|
RestoreStrokes();
|
2026-02-19 18:24:55 +08:00
|
|
|
|
VideoPresenter_OnPageChanged();
|
2025-07-28 16:04:36 +08:00
|
|
|
|
|
2025-05-25 09:29:48 +08:00
|
|
|
|
UpdateIndexInfoDisplay();
|
|
|
|
|
|
|
|
|
|
|
|
if (WhiteboardTotalCount >= 99) BtnWhiteBoardAdd.IsEnabled = false;
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (BlackBoardLeftSidePageListView.Visibility == Visibility.Visible)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
RefreshBlackBoardSidePageListView();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理白板页面删除按钮点击事件,删除当前白板页面
|
|
|
|
|
|
/// </summary>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void BtnWhiteBoardDelete_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
2026-02-22 12:23:13 +08:00
|
|
|
|
DeleteWhiteBoardPageByIndex(CurrentWhiteboardIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按页码删除指定白板页(用于预览列表等)。仅当总页数大于 1 时有效。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pageIndex">要删除的页码(1 到 WhiteboardTotalCount)</param>
|
|
|
|
|
|
private void DeleteWhiteBoardPageByIndex(int pageIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (WhiteboardTotalCount <= 1 || pageIndex < 1 || pageIndex > WhiteboardTotalCount)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2025-08-31 08:51:50 +08:00
|
|
|
|
if (currentSelectedElement != null)
|
|
|
|
|
|
{
|
2025-08-31 09:12:30 +08:00
|
|
|
|
var previousEditingMode = inkCanvas.EditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
UnselectElement(currentSelectedElement);
|
2025-08-31 09:12:30 +08:00
|
|
|
|
inkCanvas.EditingMode = previousEditingMode;
|
2025-08-31 08:51:50 +08:00
|
|
|
|
currentSelectedElement = null;
|
|
|
|
|
|
}
|
2025-08-31 11:43:52 +08:00
|
|
|
|
|
2026-02-22 12:23:13 +08:00
|
|
|
|
if (pageIndex == CurrentWhiteboardIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClearStrokes(true);
|
2026-02-22 12:42:47 +08:00
|
|
|
|
|
|
|
|
|
|
var oldTotal = WhiteboardTotalCount;
|
|
|
|
|
|
if (CurrentWhiteboardIndex != oldTotal)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (var i = CurrentWhiteboardIndex; i < oldTotal; i++)
|
2026-02-22 12:23:13 +08:00
|
|
|
|
TimeMachineHistories[i] = TimeMachineHistories[i + 1];
|
2026-02-22 12:42:47 +08:00
|
|
|
|
}
|
2026-02-22 12:23:13 +08:00
|
|
|
|
else
|
2026-02-22 12:42:47 +08:00
|
|
|
|
{
|
2026-02-22 12:23:13 +08:00
|
|
|
|
CurrentWhiteboardIndex--;
|
2026-02-22 12:42:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TimeMachineHistories[oldTotal] = null;
|
2026-02-22 12:23:13 +08:00
|
|
|
|
WhiteboardTotalCount--;
|
|
|
|
|
|
RestoreStrokes();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (pageIndex < CurrentWhiteboardIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (var i = pageIndex; i < WhiteboardTotalCount; i++)
|
2025-05-25 09:29:48 +08:00
|
|
|
|
TimeMachineHistories[i] = TimeMachineHistories[i + 1];
|
2026-02-22 12:23:13 +08:00
|
|
|
|
TimeMachineHistories[WhiteboardTotalCount] = null;
|
|
|
|
|
|
WhiteboardTotalCount--;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
CurrentWhiteboardIndex--;
|
2026-02-22 12:23:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
for (var i = pageIndex; i < WhiteboardTotalCount; i++)
|
|
|
|
|
|
TimeMachineHistories[i] = TimeMachineHistories[i + 1];
|
|
|
|
|
|
TimeMachineHistories[WhiteboardTotalCount] = null;
|
|
|
|
|
|
WhiteboardTotalCount--;
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
UpdateIndexInfoDisplay();
|
|
|
|
|
|
if (WhiteboardTotalCount < 99) BtnWhiteBoardAdd.IsEnabled = true;
|
2026-02-22 12:23:13 +08:00
|
|
|
|
if (BoardBorderLeftPageListView?.Visibility == Visibility.Visible ||
|
|
|
|
|
|
BoardBorderRightPageListView?.Visibility == Visibility.Visible)
|
|
|
|
|
|
RefreshBlackBoardSidePageListView();
|
2025-05-25 09:29:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-22 10:14:12 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新白板页码信息显示和按钮状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// - 更新页码显示文本
|
|
|
|
|
|
/// - 设置下一页按钮文本(根据是否为最后一页)
|
|
|
|
|
|
/// - 启用或禁用下一页按钮(根据是否为最后一页和最大页面数)
|
|
|
|
|
|
/// - 设置按钮颜色和透明度
|
|
|
|
|
|
/// - 启用或禁用上一页按钮(根据是否为第一页)
|
|
|
|
|
|
/// - 设置删除按钮状态(根据页面总数)
|
|
|
|
|
|
/// </remarks>
|
2025-08-03 16:46:33 +08:00
|
|
|
|
private void UpdateIndexInfoDisplay()
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
TextBlockWhiteBoardIndexInfo.Text =
|
|
|
|
|
|
$"{CurrentWhiteboardIndex}/{WhiteboardTotalCount}";
|
|
|
|
|
|
|
2025-06-11 10:47:04 +08:00
|
|
|
|
bool isLastPage = CurrentWhiteboardIndex == WhiteboardTotalCount;
|
|
|
|
|
|
bool isMaxPage = WhiteboardTotalCount >= 99;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置按钮文本
|
|
|
|
|
|
BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
|
|
|
|
|
BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
|
|
|
|
|
|
2025-11-15 18:41:42 +08:00
|
|
|
|
if (isLastPage)
|
|
|
|
|
|
{
|
|
|
|
|
|
BtnWhiteBoardSwitchNext.IsEnabled = !isMaxPage;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
BtnWhiteBoardSwitchNext.IsEnabled = true;
|
|
|
|
|
|
}
|
2025-06-11 10:47:04 +08:00
|
|
|
|
|
2025-10-04 21:44:15 +08:00
|
|
|
|
// 获取主题颜色资源
|
|
|
|
|
|
var iconForegroundBrush = Application.Current.FindResource("IconForeground") as SolidColorBrush;
|
2025-10-06 18:29:12 +08:00
|
|
|
|
|
2025-10-04 21:44:15 +08:00
|
|
|
|
// 设置下一页按钮颜色
|
|
|
|
|
|
if (iconForegroundBrush != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
BtnLeftWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
|
|
|
|
|
BtnRightWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
|
|
|
|
|
}
|
2025-06-11 10:47:04 +08:00
|
|
|
|
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1;
|
|
|
|
|
|
BtnRightWhiteBoardSwitchNextLabel.Opacity = 1;
|
2025-05-25 09:29:48 +08:00
|
|
|
|
|
|
|
|
|
|
BtnWhiteBoardSwitchPrevious.IsEnabled = true;
|
|
|
|
|
|
|
2025-08-03 16:46:33 +08:00
|
|
|
|
if (CurrentWhiteboardIndex == 1)
|
|
|
|
|
|
{
|
2025-05-25 09:29:48 +08:00
|
|
|
|
BtnWhiteBoardSwitchPrevious.IsEnabled = false;
|
2025-10-04 21:44:15 +08:00
|
|
|
|
if (iconForegroundBrush != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var disabledBrush = new SolidColorBrush(Color.FromArgb(127, iconForegroundBrush.Color.R, iconForegroundBrush.Color.G, iconForegroundBrush.Color.B));
|
|
|
|
|
|
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = disabledBrush;
|
|
|
|
|
|
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = disabledBrush;
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
|
|
|
|
|
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
2025-08-03 16:46:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-10-04 21:44:15 +08:00
|
|
|
|
if (iconForegroundBrush != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
|
|
|
|
|
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
|
|
|
|
|
}
|
2025-05-25 09:29:48 +08:00
|
|
|
|
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
|
|
|
|
|
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BtnWhiteBoardDelete.IsEnabled = WhiteboardTotalCount != 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|