Рисовалка на 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 файл:
3.1 Правый клик на References. В выпавшем контекстном меню выбираем Add Reference.... Появляется окно Add Reference. Находим в списке и удерживая Ctrl кликаем следующие компоненты: Presentation Core, Presentation Framework, System, System.Xaml  и WindowsBase. Жмем кнопку OK.
3.2 Правый клик на draw1. В контекстном меню кликнуть Add и выбрать New Item.... Появляется окно  Add New Item - draw1. Выбрать заготовку Visual C# Items -> Code -> Code File ,  Name: draw1.cs. Выйти из окна, нажав кнопку Add.

 Solution Explorer:

4) Visual Studio 2010 Main menu Projectdraw1 Properties...ApplicationOutput 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.

Удачи! :)