using H.NotifyIcon;
using Ink_Canvas.Helpers;
using Newtonsoft.Json;
using OSVersionExtension;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Application = System.Windows.Application;
using CheckBox = System.Windows.Controls.CheckBox;
using ComboBox = System.Windows.Controls.ComboBox;
using File = System.IO.File;
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using OperatingSystem = OSVersionExtension.OperatingSystem;
using RadioButton = System.Windows.Controls.RadioButton;
namespace Ink_Canvas
{
public partial class MainWindow : Window
{
#region Behavior
///
/// 内部标记:是否正在内部更改更新通道
///
private bool _isChangingUpdateChannelInternally;
/// 内部标记:是否正在内部更改「更新包架构」(32/64 位 ZIP)
private bool _isChangingUpdatePackageArchInternally;
///
/// 内部标记:是否正在内部更改遥测设置
///
private bool _isChangingTelemetryInternally;
///
/// 内部标记:是否正在内部更改遥测隐私设置
///
private bool _isChangingTelemetryPrivacyInternally;
///
/// 检查隐私文件是否存在
///
/// 如果隐私文件存在则返回true,否则返回false
///
/// 尝试从程序集资源中查找privacy.txt文件
/// 如果找到则返回true,否则返回false
///
private static bool PrivacyFileExists()
{
try
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "Ink_Canvas.privacy.txt";
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
return stream != null;
}
}
catch
{
return false;
}
}
///
/// 查找隐私文件
///
/// 隐私文件路径,如果是嵌入资源则返回"embedded",如果未找到则返回null
///
/// 先尝试从文件系统读取,搜索路径包括:
/// 1. App.RootPath
/// 2. 程序集所在目录
/// 3. 程序集所在目录的上一级目录
/// 如果文件系统中未找到,则回退到嵌入资源
///
private static string FindPrivacyFile()
{
// 先尝试从文件系统读取
string assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string[] searchPaths = new string[]
{
App.RootPath,
assemblyDir,
Path.GetDirectoryName(assemblyDir) // One level up from assembly directory
};
string foundPath = null;
foreach (string searchPath in searchPaths)
{
if (string.IsNullOrEmpty(searchPath)) continue;
string testPathTxt = Path.Combine(searchPath, "privacy.txt");
string testPathNoExt = Path.Combine(searchPath, "privacy");
if (File.Exists(testPathTxt))
{
foundPath = testPathTxt;
break;
}
else if (File.Exists(testPathNoExt))
{
foundPath = testPathNoExt;
break;
}
}
if (foundPath != null && File.Exists(foundPath))
{
return foundPath;
}
// 回退到嵌入资源
try
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "Ink_Canvas.privacy.txt";
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
if (stream != null)
{
// 返回特殊字符串表示来自嵌入资源
return "embedded";
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"从程序集资源查找隐私说明失败: {ex.Message}", LogHelper.LogType.Warning);
}
return null;
}
private void CheckUpdateChannelAndTelemetryConsistency()
{
var currentChannel = Settings.Startup.UpdateChannel;
bool isTestChannel = currentChannel == UpdateChannel.Preview || currentChannel == UpdateChannel.Beta;
if (!isTestChannel)
{
return;
}
if (!Settings.Startup.HasAcceptedTelemetryPrivacy)
{
var result = MessageBox.Show(
$"检测到您当前处于 {currentChannel} 通道,但尚未同意隐私说明。\n\n" +
"使用预览/测试通道需要同意隐私说明并启用匿名使用数据上传。\n\n" +
"是否现在同意隐私说明并启用基础数据上传?\n" +
"(选择“否”将自动切换回正式通道)",
"更新通道与隐私协议不匹配",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
try
{
// 使用 FindPrivacyFile 方法,支持文件系统和嵌入资源
string privacyPath = FindPrivacyFile();
if (string.IsNullOrEmpty(privacyPath))
{
MessageBox.Show(
"未找到隐私说明文件(privacy.txt 或 privacy)。\n\n将切换回正式通道。",
"隐私文件缺失",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
else
{
var privacyWindow = new PrivacyAgreementWindow();
privacyWindow.Owner = this;
bool? dialogResult = privacyWindow.ShowDialog();
if (dialogResult == true && privacyWindow.UserAccepted)
{
// 用户同意,保存设置
Settings.Startup.HasAcceptedTelemetryPrivacy = true;
Settings.Startup.TelemetryUploadLevel = TelemetryUploadLevel.Basic;
// 更新UI,即使 isLoaded 为 false(启动时)
// 使用标志避免触发事件处理程序
// 使用 Dispatcher 确保在 UI 线程上更新
Dispatcher.BeginInvoke(new Action(() =>
{
_isChangingTelemetryPrivacyInternally = true;
_isChangingTelemetryInternally = true;
try
{
if (CheckBoxTelemetryPrivacyAccepted != null)
{
CheckBoxTelemetryPrivacyAccepted.IsChecked = true;
}
if (ComboBoxTelemetryUploadLevel != null)
{
ComboBoxTelemetryUploadLevel.SelectedIndex = 1;
}
}
finally
{
_isChangingTelemetryPrivacyInternally = false;
_isChangingTelemetryInternally = false;
}
}), DispatcherPriority.Normal);
SaveSettingsToFile();
DeviceIdentifier.UpdateUsageChannel(currentChannel);
LogHelper.WriteLogToFile($"启动检测 | 用户同意隐私协议并启用基础遥测,保持 {currentChannel} 通道");
return; // 用户同意,直接返回,不执行后面的切换通道逻辑
}
else
{
// 用户取消或关闭窗口,切换回正式通道
LogHelper.WriteLogToFile($"启动检测 | 用户取消隐私协议,将切换回 Release 通道");
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"启动检测 | 读取隐私文件失败: {ex.Message}", LogHelper.LogType.Error);
MessageBox.Show(
"读取隐私说明文件时出错。\n\n将切换回正式通道。",
"读取隐私文件失败",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
else
{
// 用户选择"否",切换回正式通道
LogHelper.WriteLogToFile($"启动检测 | 用户选择不同意隐私协议,将切换回 Release 通道");
}
// 只有在用户不同意或取消的情况下,才切换回正式通道
Settings.Startup.UpdateChannel = UpdateChannel.Release;
DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release);
SaveSettingsToFile();
Dispatcher.BeginInvoke(new Action(() =>
{
_isChangingUpdateChannelInternally = true;
try
{
if (isLoaded && UpdateChannelSelector != null)
{
foreach (var item in UpdateChannelSelector.Items)
{
if (item is RadioButton rb && rb.Tag != null &&
string.Equals(rb.Tag.ToString(), "Release", StringComparison.OrdinalIgnoreCase))
{
rb.IsChecked = true;
break;
}
}
}
}
finally
{
_isChangingUpdateChannelInternally = false;
}
}), DispatcherPriority.Normal);
LogHelper.WriteLogToFile($"启动检测 | 用户未同意隐私协议,已切换回 Release 通道");
return;
}
if (Settings.Startup.TelemetryUploadLevel == TelemetryUploadLevel.None)
{
var result = MessageBox.Show(
$"检测到您当前处于 {currentChannel} 通道,但匿名使用数据上传已关闭。\n\n" +
"使用预览/测试通道需要启用匿名使用数据上传。\n\n" +
"是否现在启用基础数据上传?\n" +
"(选择“否”将自动切换回正式通道)",
"更新通道与遥测状态不匹配",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
Settings.Startup.TelemetryUploadLevel = TelemetryUploadLevel.Basic;
if (isLoaded && ComboBoxTelemetryUploadLevel != null)
{
ComboBoxTelemetryUploadLevel.SelectedIndex = 1;
}
SaveSettingsToFile();
DeviceIdentifier.UpdateUsageChannel(currentChannel);
LogHelper.WriteLogToFile($"启动检测 | 用户启用基础遥测,保持 {currentChannel} 通道");
}
else
{
Settings.Startup.UpdateChannel = UpdateChannel.Release;
DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release);
SaveSettingsToFile();
Dispatcher.BeginInvoke(new Action(() =>
{
_isChangingUpdateChannelInternally = true;
try
{
if (isLoaded && UpdateChannelSelector != null)
{
foreach (var item in UpdateChannelSelector.Items)
{
if (item is RadioButton rb && rb.Tag != null &&
string.Equals(rb.Tag.ToString(), "Release", StringComparison.OrdinalIgnoreCase))
{
rb.IsChecked = true;
break;
}
}
}
}
finally
{
_isChangingUpdateChannelInternally = false;
}
}), DispatcherPriority.Normal);
LogHelper.WriteLogToFile($"启动检测 | 用户未启用遥测,已切换回 Release 通道");
}
}
}
///
/// 处理遥测上传级别选择更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 根据选择的遥测级别执行相应操作:
/// 1. 如果选择关闭遥测且当前不是正式通道,提示用户并切换到正式通道
/// 2. 如果选择开启遥测但未同意隐私说明,提示用户需要先同意隐私说明
/// 3. 保存设置并显示通知
///
private void ComboBoxTelemetryUploadLevel_SelectionChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
if (_isChangingTelemetryInternally) return;
var oldLevel = Settings.Startup.TelemetryUploadLevel;
var item = ComboBoxTelemetryUploadLevel?.SelectedItem as ComboBoxItem;
if (item == null) return;
var tag = item.Tag?.ToString() ?? "0";
var newLevel = TelemetryUploadLevel.None;
switch (tag)
{
case "1":
newLevel = TelemetryUploadLevel.Basic;
break;
case "2":
newLevel = TelemetryUploadLevel.Extended;
break;
default:
newLevel = TelemetryUploadLevel.None;
break;
}
if (newLevel == TelemetryUploadLevel.None &&
oldLevel != TelemetryUploadLevel.None &&
Settings.Startup.UpdateChannel != UpdateChannel.Release)
{
var result = MessageBox.Show(
"关闭匿名使用数据上传后,将无法继续使用预览/测试通道,系统会自动切换回正式通道(Release)。\n\n是否确认关闭?",
"确认关闭遥测",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result != MessageBoxResult.Yes)
{
_isChangingTelemetryInternally = true;
try
{
int idx = 0;
switch (oldLevel)
{
case TelemetryUploadLevel.Basic:
idx = 1;
break;
case TelemetryUploadLevel.Extended:
idx = 2;
break;
default:
idx = 0;
break;
}
ComboBoxTelemetryUploadLevel.SelectedIndex = idx;
}
finally
{
_isChangingTelemetryInternally = false;
}
return;
}
_isChangingUpdateChannelInternally = true;
try
{
Settings.Startup.UpdateChannel = UpdateChannel.Release;
DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release);
if (UpdateChannelSelector != null)
{
foreach (var u in UpdateChannelSelector.Items)
{
var rb = u as RadioButton;
if (rb != null && rb.Tag != null && rb.Tag.ToString() == "Release")
{
rb.IsChecked = true;
break;
}
}
}
}
finally
{
_isChangingUpdateChannelInternally = false;
}
}
if (newLevel != TelemetryUploadLevel.None && !Settings.Startup.HasAcceptedTelemetryPrivacy)
{
MessageBox.Show(
"在开启匿名使用数据上传前,请先阅读并勾选上方的隐私说明。",
"需要同意隐私说明",
MessageBoxButton.OK,
MessageBoxImage.Warning);
_isChangingTelemetryInternally = true;
try
{
Settings.Startup.TelemetryUploadLevel = TelemetryUploadLevel.None;
if (ComboBoxTelemetryUploadLevel != null)
{
ComboBoxTelemetryUploadLevel.SelectedIndex = 0;
}
}
finally
{
_isChangingTelemetryInternally = false;
}
return;
}
Settings.Startup.TelemetryUploadLevel = newLevel;
SaveSettingsToFile();
ShowNotification("匿名使用数据上传设置已保存");
}
///
/// 处理遥测隐私同意复选框状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当用户勾选或取消勾选隐私同意复选框时执行:
/// 1. 勾选时:显示隐私协议窗口,用户同意后保存设置
/// 2. 取消勾选时:提示用户会关闭遥测并切回正式通道,用户确认后执行相应操作
///
private void CheckBoxTelemetryPrivacyAccepted_Checked(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
if (_isChangingTelemetryPrivacyInternally) return;
bool isChecked = CheckBoxTelemetryPrivacyAccepted.IsChecked == true;
if (isChecked)
{
if (!PrivacyFileExists())
{
MessageBox.Show(
"未找到隐私说明文件(privacy / privacy.txt),暂时无法启用匿名使用数据上传。",
"隐私说明缺失",
MessageBoxButton.OK,
MessageBoxImage.Warning);
_isChangingTelemetryPrivacyInternally = true;
try
{
CheckBoxTelemetryPrivacyAccepted.IsChecked = false;
}
finally
{
_isChangingTelemetryPrivacyInternally = false;
}
Settings.Startup.HasAcceptedTelemetryPrivacy = false;
SaveSettingsToFile();
return;
}
var privacyWindow = new PrivacyAgreementWindow();
privacyWindow.Owner = this;
bool? dialogResult = privacyWindow.ShowDialog();
if (dialogResult == true && privacyWindow.UserAccepted)
{
Settings.Startup.HasAcceptedTelemetryPrivacy = true;
SaveSettingsToFile();
}
else
{
_isChangingTelemetryPrivacyInternally = true;
try
{
CheckBoxTelemetryPrivacyAccepted.IsChecked = false;
}
finally
{
_isChangingTelemetryPrivacyInternally = false;
}
Settings.Startup.HasAcceptedTelemetryPrivacy = false;
SaveSettingsToFile();
}
}
else
{
// 用户主动取消勾选,提示会关闭遥测并切回正式通道
var result = MessageBox.Show(
"取消同意隐私说明后,将关闭匿名使用数据上传,并切回正式通道(Release)。\n\n是否确认?",
"确认取消隐私同意",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result != MessageBoxResult.Yes)
{
// 撤销取消操作,恢复为已勾选
_isChangingTelemetryPrivacyInternally = true;
try
{
CheckBoxTelemetryPrivacyAccepted.IsChecked = true;
}
finally
{
_isChangingTelemetryPrivacyInternally = false;
}
return;
}
// 1. 关闭遥测等级
_isChangingTelemetryInternally = true;
try
{
Settings.Startup.TelemetryUploadLevel = TelemetryUploadLevel.None;
if (ComboBoxTelemetryUploadLevel != null)
{
ComboBoxTelemetryUploadLevel.SelectedIndex = 0;
}
}
finally
{
_isChangingTelemetryInternally = false;
}
// 2. 若当前不是 Release 通道,则切回 Release
if (Settings.Startup.UpdateChannel != UpdateChannel.Release)
{
_isChangingUpdateChannelInternally = true;
try
{
Settings.Startup.UpdateChannel = UpdateChannel.Release;
DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release);
if (UpdateChannelSelector != null)
{
foreach (var u in UpdateChannelSelector.Items)
{
var rb = u as RadioButton;
if (rb != null && rb.Tag != null && rb.Tag.ToString() == "Release")
{
rb.IsChecked = true;
break;
}
}
}
}
finally
{
_isChangingUpdateChannelInternally = false;
}
}
// 3. 最后真正取消隐私同意并保存
Settings.Startup.HasAcceptedTelemetryPrivacy = false;
SaveSettingsToFile();
}
}
///
/// 处理自动更新开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当自动更新开关状态更改时:
/// 1. 保存自动更新设置
/// 2. 自动更新关闭时隐藏静默更新选项
/// 3. 如果关闭了自动更新,同时也关闭静默更新
/// 4. 根据静默更新设置显示或隐藏静默更新时间区域
///
private void ToggleSwitchIsAutoUpdate_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
if (ToggleSwitchIsAutoUpdate.IsOn && Settings?.Startup?.HasConfirmedNetCompatibilityChange != true)
{
ToggleSwitchIsAutoUpdate.IsOn = false;
MessageBox.Show(
"此版本为最后一个NET472版本,您需要手动确认才能继续接受自动更新。",
"兼容性变更",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
Settings.Startup.IsAutoUpdate = ToggleSwitchIsAutoUpdate.IsOn;
// 自动更新关闭时隐藏静默更新选项
ToggleSwitchIsAutoUpdateWithSilence.Visibility =
ToggleSwitchIsAutoUpdate.IsOn ? Visibility.Visible : Visibility.Collapsed;
// 如果关闭了自动更新,同时也关闭静默更新
if (!ToggleSwitchIsAutoUpdate.IsOn)
{
Settings.Startup.IsAutoUpdateWithSilence = false;
ToggleSwitchIsAutoUpdateWithSilence.IsOn = false;
}
// 无论如何,静默更新时间区域的显示都要跟随静默更新设置
AutoUpdateTimePeriodBlock.Visibility =
(Settings.Startup.IsAutoUpdateWithSilence && Settings.Startup.IsAutoUpdate) ?
Visibility.Visible : Visibility.Collapsed;
SaveSettingsToFile();
}
///
/// 处理静默自动更新开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当静默自动更新开关状态更改时:
/// 1. 保存静默自动更新设置
/// 2. 根据静默更新设置显示或隐藏静默更新时间区域
///
private void ToggleSwitchIsAutoUpdateWithSilence_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
if (ToggleSwitchIsAutoUpdateWithSilence.IsOn && Settings?.Startup?.HasConfirmedNetCompatibilityChange != true)
{
ToggleSwitchIsAutoUpdateWithSilence.IsOn = false;
MessageBox.Show(
"此版本为最后一个NET472版本,您需要手动确认才能继续接受自动更新。",
"兼容性变更",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
Settings.Startup.IsAutoUpdateWithSilence = ToggleSwitchIsAutoUpdateWithSilence.IsOn;
// 静默更新的时间设置区域只在静默更新开启时显示
AutoUpdateTimePeriodBlock.Visibility =
Settings.Startup.IsAutoUpdateWithSilence ? Visibility.Visible : Visibility.Collapsed;
SaveSettingsToFile();
}
///
/// 处理静默自动更新开始时间选择更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当选择静默自动更新开始时间时,保存设置到文件
///
private void AutoUpdateWithSilenceStartTimeComboBox_SelectionChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Startup.AutoUpdateWithSilenceStartTime =
(string)AutoUpdateWithSilenceStartTimeComboBox.SelectedItem;
SaveSettingsToFile();
}
///
/// 处理静默自动更新结束时间选择更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当选择静默自动更新结束时间时,保存设置到文件
///
private void AutoUpdateWithSilenceEndTimeComboBox_SelectionChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Startup.AutoUpdateWithSilenceEndTime = (string)AutoUpdateWithSilenceEndTimeComboBox.SelectedItem;
SaveSettingsToFile();
}
///
/// 处理开机启动开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当开机启动开关状态更改时:
/// 1. 如果开启,删除旧的启动项并创建新的启动项
/// 2. 如果关闭,删除所有启动项
///
private void ToggleSwitchRunAtStartup_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
if (ToggleSwitchRunAtStartup.IsOn)
{
StartAutomaticallyDel("InkCanvas");
StartAutomaticallyCreate("Ink Canvas Annotation");
}
else
{
StartAutomaticallyDel("InkCanvas");
StartAutomaticallyDel("Ink Canvas Annotation");
}
}
///
/// 处理启动时折叠开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当启动时折叠开关状态更改时,保存设置到文件
///
private void ToggleSwitchFoldAtStartup_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Startup.IsFoldAtStartup = ToggleSwitchFoldAtStartup.IsOn;
SaveSettingsToFile();
}
///
/// 处理PowerPoint支持开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当PowerPoint支持开关状态更改时:
/// 1. 保存PowerPoint支持设置
/// 2. 如果关闭PowerPoint支持,同时也关闭WPS支持
/// 3. 如果开启PowerPoint支持,初始化PPT管理器并开始监控
/// 4. 如果关闭PowerPoint支持,停止监控
///
private void ToggleSwitchSupportPowerPoint_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.PowerPointSettings.PowerPointSupport = ToggleSwitchSupportPowerPoint.IsOn;
if (!Settings.PowerPointSettings.PowerPointSupport)
{
if (Settings.PowerPointSettings.IsSupportWPS)
{
Settings.PowerPointSettings.IsSupportWPS = false;
ToggleSwitchSupportWPS.IsOn = false;
if (_pptManager != null)
{
_pptManager.IsSupportWPS = false;
}
}
}
SaveSettingsToFile();
// 使用新的PPT管理器
if (Settings.PowerPointSettings.PowerPointSupport)
{
if (_pptManager == null)
{
InitializePPTManagers();
}
StartPPTMonitoring();
}
else
{
StopPPTMonitoring();
}
}
///
/// 处理使用ROT PPT链接开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当使用ROT PPT链接开关状态更改时:
/// 1. 保存ROT PPT链接设置
/// 2. 停止PPT监控
/// 3. 如果开启ROT PPT链接且启用了PowerPoint增强,关闭PowerPoint增强
/// 4. 初始化PPT管理器
/// 5. 如果启用了PowerPoint支持,开始PPT监控
/// 6. 记录切换PPT联动架构的日志
///
private void ToggleSwitchUseRotPptLink_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.PowerPointSettings.UseRotPptLink = ToggleSwitchUseRotPptLink.IsOn;
SaveSettingsToFile();
try
{
StopPPTMonitoring();
if (Settings.PowerPointSettings.UseRotPptLink &&
Settings.PowerPointSettings.EnablePowerPointEnhancement)
{
Settings.PowerPointSettings.EnablePowerPointEnhancement = false;
if (ToggleSwitchPowerPointEnhancement != null)
{
ToggleSwitchPowerPointEnhancement.IsOn = false;
}
StopPowerPointProcessMonitoring();
SaveSettingsToFile();
}
InitializePPTManagers();
if (Settings.PowerPointSettings.PowerPointSupport)
{
StartPPTMonitoring();
}
LogHelper.WriteLogToFile(
$"已切换 PPT 联动架构为 {(Settings.PowerPointSettings.UseRotPptLink ? "ROT" : "COM")}",
LogHelper.LogType.Event);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"切换 PPT 联动架构失败: {ex}", LogHelper.LogType.Error);
}
}
///
/// 处理新幻灯片放映时显示画布开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当新幻灯片放映时显示画布开关状态更改时,保存设置到文件
///
private void ToggleSwitchShowCanvasAtNewSlideShow_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow = ToggleSwitchShowCanvasAtNewSlideShow.IsOn;
SaveSettingsToFile();
}
#endregion
#region Startup
///
/// 处理笔尖模式开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当笔尖模式开关状态更改时:
/// 1. 同步更新两个笔尖模式开关的状态
/// 2. 保存笔尖模式设置
/// 3. 根据笔尖模式设置更新边界宽度
///
private void ToggleSwitchEnableNibMode_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
if (sender == ToggleSwitchEnableNibMode)
BoardToggleSwitchEnableNibMode.IsOn = ToggleSwitchEnableNibMode.IsOn;
else
ToggleSwitchEnableNibMode.IsOn = BoardToggleSwitchEnableNibMode.IsOn;
Settings.Startup.IsEnableNibMode = ToggleSwitchEnableNibMode.IsOn;
if (Settings.Startup.IsEnableNibMode)
BoundsWidth = Settings.Advanced.NibModeBoundsWidth;
else
BoundsWidth = Settings.Advanced.FingerModeBoundsWidth;
SaveSettingsToFile();
}
#endregion
#region Appearance
///
/// 处理显示笔尖模式切换开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当显示笔尖模式切换开关状态更改时:
/// 1. 保存显示笔尖模式切换设置
/// 2. 根据设置显示或隐藏笔尖模式切换面板
///
private void ToggleSwitchEnableDisPlayNibModeToggle_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsEnableDisPlayNibModeToggler = ToggleSwitchEnableDisPlayNibModeToggle.IsOn;
SaveSettingsToFile();
if (!ToggleSwitchEnableDisPlayNibModeToggle.IsOn)
{
NibModeSimpleStackPanel.Visibility = Visibility.Collapsed;
BoardNibModeSimpleStackPanel.Visibility = Visibility.Collapsed;
}
else
{
NibModeSimpleStackPanel.Visibility = Visibility.Visible;
BoardNibModeSimpleStackPanel.Visibility = Visibility.Visible;
}
}
//private void ToggleSwitchIsColorfulViewboxFloatingBar_Toggled(object sender, RoutedEventArgs e) {
// if (!isLoaded) return;
// Settings.Appearance.IsColorfulViewboxFloatingBar = ToggleSwitchColorfulViewboxFloatingBar.IsOn;
// SaveSettingsToFile();
//}
///
/// 处理快速面板开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当快速面板开关状态更改时,保存设置到文件
///
private void ToggleSwitchEnableQuickPanel_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.IsShowQuickPanel = ToggleSwitchEnableQuickPanel.IsOn;
SaveSettingsToFile();
}
///
/// 处理启动屏幕开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当启动屏幕开关状态更改时,保存设置到文件
///
private void ToggleSwitchEnableSplashScreen_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.EnableSplashScreen = ToggleSwitchEnableSplashScreen.IsOn;
SaveSettingsToFile();
}
///
/// 处理启动屏幕样式选择更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当选择启动屏幕样式时,保存设置到文件
///
private void ComboBoxSplashScreenStyle_SelectionChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.SplashScreenStyle = ComboBoxSplashScreenStyle.SelectedIndex;
SaveSettingsToFile();
}
///
/// 处理浮动栏缩放值滑块值更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当浮动栏缩放值滑块值更改时:
/// 1. 保存浮动栏缩放设置
/// 2. 应用缩放值到浮动栏(限制在0.5-1.25范围内)
/// 3. 等待UI更新后重新计算浮动栏位置,确保居中计算准确
/// 4. 只在屏幕模式下重新计算浮动栏位置
///
private void ViewboxFloatingBarScaleTransformValueSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.ViewboxFloatingBarScaleTransformValue =
ViewboxFloatingBarScaleTransformValueSlider.Value;
SaveSettingsToFile();
var val = ViewboxFloatingBarScaleTransformValueSlider.Value;
ViewboxFloatingBarScaleTransform.ScaleX =
val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1;
ViewboxFloatingBarScaleTransform.ScaleY =
val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1;
// 等待UI更新后再重新计算浮动栏位置,确保居中计算准确
Dispatcher.BeginInvoke(new Action(async () =>
{
// 强制更新布局以确保ActualWidth正确
ViewboxFloatingBar.UpdateLayout();
// 等待一小段时间让布局完全更新
await Task.Delay(100);
// 再次强制更新布局
ViewboxFloatingBar.UpdateLayout();
// 强制重新测量和排列
ViewboxFloatingBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
ViewboxFloatingBar.Arrange(new Rect(ViewboxFloatingBar.DesiredSize));
// auto align - 新增:只在屏幕模式下重新计算浮动栏位置
if (currentMode == 0)
{
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
ViewboxFloatingBarMarginAnimation(60);
else
ViewboxFloatingBarMarginAnimation(100, true);
}
}), DispatcherPriority.Render);
}
///
/// 处理浮动栏透明度值滑块值更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当浮动栏透明度值滑块值更改时:
/// 1. 保存浮动栏透明度设置
/// 2. 应用透明度值到浮动栏
///
private void ViewboxFloatingBarOpacityValueSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.ViewboxFloatingBarOpacityValue = ViewboxFloatingBarOpacityValueSlider.Value;
SaveSettingsToFile();
ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityValue;
}
///
/// 处理PPT中浮动栏透明度值滑块值更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当PPT中浮动栏透明度值滑块值更改时,保存设置到文件
///
private void ViewboxFloatingBarOpacityInPPTValueSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue = ViewboxFloatingBarOpacityInPPTValueSlider.Value;
SaveSettingsToFile();
}
///
/// 处理托盘图标开关状态更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当托盘图标开关状态更改时:
/// 1. 保存托盘图标设置
/// 2. 根据设置显示或隐藏托盘图标示例图像
/// 3. 根据设置显示或隐藏系统托盘图标
///
private void ToggleSwitchEnableTrayIcon_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.EnableTrayIcon = ToggleSwitchEnableTrayIcon.IsOn;
ICCTrayIconExampleImage.Visibility = Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed;
var _taskbar = (TaskbarIcon)Application.Current.Resources["TaskbarTrayIcon"];
_taskbar.Visibility = ToggleSwitchEnableTrayIcon.IsOn ? Visibility.Visible : Visibility.Collapsed;
SaveSettingsToFile();
}
///
/// 处理展开按钮图像选择更改事件
///
/// 事件发送者
/// 路由事件参数
///
/// 当选择展开按钮图像类型时:
/// 1. 保存展开按钮图像类型设置
/// 2. 根据选择的图像类型更新左右展开按钮的图标
/// 3. 为不同的图像类型设置不同的大小和旋转角度
///
private void ComboBoxUnFoldBtnImg_SelectionChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Appearance.UnFoldButtonImageType = ComboBoxUnFoldBtnImg.SelectedIndex;
SaveSettingsToFile();
if (ComboBoxUnFoldBtnImg.SelectedIndex == 0)
{
RightUnFoldBtnImgChevron.Source =
new BitmapImage(new Uri("pack://application:,,,/Resources/new-icons/unfold-chevron.png"));
RightUnFoldBtnImgChevron.Width = 14;
RightUnFoldBtnImgChevron.Height = 14;
RightUnFoldBtnImgChevron.RenderTransform = new RotateTransform(180);
LeftUnFoldBtnImgChevron.Source =
new BitmapImage(new Uri("pack://application:,,,/Resources/new-icons/unfold-chevron.png"));
LeftUnFoldBtnImgChevron.Width = 14;
LeftUnFoldBtnImgChevron.Height = 14;
LeftUnFoldBtnImgChevron.RenderTransform = null;
}
else if (ComboBoxUnFoldBtnImg.SelectedIndex == 1)
{
RightUnFoldBtnImgChevron.Source =
new BitmapImage(new Uri("pack://application:,,,/Resources/new-icons/pen-white.png"));
RightUnFoldBtnImgChevron.Width = 18;
RightUnFoldBtnImgChevron.Height = 18;
RightUnFoldBtnImgChevron.RenderTransform = null;
LeftUnFoldBtnImgChevron.Source =
new BitmapImage(new Uri("pack://application:,,,/Resources/new-icons/pen-white.png"));
LeftUnFoldBtnImgChevron.Width = 18;
LeftUnFoldBtnImgChevron.Height = 18;
LeftUnFoldBtnImgChevron.RenderTransform = null;
}
}
private static readonly Lazy