Лучше всего избегать использования элементов управления полностью. Они будут A) приводить к низкой (э) производительности и B) усложнять тестирование / прорисовку попадания.
Просто создайте объекты для представления состояния таблицы (я использую объект CardContainer) и используйте Graphics.DrawImage, чтобы нарисовать все карты, где они лежат во время события рисования. Вы можете использовать один элемент управления для всей таблицы, если вам нужно также добавить другие элементы пользовательского интерфейса.
Это также упростит анимацию движения карты, если вы решите добавить анимацию.
Обновлено
Я хотел расширить этот ответ, но был отозван и просто опубликовал то, что у меня было. Вот некоторые детали, которые вы можете найти полезными. Я создал «движок пасьянсов». Движок содержит одну пасьянс (клондайк, паук, стратегия и т. Д.). Он отслеживает статистику для каждой игры и позволяет играть и редактировать отдельные игры. Игры представляют собой сценарии IronPython, что позволяет относительно легко добавлять новые игры.
Мой CardContainer - это объект, который содержит ноль или более карт.
У него есть LieDirection (Нет, Вверх, Вниз, Влево, Вправо), который определяет, как раскладываются его карты.
Имеет MaximumDepth, который ограничивает количество карт, вытянутых в LieDirection. Это удобно для таких игр, как Клондайк, где вы хотите показать только 3 верхние карты из отходов.
У него есть свойства для размещения карт. Существуют отдельные значения интервалов для карт, которые обращены вверх и вниз. Он может автоматически упаковать карты в область, определенную MaximumLength. И у него есть значение 'extra pad', по одному на каждую карту - есть ли карта с этим индексом или нет. Последний используется во время имитации мыши, чтобы «раскрыть» указанную карту, чтобы пользователь мог ясно видеть карту, которая может быть закрыта картами сверху. Это достигается установкой «дополнительной площадки» карты сверху карты наведения. Это можно было бы упростить, если иметь свойство «карта при наведении» и «интервал при наведении», но наличие дополнительного заполнения для каждой карты допускает нечетные виды пасьянсов, которые выделяют определенный «ряд» в стопках таблиц с интервалом.
У него есть метод HitTest для возврата Карты из заданного местоположения X, Y.
Все это означает, что объект Card не имеет представления о том, где он нарисован на столе. У меня сложная система анимации, поэтому местоположение карты в конечном итоге зависит от механизма анимации. Если карта в данный момент не анимируется, система анимации получает свое местоположение из контейнера.
Обратите внимание, что местоположение карты, указанное выше, предназначено исключительно для рисования. Все карты всегда привязаны только к одному CardContainer и просто перемещаются из одной в другую. Существует один «специальный» контейнер под названием «Колода», который изначально содержит каждую карту. Первоначально он находится вне стола. Контейнер имеет свойство Visible. Анимации воспроизводятся только при перемещении карты из контейнера Visible в другой контейнер Visible. Это позволяет перемещать карточки без анимации, когда это необходимо, и карточки могут «вылетать» в / из контейнеров, расположенных вне стола.
Движок также имеет рудиментарную систему компоновки для позиционирования CardContainers относительно друг друга. Одна очень удобная вещь, которую я сделал, - это использование системы координат относительно размера карты. Ширина таблицы составляет ровно 11 значений ширины карты. Независимо от того, насколько большой пользователь измеряет таблицу, ширина всегда равна 11 ширинам карты. Это означает, что размеры карты (по мнению пользователя) растут и уменьшаются. Высота является переменной, но определяется фиксированным отношением размера карты (определяется из растровых изображений карты). Если вы дадите CardContainer значение X, равное 1,0, это означает, что он будет расположен на одну ширину карты слева от таблицы. Значения являются числами с плавающей запятой, поэтому вы можете указать 1/2 ширины карты с 0,5. Это позволяет очень легко размещать элементы в сценарии, не беспокоясь о координатах экрана. Независимо от того, как пользователь изменяет размер экрана, ваша игра будет отображаться одинаково.
Двигатель также имеет неограниченное количество отмен и повторов. Это означает, что необходимо регистрировать не только перемещения карт (из одного контейнера в другой), но и все изменения свойств (как свойств карт, так и контейнеров). Отменить и повторить может быть сложно осуществить, если это не запланировано с самого начала. Сценарии имеют доступ к методу Game.LogVariableChange, так что они могут изменять значение глобальной переменной с помощью механизма записи. Это необходимо для чего-то вроде функции «трех повторений» Клондайка. Сценарий должен хранить количество использованных изменений, но если пользователь отменил изменение, изменение значения этой переменной также должно быть отменено.
Это очень хорошо работает для пасьянса, но может работать практически для любой карточной игры. Очевидно, вам не нужно идти и реализовывать все это в первый раз. Я представляю информацию только для того, чтобы дать вам несколько идей.