From 61dbcf762ce796dac5597c77bccd91a54ee74ec6 Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sun, 26 Oct 2025 00:21:10 +0800 Subject: [PATCH] =?UTF-8?q?add:=E7=82=B9=E5=90=8D=E5=BF=AB=E6=8A=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/Converters/PositionConverters.cs | 35 +++ Ink Canvas/MainWindow.xaml | 11 +- Ink Canvas/MainWindow.xaml.cs | 62 +++- Ink Canvas/MainWindow_cs/MW_Settings.cs | 14 + Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 2 + Ink Canvas/Resources/Settings.cs | 2 + Ink Canvas/Windows/QuickDrawFloatingButton.cs | 125 ++++++++ .../Windows/QuickDrawFloatingButton.xaml | 43 +++ Ink Canvas/Windows/QuickDrawWindow.cs | 283 ++++++++++++++++++ Ink Canvas/Windows/QuickDrawWindow.xaml | 77 +++++ 10 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 Ink Canvas/Converters/PositionConverters.cs create mode 100644 Ink Canvas/Windows/QuickDrawFloatingButton.cs create mode 100644 Ink Canvas/Windows/QuickDrawFloatingButton.xaml create mode 100644 Ink Canvas/Windows/QuickDrawWindow.cs create mode 100644 Ink Canvas/Windows/QuickDrawWindow.xaml diff --git a/Ink Canvas/Converters/PositionConverters.cs b/Ink Canvas/Converters/PositionConverters.cs new file mode 100644 index 00000000..d97093e4 --- /dev/null +++ b/Ink Canvas/Converters/PositionConverters.cs @@ -0,0 +1,35 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Ink_Canvas.Converters +{ + /// + /// 位置计算转换器 + /// + public class PositionConverters + { + /// + /// 减法转换器 + /// + public class SubtractConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is double baseValue && parameter is string paramStr) + { + if (double.TryParse(paramStr, out double subtractValue)) + { + return baseValue - subtractValue; + } + } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 7238863e..c9d0d120 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -3249,6 +3249,15 @@ FontWeight="Bold" Toggled="ToggleSwitchShowRandomAndSingleDraw_Toggled" /> + + + + @@ -3311,7 +3320,7 @@ VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" /> diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index a14d50ff..d0f2ceaf 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -60,6 +60,9 @@ namespace Ink_Canvas // 悬浮窗拦截管理器 private FloatingWindowInterceptorManager _floatingWindowInterceptorManager; + // 快抽悬浮按钮 + private QuickDrawFloatingButton _quickDrawFloatingButton; + // 设置面板相关状态 private bool userChangedNoFocusModeInSettings; private bool isTemporarilyDisablingNoFocusMode = false; @@ -530,6 +533,9 @@ namespace Ink_Canvas else RadioCrashNoAction.IsChecked = true; + // 显示快抽悬浮按钮 + ShowQuickDrawFloatingButton(); + // 如果当前不是黑板模式,则切换到黑板模式 if (currentMode == 0) { @@ -668,6 +674,19 @@ namespace Ink_Canvas private void Window_Closing(object sender, CancelEventArgs e) { LogHelper.WriteLogToFile("Ink Canvas closing", LogHelper.LogType.Event); + try + { + if (_quickDrawFloatingButton != null) + { + _quickDrawFloatingButton.Close(); + _quickDrawFloatingButton = null; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"关闭快抽悬浮按钮时出错: {ex.Message}", LogHelper.LogType.Error); + } + if (!CloseIsFromButton && Settings.Advanced.IsSecondConfirmWhenShutdownApp) { // 第一个确认对话框 @@ -2509,7 +2528,9 @@ namespace Ink_Canvas BoardHighlighterWidthSlider, InkWidthSlider, InkAlphaSlider, - HighlighterWidthSlider + HighlighterWidthSlider, + MLAvoidanceHistorySlider, + MLAvoidanceWeightSlider }; foreach (var slider in sliders) @@ -3053,6 +3074,45 @@ namespace Ink_Canvas } } + /// + /// 显示快抽悬浮按钮 + /// + private void ShowQuickDrawFloatingButton() + { + try + { + // 检查设置是否启用快抽功能 + if (Settings?.RandSettings?.EnableQuickDraw != true) + { + // 如果设置未启用,确保悬浮按钮被关闭 + if (_quickDrawFloatingButton != null) + { + _quickDrawFloatingButton.Close(); + _quickDrawFloatingButton = null; + } + return; + } + + // 如果已经存在悬浮按钮,先关闭它 + if (_quickDrawFloatingButton != null) + { + _quickDrawFloatingButton.Close(); + _quickDrawFloatingButton = null; + } + + // 创建并显示悬浮按钮 + _quickDrawFloatingButton = new QuickDrawFloatingButton(); + _quickDrawFloatingButton.Show(); + + LogHelper.WriteLogToFile("快抽悬浮按钮已显示", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"显示快抽悬浮按钮失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + #endregion } } diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index c494bda9..4624ac44 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -2769,6 +2769,20 @@ namespace Ink_Canvas SaveSettingsToFile(); } + private void ToggleSwitchEnableQuickDraw_Toggled(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + + // 获取开关状态并保存到设置中 + Settings.RandSettings.EnableQuickDraw = ToggleSwitchEnableQuickDraw.IsOn; + + // 保存设置到文件 + SaveSettingsToFile(); + + // 根据设置状态显示或隐藏快抽悬浮按钮 + ShowQuickDrawFloatingButton(); + } + private void ToggleSwitchExternalCaller_Toggled(object sender, RoutedEventArgs e) { if (!isLoaded) return; diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index 93d50998..85e8ec86 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -876,6 +876,7 @@ namespace Ink_Canvas RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency; RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents; ToggleSwitchShowRandomAndSingleDraw.IsOn = Settings.RandSettings.ShowRandomAndSingleDraw; + ToggleSwitchEnableQuickDraw.IsOn = Settings.RandSettings.EnableQuickDraw; ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand; ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType; RandomDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed; @@ -921,6 +922,7 @@ namespace Ink_Canvas ToggleSwitchDisplayRandWindowNamesInputBtn.IsOn = Settings.RandSettings.DisplayRandWindowNamesInputBtn; RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency; RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents; + ToggleSwitchEnableQuickDraw.IsOn = Settings.RandSettings.EnableQuickDraw; ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand; ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType; ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI; diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index 9929a55d..395ea46d 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -658,6 +658,8 @@ namespace Ink_Canvas public int MLAvoidanceHistoryCount { get; set; } = 20; [JsonProperty("mlAvoidanceWeight")] public double MLAvoidanceWeight { get; set; } = 0.8; + [JsonProperty("enableQuickDraw")] + public bool EnableQuickDraw { get; set; } = true; } public class CustomPickNameBackground diff --git a/Ink Canvas/Windows/QuickDrawFloatingButton.cs b/Ink Canvas/Windows/QuickDrawFloatingButton.cs new file mode 100644 index 00000000..50d96931 --- /dev/null +++ b/Ink Canvas/Windows/QuickDrawFloatingButton.cs @@ -0,0 +1,125 @@ +using Ink_Canvas.Helpers; +using System; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Runtime.InteropServices; +using System.Windows.Threading; + +namespace Ink_Canvas +{ + /// + /// 快抽悬浮按钮 + /// + public partial class QuickDrawFloatingButton : Window + { + public QuickDrawFloatingButton() + { + InitializeComponent(); + + // 设置无焦点状态 + this.Focusable = false; + this.ShowInTaskbar = false; + } + + + private void FloatingButton_Loaded(object sender, RoutedEventArgs e) + { + // 设置位置到屏幕右下角稍微靠近中部 + SetPositionToBottomRight(); + + // 应用置顶 + ApplyFloatingButtonTopmost(); + } + + private void SetPositionToBottomRight() + { + try + { + // 获取主屏幕的工作区域 + var workingArea = SystemParameters.WorkArea; + this.Left = workingArea.Right - this.Width - 0; + this.Top = workingArea.Bottom - this.Height - 200; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"设置悬浮按钮位置失败: {ex.Message}", LogHelper.LogType.Error); + // 如果计算失败,使用默认位置 + this.Left = 720; + this.Top = 400; + } + } + + private void FloatingButton_Click(object sender, MouseButtonEventArgs e) + { + try + { + // 打开快抽窗口 + var quickDrawWindow = new QuickDrawWindow(); + quickDrawWindow.ShowDialog(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"打开快抽窗口失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + + + + #region Win32 API 声明和置顶管理 + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + + private const int GWL_EXSTYLE = -20; + private const int WS_EX_TOPMOST = 0x00000008; + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private const uint SWP_NOMOVE = 0x0002; + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_NOACTIVATE = 0x0010; + private const uint SWP_SHOWWINDOW = 0x0040; + private const uint SWP_NOOWNERZORDER = 0x0200; + + /// + /// 应用悬浮按钮置顶 + /// + private void ApplyFloatingButtonTopmost() + { + try + { + var hwnd = new WindowInteropHelper(this).Handle; + if (hwnd == IntPtr.Zero) return; + + // 强制激活窗口 + Activate(); + Focus(); + + // 设置WPF的Topmost属性 + Topmost = true; + + // 使用Win32 API强制置顶 + // 1. 设置窗口样式为置顶 + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + + // 2. 使用SetWindowPos确保窗口在最顶层 + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + LogHelper.WriteLogToFile("快抽悬浮按钮已应用置顶", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用快抽悬浮按钮置顶失败: {ex.Message}", LogHelper.LogType.Error); + } + } + #endregion + } +} diff --git a/Ink Canvas/Windows/QuickDrawFloatingButton.xaml b/Ink Canvas/Windows/QuickDrawFloatingButton.xaml new file mode 100644 index 00000000..d743c383 --- /dev/null +++ b/Ink Canvas/Windows/QuickDrawFloatingButton.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Ink Canvas/Windows/QuickDrawWindow.cs b/Ink Canvas/Windows/QuickDrawWindow.cs new file mode 100644 index 00000000..eb92a816 --- /dev/null +++ b/Ink Canvas/Windows/QuickDrawWindow.cs @@ -0,0 +1,283 @@ +using Ink_Canvas.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Threading; +using System.Runtime.InteropServices; +using Newtonsoft.Json; +using System.IO; + +namespace Ink_Canvas +{ + /// + /// 快抽窗口 + /// + public partial class QuickDrawWindow : Window + { + private Random random = new Random(); + private int autoCloseWaitTime = 2500; // 自动关闭等待时间(毫秒) + private List nameList = new List(); // 名单列表 + + public QuickDrawWindow() + { + InitializeComponent(); + this.Focusable = false; + this.ShowInTaskbar = false; + InitializeSettings(); + LoadNamesFromFile(); + StartQuickDraw(); + } + + private void InitializeSettings() + { + try + { + if (MainWindow.Settings?.RandSettings != null) + { + autoCloseWaitTime = (int)MainWindow.Settings.RandSettings.RandWindowOnceCloseLatency * 1000; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化快抽窗口设置失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + private void LoadNamesFromFile() + { + try + { + string namesFilePath = App.RootPath + "Names.txt"; + if (File.Exists(namesFilePath)) + { + string content = File.ReadAllText(namesFilePath); + nameList = content.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) + .Select(name => name.Trim()) + .Where(name => !string.IsNullOrEmpty(name)) + .ToList(); + } + else + { + nameList.Clear(); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载名单文件失败: {ex.Message}", LogHelper.LogType.Error); + nameList.Clear(); + } + } + + private void StartQuickDraw() + { + try + { + // 延迟100ms后开始抽选动画 + new System.Threading.Thread(() => + { + System.Threading.Thread.Sleep(100); + Application.Current.Dispatcher.Invoke(() => + { + StartQuickDrawAnimation(); + }); + }).Start(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"开始快抽失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 快抽动画 + /// + private void StartQuickDrawAnimation() + { + const int animationTimes = 100; // 动画次数 + const int sleepTime = 5; // 每次动画间隔(毫秒) + + new System.Threading.Thread(() => + { + if (nameList.Count > 0) + { + // 有名单时,从名单中抽选 + StartNameDrawAnimation(animationTimes, sleepTime); + } + else + { + // 没有名单时,从1-60数字中抽选 + StartNumberDrawAnimation(animationTimes, sleepTime); + } + }).Start(); + } + + /// + /// 名单抽选动画 + /// + private void StartNameDrawAnimation(int animationTimes, int sleepTime) + { + List usedNames = new List(); + + for (int i = 0; i < animationTimes; i++) + { + // 随机选择一个名字进行动画显示,避免立即重复 + string randomName; + do + { + randomName = nameList[random.Next(0, nameList.Count)]; + } while (usedNames.Count > 0 && usedNames[usedNames.Count - 1] == randomName); + + usedNames.Add(randomName); + + Application.Current.Dispatcher.Invoke(() => + { + MainResultDisplay.Text = randomName; + }); + + System.Threading.Thread.Sleep(sleepTime); + } + + // 动画结束,显示最终结果 + Application.Current.Dispatcher.Invoke(() => + { + // 随机选择一个最终名字 + string finalName = nameList[random.Next(0, nameList.Count)]; + MainResultDisplay.Text = finalName; + }); + + // 显示结果后,等待一段时间让用户看到结果,然后关闭窗口 + new System.Threading.Thread(() => + { + System.Threading.Thread.Sleep(autoCloseWaitTime); + Application.Current.Dispatcher.Invoke(() => + { + Close(); + }); + }).Start(); + } + + /// + /// 数字抽选动画 + /// + private void StartNumberDrawAnimation(int animationTimes, int sleepTime) + { + List usedNumbers = new List(); + + for (int i = 0; i < animationTimes; i++) + { + // 随机选择一个数字进行动画显示,避免立即重复 + int randomNumber; + do + { + randomNumber = random.Next(1, 61); // 1-60 + } while (usedNumbers.Count > 0 && usedNumbers[usedNumbers.Count - 1] == randomNumber); + + usedNumbers.Add(randomNumber); + + Application.Current.Dispatcher.Invoke(() => + { + MainResultDisplay.Text = randomNumber.ToString(); + }); + + System.Threading.Thread.Sleep(sleepTime); + } + + // 动画结束,显示最终结果 + Application.Current.Dispatcher.Invoke(() => + { + // 随机选择一个最终数字 + int finalNumber = random.Next(1, 61); + MainResultDisplay.Text = finalNumber.ToString(); + }); + + // 显示结果后,等待一段时间让用户看到结果,然后关闭窗口 + new System.Threading.Thread(() => + { + System.Threading.Thread.Sleep(autoCloseWaitTime); + Application.Current.Dispatcher.Invoke(() => + { + Close(); + }); + }).Start(); + } + + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + // 窗口关闭时的清理工作 + } + + private void WindowDragMove(object sender, MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + DragMove(); + } + + + #region Win32 API 声明和置顶管理 + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + + private const int GWL_EXSTYLE = -20; + private const int WS_EX_TOPMOST = 0x00000008; + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private const uint SWP_NOMOVE = 0x0002; + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_NOACTIVATE = 0x0010; + private const uint SWP_SHOWWINDOW = 0x0040; + private const uint SWP_NOOWNERZORDER = 0x0200; + + /// + /// 应用快抽窗口置顶 + /// + private void ApplyQuickDrawWindowTopmost() + { + try + { + var hwnd = new WindowInteropHelper(this).Handle; + if (hwnd == IntPtr.Zero) return; + + // 设置WPF的Topmost属性 + Topmost = true; + + // 使用Win32 API强制置顶 + // 1. 设置窗口样式为置顶 + int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST); + + // 2. 使用SetWindowPos确保窗口在最顶层 + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + + LogHelper.WriteLogToFile("快抽窗口已应用置顶", LogHelper.LogType.Trace); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用快抽窗口置顶失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 窗口加载事件处理,确保置顶 + /// + private void QuickDrawWindow_Loaded(object sender, RoutedEventArgs e) + { + // 使用延迟确保窗口完全加载后再应用置顶 + Dispatcher.BeginInvoke(new Action(() => + { + ApplyQuickDrawWindowTopmost(); + }), DispatcherPriority.Loaded); + } + #endregion + } +} diff --git a/Ink Canvas/Windows/QuickDrawWindow.xaml b/Ink Canvas/Windows/QuickDrawWindow.xaml new file mode 100644 index 00000000..67891ed8 --- /dev/null +++ b/Ink Canvas/Windows/QuickDrawWindow.xaml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +