add: 久坐提醒项目文件

This commit is contained in:
2026-01-17 12:48:01 +08:00
commit 5fba4ff110
10 changed files with 1535 additions and 0 deletions

91
.gitignore vendored Normal file
View File

@@ -0,0 +1,91 @@
## .NET 项目忽略文件
# 编译输出
[Bb]in/
[Oo]bj/
[Oo]ut/
# Visual Studio 用户特定文件
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio 缓存/选项目录
.vs/
.vscode/
# Visual Studio 代码分析结果
*.sln.iml
# 构建结果
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# NuGet 包
*.nupkg
*.snupkg
**/packages/*
!**/packages/build/
*.nuget.props
*.nuget.targets
# MSTest 测试结果
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# .NET Core 项目文件
project.lock.json
project.fragment.lock.json
artifacts/
# Rider
.idea/
*.sln.iml
# VS Code
.vscode/
# 用户特定文件
*.user
*.suo
*.userosscache
*.sln.docstates
# 临时文件
*.tmp
*.temp
*.log
*.cache
# 操作系统文件
.DS_Store
Thumbs.db
ehthumbs.db
Desktop.ini
# 备份文件
*.bak
*.swp
*~
# 设置文件(如果包含敏感信息)
# settings.json

182
ActivityMonitor.cs Normal file
View File

@@ -0,0 +1,182 @@
using System;
using System.Windows.Forms;
namespace TimerApp
{
public enum MonitorState
{
Idle,
Working,
Resting
}
public class ActivityMonitor
{
private System.Windows.Forms.Timer _timer;
private DateTime _lastWorkStartTime;
private TimeSpan _accumulatedWorkTime;
private DateTime _restStartTime;
// 配置 (默认值)
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
public TimeSpan RestDuration { get; set; } = TimeSpan.FromMinutes(1);
public TimeSpan IdleThreshold { get; set; } = TimeSpan.FromSeconds(30);
public MonitorState CurrentState { get; private set; } = MonitorState.Idle;
// 事件
public event EventHandler<TimeSpan> WorkProgressChanged; // 剩余工作时间
public event EventHandler<TimeSpan> RestProgressChanged; // 剩余休息时间
public event EventHandler RestStarted;
public event EventHandler RestEnded;
public event EventHandler StateChanged;
public ActivityMonitor()
{
_timer = new System.Windows.Forms.Timer();
_timer.Interval = 1000; // 1秒检查一次
_timer.Tick += Timer_Tick;
}
public void Start()
{
_timer.Start();
ResetWork();
}
public void Stop()
{
_timer.Stop();
}
private void ResetWork()
{
_accumulatedWorkTime = TimeSpan.Zero;
_lastWorkStartTime = DateTime.Now;
ChangeState(MonitorState.Idle);
}
private void ChangeState(MonitorState newState)
{
if (CurrentState != newState)
{
CurrentState = newState;
StateChanged?.Invoke(this, EventArgs.Empty);
}
}
private void Timer_Tick(object sender, EventArgs e)
{
long idleMs = NativeMethods.GetIdleTime();
TimeSpan idleTime = TimeSpan.FromMilliseconds(idleMs);
if (CurrentState == MonitorState.Resting)
{
// 休息模式逻辑
TimeSpan timeInRest = DateTime.Now - _restStartTime;
TimeSpan remainingRest = RestDuration - timeInRest;
if (remainingRest <= TimeSpan.Zero)
{
// 休息结束
RestEnded?.Invoke(this, EventArgs.Empty);
ResetWork(); // 重新开始工作周期
}
else
{
RestProgressChanged?.Invoke(this, remainingRest);
}
}
else
{
// 工作/空闲模式逻辑
if (idleTime > IdleThreshold)
{
// 用户离开了
if (CurrentState == MonitorState.Working)
{
// 如果正在工作,但离开了,暂停工作计时?
// 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积
// 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲
ChangeState(MonitorState.Idle);
}
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
// 是否应该重置工作计时器?
// 假设用户去开会了1小时回来应该重新计算20分钟。
if (idleTime > RestDuration)
{
_accumulatedWorkTime = TimeSpan.Zero;
}
}
else
{
// 用户在活动
if (CurrentState == MonitorState.Idle)
{
// 从空闲变为工作
ChangeState(MonitorState.Working);
_lastWorkStartTime = DateTime.Now;
}
// 累加工作时间
// 简单的累加逻辑:这一秒是工作的
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
// 检查是否达到工作时长
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
if (remainingWork <= TimeSpan.Zero)
{
// 触发休息
_restStartTime = DateTime.Now;
ChangeState(MonitorState.Resting);
RestStarted?.Invoke(this, EventArgs.Empty);
}
else
{
WorkProgressChanged?.Invoke(this, remainingWork);
}
}
}
}
// 用于强制重置或测试
public void ForceRest()
{
_restStartTime = DateTime.Now;
ChangeState(MonitorState.Resting);
RestStarted?.Invoke(this, EventArgs.Empty);
}
public void RefreshStatus()
{
if (CurrentState == MonitorState.Working)
{
TimeSpan remaining = WorkDuration - _accumulatedWorkTime;
WorkProgressChanged?.Invoke(this, remaining);
}
else if (CurrentState == MonitorState.Resting)
{
TimeSpan timeInRest = DateTime.Now - _restStartTime;
TimeSpan remaining = RestDuration - timeInRest;
RestProgressChanged?.Invoke(this, remaining);
}
}
public void Restart()
{
_accumulatedWorkTime = TimeSpan.Zero;
_lastWorkStartTime = DateTime.Now;
// Ensure timer is running
if (!_timer.Enabled) _timer.Start();
// Force state to Working since user manually restarted
ChangeState(MonitorState.Working);
// Immediately refresh UI to show full duration
RefreshStatus();
}
}
}

