C#的winform框架制作计时器(线程控制)

  在很多应用场景中,我们可能需要实现一个计时器功能,比如应用中的计时器、倒计时等。本文将介绍如何使用C#中的TaskCancellationToken来创建一个简单的计时器,并结合WinForms界面实现计时、暂停、继续和停止功能。

项目需求

我们将实现一个具有以下功能的计时器:

  1. 开始计时:点击开始按钮后,计时器开始计时。
  2. 暂停计时:点击暂停按钮后,暂停计时。
  3. 继续计时:点击继续按钮后,恢复暂停的计时。
  4. 停止计时:点击停止按钮后,结束计时并显示总计时。

实现步骤

我们将分步实现这些功能,以下是完整代码。

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Time
{
    public partial class Form1 : Form
    {
        // 计时任务
        private Task _timerTask;

        // 计时开始时间
        private DateTime _startTime;

        // 计时暂停时间
        private DateTime _pauseTime;

        // 已用时间
        private TimeSpan _elapsedTime = TimeSpan.Zero;

        // 是否处于暂停状态
        private bool _isPaused = false;

        // 用于取消计时任务的取消令牌
        private CancellationTokenSource _cts;

        private ManualResetEvent ma;

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 开始按钮,开始计时并重置时间
        /// </summary>
        private void btnStart_Click(object sender, EventArgs e)
        {
            _elapsedTime = TimeSpan.Zero;
            _startTime = DateTime.Now;
            _isPaused = false;
            _pauseTime = DateTime.MinValue;

            StartNewTimerTask();

            UpdateButtonState(ButtonState.Started);
        }

        /// <summary>
        /// 暂停按钮,暂停计时
        /// </summary>
        private void btnPause_Click(object sender, EventArgs e)
        {
            _isPaused = true;
            _pauseTime = DateTime.Now;
            _cts.Cancel();

            UpdateButtonState(ButtonState.Paused);
        }

        /// <summary>
        /// 继续按钮,继续计时
        /// </summary>
        private void btnContinue_Click(object sender, EventArgs e)
        {
            _isPaused = false;
            _startTime = DateTime.Now - _elapsedTime;
            _pauseTime = DateTime.MinValue;

            StartNewTimerTask();

            UpdateButtonState(ButtonState.Continued);
        }

        /// <summary>
        /// 启动新的计时任务
        /// </summary>
        private void StartNewTimerTask()
        {
            _cts?.Dispose();
            _cts = new CancellationTokenSource();
            var token = _cts.Token;
             // ma.WaitOne(); 模拟量
            _timerTask = Task.Run(() => StartTimer(token), token);
        }

        /// <summary>
        /// 停止按钮,停止计时并保存时间
        /// </summary>
        private void btnStop_Click(object sender, EventArgs e)
        {
            if (_isPaused && _pauseTime != DateTime.MinValue)
            {
                _elapsedTime = _pauseTime - _startTime;
            }
            else
            {
                _elapsedTime = DateTime.Now - _startTime;
            }

            lblTime.Text = _elapsedTime.ToString(@"hh\:mm\:ss\:fff");

            UpdateButtonState(ButtonState.Stopped);

            txtSavedTime.Text = _elapsedTime.ToString(@"hh\:mm\:ss\:fff");

            _cts.Cancel();
        }

        /// <summary>
        /// 计时逻辑,支持取消令牌
        /// </summary>
        private void StartTimer(CancellationToken token)
        {
            while (!_isPaused && !token.IsCancellationRequested)
            {
                _elapsedTime = DateTime.Now - _startTime;  
               // Stopwatch timer = Stopwatch.StartNew();//高精度定时器 ----Timer
                Invoke(new Action(() =>
                {
                    lblTime.Text = _elapsedTime.ToString(@"hh\:mm\:ss\:fff");
                }));

                Task.Delay(10).Wait();
            }
        }

        /// <summary>
        /// 更新按钮状态
        /// </summary>
        /// 状态机设计模式
        private void UpdateButtonState(ButtonState state)
        {
            switch (state)
            {
                case ButtonState.Started:
                    btnStart.Enabled = false;
                    btnPause.Enabled = true;
                    btnStop.Enabled = true;
                    btnContinue.Enabled = false;
                    break;
                case ButtonState.Paused:
                    btnPause.Enabled = false;
                    btnContinue.Enabled = true;
                    break;
                case ButtonState.Continued:
                    btnPause.Enabled = true;
                    btnContinue.Enabled = false;
                    break;
                case ButtonState.Stopped:
                    btnStart.Enabled = true;
                    btnPause.Enabled = false;
                    btnStop.Enabled = false;
                    btnContinue.Enabled = false;
                    break;
            }
        }

        /// <summary>
        /// 窗体关闭时,确保取消计时任务并释放资源
        /// </summary>
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            _cts?.Cancel();
            if (_timerTask != null && !_timerTask.IsCompleted)
            {
                _timerTask.Wait();
            }
            base.OnFormClosing(e);
        }
    }

    /// <summary>
    /// 按钮状态枚举
    /// </summary>
    public enum ButtonState
    {
        Started,   // 已开始
        Paused,    // 已暂停
        Continued, // 已继续
        Stopped    // 已停止
    }

    /// <summary>
    /// 枚举扩展方法
    /// </summary>
    public static class ButtonStateExtensions
    {
        public static string GetDescription(this ButtonState state)
        {
            return state switch
            {
                ButtonState.Started => "计时已开始",
                ButtonState.Paused => "计时已暂停",
                ButtonState.Continued => "计时已继续",
                ButtonState.Stopped => "计时已停止",
                _ => "未知状态"
            };
        }
    }
}

