add: 久坐提醒项目文件
This commit is contained in:
477
MainForm.cs
Normal file
477
MainForm.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace TimerApp
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private ActivityMonitor _monitor;
|
||||
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();
|
||||
}
|
||||
|
||||
private void MainForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
// Load settings
|
||||
_settings = AppSettings.Load();
|
||||
txtWork.Text = _settings.WorkMinutes.ToString();
|
||||
txtRest.Text = _settings.RestMinutes.ToString();
|
||||
|
||||
// Apply Theme
|
||||
ApplyTheme();
|
||||
|
||||
// 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);
|
||||
|
||||
_monitor.Start();
|
||||
|
||||
// Set tray icon
|
||||
try
|
||||
{
|
||||
// Generate and set custom icon
|
||||
Icon icon = IconGenerator.GenerateClockIcon();
|
||||
this.Icon = icon;
|
||||
notifyIcon1.Icon = icon;
|
||||
}
|
||||
catch
|
||||
{
|
||||
notifyIcon1.Icon = SystemIcons.Application;
|
||||
}
|
||||
|
||||
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.BackColor = bg; // Revert to bg color (user preference)
|
||||
|
||||
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(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;
|
||||
|
||||
// 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 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<object, EventArgs>(Monitor_StateChanged), sender, e);
|
||||
return;
|
||||
}
|
||||
UpdateStatusUI();
|
||||
}
|
||||
|
||||
private void UpdateStatusUI()
|
||||
{
|
||||
if (_monitor == null) return;
|
||||
|
||||
bool dark = _settings.IsDarkMode;
|
||||
|
||||
switch (_monitor.CurrentState)
|
||||
{
|
||||
case MonitorState.Idle:
|
||||
lblStatus.Text = "状态: 空闲";
|
||||
lblStatus.ForeColor = Color.Gray;
|
||||
lblTimeLeft.Text = "--:--";
|
||||
lblTimeLeft.ForeColor = Color.Gray;
|
||||
break;
|
||||
case MonitorState.Working:
|
||||
lblStatus.Text = "状态: 工作中";
|
||||
lblStatus.ForeColor = dark ? Color.LightGreen : Color.Green;
|
||||
lblTimeLeft.ForeColor = dark ? Color.White : Color.Black;
|
||||
break;
|
||||
case MonitorState.Resting:
|
||||
lblStatus.Text = "状态: 休息中";
|
||||
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<object, TimeSpan>(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<object, EventArgs>(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<object, EventArgs>(Monitor_RestEnded), sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_restForm != null && !_restForm.IsDisposed)
|
||||
{
|
||||
_restForm.Close();
|
||||
}
|
||||
|
||||
notifyIcon1.ShowBalloonTip(3000, "休息结束", "继续加油工作吧!", ToolTipIcon.Info);
|
||||
}
|
||||
|
||||
private bool _hasShownMinimizeTip = false;
|
||||
|
||||
private void btnHide_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.Hide();
|
||||
if (!_hasShownMinimizeTip)
|
||||
{
|
||||
notifyIcon1.ShowBalloonTip(2000, "已隐藏", "程序仍在后台运行,双击托盘图标恢复。", ToolTipIcon.Info);
|
||||
_hasShownMinimizeTip = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (e.CloseReason == CloseReason.UserClosing)
|
||||
{
|
||||
e.Cancel = true;
|
||||
this.Hide();
|
||||
if (!_hasShownMinimizeTip)
|
||||
{
|
||||
notifyIcon1.ShowBalloonTip(2000, "已隐藏", "程序仍在后台运行,双击托盘图标恢复。", ToolTipIcon.Info);
|
||||
_hasShownMinimizeTip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void toolStripMenuItemExit_Click(object sender, EventArgs e)
|
||||
{
|
||||
_monitor.Stop();
|
||||
notifyIcon1.Visible = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user