Основы программирования на C#

Финальный проект

Разбить на страницы
Показывать лекцию целиком

В этой заключительной лекции новый материал появляться не будет, не будет и традиционных вопросов в конце лекции. Лекция особенная - она посвящена описанию финального проекта, в котором объединены многие, надеюсь, уже хорошо знакомые элементы.

В финальном проекте создается семейство классов, описывающих геометрические фигуры. Проектирование начинается с абстрактного класса поведения, который описывает общие свойства и методы, характерные для всех фигур семейства. Затем, используя наследование, создаются классы конкретных геометрических фигур, начиная с простейших, таких, как круги и прямоугольники, до составных, таких, как класс Person. Мы добавим в наш проект динамические структуры данных, такие, как список с курсором, для хранения в нем фигур семейства. Наконец, мы создадим интерфейс, включающий меню с десятками команд и панель с инструментальными кнопками. Интерфейс позволяет конечному пользователю выполнять различные действия над геометрическими фигурами - создавать, рисовать их на форме, перемещать их с помощью команд и перетаскивать их мышью, менять их размеры и цвет, сохранять в списке и удалять из списка, отображать все фигуры списка или очистить его полностью.

Проект может служить образцом полноценного Windows-приложения, примером проектирования в классах с демонстрацией преимуществ, предоставляемых наследованием. Закончим на этом рекламную часть и приступим к делу. Хочу предупредить, вас ждут программные тексты, почти без всяких комментариев. Все нужные комментарии были даны в предыдущих лекциях. С моей точки зрения, наиболее интересная часть программистских книжек - это та, в которой приводится программный код. И значит, эта глава самая интересная.

Абстрактный класс Figure

Приведем код класса:

using System;
using System.Drawing;
namespace Shapes
{
/// <summary>
/// Figure - это абстрактный класс; прародитель семейства 
/// классов геометрических фигур. Все фигуры имеют:
/// центр - center, масштаб - scale. статус
/// перетаскивания - dragged center - объект встроенного
/// класса (структуры) Point. Этот объект задает характерную
/// точку фигуры - чаще всего ее центр (тяжести)
/// scale задает масштаб фигуры, первоначально единичный.
/// drugged = true, когда фигура следует за курсором мыши.
/// над фигурами определены операции: параллельный
/// перенос - Move(a,b) масштабирование - Scale(s)
/// Показ фигуры - Show. Область захвата - Region_Capture
/// возвращает прямоугольник, характерный для фигуры,
/// перетаскивание фигуры возможно при установке курсора 
/// мыши в области захвата.
/// </summary>
abstract public class Figure
{
	/// <summary>
	/// закрытые для клиентов атрибуты класса - center, scale 
	/// </summary>
	protected Point center;
	protected double scale ;
	protected bool dragged;
	protected Color color;
	//Доступ к свойствам 
	public Point center_figure
	{
		get {return(center);}
		set {center = value;}
	}
	public double scale_figure
	{
		get {return(scale);}
		set {scale = value;}
	}
	public bool dragged_figure
	{
		get {return(dragged);}
		set {dragged = value;}
	}
	public Color color_figure
	{
		get {return(color);}
		set {color = value;}
	}
	/// <summary>
	/// базовый конструктор фигур
	/// </summary>
	/// <param name="x">координата X характерной точки 
	///фигуры</param>
	/// <param name="y">Координата Y характерной точки 
	///фигуры</param>
	public Figure(int x, int y)
	{
		center = new Point(x,y);
		scale = 1;
		dragged = false;
		color = Color.ForestGreen;
	}
	/// <summary>
	/// отложенный метод 
	/// Параллельный перенос фигуры на (a,b)
	/// require : true;
	/// ensure : для любой точки фигуры p(x,y): 
	/// x = old(x) +a; y = old(y) + b;
	/// </summary>
	/// <param name="a">a - перемещение по горизонтали 
	///вправо </param>
	/// <param name="b">b - перемещение по вертикали 
	///вниз</param>
	/// Замечание: Для того, чтобы фигура при рисовании была  
	/// полностью видимой, координаты всех ее точек должны  
	/// быть в пределах области рисования.
	public void Move (int a,int b)
	{
		center.X +=a; center.Y += b;
	}
	/// <summary>
	/// изменяет масштаб фигуры
	/// </summary>
	/// <param name="s">масштаб изменяется в s раз</param>
	public void Scale(double s)
	{
		scale*=s;
	}		
	/// <summary>
	/// рисование фигуры в окне, передающем объекты g и pen
	/// </summary>
	/// <param name="g"> графический объект, методы которого 
	/// рисуют фигуру</param>
	/// <param name="pen">перо рисования</param>
	public abstract void Show(Graphics g, Pen pen, 
		Brush brush);
	public abstract System.Drawing.Rectangle Region_Capture();		
}

Абстрактный класс, относящийся к этапу проектирования системы, вместе с тем является важнейшим элементом заключительного семейства классов. В этом проявляется мощь объектно-ориентированного подхода к разработке программных систем. Заметьте, на данном уровне большая часть текста представляет документацию, являющуюся неотъемлемой частью программного проекта. Документация записана в тегах <summary>, что позволяет автоматически ее извлечь и сохранить в виде XML-отчета.

Классы семейства геометрических фигур

Приведем теперь программные коды классов, являющихся потомками класса Figure.

Класс Ellipse

Вот программный код этого класса:

using System;
using System.Drawing;
namespace Shapes
{
	/// <summary>
	/// Класс Ellipse - потомок класса Figure.
	/// </summary>
	public class Ellipse: Figure
	{
		int axisA,axisB;
		Rectangle rect;
		public Ellipse(int A, int B, int x, int y ): base(x,y)
		{
			axisA = A; axisB = B;
			rect =Init();
		}
		public override void Show(Graphics g, Pen pen, Brush brush)
		{			
			rect = Init();
			g.DrawEllipse(pen,rect);
			g.FillEllipse(brush, rect);
		}
		public override Rectangle Region_Capture()
		{
			rect = Init();
			return rect;
		}
		Rectangle Init()
		{
			int a  =Convert.ToInt32(axisA*scale);
			int b =Convert.ToInt32(axisB*scale);
			int leftupX = center.X - a;
			int leftupY = center.Y - b;
			return( new Rectangle(leftupX,leftupY,2*a,2*b));
		}
	}
}

Класс Circle

Этот класс является потомком класса Ellipse:

using System;
using System.Drawing;
namespace Shapes
{
	/// <summary>
	/// Класс Circle - потомок класса Ellipse.
	/// </summary>
	public class Circle: Ellipse
	{
		public Circle( int radius,int x, 
			int y):base(radius,radius,x,y)
		{
		//Круг - это эллипс с равными полуосями (радиусом круга)
		}
	}
}

Здесь опять-таки проявляется мощь наследования. Потомок наследует все свойства и методы родителя. Ему остается только указать собственный конструктор объектов класса, да и в нем главное состоит в вызове конструктора родительского класса с передачей ему нужных аргументов.

Класс LittleCircle

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

using System;
namespace Shapes
{
	/// <summary>
	/// Класс LittleCircle - потомок класса Circle.
	/// </summary>
	public class LittleCircle:Circle
	{
		public LittleCircle(int x,int y): base(4,x,y)
		{
			// маленький круг радиуса 4
		}
	}
}

Класс Rect

Этот класс является еще одним прямым потомком класса Figure:

using System;
using System.Drawing;
namespace Shapes
{
	/// <summary>
	/// Класс Rect - потомок класса Figure.
	/// </summary>
	public class Rect:Figure
	{
		int sideA, sideB;
		Rectangle rect;
		public Rect(int sideA,int sideB, int x, int y): base(x,y)
		{
			this.sideA = sideA; this.sideB = sideB;
			rect =Init();
		}
		public override void Show(Graphics g, Pen pen, Brush brush)
		{			
			rect = Init();
			g.DrawRectangle(pen,rect);
			g.FillRectangle(brush,rect);
		}
		public override Rectangle Region_Capture()
		{
			rect = Init();
			return rect;
		}
		Rectangle Init()
		{
			int a  =Convert.ToInt32(sideA*scale);
			int b =Convert.ToInt32(sideB*scale);
			int leftupX = center.X - a/2;
			int leftupY = center.Y - b/2;
			return( new Rectangle(leftupX,leftupY,a,b));
		}
	}
}

Класс Square

Квадрат - это частный случай прямоугольника. Соответствующий класс является потомком класса Rect:

using System;
namespace Shapes
{
	/// <summary>
	/// Класс Square - потомок класса Rect.
	/// </summary>
	public class Square:Rect
	{
		public Square(int side, int x, int y): base(side,side,x,y)
		{
			//квадрат - это прямоугольник с равными сторонами
		}
	}
}

Класс Person

Этот класс является прямым потомком класса Figure. Вместе с тем, класс является клиентом трех других классов семейства - Circle, Rect и LittleCircle, поскольку элементы фигуры, составляющие человечка, являются объектами этих классов.

namespace Shapes
{
	/// <summary>
	/// Класс Person - потомок класса Figure, 
	/// клиент классов Circle, Rect, LittleCircle.
	/// </summary>
	public class Person:Figure
	{
		int head_h;
		Circle head;
		Rect body;
		LittleCircle nose;
		public Person(int head_h, int x, int y): base(x,y)
		{
			//head_h - радиус головы, x,y - ее центр.
			//остальные размеры исчисляются относительно 
			//размера головы.
			this.head_h = head_h;
			head = new Circle(head_h,x,y);
			int body_x = x;
			int body_y = y + 3*head_h;
			int body_w =2*head_h;
			int body_h = 4*head_h;
			body = new Rect(body_w, body_h, body_x,body_y);
			nose = new LittleCircle(x+head_h +2, y);
		}
		public override void Show(System.Drawing.Graphics g, 
			System.Drawing.Pen pen, System.Drawing.Brush brush)
		{
			int h = Convert.ToInt32(head_h*scale);
			//head
			int top_x = center.X - h;
			int top_y = center.Y - h;
			g.DrawEllipse(pen, top_x,top_y, 2*h,2*h);
			g.FillEllipse(brush, top_x,top_y, 2*h,2*h);
			//body
			top_y += 2*h;
			g.DrawRectangle(pen, top_x,top_y, 2*h,4*h);
			g.FillRectangle(brush, top_x,top_y, 2*h,4*h);
			//nose
			top_y -=h;
			top_x += 2*h;
			g.DrawEllipse(pen, top_x,top_y, 8,8);
			g.FillEllipse(brush, top_x,top_y, 8,8);
		}
		public override System.Drawing.Rectangle 
			Region_Capture()
		{
			int h = Convert.ToInt32(head_h*scale);
			int top_x = center.X - h;
			int top_y = center.Y - h;
			return new 
				System.Drawing.Rectangle(top_x,top_y,2*h,2*h);
		}
	}
}

Список с курсором. Динамические структуры данных

Добавим в проект классы, задающие динамические структуры данных. Конечно, можно было бы воспользоваться стандартными... Но для обучения крайне полезно уметь создавать собственные классы, задающие такие структуры данных. Список с курсором - один из важнейших образцов подобных классов:

using System;
namespace Shapes
{
	/// <summary>
	/// Класс TwoWayList(G)  описывает двусвязный список с 
	/// курсором. Элементами списка являются объекты 
	/// TwoLinkable, хранящие, помимо указателей на двух 
	/// преемников, объекты типа G.Курсор будет определять 	
	/// текущий (активный) элемент списка. Класс будет
	/// определять симметричные операции по отношению к 
	/// курсору.
	/// Конструкторы: 
	/// Конструктор без параметров будет создавать пустой 
	/// список
	/// Запросы:
	/// empty: require: true; возвращает true для пустого списка
	/// item: require: not empty(); возвращает активный элемент типа G; 
	/// require: true; возвращает число элементов списка; 
	/// count: count in[0,n] (count == 0) eqviv empty(); 
	/// index: require: not empty(); возвращает индекс активного элемента.
	/// search_res: require: true; возвращает true, если последний поиск был успешным.
	/// Команды:
	/// put_left(elem): require: true; 
	/// ensure: добавить новый элемент (elem) слева от курсора;
	/// put_right(elem): require: true; 
	/// ensure: добавить новый элемент (elem) справа от курсора;
	/// remove: require: not empty(); 
	/// ensure: удалить активный элемент;
	/// особо обрабатывается удаление последнего и единственного элементов
	/// операции с курсором:
	/// start: require: true; 
	/// ensure: сделать активным первый элемент;
	/// finish: require: true; 
	/// ensure: сделать активным последний элемент; 
	/// go_prev: require: not (index = 1); 
	/// ensure: сделать активным предыдущий элемент;
	/// go_next: require: not (index = count); 
	/// ensure: сделать активным последующий элемент;
	/// go_i(i): require:  (i in [1, count]); 
	/// ensure: сделать активным элемент с индексом i;
	/// операции поиска:
	/// search_prev(elem): require: not (index = 1); 
	/// ensure: сделать активным первый элемент elem слева от курсора;
	/// Успех или неуспех поиска сохранять в булевской 
	/// переменной search_res
	/// search_next: require: not (index = count); 
	/// ensure: сделать активным первый элемент elem справа от курсора;
	/// успех или неуспех поиска сохранять в булевской переменной search_res
	/// </summary>
	public class TwoWayList
	{
		public TwoWayList()
		{
			first = cursor = last = null;
			count = index = 0;
			search_res = false;
		}//конструктор
		/// <summary>
		/// first, cursor, last - ссылки на первый,
		/// активный и последний элементы списка
		/// Запросы count, index search_res также 
		/// реализуются атрибутами.
		/// Запросы empty, item реализуются функциями 
		/// </summary>
		protected TwoLinkable first, cursor, last;
		protected int count, index;
		protected bool search_res;
		//доступ на чтение к закрытым свойствам;
		public int Count
		{
			get
			{
				return(count);
			}
		}
		public int Index
		{
			get
			{
				return(index);
			}
		}
		public bool Search_res
		{
			get
			{
				return(search_res);
			}
		}
		/// <summary>
		/// require: true; возвращает true для непустого списка
		/// </summary>
		/// <returns></returns>
		public bool empty()
		{
			return(first == null);
		}//empty
		/// <summary>
		/// require: not empty(); возвращает активный 
		/// элемент типа G;
		/// </summary>
		/// <returns></returns>
		public Figure item()
		{
			return(cursor.Item);
		}//item
		/// <summary>
		/// require: true; 
		/// ensure: добавить новый элемент (elem) слева 
		/// от курсора;
		/// </summary>
		/// <param name="elem">Тип Figure играет роль 
		/// родового типа G
		/// хранимого элемента elem</param>
		public void put_left(Figure elem)
		{
			TwoLinkable newitem = new TwoLinkable();
			newitem.Item = elem;
			newitem.Next = cursor;
			if (empty())	//список пуст
			{
				first = cursor = last = newitem;
				index =1; count = 1;
			}
			else 
			{
				if (index == 1) 
					first =newitem;
				else
					cursor.Prev.Next = newitem;
				newitem.Prev = cursor.Prev; cursor.Prev = newitem;
				count++; index++;
			}
		}//put_right
		/// <summary>
		/// require: true; 
		/// ensure: добавить новый элемент (elem) справа 
		/// от курсора;
		/// </summary>
		/// <param name="elem">Тип Figure играет роль 
		/// родового типа G
		/// хранимого элемента elem</param>
		public void put_right(Figure elem)
		{
			TwoLinkable newitem = new TwoLinkable();
			newitem.Item = elem;
			newitem.Prev = cursor;
			if (empty())	//список пуст
			{
				first = cursor = last = newitem;
				index =1; count = 1;
			}
			else
			{
				if (index == count) 
					last =newitem;
				else
					cursor.Next.Prev = newitem;
				
				newitem.Next = cursor.Next; cursor.Next = newitem;
				count++; 
			}
		}//put_right
		public void remove()
		{
			if(count == 1)
			{
				first = last = cursor = null;
				index=0;
			}
			else if(index==1)
			{
				first = cursor.Next;
				cursor.Prev = null;
				cursor = cursor.Next;
			}
			else if(index == count)
			{
				last = cursor.Prev;
				cursor.Next = null;
				cursor = cursor.Prev;
				index--;
			}
			else
			{
				cursor.Prev.Next = cursor.Next;
				cursor.Next.Prev = cursor.Prev;
				cursor = cursor.Next;
			}
			count--;
		}//remove
		/// операции с курсором:
		/// <summary>
		/// start: require: true; 
		/// ensure: сделать активным первый элемент;
		/// </summary>
		public void start()
		{
			cursor = first; index = 1;
		}//start
		/// <summary>
		/// finish: require: true; 
		/// ensure: сделать активным последний элемент; 
		/// </summary>
		public void finish()
		{
			cursor = last; index = count;
		}//finish
		/// <summary>
		/// go_prev: require: not (index = 1); 
		/// ensure: сделать активным предыдущий элемент;
		/// </summary>
		public void go_prev()
		{
			cursor = cursor.Prev; index--;
		}// go_prev
		/// <summary>
		/// go_next: require: not (index = count); 
		/// ensure: сделать активным последующий элемент;
		/// </summary>
		public void go_next()
		{
			cursor = cursor.Next; index++;
		}// go_next
		/// <summary>
		/// go_i(i): require:  (i in [1, count]); 
		/// ensure: сделать активным элемент с индексом i;
		/// </summary>
		/// <param name="i"></param>
		public void go_i(int i)
		{
			if(i >index)
				while (i>index)
				{
					cursor = cursor.Next; index++;
				}
			else if(i<index)
				while (i<index)
				{
					cursor = cursor.Prev; index--;
				}
		}// go_i
		/// операции поиска:
		/// <summary>
		/// search_prev(elem): require: not (index = 1); 
		/// ensure: сделать активным первый элемент elem 
		/// слева от курсора;
		/// </summary>
		/// <param name="elem">искомый элемент</param>
		public virtual void search_prev(Figure elem)
		{
			bool found = false;
			while (!found  (index !=1))
			{
				cursor = cursor.Prev; index--;
				found = (elem == item());
			}
			search_res = found;
		}// search_prev
	/// <summary>
	/// успех или неуспех поиска сохранять в булевской 
	/// переменной search_res
	/// search_next: require: not (index = count); 
	/// ensure: сделать активным первый элемент elem 
	/// справа от курсора;
	/// успех или неуспех поиска сохранять в булевской 
	/// переменной search_res
	/// </summary>
	/// <param name="elem"></param>
		public virtual void search_next(Figure elem)
		{
			bool found = false;
			while (!found  (index !=count))
			{
				cursor = cursor.Next; index++;
				found = (elem == item());
			}
			search_res = found;
		}//search_next	
	}
}

Заметьте, класс подробно документирован. Для методов класса указываются предусловия и постусловия. Обратите внимание, в соответствии с принципами контрактного программирования клиент класса, прежде чем вызвать метод, должен проверить выполнимость предусловия, что повышает корректность работы системы в целом. Именно так и будет реализован вызов этих методов в классе формы, где осуществляется работа со списком.

Классы элементов списка

Рассмотрим классы, описывающие элементы списков - элементы с одним и с двумя указателями:

using System;
namespace Shapes
{
	/// <summary>
	/// Класс Linkable(T)задает элементы списка,включающие:
	/// информационное поле типа T - item 
	/// ссылку на элемент типа Linkable - next
	/// Функции:
	/// конструктор new: -> Linkable
	/// запросы: 
	/// Get_Item: Linkable -> T
	/// Get_Next: Linkable -> Linkable 
	/// процедуры:
	/// Set_Item: Linkable*T -> Linkable
	/// Set_Next: Linkable*Linkable -> Linkable 
	/// Роль типа T играет Figure
	/// </summary>
	public class Linkable
	{
		public Linkable()
		{
			item =null; next = null;
		}
		/// <summary>
		/// закрытые атрибуты класса
		/// </summary>
		Figure item;
		Linkable next;
		/// <summary>
		/// процедуры свойства для доступа к полям класса
		/// </summary>
		public Figure Item{
		get{
			return(item);
			}
		set{
			item = value;
			}
		}
		public Linkable Next{
		get{
			return(next);
			}
		set{
			next = value;
			}
		}
	}//class Linkable
	/// <summary>
	/// Класс TwoLinkable задает элементы с двумя ссылками 
	/// </summary>
	public class TwoLinkable 
	{
		
