Ihre beste Wette ist die Verwendung von Kontrollen ganz zu vermeiden. Sie werden A) zu einer schlechten Leistung führen und B) das Testen/Zeichnen von Treffern erschweren.
Erstellen Sie einfach Objekte, um den Status der Tabelle darzustellen (ich verwende ein CardContainer-Objekt) und Graphics.DrawImage zu verwenden, um alle Karten zu zeichnen, wo sie während des Paint-Ereignisses liegen. Sie können ein einzelnes Steuerelement für die gesamte Tabelle verwenden, wenn Sie auch andere UI-Elemente hinzufügen müssen.
Dadurch wird auch die Animation der Kartenbewegung einfacher, wenn Sie sich für die Animation entscheiden.
Aktualisiert
ich diese Antwort erweitern gedacht, sondern wurde abberufen und einfach geschrieben, was ich hatte. Hier sind einige Details, die Sie nützlich finden können. Ich habe eine "Solitaire Game Engine" erstellt. Die Engine beherbergt jeweils ein Solitaire-Spiel (Klondike, Spider, Strategie, etc.). Es verfolgt Statistiken für jedes Spiel und ermöglicht sowohl das Spielen als auch das Editieren der einzelnen Spiele. Die Spiele sind IronPython-Skripte, die das Hinzufügen neuer Spiele relativ einfach machen.
Mein CardContainer ist ein Objekt, das keine oder mehrere Karten enthält.
Es hat eine LieDirection (Keine, Auf, Ab, Links, Rechts), die bestimmt, wie seine Karten ausgelegt sind.
Es hat eine MaximumDepth, die die Anzahl der in der LieDirection gezeichneten Karten festlegt. Dies ist praktisch für Spiele wie Klondike, wo Sie nur die obersten 3 Karten der Verschwendung zeigen wollen.
Es hat Eigenschaften für den Abstand der Karten. Für aufgedeckte und verdeckte Karten gibt es separate Abstände. Es kann Karten automatisch in einen durch MaximumLength definierten Bereich packen. Und es hat einen "extra Pad" -Wert, einen für jede Karte - ob es eine Karte bei diesem Index gibt oder nicht. Letzterer wird während eines simulierten Maus-Schwebens verwendet, um die Karte, auf die gezeigt wird, "aufzudecken", so dass der Benutzer deutlich eine Karte sehen kann, die durch Karten darüber verdeckt sein könnte. Dies wird erreicht, indem das "extra Pad" der Karte oben auf der Karte gesetzt wird. Dies könnte durch eine "Hover-Karte" und "Hover-Spacing" -Eigenschaft vereinfacht worden sein, aber eine zusätzliche Auffüllung pro Karte ermöglicht es, Solitaire-Spiele mit einer bestimmten 'Reihe' in den Tableau-Stapeln mit Abstand zu markieren.
Es verfügt über eine HitTest-Methode, um eine Karte von einer bestimmten X, Y-Position zurückzugeben.
All dies bedeutet, dass das Card-Objekt keine Ahnung hat, wo es auf dem Tisch gezeichnet wird. Ich habe ein komplexes Animationssystem und der Standort einer Karte kommt letztendlich von der Animations-Engine. Wenn eine Karte gerade nicht animiert, erhält das Animationssystem seinen Standort aus seinem Container.
Beachten Sie, dass die oben genannte Position der Karte ausschließlich zum Zeichnen dient. Alle Karten sind immer an genau einen CardContainer angehängt und werden einfach von einem zum anderen bewegt. Es gibt einen "speziellen" Container namens Deck, der anfangs jede Karte enthält. Es ist zunächst vom Tisch entfernt positioniert. Ein Container verfügt über eine Visible-Eigenschaft. Animationen werden nur abgespielt, wenn eine Karte aus einem sichtbaren Container in einen anderen sichtbaren Container verschoben wird. Dies ermöglicht es Ihnen, Karten bei Bedarf ohne Animation zu bewegen, und Karten können von/zu den vom Tisch positionierten Behältern "herausfliegen".
Der Motor hat auch ein rudimentäres Layout-System für die Positionierung CardContainer relativ zueinander. Eine sehr praktische Sache, die ich tat, war, ein Kartengröße-relatives Koordinatensystem zu verwenden. Die 'Breite' des Tisches beträgt genau 11 Kartenbreiten. Egal wie groß der Benutzer den Tisch auslegt, die Breite beträgt immer 11 Kartenbreiten. Dies bedeutet, dass die Kartengrößen (wie vom Benutzer gesehen) wachsen und schrumpfen. Die Höhe ist variabel, wird aber durch ein festes Kartenformat bestimmt (bestimmt durch die Karten-Bitmaps). Wenn Sie einem CardContainer einen X-Wert von 1.0 geben, bedeutet dies, dass er eine Kartenbreite von der linken Seite der Tabelle entfernt liegt. Die Werte sind Gleitkommawerte, so dass Sie 1/2 Kartenbreite mit 0.5 angeben können. Dies macht es sehr einfach, Elemente im Skript zu positionieren, ohne sich um Bildschirmkoordinaten kümmern zu müssen. Unabhängig davon, wie der Benutzer die Größe des Bildschirms ändert, wird Ihr Spiel genau so angelegt.
Der Motor hat auch unbegrenzte Undo und Redo. Dies bedeutet, dass nicht nur Kartenbewegungen (von einem Container zum anderen) aufgezeichnet werden müssen, sondern auch alle Eigenschaftsänderungen (sowohl Karten- als auch Containereigenschaften) aufgezeichnet werden. Rückgängig machen und Wiederherstellen kann schwierig sein, wenn es nicht von Anfang an geplant ist. Die Skripts haben Zugriff auf eine Game.LogVariableChange-Methode, sodass sie den Wert einer globalen Variablen über den Aufzeichnungsmechanismus ändern können. Dies ist notwendig für so etwas wie Klondikes "Drei-Erledigungen". Das Skript muss die Anzahl der verwendeten Redigierungen speichern. Wenn der Benutzer jedoch eine erneute Eingabe rückgängig gemacht hat, muss auch die Wertänderung dieser Variablen rückgängig gemacht werden.
Dies funktioniert sehr gut für Solitaire, könnte aber für fast jede Art von Kartenspiel funktionieren. Natürlich müssen Sie das alles beim ersten Mal nicht umsetzen. Ich präsentiere die Informationen, um Ihnen ein paar Ideen zu geben.
Haben Sie sich das Starterset für Kartenspiele für Windows-Formulare angesehen?http://msdn.microsoft.com/en-us/vcsharp/aa336742 – MattDavey