diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt
index 68fc7c72..80c8c54c 100644
--- a/AutomaticUpdateVersionControl.txt
+++ b/AutomaticUpdateVersionControl.txt
@@ -1 +1 @@
-1.7.15.0
+1.7.16.0
diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs
index cbf311f0..25087510 100644
--- a/Ink Canvas/AssemblyInfo.cs
+++ b/Ink Canvas/AssemblyInfo.cs
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.7.15.0")]
-[assembly: AssemblyFileVersion("1.7.15.0")]
+[assembly: AssemblyVersion("1.7.16.0")]
+[assembly: AssemblyFileVersion("1.7.16.0")]
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 48a621a3..c9d0d120 100644
--- a/Ink Canvas/MainWindow.xaml
+++ b/Ink Canvas/MainWindow.xaml
@@ -3249,6 +3249,15 @@
FontWeight="Bold"
Toggled="ToggleSwitchShowRandomAndSingleDraw_Toggled" />
+
+
+
+
@@ -3299,6 +3308,62 @@
FontFamily="Consolas"
Text="{Binding ElementName=RandWindowOnceMaxStudentsSlider, Path=Value, Converter={StaticResource IntNumberToString}}" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /// 显示快抽悬浮按钮
+ ///
+ 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_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 69ff3c99..86d0cd27 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -1085,11 +1085,20 @@ namespace Ink_Canvas
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
- var randWindow = new RandWindow(Settings);
- randWindow.Show();
+ // 根据设置决定使用哪个点名窗口
+ if (Settings.RandSettings.UseNewRollCallUI)
+ {
+ // 使用新点名UI - 随机抽模式
+ new NewStyleRollCallWindow(Settings, false).ShowDialog();
+ }
+ else
+ {
+ // 使用默认的随机点名窗口
+ var randWindow = new RandWindow(Settings);
+ randWindow.Show();
- // 使用延迟确保窗口完全显示后再强制置顶
- randWindow.Dispatcher.BeginInvoke(new Action(() =>
+ // 使用延迟确保窗口完全显示后再强制置顶
+ randWindow.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
@@ -1126,6 +1135,7 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile($"强制置顶RandWindow失败: {ex.Message}", LogHelper.LogType.Error);
}
}), DispatcherPriority.Loaded);
+ }
}
public void CheckEraserTypeTab()
@@ -1232,14 +1242,30 @@ namespace Ink_Canvas
{
MessageBox.Show("无法调用外部点名:" + ex.Message);
- // 调用失败时回退到默认的随机点名窗口
- new RandWindow(Settings, true).ShowDialog();
+ // 调用失败时回退到相应的点名窗口
+ if (Settings.RandSettings.UseNewRollCallUI)
+ {
+ new NewStyleRollCallWindow(Settings, true).ShowDialog(); // 单次抽模式
+ }
+ else
+ {
+ new RandWindow(Settings, true).ShowDialog();
+ }
}
}
else
{
- // 使用默认的随机点名窗口
- new RandWindow(Settings, true).ShowDialog();
+ // 根据设置决定使用哪个点名窗口
+ if (Settings.RandSettings.UseNewRollCallUI)
+ {
+ // 使用新点名UI - 单次抽模式
+ new NewStyleRollCallWindow(Settings, true).ShowDialog();
+ }
+ else
+ {
+ // 使用默认的随机点名窗口
+ new RandWindow(Settings, true).ShowDialog();
+ }
}
}
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index e5e06c8f..4624ac44 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -2671,6 +2671,35 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
+ // 新点名UI设置事件处理
+ private void ToggleSwitchUseNewRollCallUI_Toggled(object sender, RoutedEventArgs e)
+ {
+ if (!isLoaded) return;
+ Settings.RandSettings.UseNewRollCallUI = ToggleSwitchUseNewRollCallUI.IsOn;
+ SaveSettingsToFile();
+ }
+
+ private void ToggleSwitchEnableMLAvoidance_Toggled(object sender, RoutedEventArgs e)
+ {
+ if (!isLoaded) return;
+ Settings.RandSettings.EnableMLAvoidance = ToggleSwitchEnableMLAvoidance.IsOn;
+ SaveSettingsToFile();
+ }
+
+ private void MLAvoidanceHistorySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ if (!isLoaded) return;
+ Settings.RandSettings.MLAvoidanceHistoryCount = (int)MLAvoidanceHistorySlider.Value;
+ SaveSettingsToFile();
+ }
+
+ private void MLAvoidanceWeightSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ if (!isLoaded) return;
+ Settings.RandSettings.MLAvoidanceWeight = MLAvoidanceWeightSlider.Value;
+ SaveSettingsToFile();
+ }
+
private void ProgressiveReminderVolumeSlider_ValueChanged(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
@@ -2740,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 c9699bb8..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;
@@ -886,6 +887,12 @@ namespace Ink_Canvas
ToggleSwitchUseNewStyleUI.IsOn = Settings.RandSettings.UseNewStyleUI;
ToggleSwitchEnableOvertimeCountUp.IsOn = Settings.RandSettings.EnableOvertimeCountUp;
+ // 新点名UI设置
+ ToggleSwitchUseNewRollCallUI.IsOn = Settings.RandSettings.UseNewRollCallUI;
+ ToggleSwitchEnableMLAvoidance.IsOn = Settings.RandSettings.EnableMLAvoidance;
+ MLAvoidanceHistorySlider.Value = Settings.RandSettings.MLAvoidanceHistoryCount;
+ MLAvoidanceWeightSlider.Value = Settings.RandSettings.MLAvoidanceWeight;
+
bool canEnableRedText = Settings.RandSettings.EnableOvertimeCountUp && Settings.RandSettings.EnableOvertimeRedText;
ToggleSwitchEnableOvertimeRedText.IsOn = canEnableRedText;
if (!canEnableRedText)
@@ -915,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/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index cbf311f0..25087510 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/AssemblyInfo.cs
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.7.15.0")]
-[assembly: AssemblyFileVersion("1.7.15.0")]
+[assembly: AssemblyVersion("1.7.16.0")]
+[assembly: AssemblyFileVersion("1.7.16.0")]
diff --git a/Ink Canvas/Resources/NewRollCallWindowResources.xaml b/Ink Canvas/Resources/NewRollCallWindowResources.xaml
new file mode 100644
index 00000000..0eceb7f8
--- /dev/null
+++ b/Ink Canvas/Resources/NewRollCallWindowResources.xaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs
index ba633b85..395ea46d 100644
--- a/Ink Canvas/Resources/Settings.cs
+++ b/Ink Canvas/Resources/Settings.cs
@@ -650,6 +650,16 @@ namespace Ink_Canvas
public double ProgressiveReminderVolume { get; set; } = 1.0;
[JsonProperty("progressiveReminderSoundPath")]
public string ProgressiveReminderSoundPath { get; set; } = "";
+ [JsonProperty("useNewRollCallUI")]
+ public bool UseNewRollCallUI { get; set; } = true;
+ [JsonProperty("enableMLAvoidance")]
+ public bool EnableMLAvoidance { get; set; } = true;
+ [JsonProperty("mlAvoidanceHistoryCount")]
+ 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/Resources/Styles/Dark.xaml b/Ink Canvas/Resources/Styles/Dark.xaml
index fb1ee834..b9761ed8 100644
--- a/Ink Canvas/Resources/Styles/Dark.xaml
+++ b/Ink Canvas/Resources/Styles/Dark.xaml
@@ -88,8 +88,8 @@
-
-
+
+
diff --git a/Ink Canvas/Windows/NamesInputWindow.xaml b/Ink Canvas/Windows/NamesInputWindow.xaml
index f397f495..0c9add60 100644
--- a/Ink Canvas/Windows/NamesInputWindow.xaml
+++ b/Ink Canvas/Windows/NamesInputWindow.xaml
@@ -5,15 +5,40 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Ink_Canvas"
mc:Ignorable="d" FontFamily="Microsoft YaHei UI" ui:WindowHelper.UseModernWindowStyle="True"
- ui:ThemeManager.RequestedTheme="Light" WindowStartupLocation="CenterScreen"
+ WindowStartupLocation="CenterScreen"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" Topmost="True"
Title="Ink Canvas 抽奖 - 名单导入" Height="500" Width="400"
Loaded="Window_Loaded" Closing="Window_Closing">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ink Canvas/Windows/NamesInputWindow.xaml.cs b/Ink Canvas/Windows/NamesInputWindow.xaml.cs
index ab020adc..feac031c 100644
--- a/Ink Canvas/Windows/NamesInputWindow.xaml.cs
+++ b/Ink Canvas/Windows/NamesInputWindow.xaml.cs
@@ -1,7 +1,10 @@
using Ink_Canvas.Helpers;
+using System;
using System.ComponentModel;
using System.IO;
using System.Windows;
+using System.Windows.Media;
+using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
namespace Ink_Canvas
{
@@ -14,6 +17,7 @@ namespace Ink_Canvas
{
InitializeComponent();
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
+ ApplyTheme();
}
string originText = "";
@@ -43,5 +47,111 @@ namespace Ink_Canvas
{
Close();
}
+
+ private void ApplyTheme()
+ {
+ try
+ {
+ if (MainWindow.Settings != null)
+ {
+ ApplyTheme(MainWindow.Settings);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用名单导入窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void ApplyTheme(Settings settings)
+ {
+ try
+ {
+ if (settings.Appearance.Theme == 0) // 浅色主题
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
+ ApplyThemeResources("Light");
+ }
+ else if (settings.Appearance.Theme == 1) // 深色主题
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
+ ApplyThemeResources("Dark");
+ }
+ else // 跟随系统主题
+ {
+ bool isSystemLight = IsSystemThemeLight();
+ if (isSystemLight)
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
+ ApplyThemeResources("Light");
+ }
+ else
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
+ ApplyThemeResources("Dark");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用名单导入窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void ApplyThemeResources(string theme)
+ {
+ try
+ {
+ var resources = this.Resources;
+
+ if (theme == "Light")
+ {
+ // 应用浅色主题资源
+ resources["NamesInputWindowBackground"] = new SolidColorBrush(Color.FromRgb(255, 255, 255));
+ resources["NamesInputWindowForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
+ resources["NamesInputWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(244, 244, 245));
+ resources["NamesInputWindowButtonForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
+ resources["NamesInputWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(228, 228, 231));
+ }
+ else
+ {
+ // 应用深色主题资源 - 与新计时器窗口统一
+ resources["NamesInputWindowBackground"] = new SolidColorBrush(Color.FromRgb(31, 31, 31)); // #1f1f1f
+ resources["NamesInputWindowForeground"] = new SolidColorBrush(Colors.White);
+ resources["NamesInputWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(42, 42, 42)); // #2a2a2a
+ resources["NamesInputWindowButtonForeground"] = new SolidColorBrush(Colors.White);
+ resources["NamesInputWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(224, 224, 224)); // #E0E0E0
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用名单导入窗口主题资源出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private bool IsSystemThemeLight()
+ {
+ var light = false;
+ try
+ {
+ var registryKey = Microsoft.Win32.Registry.CurrentUser;
+ var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
+ if (themeKey != null)
+ {
+ var value = themeKey.GetValue("AppsUseLightTheme");
+ if (value != null)
+ {
+ light = (int)value == 1;
+ }
+ themeKey.Close();
+ }
+ }
+ catch
+ {
+ // 如果无法读取注册表,默认使用浅色主题
+ light = true;
+ }
+ return light;
+ }
}
}
diff --git a/Ink Canvas/Windows/NewStyleRollCallWindow.cs b/Ink Canvas/Windows/NewStyleRollCallWindow.cs
new file mode 100644
index 00000000..af7b5435
--- /dev/null
+++ b/Ink Canvas/Windows/NewStyleRollCallWindow.cs
@@ -0,0 +1,1589 @@
+using Ink_Canvas.Helpers;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Media;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using System.Windows.Threading;
+using Newtonsoft.Json;
+using System.Runtime.InteropServices;
+using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
+
+namespace Ink_Canvas
+{
+ ///
+ /// 最近点名记录数据模型
+ ///
+ public class RecentRollCallData
+ {
+ public List RecentNames { get; set; } = new List();
+ public DateTime LastUpdate { get; set; } = DateTime.Now;
+ }
+
+ ///
+ /// 点名历史记录数据模型
+ ///
+ public class RollCallHistoryData
+ {
+ public List History { get; set; } = new List();
+ public Dictionary NameFrequency { get; set; } = new Dictionary();
+ public DateTime LastUpdate { get; set; } = DateTime.Now;
+ }
+
+ ///
+ /// 新点名UI风格的窗口
+ ///
+ public partial class NewStyleRollCallWindow : Window
+ {
+ public NewStyleRollCallWindow()
+ {
+ InitializeComponent();
+ AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
+
+ InitializeUI();
+ ApplyTheme(MainWindow.Settings);
+
+ // 初始化点名相关变量
+ InitializeRollCallData();
+ }
+
+ public NewStyleRollCallWindow(bool isSingleDraw)
+ {
+ InitializeComponent();
+ AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
+
+ // 设置单次抽模式
+ isSingleDrawMode = isSingleDraw;
+
+ InitializeUI();
+ ApplyTheme(MainWindow.Settings);
+
+ // 初始化点名相关变量
+ InitializeRollCallData();
+
+ // 单次抽模式:自动开始抽选
+ if (isSingleDrawMode)
+ {
+ // 延迟100ms后自动开始抽选
+ new System.Threading.Thread(() =>
+ {
+ System.Threading.Thread.Sleep(100);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ StartSingleDraw();
+ });
+ }).Start();
+ }
+ }
+
+ public NewStyleRollCallWindow(Settings settings, bool isSingleDraw = false)
+ {
+ InitializeComponent();
+ AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
+
+ // 保存设置
+ this.settings = settings;
+
+ // 设置单次抽模式
+ isSingleDrawMode = isSingleDraw;
+
+ InitializeUI();
+ ApplyTheme(settings);
+
+ // 初始化设置
+ InitializeSettings();
+
+ // 初始化点名相关变量
+ InitializeRollCallData();
+
+ // 单次抽模式:自动开始抽选
+ if (isSingleDrawMode)
+ {
+ // 延迟100ms后自动开始抽选
+ new System.Threading.Thread(() =>
+ {
+ System.Threading.Thread.Sleep(100);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ StartSingleDraw();
+ });
+ }).Start();
+ }
+ }
+
+ #region 私有字段
+ private List nameList = new List();
+ private List recentNames = new List();
+ private int currentCount = 1;
+ private bool isRollCalling = false;
+ private Timer rollCallTimer;
+ private Random random = new Random();
+ private DateTime lastActivityTime = DateTime.Now;
+
+ // 机器学习相关
+ private RollCallHistoryData historyData = new RollCallHistoryData();
+ private int maxRecentHistory = 20;
+ private double avoidanceWeight = 0.8; // 避免重复的权重
+ private const double FREQUENCY_WEIGHT = 0.2; // 频率平衡的权重
+
+ // 单次抽相关
+ private bool isSingleDrawMode = false;
+ private Random singleDrawRandom = new Random();
+
+ // 设置相关
+ private Settings settings;
+ private int autoCloseWaitTime = 2500; // 自动关闭等待时间(毫秒)
+
+ // 点名模式
+ private string selectedRollCallMode = "Random"; // 默认随机点名
+
+ // JSON文件路径
+ private static readonly string ConfigsFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs");
+ private static readonly string RollCallHistoryJsonPath = System.IO.Path.Combine(ConfigsFolder, "RollCallHistory.json");
+ #endregion
+
+ #region 初始化方法
+ private void InitializeUI()
+ {
+ UpdateCountDisplay();
+ LoadNamesFromFile();
+ UpdateListCountDisplay();
+ LoadRollCallHistory();
+ LoadSettings();
+ InitializeSingleDrawMode();
+ InitializeModeSelection();
+ InitializeExternalCallerSelection();
+ }
+
+ private void InitializeSingleDrawMode()
+ {
+ if (isSingleDrawMode)
+ {
+ // 单次抽模式:使用60个数字
+ MainResultDisplay.Text = "准备抽选...";
+ StatusDisplay.Text = "单次抽模式 - 60个数字";
+ CountDisplay.Text = "1";
+ CountMinusBtn.IsEnabled = true;
+ CountPlusBtn.IsEnabled = true;
+ }
+ else
+ {
+ // 普通点名模式
+ MainResultDisplay.Text = "点击开始点名";
+ StatusDisplay.Text = "准备就绪";
+ CountDisplay.Text = "1";
+ CountMinusBtn.IsEnabled = true;
+ CountPlusBtn.IsEnabled = true;
+ }
+ }
+
+ private void InitializeModeSelection()
+ {
+ try
+ {
+ SetModeSelection("Random");
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"初始化点名模式选择控件时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void InitializeExternalCallerSelection()
+ {
+ try
+ {
+ SetExternalCallerSelection("ClassIsland");
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"初始化外部点名选择控件时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void InitializeSettings()
+ {
+ try
+ {
+ if (settings?.RandSettings != null)
+ {
+ // 使用老点名UI的设置
+ autoCloseWaitTime = (int)settings.RandSettings.RandWindowOnceCloseLatency * 1000;
+ maxRecentHistory = settings.RandSettings.MLAvoidanceHistoryCount;
+ avoidanceWeight = settings.RandSettings.MLAvoidanceWeight;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"初始化点名设置时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void LoadSettings()
+ {
+ try
+ {
+ if (MainWindow.Settings?.RandSettings != null)
+ {
+ maxRecentHistory = MainWindow.Settings.RandSettings.MLAvoidanceHistoryCount;
+ avoidanceWeight = MainWindow.Settings.RandSettings.MLAvoidanceWeight;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"加载点名设置时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void InitializeRollCallData()
+ {
+ // 初始化点名定时器
+ rollCallTimer = new Timer(100);
+ rollCallTimer.Elapsed += RollCallTimer_Elapsed;
+ }
+
+ private void ApplyTheme()
+ {
+ try
+ {
+ // 应用主题设置
+ if (MainWindow.Settings != null)
+ {
+ ApplyTheme(MainWindow.Settings);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用新点名UI窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void ApplyTheme(Settings settings)
+ {
+ try
+ {
+ if (settings.Appearance.Theme == 0) // 浅色主题
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
+ ApplyThemeResources("Light");
+ }
+ else if (settings.Appearance.Theme == 1) // 深色主题
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
+ ApplyThemeResources("Dark");
+ SetDarkThemeBorder();
+ }
+ else // 跟随系统主题
+ {
+ bool isSystemLight = IsSystemThemeLight();
+ if (isSystemLight)
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light);
+ ApplyThemeResources("Light");
+ }
+ else
+ {
+ iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark);
+ ApplyThemeResources("Dark");
+ SetDarkThemeBorder();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用新点名UI窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ ///
+ /// 应用主题资源
+ ///
+ /// 主题类型(Light或Dark)
+ private void ApplyThemeResources(string theme)
+ {
+ try
+ {
+ // 更新窗口资源
+ var resources = this.Resources;
+
+ if (theme == "Light")
+ {
+ // 应用浅色主题资源
+ resources["NewRollCallWindowBackground"] = new SolidColorBrush(Color.FromRgb(255, 255, 255));
+ resources["NewRollCallWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(228, 228, 231));
+ resources["NewRollCallWindowTitleForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
+ resources["NewRollCallWindowDigitForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
+ resources["NewRollCallWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(244, 244, 245));
+ resources["NewRollCallWindowButtonForeground"] = new SolidColorBrush(Color.FromRgb(24, 24, 27));
+ resources["NewRollCallWindowPrimaryButtonBackground"] = new SolidColorBrush(Color.FromRgb(76, 175, 80)); // #4CAF50
+ resources["NewRollCallWindowPrimaryButtonForeground"] = new SolidColorBrush(Color.FromRgb(255, 255, 255));
+ resources["NewRollCallWindowSecondaryTextForeground"] = new SolidColorBrush(Color.FromRgb(113, 113, 122));
+ }
+ else
+ {
+ // 应用深色主题资源 - 与新计时器窗口统一
+ resources["NewRollCallWindowBackground"] = new SolidColorBrush(Color.FromRgb(31, 31, 31)); // #1f1f1f
+ resources["NewRollCallWindowBorderBrush"] = new SolidColorBrush(Color.FromRgb(224, 224, 224)); // #E0E0E0
+ resources["NewRollCallWindowTitleForeground"] = new SolidColorBrush(Colors.White);
+ resources["NewRollCallWindowDigitForeground"] = new SolidColorBrush(Colors.White);
+ resources["NewRollCallWindowButtonBackground"] = new SolidColorBrush(Color.FromRgb(42, 42, 42)); // #2a2a2a
+ resources["NewRollCallWindowButtonForeground"] = new SolidColorBrush(Colors.White);
+ resources["NewRollCallWindowPrimaryButtonBackground"] = new SolidColorBrush(Color.FromRgb(76, 175, 80)); // #4CAF50
+ resources["NewRollCallWindowPrimaryButtonForeground"] = new SolidColorBrush(Colors.White);
+ resources["NewRollCallWindowSecondaryTextForeground"] = new SolidColorBrush(Color.FromRgb(156, 163, 175)); // #9ca3af
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"应用新点名UI窗口主题资源出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private bool IsSystemThemeLight()
+ {
+ var light = false;
+ try
+ {
+ var registryKey = Microsoft.Win32.Registry.CurrentUser;
+ var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
+ if (themeKey != null)
+ {
+ var value = themeKey.GetValue("AppsUseLightTheme");
+ if (value != null)
+ {
+ light = (int)value == 1;
+ }
+ themeKey.Close();
+ }
+ }
+ catch
+ {
+ light = true;
+ }
+ return light;
+ }
+ #endregion
+
+ #region 点名模式逻辑
+ ///
+ /// 根据选择的模式进行点名
+ ///
+ private List SelectNamesByMode(List availableNames, int count)
+ {
+ switch (selectedRollCallMode)
+ {
+ case "Random":
+ return SelectNamesWithML(availableNames, count);
+ case "Sequential":
+ return SelectNamesSequentially(availableNames, count);
+ case "Group":
+ return SelectNamesInGroups(availableNames, count);
+ default:
+ return SelectNamesWithML(availableNames, count);
+ }
+ }
+
+ ///
+ /// 顺序点名:按名单顺序选择
+ ///
+ private List SelectNamesSequentially(List availableNames, int count)
+ {
+ if (availableNames.Count == 0) return new List();
+
+ var selectedNames = new List();
+ int startIndex = 0;
+
+ // 从历史记录中找到上次选择的位置
+ if (historyData.History != null && historyData.History.Count > 0)
+ {
+ string lastSelected = historyData.History.LastOrDefault();
+ if (!string.IsNullOrEmpty(lastSelected))
+ {
+ int lastIndex = availableNames.IndexOf(lastSelected);
+ if (lastIndex >= 0)
+ {
+ startIndex = (lastIndex + 1) % availableNames.Count;
+ }
+ }
+ }
+
+ for (int i = 0; i < count && i < availableNames.Count; i++)
+ {
+ int index = (startIndex + i) % availableNames.Count;
+ selectedNames.Add(availableNames[index]);
+ }
+
+ return selectedNames;
+ }
+
+ ///
+ /// 分组点名:将名单分成若干组,每组选择一个人
+ ///
+ private List SelectNamesInGroups(List availableNames, int count)
+ {
+ if (availableNames.Count == 0) return new List();
+
+ var selectedNames = new List();
+ int groupSize = Math.Max(1, availableNames.Count / count);
+
+ for (int i = 0; i < count && i * groupSize < availableNames.Count; i++)
+ {
+ int startIndex = i * groupSize;
+ int endIndex = Math.Min(startIndex + groupSize, availableNames.Count);
+
+ // 从当前组中随机选择一个人
+ var group = availableNames.GetRange(startIndex, endIndex - startIndex);
+ if (group.Count > 0)
+ {
+ int randomIndex = random.Next(0, group.Count);
+ selectedNames.Add(group[randomIndex]);
+ }
+ }
+
+ return selectedNames;
+ }
+ #endregion
+
+ #region 机器学习避免重复逻辑
+ ///
+ /// 使用机器学习算法选择点名人员,避免最近重复
+ ///
+ /// 可用名单
+ /// 需要选择的人数
+ /// 选择的人员名单
+ private List SelectNamesWithML(List availableNames, int count)
+ {
+ if (availableNames == null || availableNames.Count == 0)
+ return new List();
+
+ // 检查是否启用机器学习避免重复
+ bool enableML = MainWindow.Settings?.RandSettings?.EnableMLAvoidance ?? true;
+ if (!enableML)
+ {
+ // 如果禁用机器学习,使用简单随机选择
+ return SelectNamesRandomly(availableNames, count);
+ }
+
+ var selectedNames = new List();
+ var remainingNames = new List(availableNames);
+
+ for (int i = 0; i < count && remainingNames.Count > 0; i++)
+ {
+ string selectedName = SelectSingleNameWithML(remainingNames, selectedNames);
+ if (!string.IsNullOrEmpty(selectedName))
+ {
+ selectedNames.Add(selectedName);
+ remainingNames.Remove(selectedName);
+ }
+ }
+
+ return selectedNames;
+ }
+
+ ///
+ /// 简单随机选择点名人员
+ ///
+ private List SelectNamesRandomly(List availableNames, int count)
+ {
+ if (availableNames == null || availableNames.Count == 0)
+ return new List();
+
+ var selectedNames = new List();
+ var remainingNames = new List(availableNames);
+
+ for (int i = 0; i < count && remainingNames.Count > 0; i++)
+ {
+ int randomIndex = random.Next(remainingNames.Count);
+ selectedNames.Add(remainingNames[randomIndex]);
+ remainingNames.RemoveAt(randomIndex);
+ }
+
+ return selectedNames;
+ }
+
+ ///
+ /// 使用机器学习算法选择单个人员
+ ///
+ private string SelectSingleNameWithML(List availableNames, List alreadySelected)
+ {
+ if (availableNames.Count == 0) return null;
+ if (availableNames.Count == 1) return availableNames[0];
+
+ // 计算每个人员的权重
+ var nameWeights = new Dictionary();
+
+ foreach (string name in availableNames)
+ {
+ if (alreadySelected.Contains(name)) continue;
+
+ double weight = 1.0; // 基础权重
+
+ // 1. 避免最近重复的权重计算
+ double recentAvoidanceWeight = CalculateRecentAvoidanceWeight(name);
+ weight *= (1.0 - recentAvoidanceWeight * avoidanceWeight);
+
+ // 2. 频率平衡权重计算
+ double frequencyWeight = CalculateFrequencyWeight(name);
+ weight *= (1.0 + frequencyWeight * FREQUENCY_WEIGHT);
+
+ // 3. 确保权重不为负数
+ weight = Math.Max(weight, 0.1);
+
+ nameWeights[name] = weight;
+ }
+
+ // 使用加权随机选择
+ return WeightedRandomSelection(nameWeights);
+ }
+
+ ///
+ /// 计算避免最近重复的权重
+ ///
+ private double CalculateRecentAvoidanceWeight(string name)
+ {
+ if (historyData.History == null || historyData.History.Count == 0)
+ return 0.0;
+
+ // 获取最近记录
+ var recentHistory = historyData.History.Skip(Math.Max(0, historyData.History.Count - maxRecentHistory)).ToList();
+ int recentCount = recentHistory.Count(n => n == name);
+
+ // 计算权重:最近出现次数越多,权重越高(越应该避免)
+ return (double)recentCount / Math.Min(recentHistory.Count, maxRecentHistory);
+ }
+
+ ///
+ /// 计算频率平衡权重
+ ///
+ private double CalculateFrequencyWeight(string name)
+ {
+ if (historyData.NameFrequency == null || !historyData.NameFrequency.ContainsKey(name))
+ return 0.5; // 如果从未被选中,给予中等权重
+
+ int totalSelections = historyData.NameFrequency.Values.Sum();
+ if (totalSelections == 0) return 0.5;
+
+ int nameCount = historyData.NameFrequency[name];
+ double frequency = (double)nameCount / totalSelections;
+
+ // 频率越低,权重越高(越应该被选中)
+ return 1.0 - frequency;
+ }
+
+ ///
+ /// 加权随机选择
+ ///
+ private string WeightedRandomSelection(Dictionary nameWeights)
+ {
+ if (nameWeights.Count == 0) return null;
+
+ double totalWeight = nameWeights.Values.Sum();
+ if (totalWeight <= 0) return nameWeights.Keys.First();
+
+ double randomValue = random.NextDouble() * totalWeight;
+ double currentWeight = 0;
+
+ foreach (var kvp in nameWeights)
+ {
+ currentWeight += kvp.Value;
+ if (randomValue <= currentWeight)
+ {
+ return kvp.Key;
+ }
+ }
+
+ return nameWeights.Keys.Last();
+ }
+
+ ///
+ /// 更新点名历史记录
+ ///
+ private void UpdateRollCallHistory(List selectedNames)
+ {
+ if (selectedNames == null || selectedNames.Count == 0) return;
+
+ // 更新历史记录
+ if (historyData.History == null)
+ historyData.History = new List();
+
+ historyData.History.AddRange(selectedNames);
+
+ // 保持历史记录不超过100条
+ if (historyData.History.Count > 100)
+ {
+ historyData.History = historyData.History.Skip(historyData.History.Count - 100).ToList();
+ }
+
+ // 更新频率统计
+ if (historyData.NameFrequency == null)
+ historyData.NameFrequency = new Dictionary();
+
+ foreach (string name in selectedNames)
+ {
+ if (historyData.NameFrequency.ContainsKey(name))
+ historyData.NameFrequency[name]++;
+ else
+ historyData.NameFrequency[name] = 1;
+ }
+
+ historyData.LastUpdate = DateTime.Now;
+
+ // 保存到文件
+ SaveRollCallHistory();
+ }
+ #endregion
+
+ #region 数据持久化
+ ///
+ /// 加载点名历史记录
+ ///
+ private void LoadRollCallHistory()
+ {
+ try
+ {
+ if (!Directory.Exists(ConfigsFolder))
+ {
+ Directory.CreateDirectory(ConfigsFolder);
+ }
+
+ if (!File.Exists(RollCallHistoryJsonPath))
+ {
+ historyData = new RollCallHistoryData();
+ return;
+ }
+
+ string jsonContent = File.ReadAllText(RollCallHistoryJsonPath);
+ var data = JsonConvert.DeserializeObject(jsonContent);
+
+ if (data != null)
+ {
+ historyData = data;
+ }
+ else
+ {
+ historyData = new RollCallHistoryData();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"加载点名历史记录失败: {ex.Message}", LogHelper.LogType.Error);
+ historyData = new RollCallHistoryData();
+ }
+ }
+
+ ///
+ /// 保存点名历史记录
+ ///
+ private void SaveRollCallHistory()
+ {
+ try
+ {
+ if (!Directory.Exists(ConfigsFolder))
+ {
+ Directory.CreateDirectory(ConfigsFolder);
+ }
+
+ string jsonContent = JsonConvert.SerializeObject(historyData, Formatting.Indented);
+ File.WriteAllText(RollCallHistoryJsonPath, jsonContent);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"保存点名历史记录失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+ #endregion
+
+ #region UI更新方法
+ private void UpdateCountDisplay()
+ {
+ CountDisplay.Text = currentCount.ToString();
+ }
+
+ private void UpdateListCountDisplay()
+ {
+ ListCountDisplay.Text = $"名单人数: {nameList.Count}";
+ }
+
+ private void UpdateStatusDisplay(string status)
+ {
+ StatusDisplay.Text = status;
+ }
+
+ private void ShowResults(List results)
+ {
+ if (results == null || results.Count == 0)
+ {
+ MainResultDisplay.Text = "无结果";
+ MultiResultScrollViewer.Visibility = Visibility.Collapsed;
+ return;
+ }
+
+ if (results.Count == 1)
+ {
+ MainResultDisplay.Text = results[0];
+ MainResultDisplay.Visibility = Visibility.Visible;
+ MultiResultScrollViewer.Visibility = Visibility.Collapsed;
+ }
+ else
+ {
+ // 多个结果时,隐藏主显示区域,显示多结果区域
+ MainResultDisplay.Text = "";
+ MainResultDisplay.Visibility = Visibility.Collapsed;
+ MultiResultScrollViewer.Visibility = Visibility.Visible;
+
+ // 显示所有结果(最多20个)
+ Result1Display.Text = results.Count > 0 ? results[0] : "";
+ Result2Display.Text = results.Count > 1 ? results[1] : "";
+ Result3Display.Text = results.Count > 2 ? results[2] : "";
+ Result4Display.Text = results.Count > 3 ? results[3] : "";
+ Result5Display.Text = results.Count > 4 ? results[4] : "";
+ Result6Display.Text = results.Count > 5 ? results[5] : "";
+ Result7Display.Text = results.Count > 6 ? results[6] : "";
+ Result8Display.Text = results.Count > 7 ? results[7] : "";
+ Result9Display.Text = results.Count > 8 ? results[8] : "";
+ Result10Display.Text = results.Count > 9 ? results[9] : "";
+ Result11Display.Text = results.Count > 10 ? results[10] : "";
+ Result12Display.Text = results.Count > 11 ? results[11] : "";
+ Result13Display.Text = results.Count > 12 ? results[12] : "";
+ Result14Display.Text = results.Count > 13 ? results[13] : "";
+ Result15Display.Text = results.Count > 14 ? results[14] : "";
+ Result16Display.Text = results.Count > 15 ? results[15] : "";
+ Result17Display.Text = results.Count > 16 ? results[16] : "";
+ Result18Display.Text = results.Count > 17 ? results[17] : "";
+ Result19Display.Text = results.Count > 18 ? results[18] : "";
+ Result20Display.Text = results.Count > 19 ? results[19] : "";
+ }
+ }
+ #endregion
+
+ #region 事件处理方法
+ private void CountPlus_Click(object sender, RoutedEventArgs e)
+ {
+ if (isRollCalling) return;
+
+ // 获取老点名UI的设置
+ int maxPeopleLimit = settings?.RandSettings?.RandWindowOnceMaxStudents ?? 10;
+
+ if (isSingleDrawMode)
+ {
+ // 单次抽模式:最多选择60个数字,但受设置限制
+ int maxCount = Math.Min(maxPeopleLimit, 60);
+ currentCount = Math.Min(currentCount + 1, maxCount);
+ }
+ else
+ {
+ // 普通点名模式:根据是否有名单决定上限
+ int maxCount;
+ if (nameList.Count == 0)
+ {
+ // 没有名单时,使用60个数字
+ maxCount = Math.Min(maxPeopleLimit, 60);
+ }
+ else
+ {
+ // 有名单时,使用名单人数
+ maxCount = Math.Min(maxPeopleLimit, nameList.Count);
+ }
+ currentCount = Math.Min(currentCount + 1, maxCount);
+ }
+
+ UpdateCountDisplay();
+ }
+
+ private void CountMinus_Click(object sender, RoutedEventArgs e)
+ {
+ if (isRollCalling) return;
+ currentCount = Math.Max(currentCount - 1, 1);
+ UpdateCountDisplay();
+ }
+
+ private void ImportList_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ // 打开名单导入窗口,与老点名UI保持一致
+ var namesInputWindow = new NamesInputWindow();
+ namesInputWindow.ShowDialog();
+
+ // 重新加载名单
+ LoadNamesFromFile();
+ UpdateListCountDisplay();
+ UpdateStatusDisplay($"已导入 {nameList.Count} 个名字");
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"导入名单失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ 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 ClearList_Click(object sender, RoutedEventArgs e)
+ {
+ nameList.Clear();
+ UpdateListCountDisplay();
+ UpdateStatusDisplay("名单已清空");
+ }
+
+ private void SetModeSelection(string mode)
+ {
+ try
+ {
+ // 存储选择的模式
+ selectedRollCallMode = mode;
+
+ // 重置所有按钮状态
+ RandomModeText.FontWeight = FontWeights.Normal;
+ RandomModeText.Opacity = 0.6;
+ RandomModeText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102));
+ SequentialModeText.FontWeight = FontWeights.Normal;
+ SequentialModeText.Opacity = 0.6;
+ SequentialModeText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102));
+ GroupModeText.FontWeight = FontWeights.Normal;
+ GroupModeText.Opacity = 0.6;
+ GroupModeText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102));
+
+ // 设置选中状态和动画
+ switch (mode)
+ {
+ case "Random":
+ RandomModeText.FontWeight = FontWeights.Bold;
+ RandomModeText.Opacity = 1.0;
+ RandomModeText.Foreground = new SolidColorBrush(Colors.White);
+ SegmentedIndicator.HorizontalAlignment = HorizontalAlignment.Left;
+ SegmentedIndicator.CornerRadius = new CornerRadius(7.5, 0, 0, 7.5);
+
+ // 添加动画效果
+ var randomAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
+ new Thickness(0, 0, 0, 0),
+ TimeSpan.FromMilliseconds(200));
+ SegmentedIndicator.BeginAnimation(Border.MarginProperty, randomAnimation);
+
+ UpdateStatusDisplay("已选择点名模式: 随机点名");
+ break;
+ case "Sequential":
+ SequentialModeText.FontWeight = FontWeights.Bold;
+ SequentialModeText.Opacity = 1.0;
+ SequentialModeText.Foreground = new SolidColorBrush(Colors.White);
+ SegmentedIndicator.HorizontalAlignment = HorizontalAlignment.Left;
+ SegmentedIndicator.CornerRadius = new CornerRadius(0, 0, 0, 0);
+
+ // 添加动画效果 - 移动到中间位置
+ var sequentialAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
+ new Thickness(100, 0, 0, 0),
+ TimeSpan.FromMilliseconds(200));
+ SegmentedIndicator.BeginAnimation(Border.MarginProperty, sequentialAnimation);
+
+ UpdateStatusDisplay("已选择点名模式: 顺序点名");
+ break;
+ case "Group":
+ GroupModeText.FontWeight = FontWeights.Bold;
+ GroupModeText.Opacity = 1.0;
+ GroupModeText.Foreground = new SolidColorBrush(Colors.White);
+ SegmentedIndicator.HorizontalAlignment = HorizontalAlignment.Left;
+ SegmentedIndicator.CornerRadius = new CornerRadius(0, 7.5, 7.5, 0);
+
+ // 添加动画效果 - 移动到右侧位置
+ var groupAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
+ new Thickness(200, 0, 0, 0),
+ TimeSpan.FromMilliseconds(200));
+ SegmentedIndicator.BeginAnimation(Border.MarginProperty, groupAnimation);
+
+ UpdateStatusDisplay("已选择点名模式: 分组点名");
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"设置点名模式选择时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void RandomMode_Click(object sender, RoutedEventArgs e)
+ {
+ SetModeSelection("Random");
+ }
+
+ private void SequentialMode_Click(object sender, RoutedEventArgs e)
+ {
+ SetModeSelection("Sequential");
+ }
+
+ private void GroupMode_Click(object sender, RoutedEventArgs e)
+ {
+ SetModeSelection("Group");
+ }
+
+ private static bool isExternalCallerFirstClick = true;
+ private string selectedExternalCaller = "ClassIsland";
+
+ private void ExternalCaller_Click(object sender, RoutedEventArgs e)
+ {
+ if (isExternalCallerFirstClick)
+ {
+ MessageBox.Show(
+ "首次使用外部点名功能,请确保已安装相应的点名软件。\n" +
+ "如未安装,请前往官网下载并安装后再使用。如果已安装请再次点击此按钮。",
+ "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+ isExternalCallerFirstClick = false;
+ return;
+ }
+
+ try
+ {
+ string protocol = "";
+ switch (selectedExternalCaller)
+ {
+ case "ClassIsland":
+ protocol = "classisland://plugins/IslandCaller/Simple/1";
+ break;
+ case "SecRandom":
+ protocol = "secrandom://direct_extraction";
+ break;
+ case "NamePicker":
+ protocol = "namepicker://";
+ break;
+ default:
+ protocol = "classisland://plugins/IslandCaller/Simple/1";
+ break;
+ }
+
+ System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = protocol,
+ UseShellExecute = true
+ });
+
+ UpdateStatusDisplay($"已启动外部点名: {selectedExternalCaller}");
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("无法调用外部点名:" + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+ LogHelper.WriteLogToFile($"外部点名调用失败: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void ClassIsland_Click(object sender, RoutedEventArgs e)
+ {
+ SetExternalCallerSelection("ClassIsland");
+ }
+
+ private void SecRandom_Click(object sender, RoutedEventArgs e)
+ {
+ SetExternalCallerSelection("SecRandom");
+ }
+
+ private void NamePicker_Click(object sender, RoutedEventArgs e)
+ {
+ SetExternalCallerSelection("NamePicker");
+ }
+
+ private void SetExternalCallerSelection(string caller)
+ {
+ try
+ {
+ // 重置所有按钮状态
+ ClassIslandText.FontWeight = FontWeights.Normal;
+ ClassIslandText.Opacity = 0.6;
+ ClassIslandText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102));
+ SecRandomText.FontWeight = FontWeights.Normal;
+ SecRandomText.Opacity = 0.6;
+ SecRandomText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102));
+ NamePickerText.FontWeight = FontWeights.Normal;
+ NamePickerText.Opacity = 0.6;
+ NamePickerText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102));
+
+ // 设置选中状态和动画
+ switch (caller)
+ {
+ case "ClassIsland":
+ ClassIslandText.FontWeight = FontWeights.Bold;
+ ClassIslandText.Opacity = 1.0;
+ ClassIslandText.Foreground = new SolidColorBrush(Colors.White);
+ ExternalCallerIndicator.HorizontalAlignment = HorizontalAlignment.Left;
+ ExternalCallerIndicator.CornerRadius = new CornerRadius(7.5, 0, 0, 7.5);
+
+ // 添加动画效果
+ var classIslandAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
+ new Thickness(0, 0, 0, 0),
+ TimeSpan.FromMilliseconds(200));
+ ExternalCallerIndicator.BeginAnimation(Border.MarginProperty, classIslandAnimation);
+
+ selectedExternalCaller = "ClassIsland";
+ UpdateStatusDisplay("已选择外部点名: ClassIsland");
+ break;
+ case "SecRandom":
+ SecRandomText.FontWeight = FontWeights.Bold;
+ SecRandomText.Opacity = 1.0;
+ SecRandomText.Foreground = new SolidColorBrush(Colors.White);
+ ExternalCallerIndicator.HorizontalAlignment = HorizontalAlignment.Left;
+ ExternalCallerIndicator.CornerRadius = new CornerRadius(0, 0, 0, 0);
+
+ // 添加动画效果
+ var secRandomAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
+ new Thickness(120, 0, 0, 0),
+ TimeSpan.FromMilliseconds(200));
+ ExternalCallerIndicator.BeginAnimation(Border.MarginProperty, secRandomAnimation);
+
+ selectedExternalCaller = "SecRandom";
+ UpdateStatusDisplay("已选择外部点名: SecRandom");
+ break;
+ case "NamePicker":
+ NamePickerText.FontWeight = FontWeights.Bold;
+ NamePickerText.Opacity = 1.0;
+ NamePickerText.Foreground = new SolidColorBrush(Colors.White);
+ ExternalCallerIndicator.HorizontalAlignment = HorizontalAlignment.Left;
+ ExternalCallerIndicator.CornerRadius = new CornerRadius(0, 7.5, 7.5, 0);
+
+ // 添加动画效果
+ var namePickerAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
+ new Thickness(240, 0, 0, 0),
+ TimeSpan.FromMilliseconds(200));
+ ExternalCallerIndicator.BeginAnimation(Border.MarginProperty, namePickerAnimation);
+
+ selectedExternalCaller = "NamePicker";
+ UpdateStatusDisplay("已选择外部点名: NamePicker");
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"设置外部点名选择时出错: {ex.Message}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void StartRollCall_Click(object sender, RoutedEventArgs e)
+ {
+ if (isSingleDrawMode)
+ {
+ // 单次抽模式:直接开始抽选
+ StartSingleDraw();
+ }
+ else
+ {
+ // 普通点名模式:检查名单或使用60个数字
+ if (nameList.Count == 0)
+ {
+ // 没有导入名单时,使用60个数字
+ StartRollCallWithNumbers();
+ }
+ else
+ {
+ // 有名单时,使用名单
+ if (currentCount > nameList.Count)
+ {
+ MessageBox.Show($"点名人数不能超过名单人数!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+ StartRollCall();
+ }
+ }
+ }
+
+ private void StopRollCall_Click(object sender, RoutedEventArgs e)
+ {
+ StopRollCall();
+ }
+
+ private void Reset_Click(object sender, RoutedEventArgs e)
+ {
+ if (isRollCalling)
+ {
+ StopRollCall();
+ }
+
+ MainResultDisplay.Text = "点击开始点名";
+ MainResultDisplay.Visibility = Visibility.Visible;
+ MultiResultScrollViewer.Visibility = Visibility.Collapsed;
+ UpdateStatusDisplay("准备就绪");
+ }
+
+ private void StartRollCall()
+ {
+ isRollCalling = true;
+ StartRollCallBtn.Visibility = Visibility.Collapsed;
+ StopRollCallBtn.Visibility = Visibility.Visible;
+ UpdateStatusDisplay("正在点名...");
+
+ // 启动点名动画
+ StartRollCallAnimation();
+ }
+
+ ///
+ /// 点名动画
+ ///
+ private void StartRollCallAnimation()
+ {
+ const int animationTimes = 100; // 动画次数
+ const int sleepTime = 5; // 每次动画间隔(毫秒)
+
+ new System.Threading.Thread(() =>
+ {
+ List usedNames = new List();
+
+ for (int i = 0; i < animationTimes; i++)
+ {
+ // 随机选择一个名字进行动画显示
+ if (nameList.Count > 0)
+ {
+ int randomIndex = new Random().Next(0, nameList.Count);
+ string displayName = nameList[randomIndex];
+
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ MainResultDisplay.Text = displayName;
+ });
+ }
+
+ System.Threading.Thread.Sleep(sleepTime);
+ }
+
+ // 动画结束,显示最终结果
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ // 根据选择的模式进行不同的点名逻辑
+ var selectedNames = SelectNamesByMode(nameList, currentCount);
+
+ // 更新历史记录
+ UpdateRollCallHistory(selectedNames);
+
+ // 显示结果
+ ShowResults(selectedNames);
+ UpdateStatusDisplay($"点名完成,共选择 {selectedNames.Count} 人");
+
+ // 停止点名状态
+ isRollCalling = false;
+ StartRollCallBtn.Visibility = Visibility.Visible;
+ StopRollCallBtn.Visibility = Visibility.Collapsed;
+ });
+ }).Start();
+ }
+
+ private void StartRollCallWithNumbers()
+ {
+ isRollCalling = true;
+ StartRollCallBtn.Visibility = Visibility.Collapsed;
+ StopRollCallBtn.Visibility = Visibility.Visible;
+ UpdateStatusDisplay("正在抽选...");
+
+ // 启动数字抽选动画
+ StartNumberRollCallAnimation();
+ }
+
+ ///
+ /// 数字抽选动画
+ ///
+ private void StartNumberRollCallAnimation()
+ {
+ const int animationTimes = 100; // 动画次数
+ const int sleepTime = 5; // 每次动画间隔(毫秒)
+
+ new System.Threading.Thread(() =>
+ {
+ List usedNumbers = new List();
+
+ for (int i = 0; i < animationTimes; i++)
+ {
+ // 随机选择一个数字进行动画显示
+ int randomNumber = new Random().Next(1, 61); // 1-60
+
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ MainResultDisplay.Text = randomNumber.ToString();
+ });
+
+ System.Threading.Thread.Sleep(sleepTime);
+ }
+
+ // 动画结束,显示最终结果
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ // 使用60个数字进行抽选
+ var selectedNumbers = SelectMultipleNumbers(currentCount);
+
+ // 更新历史记录
+ UpdateRollCallHistory(selectedNumbers);
+
+ // 显示结果
+ ShowResults(selectedNumbers);
+ UpdateStatusDisplay($"抽选完成,共选择 {selectedNumbers.Count} 个数字");
+
+ // 停止点名状态
+ isRollCalling = false;
+ StartRollCallBtn.Visibility = Visibility.Visible;
+ StopRollCallBtn.Visibility = Visibility.Collapsed;
+ });
+ }).Start();
+ }
+
+ private void StopRollCall()
+ {
+ isRollCalling = false;
+ StartRollCallBtn.Visibility = Visibility.Visible;
+ StopRollCallBtn.Visibility = Visibility.Collapsed;
+ UpdateStatusDisplay("已停止点名");
+ }
+
+ ///
+ /// 开始单次抽选
+ ///
+ private void StartSingleDraw()
+ {
+ isRollCalling = true;
+ StartRollCallBtn.Visibility = Visibility.Collapsed;
+ StopRollCallBtn.Visibility = Visibility.Visible;
+ UpdateStatusDisplay("正在抽选...");
+
+ // 启动抽选动画
+ StartSingleDrawAnimation();
+ }
+
+ ///
+ /// 单次抽选动画
+ ///
+ private void StartSingleDrawAnimation()
+ {
+ const int animationTimes = 100; // 动画次数
+ const int sleepTime = 5; // 每次动画间隔(毫秒),参考老点名窗口
+
+ new System.Threading.Thread(() =>
+ {
+ if (nameList.Count > 0)
+ {
+ // 有名单时,从名单中抽选
+ StartSingleDrawNameAnimation(animationTimes, sleepTime);
+ }
+ else
+ {
+ // 没有名单时,从1-60数字中抽选
+ StartSingleDrawNumberAnimation(animationTimes, sleepTime);
+ }
+ }).Start();
+ }
+
+ ///
+ /// 单次抽选名单动画
+ ///
+ private void StartSingleDrawNameAnimation(int animationTimes, int sleepTime)
+ {
+ List usedNames = new List();
+
+ for (int i = 0; i < animationTimes; i++)
+ {
+ // 随机选择一个名字进行动画显示,避免立即重复
+ string randomName;
+ do
+ {
+ randomName = nameList[singleDrawRandom.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(() =>
+ {
+ // 根据选择的模式进行不同的点名逻辑
+ var selectedNames = SelectNamesByMode(nameList, currentCount);
+
+ // 更新历史记录
+ UpdateRollCallHistory(selectedNames);
+
+ // 显示结果
+ ShowResults(selectedNames);
+ UpdateStatusDisplay($"抽选完成,共选择 {selectedNames.Count} 人");
+
+ // 停止点名状态
+ isRollCalling = false;
+ StartRollCallBtn.Visibility = Visibility.Visible;
+ StopRollCallBtn.Visibility = Visibility.Collapsed;
+
+ if (isSingleDrawMode)
+ {
+ new System.Threading.Thread(() =>
+ {
+ System.Threading.Thread.Sleep(autoCloseWaitTime);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ Close();
+ });
+ }).Start();
+ }
+ });
+ }
+
+ ///
+ /// 单次抽选数字动画
+ ///
+ private void StartSingleDrawNumberAnimation(int animationTimes, int sleepTime)
+ {
+ List usedNumbers = new List();
+
+ for (int i = 0; i < animationTimes; i++)
+ {
+ // 随机选择一个数字进行动画显示,避免立即重复
+ int randomNumber;
+ do
+ {
+ randomNumber = singleDrawRandom.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(() =>
+ {
+ // 根据选择的数量进行抽选
+ var selectedNumbers = SelectMultipleNumbers(currentCount);
+
+ if (selectedNumbers.Count == 1)
+ {
+ MainResultDisplay.Text = selectedNumbers[0];
+ UpdateStatusDisplay($"抽选完成:{selectedNumbers[0]}");
+ }
+ else
+ {
+ MainResultDisplay.Text = "抽选结果";
+ MultiResultPanel.Visibility = Visibility.Visible;
+
+ Result1Display.Text = selectedNumbers.Count > 0 ? selectedNumbers[0] : "";
+ Result2Display.Text = selectedNumbers.Count > 1 ? selectedNumbers[1] : "";
+ Result3Display.Text = selectedNumbers.Count > 2 ? selectedNumbers[2] : "";
+
+ UpdateStatusDisplay($"抽选完成,共选择 {selectedNumbers.Count} 个数字");
+ }
+
+ // 停止点名状态
+ isRollCalling = false;
+ StartRollCallBtn.Visibility = Visibility.Visible;
+ StopRollCallBtn.Visibility = Visibility.Collapsed;
+
+ if (isSingleDrawMode)
+ {
+ new System.Threading.Thread(() =>
+ {
+ System.Threading.Thread.Sleep(autoCloseWaitTime);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ Close();
+ });
+ }).Start();
+ }
+ });
+ }
+
+ ///
+ /// 选择多个数字(不重复)
+ ///
+ private List SelectMultipleNumbers(int count)
+ {
+ var selectedNumbers = new List();
+ var usedNumbers = new List();
+
+ for (int i = 0; i < count && usedNumbers.Count < 60; i++)
+ {
+ int randomNumber = singleDrawRandom.Next(1, 61); // 1-60
+
+ // 避免重复选择
+ while (usedNumbers.Contains(randomNumber))
+ {
+ randomNumber = singleDrawRandom.Next(1, 61);
+ }
+
+ usedNumbers.Add(randomNumber);
+ selectedNumbers.Add(randomNumber.ToString());
+ }
+
+ return selectedNumbers;
+ }
+
+ private void RollCallTimer_Elapsed(object sender, ElapsedEventArgs e)
+ {
+ // 这里可以实现点名动画效果
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ // 动画逻辑可以在这里实现
+ });
+ }
+ #endregion
+
+ #region 窗口事件处理
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ // 窗口加载时的初始化
+ }
+
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ rollCallTimer?.Stop();
+ }
+
+ private void CloseButton_Click(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ private void WindowDragMove(object sender, MouseEventArgs e)
+ {
+ if (e.LeftButton == MouseButtonState.Pressed)
+ DragMove();
+ }
+
+ private void Window_MouseMove(object sender, MouseEventArgs e)
+ {
+ lastActivityTime = DateTime.Now;
+ }
+
+ private void Window_MouseEnter(object sender, MouseEventArgs e)
+ {
+ lastActivityTime = DateTime.Now;
+ }
+
+ private void SetDarkThemeBorder()
+ {
+ try
+ {
+ if (MainBorder != null)
+ {
+ MainBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(64, 64, 64));
+ }
+ }
+ catch
+ {
+ // 忽略错误
+ }
+ }
+ #endregion
+
+ #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);
+
+ [DllImport("user32.dll")]
+ private static extern bool IsWindow(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool IsWindowVisible(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool IsIconic(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr GetForegroundWindow();
+
+ [DllImport("user32.dll")]
+ private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+
+ [DllImport("kernel32.dll")]
+ private static extern uint GetCurrentProcessId();
+
+ private const int GWL_EXSTYLE = -20;
+ private const int WS_EX_TOPMOST = 0x00000008;
+ private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
+ private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
+ 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 ApplyRollCallWindowTopmost()
+ {
+ 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);
+ }
+ }
+
+ ///
+ /// 窗口加载事件处理,确保置顶
+ ///
+ private void RollCallWindow_Loaded(object sender, RoutedEventArgs e)
+ {
+ // 使用延迟确保窗口完全加载后再应用置顶
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ ApplyRollCallWindowTopmost();
+ }), DispatcherPriority.Loaded);
+ }
+ #endregion
+ }
+}
+
diff --git a/Ink Canvas/Windows/NewStyleRollCallWindow.xaml b/Ink Canvas/Windows/NewStyleRollCallWindow.xaml
new file mode 100644
index 00000000..1a62ceef
--- /dev/null
+++ b/Ink Canvas/Windows/NewStyleRollCallWindow.xaml
@@ -0,0 +1,509 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UpdateLog.md b/UpdateLog.md
index af861394..f899504d 100644
--- a/UpdateLog.md
+++ b/UpdateLog.md
@@ -100,3 +100,5 @@ ICC CE 1.7.X.X更新日志
99. 修复仅调色盘状态下浮动栏不居中
100. 修复希沃白板查杀与思锐希沃启动器导致的重复启动
101. 新增UIA窗口置顶
+102. 新增新点名窗口
+103. 新增点名快抽