Как работать с файлами
Возможность записывать/читать/изменять данные является одним из основополагающих процессов в работе компьютера. Грубо говоря больше то компьютер ничего и не умеет. Он только все время что-то меняет. А устройства на это реагируют и показывают картинку в случае монитора, издают звук если это колонка и т.д.
Нам будет интересовать работа с постоянной памятью. А именно, с файликами. Для начала мы ограничимся работой с текстовыми файлами.
Постановка задачи
Задача: посчитать сумму введенных пользователем чисел больших заданного числа T.
Если в исходной постановке задачи мы ожидали что пользователь будет последовательно вводить числа. То тут будем ожидать, что пользователь создаст файлик, а в файл пропишет набор чисел в виде строки, и само число T отдельной строкой:
3
1 2 3 4 5 6
То есть для такой записи у нас получается индивидуальная задача вида:
- Среди чисел {1, 2, 3, 4, 5, 6} выбрать те, которые больше 3 и подсчитать их сумму.
Добавляем интерфейс
Не будем мудрить и для начала просто создадим форму с одной кнопкой (я изменил свойство Text кнопки, чтобы на ней была осознанная надпись):
кликнем два раза на кнопку чтобы добавить ей обработчик, нас перекинет в редактор кода:
Добавляем файл в проект
Для тестовых целей создадим файлик, кликнем правой кнопкой на название проекта и выберем Добавить/Создать элемент
в списке найдем текстовый файл и дадим ему название task.txt
файлик должен появится в обозревателе решений:
кликнем на этот файлик два раза, чтобы отредактировать его и добавим в него наши несчастные две строчки:
3
1 2 3 4 5 6
Чтобы файлик был доступен в момент запуска программы, установим ему свойство Копировать в выходной каталог на Всегда копировать
Теперь переключимся на код формы (два раза кликнем на файл Form1.cs, затем нажмем F7). И приступим к редактированию обработчика события клика.
Читаем строчки из файла
Правим код:
using System;
// ...
using System.IO; // <<< добавил новый using, чтобы работать с файлами
namespace WindowsFormsApp5
{
public partial class Form1 : Form
{
// ...
private void button1_Click(object sender, EventArgs e)
{
// считываем содержимое файла как набор строк
var lines = File.ReadAllLines("task.txt");
// выведем содержимое на экран
MessageBox.Show(lines[0] + "\n" + lines[1]);
}
}
}
запускаем и проверяем:
Выводим данные на форму
Данные читать умеем теперь давайте выведем их на форму. Я добавлю
- два TextBox, один под строку с числами (имя: txtNumbers), второй под число T (имя: txtT);
- два Label для подписей для текстбоксов;
- кнопку c надписью Решить Задачу, на будущее.
теперь давайте сделаем чтобы по нажатию кнопки Считать данные, вместо MessageBox данные записывались в соответствующие поля на форме. Правим обработчик:
private void button1_Click(object sender, EventArgs e)
{
// считываем содержимое файла как набор строк
var lines = File.ReadAllLines("task.txt");
// заполняем значения полей
txtT.Text = lines[0];
txtNumbers.Text = lines[1];
}
запускаем, проверяем:
Ура! Мы свели задаче к лабе 1, только заполнение у нас теперь автоматическое. Давайте теперь добавим возможность выбрать произвольный файл, и вывести его содержимое на форму.
Добавляем диалоговое окно выбора файла
Правим обработчик:
private void button1_Click(object sender, EventArgs e)
{
var openFileDialog = new OpenFileDialog(); // создаем экземпляр диалогового окна
// если пользователь не нажал OK, то выходим
if (openFileDialog.ShowDialog() != DialogResult.OK)
return;
// считываем содержимое файла как набор строк
var lines = File.ReadAllLines(openFileDialog.FileName); // открываем файл выбранный в диалоговом окне
// заполняем значения полей
txtT.Text = lines[0];
txtNumbers.Text = lines[1];
}
теперь при нажатии на кнопку считать данные будет предложено выбрать файл, директория по умолчанию как правило папка Мои документы. Соответственно, чтобы было что открывать, туда надо файлик с данными для задачи положить.
Папку по умолчанию можно поменять, например, на путь к exe файлу программы:
private void button1_Click(object sender, EventArgs e)
{
var openFileDialog = new OpenFileDialog(); // создаем экземпляр диалогового окна
openFileDialog.InitialDirectory = AppDomain.CurrentDomain.BaseDirectory;
//...
}
тогда файлик никуда ложить не придется, он в папке рядом с программой будет уже лежать. Это потому, что мы указали файлу task.txt, свойство Копировать в выходной каталог на Всегда копировать.
Вернемся собственно к задаче.
Добавляю логику
Внедряем класс Logic, как и раньше, класс Form1 не трогаем, а после него добавляем класс Logic:
namespace WindowsFormsApp5
{
// это не трогаем
public partial class Form1 : Form
{
// ...
}
// класс с логикой
public class Logic
{
/**
* одна функция, которая принимает на вход массив и число
* а выдает число
*/
public static int Compute(int[] array, int T)
{
int sum = 0;
foreach(var el in array)
{
if (el > T)
{
sum += el;
}
}
return sum;
}
}
}
Добавим обработчик для решения задачи
Переключимся на форму, и кликнем дважды на кнопку Решить задачу чтобы добавить обработчик события клика для этой кнопки.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var openFileDialog = new OpenFileDialog();
// ...
txtNumbers.Text = lines[1];
}
private void button2_Click(object sender, EventArgs e)
{
// ЭТО НОВЫЙ ОБРАБОТЧИК, дальше будем работать с ним
}
}
Конвертируем данные
И так, у нас есть данные на форме, теперь надо их передать нашей функции в логике.
Давайте думать в чем у нас проблема. Мы знаем, что в txtNumbers у нас массив чисел, а в txtT число T, но мы не можем написать наш обработчик как
public partial class Form1 : Form
{
// ...
private void button2_Click(object sender, EventArgs e)
{
int sum = Logic.Compute(txtNumbers.Text, txtT.Text);
// выведем содержимое на экран
MessageBox.Show("Сумма чисел равна: " + sum.ToString());
}
}
такой код даже не скомпилируется, ведь txtNumbers.Text и txtT.Text просто возвращает текст. Следовательно, нам надо этот текст преобразовать. Преобразовать значение txtT в число это как раз чихнуть, мы делали это уже много раз, достаточно написать:
var T = int.Parse(txtT.Text);
а вот как нам разбить значение txtNumbers.Text на набор чисел? Тут нам пригодится метод Split, который позволяет разбить строку с помощью указанного разделителя (в нашем случае это будет пробел)
Преобразуем строку в массив чисел
Есть как минимум два способа сделать, первый весьма громоздкий:
// разобьем строку пробелами на элементы, параметры несут следующий смысл:
// new[] { " " } -- это список разделителей, то бишь только пробел
// StringSplitOptions.RemoveEmptyEntries -- означает что мы просим убирать пустые элементы,
// например, если рядом стоят два пробела, то технически между ними пустая строка
// чтобы такие пустых строк не появлялось, добавляется этот параметр.
var numbersAsStrings = txtNumbers.Text.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
// заведем список, куда будем добавлять преобразованные в числа элементы из разбитой строки
var numbers = new List<int>();
// в цикле каждый элемент разбитой строки, конвертируем в число и добавляем в массив numbers
foreach(var number in numbersAsStrings)
{
numbers.Add(int.Parse(number));
}
// преобразуем список в массив, чтобы можно было передать результат в метод Compute
var array = numbers.ToArray();
но такая форма записи очень страшная, так что предлагаю альтернативный способ из прекрасного мира лямбда-выражений:
// первая строка такая же
var numbersAsStrings = txtNumbers.Text.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
// а тут преобразование на лету, с использованием анонимной лямбда-функции: x => int.Parse(x)
var array = numbersAsStrings.Select(x => int.Parse(x)).ToArray();
И так, получим такой обработчик:
private void button2_Click(object sender, EventArgs e)
{
var T = int.Parse(txtT.Text);
var numbersAsStrings = txtNumbers.Text.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
var array = numbersAsStrings.Select(x => int.Parse(x)).ToArray();
int sum = Logic.Compute(array, T);
// выведем содержимое на экран
MessageBox.Show("Сумма чисел равна: " + sum.ToString());
}
навешаем обработку ошибок, чтобы не падал на пустых TextBox`ах:
private void button2_Click(object sender, EventArgs e)
{
int T;
int[] array;
try
{
T = int.Parse(txtT.Text);
var numbersAsStrings = txtNumbers.Text.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
array = numbersAsStrings.Select(x => int.Parse(x)).ToArray();
} catch (FormatException)
{
return;
}
int sum = Logic.Compute(array, T);
// выведем содержимое на экран
MessageBox.Show("Сумма чисел равна: " + sum.ToString());
}
Выводим результат на форму
Для вывода на форму, добавим:
- новое поле типа RichTextBox, установим его свойству (Name) значение txtAnswer
- новую кнопку со свойством Text равным Сохранить в файл.
кликнем два раза на вторую кнопку Решить задачу, чтобы перейти на ее разработчик. И откорректируем его:
private void button2_Click(object sender, EventArgs e)
{
int T;
// ...
int sum = Logic.Compute(array, T);
// MessageBox.Show("Сумма чисел равна: " + sum.ToString()); << эту строчку убираем:
// чтобы была возможность использовать форматирование прям для строки
// создадим экземпляр специального класса StringBuilder
var answerString = new StringBuilder();
// добавим строку с форматированием
answerString.AppendFormat("Сумма чисел больших {0} равна {1}", T, sum);
// запишем результат нашей строки в новодобавленное поле,
// тут обязательно вызвать метод ToString
txtAnswer.Text = answerString.ToString();
}
Сохраняем результат в файл
Переключимся на форму (кликнув два раза на Form1.cs в обозревателе решений). Затем кликнем два раза на кнопку Сохранить в файл чтобы добавить ей обработчик события. Окажемся в редакторе кода:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var openFileDialog = new OpenFileDialog();
// ...
txtNumbers.Text = lines[1];
}
private void button2_Click(object sender, EventArgs e)
{
int T;
// ...
txtAnswer.Text = answerString.ToString();
}
private void button3_Click(object sender, EventArgs e)
{
// НОВЫЙ ОБРАБОТЧИК, дальше работаем здесь
}
}
записывать в файл не сильно сложнее чем читать из него, достаточно сделать следующее:
public partial class Form1 : Form
{
// ...
private void button3_Click(object sender, EventArgs e)
{
// первый параметр: в какой файл записывать,
// второй параметр: что именно записывать
File.WriteAllText("answer.txt", txtAnswer.Text);
}
}
так как, бог его знает где этот файл находится, можно либо предложить пользователю указать путь к сохраняемому файлу, абсолютно по аналогии с чтением из файла:
public partial class Form1 : Form
{
// ...
private void button3_Click(object sender, EventArgs e)
{
var openFileDialog = new OpenFileDialog(); // создаем экземпляр диалогового окна
// если пользователь не нажал OK, то выходим
if (openFileDialog.ShowDialog() != DialogResult.OK)
return;
File.WriteAllText(openFileDialog.FileName, txtAnswer.Text);
}
}
либо можно прям этот файл открыть:
public partial class Form1 : Form
{
// ...
private void button3_Click(object sender, EventArgs e)
{
File.WriteAllText("answer.txt", txtAnswer.Text);
// открываем файл приложением по умолчанию
System.Diagnostics.Process.Start("answer.txt");
}
}
Вот собственно все так просто.
Как преобразовать текст в матрицу (добавлено 21/11/2018)
Чтобы преобразовать текст в число добавьте себе в код куда-нибудь такую функцию, например прям в класс Form
namespace WindowsFormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public static int[,] TextToMatrix(string text)
{
var lines = text.Split(new string[] { Environment.NewLine, "\n" }, StringSplitOptions.RemoveEmptyEntries);
var colsCount = lines[0].Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).Length;
var rowsCount = lines.Length;
var matrix = new int[rowsCount, colsCount];
for (var i = 0; i < lines.Length; ++i)
{
var elements = lines[i].Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
for (var j = 0; j < elements.Length; ++j)
{
matrix[i, j] = int.Parse(elements[j]);
}
}
return matrix;
}
// ...
}
}
Допустим вы создали файлик в проекте matrix.txt, с содержимым:
1 2 3 4
6 7 8 9
10 12 12 13
14 15 16 17
указали у него свойство “Копировать в выходной каталог” на Всегда копировать
Теперь если вам нужно текст из файла преобразовать в матрицу вы пишите
namespace WindowsFormsApp
{
public partial class Form1 : Form
{
// ...
public static int[,] TextToMatrix(string text)
{
// ...
}
private void button1_Click(object sender, EventArgs e)
{
var text = File.ReadAllText("matrix.txt"); // считали содержимое из файла, сохранили в переменную text
int[,] matrix = TextToMatrix(text); // превратили текст в матрицу
// а дальше работаете с ней как обычно
// например тут я собираю матрицу в строку чтобы отобразить ее в сообщении
string message = "";
for (var i = 0; i < matrix.GetLength(0); ++i)
{
for (var j = 0; j < matrix.GetLength(1); ++j)
{
message += matrix[i, j].ToString() + " ";
}
message += "\n";
}
MessageBox.Show(message);
}
}
}
Но по заданию вам надо будет считать содержимое файла в компоненту, а потом брать текст уже с компоненты, тут в приниципе почти ничего не меняется:
namespace WindowsFormsApp
{
public partial class Form1 : Form
{
// ...
public static int[,] TextToMatrix(string text)
{
// ...
}
private void button1_Click(object sender, EventArgs e)
{
var text = txtMatrix.Text; // считали значение компоненты RichTextBox со свосйством (Name):txtMatrix, сохранили в переменную text
int[,] matrix = TextToMatrix(text); // превратили текст в матрицу
// а дальше работаете все то же самое
string message = "";
for (var i = 0; i < matrix.GetLength(0); ++i)
{
for (var j = 0; j < matrix.GetLength(1); ++j)
{
message += matrix[i, j].ToString() + " ";
}
message += "\n";
}
MessageBox.Show(message);
}
}
}