Название: Алгоритмическое мышление при решении задач (Шамшев А. Б.)

Жанр: Информационные системы и технологии

Просмотров: 1389


Задача 20. создание графического интерфейса, реагирующего на действия пользователя

В современном мире пользователи привыкли и богатому интерфейсу, с которым  легко  взаимодействовать.  Одна  из  составных  частей  таких графических интерфейсов – графика. В этом разделе будут рассмотрены некоторые вопросы ее использования.

Для   создания   приложения  с   более   продвинутым  интерфейсом,  чем консоль, необходимо выбрать Windows Forms Application. При начале работы с таким типом проектов будет создана автоматически форма с названием Form1.

И такое приложение уже можно запускать, масштабировать окно (и другие манипуляции – например, развернуть во весь экран), закрыть.

 

 
При запуске такого приложения мы увидим следующую форму:

 

Следует отметить, что в проекте, в кодах, есть 2 файла – Form1.cs и Form1.Designer.cs. Первый из них предназначен для изменения программистом. Во втором файле находится код, который генерируется Visual Studio для дизайнера  форм  и  отвечает  за  создание  элементов  управления  на  форме (кнопок, полей ввода и других элементов). Следует отметить, что второй файл крайне не рекомендуется изменять начинающим программистам.

Следует отметить, что в Form1.cs можно создавать динамические элементы управления. Именно этим методом мы и воспользуемся.

Для того, что бы понять следующий шаг, отметим, что на форме сверху

есть полоса, которая «мешается» при вычислении координат. Поэтому часто на форму помещают объект, в котором будет происходить рисование. Например Panel. Разместим Panel динамически на форме.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing; using System.Linq; using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication4 {

public partial class Form1 : Form {

public Form1() { InitializeComponent();

//создаем  панель,  на   которой будет  происходить рисование

Panel drawPanel = new Panel();

//говорим,  что  она должна заполнять  всю  форму

drawPanel.Dock = DockStyle.Fill;

//и  добавляем ее  на   форму

this.Controls.Add(drawPanel);

}

}

}

 

Следующим шагом будет добавление возможности рисования на форме (а вернее реализация рисования, потому что на любой панели по умолчанию можно рисовать). Для этого используется событие Paint. В аргументах этого события  передается  Graphics,  который  предоставляет  холст  для  рисования (более подробно об этой концепции можно прочитать в любом учебнике по C#, в разделе, посвещенном графике).

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data; using System.Drawing; using System.Linq; using System.Text;

using System.Windows.Forms;

 

namespace WindowsFormsApplication4 {

public partial class Form1 : Form {

public Form1() {

InitializeComponent();

//создаем  панель,  на   которой будет  происходить рисование

Panel drawPanel = new Panel();

//говорим,  что  она должна заполнять  всю  форму

drawPanel.Dock = DockStyle.Fill;

//добавляем  обработчик рисования

drawPanel.Paint += new PaintEventHandler(drawPanel_Paint);

//и  добавляем ее  на   форму

this.Controls.Add(drawPanel);

}

 

void drawPanel_Paint(object sender, PaintEventArgs e) {

//для  примера  нарисуем   линию

e.Graphics.DrawLine(Pens.Red, 0, 0, 50, 50);

}

}

}

Пусть на форме мы будем рисовать квадратики, которые пользователь может «таскать» мышкой. Логично предположить, что таких объектов будет много, поэтому нам нужен список таких объектов (или массив).

Тогда получается, что нужно объявить класс, который отвечает за работу с квадратом.   Квадрат   надо   рисовать.   Для   рисования   требуется   знать   его

координаты и размеры. И для рисования нужен объект типа Graphics, в котором будет происходить рисование (пусть он нам передается в функции рисования). Так же нам надо будет проверять, попадает ли точка в заданную фигуру или нет.  Поэтому  класс,  отвечающий  за  работу  с  фигурой,  будет  выглядеть

следующим образом:

/// <summary>

/// класс, который отвечает  за  работу с  одним   квадратом

/// </summary>

public class Square {

/// <summary>

/// координаты центра  квадрата  по   X

/// </summary>

public int x;

/// <summary>

/// координаты центра  квадрата  по   Y

/// </summary>

public int y;

/// <summary>

/// размер квадрата

/// </summary>

public int size;

/// <summary>

/// конструктор

/// </summary>

/// <param name="_x">координаты  центра по   X</param>

/// <param name="_y">координаты  центра по   Y</param>

/// <param name="_size">размер  квадрата</param>

public Square(int _x, int _y, int _size) {

x = _x;

y = _y;

size = _size;

}

/// <summary>

/// функция  рисования квадрата

/// </summary>

/// <param name="e">объект,  в  котором надо рисовать</param>

public void draw(Graphics e) {

e.FillRectangle(Brushes.Green, x - size / 2, y - size / 2,

size, size);

}

/// <summary>

/// функция проверки  принадлежности  точки  квадрату

/// </summary>

/// <param name="p">точка, которая  проверяется  на

принадлежность</param>

/// <returns></returns>

public bool contains(Point p) {

var rect = new Rectangle(x - size / 2, y - size / 2, size,

size);

return rect.Contains(p);

}

}

Следующий вопрос – а как появляются квадраты на форме? Пусть они

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

public partial class Form1 : Form {

public Form1() {

InitializeComponent();

//создаем  панель,  на   которой будет  происходить рисование

Panel drawPanel = new Panel();

//говорим,  что  она должна заполнять  всю  форму

drawPanel.Dock = DockStyle.Fill;

//добавляем  обработчик рисования

drawPanel.Paint += new PaintEventHandler(drawPanel_Paint);

//добавляем  обработчик  нажатий кнопки  мыши

drawPanel.MouseClick += new

MouseEventHandler(drawPanel_MouseClick);

//и  добавляем ее  на   форму

this.Controls.Add(drawPanel);

}

 

void drawPanel_MouseClick(object sender, MouseEventArgs e) {

//если нажата  не   правая  кнопка мыши,

if (e.Button != MouseButtons.Right) {

//то  выходим

return;

}

//создаем  новый   квадрат

var newF = new Square(e.X, e.Y, 10);

//и  добавляем его  в список квадратов

figureList.Add(newF);

}

 

public List<Square> figureList = new List<Square>();

 

void drawPanel_Paint(object sender, PaintEventArgs e) {

//для  примера  нарисуем   линию

e.Graphics.DrawLine(Pens.Red, 0, 0, 50, 50);

}

}

Теперь надо нарисовать квадраты. Для того, что бы нарисовать множество

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

public partial class Form1 : Form {

public Form1() { InitializeComponent();

//создаем  панель,  на   которой будет  происходить рисование

Panel drawPanel = new Panel();

//говорим,  что  она должна заполнять  всю  форму

drawPanel.Dock = DockStyle.Fill;

//добавляем  обработчик рисования

drawPanel.Paint += new PaintEventHandler(drawPanel_Paint);

//добавляем  обработчик  нажатий кнопки  мыши

drawPanel.MouseClick += new

MouseEventHandler(drawPanel_MouseClick);

//и  добавляем ее  на   форму

this.Controls.Add(drawPanel);

}

 

void drawPanel_MouseClick(object sender, MouseEventArgs e) {

//если нажата  не   правая  кнопка мыши,

if (e.Button != MouseButtons.Right) {

//то  выходим

return;

}

//создаем  новый   квадрат

var newF = new Square(e.X, e.Y, 10);

//и  добавляем его  в список квадратов

figureList.Add(newF);

}

 

public List<Square> figureList = new List<Square>();

 

void drawPanel_Paint(object sender, PaintEventArgs e) {

for (int i = 0; i < figureList.Count; i++) {

figureList[i].draw(e.Graphics);

}

}

}

Однако, если запустить программу, будет видно, что квадраты не появляются. Это связано с тем, что по логике мы сказали, как надо рисовать, но не сказали, когда надо рисовать. Для вызова рисования необходимо вызвать метод  Invalidate  у  панели.  А  следовательно,  drawPanel  надо  вынести  из функции. Поэтому следующий шаг в решении задачи будет:

public partial class Form1 : Form {

public Panel drawPanel;

public Form1() {

InitializeComponent();

//создаем  панель,  на   которой будет  происходить рисование

drawPanel = new Panel();

//говорим,  что  она должна заполнять  всю  форму

drawPanel.Dock = DockStyle.Fill;

//добавляем  обработчик рисования

drawPanel.Paint += new PaintEventHandler(drawPanel_Paint);

//добавляем  обработчик  нажатий кнопки  мыши

drawPanel.MouseClick += new

MouseEventHandler(drawPanel_MouseClick);

//и  добавляем ее  на   форму

this.Controls.Add(drawPanel);

}

 

void drawPanel_MouseClick(object sender, MouseEventArgs e) {

//если нажата  не   правая  кнопка мыши,

if (e.Button != MouseButtons.Right) {

//то  выходим

return;

}

//создаем  новый   квадрат

var newF = new Square(e.X, e.Y, 10);

//и  добавляем его  в список квадратов

figureList.Add(newF);

//и  вызываем перерисовку  панели

drawPanel.Invalidate();

}

 

public List<Square> figureList = new List<Square>();

 

void drawPanel_Paint(object sender, PaintEventArgs e) {

for (int i = 0; i < figureList.Count; i++) {

figureList[i].draw(e.Graphics);

}

}

}

После этого квадратики стали отображаться на форме. Это является несомненным прогрессом. Но не полным решением задачи. По условию задачи нам надо «таскать» квадратики мышкой.

Для того, что бы «таскать» квадратик мышкой, пользователь должен:

1.  Нажать левую кнопку мыши;

2.  Двигать мышку с нажатой левой кнопкой мыши;

3.  Отпустить кнопку мыши.

Логично предположить, что еще надо ответить на вопрос – какой из множества квадратиков пользователь сейчас «таскает»? С учетом того, что они

хранятся в списке, индекс элемента в нем однозначно характеризует квадратик.

Так же логично предположить, что пользователь может не «таскать» квадратик  (например,  он  нажал  кнопку  мыши  не  на  квадратике),  и  эту ситуацию  надо  как-то  отличать.  Если  индекс  «таскаемого»  элемента  будет

равен -1, то это будет означать, что пользователь не «таскает» квадратик.

Так же очевидно, что когда пользователь отпускает левую кнопку мыши,

это означает, что он больше не «таскает» квадратик.

Предположим, что пользователь нажал на область, которая принадлежит сразу двум квадратикам (например, они «налезают» друг на друга). Тогда надо

«таскать» тот, который расположен «сверху». Т. е. тот, который был нарисован

последним. Поэтому проход по списку квадратиков надо организовывать от

конца к началу. С учетом этих рассуждений следующий шаг в приближении к решению будет:

public partial class Form1 : Form {

public Panel drawPanel;

public Form1() { InitializeComponent();

//создаем  панель,  на   которой будет  происходить рисование

drawPanel = new Panel();

//говорим,  что  она должна заполнять  всю  форму

drawPanel.Dock = DockStyle.Fill;

//добавляем  обработчик рисования

drawPanel.Paint += new PaintEventHandler(drawPanel_Paint);

//добавляем обработчик  "кликов" кнопки  мыши

drawPanel.MouseClick += new

MouseEventHandler(drawPanel_MouseClick);

//добавляем обработчик  нажатий кнопок  мыши

drawPanel.MouseDown += new

MouseEventHandler(drawPanel_MouseDown);

//добавляем обработчик  отпусканий  кнопок  мыши

drawPanel.MouseUp += new

MouseEventHandler(drawPanel_MouseUp);

//и  добавляем ее  на   форму

this.Controls.Add(drawPanel);

}

 

void drawPanel_MouseUp(object sender, MouseEventArgs e) {

//если  пользователь  отпустил не   левую кнопку  мыши

if (e.Button != MouseButtons.Left) {

//то  выходим

return;

}

//говорим,  что  пользователь  больше не   «таскает» квадрат

carringIndex = -1;

}

 

void drawPanel_MouseDown(object sender, MouseEventArgs e) {

//если  пользователь  нажал не   левую кнопку  мыши,

if (e.Button != MouseButtons.Left) {

//то  выходим

return;

}

//проходим  по   списку  фигур от  конца к  началу

for (int i = figureList.Count - 1; i >= 0; i--) {

//если текущая  фигура  содержит  точку, в которой

пользователь  нажал кнопку мыши

if (figureList[i].contains(e.Location)) {

//то это  означает, что ее «таскаем»

carringIndex = i;

//и  выходим, потому что  мы ее  нашли

return;

}

}

//если  дошли   досюда, это означает, что пользователь  не   попал

//ни  по   одной  фигуре, а следовательно, он   не   «таскает» их

carringIndex = -1;

}

/// <summary>

/// индекс квадратика, который  «таскается»

/// </summary>

public int carringIndex = -1;

 

void drawPanel_MouseClick(object sender, MouseEventArgs e) {

//если нажата  не   правая  кнопка мыши,

if (e.Button != MouseButtons.Right) {

//то  выходим

return;

}

//создаем  новый   квадрат

var newF = new Square(e.X, e.Y, 10);

//и  добавляем его  в список квадратов

figureList.Add(newF);

//и  вызываем перерисовку  панели

drawPanel.Invalidate();

}

 

public List<Square> figureList = new List<Square>();

 

void drawPanel_Paint(object sender, PaintEventArgs e) {

for (int i = 0; i < figureList.Count; i++) {

figureList[i].draw(e.Graphics);

}

}

}

Мы реализовали 1 и 3 пункт последовательности действий пользователя,

но не второй пункт.

Логично         предположить,          что      если    пользователь  не        «таскает»        объект

(carringIndex  ==  -1),  то  движения  мыши  обрабатывать  не  надо.  А  если

«таскает», то текущая точка мыши будет центром «таскаемого» квадратика (на самом  деле  это  не  совсем  так,  но  для  наших  целей  такое  приближение

подойдет).  Изменение  центра  квадрата  приведет  к  изменению  картины  на панели, а следовательно, ее надо перерисовать.

Приведем полный код программы:

using System.Collections.Generic;

using System.Drawing;

using System.Windows.Forms;

 

namespace WindowsFormsApplication4 {

public partial class Form1 : Form {

public Panel drawPanel;

public Form1() { InitializeComponent();

//создаем  панель,  на   которой будет  происходить рисование

drawPanel = new Panel();

//говорим,  что  она должна заполнять  всю  форму

drawPanel.Dock = DockStyle.Fill;

//добавляем  обработчик рисования

drawPanel.Paint += new PaintEventHandler(drawPanel_Paint);

//добавляем обработчик  "кликов" кнопки  мыши

drawPanel.MouseClick += new

MouseEventHandler(drawPanel_MouseClick);

//добавляем обрабаботчик   нажатий кнопок  мыши

drawPanel.MouseDown += new

MouseEventHandler(drawPanel_MouseDown);

//добавляем обработчик  отпусканий  кнопок  мыши

drawPanel.MouseUp += new

MouseEventHandler(drawPanel_MouseUp);

//добавляем  обработчик  перемещений мыши

drawPanel.MouseMove += new

MouseEventHandler(drawPanel_MouseMove);

//и  добавляем ее  на   форму

this.Controls.Add(drawPanel);

}

 

void drawPanel_MouseMove(object sender, MouseEventArgs e) {

if (carringIndex == -1) {

return;

}

figureList[carringIndex].x = e.X; figureList[carringIndex].y = e.Y; drawPanel.Invalidate();

}

 

void drawPanel_MouseUp(object sender, MouseEventArgs e) {

//если  пользователь  отпустил не   левую кнопку  мыши

if (e.Button != MouseButtons.Left) {

//то  выходим

return;

}

//говорим,  что  пользователь  больше не   «таскает» квадрат

carringIndex = -1;

}

 

void drawPanel_MouseDown(object sender, MouseEventArgs e) {

//если  пользователь  нажал не   левую кнопку  мыши,

if (e.Button != MouseButtons.Left) {

//то  выходим

return;

}

//проходим  по   списку  фигур от  конца к  началу

for (int i = figureList.Count - 1; i >= 0; i--) {

//если текущая  фигура  содержит  точку, в которой

пользователь  нажал

if (figureList[i].contains(e.Location)) {

//то это  означает, что ее «таскаем»

carringIndex = i;

//и  выходим, потому что  мы ее  нашли

return;

}

}

//если  дошли   досюда, это означает, что пользователь  не   попал

//ни  по   одной  фигуре, а следовательно, он   не   «таскает» их

carringIndex = -1;

}

/// <summary>

/// индекс квадратика, который  «таскается»

/// </summary>

public int carringIndex = -1;

 

void drawPanel_MouseClick(object sender, MouseEventArgs e) {

//если нажата  не   правая  кнопка мыши,

if (e.Button != MouseButtons.Right) {

//то  выходим

return;

}

//создаем  новый   квадрат

var newF = new Square(e.X, e.Y, 10);

//и  добавляем его  в список квадратов

figureList.Add(newF);

//и  вызываем перерисовку  панели

drawPanel.Invalidate();

}

 

public List<Square> figureList = new List<Square>();

 

void drawPanel_Paint(object sender, PaintEventArgs e) {

for (int i = 0; i < figureList.Count; i++) {

figureList[i].draw(e.Graphics);

}

}

}

/// <summary>

/// класс, который отвечает  за  работу с  одним   квадратом

/// </summary>

public class Square {

/// <summary>

/// координаты центра  квадрата  по   X

/// </summary>

public int x;

/// <summary>

/// координаты центра  квадрата  по   Y

/// </summary>

public int y;

/// <summary>

/// размер квадрата

/// </summary>

public int size;

/// <summary>

/// конструктор

/// </summary>

/// <param name="_x">координаты  центра по   X</param>

/// <param name="_y">координаты  центра по   Y</param>

/// <param name="_size">размер  квадрата</param>

public Square(int _x, int _y, int _size) {

x = _x;

y = _y;

size = _size;

}

/// <summary>

/// функция  рисования квадрата

/// </summary>

/// <param name="e">объект,  в  котором надо рисовать</param>

public void draw(Graphics e) {

e.FillRectangle(Brushes.Green, x - size / 2, y - size / 2, size, size);

}

/// <summary>

/// функция  проверки принадледности точки квадрату

/// </summary>

/// <param name="p">точка, которая  проверяется  на

принадлежность</param>

/// <returns></returns>

public bool contains(Point p) {

var rect = new Rectangle(x - size / 2, y - size / 2, size,

size);

return rect.Contains(p);

}

}

}