		public TwoLinkable()
		{
			prev = next = null;
		}
		/// <summary>
		/// закрытые атрибуты класса
		/// </summary>
		TwoLinkable prev, next;
		Figure item;
		/// <summary>
		/// процедуры свойства для доступа к полям класса
		/// </summary>
		public Figure Item
		{
			get
			{
				return(item);
			}
			set
			{
				item = value;
			}
		}
		public TwoLinkable Next
		{
			get
			{
				return(next);
			}
			set
			{
				next = value;
			}
		}
		public TwoLinkable Prev
		{
			get
			{
				return(prev);
			}
			set
			{
				prev = value;
			}
		}
	}//class TwoLinkable
}

Организация интерфейса

Создадим теперь интерфейс, позволяющий конечному пользователю работать с объектами наших классов. Как всегда, интерфейс создавался вручную в режиме проектирования. На форме я создал меню с большим числом команд и инструментальную панель с 18 кнопками, команды которых повторяли основную команду меню. Описывать процесс создания интерфейса не буду - он подробно рассмотрен в предыдущей главе. Поскольку вся работа по созданию интерфейса транслируется в программный код формы, то просто приведу этот достаточно длинный текст почти без всяких купюр:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Shapes;

namespace Final
{
	/// <summary>
	/// Эта форма обеспечивает интерфейс для создания, 
	/// рисования, показа, перемещения, сохранения в списке 
	/// и выполнения других операций над объектами семейства
	/// геометрических фигур. Форма имеет меню и 
	/// инструментальные панели.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		//fields
		Graphics graphic;
		Brush brush, clearBrush;
		Pen pen, clearPen;
		Color color;
		Figure current;
		TwoWayList listFigure;
		private System.Windows.Forms.MainMenu mainMenu1;
		private System.Windows.Forms.ImageList imageList1;
		private System.Windows.Forms.ToolBar toolBar1;
		private System.Windows.Forms.MenuItem menuItem1;
// аналогичные определения для других элементов меню
		private System.Windows.Forms.MenuItem menuItem35;
		private System.Windows.Forms.ToolBarButton 
			toolBarButton1;
// аналогичные определения для других командных кнопок
		private System.Windows.Forms.ToolBarButton 
			toolBarButton18;
		private System.ComponentModel.IContainer components;
		public Form1()
		{
			InitializeComponent();
			InitFields();
		}
		void InitFields()
		{
			graphic = CreateGraphics();
			color = SystemColors.ControlText;
			brush = new SolidBrush(color);
			clearBrush = new SolidBrush(SystemColors.Control);
			pen = new Pen(color);
			clearPen = new Pen(SystemColors.Control);
			listFigure = new TwoWayList();
			current = new Person(20, 50, 50);
		}
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}
		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			// Код, инициализирующий компоненты и построенный
			// дизайнером, опущен
		}
		#endregion
		/// <summary>
		/// Точка входа в приложение - процедура Main, 
		/// запускающая форму
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}
		private void menuItem7_Click(object sender, 
			System.EventArgs e)
		{
			createEllipse();
		}
		void createEllipse()
		{
			//clear old figure
			if (current != null) current.Show(graphic, 
				clearPen, clearBrush);
			//create ellipse
			current = new Ellipse(50, 30, 180,180);
		}
		private void menuItem8_Click(object sender, 
			System.EventArgs e)
		{
			createCircle();
		}
		void createCircle()
		{
			//clear old figure
			if (current != null) current.Show(graphic, 
				clearPen, clearBrush);
			//create circle
			current = new Circle(30, 180,180);
		}
		private void menuItem9_Click(object sender, 
			System.EventArgs e)
		{
			createLittleCircle();
		}
		void createLittleCircle()
		{
			//clear old figure
			if (current != null) current.Show(graphic, 
				clearPen, clearBrush);
			//create littlecircle
			current = new LittleCircle(180,180);
		}
		private void menuItem10_Click(object sender, 
			System.EventArgs e)
		{
			createRectangle();
		}
		void createRectangle()
		{
			//clear old figure
			if (current != null) current.Show(graphic, 
				clearPen, clearBrush);
			//create rectangle
			current = new Rect(50, 30, 180,180);
		}
		private void menuItem11_Click(object sender, 
			System.EventArgs e)
		{
			createSquare();
		}
		void createSquare()
		{
			//clear old figure
			if (current != null) current.Show(graphic, 
				clearPen, clearBrush);
			//create square
			current = new Square(30, 180,180);
		}
		private void menuItem12_Click(object sender, 
			System.EventArgs e)
		{
			createPerson();
		}
		void createPerson()
		{
			//clear old figure
			if (current != null) current.Show(graphic, 
				clearPen, clearBrush);
			//create person
			current = new Person(20, 180,180);
		}
		private void menuItem13_Click(object sender, 
			System.EventArgs e)
		{
			showCurrent();
		}
		void showCurrent()
		{
			//Show current
			current.Show(graphic, pen, brush);
		}
		private void menuItem14_Click(object sender, 
			System.EventArgs e)
		{
			clearCurrent();
		}
		void clearCurrent()
		{
			//Clear current
			current.Show(graphic, clearPen, clearBrush);
		}
		private void menuItem17_Click(object sender, 
			System.EventArgs e)
		{
			incScale();
		}
		void incScale()
		{
			//Increase scale
			current.Show(graphic, clearPen, clearBrush);
			current.Scale(1.5);
			current.Show(graphic, pen, brush);
		}
		private void menuItem18_Click(object sender, 
			System.EventArgs e)
		{
			decScale();
		}
		void decScale()
		{
			//Decrease scale
			current.Show(graphic, clearPen, clearBrush);
			current.Scale(2.0/3);
			current.Show(graphic, pen, brush);
		}
		private void menuItem19_Click(object sender, 
			System.EventArgs e)
		{
			moveLeft();
		}
		void moveLeft()
		{
			//Move left
			current.Show(graphic, clearPen, clearBrush);
			current.Move(-20,0);
			current.Show(graphic, pen, brush);
		}
		private void menuItem20_Click(object sender, 
			System.EventArgs e)
		{
			moveRight();
		}
		void moveRight()
		{
			//Move right
			current.Show(graphic, clearPen, clearBrush);
			current.Move(20,0);
			current.Show(graphic, pen, brush);
		}
		private void menuItem21_Click(object sender, 
			System.EventArgs e)
		{
			moveTop();
		}
		void moveTop()
		{
			//Move top
			current.Show(graphic, clearPen, clearBrush);
			current.Move(0,-20);
			current.Show(graphic, pen, brush);
		}
		private void menuItem22_Click(object sender, 
			System.EventArgs e)
		{
			moveDown();
		}
		void moveDown()
		{
			//Move down
			current.Show(graphic, clearPen, clearBrush);
			current.Move(0, 20);
			current.Show(graphic, pen, brush);
		}
		private void menuItem23_Click(object sender, 
			System.EventArgs e)
		{
			//choose color
			ColorDialog dialog = new ColorDialog();
			if (dialog.ShowDialog() ==DialogResult.OK) 
				color =dialog.Color;
			pen = new Pen(color); brush = 
				new SolidBrush(color); 
		}
		private void menuItem24_Click(object sender, 
			System.EventArgs e)
		{
			//Red color
			color =Color.Red;
			pen = new Pen(color); brush = 
				new SolidBrush(color); 
		}
		private void menuItem25_Click(object sender, 
			System.EventArgs e)
		{
			//Green color
			color =Color.Green;
			pen = new Pen(color); brush = 
				new SolidBrush(color); 
		}
		private void menuItem26_Click(object sender, 
			System.EventArgs e)
		{
			//Blue color
			color =Color.Blue;
			pen = new Pen(color); brush = 
				new SolidBrush(color); 
		}
		private void menuItem27_Click(object sender, 
			System.EventArgs e)
		{
			//Black color
			color =Color.Black;
			pen = new Pen(color); brush = 
				new SolidBrush(color); 
		}
		private void menuItem28_Click(object sender, 
			System.EventArgs e)
		{
			//Gold color
			color =Color.Gold;
			pen = new Pen(color); brush = 
				new SolidBrush(color); 
		}
		private void menuItem29_Click(object sender, 
			System.EventArgs e)
		{
			//put_left: добавление фигуры в список
			listFigure.put_left(current);
		}
		private void menuItem30_Click(object sender, 
			System.EventArgs e)
		{
			//put_right: добавление фигуры в список
			listFigure.put_right(current);
		}
		private void menuItem31_Click(object sender, 
			System.EventArgs e)
		{
			//remove: удаление фигуры из списка
			if(!listFigure.empty())
				listFigure.remove();
		}
		private void menuItem32_Click(object sender, 
			System.EventArgs e)
		{
			goPrev();
		}
		void goPrev()
		{
			//go_prev: передвинуть курсор влево
			if(!(listFigure.Index == 1))
			{
				listFigure.go_prev();
				current = listFigure.item();
			}
		}
		private void menuItem33_Click(object sender, 
			System.EventArgs e)
		{
			goNext();
		}
		void goNext()
		{
			//go_next: передвинуть курсор вправо
			if( !(listFigure.Index == listFigure.Count))
			{
				listFigure.go_next();
				current = listFigure.item();
			}
		}
		private void menuItem34_Click(object sender, 
			System.EventArgs e)
		{
			//go_first
			listFigure.start();
			if(!listFigure.empty())
				current = listFigure.item();
		}
		private void menuItem35_Click(object sender, 
			System.EventArgs e)
		{
			//go_last
			listFigure.finish();
			if(!listFigure.empty())
				current = listFigure.item();
		}
		private void menuItem15_Click(object sender, 
			System.EventArgs e)
		{
			showList();
		}
		void showList()
		{
			//Show List
			listFigure.start();
			while(listFigure.Index <= listFigure.Count)
			{
				current = listFigure.item();
				current.Show(graphic,pen,brush);
				listFigure.go_next();
			}
			listFigure.finish();
		}
		private void menuItem16_Click(object sender, 
			System.EventArgs e)
		{
			clearList();
		}
		void clearList()
		{
			//Clear List
			listFigure.start();
			while(!listFigure.empty())
			{
				current = listFigure.item();
				current.Show(graphic,clearPen,clearBrush);
				listFigure.remove();
			}
		}
		private void Form1_MouseMove(object sender, 
			System.Windows.Forms.MouseEventArgs e)
		{
			if((current != null)  current.dragged_figure)
			{
				current.Show(graphic,clearPen,clearBrush);
				Point pt = new Point(e.X, e.Y);
				current.center_figure = pt;
				current.Show(graphic,pen,brush);
			}
		}
		private void Form1_MouseUp(object sender, 
			System.Windows.Forms.MouseEventArgs e)
		{
			current.dragged_figure = false;
		}
		private void Form1_MouseDown(object sender, 
			System.Windows.Forms.MouseEventArgs e)
		{
			Point mousePoint = new Point (e.X, e.Y);
			Rectangle figureRect = current.Region_Capture();
			if ((current != null)  
				(figureRect.Contains(mousePoint)))
				current.dragged_figure = true;
		}
		protected override void 
			OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			//show current figure
			current.Show(graphic, pen, brush);
		}
		private void toolBar1_ButtonClick(object sender, 
			System.Windows.Forms.ToolBarButtonClickEventArgs e)
		{
			int buttonNumber = 
				toolBar1.Buttons.IndexOf(e.Button); 
			switch (buttonNumber)
			{
				case 0:
					createEllipse(); break;
				case 1:
					createCircle(); break;
				case 2:
					createLittleCircle(); break;
				case 3:
					createRectangle(); break;
				case 4:
					createSquare(); break;
				case 5:
					createPerson();  break;
				case 6:
					showCurrent(); break;
				case 7:
					clearCurrent(); break;
				case 8:
					showList();  break;
				case 9:
					clearList();  break;
				case 10:
					incScale();  break;
				case 11:
					decScale();  break;
				case 12:
					moveLeft();  break;
				case 13:
					moveRight();  break;
				case 14:
					moveTop();  break;
				case 15:
					moveDown();  break;
				case 16:
					goNext();  break;
				case 17:
					goPrev();  break;
			}
		}
	}
}

