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;