46
AppSettings.cs Normal file
View File

@@ -0,0 +1,46 @@
using System;
using System.IO;
using System.Text.Json;
namespace TimerApp
{
public class AppSettings
{
public int WorkMinutes { get; set; } = 20;
public int RestMinutes { get; set; } = 1;
public int IdleThresholdSeconds { get; set; } = 30;
public bool IsDarkMode { get; set; } = true;
private static string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
public static AppSettings Load()
{
try
{
if (File.Exists(ConfigPath))
{
string json = File.ReadAllText(ConfigPath);
return JsonSerializer.Deserialize<AppSettings>(json);
}
}
catch
{
// ignore errors, return default
}
return new AppSettings();
}
public void Save()
{
try
{
string json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(ConfigPath, json);
}
catch
{
// ignore
}
}
}
}

67
IconGenerator.cs Normal file
View File

@@ -0,0 +1,67 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
namespace TimerApp
{
public static class IconGenerator
{
public static Icon GenerateClockIcon()
{
int size = 64;
using (Bitmap bmp = new Bitmap(size, size))
using (Graphics g = Graphics.FromImage(bmp))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.Transparent);
// 绘制闹钟主体
int margin = 4;
int clockSize = size - margin * 2;
Rectangle rect = new Rectangle(margin, margin, clockSize, clockSize);
// 外圈
using (Pen pen = new Pen(Color.White, 4))
{
g.DrawEllipse(pen, rect);
}
// 两个耳朵 (闹钟铃)
using (Brush earBrush = new SolidBrush(Color.White))
{
// 左耳
g.FillPie(earBrush, 0, 0, size/2, size/2, 200, 50);
// 右耳
g.FillPie(earBrush, size/2, 0, size/2, size/2, 290, 50);
}
// 重绘外圈盖住耳朵连接处
using (Brush bgBrush = new SolidBrush(Color.FromArgb(30, 30, 30))) // 深色背景
{
g.FillEllipse(bgBrush, rect);
}
using (Pen pen = new Pen(Color.White, 3))
{
g.DrawEllipse(pen, rect);
}
// 指针
Point center = new Point(size / 2, size / 2);
using (Pen handPen = new Pen(Color.LightGreen, 3))
{
handPen.EndCap = LineCap.Round;
// 时针
g.DrawLine(handPen, center, new Point(center.X + 10, center.Y - 10));
// 分针
g.DrawLine(handPen, center, new Point(center.X, center.Y - 18));
}
// 转换为图标
// 注意GetHicon 需要释放
IntPtr hIcon = bmp.GetHicon();
return Icon.FromHandle(hIcon);
}
}
}
}

