将单个 .NET* 应用扩展到两个平台中

flowersmokes122 2007-03-14 01:22:32
日益复杂的开发人员工具以及各种移动技术逐渐、长期、缓慢的融合,为程序员带来了令人眼花缭乱的众多平台选择。然而在面对台式机和 PDA 进行选择时,为何只选一个?您可以同时选择两者。

鉴于“移植”是从一个平台到另一个平台的大规模重写,当前的工具支持您在多个平台中“扩展”您的代码。通过充分利用单个代码库,您即可使您的市场和收入增加一倍或两倍。

但是如何做呢?问题就在于此。

为了演示扩展代码的概念,我们在此以拼图游戏“Slide Puzzle”为例,讲述如何使其在台式机和掌上电脑 PDA 上都能运行。借助 Visual Studio .NET* 2003,您将了解到我们如何创建单个 Visual Basic .NET* 代码库,然后将其融入两个不同的结构。

本入门指南旨在向您介绍在同时针对多个平台进行编码时需要谨记的一些设计考虑事项。只需预先稍事规划,您即可轻松完成棘手的工作。之后,当您希望扩展到 Smartphone* 或甚至 ASP.NET* Web 应用时,您即可运用相同的技术。
尽管其中大多数代码均可用于 Visual Studio 的早期版本,但本入门指南中假定您采用的是 Visual Studio .NET 2003。集成开发环境(IDE)中包括一个 Pocket PC 2002 模拟器。

准备工作

在启动该 IDE 之前,我总是会抽出一些时间来拟定一个特性列表。在针对多个平台编写代码时,此项工作变得更加重要。正如我之前的一篇文章《在多个设备中扩展 JAVA* 应用》所述,您需要制定几项重要决策。

1. 您的平台是否联网?
如果您的目标平台能够接入互联网,那么可以考虑充分利用 Web 服务和/或 ASP.NET 移动控件。在您使用最新或频繁更新的数据时,这一点尤为重要。针对本入门指南,我会创建一些独立应用。

2. 所有平台应具备哪些核心特性?
勿庸置疑,您的应用将拥有一系列确定的核心特性,并且您应当能够在所有目标平台上部署这些特性。而我们当前的例子即是这个拼图,包括拼板,以及启动、破解或退出游戏选项。由于移动设备的显示区域千差万别,因而我选择采用一个简单的拼板尺寸调整机制(puzzle-resizer),以充分利用游戏所提供的所有空间。

3. 各平台最适用的特性都有哪些?
设备越小,花哨的功能就越少。这便意味着削减菜单选项,或甚至删除整个菜单。不过,这在掌上电脑上却是一件有趣的事。一般而言,您为掌上电脑创建的应用同样也能在台式机上运行。扩展到台式机只需添加一些特定的台式机特性。

因而,向您推荐的方法是从核心功能着手并进行纵向扩充。从手持设备开始。接下来,只添加那些充分利用更强大平台的真正必要的组件。这样可以为您的用户提供更多选择,以便其根据需求选择最佳工具。
在 VS.NET 中,尽管您可以针对 .NET Framework* 或 .NET Compact Framework*(.NET CF)进行构建,然而却不能在同一解决方案中同时针对二者进行构建。因此鉴于.NET CF 实质上是 .NET Framework 的一个子集,我将从掌上电脑版本开始构建。因为我知道,若能在这个版本上成功构建,我便能够在任何设备上构建。


文件-> 新建 ->项目。
在“项目类型”下,选择“Visual Basic 项目”;
在“模板”下,选择“智能设备应用”;
我们将其称为“SlidePuzzler”
在“智能设备应用向导”中,选中“掌上电脑”(Windows* 应用)。
绘制窗体

在涉及到代码之前,您需要执行大量的窗体设计工作。