Команд меню и кнопок в нашем интерфейсе много, поэтому много и обработчиков событий, что приводит к разбуханию кода. Но каждый из обработчиков событий довольно прост. Ограничусь кратким описанием главного меню:

  • команды пункта главного меню Create позволяют создавать геометрические фигуры разных классов;
  • команды пункта главного меню Show позволяют показать или стереть текущую фигуру или все фигуры, сохраняемые в списке;
  • две команды пункта Scale позволяют изменить масштаб фигуры (увеличить ее или уменьшить);
  • команды пункта Move позволяют перемещать текущую фигуру в четырех направлениях;
  • команды пункта Color позволяют либо задать цвет фигур в диалоговом окне, либо выбрать один из предопределенных цветов;
  • группа команд пункта List позволяет помещать текущую фигуру в список, перемещаться по списку и удалять из списка ту или иную фигуру;
  • командные кнопки инструментальной панели соответствуют наиболее важным командам меню;
  • реализована возможность перетаскивания фигур по экрану мышью.
  • В заключение взгляните, как выглядит форма в процессе работы с объектами:

    (рис 25.1) Финальный проект. Форма в процессе работы
    Страницы:

    В этой заключительной лекции новый материал появляться не будет, не будет и традиционных вопросов в конце лекции. Лекция особенная - она посвящена описанию финального проекта, в котором объединены многие, надеюсь, уже хорошо знакомые элементы.

    В финальном проекте создается семейство классов, описывающих геометрические фигуры. Проектирование начинается с абстрактного класса поведения, который описывает общие свойства и методы, характерные для всех фигур семейства. Затем, используя наследование, создаются классы конкретных геометрических фигур, начиная с простейших, таких, как круги и прямоугольники, до составных, таких, как класс Person. Мы добавим в наш проект динамические структуры данных, такие, как список с курсором, для хранения в нем фигур семейства. Наконец, мы создадим интерфейс, включающий меню с десятками команд и панель с инструментальными кнопками. Интерфейс позволяет конечному пользователю выполнять различные действия над геометрическими фигурами - создавать, рисовать их на форме, перемещать их с помощью команд и перетаскивать их мышью, менять их размеры и цвет, сохранять в списке и удалять из списка, отображать все фигуры списка или очистить его полностью.

    Проект может служить образцом полноценного Windows-приложения, примером проектирования в классах с демонстрацией преимуществ, предоставляемых наследованием. Закончим на этом рекламную часть и приступим к делу. Хочу предупредить, вас ждут программные тексты, почти без всяких комментариев. Все нужные комментарии были даны в предыдущих лекциях. С моей точки зрения, наиболее интересная часть программистских книжек - это та, в которой приводится программный код. И значит, эта глава самая интересная.

    Абстрактный класс Figure

    Приведем код класса:

    using System;
    using System.Drawing;
    namespace Shapes
    {
    /// <summary>
    /// Figure - это абстрактный класс; прародитель семейства 
    /// классов геометрических фигур. Все фигуры имеют:
    /// центр - center, масштаб - scale. статус
    /// перетаскивания - dragged center - объект встроенного
    /// класса (структуры) Point. Этот объект задает характерную
    /// точку фигуры - чаще всего ее центр (тяжести)
    /// scale задает масштаб фигуры, первоначально единичный.
    /// drugged = true, когда фигура следует за курсором мыши.
    /// над фигурами определены операции: параллельный
    /// перенос - Move(a,b) масштабирование - Scale(s)
    /// Показ фигуры - Show. Область захвата - Region_Capture
    /// возвращает прямоугольник, характерный для фигуры,
    /// перетаскивание фигуры возможно при установке курсора 
    /// мыши в области захвата.
    /// </summary>
    abstract public class Figure
    {
    	/// <summary>
    	/// закрытые для клиентов атрибуты класса - center, scale 
    	/// </summary>
    	protected Point center;
    	protected double scale ;
    	protected bool dragged;
    	protected Color color;
    	//Доступ к свойствам 
    	public Point center_figure
    	{
    		get {return(center);}
    		set {center = value;}
    	}
    	public double scale_figure
    	{
    		get {return(scale);}
    		set {scale = value;}
    	}
    	public bool dragged_figure
    	{
    		get {return(dragged);}
    		set {dragged = value;}
    	}
    	public Color color_figure
    	{
    		get {return(color);}
    		set {color = value;}
    	}
    	/// <summary>
    	/// базовый конструктор фигур
    	/// </summary>
    	/// <param name="x">координата X характерной точки 
    	///фигуры</param>
    	/// <param name="y">Координата Y характерной точки 
    	///фигуры</param>
    	public Figure(int x, int y)
    	{
    		center = new Point(x,y);
    		scale = 1;
    		dragged = false;
    		color = Color.ForestGreen;
    	}
    	/// <summary>
    	/// отложенный метод 
    	/// Параллельный перенос фигуры на (a,b)
    	/// require : true;
    	/// ensure : для любой точки фигуры p(x,y): 
    	/// x = old(x) +a; y = old(y) + b;
    	/// </summary>
    	/// <param name="a">a - перемещение по горизонтали 
    	///вправо </param>
    	/// <param name="b">b - перемещение по вертикали 
    	///вниз</param>
    	/// Замечание: Для того, чтобы фигура при рисовании была  
    	/// полностью видимой, координаты всех ее точек должны  
    	/// быть в пределах области рисования.
    	public void Move (int a,int b)
    	{
    		center.X +=a; center.Y += b;
    	}
    	/// <summary>
    	/// изменяет масштаб фигуры
    	/// </summary>
    	/// <param name="s">масштаб изменяется в s раз</param>
    	public void Scale(double s)
    	{
    		scale*=s;
    	}		
    	/// <summary>
    	/// рисование фигуры в окне, передающем объекты g и pen
    	/// </summary>
    	/// <param name="g"> графический объект, методы которого 
    	/// рисуют фигуру</param>
    	/// <param name="pen">перо рисования</param>
    	public abstract void Show(Graphics g, Pen pen, 
    		Brush brush);
    	public abstract System.Drawing.Rectangle Region_Capture();		
    }

    Абстрактный класс, относящийся к этапу проектирования системы, вместе с тем является важнейшим элементом заключительного семейства классов. В этом проявляется мощь объектно-ориентированного подхода к разработке программных систем. Заметьте, на данном уровне большая часть текста представляет документацию, являющуюся неотъемлемой частью программного проекта. Документация записана в тегах <summary>, что позволяет автоматически ее извлечь и сохранить в виде XML-отчета.

    Классы семейства геометрических фигур

    Приведем теперь программные коды классов, являющихся потомками класса Figure.

    Класс Ellipse

    Вот программный код этого класса:

    using System;
    using System.Drawing;
    namespace Shapes
    {
    	/// <summary>
    	/// Класс Ellipse - потомок класса Figure.
    	/// </summary>
    	public class Ellipse: Figure
    	{
    		int axisA,axisB;
    		Rectangle rect;
    		public Ellipse(int A, int B, int x, int y ): base(x,y)
    		{
    			axisA = A; axisB = B;
    			rect =Init();
    		}
    		public override void Show(Graphics g, Pen pen, Brush brush)
    		{			
    			rect = Init();
    			g.DrawEllipse(pen,rect);
    			g.FillEllipse(brush, rect);
    		}
    		public override Rectangle Region_Capture()
    		{
    			rect = Init();
    			return rect;
    		}
    		Rectangle Init()
    		{
    			int a  =Convert.ToInt32(axisA*scale);
    			int b =Convert.ToInt32(axisB*scale);
    			int leftupX = center.X - a;
    			int leftupY = center.Y - b;
    			return( new Rectangle(leftupX,leftupY,2*a,2*b));
    		}
    	}
    }

    Класс Circle

    Этот класс является потомком класса Ellipse:

    using System;
    using System.Drawing;
    namespace Shapes
    {
    	/// <summary>
    	/// Класс Circle - потомок класса Ellipse.
    	/// </summary>
    	public class Circle: Ellipse
    	{
    		public Circle( int radius,int x, 
    			int y):base(radius,radius,x,y)
    		{
    		//Круг - это эллипс с равными полуосями (радиусом круга)
    		}
    	}
    }

    Здесь опять-таки проявляется мощь наследования. Потомок наследует все свойства и методы родителя. Ему остается только указать собственный конструктор объектов класса, да и в нем главное состоит в вызове конструктора родительского класса с передачей ему нужных аргументов.

    Класс LittleCircle

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

    using System;
    namespace Shapes
    {
    	/// <summary>
    	/// Класс LittleCircle - потомок класса Circle.
    	/// </summary>
    	public class LittleCircle:Circle
    	{
    		public LittleCircle(int x,int y): base(4,x,y)
    		{
    			// маленький круг радиуса 4
    		}
    	}
    }

    Класс Rect

    Этот класс является еще одним прямым потомком класса Figure:

    using System;
    using System.Drawing;
    namespace Shapes
    {
    	/// <summary>
    	/// Класс Rect - потомок класса Figure.
    	/// </summary>
    	public class Rect:Figure
    	{
    		int sideA, sideB;
    		Rectangle rect;
    		public Rect(int sideA,int sideB, int x, int y): base(x,y)
    		{
    			this.sideA = sideA; this.sideB = sideB;
    			rect =Init();
    		}
    		public override void Show(Graphics g, Pen pen, Brush brush)
    		{			
    			rect = Init();
    			g.DrawRectangle(pen,rect);
    			g.FillRectangle(brush,rect);
    		}
    		public override Rectangle Region_Capture()
    		{
    			rect = Init();
    			return rect;
    		}
    		Rectangle Init()
    		{
    			int a  =Convert.ToInt32(sideA*scale);
    			int b =Convert.ToInt32(sideB*scale);
    			int leftupX = center.X - a/2;
    			int leftupY = center.Y - b/2;
    			return( new Rectangle(leftupX,leftupY,a,b));
    		}
    	}
    }

    Класс Square

    Квадрат - это частный случай прямоугольника. Соответствующий класс является потомком класса Rect:

    using System;
    namespace Shapes
    {
    	/// <summary>
    	/// Класс Square - потомок класса Rect.
    	/// </summary>
    	public class Square:Rect
    	{
    		public Square(int side, int x, int y): base(side,side,x,y)
    		{
    			//квадрат - это прямоугольник с равными сторонами
    		}
    	}
    }

    Класс Person

    Этот класс является прямым потомком класса Figure. Вместе с тем, класс является клиентом трех других классов семейства - Circle, Rect и LittleCircle, поскольку элементы фигуры, составляющие человечка, являются объектами этих классов.

    namespace Shapes
    {
    	/// <summary>
    	/// Класс Person - потомок класса Figure, 
    	/// клиент классов Circle, Rect, LittleCircle.
    	/// </summary>
    	public class Person:Figure
    	{
    		int head_h;
    		Circle head;
    		Rect body;
    		LittleCircle nose;
    		public Person(int head_h, int x, int y): base(x,y)
    		{
    			//head_h - радиус головы, x,y - ее центр.
    			//остальные размеры исчисляются относительно 
    			//размера головы.
    			this.head_h = head_h;
    			head = new Circle(head_h,x,y);
    			int body_x = x;
    			int body_y = y + 3*head_h;
    			int body_w =2*head_h;
    			int body_h = 4*head_h;
    			body = new Rect(body_w, body_h, body_x,body_y);
    			nose = new LittleCircle(x+head_h +2, y);
    		}
    		public override void Show(System.Drawing.Graphics g, 
    			System.Drawing.Pen pen, System.Drawing.Brush brush)
    		{
    			int h = Convert.ToInt32(head_h*scale);
    			//head
    			int top_x = center.X - h;
    			int top_y = center.Y - h;
    			g.DrawEllipse(pen, top_x,top_y, 2*h,2*h);
    			g.FillEllipse(brush, top_x,top_y, 2*h,2*h);
    			//body
    			top_y += 2*h;
    			g.DrawRectangle(pen, top_x,top_y, 2*h,4*h);
    			g.FillRectangle(brush, top_x,top_y, 2*h,4*h);
    			//nose
    			top_y -=h;
    			top_x += 2*h;
    			g.DrawEllipse(pen, top_x,top_y, 8,8);
    			g.FillEllipse(brush, top_x,top_y, 8,8);
    		}
    		public override System.Drawing.Rectangle 
    			Region_Capture()
    		{
    			int h = Convert.ToInt32(head_h*scale);
    			int top_x = center.X - h;
    			int top_y = center.Y - h;
    			return new 
    				System.Drawing.Rectangle(top_x,top_y,2*h,2*h);
    		}
    	}
    }

    Список с курсором. Динамические структуры данных

    Добавим в проект классы, задающие динамические структуры данных. Конечно, можно было бы воспользоваться стандартными... Но для обучения крайне полезно уметь создавать собственные классы, задающие такие структуры данных. Список с курсором - один из важнейших образцов подобных классов:

    using System;
    namespace Shapes
    {
    	/// <summary>
    	/// Класс TwoWayList(G)  описывает двусвязный список с 
    	/// курсором. Элементами списка являются объекты 
    	/// TwoLinkable, хранящие, помимо указателей на двух 
    	/// преемников, объекты типа G.Курсор будет определять 	
    	/// текущий (активный) элемент списка. Класс будет
    	/// определять симметричные операции по отношению к 
    	/// курсору.
    	/// Конструкторы: 
    	/// Конструктор без параметров будет создавать пустой 
    	/// список
    	/// Запросы:
    	/// empty: require: true; возвращает true для пустого списка
    	/// item: require: not empty(); возвращает активный элемент типа G; 
    	/// require: true; возвращает число элементов списка; 
    	/// count: count in[0,n] (count == 0) eqviv empty(); 
    	/// index: require: not empty(); возвращает индекс активного элемента.
    	/// search_res: require: true; возвращает true, если последний поиск был успешным.
    	/// Команды:
    	/// put_left(elem): require: true; 
    	/// ensure: добавить новый элемент (elem) слева от курсора;
    	/// put_right(elem): require: true; 
    	/// ensure: добавить новый элемент (elem) справа от курсора;
    	/// remove: require: not empty(); 
    	/// ensure: удалить активный элемент;
    	/// особо обрабатывается удаление последнего и единственного элементов
    	/// операции с курсором:
    	/// start: require: true; 
    	/// ensure: сделать активным первый элемент;
    	/// finish: require: true; 
    	/// ensure: сделать активным последний элемент; 
    	/// go_prev: require: not (index = 1); 
    	/// ensure: сделать активным предыдущий элемент;
    	/// go_next: require: not (index = count); 
    	/// ensure: сделать активным последующий элемент;
    	/// go_i(i): require:  (i in [1, count]); 
    	/// ensure: сделать активным элемент с индексом i;
    	/// операции поиска:
    	/// search_prev(elem): require: not (index = 1); 
    	/// ensure: сделать активным первый элемент elem слева от курсора;
    	/// Успех или неуспех поиска сохранять в булевской 
    	/// переменной search_res
    	/// search_next: require: not (index = count); 
    	/// ensure: сделать активным первый элемент elem справа от курсора;
    	/// успех или неуспех поиска сохранять в булевской переменной search_res
    	/// </summary>
    	public class TwoWayList
    	{
    		public TwoWayList()
    		{
    			first = cursor = last = null;
    			count = index = 0;
    			search_res = false;
    		}//конструктор
    		/// <summary>
    		/// first, cursor, last - ссылки на первый,
    		/// активный и последний элементы списка
    		/// Запросы count, index search_res также 
    		/// реализуются атрибутами.
    		/// Запросы empty, item реализуются функциями 
    		/// </summary>
    		protected TwoLinkable first, cursor, last;
    		protected int count, index;
    		protected bool search_res;
    		//доступ на чтение к закрытым свойствам;
    		public int Count
    		{
    			get
    			{
    				return(count);
    			}
    		}
    		public int Index
    		{
    			get
    			{
    				return(index);
    			}
    		}
    		public bool Search_res
    		{
    			get
    			{
    				return(search_res);
    			}
    		}
    		/// <summary>
    		/// require: true; возвращает true для непустого списка
    		/// </summary>
    		/// <returns></returns>
    		public bool empty()
    		{
    			return(first == null);
    		}//empty
    		/// <summary>
    		/// require: not empty(); возвращает активный 
    		/// элемент типа G;
    		/// </summary>
    		/// <returns></returns>
    		public Figure item()
    		{
    			return(cursor.Item);
    		}//item
    		/// <summary>
    		/// require: true; 
    		/// ensure: добавить новый элемент (elem) слева 
    		/// от курсора;
    		/// </summary>
    		/// <param name="elem">Тип Figure играет роль 
    		/// родового типа G
    		/// хранимого элемента elem</param>
    		public void put_left(Figure elem)
    		{
    			TwoLinkable newitem = new TwoLinkable();
    			newitem.Item = elem;
    			newitem.Next = cursor;
    			if (empty())	//список пуст
    			{
    				first = cursor = last = newitem;
    				index =1; count = 1;
    			}
    			else 
    			{
    				if (index == 1) 
    					first =newitem;
    				else
    					cursor.Prev.Next = newitem;
    				newitem.Prev = cursor.Prev; cursor.Prev = newitem;
    				count++; index++;
    			}
    		}//put_right
    		/// <summary>
    		/// require: true; 
    		/// ensure: добавить новый элемент (elem) справа 
    		/// от курсора;
    		/// </summary>
    		/// <param name="elem">Тип Figure играет роль 
    		/// родового типа G
    		/// хранимого элемента elem</param>
    		public void put_right(Figure elem)
    		{
    			TwoLinkable newitem = new TwoLinkable();
    			newitem.Item = elem;
    			newitem.Prev = cursor;
    			if (empty())	//список пуст
    			{
    				first = cursor = last = newitem;
    				index =1; count = 1;
    			}
    			else
    			{
    				if (index == count) 
    					last =newitem;
    				else
    					cursor.Next.Prev = newitem;
    				
    				newitem.Next = cursor.Next; cursor.Next = newitem;
    				count++; 
    			}
    		}//put_right
    		public void remove()
    		{
    			if(count == 1)
    			{
    				first = last = cursor = null;
    				index=0;
    			}
    			else if(index==1)
    			{
    				first = cursor.Next;
    				cursor.Prev = null;
    				cursor = cursor.Next;
    			}
    			else if(index == count)
    			{
    				last = cursor.Prev;
    				cursor.Next = null;
    				cursor = cursor.Prev;
    				index--;
    			}
    			else
    			{
    				cursor.Prev.Next = cursor.Next;
    				cursor.Next.Prev = cursor.Prev;
    				cursor = cursor.Next;
    			}
    			count--;
    		}//remove
    		/// операции с курсором:
    		/// <summary>
    		/// start: require: true; 
    		/// ensure: сделать активным первый элемент;
    		/// </summary>
    		public void start()
    		{
    			cursor = first; index = 1;
    		}//start
    		/// <summary>
    		/// finish: require: true; 
    		/// ensure: сделать активным последний элемент; 
    		/// </summary>
    		public void finish()
    		{
    			cursor = last; index = count;
    		}//finish
    		/// <summary>
    		/// go_prev: require: not (index = 1); 
    		/// ensure: сделать активным предыдущий элемент;
    		/// </summary>
    		public void go_prev()
    		{
    			cursor = cursor.Prev; index--;
    		}// go_prev
    		/// <summary>
    		/// go_next: require: not (index = count); 
    		/// ensure: сделать активным последующий элемент;
    		/// </summary>
    		public void go_next()
    		{
    			cursor = cursor.Next; index++;
    		}// go_next
    		/// <summary>
    		/// go_i(i): require:  (i in [1, count]); 
    		/// ensure: сделать активным элемент с индексом i;
    		/// </summary>
    		/// <param name="i"></param>
    		public void go_i(int i)
    		{
    			if(i >index)
    				while (i>index)
    				{
    					cursor = cursor.Next; index++;
    				}
    			else if(i<index)
    				while (i<index)
    				{
    					cursor = cursor.Prev; index--;
    				}
    		}// go_i
    		/// операции поиска:
    		/// <summary>
    		/// search_prev(elem): require: not (index = 1); 
    		/// ensure: сделать активным первый элемент elem 
    		/// слева от курсора;
    		/// </summary>
    		/// <param name="elem">искомый элемент</param>
    		public virtual void search_prev(Figure elem)
    		{
    			bool found = false;
    			while (!found  (index !=1))
    			{
    				cursor = cursor.Prev; index--;
    				found = (elem == item());
    			}
    			search_res = found;
    		}// search_prev
    	/// <summary>
    	/// успех или неуспех поиска сохранять в булевской 
    	/// переменной search_res
    	/// search_next: require: not (index = count); 
    	/// ensure: сделать активным первый элемент elem 
    	/// справа от курсора;
    	/// успех или неуспех поиска сохранять в булевской 
    	/// переменной search_res
    	/// </summary>
    	/// <param name="elem"></param>
    		public virtual void search_next(Figure elem)
    		{
    			bool found = false;
    			while (!found  (index !=count))
    			{
    				cursor = cursor.Next; index++;
    				found = (elem == item());
    			}
    			search_res = found;
    		}//search_next	
    	}
    }

    Заметьте, класс подробно документирован. Для методов класса указываются предусловия и постусловия. Обратите внимание, в соответствии с принципами контрактного программирования клиент класса, прежде чем вызвать метод, должен проверить выполнимость предусловия, что повышает корректность работы системы в целом. Именно так и будет реализован вызов этих методов в классе формы, где осуществляется работа со списком.

    Классы элементов списка

    Рассмотрим классы, описывающие элементы списков - элементы с одним и с двумя указателями:

    using System;
    namespace Shapes
    {
    	/// <summary>
    	/// Класс Linkable(T)задает элементы списка,включающие:
    	/// информационное поле типа T - item 
    	/// ссылку на элемент типа Linkable - next
    	/// Функции:
    	/// конструктор new: -> Linkable
    	/// запросы: 
    	/// Get_Item: Linkable -> T
    	/// Get_Next: Linkable -> Linkable 
    	/// процедуры:
    	/// Set_Item: Linkable*T -> Linkable
    	/// Set_Next: Linkable*Linkable -> Linkable 
    	/// Роль типа T играет Figure
    	/// </summary>
    	public class Linkable
    	{
    		public Linkable()
    		{
    			item =null; next = null;
    		}
    		/// <summary>
    		/// закрытые атрибуты класса
    		/// </summary>
    		Figure item;
    		Linkable next;
    		/// <summary>
    		/// процедуры свойства для доступа к полям класса
    		/// </summary>
    		public Figure Item{
    		get{
    			return(item);
    			}
    		set{
    			item = value;
    			}
    		}
    		public Linkable Next{
    		get{
    			return(next);
    			}
    		set{
    			next = value;
    			}
    		}
    	}//class Linkable
    	/// <summary>
    	/// Класс TwoLinkable задает элементы с двумя ссылками 
    	/// </summary>
    	public class TwoLinkable 
    	{
    		
    		public TwoLinkable()
    		{
    			prev = next = null;
    		}
    		/// <summary>
    		/// закрытые атрибуты класса
    		/// </summary>
    		TwoLinkable prev, next;
    		Figure item;
    		/// <summary>
    		/// процедуры свойства для доступа к полям класса
    		/// </summary>
    		public Figure Item
    		{
    			get
    			{
    				return(item);
    			}
    			set
    			{
    				item = value;
    			}
    		}
    		public TwoLinkable Next
    		{
    			get
    			{
    				return(next);
    			}
    			set
    			{
    				next = value;
    			}
    		}
    		public TwoLinkable Prev
    		{
    			get
    			{
    				return(prev);
    			}
    			set
    			{
    				prev = value;
    			}
    		}
    	}//class TwoLinkable
    }

    Организация интерфейса

    Создадим теперь интерфейс, позволяющий конечному пользователю работать с объектами наших классов. Как всегда, интерфейс создавался вручную в режиме проектирования. На форме я создал меню с большим числом команд и инструментальную панель с 18 кнопками, команды которых повторяли основную команду меню. Описывать процесс создания интерфейса не буду - он подробно рассмотрен в предыдущей главе. Поскольку вся работа по созданию интерфейса транслируется в программный код формы, то просто приведу этот достаточно длинный текст почти без всяких купюр:

    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;
    using Shapes;
    
    namespace Final
    {
    	/// <summary>
    	/// Эта форма обеспечивает интерфейс для создания, 
    	/// рисования, показа, перемещения, сохранения в списке 
    	/// и выполнения других операций над объектами семейства
    	/// геометрических фигур. Форма имеет меню и 
    	/// инструментальные панели.
    	/// </summary>
    	public class Form1 : System.Windows.Forms.Form
    	{
    		//fields
    		Graphics graphic;
    		Brush brush, clearBrush;
    		Pen pen, clearPen;
    		Color color;
    		Figure current;
    		TwoWayList listFigure;
    		private System.Windows.Forms.MainMenu mainMenu1;
    		private System.Windows.Forms.ImageList imageList1;
    		private System.Windows.Forms.ToolBar toolBar1;
    		private System.Windows.Forms.MenuItem menuItem1;
    // аналогичные определения для других элементов меню
    		private System.Windows.Forms.MenuItem menuItem35;
    		private System.Windows.Forms.ToolBarButton 
    			toolBarButton1;
    // аналогичные определения для других командных кнопок
    		private System.Windows.Forms.ToolBarButton 
    			toolBarButton18;
    		private System.ComponentModel.IContainer components;
    		public Form1()
    		{
    			InitializeComponent();
    			InitFields();
    		}
    		void InitFields()
    		{
    			graphic = CreateGraphics();
    			color = SystemColors.ControlText;
    			brush = new SolidBrush(color);
    			clearBrush = new SolidBrush(SystemColors.Control);
    			pen = new Pen(color);
    			clearPen = new Pen(SystemColors.Control);
    			listFigure = new TwoWayList();
    			current = new Person(20, 50, 50);
    		}
    		/// <summary>
    		/// Clean up any resources being used.
    		/// </summary>
    		protected override void Dispose( bool disposing )
    		{
    			if( disposing )
    			{
    				if (components != null) 
    				{
    					components.Dispose();
    				}
    			}
    			base.Dispose( disposing );
    		}
    		#region Windows Form Designer generated code
    		/// <summary>
    		/// Required method for Designer support - do not modify
    		/// the contents of this method with the code editor.
    		/// </summary>
    		private void InitializeComponent()
    		{
    			// Код, инициализирующий компоненты и построенный
    			// дизайнером, опущен
    		}
    		#endregion
    		/// <summary>
    		/// Точка входа в приложение - процедура Main, 
    		/// запускающая форму
    		/// </summary>
    		[STAThread]
    		static void Main() 
    		{
    			Application.Run(new Form1());
    		}
    		private void menuItem7_Click(object sender, 
    			System.EventArgs e)
    		{
    			createEllipse();
    		}
    		void createEllipse()
    		{
    			//clear old figure
    			if (current != null) current.Show(graphic, 
    				clearPen, clearBrush);
    			//create ellipse
    			current = new Ellipse(50, 30, 180,180);
    		}
    		private void menuItem8_Click(object sender, 
    			System.EventArgs e)
    		{
    			createCircle();
    		}
    		void createCircle()
    		{
    			//clear old figure
    			if (current != null) current.Show(graphic, 
    				clearPen, clearBrush);
    			//create circle
    			current = new Circle(30, 180,180);
    		}
    		private void menuItem9_Click(object sender, 
    			System.EventArgs e)
    		{
    			createLittleCircle();
    		}
    		void createLittleCircle()
    		{
    			//clear old figure
    			if (current != null) current.Show(graphic, 
    				clearPen, clearBrush);
    			//create littlecircle
    			current = new LittleCircle(180,180);
    		}
    		private void menuItem10_Click(object sender, 
    			System.EventArgs e)
    		{
    			createRectangle();
    		}
    		void createRectangle()
    		{
    			//clear old figure
    			if (current != null) current.Show(graphic, 
    				clearPen, clearBrush);
    			//create rectangle
    			current = new Rect(50, 30, 180,180);
    		}
    		private void menuItem11_Click(object sender, 
    			System.EventArgs e)
    		{
    			createSquare();
    		}
    		void createSquare()
    		{
    			//clear old figure
    			if (current != null) current.Show(graphic, 
    				clearPen, clearBrush);
    			//create square
    			current = new Square(30, 180,180);
    		}
    		private void menuItem12_Click(object sender, 
    			System.EventArgs e)
    		{
    			createPerson();
    		}
    		void createPerson()
    		{
    			//clear old figure
    			if (current != null) current.Show(graphic, 
    				clearPen, clearBrush);
    			//create person
    			current = new Person(20, 180,180);
    		}
    		private void menuItem13_Click(object sender, 
    			System.EventArgs e)
    		{
    			showCurrent();
    		}
    		void showCurrent()
    		{
    			//Show current
    			current.Show(graphic, pen, brush);
    		}
    		private void menuItem14_Click(object sender, 
    			System.EventArgs e)
    		{
    			clearCurrent();
    		}
    		void clearCurrent()
    		{
    			//Clear current
    			current.Show(graphic, clearPen, clearBrush);
    		}
    		private void menuItem17_Click(object sender, 
    			System.EventArgs e)
    		{
    			incScale();
    		}
    		void incScale()
    		{
    			//Increase scale
    			current.Show(graphic, clearPen, clearBrush);
    			current.Scale(1.5);
    			current.Show(graphic, pen, brush);
    		}
    		private void menuItem18_Click(object sender, 
    			System.EventArgs e)
    		{
    			decScale();
    		}
    		void decScale()
    		{
    			//Decrease scale
    			current.Show(graphic, clearPen, clearBrush);
    			current.Scale(2.0/3);
    			current.Show(graphic, pen, brush);
    		}
    		private void menuItem19_Click(object sender, 
    			System.EventArgs e)
    		{
    			moveLeft();
    		}
    		void moveLeft()
    		{
    			//Move left
    			current.Show(graphic, clearPen, clearBrush);
    			current.Move(-20,0);
    			current.Show(graphic, pen, brush);
    		}
    		private void menuItem20_Click(object sender, 
    			System.EventArgs e)
    		{
    			moveRight();
    		}
    		void moveRight()
    		{
    			//Move right
    			current.Show(graphic, clearPen, clearBrush);
    			current.Move(20,0);
    			current.Show(graphic, pen, brush);
    		}
    		private void menuItem21_Click(object sender, 
    			System.EventArgs e)
    		{
    			moveTop();
    		}
    		void moveTop()
    		{
    			//Move top
    			current.Show(graphic, clearPen, clearBrush);
    			current.Move(0,-20);
    			current.Show(graphic, pen, brush);
    		}
    		private void menuItem22_Click(object sender, 
    			System.EventArgs e)
    		{
    			moveDown();
    		}
    		void moveDown()
    		{
    			//Move down
    			current.Show(graphic, clearPen, clearBrush);
    			current.Move(0, 20);
    			current.Show(graphic, pen, brush);
    		}
    		private void menuItem23_Click(object sender, 
    			System.EventArgs e)
    		{
    			//choose color
    			ColorDialog dialog = new ColorDialog();
    			if (dialog.ShowDialog() ==DialogResult.OK) 
    				color =dialog.Color;
    			pen = new Pen(color); brush = 
    				new SolidBrush(color); 
    		}
    		private void menuItem24_Click(object sender, 
    			System.EventArgs e)
    		{
    			//Red color
    			color =Color.Red;
    			pen = new Pen(color); brush = 
    				new SolidBrush(color); 
    		}
    		private void menuItem25_Click(object sender, 
    			System.EventArgs e)
    		{
    			//Green color
    			color =Color.Green;
    			pen = new Pen(color); brush = 
    				new SolidBrush(color); 
    		}
    		private void menuItem26_Click(object sender, 
    			System.EventArgs e)
    		{
    			//Blue color
    			color =Color.Blue;
    			pen = new Pen(color); brush = 
    				new SolidBrush(color); 
    		}
    		private void menuItem27_Click(object sender, 
    			System.EventArgs e)
    		{
    			//Black color
    			color =Color.Black;
    			pen = new Pen(color); brush = 
    				new SolidBrush(color); 
    		}
    		private void menuItem28_Click(object sender, 
    			System.EventArgs e)
    		{
    			//Gold color
    			color =Color.Gold;
    			pen = new Pen(color); brush = 
    				new SolidBrush(color); 
    		}
    		private void menuItem29_Click(object sender, 
    			System.EventArgs e)
    		{
    			//put_left: добавление фигуры в список
    			listFigure.put_left(current);
    		}
    		private void menuItem30_Click(object sender, 
    			System.EventArgs e)
    		{
    			//put_right: добавление фигуры в список
    			listFigure.put_right(current);
    		}
    		private void menuItem31_Click(object sender, 
    			System.EventArgs e)
    		{
    			//remove: удаление фигуры из списка
    			if(!listFigure.empty())
    				listFigure.remove();
    		}
    		private void menuItem32_Click(object sender, 
    			System.EventArgs e)
    		{
    			goPrev();
    		}
    		void goPrev()
    		{
    			//go_prev: передвинуть курсор влево
    			if(!(listFigure.Index == 1))
    			{
    				listFigure.go_prev();
    				current = listFigure.item();
    			}
    		}
    		private void menuItem33_Click(object sender, 
    			System.EventArgs e)
    		{
    			goNext();
    		}
    		void goNext()
    		{
    			//go_next: передвинуть курсор вправо
    			if( !(listFigure.Index == listFigure.Count))
    			{
    				listFigure.go_next();
    				current = listFigure.item();
    			}
    		}
    		private void menuItem34_Click(object sender, 
    			System.EventArgs e)
    		{
    			//go_first
    			listFigure.start();
    			if(!listFigure.empty())
    				current = listFigure.item();
    		}
    		private void menuItem35_Click(object sender, 
    			System.EventArgs e)
    		{
    			//go_last
    			listFigure.finish();
    			if(!listFigure.empty())
    				current = listFigure.item();
    		}
    		private void menuItem15_Click(object sender, 
    			System.EventArgs e)
    		{
    			showList();
    		}
    		void showList()
    		{
    			//Show List
    			listFigure.start();
    			while(listFigure.Index <= listFigure.Count)
    			{
    				current = listFigure.item();
    				current.Show(graphic,pen,brush);
    				listFigure.go_next();
    			}
    			listFigure.finish();
    		}
    		private void menuItem16_Click(object sender, 
    			System.EventArgs e)
    		{
    			clearList();
    		}
    		void clearList()
    		{
    			//Clear List
    			listFigure.start();
    			while(!listFigure.empty())
    			{
    				current = listFigure.item();
    				current.Show(graphic,clearPen,clearBrush);
    				listFigure.remove();
    			}
    		}
    		private void Form1_MouseMove(object sender, 
    			System.Windows.Forms.MouseEventArgs e)
    		{
    			if((current != null)  current.dragged_figure)
    			{
    				current.Show(graphic,clearPen,clearBrush);
    				Point pt = new Point(e.X, e.Y);
    				current.center_figure = pt;
    				current.Show(graphic,pen,brush);
    			}
    		}
    		private void Form1_MouseUp(object sender, 
    			System.Windows.Forms.MouseEventArgs e)
    		{
    			current.dragged_figure = false;
    		}
    		private void Form1_MouseDown(object sender, 
    			System.Windows.Forms.MouseEventArgs e)
    		{
    			Point mousePoint = new Point (e.X, e.Y);
    			Rectangle figureRect = current.Region_Capture();
    			if ((current != null)  
    				(figureRect.Contains(mousePoint)))
    				current.dragged_figure = true;
    		}
    		protected override void 
    			OnPaint(System.Windows.Forms.PaintEventArgs e)
    		{
    			//show current figure
    			current.Show(graphic, pen, brush);
    		}
    		private void toolBar1_ButtonClick(object sender, 
    			System.Windows.Forms.ToolBarButtonClickEventArgs e)
    		{
    			int buttonNumber = 
    				toolBar1.Buttons.IndexOf(e.Button); 
    			switch (buttonNumber)
    			{
    				case 0:
    					createEllipse(); break;
    				case 1:
    					createCircle(); break;
    				case 2:
    					createLittleCircle(); break;
    				case 3:
    					createRectangle(); break;
    				case 4:
    					createSquare(); break;
    				case 5:
    					createPerson();  break;
    				case 6:
    					showCurrent(); break;
    				case 7:
    					clearCurrent(); break;
    				case 8:
    					showList();  break;
    				case 9:
    					clearList();  break;
    				case 10:
    					incScale();  break;
    				case 11:
    					decScale();  break;
    				case 12:
    					moveLeft();  break;
    				case 13:
    					moveRight();  break;
    				case 14:
    					moveTop();  break;
    				case 15:
    					moveDown();  break;
    				case 16:
    					goNext();  break;
    				case 17:
    					goPrev();  break;
    			}
    		}
    	}
    }

    Команд меню и кнопок в нашем интерфейсе много, поэтому много и обработчиков событий, что приводит к разбуханию кода. Но каждый из обработчиков событий довольно прост. Ограничусь кратким описанием главного меню:

  • команды пункта главного меню Create позволяют создавать геометрические фигуры разных классов;
  • команды пункта главного меню Show позволяют показать или стереть текущую фигуру или все фигуры, сохраняемые в списке;
  • две команды пункта Scale позволяют изменить масштаб фигуры (увеличить ее или уменьшить);
  • команды пункта Move позволяют перемещать текущую фигуру в четырех направлениях;
  • команды пункта Color позволяют либо задать цвет фигур в диалоговом окне, либо выбрать один из предопределенных цветов;
  • группа команд пункта List позволяет помещать текущую фигуру в список, перемещаться по списку и удалять из списка ту или иную фигуру;
  • командные кнопки инструментальной панели соответствуют наиболее важным командам меню;
  • реализована возможность перетаскивания фигур по экрану мышью.
  • В заключение взгляните, как выглядит форма в процессе работы с объектами:

    (рис 25.1) Финальный проект. Форма в процессе работы
    Вернуться к учебному плану