From 40fc4e89e0b1f8daca24fbb517fc0847e1e0e4fd Mon Sep 17 00:00:00 2001
From: CJKmkp <2564608840@qq.com>
Date: Thu, 30 Apr 2026 19:09:55 +0800
Subject: [PATCH] =?UTF-8?q?add:PPT=E5=A2=9E=E5=BC=BA=E9=A2=84=E8=A7=88?=
=?UTF-8?q?=E8=A7=86=E5=9B=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/MainWindow_cs/MW_PPT.cs | 302 +++++++++++++++++-
Ink Canvas/MainWindow_cs/MW_Settings.cs | 1 +
Ink Canvas/Properties/Strings.en-US.resx | 11 +-
Ink Canvas/Properties/Strings.resx | 11 +-
Ink Canvas/Resources/Settings.cs | 3 +
.../SettingsViews/Pages/PowerPointPage.xaml | 7 +
.../Pages/PowerPointPage.xaml.cs | 8 +
7 files changed, 332 insertions(+), 11 deletions(-)
diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs
index 010eda9a..5fff5fec 100644
--- a/Ink Canvas/MainWindow_cs/MW_PPT.cs
+++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs
@@ -12,7 +12,10 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
using System.Windows.Ink;
+using System.Windows.Markup;
+using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows.Threading;
using Application = System.Windows.Application;
@@ -180,6 +183,9 @@ namespace Ink_Canvas
/// PowerPoint 全屏放映顶层窗口类名(与编辑态 PPTFrameClass 区分)。
///
private const string PowerPointSlideShowWindowClassName = "screenClass";
+
+ private Popup _pptEnhancedPreviewPopup;
+ private ListBox _pptEnhancedPreviewListBox;
#endregion
#region PPT Managers
@@ -1371,6 +1377,8 @@ namespace Ink_Canvas
{
try
{
+ await Application.Current.Dispatcher.InvokeAsync(() => DestroyPptEnhancedPreviewPopup());
+
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
{
FoldFloatingBar_MouseUp(new object(), null);
@@ -2349,19 +2357,26 @@ namespace Ink_Canvas
GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF"));
CursorIcon_Click(null, null);
- // 使用新的PPT管理器显示导航
- if (_pptManager.TryShowSlideNavigation())
+ if (Settings.PowerPointSettings.EnablePPTButtonEnhancedPreview)
{
- LogHelper.WriteLogToFile("成功显示PPT幻灯片导航", LogHelper.LogType.Trace);
- // 若启用了“翻页时跳过PPT动画”,显示导航后把焦点拉回本窗口
- if (Settings.PowerPointSettings.SkipAnimationsWhenGoNext)
- {
- try { this.Activate(); } catch { }
- }
+ await ShowEnhancedPptPreviewAsync(sender as FrameworkElement);
}
else
{
- LogHelper.WriteLogToFile("显示PPT幻灯片导航失败", LogHelper.LogType.Warning);
+ // 使用新的PPT管理器显示导航
+ if (_pptManager.TryShowSlideNavigation())
+ {
+ LogHelper.WriteLogToFile("成功显示PPT幻灯片导航", LogHelper.LogType.Trace);
+ // 若启用了“翻页时跳过PPT动画”,显示导航后把焦点拉回本窗口
+ if (Settings.PowerPointSettings.SkipAnimationsWhenGoNext)
+ {
+ try { this.Activate(); } catch { }
+ }
+ }
+ else
+ {
+ LogHelper.WriteLogToFile("显示PPT幻灯片导航失败", LogHelper.LogType.Warning);
+ }
}
// 控制居中
@@ -2377,6 +2392,273 @@ namespace Ink_Canvas
}
}
+ private sealed class PptEnhancedPreviewItem
+ {
+ public int SlideNumber { get; set; }
+ public BitmapImage Thumbnail { get; set; }
+ }
+
+ private async Task ShowEnhancedPptPreviewAsync(FrameworkElement placementTarget = null)
+ {
+ if (_pptEnhancedPreviewPopup != null && _pptEnhancedPreviewPopup.IsOpen && placementTarget != null &&
+ ReferenceEquals(_pptEnhancedPreviewPopup.PlacementTarget, placementTarget))
+ {
+ _pptEnhancedPreviewPopup.IsOpen = false;
+ return;
+ }
+
+ var slides = await Task.Run(BuildPptPreviewItems);
+ if (slides == null || slides.Count == 0)
+ {
+ LogHelper.WriteLogToFile("PPT增强预览未生成可用缩略图,改用默认导航", LogHelper.LogType.Warning);
+ _pptManager.TryShowSlideNavigation();
+ return;
+ }
+
+ await Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ EnsurePptEnhancedPreviewPopupCreated();
+ if (_pptEnhancedPreviewListBox == null || _pptEnhancedPreviewPopup == null) return;
+
+ _pptEnhancedPreviewListBox.ItemsSource = slides;
+ var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
+ if (currentSlide > 0)
+ {
+ var selected = slides.Find(s => s.SlideNumber == currentSlide);
+ _pptEnhancedPreviewListBox.SelectedItem = selected;
+ if (selected != null)
+ {
+ _pptEnhancedPreviewListBox.ScrollIntoView(selected);
+ }
+ }
+
+ var anchor = placementTarget ?? PPTLSPageButton;
+ if (anchor != null)
+ {
+ _pptEnhancedPreviewPopup.PlacementTarget = anchor;
+ if (anchor == PPTLBPageButton || anchor == PPTRBPageButton)
+ {
+ _pptEnhancedPreviewPopup.Placement = PlacementMode.Top;
+ _pptEnhancedPreviewPopup.HorizontalOffset = 0;
+ _pptEnhancedPreviewPopup.VerticalOffset = -10;
+ }
+ else if (anchor == PPTRSPageButton)
+ {
+ _pptEnhancedPreviewPopup.Placement = PlacementMode.Left;
+ _pptEnhancedPreviewPopup.HorizontalOffset = -12;
+ _pptEnhancedPreviewPopup.VerticalOffset = 0;
+ }
+ else
+ {
+ _pptEnhancedPreviewPopup.Placement = PlacementMode.Right;
+ _pptEnhancedPreviewPopup.HorizontalOffset = 12;
+ _pptEnhancedPreviewPopup.VerticalOffset = 0;
+ }
+ }
+
+ _pptEnhancedPreviewPopup.IsOpen = true;
+ });
+ }
+
+ private void DestroyPptEnhancedPreviewPopup()
+ {
+ try
+ {
+ if (_pptEnhancedPreviewListBox != null)
+ {
+ _pptEnhancedPreviewListBox.MouseUp -= PPTEnhancedPreviewListBox_OnMouseUp;
+ _pptEnhancedPreviewListBox.ItemsSource = null;
+ }
+
+ if (_pptEnhancedPreviewPopup != null)
+ {
+ _pptEnhancedPreviewPopup.IsOpen = false;
+ _pptEnhancedPreviewPopup.Child = null;
+ _pptEnhancedPreviewPopup.PlacementTarget = null;
+ }
+ }
+ catch
+ {
+ // ignore dispose errors
+ }
+ finally
+ {
+ _pptEnhancedPreviewListBox = null;
+ _pptEnhancedPreviewPopup = null;
+ }
+ }
+
+ private void EnsurePptEnhancedPreviewPopupCreated()
+ {
+ if (_pptEnhancedPreviewPopup != null) return;
+
+ var listBox = new ListBox
+ {
+ Width = 220,
+ Height = 320,
+ Background = Brushes.Transparent,
+ BorderBrush = Brushes.Transparent,
+ BorderThickness = new Thickness(0),
+ SelectionMode = SelectionMode.Single
+ };
+ listBox.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Hidden);
+ listBox.SetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled);
+ listBox.MouseUp += PPTEnhancedPreviewListBox_OnMouseUp;
+
+ var templateXaml = @"
+
+
+
+
+
+
+
+
+";
+ listBox.ItemTemplate = (DataTemplate)XamlReader.Parse(templateXaml);
+ var itemStyleXaml = @"
+";
+ listBox.ItemContainerStyle = (Style)XamlReader.Parse(itemStyleXaml);
+
+ _pptEnhancedPreviewListBox = listBox;
+ _pptEnhancedPreviewPopup = new Popup
+ {
+ AllowsTransparency = true,
+ StaysOpen = true,
+ Placement = PlacementMode.Right,
+ PopupAnimation = PopupAnimation.Fade,
+ Child = listBox
+ };
+ }
+
+ private void PPTEnhancedPreviewListBox_OnMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
+ {
+ if (_pptEnhancedPreviewListBox?.SelectedItem is not PptEnhancedPreviewItem item) return;
+ try
+ {
+ if (_pptManager?.TryNavigateToSlide(item.SlideNumber) == true)
+ {
+ LogHelper.WriteLogToFile($"PPT增强预览跳转成功:{item.SlideNumber}", LogHelper.LogType.Trace);
+ }
+ else
+ {
+ LogHelper.WriteLogToFile($"PPT增强预览跳转失败:{item.SlideNumber}", LogHelper.LogType.Warning);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"PPT增强预览跳转异常: {ex}", LogHelper.LogType.Error);
+ }
+ finally
+ {
+ if (_pptEnhancedPreviewPopup != null)
+ {
+ _pptEnhancedPreviewPopup.IsOpen = false;
+ }
+ }
+ }
+
+ private List BuildPptPreviewItems()
+ {
+ var result = new List();
+ string tempDir = null;
+ Presentation activePresentation = null;
+ Slides slides = null;
+
+ try
+ {
+ activePresentation = _pptManager?.GetCurrentActivePresentation() as Presentation;
+ if (activePresentation == null)
+ {
+ return result;
+ }
+
+ slides = activePresentation.Slides;
+ if (slides == null) return result;
+
+ int count = slides.Count;
+ if (count <= 0) return result;
+
+ tempDir = Path.Combine(Path.GetTempPath(), "InkCanvas", "PPTPreviews", Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(tempDir);
+
+ for (int i = 1; i <= count; i++)
+ {
+ Slide slide = null;
+ try
+ {
+ slide = slides[i];
+ var imagePath = Path.Combine(tempDir, $"slide_{i:0000}.png");
+ slide.Export(imagePath, "PNG", 320, 180);
+ var image = LoadBitmapImage(imagePath);
+ if (image == null) continue;
+
+ result.Add(new PptEnhancedPreviewItem
+ {
+ SlideNumber = i,
+ Thumbnail = image
+ });
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"生成PPT第{i}页缩略图失败: {ex.Message}", LogHelper.LogType.Warning);
+ }
+ finally
+ {
+ if (slide != null)
+ {
+ try { Marshal.ReleaseComObject(slide); } catch { }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"构建PPT增强预览列表失败: {ex}", LogHelper.LogType.Error);
+ }
+ finally
+ {
+ if (!string.IsNullOrWhiteSpace(tempDir) && Directory.Exists(tempDir))
+ {
+ try { Directory.Delete(tempDir, true); } catch { }
+ }
+ }
+
+ return result;
+ }
+
+ private static BitmapImage LoadBitmapImage(string path)
+ {
+ try
+ {
+ if (!File.Exists(path)) return null;
+ var bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.CacheOption = BitmapCacheOption.OnLoad;
+ bitmap.UriSource = new Uri(path, UriKind.Absolute);
+ bitmap.EndInit();
+ bitmap.Freeze();
+ return bitmap;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
///
/// 处理“开始幻灯片放映”按钮的点击事件
///
@@ -2505,6 +2787,8 @@ namespace Ink_Canvas
{
try
{
+ await Application.Current.Dispatcher.InvokeAsync(() => DestroyPptEnhancedPreviewPopup());
+
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
{
FoldFloatingBar_MouseUp(new object(), null);
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index 95cfada4..74d9b18a 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -905,6 +905,7 @@ namespace Ink_Canvas
Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode = false;
Settings.PowerPointSettings.IsEnableFingerGestureSlideShowControl = false;
Settings.PowerPointSettings.IsSupportWPS = false;
+ Settings.PowerPointSettings.EnablePPTButtonEnhancedPreview = false;
Settings.Canvas.InkWidth = 2.5;
Settings.Canvas.IsShowCursor = false;
diff --git a/Ink Canvas/Properties/Strings.en-US.resx b/Ink Canvas/Properties/Strings.en-US.resx
index c48aa2f2..d719c19b 100644
--- a/Ink Canvas/Properties/Strings.en-US.resx
+++ b/Ink Canvas/Properties/Strings.en-US.resx
@@ -925,7 +925,16 @@
PPT page button clickable
- # When enabled, clicking the page button opens PowerPoint grid thumbnails. Not supported in WPS.
+ # Enable page-button navigation on click. By default it uses PowerPoint grid thumbnails (not supported in WPS); use the sub-setting below to manually enable enhanced preview for WPS support.
+
+
+ Page button click sub-settings
+
+
+ Enhanced preview for page button
+
+
+ # Manually enable this option to show a thumbnail page list when clicking the page button (supports both WPS and PowerPoint), then click a thumbnail to jump.
PPT long-press to turn page
diff --git a/Ink Canvas/Properties/Strings.resx b/Ink Canvas/Properties/Strings.resx
index 5b7aa7cb..3a9e27e6 100644
--- a/Ink Canvas/Properties/Strings.resx
+++ b/Ink Canvas/Properties/Strings.resx
@@ -961,7 +961,16 @@
PPT 页码按钮可点击
- # 开启该选项后,点击页码按钮可以唤起PowerPoint自带的网格缩略图视图。WPS不支持该功能,开启也没用。
+ # 开启该选项后,点击页码按钮可唤起页码导航。默认调用 PowerPoint 网格缩略图(WPS 不支持);可在下方子设置手动开启增强型预览以支持 WPS。
+
+
+ 页码按钮点击子设置
+
+
+ PPT 页码按钮增强型预览
+
+
+ # 手动开启后,点击页码按钮将显示缩略图页列表(支持 WPS 与 PowerPoint),并可点击跳转到目标页。
PPT 翻页按钮长按翻页
diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs
index 79abcaa5..1717f73d 100644
--- a/Ink Canvas/Resources/Settings.cs
+++ b/Ink Canvas/Resources/Settings.cs
@@ -371,6 +371,9 @@ namespace Ink_Canvas
[JsonProperty("enablePPTButtonPageClickable")]
public bool EnablePPTButtonPageClickable { get; set; } = true;
+ [JsonProperty("enablePPTButtonEnhancedPreview")]
+ public bool EnablePPTButtonEnhancedPreview { get; set; } = false;
+
[JsonProperty("enablePPTButtonLongPressPageTurn")]
public bool EnablePPTButtonLongPressPageTurn { get; set; } = true;
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml
index a1574fbe..14a763a5 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml
+++ b/Ink Canvas/Windows/SettingsViews/Pages/PowerPointPage.xaml
@@ -178,6 +178,13 @@
Visibility="{Binding IsOn, ElementName=CardShowPPTButton, Converter={StaticResource BooleanToVisibilityConverter}}"
Toggled="ToggleSwitchEnablePPTButtonPageClickable_OnToggled"/>
+
+
2 && bOpt[2] == '2';
CardEnablePPTButtonPageClickable.IsOn = ppt.EnablePPTButtonPageClickable;
+ CardEnablePPTButtonEnhancedPreview.IsOn = ppt.EnablePPTButtonEnhancedPreview;
CardEnablePPTButtonLongPressPageTurn.IsOn = ppt.EnablePPTButtonLongPressPageTurn;
CardShowCanvasAtNewSlideShow.IsOn = ppt.IsShowCanvasAtNewSlideShow;
@@ -254,6 +255,13 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages
SettingsManager.SaveSettingsToFile();
}
+ private void ToggleSwitchEnablePPTButtonEnhancedPreview_OnToggled(object sender, RoutedEventArgs e)
+ {
+ if (!_isLoaded) return;
+ SettingsManager.Settings.PowerPointSettings.EnablePPTButtonEnhancedPreview = CardEnablePPTButtonEnhancedPreview.IsOn;
+ SettingsManager.SaveSettingsToFile();
+ }
+
private void ToggleSwitchEnablePPTButtonLongPressPageTurn_OnToggled(object sender, RoutedEventArgs e)
{
if (!_isLoaded) return;