При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Microsoft.Xna.Framework.Graphics;
namespace Application1 { public partial class MainForm : Form { // Объявим поле графического устройства для видимости в методах GraphicsDevice device;
public MainForm() { InitializeComponent();
// Подпишемся на событие Load формы this.Load += new EventHandler(MainForm_Load);
// Попишемся на событие FormClosed формы this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed); }
void MainForm_FormClosed(object sender, FormClosedEventArgs e) { // Удаляем (освобождаем) устройство device.Dispose(); // На всякий случай присваиваем ссылке на устройство значение null device = null; }
void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства PresentationParameters presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height;
// Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); }
protected override void OnPaint(PaintEventArgs e) { device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);
base.OnPaint(e); } } } Выбрасывается исключение: Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл. Делаю все пунктуально. В чем может быть проблема? |
Windows Forms и XNA 3.0
Избирательная очистка буферов
Теперь попробуем применить более сложную перегрузку метода Clear() для избирательной очистки экрана и нарисовать с ее помощью шахматную доску:
public void Clear(Microsoft.Xna.Framework.Graphics.ClearOptions options, Microsoft.Xna.Framework.Graphics.Color color, float depth, int stencil, Microsoft.Xna.Framework.Rectangle[] regions)
- options - набор битовых флагов, указывающих, какие буферы необходимо очистить. Для очистки экранного буфера используется флаг Target перечисления ClearOptions. Остальные флаги DepthBuffer и Stencil этого перечисления используются для очистки соответственно буфера глубины и буфера шаблона, и будут рассмотрены далее
- color - цвет, которым будет закрашен буфер. Задаeтся с использованием структуры Microsoft.Xna.Framework.Graphics.Color, являющейся аналогом структуры System.Drawing.Color. Необходимость дублирования обусловлено стремлением разработчиков Microsoft сделать XNA Framework переносимой на другие платформы
- depth - значение, которым будет заполнен буфер глубины
- stencil - значение, которым будет заполнен буфер шаблона
- regions - массив структур Microsoft.Xna.Framework.Rectangle, задающих прямоугольные области экрана, которые должны быть очищены. Области экрана задаются в оконных координатах клиентской области формы - начало координат расположено в ее левом верхнем углу
Для рисования шахматной доски вначале мы очистим экран белым цветом, затем создим массив областей экрана, соответствующих клеткам доски коричневого цвета и ещe раз очистим экран, но уже коричневым цветом.
- Модифицируйте перекрытый метод OnPaint() следующим образом
protected override void OnPaint(PaintEventArgs e) { // Очищаем экран белым цветом device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke); // Создаeм массив областей закраски, соответствующих коричневым клеткам Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32]; int k = 0; // Счетчик элементов массива // Перебираем коричневые клетки шахматной доски for (int j = 0; j < 8; j++) // Строки шахматной доски for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски { // Заносим в массив параметры рисования очередной клетки rects[k] = new Microsoft.Xna.Framework.Rectangle( i * this.ClientSize.Width / 8, // Отступ по горизонтали j * this.ClientSize.Height / 8, // Отступ по вертикали this.ClientSize.Width / 8, // Ширина клетки this.ClientSize.Height / 8); // Высота клетки k++; // Увеличиваем счетчик } // Закрашиваем все области из массива rects коричневым цветом device.Clear(ClearOptions.Target, Microsoft.Xna.Framework. Graphics.Color.Brown, 0.0f, 0, rects); // Копируем задний буфер на экран device.Present(); base.OnPaint(e); }
- Запустите приложение - на экране появится окно, раскрашенное как шахматная доска
Устранение скрытых дефектов приложения
Попробуйте поизменять размеры окна и отметьте, что программа отображает доску непонятным образом как при уменьшении, так и при увеличении окна. Устраним сначала проблему, возникающую при уменьшении окна.
Начнем с того, что при уменьшении окна необходимости в дорисовке экрана нет и событие Paint наследуемого формой класса Control не срабатывает. В принципе, для борьбы с этим недоразумением мы могли бы перекрыть метод диспетчеризации OnResize() формы (или подписаться на событие Resize ) и вставить туда вызов метода Invalidate(), принудительно генерирующего событие Paint. Однако существует гораздо более элегантное решение: если установить у формы стиль ResizeRedraw, то при изменении размера формы будет автоматически генерироваться событие Paint.
- Добавьте в обработчик события Load установку стиля формы ResizeRedraw
void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства PresentationParameters presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height; // Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); // Перерисовывать форму при изменении размеров this.SetStyle(ControlStyles.ResizeRedraw, true); }
- Запустите приложение и поизменяйте размеры окна
Теперь экран при уменьшении размеров стал перерисовываться. Но здесь стала очевидной другая проблема: при изменении размеров появились заметные мигания окна. Чтобы эти мигания стали еще заметнее, выполните следующее:
- В рабочей области редактора кода вызовите контекстное меню и выполните команду View Designer (или просто выполните команду меню оболочки View/Designer )
- Выделите форму щелчком мыши и в панели Properties установите свойство BackColor формы в значение Green
- Запустите приложение и поизменяйте размеры окна - сейчас мигания зеленого цвета на фоне шахматной доски стали более заметны
Дело здесь в том, что перед каждым вызовом события Paint автоматически вызывается виртуальный метод OnPaintBackground() наследуемого формой класса Control, который очищает экран цветом BackColor. Эта функциональность позволяет разработчику, использующему обычный GDI+, не заботится об очистке экрана, однако в нашем случае такая "самовольная" очистка формы стандартными средствами Windows приводит лишь к мерцанию. Следовательно, нам необходимо запретить форме закрашивать экран перед вызовом обработчика события Paint. Это делается установкой для формы стиля Opaque.
- Добавьте в обработчик события Load установку стиля формы Opaque
void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства PresentationParameters presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height; // Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); // Перерисовывать форму при изменении размеров this.SetStyle(ControlStyles.ResizeRedraw, true); // Запретить очистку экрана средствами GDI+ this.SetStyle(ControlStyles.Opaque, true); }
- Запустите приложение - теперь мигания формы при изменении ее размеров исчезли
И так, первую проблему мы решили, но осталась вторая, гораздо более неприятная - некорректное масштабирование шахматной доски при изменении размера окна. Дело в том, что при создании графического устройства мы задаeм размер вспомогательного буфера, используемого при двойной буферизации, равным размеру клиентской области окна и к тому же только один раз (в обработчике события Load формы). Когда мы изменяем размер окна, его клиентская область так же изменяется. А вот размер вспомогательного буфера графического устройства остаeтся неизменным. Получается, что при изменении размеров окна происходит рассинхронизация между размерами клиентской области окна просмотра и вспомогательного буфера-источника, в котором рисуется изображение. В результате, приложение начинает работать некорректно.
Чтобы синхронно подстраивать параметры графического устройства под новые размеры окна, нужно перекрыть метод диспетчеризации OnResize() формы, включить в него новые настройки объекта presentParams и перезапустить объект device его методом Reset(). Но прежде всего нужно ссылку на объект presentParams сделать видимой в методе диспетчеризации OnResize(), т.е. вынести ее объявление в поле класса.
- На основе вышесказанного внесите следующие изменения в класс MainForm
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Microsoft.Xna.Framework.Graphics; namespace Application1 { public partial class MainForm : Form { // Объявим поле графического устройства для видимости в методах GraphicsDevice device; // Объявим объект настроек графического устройства PresentationParameters presentParams; public MainForm() { InitializeComponent(); // Подпишемся на событие Load формы this.Load += new EventHandler(MainForm_Load); // Попишемся на событие FormClosed формы this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed); } void MainForm_FormClosed(object sender, FormClosedEventArgs e) { // Удаляем (освобождаем) устройство device.Dispose(); // На всякий случай присваиваем ссылке на устройство значение null device = null; } void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства //PresentationParameters presentParams = new PresentationParameters(); presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height; // Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); // Перерисовывать форму при изменении размеров this.SetStyle(ControlStyles.ResizeRedraw, true); // Запретить очистку экрана средствами GDI+ this.SetStyle(ControlStyles.Opaque, true); } protected override void OnResize(EventArgs e) { // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height; // Сбрасываем устройство и применяем к нему новые параметры device.Reset(presentParams); base.OnResize(e); } protected override void OnPaint(PaintEventArgs e) { // Очищаем экран белым цветом device.Clear(Microsoft.Xna.Framework.Graphics.Color.WhiteSmoke); // Создаeм массив областей закраски, соответствующих коричневым клеткам Microsoft.Xna.Framework.Rectangle[] rects = new Microsoft.Xna.Framework.Rectangle[32]; int k = 0; // Счетчик элементов массива // Перебираем коричневые клетки шахматной доски for (int j = 0; j < 8; j++) // Строки шахматной доски for (int i = j % 2; i < 8; i += 2) // Столбцы шахматной доски { // Заносим в массив параметры рисования очередной клетки rects[k] = new Microsoft.Xna.Framework.Rectangle( i * this.ClientSize.Width / 8, // Отступ по горизонтали j * this.ClientSize.Height / 8, // Отступ по вертикали this.ClientSize.Width / 8, // Ширина клетки this.ClientSize.Height / 8); // Высота клетки k++; // Увеличиваем счетчик } // Закрашиваем все области из массива rects коричневым цветом device.Clear(ClearOptions.Target, Microsoft.Xna.Framework. Graphics.Color.Brown, 0.0f, 0, rects); // Копируем задний буфер на экран device.Present(); base.OnPaint(e); } } }
Операция сброса устройства, выполняемая при вызове метода Reset(), является очень медленной операция. Никогда не вставляйте еe без причины в обработчик события Paint или перекрытый метод диспетчеризации OnPaint(). Они вызываются очень часто и это приведeт к заметному падению производительности. В нашем случае эта операция вставлена в перекрытый метод диспетчеризации OnResize() и выполняется только при изменении пользователем размеров формы, что бывает редко.
- Выполните приложение - теперь рисование при изменениях окна работает нормально
Но здесь скрыта еще одна проблема. Если уменьшить вертикальный размер клиентской области формы до нуля (уменьшить горизонтальный размер до нуля мешают системные кнопки и заголовок окна), то приложение завершится аварийно именно при попытке установки нулевых размеров заднего буфера в графическом устройстве. Для решения этой проблемы нужно ограничить минимальный размер клиентской области хотя бы одним пикселом. Это легко можно сделать при помощи свойства MinimumSize формы, которое задает минимальные размеры окна (но не клиентской области!).
- Добавьте в обработчик события Load следующий код, устанавливающий минимально допустимый для изменения размер окна при заданном размере клиентской области 1x1 пикселов
void MainForm_Load(object sender, EventArgs e) { // Создаем объект представления для настройки графического устройства //PresentationParameters presentParams = new PresentationParameters(); presentParams = new PresentationParameters(); // Настраиваем объект представления через его свойства presentParams.IsFullScreen = false; // Включаем оконный режим presentParams.BackBufferCount = 1; // Включаем задний буфер // для двойной буферизации // Переключение переднего и заднего буферов // должно осуществляться с максимальной эффективностью presentParams.SwapEffect = SwapEffect.Discard; // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height; // Создадим графическое устройство с заданными настройками device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware, this.Handle, presentParams); // Перерисовывать форму при изменении размеров this.SetStyle(ControlStyles.ResizeRedraw, true); // Запретить очистку экрана средствами GDI+ this.SetStyle(ControlStyles.Opaque, true); // Вычисляем размер окна при размерах клиентской области 1x1 пикселов. // Полученное значение присваиваем свойству MinimumSize this.MinimumSize = this.SizeFromClientSize(new Size(1, 1)); }
Можно запустить приложение и убедиться, что при интерактивном уменьшении размеров клиентской области остается еще один пиксел в запасе и приложение продолжает устойчиво работать. Но есть еще одна скрытая родственная лазейка для получения нулевых размеров клиентской области - минимизация окна с помощью системной кнопки. Для ее решения сброс графического устройства для установки новых размеров буфера нужно выполнять только после проверки состояния окна, убедившись, что оно не минимизировано.
- Добавьте в перекрытый метод диспетчеризации OnResize() следующую проверку состояния окна
protected override void OnResize(EventArgs e) { // Устанавливаем размеры заднего буфера по клиентской области окна формы presentParams.BackBufferWidth = this.ClientSize.Width; presentParams.BackBufferHeight = this.ClientSize.Height; // Сбрасываем устройство и применяем к нему новые параметры // Если окно не минимизировано if (this.WindowState != FormWindowState.Minimized) device.Reset(presentParams); base.OnResize(e); }
- Запустите приложение, минимизируйте окно - приложение продолжает устойчиво работать!