From baa9e1003ef1154e3dc852103e37e9e2b7dc2cc7 Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sun, 27 Jul 2025 22:00:37 +0800 Subject: [PATCH 1/9] fix:issue #93 --- .../MainWindow_cs/MW_FloatingBarIcons.cs | 6 ++ Ink Canvas/MainWindow_cs/MW_Settings.cs | 12 +++- Ink Canvas/MainWindow_cs/MW_TouchEvents.cs | 67 ++++++++++++++++++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 37fcd6cf..251d0b0e 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -1726,7 +1726,11 @@ namespace Ink_Canvas { } ClearStrokes(false); + // 保存非笔画元素(如图片) + var preservedElements = PreserveNonStrokeElements(); inkCanvas.Children.Clear(); + // 恢复非笔画元素 + RestoreNonStrokeElements(preservedElements); CancelSingleFingerDragMode(); @@ -2021,5 +2025,7 @@ namespace Ink_Canvas { } } } + + } } diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index bffec901..93f84f04 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -1507,18 +1507,22 @@ namespace Ink_Canvas { InkCanvasEditingMode currentEditingMode = inkCanvas.EditingMode; int currentDrawingShapeMode = drawingShapeMode; bool currentForceEraser = forceEraser; - + inkCanvas.StylusDown -= MainWindow_StylusDown; inkCanvas.StylusMove -= MainWindow_StylusMove; inkCanvas.StylusUp -= MainWindow_StylusUp; inkCanvas.TouchDown -= MainWindow_TouchDown; inkCanvas.TouchDown += Main_Grid_TouchDown; - + // 先设为None再设回原来的模式,避免可能的事件冲突 inkCanvas.EditingMode = InkCanvasEditingMode.None; + // 保存非笔画元素(如图片) + var preservedElements = PreserveNonStrokeElements(); inkCanvas.Children.Clear(); + // 恢复非笔画元素 + RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = false; - + // 恢复到之前的编辑状态 inkCanvas.EditingMode = currentEditingMode; drawingShapeMode = currentDrawingShapeMode; @@ -2247,5 +2251,7 @@ namespace Ink_Canvas { Settings.Canvas.EnablePalmEraser = ToggleSwitchEnablePalmEraser.IsOn; SaveSettingsToFile(); } + + } } diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index 0212112c..7f803d2d 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -21,6 +21,46 @@ namespace Ink_Canvas { private Point centerPoint = new Point(0, 0); private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink; + /// + /// 保存画布上的非笔画元素(如图片、媒体元素等) + /// + private List PreserveNonStrokeElements() + { + var preservedElements = new List(); + + // 遍历inkCanvas的所有子元素 + for (int i = inkCanvas.Children.Count - 1; i >= 0; i--) + { + var child = inkCanvas.Children[i]; + + // 保存图片、媒体元素等非笔画相关的UI元素 + if (child is Image || child is MediaElement || + (child is Border border && border.Name != "AdvancedEraserOverlay")) + { + preservedElements.Add(child); + } + } + + return preservedElements; + } + + /// + /// 恢复之前保存的非笔画元素到画布 + /// + private void RestoreNonStrokeElements(List preservedElements) + { + if (preservedElements == null) return; + + foreach (var element in preservedElements) + { + // 确保元素没有父容器再添加到inkCanvas + if (element is FrameworkElement fe && fe.Parent == null) + { + inkCanvas.Children.Add(element); + } + } + } + private void BorderMultiTouchMode_MouseUp(object sender, MouseButtonEventArgs e) { if (isInMultiTouchMode) { inkCanvas.StylusDown -= MainWindow_StylusDown; @@ -31,12 +71,16 @@ namespace Ink_Canvas { if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) { inkCanvas.EditingMode = InkCanvasEditingMode.Ink; } + // 保存非笔画元素(如图片) + var preservedElements = PreserveNonStrokeElements(); inkCanvas.Children.Clear(); + // 恢复非笔画元素 + RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = false; - + } else { - + inkCanvas.StylusDown += MainWindow_StylusDown; inkCanvas.StylusMove += MainWindow_StylusMove; inkCanvas.StylusUp += MainWindow_StylusUp; @@ -45,7 +89,11 @@ namespace Ink_Canvas { if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) { inkCanvas.EditingMode = InkCanvasEditingMode.None; } + // 保存非笔画元素(如图片) + var preservedElements = PreserveNonStrokeElements(); inkCanvas.Children.Clear(); + // 恢复非笔画元素 + RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = true; } } @@ -118,7 +166,12 @@ namespace Ink_Canvas { VisualCanvasList.Remove(e.StylusDevice.Id); TouchDownPointsList.Remove(e.StylusDevice.Id); if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0) { - inkCanvas.Children.Clear(); + // 只清除手写笔预览相关的Canvas,不清除所有子元素 + foreach (var canvas in VisualCanvasList.Values.ToList()) { + if (inkCanvas.Children.Contains(canvas)) { + inkCanvas.Children.Remove(canvas); + } + } StrokeVisualList.Clear(); VisualCanvasList.Clear(); TouchDownPointsList.Clear(); @@ -446,7 +499,11 @@ namespace Ink_Canvas { { inkCanvas.EditingMode = InkCanvasEditingMode.Ink; } + // 保存非笔画元素(如图片) + var preservedElements = PreserveNonStrokeElements(); inkCanvas.Children.Clear(); + // 恢复非笔画元素 + RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = false; // 关闭多指书写时,恢复手掌擦开关 if (palmEraserWasEnabledBeforeMultiTouch) { @@ -471,7 +528,11 @@ namespace Ink_Canvas { { inkCanvas.EditingMode = InkCanvasEditingMode.None; } + // 保存非笔画元素(如图片) + var preservedElements = PreserveNonStrokeElements(); inkCanvas.Children.Clear(); + // 恢复非笔画元素 + RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = true; // 启用多指书写时,自动禁用手掌擦 palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser; From b2ef8b96ef616f9a7ce8bf0e9ff1855a6443968b Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sun, 27 Jul 2025 22:52:04 +0800 Subject: [PATCH 2/9] fix:issue #93 --- Ink Canvas/MainWindow.xaml | 6 ++++ Ink Canvas/MainWindow_cs/MW_BoardIcons.cs | 32 +++++++++++++++++++ Ink Canvas/MainWindow_cs/MW_Settings.cs | 7 ++++ Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 1 + Ink Canvas/Resources/Settings.cs | 2 ++ 5 files changed, 48 insertions(+) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index eb456c64..ca788761 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -774,6 +774,12 @@ FontFamily="Microsoft YaHei UI" FontWeight="Bold" Toggled="ToggleSwitchClearCanvasAndClearTimeMachine_Toggled" /> + + + + Date: Mon, 28 Jul 2025 00:48:50 +0800 Subject: [PATCH 3/9] fix:issue #93 --- Ink Canvas/Resources/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index eb9428fb..3831f086 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -88,7 +88,7 @@ namespace Ink_Canvas [JsonProperty("enablePalmEraser")] public bool EnablePalmEraser { get; set; } = true; [JsonProperty("clearCanvasAlsoClearImages")] - public bool ClearCanvasAlsoClearImages { get; set; } = false; + public bool ClearCanvasAlsoClearImages { get; set; } = true; } public enum OptionalOperation From 7a141822e71a21f53f4a422751648481d93a9ab8 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Mon, 28 Jul 2025 01:49:54 +0800 Subject: [PATCH 4/9] fix:issue #93 --- Ink Canvas/MainWindow_cs/MW_BoardControls.cs | 17 +++++++++++++++++ Ink Canvas/MainWindow_cs/MW_TimeMachine.cs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index 3e03157d..d52fed64 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -29,6 +29,15 @@ namespace Ink_Canvas { TimeMachineHistories[0] = timeMachineHistory; timeMachine.ClearStrokeHistory(); } else { + // 新增:提交所有图片元素到时间机器历史记录 + foreach (var child in inkCanvas.Children) + { + if (child is Image img) + { + timeMachine.CommitElementInsertHistory(img); + } + } + var timeMachineHistory = timeMachine.ExportTimeMachineHistory(); TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory; timeMachine.ClearStrokeHistory(); @@ -71,6 +80,14 @@ namespace Ink_Canvas { foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item); } else { timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]); + // 新增:通过时间机器历史恢复图片 + foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) + { + if (item.CommitType == TimeMachineHistoryType.ElementInsert) + { + inkCanvas.Children.Add(item.InsertedElement); + } + } foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item); // 恢复当前页图片信息 inkCanvas.Children.Clear(); diff --git a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs index 3774ddfa..7a9346b5 100644 --- a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs +++ b/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs @@ -130,7 +130,7 @@ namespace Ink_Canvas { canvas.Strokes.Remove(currentStroke); } } else if (item.CommitType == TimeMachineHistoryType.ElementInsert) { - if (!item.StrokeHasBeenCleared) { + if (item.StrokeHasBeenCleared) { // Undo: 移除元素 if (item.InsertedElement != null && inkCanvas.Children.Contains(item.InsertedElement)) inkCanvas.Children.Remove(item.InsertedElement); From 41409c39d5fcef025c2e6bac64bf38801377c3e1 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 18:05:03 +0000 Subject: [PATCH 5/9] docs: update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 425438b7..06ba1be2 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,9 @@ MKStoler1024
MKStoler1024

