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 } }