在当今数字化时代,打字已经成为一项必不可少的技能。无论是工作、学习还是日常沟通,快速而准确的打字能力都能极大地提高效率。为了帮助大家提升英文打字水平,本文将带你从零开始,使用C#开发一款功能完善的英文打字练习软件。通过这个项目,你不仅能学习到C#编程的核心技术,还能掌握如何设计一个用户友好的桌面应用程序。
我们的英文打字练习软件将具备以下核心功能:
多篇文章选择:用户可以从多篇英文文章中选择练习内容。
实时打字反馈:用户输入时,程序会实时检查输入的正确性,并用颜色标记错误。
打字速度与准确率统计:程序会计算用户的打字速度(WPM)、完成率和正确率。
分段练习:文章内容会被拆分为多个段落,用户逐段完成练习。
计时功能:记录用户的练习时间,帮助用户了解自己的进步。
通过这个项目,你将学习到以下技术要点:
C# WinForms 开发
RichTextBox 控件的使用
计时器(Timer)的应用
事件驱动编程
字符串处理与拆分
用户输入验证与反馈
首先,我们使用C# WinForms创建一个桌面应用程序。主界面包括以下控件:
RichTextBox:用于显示英文文章内容和用户输入。
ComboBox:用于选择不同的文章。
Button:用于开始和停止练习。
Label:用于显示打字速度、完成率和正确率。
public MainForm()
{
InitializeComponent();
Load += MainForm_Load;
// 初始化计时器
timer = new Timer();
timer.Interval = 10; // 设置时间间隔为10毫秒
timer.Tick += Timer_Tick;
// 初始化文章字典
articles = new Dictionary
{
{ "Article 1", "In the early days of the internet..." },
{ "Article 2", "Artificial intelligence is transforming industries..." },
// 其他文章...
};
}
用户可以通过 ComboBox
选择不同的文章。文章内容会被拆分为每行50个字符,并显示在多个 RichTextBox
中。
private void comboBoxArticles_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBoxArticles.SelectedItem != null)
{
string selectedArticle = comboBoxArticles.SelectedItem.ToString();
articleTextTotal = articles[selectedArticle]; // 获取选中的文章内容
// 按每50个字符拆分文章
int chunkSize = 50;
documentLines = Enumerable.Range(0, (int)Math.Ceiling((double)articleTextTotal.Length / chunkSize))
.Select(i => articleTextTotal.Substring(i * chunkSize, Math.Min(chunkSize, articleTextTotal.Length - i * chunkSize)))
.ToArray();
// 加载前5行到RichTextBox
LoadNextLines();
}
}
用户在 RichTextBox
中输入时,程序会实时检查输入的正确性,并用颜色标记错误。
private void HandleKeyPress(RichTextBox inputRichTextBox, RichTextBox docRichTextBox, KeyPressEventArgs e)
{
attemptedChars++; // 记录尝试输入的字符数
int localIndex = inputRichTextBox.TextLength; // 当前输入框的字符索引
string articleText = docRichTextBox.Text;
if (localIndex >= articleText.Length) return;
char inputChar = e.KeyChar;
if (char.IsControl(inputChar)) return; // 忽略控制字符
if (inputChar == articleText[localIndex])
{
correctChars++; // 正确输入,增加正确次数
userInput.Append(inputChar);
// 设置正确字符的颜色为黑色
docRichTextBox.Select(localIndex, 1);
docRichTextBox.SelectionColor = Color.Black;
docRichTextBox.DeselectAll();
}
else
{
// 设置错误字符的颜色为红色
docRichTextBox.Select(localIndex, 1);
docRichTextBox.SelectionColor = Color.Red;
docRichTextBox.DeselectAll();
}
// 更新完成率和正确率
CalculateCompletionRate();
// 每次文本改变时重置计时器
timer.Stop();
timer.Start();
}
程序会实时计算用户的打字速度(WPM)、完成率和正确率,并显示在界面上。
private void CalculateCompletionRate()
{
if (stopwatch.Elapsed.TotalMinutes == 0 || articleTextTotal.Length == 0 || attemptedChars == 0)
{
labelTypingSpeed.Text = "Typing Speed: 0.00 WPM";
labelCompletionRate.Text = "Completion Rate: 0.00%";
labelAccuracyRate.Text = "Accuracy Rate: 0.00%";
return;
}
// 计算打字速度(WPM:每分钟单词数)
double typingSpeed = (double)correctChars / 5 / stopwatch.Elapsed.TotalMinutes;
labelTypingSpeed.Text = $"Typing Speed: {typingSpeed:F2} WPM";
// 计算完成率
double completionRate = (double)correctChars / articleTextTotal.Length;
labelCompletionRate.Text = $"Completion Rate: {completionRate * 100:F2}%";
// 计算正确率
double accuracyRate = (double)correctChars / attemptedChars;
labelAccuracyRate.Text = $"Accuracy Rate: {accuracyRate * 100:F2}%";
}
程序使用 Timer
控件来检测用户是否完成当前段落的输入。如果完成,则自动加载下一段落。
private void Timer_Tick(object sender, EventArgs e)
{
// 检查输入长度是否与对应文档长度相同
if (richTextBoxInput1.TextLength >= richTextBoxDoc1.TextLength)
{
richTextBoxInputsz[1].Focus();
richTextBoxInputsz[1].SelectionStart = richTextBoxInputsz[1].TextLength;
if (richTextBoxInput2.TextLength >= richTextBoxDoc2.TextLength)
{
richTextBoxInputsz[2].Focus();
richTextBoxInputsz[2].SelectionStart = richTextBoxInputsz[2].TextLength;
if (richTextBoxInput3.TextLength >= richTextBoxDoc3.TextLength)
{
richTextBoxInputsz[3].Focus();
richTextBoxInputsz[3].SelectionStart = richTextBoxInputsz[3].TextLength;
if (richTextBoxInput4.TextLength >= richTextBoxDoc4.TextLength)
{
richTextBoxInputsz[4].Focus();
richTextBoxInputsz[4].SelectionStart = richTextBoxInputsz[4].TextLength;
if (richTextBoxInput5.TextLength >= richTextBoxDoc5.TextLength)
{
// 如果所有条件都满足,加载下一组行
LoadNextLines();
}
}
}
}
}
// 停止Timer以避免重复执行
timer.Stop();
}
通过这个项目,我们不仅实现了一个功能完善的英文打字练习软件,还深入学习了C# WinForms开发的核心技术。以下是本项目的亮点:
实时反馈:通过颜色标记错误,帮助用户快速纠正输入。
分段练习:将文章拆分为多个段落,降低练习难度。
数据统计:实时计算打字速度、完成率和正确率,帮助用户了解自己的进步。
如果你对C#开发感兴趣,或者想提升自己的打字能力,不妨动手实现这个项目!通过实践,你将掌握更多编程技巧,同时也能打造一款属于自己的实用工具。
增加更多文章:扩展文章库,提供更多练习内容。
支持自定义文章:允许用户导入自己的文本进行练习。
优化界面设计:美化界面,提升用户体验。
添加音效:在用户输入正确或错误时播放提示音。
希望本文能激发你对编程的兴趣,期待你在评论区分享你的学习心得和项目成果!
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using System.Xml;
namespace TypingPracticeApp
{
public partial class MainForm : Form
{
private string articleTextTotal;
private StringBuilder userInput;
private Stopwatch stopwatch; // 用于计算打字速度
private int correctChars; // 正确输入的字符数量
private int attemptedChars; // 尝试输入的字符数量
private Timer timer;
private Dictionary articles;// 添加一个字典来存储题目和对应的文章
// 用于存储按字符拆分后的文件内容
private string[] documentLines;
// 用于显示英文文档的 RichTextBox 控件
private RichTextBox[] richTextBoxDocumentsz;
// 用于用户输入的 RichTextBox 控件
private RichTextBox[] richTextBoxInputsz;
// 当前文档的 StreamReader
private StreamReader documentReader;
// 当前加载的行索引
private int currentLineIndex;
private int currentLineIndexFirst;
private int comboBoxArticlesID;
public MainForm()
{
InitializeComponent();
Load += MainForm_Load;
// 初始化Timer控件
timer = new Timer();
timer.Interval = 10; // 设置时间间隔为500毫秒(0.5秒)
timer.Tick += Timer_Tick;
// 初始化文章字典
articles = new Dictionary
{
{ "Article 1", "In the early days of the internet, people relied on dial-up connections to access the web. It was slow and often frustrating, but it opened up a new world of information. Today, high-speed broadband has revolutionized how we connect, enabling instant communication and access to vast resources. The internet has become an essential part of daily life, shaping how we work, learn, and socialize." },
{ "Article 2", "Artificial intelligence is transforming industries across the globe. From healthcare to finance, AI is being used to analyze data, automate tasks, and improve decision-making. While some fear that AI will replace human jobs, others believe it will create new opportunities and enhance productivity. The key is to ensure that AI is developed and used responsibly, with a focus on ethical considerations." },
{ "Article 3", "Climate change is one of the most pressing challenges of our time. Rising temperatures, melting ice caps, and extreme weather events are clear signs of its impact. Governments, businesses, and individuals must work together to reduce carbon emissions and adopt sustainable practices. Renewable energy sources, such as solar and wind power, offer hope for a cleaner, greener future." },
{ "Article 4", "Space exploration has always captured the human imagination. From the first moon landing to the recent Mars rover missions, we continue to push the boundaries of what is possible. Private companies like SpaceX are now joining the effort, aiming to make space travel more accessible. The dream of colonizing other planets is no longer science fiction but a potential reality." },
{ "Article 5", "Education is the foundation of a prosperous society. It empowers individuals, fosters innovation, and drives economic growth. However, access to quality education remains unequal in many parts of the world. Efforts to bridge this gap, such as online learning platforms and scholarships, are crucial for creating a more equitable future where everyone has the opportunity to succeed." },
{ "Article 6", "The rise of social media has changed how we communicate and share information. Platforms like Facebook, Twitter, and Instagram allow us to connect with people worldwide, but they also raise concerns about privacy and misinformation. As we navigate this digital landscape, it is important to use these tools responsibly and critically evaluate the content we consume and share." }
};
}
private void MainForm_Load(object sender, EventArgs e)
{
// 初始化 RichTextBox 控件数组
richTextBoxDocumentsz = new RichTextBox[]
{
richTextBoxDoc1, richTextBoxDoc2, richTextBoxDoc3, richTextBoxDoc4, richTextBoxDoc5
};
richTextBoxInputsz = new RichTextBox[]
{
richTextBoxInput1, richTextBoxInput2, richTextBoxInput3, richTextBoxInput4, richTextBoxInput5
};
// 设置字体
SetRichTextBoxFonts(richTextBoxDocumentsz, "微软雅黑", 12, Color.Black);
SetRichTextBoxFonts(richTextBoxInputsz, "微软雅黑", 12, Color.Black);
// 绑定 KeyPress 事件
richTextBoxInput1.KeyPress += (s, ev) => HandleKeyPress(richTextBoxInput1, richTextBoxDoc1, ev);
richTextBoxInput2.KeyPress += (s, ev) => HandleKeyPress(richTextBoxInput2, richTextBoxDoc2, ev);
richTextBoxInput3.KeyPress += (s, ev) => HandleKeyPress(richTextBoxInput3, richTextBoxDoc3, ev);
richTextBoxInput4.KeyPress += (s, ev) => HandleKeyPress(richTextBoxInput4, richTextBoxDoc4, ev);
richTextBoxInput5.KeyPress += (s, ev) => HandleKeyPress(richTextBoxInput5, richTextBoxDoc5, ev);
// 初始化 ComboBox,加载文章标题
comboBoxArticles.Items.Clear();
comboBoxArticles.Items.AddRange(articles.Keys.ToArray());
comboBoxArticles.SelectedIndex = 0; // 默认选择第一个题目
// 手动调用一次 SelectedIndexChanged 事件,加载默认文章内容
comboBoxArticles_SelectedIndexChanged(comboBoxArticles, EventArgs.Empty);
// 初始化 documentLines 数组
documentLines = new string[0]; // 初始化为空数组
// 初始化其他变量
userInput = new StringBuilder();
stopwatch = new Stopwatch();
correctChars = 0;
attemptedChars = 0;
}
private void comboBoxArticles_SelectedIndexChanged(object sender, EventArgs e)
{
comboBoxArticlesID = comboBoxArticles.SelectedIndex;
// 当用户选择不同的题目时,更新 richTextBoxArticle 中的文本
if (comboBoxArticles.SelectedItem != null)
{
string selectedArticle = comboBoxArticles.SelectedItem.ToString();
articleTextTotal = articles[selectedArticle]; // 从字典中获取选中的文章内容
string fileContent = articleTextTotal; // 更新文本
// 读取文件内容
fileContent = fileContent.Replace("\r\n", "").Replace("\n", ""); // 移除换行符
// 按每 50 个字符拆分
int chunkSize = 50; // 每行的字符数
documentLines = Enumerable.Range(0, (int)Math.Ceiling((double)fileContent.Length / chunkSize))
.Select(i => fileContent.Substring(i * chunkSize, Math.Min(chunkSize, fileContent.Length - i * chunkSize)))
.ToArray();
// 重置当前行索引
currentLineIndex = 0;
currentLineIndexFirst = 0;
// 加载前 5 行到 RichTextBoxDocument
LoadNextLines();
// 重置输入和统计
ClearRichTextBoxes(richTextBoxInputsz);
correctChars = 0;
attemptedChars = 0;
// 重置颜色
SetRichTextBoxFonts(richTextBoxDocumentsz, "微软雅黑", 12, Color.Black);
SetRichTextBoxFonts(richTextBoxInputsz, "微软雅黑", 12, Color.Black);
// 启用开始按钮,禁用停止按钮
buttonStart.Enabled = true;
buttonStop.Enabled = false;
}
}
private void buttonStart_Click(object sender, EventArgs e)
{
// 清空输入框和统计
ClearRichTextBoxes(richTextBoxInputsz);
userInput.Clear();
correctChars = 0;
attemptedChars = 0;
// 重置并启动计时器
stopwatch.Reset();
stopwatch.Start();
// 切换按钮状态
buttonStart.Enabled = false;
buttonStop.Enabled = true;
// 将焦点设置到第一个输入框
richTextBoxInput1.Focus();
}
private void buttonStop_Click(object sender, EventArgs e)
{
// 停止计时器
stopwatch.Stop();
// 切换按钮状态
buttonStart.Enabled = true;
buttonStop.Enabled = false;
// 计算并显示完成率和正确率
CalculateCompletionRate();
}
private void Timer_Tick(object sender, EventArgs e)
{
// 在此处更新TextBox的文本格式
SetRichTextBoxFonts(richTextBoxInputsz, "微软雅黑", 12, Color.Black);
// 检查输入长度是否与对应文档长度相同
if (richTextBoxInput1.TextLength >= richTextBoxDoc1.TextLength)
{
richTextBoxInputsz[1].Focus();
richTextBoxInputsz[1].SelectionStart = richTextBoxInputsz[1].TextLength;
if (richTextBoxInput2.TextLength >= richTextBoxDoc2.TextLength)
{
richTextBoxInputsz[2].Focus();
richTextBoxInputsz[2].SelectionStart = richTextBoxInputsz[2].TextLength;
if (richTextBoxInput3.TextLength >= richTextBoxDoc3.TextLength)
{
richTextBoxInputsz[3].Focus();
richTextBoxInputsz[3].SelectionStart = richTextBoxInputsz[3].TextLength;
if (richTextBoxInput4.TextLength >= richTextBoxDoc4.TextLength)
{
richTextBoxInputsz[4].Focus();
richTextBoxInputsz[4].SelectionStart = richTextBoxInputsz[4].TextLength;
if (richTextBoxInput5.TextLength >= richTextBoxDoc5.TextLength)
{
// 如果所有条件都满足,加载下一组行
LoadNextLines();
}
}
}
}
}
// 停止Timer以避免重复执行
timer.Stop();
}
private void CalculateCompletionRate()
{
// 防止除零错误
if (stopwatch.Elapsed.TotalMinutes == 0 || articleTextTotal.Length == 0 || attemptedChars == 0)
{
labelTypingSpeed.Text = "Typing Speed: 0.00 WPM";
labelCompletionRate.Text = "Completion Rate: 0.00%";
labelAccuracyRate.Text = "Accuracy Rate: 0.00%";
return;
}
// 计算打字速度(WPM:每分钟单词数)
// 假设一个单词 = 5 个字符(包括空格)
double typingSpeed = (double)correctChars / 5 / stopwatch.Elapsed.TotalMinutes;
labelTypingSpeed.Text = $"Typing Speed: {typingSpeed:F2} WPM";
// 计算完成度(基于当前文档的总字符数)
double completionRate = (double)correctChars / articleTextTotal.Length;
labelCompletionRate.Text = $"Completion Rate: {completionRate * 100:F2}%";
// 计算正确率(基于尝试输入的字符数)
double accuracyRate = (double)correctChars / attemptedChars;
labelAccuracyRate.Text = $"Accuracy Rate: {accuracyRate * 100:F2}%";
}
private void buttonLoadDocument_Click(object sender, EventArgs e)
{
// 打开文件对话框
OpenFileDialog openFileDialog = new OpenFileDialog
{
Filter = "Text Files|*.txt",
Title = "Select a Text File"
};
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// 使用 using 语句确保 StreamReader 被正确释放
using (documentReader = new StreamReader(openFileDialog.FileName))
{
// 读取文件内容
string fileContent = documentReader.ReadToEnd(); // 使用 ReadToEnd 读取全部内容
fileContent = fileContent.Replace("\r\n", "").Replace("\n", ""); // 移除换行符
// 按每 50 个字符拆分
int chunkSize = 50; // 每行的字符数
documentLines = Enumerable.Range(0, (int)Math.Ceiling((double)fileContent.Length / chunkSize))
.Select(i => fileContent.Substring(i * chunkSize, Math.Min(chunkSize, fileContent.Length - i * chunkSize)))
.ToArray();
// 重置当前行索引
currentLineIndex = 0;
currentLineIndexFirst = 0;
// 加载前 5 行到 RichTextBoxDocument
LoadNextLines();
}
}
}
// 加载下一组 5 行到 RichTextBoxDocument
private void LoadNextLines()
{
// 清空所有 RichTextBox
ClearRichTextBoxes(richTextBoxDocumentsz);
ClearRichTextBoxes(richTextBoxInputsz);
// 检查 documentLines 是否为空或已经加载完毕
if (documentLines == null || documentLines.Length == 0)
{
MessageBox.Show("No document content to load.");
return;
}
// 加载 5 行内容
StringBuilder currentArticleText = new StringBuilder();
for (int i = 0; i < 5; i++)
{
if (currentLineIndex < documentLines.Length)
{
// 将拆分的字符内容放入 RichTextBox
richTextBoxDocumentsz[i].Text = documentLines[currentLineIndex];
currentArticleText.Append(documentLines[currentLineIndex]); // 更新当前文章内容
currentLineIndexFirst++;
currentLineIndex++;
richTextBoxInputsz[0].Focus();
}
else
{
// 如果文档内容已加载完毕,显示提示信息
if (i == 0) // 只有在没有任何内容加载时才显示消息框
{
FinishDone();
MessageBox.Show("打字测试完成。");
}
break;
}
}
// 更新 articleText 为当前加载的内容
articleTextTotal = currentArticleText.ToString();
// 将焦点设置到第一个输入框
richTextBoxInputsz[0].Focus();
}
private void FinishDone()
{
// 重置状态
currentLineIndex = 0;
currentLineIndexFirst = 0;
// 停止Timer以避免重复执行
timer.Stop();
stopwatch.Stop();
buttonStart.Enabled = true;
buttonStop.Enabled = false;
// 清空所有 RichTextBox
ClearRichTextBoxes(richTextBoxDocumentsz);
ClearRichTextBoxes(richTextBoxInputsz);
// 重置文章内容
comboBoxArticles_SelectedIndexChanged(comboBoxArticles, EventArgs.Empty);
}
// 清空所有 RichTextBox
private void ClearRichTextBoxes(RichTextBox[] rtbArray)
{
foreach (var rtb in rtbArray)
{
rtb.Clear();
}
}
// 设置 RichTextBox 数组字体的方法
private void SetRichTextBoxFonts(RichTextBox[] rtbArray, string fontName, float fontSize,Color black)
{
foreach (var rtb in rtbArray)
{
rtb.Font = new Font(fontName, fontSize);
}
foreach (var rtb in rtbArray)
{
rtb.SelectionColor = black;// Color.Black
}
}
private void HandleKeyPress(RichTextBox inputRichTextBox, RichTextBox docRichTextBox, KeyPressEventArgs e)
{
attemptedChars++; // 每次按键都增加尝试次数
int localIndex = inputRichTextBox.TextLength; // 使用 localIndex 避免与成员变量混淆
string articleText = docRichTextBox.Text;
if (localIndex >= articleText.Length) return;
char inputChar = e.KeyChar;
if (char.IsControl(inputChar)) return; // 忽略控制字符
if (inputChar == articleText[localIndex])
{
correctChars++; // 正确输入,增加正确次数
userInput.Append(inputChar);
int charIndex = localIndex; // 第几个字符
if (charIndex >= 0)
{
docRichTextBox.Select(charIndex, 1);
docRichTextBox.SelectionColor = Color.Black;
docRichTextBox.DeselectAll();
}
}
else
{
int charIndex = localIndex; // 第几个字符
if (charIndex >= 0)
{
docRichTextBox.Select(charIndex, 1);
docRichTextBox.SelectionColor = Color.Red;
docRichTextBox.DeselectAll();
}
}
CalculateCompletionRate();
// 每次文本改变时重置Timer
timer.Stop();
timer.Start();
}
}
}