📖 💻 🎨 Awesome Iwb
Awesome Iwb

📖 + + PrefacedCorg
PrefacedCorg

💻 + From d03564bce95ffce4a856a597267748c465bb56aa Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 18:05:04 +0000 Subject: [PATCH 6/9] docs: update .all-contributorsrc --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1cbd9444..41dcfa6e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -79,6 +79,15 @@ "contributions": [ "doc" ] + }, + { + "login": "PrefacedCorg", + "name": "PrefacedCorg", + "avatar_url": "https://avatars.githubusercontent.com/u/129855423?v=4", + "profile": "https://github.com/PrefacedCorg", + "contributions": [ + "code" + ] } ] } From 5d42a8957e885cbbcfb5214e3d03890f7512170b Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Mon, 28 Jul 2025 11:15:35 +0800 Subject: [PATCH 7/9] fix:issue #93 --- Ink Canvas/MainWindow.xaml.cs | 3 + .../MainWindow_cs/MW_ElementsControls.cs | 12 + .../MainWindow_cs/MW_SelectionGestures.cs | 389 ++++++++++++++++++ 3 files changed, 404 insertions(+) diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 3fedee0b..f0456dca 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -404,6 +404,9 @@ namespace Ink_Canvas { // 确保开关和设置同步 ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode; ApplyNoFocusMode(); + + // 初始化UIElement选择系统 + InitializeUIElementSelection(); } private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e) { diff --git a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs index a2dfe888..9b725971 100644 --- a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; +using System.Windows.Input; using Microsoft.Win32; namespace Ink_Canvas @@ -41,6 +42,10 @@ namespace Ink_Canvas inkCanvas.Children.Add(image); + // 添加鼠标事件处理,使图片可以被选择 + image.MouseDown += UIElement_MouseDown; + image.IsManipulationEnabled = true; + timeMachine.CommitElementInsertHistory(image); } } @@ -72,6 +77,9 @@ namespace Ink_Canvas int height = bitmapImage.PixelHeight; Image image = new Image(); + // 设置拉伸模式为Fill,支持任意比例缩放 + image.Stretch = Stretch.Fill; + if (isLoaded && Settings.Canvas.IsCompressPicturesUploaded && (width > 1920 || height > 1080)) { double scaleX = 1920.0 / width; @@ -116,6 +124,10 @@ namespace Ink_Canvas InkCanvas.SetTop(mediaElement, 0); inkCanvas.Children.Add(mediaElement); + // 添加鼠标事件处理,使媒体元素可以被选择 + mediaElement.MouseDown += UIElement_MouseDown; + mediaElement.IsManipulationEnabled = true; + mediaElement.LoadedBehavior = MediaState.Manual; mediaElement.UnloadedBehavior = MediaState.Manual; mediaElement.Loaded += async (_, args) => diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs index ea33faef..ffc84b20 100644 --- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs +++ b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs @@ -6,6 +6,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 { @@ -272,12 +273,31 @@ namespace Ink_Canvas { if (isProgramChangeStrokeSelection) return; if (inkCanvas.GetSelectedStrokes().Count == 0) { GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + // 当没有选中笔画时,检查是否有选中的UIElement + CheckUIElementSelection(); } else { GridInkCanvasSelectionCover.Visibility = Visibility.Visible; BorderStrokeSelectionClone.Background = Brushes.Transparent; isStrokeSelectionCloneOn = false; updateBorderStrokeSelectionControlLocation(); + // 当选中笔画时,取消UIElement选择 + DeselectUIElement(); + } + } + + private void CheckUIElementSelection() + { + // 检查InkCanvas中的UIElement是否被选中 + var selectedElements = inkCanvas.GetSelectedElements(); + if (selectedElements.Count > 0) + { + var element = selectedElements[0]; + SelectUIElement(element); + } + else + { + DeselectUIElement(); } } @@ -436,5 +456,374 @@ namespace Ink_Canvas { inkCanvas.IsManipulationEnabled = true; SetCursorBasedOnEditingMode(inkCanvas); } + + #region UIElement Selection and Resize + + private UIElement selectedUIElement = null; + private System.Windows.Controls.Canvas resizeHandlesCanvas = null; + private readonly List resizeHandles = new List(); + private bool isResizing = false; + private ResizeDirection currentResizeDirection = ResizeDirection.None; + private Point resizeStartPoint; + private Rect originalElementBounds; + + private enum ResizeDirection + { + None, + TopLeft, + TopCenter, + TopRight, + MiddleLeft, + MiddleRight, + BottomLeft, + BottomCenter, + BottomRight + } + + private void InitializeUIElementSelection() + { + // 创建拖拽手柄画布 + if (resizeHandlesCanvas == null) + { + resizeHandlesCanvas = new System.Windows.Controls.Canvas + { + Background = Brushes.Transparent, + IsHitTestVisible = true, + Visibility = Visibility.Collapsed + }; + + // 将手柄画布添加到主网格中,确保它在InkCanvas之上 + var mainGrid = inkCanvas.Parent as Grid; + if (mainGrid != null) + { + mainGrid.Children.Add(resizeHandlesCanvas); + Panel.SetZIndex(resizeHandlesCanvas, 1000); // 确保在最上层 + } + } + + // 创建8个拖拽手柄 + CreateResizeHandles(); + } + + private void CreateResizeHandles() + { + resizeHandles.Clear(); + resizeHandlesCanvas.Children.Clear(); + + var directions = new[] + { + ResizeDirection.TopLeft, ResizeDirection.TopCenter, ResizeDirection.TopRight, + ResizeDirection.MiddleLeft, ResizeDirection.MiddleRight, + ResizeDirection.BottomLeft, ResizeDirection.BottomCenter, ResizeDirection.BottomRight + }; + + foreach (var direction in directions) + { + var handle = new Rectangle + { + Width = 12, + Height = 12, + Fill = Brushes.White, + Stroke = Brushes.DodgerBlue, + StrokeThickness = 2, + Cursor = GetCursorForDirection(direction), + Tag = direction + }; + + handle.MouseDown += ResizeHandle_MouseDown; + handle.MouseMove += ResizeHandle_MouseMove; + handle.MouseUp += ResizeHandle_MouseUp; + + resizeHandles.Add(handle); + resizeHandlesCanvas.Children.Add(handle); + } + } + + private Cursor GetCursorForDirection(ResizeDirection direction) + { + switch (direction) + { + case ResizeDirection.TopLeft: + case ResizeDirection.BottomRight: + return Cursors.SizeNWSE; + case ResizeDirection.TopRight: + case ResizeDirection.BottomLeft: + return Cursors.SizeNESW; + case ResizeDirection.TopCenter: + case ResizeDirection.BottomCenter: + return Cursors.SizeNS; + case ResizeDirection.MiddleLeft: + case ResizeDirection.MiddleRight: + return Cursors.SizeWE; + default: + return Cursors.Arrow; + } + } + + private void SelectUIElement(UIElement element) + { + if (selectedUIElement == element) return; + + // 取消之前的选择 + DeselectUIElement(); + + // 清除笔画选择 + if (inkCanvas.GetSelectedStrokes().Count > 0) + { + isProgramChangeStrokeSelection = true; + inkCanvas.Select(new StrokeCollection()); + isProgramChangeStrokeSelection = false; + } + + selectedUIElement = element; + + if (element != null) + { + // 初始化选择系统(如果还没有初始化) + if (resizeHandlesCanvas == null) + { + InitializeUIElementSelection(); + } + + // 显示拖拽手柄 + ShowResizeHandles(); + } + } + + private void DeselectUIElement() + { + selectedUIElement = null; + HideResizeHandles(); + } + + private void ShowResizeHandles() + { + if (selectedUIElement == null || resizeHandlesCanvas == null) return; + + var bounds = GetUIElementBounds(selectedUIElement); + UpdateResizeHandlesPosition(bounds); + resizeHandlesCanvas.Visibility = Visibility.Visible; + } + + private void HideResizeHandles() + { + if (resizeHandlesCanvas != null) + { + resizeHandlesCanvas.Visibility = Visibility.Collapsed; + } + } + + private Rect GetUIElementBounds(UIElement element) + { + var left = InkCanvas.GetLeft(element); + var top = InkCanvas.GetTop(element); + + if (double.IsNaN(left)) left = 0; + if (double.IsNaN(top)) top = 0; + + var width = 0.0; + var height = 0.0; + + if (element is FrameworkElement fe) + { + width = fe.ActualWidth > 0 ? fe.ActualWidth : fe.Width; + height = fe.ActualHeight > 0 ? fe.ActualHeight : fe.Height; + } + + return new Rect(left, top, width, height); + } + + private void UpdateResizeHandlesPosition(Rect bounds) + { + if (resizeHandles.Count != 8) return; + + var handleSize = 12.0; + var halfHandle = handleSize / 2; + + // 计算手柄位置 + var positions = new[] + { + new Point(bounds.Left - halfHandle, bounds.Top - halfHandle), // TopLeft + new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Top - halfHandle), // TopCenter + new Point(bounds.Right - halfHandle, bounds.Top - halfHandle), // TopRight + new Point(bounds.Left - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleLeft + new Point(bounds.Right - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleRight + new Point(bounds.Left - halfHandle, bounds.Bottom - halfHandle), // BottomLeft + new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Bottom - halfHandle), // BottomCenter + new Point(bounds.Right - halfHandle, bounds.Bottom - halfHandle) // BottomRight + }; + + for (int i = 0; i < resizeHandles.Count && i < positions.Length; i++) + { + System.Windows.Controls.Canvas.SetLeft(resizeHandles[i], positions[i].X); + System.Windows.Controls.Canvas.SetTop(resizeHandles[i], positions[i].Y); + } + } + + private void ResizeHandle_MouseDown(object sender, MouseButtonEventArgs e) + { + if (selectedUIElement == null) return; + + var handle = sender as Rectangle; + if (handle?.Tag is ResizeDirection direction) + { + isResizing = true; + currentResizeDirection = direction; + resizeStartPoint = e.GetPosition(inkCanvas); + originalElementBounds = GetUIElementBounds(selectedUIElement); + + handle.CaptureMouse(); + e.Handled = true; + } + } + + private void ResizeHandle_MouseMove(object sender, MouseEventArgs e) + { + if (!isResizing || selectedUIElement == null) return; + + var currentPoint = e.GetPosition(inkCanvas); + var deltaX = currentPoint.X - resizeStartPoint.X; + var deltaY = currentPoint.Y - resizeStartPoint.Y; + + ResizeUIElement(deltaX, deltaY); + e.Handled = true; + } + + private void ResizeHandle_MouseUp(object sender, MouseButtonEventArgs e) + { + if (isResizing) + { + isResizing = false; + currentResizeDirection = ResizeDirection.None; + + var handle = sender as Rectangle; + handle?.ReleaseMouseCapture(); + e.Handled = true; + } + } + + private void ResizeUIElement(double deltaX, double deltaY) + { + if (selectedUIElement == null) return; + + var newBounds = originalElementBounds; + const double minSize = 20.0; + + switch (currentResizeDirection) + { + case ResizeDirection.TopLeft: + var newWidth = originalElementBounds.Width - deltaX; + var newHeight = originalElementBounds.Height - deltaY; + if (newWidth >= minSize && newHeight >= minSize) + { + newBounds.X = originalElementBounds.X + deltaX; + newBounds.Y = originalElementBounds.Y + deltaY; + newBounds.Width = newWidth; + newBounds.Height = newHeight; + } + break; + + case ResizeDirection.TopCenter: + var newHeightTC = originalElementBounds.Height - deltaY; + if (newHeightTC >= minSize) + { + newBounds.Y = originalElementBounds.Y + deltaY; + newBounds.Height = newHeightTC; + } + break; + + case ResizeDirection.TopRight: + var newWidthTR = originalElementBounds.Width + deltaX; + var newHeightTR = originalElementBounds.Height - deltaY; + if (newWidthTR >= minSize && newHeightTR >= minSize) + { + newBounds.Y = originalElementBounds.Y + deltaY; + newBounds.Width = newWidthTR; + newBounds.Height = newHeightTR; + } + break; + + case ResizeDirection.MiddleLeft: + var newWidthML = originalElementBounds.Width - deltaX; + if (newWidthML >= minSize) + { + newBounds.X = originalElementBounds.X + deltaX; + newBounds.Width = newWidthML; + } + break; + + case ResizeDirection.MiddleRight: + var newWidthMR = originalElementBounds.Width + deltaX; + if (newWidthMR >= minSize) + { + newBounds.Width = newWidthMR; + } + break; + + case ResizeDirection.BottomLeft: + var newWidthBL = originalElementBounds.Width - deltaX; + var newHeightBL = originalElementBounds.Height + deltaY; + if (newWidthBL >= minSize && newHeightBL >= minSize) + { + newBounds.X = originalElementBounds.X + deltaX; + newBounds.Width = newWidthBL; + newBounds.Height = newHeightBL; + } + break; + + case ResizeDirection.BottomCenter: + var newHeightBC = originalElementBounds.Height + deltaY; + if (newHeightBC >= minSize) + { + newBounds.Height = newHeightBC; + } + break; + + case ResizeDirection.BottomRight: + var newWidthBR = originalElementBounds.Width + deltaX; + var newHeightBR = originalElementBounds.Height + deltaY; + if (newWidthBR >= minSize && newHeightBR >= minSize) + { + newBounds.Width = newWidthBR; + newBounds.Height = newHeightBR; + } + break; + } + + // 应用新的尺寸和位置 + ApplyUIElementBounds(selectedUIElement, newBounds); + + // 更新手柄位置 + UpdateResizeHandlesPosition(newBounds); + } + + private void ApplyUIElementBounds(UIElement element, Rect bounds) + { + InkCanvas.SetLeft(element, bounds.X); + InkCanvas.SetTop(element, bounds.Y); + + if (element is FrameworkElement fe) + { + fe.Width = bounds.Width; + fe.Height = bounds.Height; + } + } + + private void UIElement_MouseDown(object sender, MouseButtonEventArgs e) + { + if (inkCanvas.EditingMode == InkCanvasEditingMode.Select) + { + var element = sender as UIElement; + if (element != null) + { + // 切换到选择模式并选择这个元素 + inkCanvas.Select(new UIElement[] { element }); + SelectUIElement(element); + e.Handled = true; + } + } + } + + #endregion } } \ No newline at end of file From b5d9e21f3740512cf84b4502345a6df6e406288d Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Mon, 28 Jul 2025 11:35:47 +0800 Subject: [PATCH 8/9] =?UTF-8?q?improve:=E7=94=A8=E6=88=B7=E4=BD=93?= =?UTF-8?q?=E9=AA=8C=E5=88=86=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/Helpers/DeviceIdentifier.cs | 428 ++++++++++++++++++++----- Ink Canvas/MainWindow.xaml | 1 - Ink Canvas/MainWindow.xaml.cs | 21 +- 3 files changed, 348 insertions(+), 102 deletions(-) diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs index e1040389..5802522c 100644 --- a/Ink Canvas/Helpers/DeviceIdentifier.cs +++ b/Ink Canvas/Helpers/DeviceIdentifier.cs @@ -447,7 +447,7 @@ namespace Ink_Canvas.Helpers } /// - /// 使用频率统计数据结构 + /// 使用频率统计数据结构(优化至秒级精度) /// private class UsageStats { @@ -460,10 +460,20 @@ namespace Ink_Canvas.Helpers [JsonProperty("launchCount")] public int LaunchCount { get; set; } + // 新的秒级精度字段 + [JsonProperty("totalUsageSeconds")] + public long TotalUsageSeconds { get; set; } + + [JsonProperty("averageSessionSeconds")] + public double AverageSessionSeconds { get; set; } + + // 保留旧字段以保持向后兼容性(已弃用) [JsonProperty("totalUsageMinutes")] + [Obsolete("已弃用,请使用 TotalUsageSeconds")] public long TotalUsageMinutes { get; set; } [JsonProperty("averageSessionMinutes")] + [Obsolete("已弃用,请使用 AverageSessionSeconds")] public double AverageSessionMinutes { get; set; } [JsonProperty("lastUpdateCheck")] @@ -481,12 +491,12 @@ namespace Ink_Canvas.Helpers [JsonProperty("lastModified")] public DateTime LastModified { get; set; } - // 每周统计数据 + // 每周统计数据(秒级精度) [JsonProperty("weeklyLaunchCount")] public int WeeklyLaunchCount { get; set; } - [JsonProperty("weeklyUsageMinutes")] - public long WeeklyUsageMinutes { get; set; } + [JsonProperty("weeklyUsageSeconds")] + public long WeeklyUsageSeconds { get; set; } [JsonProperty("weekStartDate")] public DateTime WeekStartDate { get; set; } @@ -494,11 +504,58 @@ namespace Ink_Canvas.Helpers [JsonProperty("lastWeekLaunchCount")] public int LastWeekLaunchCount { get; set; } + [JsonProperty("lastWeekUsageSeconds")] + public long LastWeekUsageSeconds { get; set; } + + // 保留旧字段以保持向后兼容性(已弃用) + [JsonProperty("weeklyUsageMinutes")] + [Obsolete("已弃用,请使用 WeeklyUsageSeconds")] + public long WeeklyUsageMinutes { get; set; } + [JsonProperty("lastWeekUsageMinutes")] + [Obsolete("已弃用,请使用 LastWeekUsageSeconds")] public long LastWeekUsageMinutes { get; set; } /// - /// 检查并重置每周统计数据 + /// 数据迁移:从分钟精度迁移到秒级精度 + /// + public void MigrateToSecondsPrecision() + { + try + { + // 如果新字段为空但旧字段有数据,进行迁移 + if (TotalUsageSeconds == 0 && TotalUsageMinutes > 0) + { + TotalUsageSeconds = TotalUsageMinutes * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移总使用时长: {TotalUsageMinutes}分钟 -> {TotalUsageSeconds}秒"); + } + + if (AverageSessionSeconds == 0 && AverageSessionMinutes > 0) + { + AverageSessionSeconds = AverageSessionMinutes * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移平均会话时长: {AverageSessionMinutes}分钟 -> {AverageSessionSeconds}秒"); + } + + if (WeeklyUsageSeconds == 0 && WeeklyUsageMinutes > 0) + { + WeeklyUsageSeconds = WeeklyUsageMinutes * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移每周使用时长: {WeeklyUsageMinutes}分钟 -> {WeeklyUsageSeconds}秒"); + } + + if (LastWeekUsageSeconds == 0 && LastWeekUsageMinutes > 0) + { + LastWeekUsageSeconds = LastWeekUsageMinutes * 60; + LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移上周使用时长: {LastWeekUsageMinutes}分钟 -> {LastWeekUsageSeconds}秒"); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 数据迁移失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 检查并重置每周统计数据(秒级精度) /// public void CheckAndResetWeeklyStats() { @@ -510,14 +567,18 @@ namespace Ink_Canvas.Helpers { // 保存上周数据 LastWeekLaunchCount = WeeklyLaunchCount; - LastWeekUsageMinutes = WeeklyUsageMinutes; + LastWeekUsageSeconds = WeeklyUsageSeconds; + + // 同时更新旧字段以保持兼容性 + LastWeekUsageMinutes = LastWeekUsageSeconds / 60; // 重置本周数据 WeeklyLaunchCount = 0; + WeeklyUsageSeconds = 0; WeeklyUsageMinutes = 0; WeekStartDate = currentWeekStart; - LogHelper.WriteLogToFile($"DeviceIdentifier | 每周统计重置 - 上周启动: {LastWeekLaunchCount}次, 上周使用: {LastWeekUsageMinutes}分钟"); + LogHelper.WriteLogToFile($"DeviceIdentifier | 每周统计重置 - 上周启动: {LastWeekLaunchCount}次, 上周使用: {FormatDuration(LastWeekUsageSeconds)}"); } } @@ -541,20 +602,25 @@ namespace Ink_Canvas.Helpers } /// - /// 记录本周的使用时长 + /// 记录本周的使用时长(秒级精度) /// - public void RecordWeeklyUsage(long minutes) + public void RecordWeeklyUsage(long seconds) { CheckAndResetWeeklyStats(); - WeeklyUsageMinutes += minutes; + WeeklyUsageSeconds += seconds; + // 同时更新旧字段以保持兼容性 + WeeklyUsageMinutes = WeeklyUsageSeconds / 60; } + + /// - /// 计算数据哈希值用于完整性验证 + /// 计算数据哈希值用于完整性验证(秒级精度) /// public void UpdateDataHash() { - var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}"; + // 使用秒级精度数据计算哈希 + var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}"; using (var sha256 = SHA256.Create()) { var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); @@ -564,17 +630,30 @@ namespace Ink_Canvas.Helpers } /// - /// 验证数据完整性 + /// 验证数据完整性(秒级精度) /// public bool VerifyDataIntegrity() { try { - var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}"; + // 首先尝试使用秒级精度验证 + var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}"; using (var sha256 = SHA256.Create()) { var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); var expectedHash = Convert.ToBase64String(hashBytes); + if (DataHash == expectedHash) + { + return true; + } + } + + // 如果秒级精度验证失败,尝试使用旧的分钟精度验证(向后兼容) + var oldDataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}"; + using (var sha256 = SHA256.Create()) + { + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(oldDataString)); + var expectedHash = Convert.ToBase64String(hashBytes); return DataHash == expectedHash; } } @@ -585,6 +664,37 @@ namespace Ink_Canvas.Helpers } } + /// + /// 格式化时长显示(秒级精度) + /// + /// 总秒数 + /// 格式化的时长字符串 + public static string FormatDuration(long totalSeconds) + { + if (totalSeconds < 60) + { + return $"{totalSeconds}秒"; + } + else if (totalSeconds < 3600) + { + var minutes = totalSeconds / 60; + var seconds = totalSeconds % 60; + return seconds > 0 ? $"{minutes}分{seconds}秒" : $"{minutes}分钟"; + } + else + { + var hours = totalSeconds / 3600; + var minutes = (totalSeconds % 3600) / 60; + var seconds = totalSeconds % 60; + + var result = $"{hours}小时"; + if (minutes > 0) result += $"{minutes}分"; + if (seconds > 0) result += $"{seconds}秒"; + + return result; + } + } + /// /// 更新推送优先级枚举 /// @@ -640,7 +750,7 @@ namespace Ink_Canvas.Helpers } /// - /// 记录应用退出(计算使用时长) + /// 记录应用退出(计算使用时长 - 秒级精度) /// public static void RecordAppExit() { @@ -650,21 +760,31 @@ namespace Ink_Canvas.Helpers { var stats = LoadUsageStats(); - // 计算本次会话时长 - long sessionMinutes = 0; + // 执行数据迁移(如果需要) + stats.MigrateToSecondsPrecision(); + + // 计算本次会话时长(秒级精度) + long sessionSeconds = 0; if (stats.LastLaunchTime != DateTime.MinValue) { var sessionDuration = DateTime.Now - stats.LastLaunchTime; - sessionMinutes = (long)sessionDuration.TotalMinutes; - stats.TotalUsageMinutes += sessionMinutes; + sessionSeconds = (long)sessionDuration.TotalSeconds; - // 记录每周使用时长 - stats.RecordWeeklyUsage(sessionMinutes); + // 更新秒级精度数据 + stats.TotalUsageSeconds += sessionSeconds; - // 更新平均会话时长 + // 同时更新旧字段以保持兼容性 + stats.TotalUsageMinutes = stats.TotalUsageSeconds / 60; + + // 记录每周使用时长(秒级精度) + stats.RecordWeeklyUsage(sessionSeconds); + + // 更新平均会话时长(秒级精度) if (stats.LaunchCount > 0) { - stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; + stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; + // 同时更新旧字段以保持兼容性 + stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; } } @@ -676,7 +796,9 @@ namespace Ink_Canvas.Helpers SaveUsageStats(stats); - LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出 - 本次会话: {sessionMinutes}分钟, 总时长: {stats.TotalUsageMinutes}分钟, 本周时长: {stats.WeeklyUsageMinutes}分钟"); + LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出 - 本次会话: {FormatDuration(sessionSeconds)}, " + + $"总时长: {FormatDuration(stats.TotalUsageSeconds)}, " + + $"本周时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); } } catch (Exception ex) @@ -699,16 +821,28 @@ namespace Ink_Canvas.Helpers // 计算最近活跃度 var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; - // 使用真实的每周数据 + // 使用真实的每周数据(秒级精度) var currentWeekLaunches = stats.WeeklyLaunchCount; - var currentWeekMinutes = stats.WeeklyUsageMinutes; + var currentWeekSeconds = stats.WeeklyUsageSeconds; + + // 如果秒级数据为空但分钟数据存在,进行转换 + if (currentWeekSeconds == 0 && stats.WeeklyUsageMinutes > 0) + { + currentWeekSeconds = stats.WeeklyUsageMinutes * 60; + } // 如果本周数据不足,参考上周数据 var weeklyLaunches = currentWeekLaunches > 0 ? currentWeekLaunches : stats.LastWeekLaunchCount; - var weeklyMinutes = currentWeekMinutes > 0 ? currentWeekMinutes : stats.LastWeekUsageMinutes; + var weeklySeconds = currentWeekSeconds > 0 ? currentWeekSeconds : stats.LastWeekUsageSeconds; + + // 如果秒级数据仍为空,使用分钟数据转换 + if (weeklySeconds == 0 && stats.LastWeekUsageMinutes > 0) + { + weeklySeconds = stats.LastWeekUsageMinutes * 60; + } // 综合评分系统(0-100分) - var frequencyScore = CalculateFrequencyScoreWithWeeklyData(stats, daysSinceLastUse, weeklyLaunches, weeklyMinutes); + var frequencyScore = CalculateFrequencyScoreWithWeeklyData(stats, daysSinceLastUse, weeklyLaunches, weeklySeconds); // 根据综合评分确定频率分类和更新优先级 if (frequencyScore >= 80) @@ -728,7 +862,7 @@ namespace Ink_Canvas.Helpers } LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率计算 - 评分: {frequencyScore}, 频率: {stats.UsageFrequency}, " + - $"优先级: {stats.UpdatePriority}, 本周启动: {currentWeekLaunches}次, 本周时长: {currentWeekMinutes}分钟"); + $"优先级: {stats.UpdatePriority}, 本周启动: {currentWeekLaunches}次, 本周时长: {FormatDuration(currentWeekSeconds)}"); } catch (Exception ex) { @@ -740,16 +874,16 @@ namespace Ink_Canvas.Helpers } /// - /// 基于每周真实数据计算综合频率评分(0-100分) + /// 基于每周真实数据计算综合频率评分(0-100分,秒级精度) /// 评分标准:≥80分=高频用户,40-79分=中频用户,<40分=低频用户 /// /// 使用统计数据 /// 距离最后使用的天数 /// 每周启动次数 - /// 每周使用时长 + /// 每周使用时长(秒) /// 综合评分(0-100分) private static int CalculateFrequencyScoreWithWeeklyData(UsageStats stats, double daysSinceLastUse, - long weeklyLaunches, long weeklyMinutes) + long weeklyLaunches, long weeklySeconds) { var score = 0; @@ -766,16 +900,17 @@ namespace Ink_Canvas.Helpers else if (weeklyLaunches >= 3) score += 15; // 3-4次:中频使用 else if (weeklyLaunches >= 1) score += 10; // 1-2次:低频使用 - // 每周使用时长评分(20分)- 基于真实的每周使用时长 - if (weeklyMinutes >= 600) score += 20; // 10小时以上:重度使用 - else if (weeklyMinutes >= 300) score += 15; // 5-10小时:中重度使用 - else if (weeklyMinutes >= 120) score += 10; // 2-5小时:中度使用 - else if (weeklyMinutes >= 60) score += 5; // 1-2小时:轻度使用 + // 每周使用时长评分(20分)- 基于真实的每周使用时长(秒级精度) + if (weeklySeconds >= 36000) score += 20; // 10小时以上:重度使用 + else if (weeklySeconds >= 18000) score += 15; // 5-10小时:中重度使用 + else if (weeklySeconds >= 7200) score += 10; // 2-5小时:中度使用 + else if (weeklySeconds >= 3600) score += 5; // 1-2小时:轻度使用 - // 历史使用深度评分(10分)- 反映用户的长期使用习惯 - if (stats.TotalUsageMinutes >= 3000) score += 10; // 50小时以上:资深用户 - else if (stats.TotalUsageMinutes >= 1200) score += 7; // 20-50小时:中等用户 - else if (stats.TotalUsageMinutes >= 300) score += 4; // 5-20小时:新手用户 + // 历史使用深度评分(10分)- 反映用户的长期使用习惯(秒级精度) + var totalSeconds = stats.TotalUsageSeconds > 0 ? stats.TotalUsageSeconds : stats.TotalUsageMinutes * 60; + if (totalSeconds >= 180000) score += 10; // 50小时以上:资深用户 + else if (totalSeconds >= 72000) score += 7; // 20-50小时:中等用户 + else if (totalSeconds >= 18000) score += 4; // 5-20小时:新手用户 return Math.Min(100, score); } @@ -817,14 +952,14 @@ namespace Ink_Canvas.Helpers } /// - /// 获取使用统计信息 + /// 获取使用统计信息(秒级精度) /// - public static (int launchCount, long totalMinutes, double avgSession, UpdatePriority priority) GetUsageStats() + public static (int launchCount, long totalSeconds, double avgSessionSeconds, UpdatePriority priority) GetUsageStats() { try { var stats = LoadUsageStats(); - return (stats.LaunchCount, stats.TotalUsageMinutes, stats.AverageSessionMinutes, stats.UpdatePriority); + return (stats.LaunchCount, stats.TotalUsageSeconds, stats.AverageSessionSeconds, stats.UpdatePriority); } catch (Exception ex) { @@ -834,7 +969,27 @@ namespace Ink_Canvas.Helpers } /// - /// 加载使用统计 - 支持多重备份恢复和智能反篡改(强化版本) + /// 获取使用统计信息(兼容性方法 - 分钟精度) + /// + [Obsolete("请使用 GetUsageStats() 获取秒级精度数据")] + public static (int launchCount, long totalMinutes, double avgSessionMinutes, UpdatePriority priority) GetUsageStatsInMinutes() + { + try + { + var stats = LoadUsageStats(); + var totalMinutes = stats.TotalUsageSeconds / 60; + var avgMinutes = stats.AverageSessionSeconds / 60; + return (stats.LaunchCount, totalMinutes, avgMinutes, stats.UpdatePriority); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 获取使用统计失败: {ex.Message}", LogHelper.LogType.Error); + return (0, 0, 0, UpdatePriority.Medium); + } + } + + /// + /// 加载使用统计 - 支持多重备份恢复和智能反篡改 /// private static UsageStats LoadUsageStats() { @@ -851,6 +1006,9 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"DeviceIdentifier | 使用最可信数据源恢复使用统计: {bestData.Source}"); + // 执行数据迁移(如果需要) + bestData.Stats.MigrateToSecondsPrecision(); + // 确保备份同步 SaveUsageStatsToAllLocations(bestData.Stats); return bestData.Stats; @@ -864,6 +1022,10 @@ namespace Ink_Canvas.Helpers if (partiallyRecoveredData != null) { LogHelper.WriteLogToFile($"DeviceIdentifier | 从部分损坏数据中恢复使用统计"); + + // 执行数据迁移(如果需要) + partiallyRecoveredData.MigrateToSecondsPrecision(); + SaveUsageStatsToAllLocations(partiallyRecoveredData); return partiallyRecoveredData; } @@ -873,14 +1035,16 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"DeviceIdentifier | 加载使用统计失败: {ex.Message}", LogHelper.LogType.Error); } - // 返回新的统计对象 + // 返回新的统计对象(秒级精度) var newStats = new UsageStats { DeviceId = DeviceId, LastLaunchTime = DateTime.Now, LaunchCount = 0, - TotalUsageMinutes = 0, - AverageSessionMinutes = 0, + TotalUsageSeconds = 0, + AverageSessionSeconds = 0, + TotalUsageMinutes = 0, // 保持兼容性 + AverageSessionMinutes = 0, // 保持兼容性 LastUpdateCheck = DateTime.MinValue, UpdatePriority = UpdatePriority.Medium, UsageFrequency = UsageFrequency.Medium @@ -1129,8 +1293,9 @@ namespace Ink_Canvas.Helpers UsageFrequency = UsageFrequency.Medium }; - // 使用多数投票或最大值策略恢复关键数据 + // 使用多数投票或最大值策略恢复关键数据(秒级精度) var launchCounts = dataSources.Where(d => d.Stats.LaunchCount > 0).Select(d => d.Stats.LaunchCount).ToList(); + var usageSeconds = dataSources.Where(d => d.Stats.TotalUsageSeconds > 0).Select(d => d.Stats.TotalUsageSeconds).ToList(); var usageMinutes = dataSources.Where(d => d.Stats.TotalUsageMinutes > 0).Select(d => d.Stats.TotalUsageMinutes).ToList(); if (launchCounts.Count > 0) @@ -1138,15 +1303,23 @@ namespace Ink_Canvas.Helpers recoveredStats.LaunchCount = (int)launchCounts.Average(); // 使用平均值 } - if (usageMinutes.Count > 0) + // 优先使用秒级数据,如果没有则使用分钟数据转换 + if (usageSeconds.Count > 0) + { + recoveredStats.TotalUsageSeconds = (long)usageSeconds.Average(); // 使用平均值 + recoveredStats.TotalUsageMinutes = recoveredStats.TotalUsageSeconds / 60; // 兼容性 + } + else if (usageMinutes.Count > 0) { recoveredStats.TotalUsageMinutes = (long)usageMinutes.Average(); // 使用平均值 + recoveredStats.TotalUsageSeconds = recoveredStats.TotalUsageMinutes * 60; // 转换为秒 } - // 重新计算平均会话时长 + // 重新计算平均会话时长(秒级精度) if (recoveredStats.LaunchCount > 0) { - recoveredStats.AverageSessionMinutes = (double)recoveredStats.TotalUsageMinutes / recoveredStats.LaunchCount; + recoveredStats.AverageSessionSeconds = (double)recoveredStats.TotalUsageSeconds / recoveredStats.LaunchCount; + recoveredStats.AverageSessionMinutes = recoveredStats.AverageSessionSeconds / 60; // 兼容性 } // 重新计算使用频率 @@ -1155,7 +1328,7 @@ namespace Ink_Canvas.Helpers // 更新数据完整性哈希 recoveredStats.UpdateDataHash(); - LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复完成 - 启动次数: {recoveredStats.LaunchCount}, 使用时长: {recoveredStats.TotalUsageMinutes}分钟"); + LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复完成 - 启动次数: {recoveredStats.LaunchCount}, 使用时长: {FormatDuration(recoveredStats.TotalUsageSeconds)}"); return recoveredStats; } catch (Exception ex) @@ -1178,19 +1351,32 @@ namespace Ink_Canvas.Helpers { var deviceId = key.GetValue("DeviceId") as string; var launchCount = key.GetValue("LaunchCount"); + + // 秒级精度数据 + var totalSeconds = key.GetValue("TotalUsageSeconds"); + var avgSessionSeconds = key.GetValue("AverageSessionSeconds"); + + // 兼容性:分钟精度数据 var totalMinutes = key.GetValue("TotalUsageMinutes"); + var avgSessionMinutes = key.GetValue("AverageSessionMinutes"); + var lastLaunch = key.GetValue("LastLaunchTime") as string; var priority = key.GetValue("UpdatePriority"); var frequency = key.GetValue("UsageFrequency"); var dataHash = key.GetValue("DataHash") as string; var lastUpdate = key.GetValue("LastUpdate") as string; - // 每周统计数据 + // 每周统计数据(秒级精度) var weeklyLaunchCount = key.GetValue("WeeklyLaunchCount"); + var weeklyUsageSeconds = key.GetValue("WeeklyUsageSeconds"); + var lastWeekUsageSeconds = key.GetValue("LastWeekUsageSeconds"); + + // 兼容性:分钟精度数据 var weeklyUsageMinutes = key.GetValue("WeeklyUsageMinutes"); + var lastWeekUsageMinutes = key.GetValue("LastWeekUsageMinutes"); + var weekStartDate = key.GetValue("WeekStartDate") as string; var lastWeekLaunchCount = key.GetValue("LastWeekLaunchCount"); - var lastWeekUsageMinutes = key.GetValue("LastWeekUsageMinutes"); if (!string.IsNullOrEmpty(deviceId) && launchCount != null) { @@ -1198,26 +1384,42 @@ namespace Ink_Canvas.Helpers { DeviceId = deviceId, LaunchCount = Convert.ToInt32(launchCount), + + // 秒级精度数据 + TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0, + AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0, + + // 兼容性:分钟精度数据 TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0, + AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0, + LastLaunchTime = DateTime.TryParse(lastLaunch, out var dt) ? dt : DateTime.Now, UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, DataHash = dataHash, - AverageSessionMinutes = 0, LastUpdateCheck = DateTime.MinValue, - // 每周统计数据 + // 每周统计数据(秒级精度) WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, + WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0, + LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0, + + // 兼容性:分钟精度数据 WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, + LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0, + WeekStartDate = DateTime.TryParse(weekStartDate, out var wsd) ? wsd : DateTime.MinValue, - LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0, - LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0 + LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0 }; + // 执行数据迁移(如果需要) + stats.MigrateToSecondsPrecision(); + // 重新计算平均会话时长 - if (stats.LaunchCount > 0) + if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0) { - stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; + stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; + stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; } var dataSource = new DataSourceInfo @@ -1260,46 +1462,75 @@ namespace Ink_Canvas.Helpers if (key != null) { var launchCount = key.GetValue("LC"); + + // 秒级精度数据 + var totalSeconds = key.GetValue("TUS"); + var avgSessionSeconds = key.GetValue("ASS"); + + // 兼容性:分钟精度数据 var totalMinutes = key.GetValue("TUM"); + var avgSessionMinutes = key.GetValue("ASM"); + var lastLaunchBinary = key.GetValue("LLT"); var priority = key.GetValue("UP"); var frequency = key.GetValue("UF"); var dataHash = key.GetValue("DH") as string; var lastUpdateBinary = key.GetValue("LU"); - // 每周统计数据 + // 每周统计数据(秒级精度) var weeklyLaunchCount = key.GetValue("WLC"); + var weeklyUsageSeconds = key.GetValue("WUS"); + var lastWeekUsageSeconds = key.GetValue("LWUS"); + + // 兼容性:分钟精度数据 var weeklyUsageMinutes = key.GetValue("WUM"); - var weekStartDateBinary = key.GetValue("WSD"); - var lastWeekLaunchCount = key.GetValue("LWLC"); var lastWeekUsageMinutes = key.GetValue("LWUM"); - if (launchCount != null && totalMinutes != null) + var weekStartDateBinary = key.GetValue("WSD"); + var lastWeekLaunchCount = key.GetValue("LWLC"); + + if (launchCount != null && (totalSeconds != null || totalMinutes != null)) { var stats = new UsageStats { DeviceId = DeviceId, LaunchCount = Convert.ToInt32(launchCount), - TotalUsageMinutes = Convert.ToInt64(totalMinutes), + + // 秒级精度数据 + TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0, + AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0, + + // 兼容性:分钟精度数据 + TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0, + AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0, + LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now, UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, DataHash = dataHash, - AverageSessionMinutes = 0, LastUpdateCheck = DateTime.MinValue, - // 每周统计数据 + // 每周统计数据(秒级精度) WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, + WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0, + LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0, + + // 兼容性:分钟精度数据 WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, + LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0, + WeekStartDate = weekStartDateBinary != null ? DateTime.FromBinary(Convert.ToInt64(weekStartDateBinary)) : DateTime.MinValue, - LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0, - LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0 + LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0 }; + // 执行数据迁移(如果需要) + stats.MigrateToSecondsPrecision(); + // 重新计算平均会话时长 - if (stats.LaunchCount > 0) + if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0) { - stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; + stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; + stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; } var dataSource = new DataSourceInfo @@ -1497,21 +1728,35 @@ namespace Ink_Canvas.Helpers { key.SetValue("DeviceId", stats.DeviceId); key.SetValue("LaunchCount", stats.LaunchCount); + + // 秒级精度数据 + key.SetValue("TotalUsageSeconds", stats.TotalUsageSeconds); + key.SetValue("AverageSessionSeconds", stats.AverageSessionSeconds); + + // 兼容性:分钟精度数据 key.SetValue("TotalUsageMinutes", stats.TotalUsageMinutes); + key.SetValue("AverageSessionMinutes", stats.AverageSessionMinutes); + key.SetValue("LastLaunchTime", stats.LastLaunchTime.ToString("yyyy-MM-dd HH:mm:ss")); key.SetValue("UpdatePriority", (int)stats.UpdatePriority); key.SetValue("UsageFrequency", (int)stats.UsageFrequency); key.SetValue("DataHash", stats.DataHash ?? ""); key.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - // 每周统计数据 + // 每周统计数据(秒级精度) key.SetValue("WeeklyLaunchCount", stats.WeeklyLaunchCount); + key.SetValue("WeeklyUsageSeconds", stats.WeeklyUsageSeconds); + key.SetValue("LastWeekUsageSeconds", stats.LastWeekUsageSeconds); + + // 兼容性:分钟精度数据 key.SetValue("WeeklyUsageMinutes", stats.WeeklyUsageMinutes); - key.SetValue("WeekStartDate", stats.WeekStartDate.ToString("yyyy-MM-dd")); - key.SetValue("LastWeekLaunchCount", stats.LastWeekLaunchCount); key.SetValue("LastWeekUsageMinutes", stats.LastWeekUsageMinutes); - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到主注册表 - 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次, 总时长: {stats.TotalUsageMinutes}分钟, 本周时长: {stats.WeeklyUsageMinutes}分钟"); + key.SetValue("WeekStartDate", stats.WeekStartDate.ToString("yyyy-MM-dd")); + key.SetValue("LastWeekLaunchCount", stats.LastWeekLaunchCount); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到主注册表 - 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次, " + + $"总时长: {FormatDuration(stats.TotalUsageSeconds)}, 本周时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); } else { @@ -1548,21 +1793,34 @@ namespace Ink_Canvas.Helpers { if (key != null) { - // 使用编码的键名来隐藏数据 + // 使用编码的键名来隐藏数据(秒级精度) key.SetValue("LC", stats.LaunchCount); // LaunchCount + + // 秒级精度数据 + key.SetValue("TUS", stats.TotalUsageSeconds); // TotalUsageSeconds + key.SetValue("ASS", stats.AverageSessionSeconds); // AverageSessionSeconds + + // 兼容性:分钟精度数据 key.SetValue("TUM", stats.TotalUsageMinutes); // TotalUsageMinutes + key.SetValue("ASM", stats.AverageSessionMinutes); // AverageSessionMinutes + key.SetValue("LLT", stats.LastLaunchTime.ToBinary()); // LastLaunchTime key.SetValue("UP", (int)stats.UpdatePriority); // UpdatePriority key.SetValue("UF", (int)stats.UsageFrequency); // UsageFrequency key.SetValue("DH", stats.DataHash ?? ""); // DataHash key.SetValue("LU", DateTime.Now.ToBinary()); // LastUpdate - // 每周统计数据 + // 每周统计数据(秒级精度) key.SetValue("WLC", stats.WeeklyLaunchCount); // WeeklyLaunchCount + key.SetValue("WUS", stats.WeeklyUsageSeconds); // WeeklyUsageSeconds + key.SetValue("LWUS", stats.LastWeekUsageSeconds); // LastWeekUsageSeconds + + // 兼容性:分钟精度数据 key.SetValue("WUM", stats.WeeklyUsageMinutes); // WeeklyUsageMinutes + key.SetValue("LWUM", stats.LastWeekUsageMinutes); // LastWeekUsageMinutes + key.SetValue("WSD", stats.WeekStartDate.ToBinary()); // WeekStartDate key.SetValue("LWLC", stats.LastWeekLaunchCount); // LastWeekLaunchCount - key.SetValue("LWUM", stats.LastWeekUsageMinutes); // LastWeekUsageMinutes successCount++; LogHelper.WriteLogToFile($"DeviceIdentifier | 成功保存到备用注册表位置: {path}"); @@ -2013,15 +2271,15 @@ namespace Ink_Canvas.Helpers { try { - var (launchCount, totalMinutes, avgSession, priority) = GetUsageStats(); + var (launchCount, totalSeconds, avgSessionSeconds, priority) = GetUsageStats(); var frequency = GetUsageFrequency(); var stats = LoadUsageStats(); var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; return $"设备ID: {DeviceId}\n" + $"启动次数: {launchCount}\n" + - $"总使用时长: {totalMinutes}分钟 ({totalMinutes / 60.0:F1}小时)\n" + - $"平均会话时长: {avgSession:F1}分钟\n" + + $"总使用时长: {FormatDuration(totalSeconds)}\n" + + $"平均会话时长: {FormatDuration((long)avgSessionSeconds)}\n" + $"使用频率: {frequency}\n" + $"更新优先级: {priority}\n" + $"最后使用: {daysSinceLastUse:F1}天前\n" + @@ -2209,11 +2467,11 @@ namespace Ink_Canvas.Helpers { summary.AppendLine($"数据完整性: {(stats.VerifyDataIntegrity() ? "✓" : "✗")}"); summary.AppendLine($"总启动次数: {stats.LaunchCount}"); - summary.AppendLine($"总使用时长: {stats.TotalUsageMinutes}分钟 ({stats.TotalUsageMinutes / 60.0:F1}小时)"); + summary.AppendLine($"总使用时长: {FormatDuration(stats.TotalUsageSeconds)}"); summary.AppendLine($"本周启动次数: {stats.WeeklyLaunchCount}"); - summary.AppendLine($"本周使用时长: {stats.WeeklyUsageMinutes}分钟 ({stats.WeeklyUsageMinutes / 60.0:F1}小时)"); + summary.AppendLine($"本周使用时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); summary.AppendLine($"上周启动次数: {stats.LastWeekLaunchCount}"); - summary.AppendLine($"上周使用时长: {stats.LastWeekUsageMinutes}分钟 ({stats.LastWeekUsageMinutes / 60.0:F1}小时)"); + summary.AppendLine($"上周使用时长: {FormatDuration(stats.LastWeekUsageSeconds)}"); summary.AppendLine($"本周开始日期: {(stats.WeekStartDate != DateTime.MinValue ? stats.WeekStartDate.ToString("yyyy-MM-dd") : "未设置")}"); summary.AppendLine($"使用频率: {stats.UsageFrequency}"); summary.AppendLine($"更新优先级: {stats.UpdatePriority}"); diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index ca788761..081c9693 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -5793,7 +5793,6 @@
- Date: Mon, 28 Jul 2025 11:37:58 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/AssemblyInfo.cs | 4 ++-- Ink Canvas/Properties/AssemblyInfo.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 536f86e3..4d4146f2 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -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.4.0")] -[assembly: AssemblyFileVersion("1.7.4.0")] +[assembly: AssemblyVersion("1.7.4.1")] +[assembly: AssemblyFileVersion("1.7.4.1")] diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs index aa9cd197..91fbdc2a 100644 --- a/Ink Canvas/Properties/AssemblyInfo.cs +++ b/Ink Canvas/Properties/AssemblyInfo.cs @@ -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.4.0")] -[assembly: AssemblyFileVersion("1.7.4.0")] +[assembly: AssemblyVersion("1.7.4.1")] +[assembly: AssemblyFileVersion("1.7.4.1")]