Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
insrukcii k lr3_ robota z podiyamy.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
883.71 Кб
Скачать

Устранение скрытых дефектов приложения

Попробуйте поизменять размеры окна и отметьте, что программа отображает доску непонятным образом как при уменьшении, так и при увеличении окна. Устраним сначала проблему, возникающую при уменьшении окна.

Начнем с того, что при уменьшении окна необходимости в дорисовке экрана нет и событие 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 формы, которое задает минимальные размеры окна (но не клиентской области!).

Задание размеров окна в целом (вместе с заголовком и рамкой) в режиме проектирования через панель Properties будет не точным. Размер клиентской области, который отведет система при заданном размере окна в целом, зависит от множества факторов: установленной операционной системы, пользовательских настроек и т.п. Правилнее устанавливать значение свойства MinimumSize программно посредством метода формы SizeFromClientSize(), который возвращает размер окна при заданном размере именно клиентской области.

  • Добавьте в обработчик события 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);

}

  • Запустите приложение, минимизируйте окно - приложение продолжает устойчиво работать!

Потеря информации графическим устройством

Операционная система Windows является многозадачной системой, в которой одновременно могут выполняться несколько задач и делить между собой одни и те же ресурсы. Существует опасность, что настройки графического устройства, установленные нашим приложением XNA, в любой момент могут сбиться другим подобным приложением. Такая ситуация называется потерей устройства (Device Lost). Это легко проверить, если при запущенном приложении XNA попытаться изменить разрешение экрана - приложение завершится аварийно.

Отсюда возникает задача о восстановлении потерянной информации, рассмотрением которой мы сейчас и займемся. Информацию о состоянии, в котором в данный момент находится устройство, можно получить из свойства GraphicsDeviceStatus нашего объекта (экземпляра класса GraphicsDevice ), которое имеет одно из значений одноименного перечисления GraphicsDeviceStatus:

  • Normal - графическое устройство работает нормально

  • NotReset - графическое устройство потеряно, но может быть восстановлено методом Reset() объекта устройства

  • Lost - графическое устройство потеряно и пока не может быть восстановлено

При восстановимой потере устройства приложение выдает исключениеMicrosoft.Xna.Framework.Graphics.DeviceNotResetException, а при невосстановимой -Microsoft.Xna.Framework.Graphics.DeviceLostException. Если сгенерировано исключениеDeviceNotResetException, то приложение должно восстановить устройство методом device.Reset() и снова перерисовать изображение путeм вызова метода Invalidate(), в противном случае просто не реагировать на событие Paint, требующее обновить экран.

  • На основе вышесказанного модифицируйте обработку события Paint следующим образом

bool closing = false;

protected override void OnPaint(PaintEventArgs e)

{

if (closing) return;

try // Попытка

{

// Очищаем экран белым цветом

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);

}

// Откаты

catch (DeviceNotResetException) // Устройство можно восстановить

{

device.Reset(presentParams);

this.Invalidate();

}

catch (DeviceLostException) // Устройство нельзя восстановить

{

closing = true; // Больше не выполнять OnPaint()

string title = "Сбой графического устройства";

string message = "Работа программы будет завершена.\n"

+ "Закройте все ненужные программы\n"

+ "и повторите запуск этого приложения";

MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Stop);

Application.Idle += new EventHandler(Application_Idle);

return;

}

catch (Exception ext) // Все другие исключения

{

closing = true;

MessageBox.Show(ext.Message);

Application.Idle += Application_Idle1;

}

}

void Application_Idle(object sender, EventArgs e)

{

this.Close(); // Закрывает окно, у нас оно же и главное окно приложения

}

void Application_Idle1(object sender, EventArgs e)

{

Application.Exit(); // Завершает работу приложения (приведено для разнообразия)

}

  • Запустите приложение и попробуйте изменить разрешение экрана, чтобы поймать исключение невосстановимой потери устройства

В этом случае будет выдано окно с сообщением

Упражнение 2. Вывод графической информации XNA на пользовательский элемент управления

В упражнении 1 наше приложение осуществляло монопольный вывод графической информации на всю поверхность формы. Такой подход не позволяет размещать на форме элементы управления. Можно попробовать использовать вспомогательные диалоговые окна или плавающие панели инструментов, но это не очень красивое решение проблемы. Гораздо интереснее было бы научиться выводить информацию не на саму форму, а на элементы управления, размещенные на ней (к примеру, на элемент управления Panel ). Это позволило бы на освободившемся пространстве размещать другие нужные элементы пользовательского интерфейса.

Все элементы управления наследуют от класса Control, в том числе класс Form и Panel. Ранее при управлении экземпляром класса Form мы фактически использовали методы и свойства класса Control. В данном упражнении мы с таким же успехом можем управлять экземпляром классаPanel, помещенным на форму.