在“Solution Explorer”中,右击“Form1.vb”并选择“属性”;将该文件名改为“SlidePuzzle.vb”。
同样,单击窗体并将窗体名改为“SlidePuzzle”,文本名改为“Slide Puzzle”。
同时,还要记住(我经常会忘记)右击“项目”并选择“属性”,将“启动目标”改为新的窗体名。
在窗体下方,您会看到一个“主菜单 1”对象。选择该对象,并将其名称(在“属性”下)改为“PuzzleMenu”。
在窗体底部(可能需要向下滚动),您会看到菜单文本。您可以通过代码创建上述所有内容,但在此只构建菜单会更加简单。为了简化此项工作,可创建一个菜单选项:“菜单”,其中包含三个子选项:“新游戏”、“破解”和“退出”。
(通过“属性”)为菜单各选项命名:分别为“主菜单”、“新游戏菜单”、“破解菜单”和“退出菜单”。
在窗体上覆盖一个平面,称为“PuzzleBox”。尺寸大小无关紧要,因为它们会通过代码重新调节,但须尽量覆盖整个窗体。
以下是您可以采用的两个图片。“Testgrid.gif”是一个 4×4 网格,带有 50×50 像素大小的拼版(Tile),总尺寸为 200×200 像素。如果您希望通过删除调节大小部分和添加常量来限制拼图的尺寸,那么此图片十分便利。“Gaia.jpg”可为您带来更多优势,且尺寸略大于 PDA 模拟器。将其中一项或将二者全部保存到您的项目目录中,然后将“Gaia.jpg”添加到项目中。
在“Gaia.jpg”图片的“属性”项下,将“构建动作”改为“嵌入式资源”。这样便使其与可执行文件合而为一,而非作为独立的文件。
Testgrid.gif

Gaia.jpg

现在,窗体和项目均已作好准备。我们可以开始编码了。
在扩展应用时,为了充分利用大多数代码,请尽量将操作代码从 UI 分流到其它类型。最理想的情况是,表格代码只包含针对您不同平台的独特代码。正如您所知,此类完美的优化本身便是一门艺术。随着时间的推移,您无疑会发现描绘功能的全新方法,以最充分地利用可扩展代码。

作为您最基本的组件,拼板(Tile)类型首当其冲。它当然代表了您在整个拼图上四处滑动的小方块。启动时,通过右击该项目并选择“添加->添加类型”来为您的项目添加类型。将该类型命名为“Tile.vb”。

由于拼版将用作“PuzzleBox”面板下的窗体控件,因而只需在类型说明下方添加以下代码:


Inherits System.Windows.Forms.Control
接下来是各种成员声明。

Private m_PuzzlePicture As Image
Private m_DisplayRect As Rectangle
Public CurrentLoc As Integer

Sub New 包含的几点无需关注。首先,添加以下代码:


Public Sub New(ByVal m_Image As Image, ByVal m_X As Integer, _
ByVal m_Y As Integer, ByVal m_Width As Integer, _
ByVal m_Height As Integer, ByVal m_Name As Integer)

m_PuzzlePicture = m_Image
m_DisplayRect = New Rectangle(m_X, m_Y, m_Width, m_Height)
Location = New Point(m_X, m_Y)
Size = New Size(m_Width, m_Height)
Text = m_Name
CurrentLoc = m_Name
End Sub


作为窗体控件,拼板(Tile)对象具有除您定义的属性之外的几种额外属性,如位置、大小和文本。这些属性在您围绕拼图平面四处移动拼板(Tile)时迟早会派上用场,特别是位置和大小属性。还需注意的是,在接下来的代码中,CurrentLoc 始终代表拼板(Tile)的当前位置和文本,而对象属性则代表拼板(Tile)的原始位置。对象属性用于支持后来的价值,以进一步提高在应用其它地方对其进行参考的灵活性。

这同样也可作为 .NET CF 与 .NET Framework 之间存在差异的一个例子。在台式机应用中,名称是可以编程方式设定的属性之一。而在 PDA 应用中,名称则为只读而不能设定。为此,在这里将代之以使用文本。这是对从小做起并进行纵向扩充的另一个合理解释。