431
MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,431 @@
namespace TimerApp
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.label1 = new System.Windows.Forms.Label();
this.btnWorkMinus = new System.Windows.Forms.Button();
this.txtWork = new System.Windows.Forms.TextBox();
this.btnWorkPlus = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.btnRestMinus = new System.Windows.Forms.Button();
this.txtRest = new System.Windows.Forms.TextBox();
this.btnRestPlus = new System.Windows.Forms.Button();
this.btnStartStop = new System.Windows.Forms.Button();
this.btnReset = new System.Windows.Forms.Button();
this.lblStatus = new System.Windows.Forms.Label();
this.lblTimeLeft = new System.Windows.Forms.Label();
this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.toolStripMenuItemShow = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItemExit = new System.Windows.Forms.ToolStripMenuItem();
this.btnHide = new System.Windows.Forms.Button();
this.pnlTitle = new System.Windows.Forms.Panel();
this.lblTitle = new System.Windows.Forms.Label();
this.btnClose = new System.Windows.Forms.Button();
this.btnMin = new System.Windows.Forms.Button();
this.btnTheme = new System.Windows.Forms.Button();
this.pnlSettings = new System.Windows.Forms.Panel();
this.contextMenuStrip1.SuspendLayout();
this.pnlTitle.SuspendLayout();
this.pnlSettings.SuspendLayout();
this.SuspendLayout();
//
// pnlTitle
//
this.pnlTitle.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.pnlTitle.Controls.Add(this.btnTheme);
this.pnlTitle.Controls.Add(this.btnMin);
this.pnlTitle.Controls.Add(this.btnClose);
this.pnlTitle.Controls.Add(this.lblTitle);
this.pnlTitle.Dock = System.Windows.Forms.DockStyle.Top;
this.pnlTitle.Location = new System.Drawing.Point(0, 0);
this.pnlTitle.Name = "pnlTitle";
this.pnlTitle.Size = new System.Drawing.Size(320, 40);
this.pnlTitle.TabIndex = 10;
this.pnlTitle.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pnlTitle_MouseDown);
//
// lblTitle
//
this.lblTitle.AutoSize = true;
this.lblTitle.Font = new System.Drawing.Font("Microsoft YaHei UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblTitle.ForeColor = System.Drawing.Color.LightGray;
this.lblTitle.Location = new System.Drawing.Point(12, 9);
this.lblTitle.Name = "lblTitle";
this.lblTitle.Size = new System.Drawing.Size(82, 23);
this.lblTitle.TabIndex = 0;
this.lblTitle.Text = "久坐提醒";
this.lblTitle.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pnlTitle_MouseDown);
//
// btnClose
//
this.btnClose.Dock = System.Windows.Forms.DockStyle.Right;
this.btnClose.FlatAppearance.BorderSize = 0;
this.btnClose.FlatAppearance.MouseOverBackColor = System.Drawing.Color.Red;
this.btnClose.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnClose.ForeColor = System.Drawing.Color.White;
this.btnClose.Location = new System.Drawing.Point(280, 0);
this.btnClose.Name = "btnClose";
this.btnClose.Size = new System.Drawing.Size(40, 40);
this.btnClose.TabIndex = 1;
this.btnClose.Text = "✕";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// btnTheme
//
this.btnTheme.Dock = System.Windows.Forms.DockStyle.Right;
this.btnTheme.FlatAppearance.BorderSize = 0;
this.btnTheme.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnTheme.ForeColor = System.Drawing.Color.White;
this.btnTheme.Location = new System.Drawing.Point(200, 0);
this.btnTheme.Name = "btnTheme";
this.btnTheme.Size = new System.Drawing.Size(40, 40);
this.btnTheme.TabIndex = 3;
this.btnTheme.Text = "☀";
this.btnTheme.UseVisualStyleBackColor = true;
this.btnTheme.Click += new System.EventHandler(this.btnTheme_Click);
//
// btnMin
//
this.btnMin.Dock = System.Windows.Forms.DockStyle.Right;
this.btnMin.FlatAppearance.BorderSize = 0;
this.btnMin.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnMin.ForeColor = System.Drawing.Color.White;
this.btnMin.Location = new System.Drawing.Point(240, 0);
this.btnMin.Name = "btnMin";
this.btnMin.Size = new System.Drawing.Size(40, 40);
this.btnMin.TabIndex = 2;
this.btnMin.Text = "―";
this.btnMin.UseVisualStyleBackColor = true;
this.btnMin.Click += new System.EventHandler(this.btnMin_Click);
//
// lblTimeLeft
//
this.lblTimeLeft.Dock = System.Windows.Forms.DockStyle.Top;
this.lblTimeLeft.Font = new System.Drawing.Font("Segoe UI Light", 48F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblTimeLeft.ForeColor = System.Drawing.Color.White;
this.lblTimeLeft.Location = new System.Drawing.Point(0, 40);
this.lblTimeLeft.Name = "lblTimeLeft";
this.lblTimeLeft.Size = new System.Drawing.Size(320, 120);
this.lblTimeLeft.TabIndex = 6;
this.lblTimeLeft.Text = "00:00";
this.lblTimeLeft.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// lblStatus
//
this.lblStatus.Dock = System.Windows.Forms.DockStyle.Top;
this.lblStatus.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblStatus.ForeColor = System.Drawing.Color.Gray;
this.lblStatus.Location = new System.Drawing.Point(0, 160);
this.lblStatus.Name = "lblStatus";
this.lblStatus.Size = new System.Drawing.Size(320, 40);
this.lblStatus.TabIndex = 5;
this.lblStatus.Text = "Idle";
this.lblStatus.TextAlign = System.Drawing.ContentAlignment.TopCenter;
//
// pnlSettings
//
this.pnlSettings.Controls.Add(this.btnReset);
this.pnlSettings.Controls.Add(this.btnStartStop);
this.pnlSettings.Controls.Add(this.btnRestPlus);
this.pnlSettings.Controls.Add(this.txtRest);
this.pnlSettings.Controls.Add(this.btnRestMinus);
this.pnlSettings.Controls.Add(this.label2);
this.pnlSettings.Controls.Add(this.btnWorkPlus);
this.pnlSettings.Controls.Add(this.txtWork);
this.pnlSettings.Controls.Add(this.btnWorkMinus);
this.pnlSettings.Controls.Add(this.label1);
this.pnlSettings.Dock = System.Windows.Forms.DockStyle.Bottom;
this.pnlSettings.Location = new System.Drawing.Point(0, 180);
this.pnlSettings.Name = "pnlSettings";
this.pnlSettings.Size = new System.Drawing.Size(320, 170);
this.pnlSettings.TabIndex = 11;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label1.ForeColor = System.Drawing.Color.LightGray;
this.label1.Location = new System.Drawing.Point(40, 20);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(100, 23);
this.label1.TabIndex = 0;
this.label1.Text = "工作 (分):";
//
// btnWorkMinus
//
this.btnWorkMinus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.btnWorkMinus.FlatAppearance.BorderSize = 0;
this.btnWorkMinus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnWorkMinus.ForeColor = System.Drawing.Color.White;
this.btnWorkMinus.Location = new System.Drawing.Point(160, 18);
this.btnWorkMinus.Name = "btnWorkMinus";
this.btnWorkMinus.Size = new System.Drawing.Size(30, 30);
this.btnWorkMinus.TabIndex = 1;
this.btnWorkMinus.Text = "-";
this.btnWorkMinus.UseVisualStyleBackColor = false;
//
// txtWork
//
this.txtWork.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.txtWork.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.txtWork.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.txtWork.ForeColor = System.Drawing.Color.White;
this.txtWork.Location = new System.Drawing.Point(190, 18);
this.txtWork.Name = "txtWork";
this.txtWork.ReadOnly = false;
this.txtWork.Size = new System.Drawing.Size(60, 30);
this.txtWork.TabIndex = 2;
this.txtWork.Text = "20";
this.txtWork.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// btnWorkPlus
//
this.btnWorkPlus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.btnWorkPlus.FlatAppearance.BorderSize = 0;
this.btnWorkPlus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnWorkPlus.ForeColor = System.Drawing.Color.White;
this.btnWorkPlus.Location = new System.Drawing.Point(250, 18);
this.btnWorkPlus.Name = "btnWorkPlus";
this.btnWorkPlus.Size = new System.Drawing.Size(30, 30);
this.btnWorkPlus.TabIndex = 3;
this.btnWorkPlus.Text = "+";
this.btnWorkPlus.UseVisualStyleBackColor = false;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.label2.ForeColor = System.Drawing.Color.LightGray;
this.label2.Location = new System.Drawing.Point(40, 60);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(100, 23);
this.label2.TabIndex = 4;
this.label2.Text = "休息 (分):";
//
// btnRestMinus
//
this.btnRestMinus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.btnRestMinus.FlatAppearance.BorderSize = 0;
this.btnRestMinus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnRestMinus.ForeColor = System.Drawing.Color.White;
this.btnRestMinus.Location = new System.Drawing.Point(160, 58);
this.btnRestMinus.Name = "btnRestMinus";
this.btnRestMinus.Size = new System.Drawing.Size(30, 30);
this.btnRestMinus.TabIndex = 5;
this.btnRestMinus.Text = "-";
this.btnRestMinus.UseVisualStyleBackColor = false;
//
// txtRest
//
this.txtRest.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.txtRest.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.txtRest.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.txtRest.ForeColor = System.Drawing.Color.White;
this.txtRest.Location = new System.Drawing.Point(190, 58);
this.txtRest.Name = "txtRest";
this.txtRest.ReadOnly = false;
this.txtRest.Size = new System.Drawing.Size(60, 30);
this.txtRest.TabIndex = 6;
this.txtRest.Text = "1";
this.txtRest.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// btnRestPlus
//
this.btnRestPlus.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.btnRestPlus.FlatAppearance.BorderSize = 0;
this.btnRestPlus.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnRestPlus.ForeColor = System.Drawing.Color.White;
this.btnRestPlus.Location = new System.Drawing.Point(250, 58);
this.btnRestPlus.Name = "btnRestPlus";
this.btnRestPlus.Size = new System.Drawing.Size(30, 30);
this.btnRestPlus.TabIndex = 7;
this.btnRestPlus.Text = "+";
this.btnRestPlus.UseVisualStyleBackColor = false;
//
// btnStartStop
//
this.btnStartStop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
this.btnStartStop.FlatAppearance.BorderSize = 0;
this.btnStartStop.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnStartStop.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.btnStartStop.ForeColor = System.Drawing.Color.White;
this.btnStartStop.Location = new System.Drawing.Point(170, 110);
this.btnStartStop.Name = "btnStartStop";
this.btnStartStop.Size = new System.Drawing.Size(110, 35);
this.btnStartStop.TabIndex = 4;
this.btnStartStop.Text = "应用设置";
this.btnStartStop.UseVisualStyleBackColor = false;
this.btnStartStop.Click += new System.EventHandler(this.btnStartStop_Click);
//
// btnReset
//
this.btnReset.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
this.btnReset.FlatAppearance.BorderSize = 0;
this.btnReset.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnReset.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.btnReset.ForeColor = System.Drawing.Color.White;
this.btnReset.Location = new System.Drawing.Point(40, 110);
this.btnReset.Name = "btnReset";
this.btnReset.Size = new System.Drawing.Size(110, 35);
this.btnReset.TabIndex = 8;
this.btnReset.Text = "重置计时";
this.btnReset.UseVisualStyleBackColor = false;
this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
//
// btnHide
//
this.btnHide.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
this.btnHide.FlatAppearance.BorderSize = 0;
this.btnHide.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnHide.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.btnHide.ForeColor = System.Drawing.Color.White;
this.btnHide.Location = new System.Drawing.Point(40, 160);
this.btnHide.Name = "btnHide";
this.btnHide.Size = new System.Drawing.Size(220, 35);
this.btnHide.TabIndex = 7;
this.btnHide.Text = "最小化到托盘";
this.btnHide.UseVisualStyleBackColor = false;
this.btnHide.Visible = false;
this.btnHide.Click += new System.EventHandler(this.btnHide_Click);
//
// notifyIcon1
//
this.notifyIcon1.ContextMenuStrip = this.contextMenuStrip1;
this.notifyIcon1.Text = "TimerApp";
this.notifyIcon1.Visible = true;
this.notifyIcon1.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.notifyIcon1_MouseDoubleClick);
//
// contextMenuStrip1
//
this.contextMenuStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripMenuItemShow,
this.toolStripMenuItemExit});
this.contextMenuStrip1.Name = "contextMenuStrip1";
this.contextMenuStrip1.Size = new System.Drawing.Size(109, 52);
//
// toolStripMenuItemShow
//
this.toolStripMenuItemShow.Name = "toolStripMenuItemShow";
this.toolStripMenuItemShow.Size = new System.Drawing.Size(108, 24);
this.toolStripMenuItemShow.Text = "显示";
this.toolStripMenuItemShow.Click += new System.EventHandler(this.toolStripMenuItemShow_Click);
//
// toolStripMenuItemExit
//
this.toolStripMenuItemExit.Name = "toolStripMenuItemExit";
this.toolStripMenuItemExit.Size = new System.Drawing.Size(108, 24);
this.toolStripMenuItemExit.Text = "退出";
this.toolStripMenuItemExit.Click += new System.EventHandler(this.toolStripMenuItemExit_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(30)))), ((int)(((byte)(30)))));
this.ClientSize = new System.Drawing.Size(320, 380);
this.Controls.Add(this.pnlSettings); // Add panel first to be at bottom of z-order
this.Controls.Add(this.lblStatus);
this.Controls.Add(this.lblTimeLeft);
this.Controls.Add(this.pnlTitle);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Focus Timer";
this.Load += new System.EventHandler(this.MainForm_Load);
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
// Ensure pnlSettings matches form background initially
this.pnlSettings.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(30)))), ((int)(((byte)(30)))));
this.contextMenuStrip1.ResumeLayout(false);
this.pnlTitle.ResumeLayout(false);
this.pnlTitle.PerformLayout();
this.pnlSettings.ResumeLayout(false);
this.pnlSettings.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button btnWorkMinus;
private System.Windows.Forms.TextBox txtWork;
private System.Windows.Forms.Button btnWorkPlus;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button btnRestMinus;
private System.Windows.Forms.TextBox txtRest;
private System.Windows.Forms.Button btnRestPlus;
private System.Windows.Forms.Button btnStartStop;
private System.Windows.Forms.Button btnReset;
private System.Windows.Forms.Label lblStatus;
private System.Windows.Forms.Label lblTimeLeft;
private System.Windows.Forms.NotifyIcon notifyIcon1;
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemShow;
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemExit;
private System.Windows.Forms.Button btnHide;
private System.Windows.Forms.Panel pnlTitle;
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.Button btnMin;
private System.Windows.Forms.Button btnTheme;
private System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.Panel pnlSettings;
}
}

