这是“使用 C# 开发智能手机软件:推箱子” 系列文章的第二十二篇。在这篇文章中,介绍 Window/MainForm.Replay.cs 源程序文件。这个源程序文件是 MainForm 类的一部分,该类继承自 System.Windows.Forms.Form 类,表示推箱子的主窗体。而本篇文章讲述的是推箱子“回放”过程,如下图所示:

我们先看看 MainForm.Designer.cs 源程序文件(该文件是由 Visual Studio 2005 IDE 自动生成的)中和“回放”相关的部分:
namespace Skyiv.Ben.PushBox.Window
{
partial class MainForm
{
// 注意:省略了很多代码,仅保留和 miReplayOrLand 相关的部分。
private void InitializeComponent()
{
this.miReplayOrLand = new System.Windows.Forms.MenuItem();
this.mnuMain.MenuItems.Add(this.miReplayOrLand);
this.miReplayOrLand.Text = "回放";
this.miReplayOrLand.Click += new System.EventHandler(this.miReplayOrLand_Click);
}
private System.Windows.Forms.MenuItem miReplayOrLand;
}
}
{
partial class MainForm
{
// 注意:省略了很多代码,仅保留和 miReplayOrLand 相关的部分。
private void InitializeComponent()
{
this.miReplayOrLand = new System.Windows.Forms.MenuItem();
this.mnuMain.MenuItems.Add(this.miReplayOrLand);
this.miReplayOrLand.Text = "回放";
this.miReplayOrLand.Click += new System.EventHandler(this.miReplayOrLand_Click);
}
private System.Windows.Forms.MenuItem miReplayOrLand;
}
}
上述代码片断展示了在推箱子游戏的主窗体上点击“回放”时发生的事件:
this.miReplayOrLand.Click += new System.EventHandler(this.miReplayOrLand_Click);
也就是说如果以前使用“录像”功能保存过通关步骤的话,可以通过“回放”功能将该通关步骤重新播放出来。这是由下面的 MainForm.Replay.cs 源程序代码实现的:
1 using System;
2 using System.Drawing;
3 using System.Threading;
4 using Skyiv.Ben.PushBox.Common;
5
6 namespace Skyiv.Ben.PushBox.Window
7 {
8 partial class MainForm
9 {
10 Rectangle workerThreadInvalidRectangle;
11 bool workerThreadIsStop;
12
13 private void miReplayOrLand_Click(object sender, EventArgs e)
14 {
15 if (env.IsDesign)
16 {
17 env.Pen = Block.Land;
18 UpdateStatus();
19 }
20 else
21 {
22 env.IsReplay = true;
23 workerThreadIsStop = false;
24 UpdateStatus();
25 ThreadPool.QueueUserWorkItem(WorkerThreadReplay, env.GetSteps());
26 }
27 }
28
29 /// <summary>
30 /// 回放通关步骤,使用后台工作线程
31 /// </summary>
32 /// <param name="steps">通关步骤</param>
33 private void WorkerThreadReplay(object steps)
34 {
35 try
36 {
37 foreach (char c in (string)steps)
38 {
39 if (workerThreadIsStop) break;
40 if (env.ReplayDelay > 0) Thread.Sleep(env.ReplayDelay);
41 Step step = c;
42 if (!env.StepIt(step.Direct, step.IsStop, out workerThreadInvalidRectangle)) break;
43 Invoke(new EventHandler(WorkerThreadUpdateStatus));
44 }
45 }
46 finally
47 {
48 env.IsReplay = false;
49 Invoke(new EventHandler(WorkerThreadUpdateStatus));
50 }
51 }
52
53 /// <summary>
54 /// 更新主窗体状态
55 /// </summary>
56 /// <param name="sender">事件源</param>
57 /// <param name="e">不包含任何事件数据的事件参数</param>
58 void WorkerThreadUpdateStatus(object sender, EventArgs e)
59 {
60 Invalidate(workerThreadInvalidRectangle);
61 UpdateStatus();
62 }
63 }
64 }
65
66
2 using System.Drawing;
3 using System.Threading;
4 using Skyiv.Ben.PushBox.Common;
5
6 namespace Skyiv.Ben.PushBox.Window
7 {
8 partial class MainForm
9 {
10 Rectangle workerThreadInvalidRectangle;
11 bool workerThreadIsStop;
12
13 private void miReplayOrLand_Click(object sender, EventArgs e)
14 {
15 if (env.IsDesign)
16 {
17 env.Pen = Block.Land;
18 UpdateStatus();
19 }
20 else
21 {
22 env.IsReplay = true;
23 workerThreadIsStop = false;
24 UpdateStatus();
25 ThreadPool.QueueUserWorkItem(WorkerThreadReplay, env.GetSteps());
26 }
27 }
28
29 /// <summary>
30 /// 回放通关步骤,使用后台工作线程
31 /// </summary>
32 /// <param name="steps">通关步骤</param>
33 private void WorkerThreadReplay(object steps)
34 {
35 try
36 {
37 foreach (char c in (string)steps)
38 {
39 if (workerThreadIsStop) break;
40 if (env.ReplayDelay > 0) Thread.Sleep(env.ReplayDelay);
41 Step step = c;
42 if (!env.StepIt(step.Direct, step.IsStop, out workerThreadInvalidRectangle)) break;
43 Invoke(new EventHandler(WorkerThreadUpdateStatus));
44 }
45 }
46 finally
47 {
48 env.IsReplay = false;
49 Invoke(new EventHandler(WorkerThreadUpdateStatus));
50 }
51 }
52
53 /// <summary>
54 /// 更新主窗体状态
55 /// </summary>
56 /// <param name="sender">事件源</param>
57 /// <param name="e">不包含任何事件数据的事件参数</param>
58 void WorkerThreadUpdateStatus(object sender, EventArgs e)
59 {
60 Invalidate(workerThreadInvalidRectangle);
61 UpdateStatus();
62 }
63 }
64 }
65
66
几点说明:
- 因为“回放”是一个长时间的过程,为了防止用户界面失去响应,所以使用了多线程技术,在后台工作线程进行回放。也说是将回放通关步骤的 WorkerThreadReplay 方法加入到线程池中去:ThreadPool.QueueUserWorkItem(WorkerThreadReplay, env.GetSteps());
- workerThreadInvalidRectangle 字段(Rectangle 类型)表明“回放”时主窗体需要更新的矩形区域。该值是由 Env 类的 StepIt 方法设定。
- workerThreadIsStop 字段(bool 类型)指示是否停止回放,初始值为 false。当用户按“停止”按钮时,该字段被设置为 true,从而停止回放。
- miReplayOrLand_Click 方法响应用户的“回放”请求,启动后台“回放”线程。
- WorkerThreadReplay 方法在后台线程执行实际的“回放”动作,她实际上是对已经保存的通关步骤的每一步调用 Env 类的 StepIt 方法来进行“回放”,并通过 Invoke 调用 WorkerThreadUpdateStatus 方法更新主窗体状态。
- WorkerThreadUpdateStatus 方法负责更新主窗体状态。
- 如果在智能手机上进行“回放”,就是不使用后台线程,用户界面也不会失去响应,不知是什么原因。
上一篇:使用 C# 开发智能手机软件:推箱子(二十一)
下一篇:使用 C# 开发智能手机软件:推箱子(二十三)
返回目录
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述