diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 80df8ba1..5de9bf16 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -111,6 +111,7 @@ + diff --git a/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml b/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml index 7de67c6b..e0b270be 100644 --- a/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml +++ b/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml @@ -8,6 +8,7 @@ xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf" Title="InkCanvasForClass 设置" Width="1138" Height="750" + MinWidth="800" MinHeight="600" WindowStartupLocation="CenterScreen" ui:ThemeManager.IsThemeAware="True" ui:TitleBar.ExtendViewIntoTitleBar="True" @@ -15,6 +16,7 @@ ui:WindowHelper.UseModernWindowStyle="True" ui:TitleBar.Height="48" mc:Ignorable="d"> + @@ -22,6 +24,7 @@ + + - + + FontSize="20" + FontWeight="SemiBold"/> + + TextChanged="OnControlsSearchBoxTextChanged"> - + + + + + + + Tag="Iconography" + ToolTipService.ToolTip="图标设置"> - - + + + Tag="Typography" + ToolTipService.ToolTip="排版设置"> - - + + + + + Tag="Theme" + ToolTipService.ToolTip="主题设置"> - - + + + Tag="Colors" + ToolTipService.ToolTip="颜色设置"> - - + + + Tag="Fonts" + ToolTipService.ToolTip="字体设置"> - + @@ -170,7 +189,7 @@ - + diff --git a/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs b/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs index 71d93cdf..f547b119 100644 --- a/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs @@ -4,20 +4,39 @@ using System; using System.Collections.Generic; using System.Windows; using System.Windows.Navigation; -using System.Windows.Forms; +using System.Windows.Interop; +using System.Windows.Input; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.Linq; +using MessageBox = System.Windows.MessageBox; +using Screen = System.Windows.Forms.Screen; namespace Ink_Canvas.Windows.SettingsViews2 { + // 插件设置页面契约接口,所有插件必须实现此接口即可自动接入 + public interface IPluginSettingsPage + { + string PageTag { get; } // 页面唯一标识,不可与内置页面重复 + string PageTitle { get; } // 导航菜单显示的标题 + string PageIconCode { get; } // Segoe MDL2 Assets 图标字符,例:"\xE713"(设置图标) + Type PageType { get; } // 插件设置页面的类型(继承自Page) + bool IsFooterItem { get; } // 是否放在导航底部菜单 + } + public partial class SettingsWindow2 : Window { - private Dictionary _pageTypes; - private Dictionary _pages = new Dictionary(); + private readonly Dictionary _pageTypes; + private readonly Dictionary _pages = new Dictionary(); + + [ImportMany(typeof(IPluginSettingsPage))] + private IEnumerable _pluginPages; // 自动导入所有插件页面 public SettingsWindow2() { InitializeComponent(); - // 初始化页面类型映射 + // 初始化内置页面映射 _pageTypes = new Dictionary { { "Basic", typeof(Basic) }, @@ -34,157 +53,260 @@ namespace Ink_Canvas.Windows.SettingsViews2 { "Settings", typeof(SettingsPage) } }; - // 默认选中第一个项目 + // 加载插件页面 + LoadPluginSettingsPages(); + // 初始化导航菜单(内置+插件) + InitializeNavigationMenu(); + + // 默认选中第一个菜单项 if (NavigationViewControl.MenuItems.Count > 0) { NavigationViewControl.SelectedItem = NavigationViewControl.MenuItems[0]; } - // 窗口加载完成后设置最大尺寸,确保能正确获取 DPI 缩放因子 + // 窗口生命周期事件注册 this.Loaded += (sender, e) => { - SetMaxWindowSize(); + SetMaxSizeAndCenter(); + RegisterDpiChangedListener(); + }; + + // 窗口拖动到其他屏幕时自动适配 + this.LocationChanged += (sender, e) => + { + SetMaxSizeAndCenter(); + }; + + // 窗口关闭时释放资源 + this.Closed += (sender, e) => + { + UnregisterDpiChangedListener(); + _pages.Clear(); + _pageTypes.Clear(); + }; + + // 修复触摸屏操作后鼠标指针消失的问题 + FixTouchScreenCursorIssue(); + } + + #region 修复触摸屏鼠标指针消失问题 + private void FixTouchScreenCursorIssue() + { + // 触摸结束时强制显示鼠标指针 + this.TouchUp += (s, e) => + { + ShowCursor(true); + }; + + // 鼠标进入窗口时确保指针可见 + this.MouseEnter += (s, e) => + { + ShowCursor(true); + }; + + // 窗口激活时确保指针可见 + this.Activated += (s, e) => + { + ShowCursor(true); }; } - private void SetMaxWindowSize() + [System.Runtime.InteropServices.DllImport("user32.dll")] + private static extern int ShowCursor(bool bShow); + #endregion + + #region 插件化动态设置页面核心逻辑 + private void LoadPluginSettingsPages() { - // 设置最大高度和宽度为工作区的高度和宽度,分别减去 40 和 10,并考虑 DPI 缩放 - var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea; - - // 获取 DPI 缩放因子 + try + { + // 扫描程序目录下Plugins文件夹中的插件dll + var pluginCatalog = new DirectoryCatalog("./Plugins", "*.dll"); + var container = new CompositionContainer(pluginCatalog); + container.ComposeParts(this); + + // 将插件页面注册到页面映射字典 + foreach (var pluginPage in _pluginPages) + { + if (!_pageTypes.ContainsKey(pluginPage.PageTag)) + { + _pageTypes.Add(pluginPage.PageTag, pluginPage.PageType); + } + } + } + catch (Exception ex) + { + // 插件加载失败不影响主程序运行,仅输出调试日志 + System.Diagnostics.Debug.WriteLine($"插件加载失败: {ex.Message}"); + } + } + + private void InitializeNavigationMenu() + { + // 自动将插件页面添加到导航菜单 + foreach (var pluginPage in _pluginPages) + { + var navItem = new NavigationViewItem + { + Tag = pluginPage.PageTag, + Content = pluginPage.PageTitle, + Icon = new FontIcon { Glyph = pluginPage.PageIconCode } + }; + + if (pluginPage.IsFooterItem) + { + NavigationViewControl.FooterMenuItems.Add(navItem); + } + else + { + NavigationViewControl.MenuItems.Add(navItem); + } + } + } + #endregion + + #region 高DPI/多屏自适应窗口控制 + private void SetMaxSizeAndCenter() + { + if (!this.IsLoaded) return; + + // 1. 获取窗口当前所在屏幕(而非固定主屏,彻底解决多屏问题) + var windowHandle = new WindowInteropHelper(this).Handle; + var currentScreen = Screen.FromHandle(windowHandle); + var workingArea = currentScreen.WorkingArea; + var screenBounds = currentScreen.Bounds; + + // 2. 获取当前窗口的DPI缩放因子 + var source = PresentationSource.FromVisual(this); double dpiScaleX = 1.0; double dpiScaleY = 1.0; - var source = System.Windows.PresentationSource.FromVisual(this); - if (source != null) + + if (source?.CompositionTarget != null) { dpiScaleX = source.CompositionTarget.TransformToDevice.M11; dpiScaleY = source.CompositionTarget.TransformToDevice.M22; } - - // 先将物理像素转换为逻辑像素,再减去边距 - this.MaxWidth = (workingArea.Width / dpiScaleX) - 10; - this.MaxHeight = (workingArea.Height / dpiScaleY) - 40; - - // 确保窗口居中显示 - this.Left = (workingArea.Width / dpiScaleX - this.Width) / 2; - this.Top = (workingArea.Height / dpiScaleY - this.Height) / 2; + + // 3. 物理像素 → WPF设备无关像素(DIP)转换 + double workAreaWidthDip = workingArea.Width / dpiScaleX; + double workAreaHeightDip = workingArea.Height / dpiScaleY; + double screenLeftDip = screenBounds.Left / dpiScaleX; + double screenTopDip = screenBounds.Top / dpiScaleY; + + // 4. 设置窗口最大尺寸(保留你原有的边距) + this.MaxWidth = workAreaWidthDip - 10; + this.MaxHeight = workAreaHeightDip - 40; + + // 5. 窗口在当前屏幕居中(解决副屏居中跑偏问题) + this.Left = screenLeftDip + (workAreaWidthDip - this.ActualWidth) / 2; + this.Top = screenTopDip + (workAreaHeightDip - this.ActualHeight) / 2; } + #region DPI/系统缩放变化监听 + private HwndSource _hwndSource; + private void RegisterDpiChangedListener() + { + _hwndSource = PresentationSource.FromVisual(this) as HwndSource; + _hwndSource?.AddHook(DpiChangedWndProc); + } + + private void UnregisterDpiChangedListener() + { + _hwndSource?.RemoveHook(DpiChangedWndProc); + _hwndSource = null; + } + + private IntPtr DpiChangedWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + const int WM_DPICHANGED = 0x02E0; + // 系统DPI/缩放变化时自动重新计算窗口参数 + if (msg == WM_DPICHANGED) + { + SetMaxSizeAndCenter(); + handled = true; + } + return IntPtr.Zero; + } + #endregion + #endregion + + #region 导航逻辑优化(含页面缓存) private void OnNavigationViewSelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) { + // 处理自带的设置项导航 if (args.IsSettingsSelected) { - // 导航到设置页面 NavigateToPage("Settings"); + NavigationViewControl.Header = "设置"; + return; } - else if (args.SelectedItem is iNKORE.UI.WPF.Modern.Controls.NavigationViewItem item) + + // 处理普通导航项 + if (args.SelectedItem is NavigationViewItem selectedItem) { - // 检查是否有Tag,如果有则导航 - string tag = item.Tag as string; - if (!string.IsNullOrEmpty(tag)) + string tag = selectedItem.Tag as string; + if (!string.IsNullOrEmpty(tag) && _pageTypes.ContainsKey(tag)) { - // 检查当前页面是否已经是目标页面,避免重复导航 + // 避免重复导航到当前页面 if (rootFrame.SourcePageType != _pageTypes[tag]) { NavigateToPage(tag); } + NavigationViewControl.Header = selectedItem.Content; } - // 父级导航项(有子菜单)会自动展开,不需要额外处理 } } public void NavigateToPage(string pageTag) { - if (_pageTypes.TryGetValue(pageTag, out Type pageType)) + if (!_pageTypes.TryGetValue(pageTag, out Type pageType)) return; + + try { - try + // 页面缓存:已创建的页面直接复用,保留状态,避免重复初始化 + if (!_pages.TryGetValue(pageTag, out var cachedPage)) { - // 使用Type参数导航,这样可以正确记录导航历史 - rootFrame.Navigate(pageType); - // 更新标题 - if (NavigationViewControl.SelectedItem is iNKORE.UI.WPF.Modern.Controls.NavigationViewItem selectedItem) - { - NavigationViewControl.Header = selectedItem.Content; - } - } - catch (Exception ex) - { - iNKORE.UI.WPF.Modern.Controls.MessageBox.Show($"导航到页面时出错: {ex.Message}", "错误"); + cachedPage = Activator.CreateInstance(pageType); + _pages.Add(pageTag, cachedPage); } + + // 导航到缓存页面 + rootFrame.Navigate(cachedPage.GetType(), cachedPage); } - } - - private void OnControlsSearchBoxQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) - { - string query = args.QueryText.ToLower(); - List allItems = new List(); - foreach (object item in NavigationViewControl.MenuItems) allItems.Add(item); - foreach (object item in NavigationViewControl.FooterMenuItems) allItems.Add(item); - - foreach (object item in allItems) + catch (Exception ex) { - if (item is iNKORE.UI.WPF.Modern.Controls.NavigationViewItem navItem) - { - string content = navItem.Content?.ToString().ToLower(); - if (content != null && content.Contains(query)) - { - NavigationViewControl.SelectedItem = navItem; - break; - } - } - } - } - - private void OnControlsSearchBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) - { - if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) - { - string query = sender.Text.ToLower(); - List suggestions = new List(); - - List allItems = new List(); - foreach (object item in NavigationViewControl.MenuItems) allItems.Add(item); - foreach (object item in NavigationViewControl.FooterMenuItems) allItems.Add(item); - - foreach (object item in allItems) - { - if (item is iNKORE.UI.WPF.Modern.Controls.NavigationViewItem navItem) - { - string content = navItem.Content?.ToString(); - if (content != null && content.ToLower().Contains(query)) - { - suggestions.Add(content); - } - } - } - - sender.ItemsSource = suggestions; + MessageBox.Show($"导航到页面时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private void OnNavigationViewBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) { - if (rootFrame.CanGoBack) - { - rootFrame.GoBack(); - } + if (rootFrame.CanGoBack) rootFrame.GoBack(); } private void OnRootFrameNavigated(object sender, NavigationEventArgs e) { - // 更新NavigationView的选中状态 - Type pageType = rootFrame.SourcePageType; - foreach (KeyValuePair kvp in _pageTypes) + // 导航后同步导航项选中状态 + Type currentPageType = rootFrame.SourcePageType; + + // 处理设置项的选中状态 + if (currentPageType == typeof(SettingsPage)) { - if (kvp.Value == pageType) + NavigationViewControl.SelectedItem = NavigationViewControl.SettingsItem; + NavigationViewControl.Header = "设置"; + return; + } + + // 同步其他页面的选中状态 + foreach (var kvp in _pageTypes) + { + if (kvp.Value == currentPageType) { - // 找到对应的NavigationViewItem - NavigationViewItem item = FindNavigationViewItemByTag(kvp.Key); - if (item != null && NavigationViewControl.SelectedItem != item) + var targetItem = FindNavigationViewItemByTag(kvp.Key); + if (targetItem != null && NavigationViewControl.SelectedItem != targetItem) { - NavigationViewControl.SelectedItem = item; - NavigationViewControl.Header = item.Content; + NavigationViewControl.SelectedItem = targetItem; + NavigationViewControl.Header = targetItem.Content; } break; } @@ -193,42 +315,103 @@ namespace Ink_Canvas.Windows.SettingsViews2 private NavigationViewItem FindNavigationViewItemByTag(string tag) { - // 遍历所有主菜单项 - foreach (object item in NavigationViewControl.MenuItems) + // 遍历主菜单 + foreach (var item in NavigationViewControl.MenuItems) { if (item is NavigationViewItem navItem) { if (navItem.Tag as string == tag) - { return navItem; - } - // 检查子菜单项 - foreach (object child in navItem.MenuItems) + + // 遍历子菜单,自动展开父项 + foreach (var childItem in navItem.MenuItems) { - if (child is NavigationViewItem childNavItem) + if (childItem is NavigationViewItem childNavItem && childNavItem.Tag as string == tag) { - if (childNavItem.Tag as string == tag) - { - // 展开父项 - navItem.IsExpanded = true; - return childNavItem; - } + navItem.IsExpanded = true; + return childNavItem; } } } } - // 遍历页脚菜单项 - foreach (object item in NavigationViewControl.FooterMenuItems) + + // 遍历底部菜单 + foreach (var item in NavigationViewControl.FooterMenuItems) + { + if (item is NavigationViewItem navItem && navItem.Tag as string == tag) + { + return navItem; + } + } + + return null; + } + #endregion + + #region 搜索框逻辑优化 + private void OnControlsSearchBoxQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + if (string.IsNullOrWhiteSpace(args.QueryText)) return; + + string query = args.QueryText.Trim().ToLower(); + var allNavItems = GetAllNavigationItems(); + + var targetItem = allNavItems.FirstOrDefault(item => + item.Content?.ToString().ToLower().Contains(query) == true); + + if (targetItem != null) + { + NavigationViewControl.SelectedItem = targetItem; + } + } + + private void OnControlsSearchBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason != AutoSuggestionBoxTextChangeReason.UserInput) return; + + string query = sender.Text.Trim().ToLower(); + var suggestions = new List(); + + if (!string.IsNullOrEmpty(query)) + { + var allNavItems = GetAllNavigationItems(); + suggestions = allNavItems + .Where(item => item.Content?.ToString().ToLower().Contains(query) == true) + .Select(item => item.Content.ToString()) + .ToList(); + } + + sender.ItemsSource = suggestions; + } + + // 统一获取所有导航项(主菜单+子菜单+底部菜单+插件页面) + private List GetAllNavigationItems() + { + var items = new List(); + + // 主菜单+子菜单 + foreach (var item in NavigationViewControl.MenuItems) { if (item is NavigationViewItem navItem) { - if (navItem.Tag as string == tag) + items.Add(navItem); + foreach (var child in navItem.MenuItems) { - return navItem; + if (child is NavigationViewItem childNavItem) + items.Add(childNavItem); } } } - return null; + + // 底部菜单 + foreach (var item in NavigationViewControl.FooterMenuItems) + { + if (item is NavigationViewItem navItem) + items.Add(navItem); + } + + return items; } + #endregion } }