477
MainForm.cs Normal file
View 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;
}
}
}

52
NativeMethods.cs Normal file
View File

@@ -0,0 +1,52 @@
using System;
using System.Runtime.InteropServices;
namespace TimerApp
{
public static class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
struct LASTINPUTINFO
{
public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO));
[MarshalAs(UnmanagedType.U4)]
public UInt32 cbSize;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwTime;
}
[DllImport("user32.dll")]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
/// <summary>
/// 获取系统空闲时间(毫秒)
/// </summary>
/// <returns></returns>
public static long GetIdleTime()
{
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
lastInputInfo.dwTime = 0;
if (GetLastInputInfo(ref lastInputInfo))
{
// Environment.TickCount 可能会在大约 24.9 天后翻转为负数,
// 但 GetLastInputInfo 返回的也是 uint (DWORD),所以我们统一转为 long 处理差值
// 或者直接使用 unchecked 减法处理溢出
// 更稳健的做法是使用 GetTickCount64 (Vista+),但 Environment.TickCount 在 .NET Core 3.1+ 已经是 64位了(Environment.TickCount64)
// 这里为了兼容性,我们简单处理。注意 GetLastInputInfo 返回的是 uint 毫秒数。
long envTicks = Environment.TickCount;
// 处理 TickCount 翻转问题 (Environment.TickCount 是 intGetLastInputInfo 是 uint)
// 简单的做法:
return (long)GetTickCount() - (long)lastInputInfo.dwTime;
}
return 0;
}
[DllImport("kernel32.dll")]
static extern uint GetTickCount();
}
}

