C# WinForms打字程序Stopwatch计算PPM结果异常问题求助
C# WinForms打字速度程序PPM计算问题
问题描述
我正在开发一款基于C# Windows Forms的打字速度程序,使用Stopwatch测量耗时,通过按键次数计算Letters per minute(PPM),但计算结果始终不准确。尽管Stopwatch能正常计时,timesPressed会在用户每输入一个字符时递增,但PPM的计算结果不符合预期,无法定位问题所在。
相关代码
using System.Diagnostics; namespace DAM_M07_UF1_PT1_Chamorro1_Alex { public partial class Form1 : Form { private string time; private Stopwatch stopwatch; Random generator = new Random(); int timesPressed; public string[] panels = { "There is already a vending machine for couples. It sounds like a joke, but there is already a vending machine for couples in some places. The idea is that, if we already have machines for so many things, why not a vending machine for couples? In a world where everything seems automated, now even relationships can arise from a vending machine for couples.", "Confusing, doubtful, dark. Sometimes, everything feels confusing, doubtful, dark. When you don't have the answers, everything feels confusing, doubtful, dark, and you don't know where to go. In moments like these, it's normal to feel confused, doubtful, dark, because the path isn't always clear. You just have to remember that, even though it feels confusing, doubtful, dark, there is always a way out.", "No plans, no schedules, and no phones. Sometimes you just want to live without plans, no schedules, and no phones. Escape from everything, no plans, no schedules, and no phones, to truly disconnect. It's a liberating idea: no plans, no schedules, and no phones. This way you could live each moment without worrying about the clock or the phone. No plans, no schedules, and no phones, just to be at peace.", "Riding a bike from Norway to Portugal. Can you imagine what it would be like to ride a bike from Norway to Portugal? It would be an incredible adventure: riding a bike from Norway to Portugal, passing through so many places. The views, the villages, the people you would meet if you decided to ride a bike from Norway to Portugal. It would be an unforgettable trip, riding a bike from Norway to Portugal, with the freedom of the road.", "Crab, jellyfish, and starfish. In the sea, you see creatures like the crab, jellyfish, and starfish. They are fascinating: crab, jellyfish, and starfish, each with its own shape and movement. You watch how the crab, jellyfish, and starfish move in their habitat. Crab, jellyfish, and starfish, forming a small world of marine life.", "Pass programming. The goal is clear: pass programming. You know that if you put in the effort, you'll pass programming and feel the achievement. All those exercises and practices are leading you to one goal: pass programming. Even though it's hard, you remind yourself that the important thing is to pass programming, and that every step brings you closer to the goal.", "Are we the two of us or is it just a thousand things? Sometimes I wonder if it's just the two of us or if it's a thousand things. Maybe everything is confusing, and we don't know if it's just the two of us or a thousand things in our minds. If we think about it, whether it's the two of us or just a thousand things, maybe it's an enigma we'll never solve. But in the end, what matters is whether it's the two of us or just a thousand things we can't control.", "The early bird catches the worm. They always say: 'The early bird catches the worm.' It's a saying that repeats because the early bird catches the worm by taking advantage of the day. And even though it's sometimes hard to get up early, you remember that the early bird catches the worm. This saying inspires you, and you know that the early bird catches the worm to achieve their goals.", "I’m going to make him an offer he can't refuse. When you want to achieve something, you say: 'I'm going to make him an offer he can't refuse.' It's the idea of making something so good that you say: I'm going to make him an offer he can't refuse. Sometimes you have to take risks and think: I'm going to make him an offer he can't refuse. Because in the end, opportunities come when someone says: I'm going to make him an offer he can't refuse.", "Living alone is like being at a party where no one notices you. Sometimes, living alone feels like being at a party where no one notices you. You have freedom, but you might also feel that living alone is like being at a party where no one notices you. Independence is beautiful, even though living alone feels like being at a party where no one notices you. You find a unique peace, even though living alone feels like being at a party where no one notices you." }; public Form1() { InitializeComponent(); stopwatch = new Stopwatch(); } private void Form1_Load(object sender, EventArgs e) { } private void startButton_Click(object sender, EventArgs e) { int selection = generator.Next(0, panels.Length); typingTextBox.Text = panels[selection]; stopwatch.Restart(); timer1.Start(); } private void timer1_Tick(object sender, EventArgs e) { TimeSpan ts = stopwatch.Elapsed; var hours = ts.Hours; var minutes = ts.Minutes; var seconds = ts.Seconds; var milliseconds = ts.Milliseconds; time = $"{hours}:{minutes}:{seconds}:{milliseconds}"; clock.Text = time; } private void timerButton_Click(object sender, EventArgs e) { resumeButton.Enabled = true; timer1.Stop(); stopwatch.Stop(); userTextBox.Enabled = false; } private void resumeButton_Click(object sender, EventArgs e) { resumeButton.Enabled = false; stopwatch.Start(); timer1.Start(); userTextBox.Enabled = true; } private void userTextBox_TextChanged(object sender, EventArgs e) { timesPressed++; } private void checkButton_Click(object sender, EventArgs e) { int errorCount = 0; string text = userTextBox.Text; string correctText = typingTextBox.Text; // Compare each character for (int i = 0; i < text.Length; i++) { if (text[i] != correctText[i]) { errorCount++; } } // If no errors, calculate letters per minute if (errorCount == 0) { TimeSpan ts = stopwatch.Elapsed; double totalSeconds = ts.TotalSeconds; // Use seconds for more accurate calculation double lettersPerSecond = totalSeconds > 0 ? timesPressed / totalSeconds : 0; double lettersPerMinute = lettersPerSecond * 60; // Convert to letters per minute // Round the value lettersPerMinute = Math.Round(lettersPerMinute, 2); MessageBox.Show($"Letters per minute: {lettersPerMinute}"); } else { MessageBox.Show($"Something is not quite right, you have {errorCount} errors."); } } } }
已尝试方案
- 使用
Stopwatch.Elapsed获取总秒数,先计算每秒字符数再转换为PPM; - 排查过
TextChanged事件触发过于频繁的可能,但问题仍未解决。
咨询问题
- 采用
Stopwatch测量耗时来计算打字速度是否为正确方案? - 如何提升PPM计算的准确性?
- 有没有更优的按键次数与耗时追踪方案用于打字速度程序?
解答
1. Stopwatch是否是正确的计时方案?
是。Stopwatch是.NET中高精度计时的首选方案,它基于系统高分辨率计时器,计时精度远高于DateTime或普通Timer,完全适配打字速度程序的耗时测量需求。
2. 如何提升PPM计算的准确性?
你的核心问题出在按键统计逻辑和文本对比逻辑上,具体修正点如下:
TextChanged事件的缺陷:该事件会在删除字符、粘贴/剪切文本时触发,导致timesPressed被错误累加(比如按退格键也会计数)。- 文本对比漏洞:当前仅对比用户输入的前N个字符,若输入长度超过目标文本会直接报错,且错误统计不完整;同时未重置
timesPressed,导致多次测试的按键数累加。 - PPM计算逻辑偏差:错误字符不应计入有效打字速度,当前用总按键数计算不符合实际需求。
具体修正步骤:
- 重置测试状态:在
startButton_Click中添加初始化逻辑:
userTextBox.Text = string.Empty; timesPressed = 0;
- 改用
KeyPress统计有效按键:替换TextChanged事件,仅统计可打印字符:
private void userTextBox_KeyPress(object sender, KeyPressEventArgs e) { // 只统计可打印的有效字符(排除退格、控制键) if (char.IsLetterOrDigit(e.KeyChar) || char.IsPunctuation(e.KeyChar) || char.IsWhiteSpace(e.KeyChar)) { timesPressed++; } }
- 完善文本对比逻辑:覆盖所有输入场景的错误统计:
int minLength = Math.Min(text.Length, correctText.Length); for (int i = 0; i < minLength; i++) { if (text[i] != correctText[i]) { errorCount++; } } // 统计输入长度超出目标文本的错误 if (text.Length > correctText.Length) { errorCount += text.Length - correctText.Length; }
- 用正确字符数计算PPM:
int correctChars = text.Length - errorCount; double lettersPerMinute = totalSeconds > 0 ? (correctChars / totalSeconds) * 60 : 0;
3. 更优的按键与耗时追踪方案
- 实时统计有效输入:在用户输入时逐字符对比,实时标记错误并统计正确字符数,无需最后一次性对比。
- 分段实时测速:用
Stopwatch记录每个字符的输入时间,或每隔固定时间采样当前正确字符数与耗时,计算实时PPM。 - 精准区分操作类型:针对粘贴、删除等操作单独处理,比如粘贴文本时直接统计粘贴的字符数,而非按触发次数计数。
内容的提问来源于stack exchange,提问作者chamorro
相关产品推荐
相关产品推荐

