Вначале отвечу на вопрос «Зачем их дружить». Ответ прост — WPF хорош для пользовательских интерфейсов, XNA для сложной 3D графики и если вы делаете клиентское приложение со сложным интерфейсом и 3D элементами в нем, то связка XNA & WPF как раз для вас.
Статью я буду иллюстрировать на примере простенького медиаплеера, который я сейчас пишу.
Итак, у нас есть скроллер обложек написанный на XNA. Выглядит он так:
А еще у нас есть WPF проект будущего медиаплеера, вот его скриншот:
Наша задача состоит в том, чтобы поместить первое во второе. Поступим также, как поступали ранее с приложениями на Windows Forms: передадим в конструктор класса Game хендл на элемент управления, поверх которого мы будем рисовать. Но есть одна небольшая проблема: в WPF хендлами обладают только окна. Впрочем, эта проблема легко решаема: добавим в окно элемент управления WindowsFormsHost и поместим в него панель (Panel) из пространства имен System.Windows.Forms. И вот хендл этой панели мы и передадим в конструктор игры. Итак, действуем по порядку:
Поясню последний пункт:
Отлично, наша «игра» отрисовывается в отведенную для нее панель, но есть несколько пунктов, по которым меня не устраивает данное решение:
Будем решать эти проблемы. Для этого я предлагаю прекратить использование класса игры и написать небольшой менеджер устройства. Нам придется вручную инициализировать устройство, по таймеру перерисовывать сцену, откликаться на изменение размеров контрола и его уничтожение. В менеджер мы передаем контрол, на котором будем рисовать, подписываемся на события ресайза и отрисовки контрола, создаем устройство и таймер, по которому мы будем вызывать инициировать события Update и Draw. Вы можете посмотреть исходный код графического менеджера здесь или в прилагаемом проекте. Нам осталось лишь разобраться с панелью. Оказывается нужно лишь применить соответствующие стили и панель перестанет моргать. Неудобство лишь в том, что модификатор доступа метода SetStyle — protected. Ну ничего, унаследуемся от панели и в конструкторе применим нужные нам стили. Исходный код оптимизированной панели прост до безобразия:
Итак, у нас все готово, запускаем проект и видим, что все работает как надо: панелька не моргает, корректно меняются размеры, приложение не открывает дополнительных окон.
Здесь вы можете скачать начальный проект приложения со связкой WPF & XNA. Ну а вот что получилось у меня:
Статью я буду иллюстрировать на примере простенького медиаплеера, который я сейчас пишу.
Итак, у нас есть скроллер обложек написанный на XNA. Выглядит он так:
А еще у нас есть WPF проект будущего медиаплеера, вот его скриншот:
Наша задача состоит в том, чтобы поместить первое во второе. Поступим также, как поступали ранее с приложениями на Windows Forms: передадим в конструктор класса Game хендл на элемент управления, поверх которого мы будем рисовать. Но есть одна небольшая проблема: в WPF хендлами обладают только окна. Впрочем, эта проблема легко решаема: добавим в окно элемент управления WindowsFormsHost и поместим в него панель (Panel) из пространства имен System.Windows.Forms. И вот хендл этой панели мы и передадим в конструктор игры. Итак, действуем по порядку:
- Объединим XNA и WPF проекты в одном решении
- Удаляем у XNA проекта файл Program.cs и в свойствах проекта меняем тип с Windows приложения на библиотеку классов.
- Добавим Microsoft.Xna.Framework.Game в References WPF проекта
- Добавим в объявление конструктора игры параметр IntPtr handle
- В конструкторе формы WPF создадим экземпляр класса игры и передаем ему хендл на панель и вызовем у игры метод Run() в отдельном потоке
- Во время инициализации устройства присвоим наш хендл
Поясню последний пункт:
IntPtr Handle;
public MainGame(IntPtr handle)
{
Handle = handle;
graphics = new GraphicsDeviceManager(this);
graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(PreparingDeviceSettings);
}
public void PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
{
e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = Handle;
}
* This source code was highlighted with Source Code Highlighter.
Отлично, наша «игра» отрисовывается в отведенную для нее панель, но есть несколько пунктов, по которым меня не устраивает данное решение:
- При запуске приложения параллельно создается какое-то левое окно. Его конечно можно скрыть, но все равно это нехорошо.
- При изменении размеров формы у нас происходит тупое растягивание/сжатие картинки. Хотелось бы реинициализировать устройство для обеспечения наилучшего качества картинки.
- На мой взгляд, самое главное: игра и приложение живут в разных потоках. Это приводит к ошибкам при попытке работы с WPF-контролами из XNA кода.
- Панель предательски моргает при изменении ее размеров.
Будем решать эти проблемы. Для этого я предлагаю прекратить использование класса игры и написать небольшой менеджер устройства. Нам придется вручную инициализировать устройство, по таймеру перерисовывать сцену, откликаться на изменение размеров контрола и его уничтожение. В менеджер мы передаем контрол, на котором будем рисовать, подписываемся на события ресайза и отрисовки контрола, создаем устройство и таймер, по которому мы будем вызывать инициировать события Update и Draw. Вы можете посмотреть исходный код графического менеджера здесь или в прилагаемом проекте. Нам осталось лишь разобраться с панелью. Оказывается нужно лишь применить соответствующие стили и панель перестанет моргать. Неудобство лишь в том, что модификатор доступа метода SetStyle — protected. Ну ничего, унаследуемся от панели и в конструкторе применим нужные нам стили. Исходный код оптимизированной панели прост до безобразия:
public class OptimizedPanel : Panel
{
public OptimizedPanel()
: base()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.Opaque, true);
}
}
* This source code was highlighted with Source Code Highlighter.
Итак, у нас все готово, запускаем проект и видим, что все работает как надо: панелька не моргает, корректно меняются размеры, приложение не открывает дополнительных окон.
Здесь вы можете скачать начальный проект приложения со связкой WPF & XNA. Ну а вот что получилось у меня: