БлогNot. Решаем типовые задачи на C# с помощью Windows Forms

Решаем типовые задачи на C# с помощью Windows Forms

В последнее время здесь был ряд заметок по работе с приложениями Windows Forms на C#, все вместе их можно рассматривать как набросок небольшого курса вроде "Технологии программирования, часть 1", изучаемого, когда "Языки программирования" в лице основ C++ и C# уже "пройдены":

1. Введение и основы Windows Forms

2. Взаимодействие форм. MDI-приложения

3. Работа со строками и списочными компонентами

4. Табличные компоненты и работа с ними

5. Графика (отрисовка, работа с изображениями)

6. Графика (работа с Chart-компонентами и анимация в .NET)

7. Интеграция с офисными приложениями

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

В настоящее время нами освоены следующие возможности библиотеки .NET:

  • управление приложением и компонентами с помощью событий и свойств;
  • создание прикладных приложений и вычислительная обработка данных на основе базовых компонент (Panel, Button, Label, TextBox);
  • списочные и табличные компоненты, обработка информации с их помощью (ListView, ListBox, ComboBox, DataGridView, DataGrid);
  • работа с графической канвой и таймером, управление отображением и перемещением графических объектов (PictureBox, Graphics, Timer);
  • работа с текстовыми и структурированными файлами средствами .NET;
  • динамическое создание/удаление компонентов, управление дочерними объектами.

Как правило, в зачётной или экзаменационной работе по подобному курсу задач на "конкретные компоненты" нет (если тип компоненты отдельно не оговорен в условии), а задача состоит в том, чтобы написать работающее приложение согласно поставленному условию и с применением компонент, которые кажутся вам наиболее подходящими для этой цели.

Проект Ex8_1. Реализовать отсортированный по алфавиту список имён с поддержкой операций добавления и удаления элементов, сохранением списка строк в файл и загрузкой его из файла. Допустимые символы в именах – буквы и цифры.

Задачу можно решать как на основе списочных, так и табличных компонент. Поскольку требуется сортировка данных, удобнее решать на основе какого-либо списка, имеющего встроенное свойство Sorted. Форма будет иметь следующий вид:

Повторите, какие свойства формы вы поменяли, чтобы получить такой вид окна?
Повторите, какие свойства формы вы поменяли, чтобы получить такой вид окна?

Справа расположена panel1 со свойством Dock = Right, на ней 4 кнопки button1, ..., button4 для выполнения предусмотренных задачей действий. Слева – список comboBox1 со свойством Dock = Fill.

По загрузке формы настроим список для нашей задачи:

  private void Form1_Load (object sender, EventArgs e) {
   comboBox1.Sorted = true; //список будет сортироваться
   comboBox1.DropDownStyle = ComboBoxStyle.Simple; //развёрнутый вид списка
  }

Кнопка 1 будет добавлять запись, если такой же записи ещё нет в списке:

  private void button1_Click (object sender, EventArgs e) {
   String r = comboBox1.Text;
   if (comboBox1.FindString (r) == -1) comboBox1.Items.Add (r);
  }

Кнопка 2 будет удалять выбранный в списке элемент, если таковой есть:

  private void button2_Click (object sender, EventArgs e) {
   if (comboBox1.SelectedIndex != -1) 
    comboBox1.Items.Remove (comboBox1.SelectedItem);
  }

Кнопка 3 выполнит работу по сохранению файла с использованием поточного класса StreamWriter. Для простоты используем файл с фиксированным именем data.txt, располагающийся в текущей папке.

  private void button3_Click (object sender, EventArgs e) {
   try {
    System.IO.StreamWriter file = new System.IO.StreamWriter ("data.txt");
    for (int i = 0; i < comboBox1.Items.Count; i++)
     file.WriteLine (comboBox1.Items [i].ToString ());
    file.Close ();
   }
   catch (Exception) {
    MessageBox.Show ("Не могу записать data.txt");
   }
  }

Кнопка 4 отвечает за загрузку элементов списка из файла data.txt. Чтобы можно было закрыть дескриптор файла после чтения данных, применим поточный класс StreamReader:

  private void button4_Click (object sender, EventArgs e) {
   try {
    System.IO.StreamReader file = new System.IO.StreamReader ("data.txt");
    String line;
    comboBox1.Items.Clear ();
    while (( line = file.ReadLine () ) != null) 
     comboBox1.Items.Add (line);
    file.Close ();
   }
   catch (Exception) {
    MessageBox.Show ("Не могу открыть data.txt");
   }
  }

