Рисовалка на WPF
Пустое окно
Введение в Visual Studio 2010:
1) Main Menu после старта VS 2010: Tools → Options → открыть : Projects and Solutions → General Projects location: → C:\temp
2) Main Menu VS 2010: File → New Project... → Visual C# → Windows → Empty Project : Name: draw1 → Location: C:\temp → Create directory for solution: отключить → OK.
3) В окне с наименованием: Solution Explorer -Solution 'draw1' (1 project) мы должны добавить 4 References и draw1.cs файл: | Solution Explorer: |
4) Visual Studio 2010 Main menu → Project → draw1 Properties... → Application → Output type: Изменить с Console Application на Windows Application.
5) Напишите следующие строки кода draw1.cs:
public class window1 : System.Windows.Window
{
[System.STAThread] static void Main() {
new System.Windows.Application().Run( new window1() );
}
}
7) Клик Debug в Main Menu, в подменю Клик Start Debugging F5.
Простейшая программа теперь автоматически компилится, линкуется и стартует. Проконтролируйте окно Error List- в нижней части Visual Studio.
Наша программа стартует автоматически, как отдельное окно, состоящее из трех частей:
- Главное окно с заголовком;
- три кнопки Minimize, Maximize, Close
- узкая рамка с четырьмя подвижными границами и углами.
Закройте окно и убедитесь, что все инстанции draw1.exe были завершены.
Для контроля запустите Task Manager через комбинацию Ctrl+Alt+Del и убедитесь, что draw1.exe-процесс в списке отсутствует. Если нет, нажмите кнопку - Завершить Процесс.
Минимальная рисовалка
Удалите все содержимое draw1.cs. Никакого кода не остается.
Запишите следующие строки кода в draw1.cs:
using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Shapes; using System.Windows.Input; public class window1 : Window { [STAThread] static void Main() { new Application().Run( new window1() ); } Canvas canvas = new Canvas(); TextBox textBox = new TextBox(); Polyline p = new Polyline(); Point p0 = new Point(); Point p1 = new Point(); bool pressed = false; public window1() //constructor { this.Width = this.Height = 500; this.Title = "draw1"; Content = canvas; canvas.Children.Add( p ); canvas.Children.Add( textBox ); p.Stroke = Brushes.Black; p.StrokeThickness = 2; p.Points = new PointCollection(); textBox.Text = "Press the left mouse button and move!"; } protected override void OnMouseDown( MouseButtonEventArgs args ) { p.Points.Clear(); //erase everything p0 = args.GetPosition( canvas ); //get mouse position p.Points.Add( p0 ); //store mouse position pressed = true; } protected override void OnMouseMove( MouseEventArgs args ) { if ( !pressed ) return; p1 = args.GetPosition( canvas ); //get mouse position p.Points.Add( p1 ); //store mouse position p0 = p1; //old end is new start } protected override void OnMouseUp( MouseButtonEventArgs args ) { p.Points.Add ( p.Points[0] ); //close polygon pressed = false; } }
Клик Debug в Main Menu, в подменю Клик Start Debugging F5.
Если нет ошибок, то программа снова автоматически компилится, линкуется и стартует.
Упражнения:
1) Попробуйте другие цвета в p.Stroke = Brushes.Black;.
2) Измените параметр толщины линии p.StrokeThickness = 2;.
3) Поменяйте значение textbox.Text = "Press the left mouse button and move!";.
4) Попробуйте другие стартовые размеры окна this.Width = this.Height = 500;.
5) Закоментарьте строку // in front of p.Points.Add ( p.Points[0] ); //close polygon.
6) Потяните за края окна, чтобы поменять его размеры.
Совет: в случае ошибки синтаксиса или организации кода, компилятор выдает сообщение об ошибке Message Box: There were build errors. .... Закройте это окно кнопкой No. В нижней части окна Visual Studio появится окно Error List со списком предупреждений или ошибок. В этом списке переходите к самой первой ошибке (игнорируя warnings). Двойной клик на ошибку переведет текстовой курсор на место (строку) найденной ошибки. Найдите и устраните ошибку в этой линии. Иногда ошибка указывается на линии, где все в порядке, и проблема находится выше - если например вы забыли закрыть скобку или поставить точку с запятой в предыдущей строке. Игнорируйте все ошибки после первой, т.к. часто последующие ошибки возникают в следствии первой. Повторяйте процедуру исправления ошибок и компиляции, пока программа не запустится без сообщений об ошибках.
Отображение координат
Версия 2: Закройте draw1.
Допишите в конструктор: public window1() под строкой textbox.Text = "Press the left mouse button and move!";
textBox.FontFamily = new FontFamily( "Courier New" );
textBox.FontSize = 12;
canvas.Background = new LinearGradientBrush( Colors.Red, Colors.Blue, 90 );
Напишите дополнительные строки в обоих оброботчиках событий:
в ... OnMouseDown( ... ) под строкой p.Points.Add( p0 ); и
в ... OnMouseMove( ... ) под строкой p.Points.Add( p1 );:
textBox.Text = p0.X.ToString() + '/' + p0.Y.ToString();
Упражнение:
Попробуйте другие углы градиентной заливки вместо 90 в Background = new LinearGradientBrush( Colors.Red, Colors.Blue, 90 );.
Отображение Вертексов
Версия3: Закройте draw1.
Отметим каждую зафиксированную координату маленьким кружком с диаметром 5, написав новую функцию void vertexCircle( Point p ) и вызвав ее в обработчиках событий
... OnMouseDown( ... ) и
... OnMouseMove( ... )
Replace all three event handlers by:
protected override void OnMouseDown( MouseButtonEventArgs args )
{
canvas.Children.Clear(); //erase everything from the canvas
canvas.Children.Add( p ); //except the polygon
canvas.Children.Add( textBox ); //and the textbox
p.Points.Clear(); //erase everything from the polygon
p0 = args.GetPosition( canvas ); //get mouse position
p.Points.Add( p0 ); //store mouse position
textBox.Text = p0.X.ToString() + '/' + p0.Y.ToString();
vertexCircle( p0 ); //mark this vertex
pressed = true;
}
protected override void OnMouseMove( MouseEventArgs args )
{
if ( !pressed ) return;
p1 = args.GetPosition( canvas ); //get mouse position
p.Points.Add( p1 ); //store mouse position
textBox.Text = p0.X.ToString() + '/' + p0.Y.ToString();
vertexCircle( p1 ); //mark this vertex
p0 = p1; //old end is new start
}
protected override void OnMouseUp( MouseButtonEventArgs args )
{
p.Points.Add ( p.Points[0] ); //close polygon
pressed = false;
}
void vertexCircle( Point p ) //vertex marker function
{
Ellipse elli = new Ellipse();
elli.Width = elli.Height = 5; //diameter
elli.Stroke = Brushes.Black;
canvas.Children.Add( elli ); //add it to the canvas
Canvas.SetLeft( elli, p.X - 2 ); //x-position on the canvas
Canvas.SetTop ( elli, p.Y - 2 ); //y-position on the canvas
}
Клик Debug в Main Menu, в подменю Клик Start Debugging F5.
Упражнения:
1) Попробуйте другой vertexCircle диаметр в elli.Width = elli.Height = 5;.
2) Попробуйте другие цвета elli.Stroke = Brushes.Black;.
Порисуйте быстрее и медленнее, заметьте, что плотность Вертексов зависит от скорости рисования. Медленные компьютеры производят меньше Вертексов, чем быстрые, т.к. их операционная система обрабатывает меньше сообщений WM_MouseMove посылаемых в класс window1 , активирующий обработчик событий protected override void OnMouseMove( MouseEventArgs args ).
Минимальная дистанция между Вертексами
Версия4: Закройте draw1.
Добавьте три дополнительные линии в protected override void OnMouseMove( MouseEventArgs args ) под строкой p1 = args.GetPosition( canvas );:
double dx = p1.X - p0.X;
double dy = p1.Y - p0.Y;
if ( dx*dx + dy*dy < 400 ) return;
Клик Debug в Main Menu, в подменю Клик Start Debugging F5.
Perimeter, area, center of gravity, bounding box
Версия 5: Закройте draw1.
Write an additional declaration into the head of public class window1 : Window below the line Point p1 = new Point();:
Point mid_of_polygon = new Point();
Поменяйте protected override void OnMouseUp( MouseEventArgs args ) до такого вида:
protected override void OnMouseUp( MouseButtonEventArgs args )
{ p.Points.Add ( p.Points[0] ); //closed polygon
mid_of_polygon = new Point( 0, 0 );
Point mid_of_minmax = new Point( 0, 0 );
Rectangle minmax_rectangle = new Rectangle();
Rectangle mid_of_minmax_rectangle = new Rectangle();
Ellipse mid_of_polygon_circle = new Ellipse ();
Double perimeter = 0, area = 0;
Double xmin, xmax, ymin, ymax;
xmin = xmax = p0.X = p.Points[0].X;
ymin = ymax = p0.Y = p.Points[0].Y;
for ( int i=1; i < p.Points.Count; i++ )
{
p1 = p.Points[i];
Double dx = p1.X - p0.X;
Double dy = p1.Y - p0.Y;
Double my = (p0.Y + p1.Y) / 2.0;
perimeter += Math.Sqrt( dx*dx + dy*dy ); //Pythagoras
area += dx * my; //Trapezoid formula
if ( p1.X < xmin ) xmin = p1.X; //shift the left wall to the left
if ( p1.X > xmax ) xmax = p1.X; //shift the right wall to the right
if ( p1.Y < ymin ) ymin = p1.Y; //shift the upper wall upward
if ( p1.Y > ymax ) ymax = p1.Y; //shift the lower wall downward
mid_of_polygon.X += p1.X; //sum up all x-coordinates
mid_of_polygon.Y += p1.Y; //sum up all x-coordinates
p0 = p1; //set the new start to the former end
}
mid_of_minmax.X = ( xmax + xmin ) / 2; //mid between left and right border
mid_of_minmax.Y = ( ymax + ymin ) / 2; //mid between upper and lower border
mid_of_polygon.X /= p.Points.Count-1; //mean of all x-coordinates
mid_of_polygon.Y /= p.Points.Count-1; //mean of all y-coordinates
mid_of_minmax_rectangle .Width = mid_of_minmax_rectangle .Height = 5;
mid_of_polygon_circle.Width = mid_of_polygon_circle.Height = 5;
minmax_rectangle.Width = xmax - xmin + 2;
minmax_rectangle.Height = ymax - ymin + 2;
Canvas.SetLeft( minmax_rectangle , xmin );
Canvas.SetTop ( minmax_rectangle , ymin );
Canvas.SetLeft( mid_of_minmax_rectangle, mid_of_minmax .X - 2 );
Canvas.SetTop ( mid_of_minmax_rectangle, mid_of_minmax .Y - 2 );
Canvas.SetLeft( mid_of_polygon_circle , mid_of_polygon.X - 2 );
Canvas.SetTop ( mid_of_polygon_circle , mid_of_polygon.Y - 2 );
canvas.Children.Add( minmax_rectangle );
canvas.Children.Add( mid_of_minmax_rectangle );
canvas.Children.Add( mid_of_polygon_circle );
minmax_rectangle.Stroke = mid_of_minmax_rectangle.Stroke = mid_of_polygon_circle.Stroke =
Brushes.Black;
textBox.Text = String.Format( "Vertices = {0}\n" , p.Points.Count-1 );
textBox.Text += String.Format( "Perimeter = {0,2:F1}\n", perimeter );
textBox.Text += String.Format( "Area = {0,2:F1}" , area );
pressed = false;
}
Клик Debug в Main Menu, в подменю Клик Start Debugging F5.
Упражнение:
Попробуйте нарисовать квадрат 100x100 точек, на сколько сможете точно. Периметр должен получиться примерно 400 а Площадь - примерно 10000 или -10000.
Анимация
Версия 6: Закрой draw1.
Добавьте дополнительный компонент в заголовок draw1.cs под линией using System.Windows.Input;:
using System.Windows.Threading;
Запишите три дополнительные декларации в заголовок public class window1 : Window под линией Point mid_of_polygon = new Point();:
Button button = new Button(); int tickcount = 0; DispatcherTimer timer = new DispatcherTimer();
Напишите 4 дополнительные строки в конструктор public window1() под строкой FontSize = 12;:
button.Click += buttonOnClick; button.Content = "Click here to start the animation !"; timer.Interval = TimeSpan.FromMilliseconds( 1 ); timer.Tick += TimerOnTick;
Добавьте две новые строки в конец функции ... OnMouseUp( ... ) под строкой pressed = false;:
canvas.Children.Add( button ); OnRenderSizeChanged( null );
Напишите три новых обработчика событий под уже существующими, но перед последней фигурной скобкой:
protected override void OnRenderSizeChanged( SizeChangedInfo sizeInfo ) { Canvas.SetLeft( button, 0 ); Canvas.SetTop ( button, canvas.ActualHeight-20 ); button.Width = canvas.ActualWidth; } void buttonOnClick( Object sender, RoutedEventArgs rea ) { canvas.Children.Clear(); canvas.Children.Add( p ); timer.Start(); } void TimerOnTick( Object sender, EventArgs args ) { if ( tickcount < 360 ) tickcount++; else { timer.Stop(); tickcount = 0; return; } double arcus = 2*Math.PI / 360; double cosinus = Math.Cos( arcus ); double sinus = Math.Sin( arcus ); double zoom = 1.0; if ( tickcount < 90 || tickcount > 270 ) zoom = 0.99; else zoom = 1.01; for ( int i=0; i < p.Points.Count; i++ ) { Point pp = p.Points[i]; double x = zoom * ( pp.X - mid_of_polygon.X ); double y = zoom * ( pp.Y - mid_of_polygon.Y ); pp.X = x*cosinus - y*sinus + mid_of_polygon.X; pp.Y = x*sinus + y*cosinus + mid_of_polygon.Y; p.Points[i] = pp; } }
Клик Debug в Main Menu, в подменю Клик Start Debugging F5.
Пояснения:
1) Обработчик события ... OnRenderSizeChanged( ... ) создает кнопку внизу канвы и заботится о том, чтобы она была всегда видна при изменении размеров окна window1 .
2) Обработчик события void buttonOnClick( ... ) стирает все кроме Полигона и стартует анимацию.
3) Обработчик события void TimerOnTick( ... ) вращает Полигон от 0 до 360 градусов. Во время вращения Полигон уменьшается в диапазоне от 0° до 89°, растет от 90° до 270° и снова уменьшается от 271° до 360°. После этого вращения, Полигон возвращается в исходное состояние (размер и позицию).
Упражнения:
1) Попробуйте установить другие временные интервалы между кадрами - 50, 100 timer.Interval = TimeSpan.FromMilliseconds( 1 );.
2) Попробуйте поменять количество кадров if ( tickcount < 360 ) tickcount++;.
3) Попробуйте уменьшить или увеличить угловой шаг double arcus = 2*Math.PI / 360;.
4) Поменять коэффициенты масштабирования 0.95 и 1.05 и другие.
5) Поменяйте направление вращения, инвертировав знаки в обоих выражениях y*sinus and x*sinus.
Запись в XAML-Файл
Версия 7: Закройте draw1.
В окне : Solution Explorer -Solution 'draw1' нужно добавить еще один Reference:
Правый клик на References. В контекстном меню клик на Add Reference....
В окне Add Reference- найти и отметить System.XML . Закрыть окно Add Reference- кликнув OK.
Добавить компонент в заголовок draw1.cs под строкой using System.Windows.Threading;:
using System.Windows.Markup;
using System.IO;
Вставить две линии в конце обработчика ... OnMouseUp( ... ) под линией pressed = false;:
FileStream f = new FileStream( "C:\\temp\\draw1\\canvas.xaml", FileMode.Create,
FileAccess.Write );
XamlWriter.Save( canvas, f );
Испытайте программу, найдите записанный файл C:\temp\draw1\canvas.xaml.
Упражнения:
1) Двойной Клик на C:\temp\draw1\canvas.xaml. Internet Explorer откроет файл и покажет записанную Канву как Browser Object.
2) Запустите Notepad или Textpad и загрузите в него C:\temp\draw1\canvas.xaml. Вы увидите XAML-описание вашей Канвы и ее дочерних объектов. К сожалению все тэги последовательно одной строкой. Чтобы преобразовать этот текст в понятный структурированный формат, вы должны отредактировать текст, вставить переносы и табуляцию.
3) Измените x- и y-координаты последнего вертекса Полигона на 0, 0 и запишите XAML-файл. Загрузите файл в Internet Explorer.
Удачи! :)