add:PPT增强预览视图

This commit is contained in:
2026-04-30 19:09:55 +08:00
parent 6fd4a4f036
commit 40fc4e89e0
7 changed files with 332 additions and 11 deletions
+293 -9
View File
@@ -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 区分)。
/// </summary>
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 = @"
<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<Border Margin='6,5' Padding='0' Height='118' CornerRadius='6' BorderThickness='0' Background='Transparent'>
<Border BorderBrush='#7a8ea8' BorderThickness='0.6' CornerRadius='6' Background='Transparent' Padding='1'>
<Border CornerRadius='5' ClipToBounds='True' Background='Transparent'>
<Image Source='{Binding Thumbnail}' Stretch='UniformToFill'/>
</Border>
</Border>
</Border>
</DataTemplate>";
listBox.ItemTemplate = (DataTemplate)XamlReader.Parse(templateXaml);
var itemStyleXaml = @"
<Style xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
TargetType='ListBoxItem'>
<Setter Property='Background' Value='Transparent'/>
<Setter Property='BorderThickness' Value='0'/>
<Setter Property='FocusVisualStyle' Value='{x:Null}'/>
<Setter Property='Template'>
<Setter.Value>
<ControlTemplate TargetType='ListBoxItem'>
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>";
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<PptEnhancedPreviewItem> BuildPptPreviewItems()
{
var result = new List<PptEnhancedPreviewItem>();
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;
}
}
/// <summary>
/// 处理“开始幻灯片放映”按钮的点击事件
/// </summary>
@@ -2505,6 +2787,8 @@ namespace Ink_Canvas
{
try
{
await Application.Current.Dispatcher.InvokeAsync(() => DestroyPptEnhancedPreviewPopup());
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
{
FoldFloatingBar_MouseUp(new object(), null);
+1
View File
@@ -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;
+10 -1
View File
@@ -925,7 +925,16 @@
<value>PPT page button clickable</value>
</data>
<data name="PPT_PageButtonClickableHint" xml:space="preserve">
<value># When enabled, clicking the page button opens PowerPoint grid thumbnails. Not supported in WPS.</value>
<value># 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.</value>
</data>
<data name="PPT_PageButtonClickable_SubSettings" xml:space="preserve">
<value>Page button click sub-settings</value>
</data>
<data name="PPT_EnhancedPreview" xml:space="preserve">
<value>Enhanced preview for page button</value>
</data>
<data name="PPT_EnhancedPreviewHint" xml:space="preserve">
<value># 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.</value>
</data>
<data name="PPT_LongPressPageTurn" xml:space="preserve">
<value>PPT long-press to turn page</value>
+10 -1
View File
@@ -961,7 +961,16 @@
<value>PPT 页码按钮可点击</value>
</data>
<data name="PPT_PageButtonClickableHint" xml:space="preserve">
<value># 开启该选项后,点击页码按钮可唤起PowerPoint自带的网格缩略图视图。WPS不支持该功能,开启也没用。</value>
<value># 开启该选项后,点击页码按钮可唤起页码导航。默认调用 PowerPoint 网格缩略图WPS 不支持);可在下方子设置手动开启增强型预览以支持 WPS。</value>
</data>
<data name="PPT_PageButtonClickable_SubSettings" xml:space="preserve">
<value>页码按钮点击子设置</value>
</data>
<data name="PPT_EnhancedPreview" xml:space="preserve">
<value>PPT 页码按钮增强型预览</value>
</data>
<data name="PPT_EnhancedPreviewHint" xml:space="preserve">
<value># 手动开启后,点击页码按钮将显示缩略图页列表(支持 WPS 与 PowerPoint),并可点击跳转到目标页。</value>
</data>
<data name="PPT_LongPressPageTurn" xml:space="preserve">
<value>PPT 翻页按钮长按翻页</value>
+3
View File
@@ -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;
@@ -178,6 +178,13 @@
Visibility="{Binding IsOn, ElementName=CardShowPPTButton, Converter={StaticResource BooleanToVisibilityConverter}}"
Toggled="ToggleSwitchEnablePPTButtonPageClickable_OnToggled"/>
<controls:LabeledSettingsCard x:Name="CardEnablePPTButtonEnhancedPreview"
Header="{i18n:I18n Key=PPT_EnhancedPreview}"
Description="{i18n:I18n Key=PPT_EnhancedPreviewHint}"
Icon="{x:Static ui:SegoeFluentIcons.TouchPointer}"
Visibility="{Binding IsOn, ElementName=CardEnablePPTButtonPageClickable, Converter={StaticResource BooleanToVisibilityConverter}}"
Toggled="ToggleSwitchEnablePPTButtonEnhancedPreview_OnToggled"/>
<controls:LabeledSettingsCard x:Name="CardEnablePPTButtonLongPressPageTurn"
Header="{i18n:I18n Key=PPT_LongPressPageTurn}"
Description="{i18n:I18n Key=PPT_LongPressPageTurnHint}"
@@ -72,6 +72,7 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages
CheckboxBPPTBlackBackground.IsChecked = bOpt.Length > 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;