您最好的选择是完全避免使用控件。它们将 A) 导致较差的性能和 B) 使命中测试/绘图复杂化。
只需创建对象来表示表格的状态(我使用 CardContainer 对象)并使用 Graphics.DrawImage 来绘制它们在绘制事件期间所在的所有卡片。如果您还需要添加其他 UI 元素,则可以对整个表格使用单个控件。
如果您决定添加动画,这也将使动画卡片移动更简单。
更新
我打算扩大这个答案,但被叫走了,只是发布了我所拥有的。以下是一些您可能会觉得有用的细节。我创建了一个“纸牌游戏引擎”。该引擎一次承载一个纸牌游戏(克朗代克、蜘蛛、策略等)。它跟踪每个游戏的统计数据,并允许玩和编辑单个游戏。这些游戏是 IronPython 脚本,这使得添加新游戏相对容易。
My CardContainer 是一个包含零个或多个卡片的对象。
它有一个 LieDirection (None, Up, Down, Left, Right),它决定了它的卡片是如何布局的。
它有一个限制在 LieDirection 中绘制的卡片数量的 MaximumDepth。这对于像克朗代克这样的游戏来说很方便,您只想显示前 3 张浪费的牌。
它具有用于间隔卡片的属性。面朝上和面朝下的卡片有单独的间距值。它可以将卡片自动打包到由最大长度定义的区域中。它有一个“额外的”值,每张卡片一个——无论该索引处是否有卡片。后者在模拟鼠标悬停期间用于“发现”指向的卡片,以便用户可以清楚地看到可能被上面的卡片遮挡的卡片。这是通过在悬停卡顶部设置卡的“额外垫”来实现的。这可以通过具有“悬停卡”和“悬停间距”属性来简化,但是每张卡具有额外的填充允许奇数种类的纸牌游戏,这些纸牌游戏突出显示具有间距的画面堆中的特定“行”。
它有一个 HitTest 方法可以从给定的 X、Y 位置返回一张卡片。
所有这一切都意味着 Card 对象不知道它在桌子上的位置。我有一个复杂的动画系统,所以卡片的位置最终来自动画引擎。如果卡片当前没有动画,动画系统从它的容器中获取它的位置。
请注意,上面提到的卡片位置仅用于绘制。所有的卡片总是附在一个 CardContainer 上,并且简单地从一张卡移动到另一张卡。有一个“特殊”容器,称为甲板,最初包含每张卡片。它最初位于桌子之外。容器具有 Visible 属性。仅当将卡片从可见容器移动到另一个可见容器时才会播放动画。这允许您在必要时在没有动画的情况下移动卡片,并且卡片可以“飞出”到/从放置在桌子外的容器中。
该引擎还有一个基本的布局系统,用于相对于彼此定位 CardContainer。我做的一件非常方便的事情是使用卡片大小相对坐标系。表格的“宽度”正好是 11 个卡片宽度。无论用户的桌子尺寸有多大,宽度始终是 11 卡宽度。这意味着卡片大小(如用户所见)会增长和缩小。高度是可变的,但由固定的卡片大小比例决定(由卡片位图决定)。如果您给 CardContainer 一个 X 值 1.0,这意味着它将位于距离表格左侧一个卡片宽度的位置。这些值是浮点数,因此您可以使用 0.5 指定 1/2 卡片宽度。这使得在脚本中定位元素变得非常容易,而不必担心屏幕坐标。无论用户如何改变屏幕的大小,
该引擎还具有无限的撤消和重做功能。这意味着不仅要记录卡片移动(从一个容器到另一个容器),而且还要记录所有属性更改(卡片和容器属性)。如果从一开始就没有计划好,撤消和重做可能难以实施。脚本可以访问 Game.LogVariableChange 方法,以便它们可以通过记录机制更改全局变量的值。这对于像克朗代克的“三重交易”功能这样的东西是必要的。该脚本必须存储使用的重新交易次数,但如果用户取消了重新交易,则该变量的值更改也必须撤消。
这对纸牌非常有效,但几乎适用于任何类型的纸牌游戏。显然,您不必第一次去实施所有这些。我提供这些信息只是为了给你一些想法。