Единственная сложность состоит в том, что при управлении экземпляром Form наш код размещался внутри его расширения - классе MainForm, и имел доступ ко всем общедоступным ( public ) и защищенным ( protected ) свойствам и методам, унаследованным, в конечном итоге, от класса Control. А если попытаться просто управлять экземпляром класса Panel, помещенным на форму, то видимыми из расширения класса Form в объекте Panel (и, соответственно, в Control ) будут только общедоступные члены. Чтобы получить полный доступ к нужным сервисам класса Control, нужно создать расширение класса Panel и упаковать в нем вызовы защищенных членов в общедоступные.

Фактически, нам нужно создать новый элемент управления, которые называют пользовательскими (в отличии от стандартных библиотечных).

Создание заготовки пользовательского элемента управления

  • Закройте в рабочей области оболочки все редактируемые ранее документы командой меню Window/Close All Documents

  • Командой File/Add/New Project вызовите мастер добавления в решение нового проекта с именем XNAPanel и заполните его окно так

увеличить изображение

  • Через панель Solution Explorer переименуйте автоматически созданный файл Class1.cs на XNAPanel.cs

  • Командой Project/Add Reference подключите к проекту библиотечные сборки System.Windows.Forms.dll и System.Drawing.dll, а в начало файла XNAPanel.cs вставьте инструкции:

  • using System.Windows.Forms;

using System.Drawing;

  • Модифицируйте XNAPanel.cs следующим образом

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows.Forms;

using System.Drawing;

namespace XNAPanel

{

public class XNAPanel : Panel

{

// Конструктор по умолчанию

public XNAPanel()

{

// Изменим цвет фона панели, чтобы визуально отличить от других

BackColor = Color.CornflowerBlue;

// Установим минимальный размер,

// чтобы устранить возможное исключение

MinimumSize = new Size(1, 1);

}

// Сделаем общедоступным нужный нам защищенный метод

public new void SetStyle(ControlStyles flag, bool value)

{

base.SetStyle(flag, value);

}

}

}

  • Командой меню Build/Build XNAPanel откомпилируйте данный проект

Мы создали библиотечный пользовательский элемент управления для вывода графической информации XNA, который теперь можно размещать на форме.

  • Командой File/Add/New Project вызовите мастер добавления в решение нового проекта с именем Application2 и заполните его окно так

увеличить изображение

После того, как мастер создаст новый проект и выведет визуальное представление формы в рабочую область, можно убедиться, что в панели инструментов Toolbox появилась новая вкладка XNAPanel Components с пиктограммой компонета XNAPanel.

  • Через Обозреватель решений переименуйте файл Form1.cs проекта Application2 в MainForm.cs, поменяйте заголовок окнаText="Упражнение 2" и из панели инструментов Toolbox поместите на форму экземпляр компонента SplitContainer (можно просто выполнить двойной щелчок мышью на компоненте). Убедитесь, что свойство Dock объекта splitContainer1 имеет значение Fill, т.е. что объект занимает всю клиентскую область формы

  • Установите для объекта splitContainer1 свойство BorderStyle в значение FixedSingle, чтобы создать черные рамки вокруг панелей элемента

  • Выделите курсором левую панель Panel1 объекта splitContainer1, поместите в нее из панели Toolbox экземпляр нашего компонента XNAPanelи установите его свойство Dock в значение Fill, чтобы он занял всю область объекта Panel1

  • Выделите курсором правую панель объекта splitContainer1, поместите на нее экземпляр компонента GroupBox и разверните его на всю панель установкой свойства Dock в значение Fill. Присвойте объекту groupBox1 заголовок " Параметры " через его свойство Text

  • Выделите объект groupBox1 и добавьте в него три текстовых метки Label с надписями

    • Цвет вершины №1

    • Цвет вершины №2

    • Цвет вершины №3

  • Поместите напротив этих меток три экземпляра компонента Button и присвойте им имена vertex1Color, vertex2 Color и vertex3Color

  • Выделите все три экземпляра компонента Button, очистите для них содержимое свойства Text и установите свойство Size равным ( 30 ; 20 )

  • Настройте для каждой кнопки уникальный цвет фона через свойство BackColor

  • Добавьте к форме экземпляр невизуального компонента ColorDialog, пиктограмма которого будет размещена в подвале рабочей области (Workspace ) дизайнера формы

В результате форма в режиме проектирования примет следующий вид

Средствами XNA мы нарисуем примитивный треугольник на экземпляре нашего компонента XNAPanel, цвет вершин которого мы будем выбирать из диалоговой панели. Для этого, как и ранее, нужно добавить в класс формы несколько вспомогательных полей, создать обработчики событий Load,FormClosed, Paint и т.д. и заполнить их соответствующим кодом. Но прежде, чем приступить к программированию обработчиков, познакомимся с новой техникой, которую мы будем использовать в этом упражнении для управления видеокартой на низком уровне.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]