particle1D

advertisement
Текущее состояние проекта по моделированию одномерного
движения частицы
Создание Windows-приложения
1.
2.
3.
4.
5.
6.
7.
8.
Выбираем new project… и в окне шаблонов выбираем Windows Forms Application.
Указываем папку расположения проекта кнопкой browse….
Назначаем имя решения sParticle
Назначаем имя проекта Particle1D.
В окне Solution Explorer изменяем имя файла program.cs на Particle1D.cs.
Подтверждаем изменение имени класса Program на Particle1D.
Изменяем имя файла Form1.cs на fParticle1D.cs
Подтверждаем изменение имени формы.
Состав приложения
1. В состав проекта входят три основных файла с кодом:
a. Файл Particle1D.cs с описанием класса Particle1D, содержащего метод Main,
который состоит в вызове трех методов класса Application. Этот файл мы не
редактируем. Он остается неизменным. Основным в нем является метод
Application.Run, который создает экземпляр формы класса fParticle1D и
обслуживает основной цикл приложения, считывая события из очереди,
сформированной операционной системой, до окончания приложения (нажатие
кнопки с крестиком в правом верхнем углу окна).
b. Два файла fParticle1D.cs и fParticle1D.Designer.cs с описанием класса fParticle1D. Так
как класс один, а файлов два, то в заголовке класса в обоих файлах есть
модификатор partial.
i. Файл fParticle1D.Designer.cs редактируется дизайнером в процессе
визуального конструирования. Основным методом дизайнера является
InitializeComponent(). Этот метод вызывается конструктором класса,
расположенном во втором файле fParticle1D.cs. Код дизайнера мы не
трогаем.
ii. Файл fParticle1D.cs является описанием той части класса fParticle1D, которая
редактируется программистом непосредственно.
2. На этой стадии приложение должно работать, но оно будет содержать пустое окно,
способное менять размеры и положение на экране, но не содержащее ничего кроме
заголовка. Попробуйте запустить приложение командой StartDebugging (клавиша F5)
(зеленая стрелка).
Визуальное редактирование
Для редактирования приложения откройте окно fParticle1D.cs[Design] командой View->Designer
(shift+F7) с изображением формы и окно свойств командой View->Properties Window. Кнопку
вызова окна свойств лучше поместить на правую или левую панель, т.к. оно часто используется в
процессе редактирования.
1. В окне свойств найдем свойство формы Text и в правой колонке заменим текст Form1
текстом Particle1D movement. Это текст заголовка окна.
2. Свойству StartPosition формы дадим значение CenterScreen. При запуске приложения окно
будет открываться в центре экрана.
1
3. Заметим, что в верхней строке окна свойств стоит выпадающий список тех объектов,
свойства которых можно редактировать. В настоящее время в списке находится только
сама форма fParticle1D.
4. Запустите приложение, убедившись, что окно появляется в центре и ее заголовок
«Particle1D movement».
5. Откроем окно с управляющими элементами Toolbox командой View->Toolbox. Здесь
находятся различные управляющие элементы (будем называть их «контролы»), которые
могут быть использованы при создании приложения. Найдем контрол класса Button,
щелкнем по нему, а затем по поверхности формы. На ней должна появиться кнопка.
6. Среда дает кнопке имя Button1. Это указано в окне свойств как значение свойства Name.
При желании это имя может быть изменено. Свойство Text кнопки по умолчанию также
имеет значение имени. Оно независимо может быть изменено. Сделайте это. Наберите,
например, в свойстве Text кнопки значение Кликни меня!. В отличие от значения
Name,которое может быть только идентификатором (содержать только буквы, цифры и
знак подчеркивания, начинаясь только с буквы или знака подчеркивания, и не имея
пробела), свойство Text может быть любой строкой.
7. В окне свойств есть две вкладки – собственно свойства и вкладка событий. Вкладка свойств
открывается кнопкой с изображением списка, а вкладка событий – с изображением
молнии. В окне событий кнопки по умолчанию выделена строка с событием Click. Для
кнопки это событие по умолчанию. Дважды щелкнув по изображению кнопки на форме,
вы сформируете скелет кода обработчика события Click в коде класса формы fParticle1D.
8. Обработчиком события Click является метод класса формы с именем, по умолчанию
состоящим из имени кнопки, знака подчеркивания и слова Click. Обработчики событий
обычно имеют два параметра. Один sender типа object содержит ссылку на объект,
вызывающий событие. В данном случае это конкретная кнопка. Второй параметр имеет
имя e и относится к типу EventArgs или классу, наследующему от EventArgs. Параметр e
содержит информацию о событии, на которое реагирует обработчик. Если это объект
класса EventArgs как в случае Click, то никакой специальной информации объект e не несет.
9. Наберите внутри обработчика оператор Text = ”Я кликнул кнопку!”; Запустите приложение
и кликнете кнопку. Заголовок формы должен измениться и показать этот текст.
10. Сотрите копку на форме. Для этого выделите ее и нажмите Delete. После этого сотрите
обработчик клика кнопки в тексте программы.
Дизайн окна, формирующего изображение частицы
1. Откройте окно Toolbox. В разделе Menus & Toolbars найдите компоненту класса
ToolStripContainer. Щелкните по ней, а затем по поверхности формы. На форме окажется
объект этой компоненты с именем toolStripContainer1 по умолчанию. В этом объекте пять
панелей. Панели, лежащие по периметру, служат для размещения управляющих
элементов, а центральная панель (ContentPanel) – для размещения содержательной части
окна. Так устроено, в частности, главное окно Visual Studio. На верхней панели
размещается главное меню, на нижней - элементы строки статуса, а на боковых панелях –
кнопки и другие контролы. Установите свойство Dock контейнера в Fill. Тогда контейнер
будет жестко привязан к размерам окна, заполняя всю клиентскую область.
2. В окне Toolbox в разделе Common Components найдите компоненту PictureBox и поместите
ее на центральную панель контейнера. В окне Properties свойству Dock нового объекта
pictureBox1 придайте значение Fill. Контейнер класса PictureBox лучше всего подходит для
размещения графики, хотя помещать графические изображения можно и непосредственно
на поверхность формы или центральной панели. Разница в том, что свойства объектов
PictureBox настроены так, что при анимации изображения воспроизведение реализуется
без мерцания.
2
Создадим на поверхности этого объекта фон в виде клеточки и нарисуем прямую
горизонтальную линию, расположению на середине высоты. Все изображения
формируются с помощью объекта класса Graphics. Доступ к объекту Graphics имеется в
обработчике события Paint через параметр e – объект класса PaintEventArgs. Создайте
обработчик Paint объекта pictureBox1 (окно свойств, страничка событий). Объект класса
Graphics находится в свойстве e.Graphics. Наберите в обработчике код
using (HatchBrush hb = new HatchBrush(HatchStyle.Cross, Color.LightGray, Color.Navy))
{
e.Graphics.FillRectangle(hb, pictureBox1.ClientRectangle);
}
Классы HatchBrush (штриховая кисть) и HatchStyle (стиль штриховки) находятся в
пространстве имен System.Drawing.Drawing2D. Добавьте в начало файла декларацию
using System.Drawing.Drawing2D; для короткого доступа к классам этого пространства
имен.
В строке кода используется оператор using (не путать с одноименным декларатором using).
Использование оператора using упрощает работу с объектами, которые отнимают
значительные ресурсы. В заключение кода, охваченного оператором using, ресурсы
объекта освобождаются. В данном случае этим объектом является hb – объект класса
штриховой кисти. Он объявляется в скобках, следующих за using, а освобождается после
выполнения одной команды FillRectangle. При создании hb указывается стиль штриховки,
обеспечиваемой кистью – "клеточка" (cross), цвет контура клеточки (светло серый) и цвет
фона (темно синий). Метод FillRectangle объекта e.Graphics заполняет кистью hb
прямоугольник pictureBoxe1.ClientRectangle, указанный вторым параметром. Этот
прямоугольник охватывает всю клиентскую область pictureBox1. Проверьте результат.
3. Прорисовка сплошных фигур производится методами типа Fill с использованием
различных кистей – объектов классов наследников абстрактного класса Brush (нельзя
создать объект класса Brush!). Изображение контуров фигур делается с помощью методов
типа Draw с использованием объектов класса карандаша Pen. Поэтому, для изображения
горизонтальной прямой посередине нашего контейнера можно написать строку кода:
e.Graphics.DrawLine(new Pen(Color.White, 3), new Point(0, pictureBox1.ClientSize.Height / 2),
new Point(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height / 2));
Этот код создает экземпляр карандаша белого цвета шириной в 3 пикселя, затем создает
две точки – в середине левой границы контейнера и в середине его правой границы. Эти
три параметра подставляются в метод, который рисует прямую. Проверьте результат.
4. Если попытаетесь изменить размеры окна, расширяя его и одновременно удлиняя, то
увидите искажения в изображении прямой линии, - изменение размеров не обязательно
приводит к полной перерисовке изображения. Для устранения дефекта следует добавить
обработчик события Resize нашего контейнера pictureBox1, набрав в нем код
pictureBox1.Refresh(), который перерисовывает полностью содержание контейнера,
проходя в том числе и через команды, записанные выше внутри обработчика события
Paint.
5. Ширина линии, которая задана выше значением 3, будет выглядеть лучше в виде
"говорящей" константы lineWidth, описанной внутри класса fParticle1D. Наберите строку
кода const int lineWidth = 3 сразу вслед за открывающей скобкой { описания класса
fParticle1D. Число 3 в методе DrawLine замените lineWidth. Такой вид кода читается легче,
да и значение постоянной легче изменить, если она описана в начале класса, а не где-либо
внутри кода. Переменные, зависящие от размеров контейнера pictureBox1, также проще
обозначить отдельными идентификаторами int pbWidth – ширина, linePos – половина
высоты или положение прямой. Опишите эти переменные в качестве полей формы на
строках, следующих за описанием lineWidth. Значение новых полей могут меняться только
в обработчике события Resize. Туда поместите строки (перед вызовом Refresh)
pbWidth = pictureBox1.ClientSize.Width;
3
6.
7.
8.
9.
linePos = pictureBox1.ClientSize.Height / 2;
В коде вызова DrawLine теперь можно заменить соответствующие выражения их более
короткими обозначениями. Новая версия кода должна работать и при изменении
размеров изображения. Проверьте
Изобразите частицу в форме окружности, находящейся в центре контейнера с радиусом
pRadius, который опишите постоянной типа int со значением, например, 10. С этой целью
достаточно вызвать метод FillEllipse в виде
e.Graphics.FillEllipse(new SolidBrush(Color.Yellow), new Rectangle(new Point(pbWidth / 2 –
pRadius, linePos - pRadius), new Size(2 * pRadius, 2 * pRadius))); Здесь используется кисть
класса SolidBrush (сплошная кисть) желтого цвета, а эллипс вписан в прямоугольник
(вернее, квадрат) с левой верхней вершиной в точке, отстоящей на pRadius влево и вверх
от центра контейнера, и размером с удвоенный радиус частицы.
Так как радиус частицы является const, то размеры прямоугольника, т.е. объект структуры
Size с шириной и высотой 2 * pRadius, является постоянной величиной с точки зрения кода.
Однако добавление константы типа Size в форму fParticle1D не допускает синтаксис языка.
Дело в том, что значения постоянных (const) записывается в память компьютера еще на
стадии компиляции. Но на этой стадии нельзя создать экземпляр структуры, как того
требовал бы код:
const Size pSize = new Size(2 * pRadius, 2 * pRadius);
В то же время, хотелось бы использовать значение pSize при инициализации другой
переменной – прямоугольника частицы, объявив этот прямоугольник в виде поля формы
fParticle1D:
Rectangle pRect = new Rectangle(new Point(), pSize);
Выход в том, чтобы описать pSize как переменную с модификатором static. Такие
переменные инициализируются на стадии выполнения, но до инициализации объекта
формы, т.е. до инициализации pRect. Поэтому замените const на static, и в FillEllipse
поставьте вызов pSize. Испытайте новую версию кода.
Что касается нового поля pRect, заданного выше, то его полное задание, включающее
конкретное положение, а не только размер, следует проводить в обработчике события
Resize, т.к. положение (свойство Location) прямоугольника частицы зависит от размеров
pictureBox1. Добавьте строку
pRect.Location = new Point(pbWidth / 2 – pRadius, linePos - pRadius);
внутри этого обработчика перед вызовом Refresh(),но, конечно, после переопределения
pbWidth и linePos. После этой редакции можно весь параметр прямоугольника в вызове
DrawEllipse заменить на pRect. Проверьте код.
Изображение частицы получилось в виде плоского желтого диска. Однако ему можно
придать объемный вид, не используя методы 3D-графики. Для этого следует использовать
не сплошную кисть, а градиентную. Существует класс PathGradientBrush, объекты которого
могут создавать градиентный цвет. Для создания объектов этого класса требуется указать
объект другого класса GraphicsPath.
Полностью фрагмент кода, которым можно заменить прежний код с вызовом FillEllipse,
выглядит следующим образом:
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddEllipse(pRect);
using (PathGradientBrush pBr = new PathGradientBrush(gp))
{
pBr.CenterColor = Color.Yellow;
pBr.SurroundColors = new Color[] { Color.Black };
e.Graphics.FillPath(pBr, gp);
}
}
4
10.
11.
12.
13.
В этом коде вначале создается объект gp класса GraphicsPath. В его коллекцию методом
Add добавляется единственная фигура – эллипс, изображающий частицу. Затем, используя
объект gp, создается объект pBr класса PathGradientBrush. Градиент реализуется от
центрального желтого цвета (свойство CenterColor), до периферийных цветов – массива
цветов SurroundColors, в данном случае состоящего из одного элемента – черного цвета.
Используя кисть pBr, методом FillPath заполняется объект gp. Испытайте новую версию
кода.
Перейдем к построению кода, симулирующего физическое перемещение частицы по
прямой. Для этого нам потребуется ряд новых полей, описывающих положение, время и
скорость частицы в физических единицах. Все эти величины будут иметь тип double.
Пусть положение определяется значением x, время t, скорость v (по умолчанию 1),
положения левой и правой границ отрезка прямой, отвечающим границам изображенной
прямой, left (пусть -1), и right (пусть 1), соответственно. Кроме того введем поле dt, по
умолчанию равное .005 и определяющее "бесконечно малый" интервал физического
времени между кадрами.
Текущее положение прямоугольника частицы теперь будет зависеть от x по формуле
pRect.Location = new Point((int)Math.Round(pbWidth * (x – left) / (right – left)) – pRadius,
linePos - pRadius); Отметим, что функция Round возвращает округленное до ближайшего
целого значение аргумента, но тип остается double, поэтому преобразование к типу int
необходимо.
Теперь положение прямоугольника зависит не только от размеров контейнера, но и от
текущей физической координаты. Поэтому оператор определения pRect.Location следует
выделить в отдельный метод. Назовем его RelocateParticle и запишем в виде
void RelocateParticle()
{
pRect.Location = new Point((int)Math.Round(pbWidth * (x - left) / (right - left)) - pRadius,
linePos - pRadius);
}
В обработчике события Resize контейнера теперь следует вызывать именно этот метод.
Для анимации напишем отдельный метод DoStep, который меняет положение частицы и
перерисовывает изображение
void DoStep()
{
t += dt;
x += v * dt;
if (x > right || x < left)
v = -v;
RelocateParticle();
pictureBox1.Refresh();
}
Скорость частицы меняет знак при попытке пересечь границы физического пространства.
Этот метод следует вызывать на каждом шаге изображаемого движения. Поэтому нужен
соответствующий цикл. Его можно организовать с помощью объекта класса Timer.
Поставьте этот объект из Toolbox (раздел Components) на форму. Свойство Interval
таймера поставьте в значение 10 (это интервал времени в миллисекундах, через который
будет наступать событие Tick таймера).
Выберите обработчик события Tick таймера и в этом обработчике вызовите DoStep().
Пока таймер не активирован (по умолчанию его свойство Enabled равно false) никаких
изменений происходить не будет. Поэтому необходимо ввести какое-нибудь событие,
активирующее/дезактивирующее таймер. Это может быть просто событие Click на
контейнере pictureBox1. Добавьте этот обработчик и напишите в нем оператор
5
timer1.Enabled = !timer1.Enabled. Запустите приложение и проверьте его работу, кликая по
поверхности контейнера.
14. Для включения/выключения движения лучше использовать графический элемент –
кнопку. Для этого из Tollbox (раздел Menus & Toolbars) поместите на левую панель
контейнера объект класса ToolStrip. Этот компонент является коллекцией управляющих
элементов. Выберите из коллекции кнопку. Замените в ее свойстве DisplayStyle значение
Image на Text, а свойство Text установите в Start. Назовите кнопку startButton. Выберите
событие клика этой кнопки и поместите в обработчик этого события переключение
активности таймера timer1.Enabled = !timer1.Enabled. Уберите обработчик клика
контейнера pictureBox1 в окне свойств, а затем сотрите его в коде. Испытайте работу
новой версии. Клик по кнопке должен запускать и останавливать движение частицы.
15. Интервал времени между кадрами можно значительно уменьшить, если отказаться от
использования обработчика события Tick таймера. Можно ввести в классе fParticle1D
новое поле логического типа bool run; и метод move следующего содержания
void move()
{
while (run)
{
DoStep();
Application.DoEvents();
}
}
Здесь в цикле, пока значение run равно true вызывается метод DoStep(). В цикле есть
также вызов метода Application.DoEvents(), который просматривает очередь событий,
поступивших в приложение. Это необходимо, чтобы приложение откликалось на внешние
события. Теперь в обработчике клика кнопки startButton следует менять переключать
значение поля run, написав строку run = !run; вместо строки, переключающей активность
таймера. Вызывать метод move лучше из обработчика таймера, чем клика кнопки. Для
этого в обработчике клика по кнопке следует добавить оператор if (run) timer1.Enabled =
true; Он включит таймер. В обработчик события Tick таймера следует поместить код,
дезактивирующий таймер и вызывающий метод move. В обработчик клика по кнопке
можно добавить также оператор, меняющий текст на кнопке. Тогда оба эти обработчика
примут вид
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;// Дезактивируем таймер
move();// Включаем цикл
}
private void startButton_Click(object sender, EventArgs e)
{
run = !run;// Переключаем флаг движения
startButton.Text = run ? "Stop" : "Start";// Меняем надпись на кнопке
if (run) timer1.Enabled = true;// Активируем таймер для включения цикла движения
}
Проверьте последнюю версию кода.
Если во время движения нажать кнопку закрытия приложения, то приложение не
остановится. События закрытия формы приостановится из-за того, что флаг run остается
равным true и цикл в методе move продолжается. Поправить ситуацию можно. Добавьте
обработчик события FormClosed и наберите в нем оператор run = false. Теперь приложение
должно закрываться нормально.
16. Еще один способ ускорить смену изображения в анимации состоит в том, чтобы обновлять
не целиком все изображение, вызывая pictureBox1.Refresh() в методе DoStep(), а обновляя
6
только те области экрана, через которые проходит частица. Для этой цели используются
методы Invalidate и Update. Метод Invalidate с параметром pRect укажет, что обновлению
подлежит только область, где находится частица, а метод Update() вызывает собственно
обновление, т.е. событие Paint. После этого метод DoStep() примет вид
void DoStep()
{
// Метод Invalidate добавляет область, которую следует
// обновить при следующем событии Paint
pictureBox1.Invalidate(pRect);// Убираем прежнее положение частицы
t += dt;// Меняем время
x += v * dt;// Меняем координату
if (x > right || x < left) // Отражение от границ
v = -v;
RelocateParticle();// Меняем положение прямоугольника частицы
pictureBox1.Invalidate(pRect);// Добавляем новое положение частицы
pictureBox1.Update();// Обновляем изображение, вызвав Paint
}
Испытайте новую версию кода, сравнив визуально скорость воспроизведения кадров
анимации.
Как можно реализовать произвольный выбор начальных условий движения – координаты
x и скорости v? Рассмотрим следующие решения. Для выбора начальной координаты x
можно обработать событие MouseClick на контейнере pictureBox1 так, чтобы при клике в
области прямой частица принимала соответствующую координату x. Выберите это событие
и введите туда следующий код
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
// Код обработчика выполняется только для покоящегося состояния и
// при клике в области прямой. Туда помещается частица
if (run || e.Button != MouseButtons.Left || e.Y < linePos - 3 || e.Y > linePos + 3)
return;
// Обновляется поле координаты частицы
x = left + e.X * (right - left) / pbWidth;
t = 0;// Обнуляется время
RelocateParticle();
pictureBox1.Refresh();
}
Проверьте работу новой версии.
17. Для задания значения начальной скорости v можно использовать, например, контекстное
меню. С этой целью из Toolbox (раздел Menus & Toolbars) перенесите на форму объект
класса ContextMenuStrip. Образуется объект contextMenuStrip1. В окне свойств контейнера
pictureBox1 найдите свойство ContextMenuStrip и выберите contextMenuStrip1. Тогда к
pictureBox1 будет подключено это меню. В свойствах самого меню contextMenuStrip1
найдите свойство Items (элементы меню). Открыв окно элементов, выберите тип элемента
TextBox и добавьте к элементам. Назовите этот элемент vBox. Через него будет
редактироваться значение начальной скорости.
Потребуем, чтобы контекстное меню открывалось только, если курсор попадает в
прямоугольник частицы и только в том случае, если частица покоится. Для этого создайте
обработчик события Opening контекстного меню и поместите в него следующий код
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
// Условие появления контекстного меню:
// частица покоится, и курсор находится внутри прямоугольника частицы
7
e.Cancel = run ||
!pRect.IntersectsWith(new Rectangle(pictureBox1.PointToClient(Cursor.Position),
new Size(1, 1)));
// Вносится текущая скорость
if (!e.Cancel)
vBox.Text = v.ToString("f");
}
Обратите внимание на метод PointToClient, который позволяет преобразовать координаты
точки, где находится курсор, определенные в глобальных координатах экрана, к
локальным координатам pictureBox1. Метод прямоугольника IntersectsWith возвращает
true, если прямоугольник вблизи курсора имеет общие точки с прямоугольником частицы.
Проверьте работу приложения. Меню должно открываться только при клике в области
положения частицы и в нем должно появляться текущее значение скорости v.
После редактирования скорости в vBox необходимо обработать событие, при котором
величина из vBox изменяет значение скорости v. Для этого можно использовать событие
MouseLeave компоненты vBox, которое наступает при выходе курсора мышки из области
vBox, набрав в его обработчике следующий код
private void vBox_MouseLeave(object sender, EventArgs e)
{
contextMenuStrip1.Close();// Закрываем контекстное меню
v = Double.Parse(vBox.Text);// Сохраняем новое значение скорости
t = 0;// Обнуляем время
pictureBox1.Refresh();// Обновляем изображение
}
Проверьте работу приложения в этой редакции.
Оператор pictureBox1.Refresh() является лишним в этом контексте, т.к. изображение не
меняется при изменении скорости. Но можно качественно визуализировать скорость
частицы, используя смешивание цветов. Пусть когда скорость частицы равна нулю
изображение частицы остается прежним, но при больших скоростях ее изображение
становится как бы более прозрачным. Эффект прозрачности достигается в графике
смешиванием цвета объекта и цвета фона в заданной пропорции. Величина пропорции
определяется так называемой альфа-компонентой цвета. Эта компонента может иметь
значения, лежащие в интервале [0; 255]. При максимальном значении объект полностью
непрозрачен. Это так по умолчанию. При значении 0 объект исчезает полностью. Можно
поставить значение альфа-компоненты цвета в зависимость от величины скорости. С этой
целью изменим раздел прорисовки частицы в обработчике pictureBox1_Paint
// Параметр смешивания цветов зависит от скорости
int alpha = (int)(255 / (1 + Math.Abs(v) / 3));
if (alpha < 120) alpha = 120; // Ограничим возможные значения alpha снизу
// Определяются текущие цвета
Color curCenterColor = Color.FromArgb(alpha, Color.Yellow);
Color[] curSurColors = { Color.FromArgb(alpha, Color.Black) };
// Создается объект класса графического контура для формирования градиентной кисти
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddEllipse(pRect);// К объекту добавляется эллипс частицы
// Создается градиентная кисть для заданного графического контура
using (PathGradientBrush pBr = new PathGradientBrush(gp))
{
pBr.CenterColor = curCenterColor;// Определяется цвет центра частицы
pBr.SurroundColors = curSurColors;// Определяется цвет периметра частицы
e.Graphics.FillPath(pBr, gp);// Формируется изображение частицы
8
}
}
Обратите внимание на метод FromArgb структуры Color, который смешивает цвета.
Посмотрите эффект, задавая разные значения скорости.
18. Кроме события MouseLeave объекта vBox, использованного выше, можно тот же код
поместить в обработчик события нажатия клавиши Enter, естественного после набора
нового значения скорости в vBox. Это событие (как и любое событие нажатия клавиши на
клавиатуре) называется KeyPress. Можно, конечно, использовать обработчик этого
события, введя его стандартным образом через окно свойств vBox. Но, т.к. туда надо будет
поместить практически тот же код, что и в обработчик MouseLeave, лучше сослаться на уже
имеющийся обработчик MouseLeave. Для этого в конструкторе формы fParticle1D следует
добавить оператор вида
// Подключаем обработчик нажатия клавиши
vBox.KeyPress += new KeyPressEventHandler(vBox_MouseLeave);
После этого надо немного отредактировать код обработчика MouseLeave, чтобы учесть,
что он может использоваться и при нажатии клавиши Enter. Добавим в начало следующий
оператор
// Код обработчика не выполняется,
// если это событие нажатия клавиши, которая не является Enter
if (e is KeyPressEventArgs && (e as KeyPressEventArgs).KeyChar != 13)// Код Enter 13
return;
Обратите внимание на использование операторов is и as. Почитайте о свойстве
контрвариантности делегатов, которое используется здесь для подключения обработчика
события MouseLeave в качестве обработчика события KeyPress, требующего делегата с
другой сигнатурой.
Проверьте новую версию кода в работе.
19. Еще одна возможность задавать начальную скорость – использовать обработку поворота
колесика мышки над частицей. Это событие называется MouseWheel. В окне свойств
pictureBox1 такого события нет. Объясняется это тем, что это событие возникает, если у
присутствующей мышки есть колесико. Поэтому обработчик события MouseWheel
подключим непосредственно в коде конструктора формы fParticle1D, добавив в
конструктор оператор вида:
if (SystemInformation.MouseWheelPresent)
// Подключаем обработчик колесика мышки
pictureBox1.MouseWheel += new MouseEventHandler(pictureBox1_MouseWheel);
Обработчик запишем в виде
void pictureBox1_MouseWheel(object sender, MouseEventArgs e)
{
// Код обработчика не выполняется, если система в состоянии движения
// либо если курсор не находится внутри прямоугольника частицы
if (run || !pRect.IntersectsWith(new Rectangle(e.Location, new Size(1, 1))))
return;
// Меняется скорость
v += (double)e.Delta / 1000;
pictureBox1.Refresh();// обновляется изображение
}
Для работы колесика необходимо еще, чтобы pictureBox1 воспринимал действие поворота
колесика. Поэтому необходимо при входе курсора в область pictureBox1 (событие
MouseEnter) вызвать метод Focus(). Добавьте к PictureBox1 обработчик этого события:
private void pictureBox1_MouseEnter(object sender, EventArgs e)
{
// Фокус устанавливается на контейнер. Это необходимо для работы колесика.
9
pictureBox1.Focus();
}
Проверьте работу кода.
20. В этой версии частица может двигаться только с неизменной по величине скоростью.
Можно добавить в программу немного физики, введя силу и потенциальную энергию.
Рассмотрим 4 случая – свободная частица, которая у нас уже есть, частица на пружинке
(осциллятор), на которую действует сила, равная -2 x, частица в среде, где сила трения
равна -2 v, и частица в переменном поле с силой 10 cos(t).
Введите новое поле целого типа problem, которое будет нумеровать эти задачи, и
добавьте метод f(), вычисляющий силу во всех перечисленных случаях:
double f()
{
switch (problem)
{
case 0: return 0;
case 1: return -2 * x;
case 2: return -2 * v;
case 3: return 10 * Math.Cos(t);
default: return Double.NaN;
}
}
Можно сразу добавить метод U(), вычисляющий потенциальную энергию в этих задачах:
double U()
{
switch (problem)
{
case 2:
case 0: return 0;
case 1: return x * x;
case 3: return -10 * x * Math.Cos(t);
default: return Double.NaN;
}
}
Обратите внимание на синтаксис оператора switch.
Надо добавить влияние силы на движение частицы. Это следует сделать в методе
DoStep(), где изменяется время, добавив в него оператор изменения скорости v +=f();
(считаем, что масса частицы равна 1).
Проверьте новую версию. Если Вы не инициализировали значения problem, то по
умолчанию оно равно 0 и ничего нового Вы не увидите. Но, если задать в описании
int problem = 1;, то частица будет двигаться по закону осциллятора и т.д.
21. Изменять выбор задачи можно, используя главное меню, которое следует разместить на
верхней панели контейнера toolStripContainer1. Найдите в Toolbox в разделе Menus &
Toolbars компоненту MenuStrip и перенесите ее на верхнюю панель. Наберите в качестве
заголовка слово "Задачи", а в выпадающих командах соответственно "свободная частица",
"осциллятор", "частица в среде", "частица в нестационарном поле". Дайте меню простое
имя sysMenuItem. Для активации команд меню можно в нашем случае использовать одно
событие для всех команд. В списке событий sysMenuItem найдите событие
DropDownItemClicked. Это событие наступает при выборе любой выпадающей команды.
Создайте обработчик этого события в виде:
private void sysMenuItem_DropDownItemClicked (object sender,
ToolStripItemClickedEventArgs e)
{
10
// Определяем индекс задачи
problem = sysMenuItem.DropDownItems.IndexOf(e.ClickedItem);
// меняем заголовок окна - имя новой задачи
Text = e.ClickedItem.Text;
// Обнуляем время
t = 0;
}
Обратите внимание, как определяется номер задачи в зависимости от выбранной
команды.
Для того, чтобы имя задачи появлялось при первом запуске, следует добавить в
конструктор формы fProblem1D оператор:
Text = sysMenuItem.DropDownItems[problem].Text;// Заголовок формы - название задачи
Имеет смысл также деактивировать меню задач после включения движения. Для этого в
обработчик клика кнопки startButton добавьте оператор:
sysMenuItem.Enabled = !run;// Активируем или деактивируем меню задач
Теперь, выбирая команду меню sysMenuItem, Вы можете менять задачу.
Проверьте работу новой версии кода.
22. Желательно следить за изменением состояния частицы в форме чисел. Для этого на
нижнюю панель контейнера toolStripConainer1 можно добавить компоненту класса
StatusStrip из Toolbox (раздел Menus & Toolbars). Выбрать в statusStrip1 объект класса
StatusLabel, дать ему имя stLabel и стереть свойство Text. Теперь в текст этого ярлыка
можно помещать текущую информацию о состоянии движения. Для этого добавьте к
форме метод:
void RestoreStatus()
{
stLabel.Text = "time = " + t.ToString("f") + " x = " + x.ToString("f") +
" v = " + v.ToString("f") + " Energy = " + (.5 * v * v + U()).ToString("f");
}
Внесите вызов этого метода везде, где меняется состояние частицы: в конструктор формы
для инициализации, в обработчики, где задаются начальные значения координаты и
скорости, в метод DoStep, в обработчик выбора задачи.
Проверьте работу новой версии.
23. Наконец, удобно изображать закон движения – функции x(t), v(t) и зависимость полной
энергии от времени на графиках. Для этого нужна компонента, изображающая графики. Я
использую бесплатную версию компоненты TeeChart (Net2Lite version) фирмы Steema
Software. Скачайте ее отсюда, установите на свой компьютер. После этого добавьте ее в
список окна Toolbox, для чего
a. В меню Tools выберите команду Choose Toolbox Items
b. Выберите закладку Browse и найдите установленную библиотеку TeeChart.Lite.dll
по адресу (если устанавливали по умолчанию)
C:\Program Files\Steema Software\TeeChart.Lite v2 for .NET\Framework_v2.0
c. Добавьте библиотеку к элементам Toolbox.
24. Потренируйтесь с редактированием и работой с TeeChart в отдельном приложении, а
затем подключите к нашему приложению.
Будут вопросы – пишите.
11
Download