Осталось обеспечить ввод только разрешённых символов, для этого достаточно добавить обработчик события KeyPress (там доступно свойство KeyChar, в отличие от KeyCode в обработчике события KeyDown) для списка comboBox1:

  private void comboBox1_KeyPress (object sender, KeyPressEventArgs e) {
   char c = e.KeyChar;
   if (Char.IsLetterOrDigit (c) || c == (char) Keys.Back || c == (char) Keys.Enter)
    return;
   else e.Handled = true;
  }

Задача решена полностью.

 Скачать пример Ex8_1 в архиве .zip с проектом C# Visual Studio 2019 (11 Кб)

Проект Ex8_2. На графической канве отобразить работу светофора с задержкой между состояниями "красный-жёлтый-зелёный" 1 сек.

Форма – пустое окно Windows Forms.

Для решения задачи используем канву формы и программно созданный таймер.

Опишем таймер, счётчик состояний и размер элемента светофора в классе формы:

private Timer timer1;
private int cnt;
private const int size = 120; //ширина и высота окружностей

По событию загрузки формы (Load) настроим размеры окна и инициализируем таймер:

  private void Form1_Load (object sender, EventArgs e) {
   this.ClientSize = new System.Drawing.Size (size, size*3);
   this.DoubleBuffered = true;
   timer1 = new Timer ();
   timer1.Interval = 1000;
   timer1.Tick += (sendr, args) => {
    Invalidate ();
    cnt = ( cnt + 1 ) % 3;
   };
   timer1.Enabled = true;
   cnt = 0;
  }

Обработчик события Tick таймера мы встроили в оператор назначения как стрелочную функцию. Обратите внимание, что этот обработчик таймера только вызывает перерисовку формы (вызов Invalidate(), отправляющий сообщение стандартному методу пререрисовки Paint()) и меняет счётчик состояний cnt, а саму отрисовку будет делать метод Paint:

  private void Form1_Paint (object sender, PaintEventArgs e) {
   Pen [] pens = new Pen [] { Pens.Red, Pens.Yellow, Pens.Green };
   for (int i = 0; i < 3; i++)
    e.Graphics.DrawEllipse (pens [i], 0, i * size, size, size); //контуры 3 кружков
   Brush [] brushes = new Brush [] { Brushes.Red, Brushes.Yellow, Brushes.Green };
   e.Graphics.FillEllipse (brushes [cnt], 0, cnt * size, size, size); //текущий закрасить
  }

Для простоты непосредственно в функции описаны массивы перьев Pen и кистей Brush нужных цветов. Но лучше вынести эти массивы тоже в свойства класса, а создать их один раз в конструкторе формы.

Альтернативный подход (без привязки к методу Paint) – в обработчике события таймера программно создавать Bitmap нужной размерности, выполнять отрисовку на нём, а затем назначать его компоненте PictureBox.

Задача решена полностью.

 Скачать пример Ex8_2 в архиве .zip с проектом C# Visual Studio 2019 (11 Кб)

Проект Ex8_3. Поддержка динамического списка компонент TextBox произвольной размерности. Реализовать добавление и удаление компонент.

Форма – пустая Windows Forms. В качестве примера будем создавать поля ввода TextBox в месте щелчка мышью по форме и удалять их при щелчке мышью на самих полях.

Нам не понадобится отдельно сохранять поля ввода в каком-либо списке, хотя мы могли бы сделать это, например, так:

using System.Collections.Generic;
//...
List <TextBox> F; 
 //динамический список System.Collections.Generic.List объектов типа TextBox
//...
F = new List <TextBox>(); //конструктор списка
//...
F.Add (T); //где T - созданный TextBox

Дело в том, что у формы уже есть контейнер Controls с методами Add и Remove.

В классе формы дополнительно опишем только счётчик контролов:

  int cnt; //счётчик объектов

Инициализируем счётчик в конструкторе формы:

  public Form1 () {
   InitializeComponent ();
   cnt = 0;
  }