代码说明

  1. 计时逻辑实现

    • 通过 TaskCancellationToken 来管理计时任务的启动、暂停、继续和停止。
    • 计时器的时间计算是通过 DateTime.Now 和开始时间 (_startTime) 来实现的,计算出已用时间 (_elapsedTime)。
  2. UI 更新

    • 通过 Invoke 方法在计时过程中更新界面上的显示(lblTime)。
    • 使用了格式化的字符串 @"hh\:mm\:ss\:fff" 来显示小时、分钟、秒和毫秒。
  3. 按钮状态管理

    • 通过 ButtonState 枚举来管理按钮的启用/禁用状态。
    • UpdateButtonState 方法根据当前的按钮状态来更新界面的按钮状态,确保界面交互的合理性。
  4. 取消令牌

    • 使用 CancellationTokenSource 来支持在计时过程中取消计时任务。
    • 通过 Cancel() 方法可以取消当前的计时任务,达到暂停和停止计时的目的。
  5. 窗体关闭时处理

    • 在窗体关闭时确保取消正在进行的计时任务,避免资源泄露。

designer代码如下:

namespace Time
{
    partial class Form1
    {
        // 组件容器
        private System.ComponentModel.IContainer components = null;

        // 释放组件的方法
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        // 窗体控件初始化
        private void InitializeComponent()
        {
            lblTime = new Label();
            btnStart = new Button();
            btnPause = new Button();
            btnStop = new Button();
            btnContinue = new Button();
            txtSavedTime = new TextBox();
            SuspendLayout();
            // 
            // lblTime
            // 
            lblTime.AutoSize = true;
            lblTime.Font = new Font("Segoe UI", 36F, FontStyle.Regular, GraphicsUnit.Point);
            lblTime.ForeColor = Color.FromArgb(60, 60, 60);
            lblTime.Location = new Point(62, 49);
            lblTime.Name = "lblTime";
            lblTime.Size = new Size(362, 81);
            lblTime.TabIndex = 0;
            lblTime.Text = "00:00:00:000";
            // 
            // btnStart
            // 
            btnStart.BackColor = Color.FromArgb(240, 240, 240);
            btnStart.FlatStyle = FlatStyle.Flat;
            btnStart.Font = new Font("Segoe UI", 14F, FontStyle.Regular, GraphicsUnit.Point);
            btnStart.ForeColor = Color.FromArgb(60, 60, 60);
            btnStart.Location = new Point(24, 160);
            btnStart.Name = "btnStart";
            btnStart.Size = new Size(100, 50);
            btnStart.TabIndex = 1;
            btnStart.Text = "开始";
            btnStart.UseVisualStyleBackColor = false;
            btnStart.Click += btnStart_Click;
            // 
            // btnPause
            // 
            btnPause.BackColor = Color.FromArgb(240, 240, 240);
            btnPause.Enabled = false;
            btnPause.FlatStyle = FlatStyle.Flat;
            btnPause.Font = new Font("Segoe UI", 14F, FontStyle.Regular, GraphicsUnit.Point);
            btnPause.ForeColor = Color.FromArgb(60, 60, 60);
            btnPause.Location = new Point(140, 160);
            btnPause.Name = "btnPause";
            btnPause.Size = new Size(100, 50);
            btnPause.TabIndex = 2;
            btnPause.Text = "暂停";
            btnPause.UseVisualStyleBackColor = false;
            btnPause.Click += btnPause_Click;
            // 
            // btnStop
            // 
            btnStop.BackColor = Color.FromArgb(240, 240, 240);
            btnStop.Enabled = false;
            btnStop.FlatStyle = FlatStyle.Flat;
            btnStop.Font = new Font("Segoe UI", 14F, FontStyle.Regular, GraphicsUnit.Point);
            btnStop.ForeColor = Color.FromArgb(60, 60, 60);
            btnStop.Location = new Point(374, 160);
            btnStop.Name = "btnStop";
            btnStop.Size = new Size(100, 50);
            btnStop.TabIndex = 4;
            btnStop.Text = "停止";
            btnStop.UseVisualStyleBackColor = false;
            btnStop.Click += btnStop_Click;
            // 
            // btnContinue
            // 
            btnContinue.BackColor = Color.FromArgb(240, 240, 240);
            btnContinue.Enabled = false;
            btnContinue.FlatStyle = FlatStyle.Flat;
            btnContinue.Font = new Font("Segoe UI", 14F, FontStyle.Regular, GraphicsUnit.Point);
            btnContinue.ForeColor = Color.FromArgb(60, 60, 60);
            btnContinue.Location = new Point(259, 160);
            btnContinue.Name = "btnContinue";
            btnContinue.Size = new Size(100, 50);
            btnContinue.TabIndex = 3;
            btnContinue.Text = "继续";
            btnContinue.UseVisualStyleBackColor = false;
            btnContinue.Click += btnContinue_Click;
            // 
            // txtSavedTime
            // 
            txtSavedTime.BackColor = Color.FromArgb(245, 245, 245);
            txtSavedTime.BorderStyle = BorderStyle.None;
            txtSavedTime.Enabled = false;
            txtSavedTime.Font = new Font("Segoe UI", 14F, FontStyle.Regular, GraphicsUnit.Point);
            txtSavedTime.ForeColor = Color.FromArgb(60, 60, 60);
            txtSavedTime.Location = new Point(140, 230);
            txtSavedTime.Name = "txtSavedTime";
            txtSavedTime.Size = new Size(300, 32);
            txtSavedTime.TabIndex = 5;
            // 
            // Form1
            // 
            ClientSize = new Size(500, 300);
            Controls.Add(txtSavedTime);
            Controls.Add(btnStop);
            Controls.Add(btnContinue);
            Controls.Add(btnPause);
            Controls.Add(btnStart);
            Controls.Add(lblTime);
            Name = "Form1";
            Text = "计时器";
            ResumeLayout(false);
            PerformLayout();
        }

        // 窗体控件
        private System.Windows.Forms.Label lblTime;  // 显示时间的标签
        private System.Windows.Forms.Button btnStart;  // 开始按钮
        private System.Windows.Forms.Button btnPause;  // 暂停按钮
        private System.Windows.Forms.Button btnStop;  // 停止按钮
        private System.Windows.Forms.Button btnContinue;  // 继续按钮
        private System.Windows.Forms.TextBox txtSavedTime;  // 用于保存时间的文本框
    }
}

效果图:


总结

本示例实现了一个简单的计时器,涵盖了计时的启动、暂停、继续和停止功能。通过使用 TaskCancellationTokenManualResetEvent 和按钮状态管理,我们可以更好地控制计时器的运行,并确保 UI 在多线程环境下保持响应。这个结构也便于扩展,可以根据需求进一步增加功能,比如记录多个计时任务,添加更多的操作等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值