using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using Microsoft.Win32; namespace TimerApp { public partial class MainForm : Form { private ActivityMonitor _monitor = null!; private AppSettings _settings; private RestForm? _restForm; // Colors private Color _darkBg = Color.FromArgb(30, 30, 30); private Color _lightBg = Color.FromArgb(240, 240, 240); private Color _darkPanel = Color.FromArgb(45, 45, 48); private Color _lightPanel = Color.FromArgb(200, 200, 200); private Color _darkText = Color.White; private Color _lightText = Color.Black; // Drag window support [DllImport("user32.dll")] public static extern bool ReleaseCapture(); [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); [DllImport("user32.dll")] static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight); [DllImport("user32.dll")] static extern bool ShowCaret(IntPtr hWnd); private const int WM_NCLBUTTONDOWN = 0xA1; private const int HT_CAPTION = 0x2; public MainForm() { InitializeComponent(); // 在窗口显示前设置背景色,避免白色闪烁 // 确保所有控件在显示前都有正确的背景色 this.SuspendLayout(); // 先加载设置,以便知道应该使用什么背景色 _settings = AppSettings.Load(); bool dark = _settings.IsDarkMode; Color bg = dark ? _darkBg : _lightBg; this.BackColor = bg; pnlSettings.BackColor = bg; lblTimeLeft.BackColor = bg; lblStatus.BackColor = bg; label1.BackColor = bg; label2.BackColor = bg; // 设置文本框和按钮的初始背景色 Color panelColor = dark ? _darkPanel : _lightPanel; txtWork.BackColor = panelColor; txtRest.BackColor = panelColor; btnWorkMinus.BackColor = panelColor; btnWorkPlus.BackColor = panelColor; btnRestMinus.BackColor = panelColor; btnRestPlus.BackColor = panelColor; btnStartStop.BackColor = dark ? Color.FromArgb(63, 63, 70) : Color.White; btnReset.BackColor = dark ? Color.FromArgb(63, 63, 70) : Color.White; btnPause.BackColor = dark ? Color.FromArgb(63, 63, 70) : Color.White; // 优化绘制,减少闪烁 this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true); this.ResumeLayout(false); } private void MainForm_Load(object sender, EventArgs e) { // Settings already loaded in constructor, just update text txtWork.Text = _settings.WorkMinutes.ToString(); txtRest.Text = _settings.RestMinutes.ToString(); // Apply Theme - 确保在窗口显示前应用主题 ApplyTheme(); // 强制刷新背景色,确保所有控件都正确显示 this.Invalidate(true); pnlSettings.Invalidate(true); this.Update(); // Init monitor _monitor = new ActivityMonitor(); ApplySettings(); // Bind events _monitor.StateChanged += Monitor_StateChanged; _monitor.WorkProgressChanged += Monitor_WorkProgressChanged; _monitor.RestStarted += Monitor_RestStarted; _monitor.RestEnded += Monitor_RestEnded; _monitor.RestProgressChanged += Monitor_RestProgressChanged; // Numeric Buttons btnWorkMinus.Click += (s, ev) => AdjustTime(txtWork, -1, 1, 120); btnWorkPlus.Click += (s, ev) => AdjustTime(txtWork, 1, 1, 120); btnRestMinus.Click += (s, ev) => AdjustTime(txtRest, -1, 1, 30); btnRestPlus.Click += (s, ev) => AdjustTime(txtRest, 1, 1, 30); // Manual input validation txtWork.KeyPress += ValidateDigitInput; txtRest.KeyPress += ValidateDigitInput; txtWork.Leave += (s, ev) => ValidateRange((TextBox)s!, 1, 120); txtRest.Leave += (s, ev) => ValidateRange((TextBox)s!, 1, 30); // Focus handling (remove custom caret) txtWork.KeyDown += TextBox_KeyDown; txtRest.KeyDown += TextBox_KeyDown; // Setup TextBoxes with Panels for vertical centering SetupTextBoxPanel(txtWork, pnlSettings); SetupTextBoxPanel(txtRest, pnlSettings); // 确保容器背景色正确(在 SetupTextBoxPanel 之后再次应用主题) UpdateTextBoxStyle(txtWork, _settings.IsDarkMode); UpdateTextBoxStyle(txtRest, _settings.IsDarkMode); // 再次确保 pnlSettings 的背景色正确 bool dark = _settings.IsDarkMode; Color bg = dark ? _darkBg : _lightBg; pnlSettings.BackColor = bg; pnlSettings.Refresh(); _monitor.Start(); SystemEvents.PowerModeChanged += OnPowerModeChanged; // Set tray icon try { // Generate and set custom icon Icon icon = IconGenerator.GenerateClockIcon(64); this.Icon = icon; notifyIcon1.Icon = icon; contextMenuStrip1.ShowImageMargin = true; toolStripMenuItemShow.Image = null; toolStripMenuItemExit.Image = null; } catch { notifyIcon1.Icon = SystemIcons.Application; contextMenuStrip1.ShowImageMargin = true; toolStripMenuItemShow.Image = null; toolStripMenuItemExit.Image = null; } UpdateStatusUI(); } private void AdjustTime(TextBox txt, int delta, int min, int max) { if (int.TryParse(txt.Text, out int val)) { val += delta; if (val < min) val = min; if (val > max) val = max; txt.Text = val.ToString(); } } private void ValidateDigitInput(object? sender, KeyPressEventArgs e) { // Allow control keys (backspace, etc.) and digits if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar)) { e.Handled = true; } } private void SetupTextBoxPanel(TextBox txt, Panel parent) { // Create a container panel for the textbox Panel container = new Panel(); container.Size = txt.Size; // 60x30 container.Location = txt.Location; // 确保容器背景色与文本框背景色一致 container.BackColor = txt.BackColor; // Adjust textbox to be centered inside txt.Parent = container; txt.Location = new Point(0, (container.Height - txt.Height) / 2); // Center vertically txt.Dock = DockStyle.None; txt.Width = container.Width; txt.BorderStyle = BorderStyle.None; // Add container to parent parent.Controls.Add(container); // Ensure correct tab order and tagging container.Tag = txt.Tag; // Copy tag if any // 确保容器背景色与父容器一致(如果文本框背景色与父容器不同) // 这里容器应该使用文本框的背景色,因为它是文本框的容器 } private void ValidateRange(TextBox txt, int min, int max) { if (int.TryParse(txt.Text, out int val)) { if (val < min) txt.Text = min.ToString(); if (val > max) txt.Text = max.ToString(); } else { txt.Text = min.ToString(); // Default to min if invalid/empty } // Clear selection on leave txt.SelectionLength = 0; } // CustomCaret removed to use system caret with centered text private void TextBox_KeyDown(object? sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { // Lose focus by focusing the label lblStatus.Focus(); e.Handled = true; e.SuppressKeyPress = true; // Prevent 'ding' sound } } private void ApplyTheme() { bool dark = _settings.IsDarkMode; Color bg = dark ? _darkBg : _lightBg; Color panel = dark ? _darkPanel : _lightPanel; Color text = dark ? _darkText : _lightText; Color btnBg = dark ? Color.FromArgb(45, 45, 48) : Color.White; // 确保窗口和所有面板的背景色一致 this.BackColor = bg; pnlTitle.BackColor = panel; // 强制设置 pnlSettings 的背景色,确保与窗口背景色一致 pnlSettings.BackColor = bg; pnlSettings.Invalidate(); // 强制重绘 lblTimeLeft.BackColor = bg; lblStatus.BackColor = bg; label1.BackColor = bg; label2.BackColor = bg; // 确保所有标签都使用正确的背景色 foreach (Control ctrl in pnlSettings.Controls) { if (ctrl is Label label && label.BackColor != bg) { label.BackColor = bg; } } lblTitle.ForeColor = dark ? Color.LightGray : Color.FromArgb(64, 64, 64); lblTimeLeft.ForeColor = text; label1.ForeColor = dark ? Color.LightGray : Color.DimGray; label2.ForeColor = dark ? Color.LightGray : Color.DimGray; // Update buttons UpdateButtonStyle(btnStartStop, dark); UpdateButtonStyle(btnReset, dark); UpdateButtonStyle(btnPause, dark); UpdateButtonStyle(btnHide, dark); // Numeric buttons and text UpdateNumericButtonStyle(btnWorkMinus, dark); UpdateNumericButtonStyle(btnWorkPlus, dark); UpdateNumericButtonStyle(btnRestMinus, dark); UpdateNumericButtonStyle(btnRestPlus, dark); UpdateTextBoxStyle(txtWork, dark); UpdateTextBoxStyle(txtRest, dark); // Title buttons btnClose.ForeColor = text; btnMin.ForeColor = text; // Set hover background colors for title buttons if (dark) { // 深色模式下使用深色悬停背景 btnClose.FlatAppearance.MouseOverBackColor = Color.FromArgb(232, 17, 35); // 深色模式下的红色(稍微暗一点) btnMin.FlatAppearance.MouseOverBackColor = Color.FromArgb(63, 63, 70); btnTheme.FlatAppearance.MouseOverBackColor = Color.FromArgb(63, 63, 70); } else { // 浅色模式下使用浅色悬停背景 btnClose.FlatAppearance.MouseOverBackColor = Color.Red; btnMin.FlatAppearance.MouseOverBackColor = Color.FromArgb(220, 220, 220); // 浅灰色,接近标题栏背景 btnTheme.FlatAppearance.MouseOverBackColor = Color.FromArgb(220, 220, 220); // 浅灰色,接近标题栏背景 } // Theme button with Segoe MDL2 Assets btnTheme.ForeColor = text; btnTheme.Font = new Font("Segoe MDL2 Assets", 10F, FontStyle.Regular, GraphicsUnit.Point); btnTheme.Text = dark ? "\uE706" : "\uE708"; // Sun : Moon // Context Menu Theme // 浅色模式下使用白色背景,深色模式使用深色面板色 Color menuBg = dark ? panel : Color.White; var menuRenderer = new ToolStripProfessionalRenderer(new CustomColorTable(dark, menuBg)); contextMenuStrip1.Renderer = menuRenderer; contextMenuStrip1.BackColor = menuBg; contextMenuStrip1.ForeColor = dark ? text : Color.Black; UpdateStatusUI(); // Re-apply status colors } private class CustomColorTable : ProfessionalColorTable { private bool _dark; private Color _backColor; public CustomColorTable(bool dark, Color backColor) { _dark = dark; _backColor = backColor; } public override Color MenuItemSelected => _dark ? Color.FromArgb(63, 63, 70) : base.MenuItemSelected; public override Color MenuItemBorder => _dark ? Color.FromArgb(63, 63, 70) : base.MenuItemBorder; public override Color MenuBorder => _dark ? Color.FromArgb(45, 45, 48) : base.MenuBorder; // Fix white margin in dark mode, and set margin color to background color in light mode public override Color ImageMarginGradientBegin => _backColor; public override Color ImageMarginGradientMiddle => _backColor; public override Color ImageMarginGradientEnd => _backColor; } private void UpdateButtonStyle(Button btn, bool dark) { btn.BackColor = dark ? Color.FromArgb(63, 63, 70) : Color.White; btn.ForeColor = dark ? Color.White : Color.Black; } private void UpdateNumericButtonStyle(Button btn, bool dark) { btn.BackColor = dark ? Color.FromArgb(45, 45, 48) : Color.FromArgb(230, 230, 230); btn.ForeColor = dark ? Color.White : Color.Black; } private void UpdateTextBoxStyle(TextBox txt, bool dark) { Color bgColor = dark ? Color.FromArgb(45, 45, 48) : Color.White; txt.BackColor = bgColor; txt.ForeColor = dark ? Color.White : Color.Black; // Also update parent container if it exists if (txt.Parent is Panel p) { p.BackColor = bgColor; } } private void btnTheme_Click(object sender, EventArgs e) { _settings.IsDarkMode = !_settings.IsDarkMode; _settings.Save(); ApplyTheme(); } private void btnStartStop_Click(object sender, EventArgs e) { // Save settings if (int.TryParse(txtWork.Text, out int w)) _settings.WorkMinutes = w; if (int.TryParse(txtRest.Text, out int r)) _settings.RestMinutes = r; _settings.Save(); ApplySettings(); _monitor.RefreshStatus(); // Force update UI // MessageBox removed to prevent blocking timer } private void btnReset_Click(object sender, EventArgs e) { _monitor.Restart(); } private void btnPause_Click(object sender, EventArgs e) { if (_monitor.IsPaused) { _monitor.Resume(); btnPause.Text = "暂停"; } else { _monitor.Pause(); btnPause.Text = "恢复"; } } private void ApplySettings() { int workMin = 20; int restMin = 1; int.TryParse(txtWork.Text, out workMin); int.TryParse(txtRest.Text, out restMin); _monitor.WorkDuration = TimeSpan.FromMinutes(workMin); _monitor.RestDuration = TimeSpan.FromMinutes(restMin); _monitor.IdleThreshold = TimeSpan.FromSeconds(_settings.IdleThresholdSeconds); } private void Monitor_StateChanged(object? sender, EventArgs e) { if (InvokeRequired) { Invoke(new Action(Monitor_StateChanged), sender, e); return; } UpdateStatusUI(); } private void UpdateStatusUI() { if (_monitor == null) return; bool dark = _settings.IsDarkMode; // 更新暂停按钮状态 if (_monitor.CurrentState == MonitorState.Idle) { btnPause.Enabled = false; btnPause.Text = "暂停"; } else { btnPause.Enabled = true; btnPause.Text = _monitor.IsPaused ? "恢复" : "暂停"; } switch (_monitor.CurrentState) { case MonitorState.Idle: lblStatus.Text = "状态: 空闲"; lblStatus.ForeColor = Color.Gray; lblTimeLeft.Text = "--:--"; lblTimeLeft.ForeColor = Color.Gray; break; case MonitorState.Working: lblStatus.Text = _monitor.IsPaused ? "状态: 工作中 (已暂停)" : "状态: 工作中"; lblStatus.ForeColor = dark ? Color.LightGreen : Color.Green; lblTimeLeft.ForeColor = dark ? Color.White : Color.Black; break; case MonitorState.Resting: lblStatus.Text = _monitor.IsPaused ? "状态: 休息中 (已暂停)" : "状态: 休息中"; lblStatus.ForeColor = dark ? Color.LightSkyBlue : Color.Blue; lblTimeLeft.ForeColor = dark ? Color.LightSkyBlue : Color.Blue; break; } } private void Monitor_WorkProgressChanged(object? sender, TimeSpan remaining) { if (InvokeRequired) { Invoke(new Action(Monitor_WorkProgressChanged), sender, remaining); return; } lblTimeLeft.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}"; // Update tray tooltip if (remaining.TotalMinutes < 1) { notifyIcon1.Text = $"即将休息: {remaining.Seconds}秒"; } else { notifyIcon1.Text = $"工作中: 剩余 {remaining.Minutes} 分钟"; } } private void Monitor_RestStarted(object? sender, EventArgs e) { if (InvokeRequired) { Invoke(new Action(Monitor_RestStarted), sender, e); return; } // Show rest form if (_restForm == null || _restForm.IsDisposed) { _restForm = new RestForm(); _restForm.SkipRequested += RestForm_SkipRequested; } _restForm.Show(); } private void RestForm_SkipRequested(object? sender, EventArgs e) { _monitor.Stop(); _monitor.Start(); } private void Monitor_RestProgressChanged(object? sender, TimeSpan remaining) { if (_restForm != null && !_restForm.IsDisposed && _restForm.Visible) { _restForm.UpdateTime(remaining); } } private void Monitor_RestEnded(object? sender, EventArgs e) { if (InvokeRequired) { Invoke(new Action(Monitor_RestEnded), sender, e); return; } if (_restForm != null && !_restForm.IsDisposed) { _restForm.Close(); } notifyIcon1.ShowBalloonTip(3000, "休息结束", "继续加油工作吧!", ToolTipIcon.Info); } private void btnHide_Click(object sender, EventArgs e) { this.Hide(); if (!_settings.HasShownMinimizeTip) { notifyIcon1.ShowBalloonTip(2000, "已隐藏", "程序仍在后台运行,双击托盘图标恢复。", ToolTipIcon.Info); _settings.HasShownMinimizeTip = true; _settings.Save(); } } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { if (e.CloseReason == CloseReason.UserClosing) { e.Cancel = true; this.Hide(); if (!_settings.HasShownMinimizeTip) { notifyIcon1.ShowBalloonTip(2000, "已隐藏", "程序仍在后台运行,双击托盘图标恢复。", ToolTipIcon.Info); _settings.HasShownMinimizeTip = true; _settings.Save(); } } else { SystemEvents.PowerModeChanged -= OnPowerModeChanged; notifyIcon1.Visible = false; notifyIcon1.Dispose(); } } private void notifyIcon1_MouseDoubleClick(object? sender, MouseEventArgs e) { ShowForm(); } private void toolStripMenuItemShow_Click(object? sender, EventArgs e) { ShowForm(); } private void ShowForm() { this.Show(); this.WindowState = FormWindowState.Normal; this.Activate(); } public void ActivateFromExternal() { if (InvokeRequired) { BeginInvoke(new Action(ActivateFromExternal)); return; } this.Show(); this.WindowState = FormWindowState.Normal; this.ShowInTaskbar = true; this.BringToFront(); this.Activate(); this.TopMost = true; this.TopMost = false; } private void toolStripMenuItemExit_Click(object sender, EventArgs e) { _monitor.Stop(); notifyIcon1.Visible = false; notifyIcon1.Dispose(); Application.Exit(); } private void pnlTitle_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { ReleaseCapture(); SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0); } } private void btnClose_Click(object sender, EventArgs e) { this.Close(); } private void btnMin_Click(object sender, EventArgs e) { this.WindowState = FormWindowState.Minimized; } private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e) { if (e.Mode == PowerModes.Resume) { // 系统唤醒时,强制重置媒体播放检测状态, // 避免因检测线程挂起导致一直误报“正在播放”而无法进入工作状态。 NativeMethods.InvalidateMediaCache(); } } } }