Щелчку по форме мышью соответствует событие MouseClick. Достаточно в обработчике этого события формы создать программно новый TextBox и добавить его в список компонент формы:

  private void Form1_MouseClick (object sender, MouseEventArgs e) {
   TextBox T = new TextBox ();
   T.Text = "Text" + ( cnt++ );
   T.Location = new Point (e.X, e.Y);
   T.Parent = this;
   this.Controls.Add (T);
  }

Длина списка не ограничена. Однако если мы хотим удалять компоненты TextBox по какому-то событию, например, по щелчку на них, придётся всем создаваемым TextBox программно назначать обработчик этого события, так что вставим соответствующий код в метод Form1_MouseClick (перед добавлением поля ввода в список контролов):

   T.Click += (sendr, args) => {
    this.Controls.Remove ((TextBox)sendr);
   };

Задача решена полностью.

Альтернативным, но избыточным решением, как сказано выше, были бы статические или динамические массивы компонент.

 Скачать пример Ex8_3 в архиве .zip с проектом C# Visual Studio 2019 (10 Кб)

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

Сделаем всё максимально просто согласно условию задачи.

Нам понадобится растянутый на всю клиентскую часть формы PictureBox (свойство Dock = Fill) и таймер, у которого установлены свойства Enabled = true и Interval = 200.

В классе формы опишем нужные данные:

  public const int width = 10, height = 10, k = 16;
   //размеры игровой доски и одного поля
  public int [,] field = new int [width, height];
   //сама игровая доска
  public int [] hero = new int [] { 1, 1};
   //координаты героя
  public Bitmap bitfield = new Bitmap (k * width, k * height );
   //битмап для отрисовки
  public Graphics gr;
   //графический контекст

Массив в C# автоматически инициализируется нулями, поэтому достаточно выставить в значение "1" те элементы массива поля, которые соответствуют "стенам". Сделаем это в конструкторе формы, там же установим размер окна и получим графический контекст:

  public Form1 () {
   InitializeComponent ();
   //установим размер окна и получим графический контекст:
   this.ClientSize = new Size (k * width, k * height);
   gr = Graphics.FromImage (bitfield);
   //выставим все крайние поля в единицы:
   for (int i = 0; i < width; i++) {
    field [i, 0] = 1;
    field [i, height - 1] = 1;
   }
   for (int i = 0; i < height; i++) {
    field [0, i] = 1;
    field [width - 1, i] = 1;
   }
   //выставьте и некоторые другие поля в единицы...
  }

Обработчик нажатия клавиши формы будет получать текущее положение героя и менять его, если этому не мешают препятствия (в нашем случае - "стены"):

  private void Form1_KeyDown (object sender, KeyEventArgs e) {
   //обрабатываем нажатия клавиш со стрелками
   int x = hero [0], y = hero [1];
   switch (e.KeyCode) {
    case Keys.Left:
     if (x > 0 && field [x - 1, y] == 0) x--;
    break;
    case Keys.Right:
     if (x < width - 1 && field [x + 1, y] == 0) x++;
    break;
    case Keys.Up:
     if (y > 0 && field [x, y-1] == 0) y--;
    break;
    case Keys.Down:
     if (x < height - 1 && field [x , y+1] == 0) y++;
    break;
   }
   hero[0] = x; hero [1] = y;
  }

Обработчик события таймера будет просто запрашивать отрисовку, которую мы, на случай развития приложения, вынесем в отдельный метод DrawMe:

  private void TickTimer_Tick (object sender, EventArgs e) {
   //по таймеру просто запрашиваем отрисовку
   DrawMe ();
  }

  public void DrawMe () {
   //метод для отрисовки поля и героя
   gr.Clear (Color.Black);
   for (int i = 0; i < width; i++)
    for (int j = 0; j < height; j++)
     if (field [i, j] == 1) {
      gr.FillRectangle (Brushes.Green, i * k, j * k, k, k);
      gr.DrawRectangle (Pens.Black, i * k, j * k, k, k);
     }
   gr.FillEllipse (Brushes.Red, hero[0] * k, hero [1] * k, k, k);
   pictureBox1.Image = bitfield;
  }

Задача решена полностью, вот что вышло:

вид приложения, стены - только по краям поля
вид приложения, стены - только по краям поля

 Скачать пример Ex8_4 в архиве .zip с проектом C# Visual Studio 2019 (11 Кб)

13.05.2023, 10:04 [1456 просмотров]


теги: c# учебное программирование

К этой статье пока нет комментариев, Ваш будет первым