将图像分割为可移动的小块的关键在于忽略该对象的绘制规则。利用它来绘制整个图像中与这一具体拼板(Tile)相关的特定部分。

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
MyBase.OnPaint(e)
e.Graphics.DrawImage(m_PuzzlePicture, ClientRectangle, _
m_DisplayRect, GraphicsUnit.Pixel)
End Sub

为了简化说明,尚未添加代码以实际扩展或调整拼图的大小。以上代码仅仅展示了更多的可用图像。如果您希望获得更有趣的体验,您还可以为拼板(Tile)创建添加一些算法来调整图像的大小。(实际上,带有 VS.NET 的代码样本包括一个通过 C#* 为掌上电脑编写的 Slide Puzzle 游戏。其中包括除我在此展示的特性之外的几项其它特性,以及一些用于创建拼图游戏的不同技术,但它自身并未轻易参与多平台开发。)

大多数实际工作均在 Grid 类中进行,使用该类表示拼图。其实质上是拼板(Tile)对象以及基本游戏机制所需的所有方法的集合。为项目添加一个新类型,并称之为“Grid.vb”。


...全文
450 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
wzzwt 2008-09-25
  • 打赏
  • 举报
回复
N多高人牛人....此帖让我受益匪浅,值得收藏! 继续关注
zy_914 2007-09-14
  • 打赏
  • 举报
回复
好文章,学习
david_xie 2007-09-13
  • 打赏
  • 举报
回复
路过
wuyuan1010 2007-09-12
  • 打赏
  • 举报
回复
我也抱着怀疑的态度 呵呵 不过是个好帖子 先收藏下
weiyongjin 2007-08-07
  • 打赏
  • 举报
回复
工程师有这么高的能力吗
zhsj64 2007-06-18
  • 打赏
  • 举报
回复
好文,收藏了。。
jp800308 2007-03-21
  • 打赏
  • 举报
回复
学习
aa211314 2007-03-21
  • 打赏
  • 举报
回复
很好,想研究
iu_81 2007-03-21
  • 打赏
  • 举报
回复
mark
  • 打赏
  • 举报
回复
不错,准备研究研究这方面的东西
yiluzoulai 2007-03-21
  • 打赏
  • 举报
回复
不错哦,谢谢共享
hxDreamer 2007-03-21
  • 打赏
  • 举报
回复
迷糊。。。intel也搞.net?
flowersmokes122 2007-03-14
  • 打赏
  • 举报
回复
首先是成员声明。这些声明应当为自我阐释。

Public TileSet As Tile()
Private m_PuzzlePicture As Image
Private m_TileRows, m_TileCols, m_BlankTile As Integer
Private m_TileWidth As Integer
Private m_TileHeight As Integer

接下来(例如)为所有这些新变量分配数值。

Public Sub New(ByVal m_Picture As Image, _
ByVal m_RowCount As Integer, ByVal m_ColCount As Integer, _
ByVal m_Width As Integer, ByVal m_Height As Integer)

m_PuzzlePicture = m_Picture
m_TileRows = m_RowCount
m_TileCols = m_ColCount
m_TileWidth = m_Width
m_TileHeight = m_Height

Setup()
End Sub

最后,您会注意到一个 Setup() 例程。以下是支持它的代码。这实际上创建了拼图底盘及其所有拼板(Tile)。注意,这里没有出现常量。

Private Sub Setup()

Dim tileCount As Integer = (m_TileRows * m_TileCols)
ReDim TileSet(tileCount - 1)
m_BlankTile = tileCount - 1

Dim thisX, thisY, thisRow, thisCol, thisTile As Integer
For thisRow = 0 To m_TileRows - 1
For thisCol = 0 To m_TileCols - 1
thisTile = (thisRow * m_TileRows) + thisCol
thisY = (thisRow * m_TileHeight)
thisX = (thisCol * m_TileWidth)
TileSet(thisTile) = New Tile(m_PuzzlePicture, _
thisX, thisY, m_TileWidth, m_TileHeight, _
thisTile)
Next
Next

TileSet(m_BlankTile).Visible = False

End Sub

关于 Setup() 例程,需要注意最后一行:它将“空白”拼板(Tile)的 Visible 属性设为“False”。可以将其轻松添加到窗体而非 Grid 类中。您可能正希望这样做,并根据您为用户界面(UI)添加的新特性来确定。我在此进行补充的原因是想说明:这是在代码运行于多个平台时将其抽出 UI 的少许机会之一。

进一步来论述这一概念:在面板上交换拼板(Tile)的 Swap() 例程实际上操控着窗体对象,尽管它本身并不接触表格。您在创建“交换”(swapping)代码时拥有许多选择。以下代码不但可在多个平台上运行,而且还可独立于 UI 代码运行。

Public Sub Swap(ByVal m_Tile1 As Integer, ByVal m_Tile2 As Integer)
Dim tmpPnt As Point = TileSet(m_Tile1).Location
TileSet(m_Tile1).Location = TileSet(m_Tile2).Location
TileSet(m_Tile2).Location = tmpPnt

Dim tmpLoc As Integer = TileSet(m_Tile1).CurrentLoc
TileSet(m_Tile1).CurrentLoc = TileSet(m_Tile2).CurrentLoc
TileSet(m_Tile2).CurrentLoc = tmpLoc
End Sub

您需要一种简单的 Get() 方法来恢复空白拼板(Tile)的数值。

Public Function GetBlank() As Integer
Return m_BlankTile
End Function

下一个例程有点棘手。它将一个参考文件接收到一个拼板(Tile),然后根据其位置来计算能否将其与空白拼板(Tile)互换。

Public Sub CheckTile(ByVal m_ThisTile As Integer)
Dim intXDiff, intYDiff As Integer
intXDiff = Math.Abs(TileSet(m_ThisTile).Left - _
TileSet(m_BlankTile).Left)
intYDiff = Math.Abs(TileSet(m_ThisTile).Top - _
TileSet(m_BlankTile).Top)
If ((intXDiff = 0) And (intYDiff <= m_TileHeight)) _
Or ((intXDiff lt;= m_TileWidth) And (intYDiff = 0)) Then
Swap(m_ThisTile, m_BlankTile)
End If
End Sub

Shuffle() 进程交换了数次拼板。“100”次足够了。

Public Sub Shuffle()
Dim swapper, i As Integer
Randomize()
For i = 1 To 100
swapper = CInt(Int(m_BlankTile * Rnd()))
Swap(swapper, m_BlankTile)
Next
End Sub

在每次移动之后,您将需要查看拼图是否已被破解。

Public Function IsSolved() As Boolean
Dim solved As Boolean = True
For thisTile As Integer = 0 To UBound(TileSet)
If (TileSet(thisTile).CurrentLoc <> CStr(thisTile)) Then
solved = False
Exit For
End If
Next
If solved Then TileSet(m_BlankTile).Visible = True
Return solved
End Function

最后,对于像我这样欠缺耐心的游戏玩家,Solve() 例程可以使游戏容易许多。

Public Sub Solve()
Dim thisTile As Integer
For loc As Integer = 0 To UBound(TileSet)
thisTile = 0
While TileSet(thisTile).CurrentLoc <> loc
thisTile += 1
End While
Swap(thisTile, TileSet(thisTile).CurrentLoc)
Next
TileSet(m_BlankTile).Visible = True
End Sub

支持 Grid.vb 的完整代码可点击这里获取。

拼板(Tile)与 Grid 类旨在各个部署平台之间共享。然而,一般而言不能共享的是 UI ― 但正如您所见,即使 UI 也可以在很大程度上实现共享。

566

社区成员

发帖
与我相关
我的任务
社区描述
英特尔® 边缘计算,聚焦于边缘计算、AI、IoT等领域,为开发者提供丰富的开发资源、创新技术、解决方案与行业活动。
社区管理员
  • 英特尔技术社区
  • shere_lin
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