16
Program.cs Normal file
View File

@@ -0,0 +1,16 @@
namespace TimerApp;
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}

162
RestForm.cs Normal file
View File

@@ -0,0 +1,162 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TimerApp
{
public class RestForm : Form
{
private Label lblMessage;
private Label lblTimer;
private Button btnSkip;
public event EventHandler SkipRequested;
public RestForm()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.lblMessage = new System.Windows.Forms.Label();
this.lblTimer = new System.Windows.Forms.Label();
// this.btnSkip = new RoundedButton(); // Removed custom button
this.SuspendLayout();
// ... (keep form settings)
//
// Form 设置
//
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.WindowState = FormWindowState.Maximized;
this.TopMost = true;
this.BackColor = System.Drawing.Color.Black;
this.Opacity = 0.90; // 90% 不透明度
this.ShowInTaskbar = false;
this.Name = "RestForm";
this.Text = "Rest Now";
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);
//
// lblMessage
//
this.lblMessage.AutoSize = false;
this.lblMessage.Font = new System.Drawing.Font("Microsoft YaHei UI", 36F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
this.lblMessage.ForeColor = System.Drawing.Color.White;
this.lblMessage.Location = new System.Drawing.Point(100, 100);
this.lblMessage.Name = "lblMessage";
// 初始大小会在CenterControls中动态调整
this.lblMessage.Size = new System.Drawing.Size(800, 100);
this.lblMessage.TabIndex = 0;
this.lblMessage.Text = "休息一下,看看远方";
this.lblMessage.TextAlign = ContentAlignment.MiddleCenter;
//
// lblTimer
//
this.lblTimer.AutoSize = false;
this.lblTimer.Font = new System.Drawing.Font("Segoe UI Light", 72F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblTimer.ForeColor = System.Drawing.Color.LightGreen;
this.lblTimer.Location = new System.Drawing.Point(100, 200);
this.lblTimer.Name = "lblTimer";
// 初始大小会在CenterControls中动态调整
this.lblTimer.Size = new System.Drawing.Size(400, 180);
this.lblTimer.TabIndex = 1;
this.lblTimer.Text = "01:00";
this.lblTimer.TextAlign = ContentAlignment.MiddleCenter;
//
// btnSkip
//
this.btnSkip = new System.Windows.Forms.Button();
this.btnSkip.BackColor = System.Drawing.Color.FromArgb(63, 63, 70);
this.btnSkip.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnSkip.FlatAppearance.BorderSize = 0;
this.btnSkip.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(80, 80, 90);
this.btnSkip.Font = new System.Drawing.Font("Microsoft YaHei UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.btnSkip.ForeColor = System.Drawing.Color.White;
this.btnSkip.Location = new System.Drawing.Point(100, 400);
this.btnSkip.Name = "btnSkip";
this.btnSkip.Size = new System.Drawing.Size(120, 46);
this.btnSkip.TabIndex = 2;
this.btnSkip.Text = "跳过";
this.btnSkip.UseVisualStyleBackColor = false;
this.btnSkip.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnSkip.Click += new System.EventHandler(this.btnSkip_Click);
// Add controls
this.Controls.Add(this.btnSkip);
this.Controls.Add(this.lblMessage);
this.Controls.Add(this.lblTimer);
this.btnSkip.BringToFront();
this.Load += new System.EventHandler(this.RestForm_Load);
this.Resize += new System.EventHandler(this.RestForm_Resize);
this.ResumeLayout(false);
this.PerformLayout();
}
private void RestForm_Load(object sender, EventArgs e)
{
CenterControls();
}
private void RestForm_Resize(object sender, EventArgs e)
{
CenterControls();
}
private void CenterControls()
{
// 简单的居中计算
int centerX = this.Width / 2;
int centerY = this.Height / 2;
// Equal spacing logic
int spacing = 80;
// 使用足够大的固定大小,确保文本完整显示
// 使用屏幕宽度的85%作为最大宽度,确保在不同分辨率下都能完整显示
int maxWidth = (int)(this.Width * 0.85);
// 消息文本:使用足够大的尺寸
lblMessage.Size = new Size(maxWidth, 120);
// 时间文本使用足够大的尺寸72pt字体需要较大空间
lblTimer.Size = new Size(maxWidth, 200);
// Timer Center = centerY
lblTimer.Location = new Point(centerX - lblTimer.Width / 2, centerY - lblTimer.Height / 2);
// Message above Timer
lblMessage.Location = new Point(centerX - lblMessage.Width / 2, lblTimer.Top - spacing - lblMessage.Height);
// Button below Timer
btnSkip.Location = new Point(centerX - btnSkip.Width / 2, lblTimer.Bottom + spacing);
}
public void UpdateTime(TimeSpan remaining)
{
if (lblTimer.InvokeRequired)
{
lblTimer.Invoke(new Action<TimeSpan>(UpdateTime), remaining);
}
else
{
// 只更新文本,不重新居中,避免闪烁
// 由于lblTimer已设置固定大小文本居中显示不会因为文本变化而改变位置
lblTimer.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
}
}
private void btnSkip_Click(object sender, EventArgs e)
{
SkipRequested?.Invoke(this, EventArgs.Empty);
this.Close();
}
}
}

11
TimerApp.csproj Normal file
View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>