Партнерка на США и Канаду по недвижимости, выплаты в крипто
- 30% recurring commission
- Выплаты в USDT
- Вывод каждую неделю
- Комиссия до 5 лет за каждого referral
Создание Windows-приложения на C#, редактирующего бинарный файл
Целью приложения является возможность визуального редактирования содержимого байтового потока, связанного с бинарным файлом.
1. Открыть решение solWriteReadFiles.
2. В окне Solution Explorer над заголовком решения solWriteReadFiles в контекстном меню (правая кнопка мышки) выбрать команду Add->New Project…
3. В открывшемся окне Add New Project
a. на левой панели Project Types: выбрать узел Visual C#->Windows,
b. на правой панели Templates: выбрать шаблон Windows Forms Application
c. в строке Name: набрать имя проекта prEditBinFile
d. щелкнуть OK
4. Должно появиться окно дизайнера проекта с именем Form1.cs [Design] с изображением пустого окна приложения серого цвета с заголовком Form1.
В этом окне визуально редактируются свойства окна приложения, изображенного серым цветом.
1. Изменим имя основного файла проекта. Это файл Program. cs, который находится в узле проекта prEditBinFile окна Solution Explorer. Для изменения имени следует вызвать контекстное меню, щелкнув правой кнопкой над строкой Program. cs, и выбрать команду Rename. После этого набрать новое имя prEditBinFile. cs. Далее, подтвердить изменения, которые возникнут.
2. Изменим имя файла формы Form1.cs аналогично, выбрав имя fEditBinFile. cs.
3. Перейдем к редактированию свойств окна. Раздвинем серое окно примерно на половину поля внешнего окна, захватив курсором мышки небольшие квадраты, расположенные на правой и нижней границе окна, и потянув мышку вправо и вниз соответственно, либо потянув за квадрат в нижнем правом углу по диагонали вправо и вниз.
4. Изменим другие свойства окна приложения, используя окно Properties (если окно Properties не открыто, это можно сделать через меню View среды)
a. В окне Properties найдем свойство Text, содержащее в правой колонке текст Form1. Это заголовок окна по умолчанию. Изменим значение этого свойства на Edit Binary File. После нажатия клавиши Enter это изменение должно появиться в заголовке окна на экране.
b. Значение другого свойства Start Position (начальное положение окна на экране после запуска приложения), по умолчанию равного WindowsDefaultLocation (положение по умолчанию), изменим на CenterScreen. Теперь после запуска приложения окно будет изображаться в центре экрана.
5. В окне Solution Explorer вызовем контекстное меню над заголовком проекта prEditBinFile и выберем команду Set As Startup Project. Теперь при активации решения кнопкой с зеленой стрелкой (команда Start Debugging) активироваться будет именно последний проект prEditBinFile.
6. Активируем приложение, чтобы увидеть результат. Пока пустое окно с заголовком Edit Binary File должно появиться в центре экрана.
Файловый состав приложения
Созданное приложение состоит из двух существенных для программиста файлов, которые мы переименовали. Это файл проекта prEditBinFile. cs и файл формы fEditBinFile. cs. Основной файл, который мы будем редактировать, это файл формы fEditBinFile. cs. Файл проекта prEditBinFile. cs является собственно файлом приложения, так как именно в нем описана функция main, но редактировать его мы не будем, а оставим в том виде, в котором его создала среда.
Команда Code из меню View открывает панель с кодом файла fEditBinFile. cs.
Файл fEditBinFile. cs содержит описание класса с именем fEditBinFile.
Пространство имен
Файл открывается списком пространств имен, в которых описаны некоторые стандартные классы. Пространства имен есть подобие разделов библиотеки. Каждый класс имеет полное имя, включающее имя пространства имен, в котором он описан. Но если написать в начале кодового файла список using с именами пространств имен, то в коде можно использовать короткие имена классов.
По умолчанию список пространств имен имеет, как мы видим, вид
using System;
using System. Collections. Generic;
using ponentModel;
using System. Data;
using System. Drawing;
using System. Linq;
using System. Text;
using System. Windows. Forms;
Далее открывается пространство имен, в котором описано создаваемое приложение и класс формы. Имя этого пространства имен совпадает с именем проекта
namespace prEditBinFile
{
Закрывающая фигурная скобка } в конце файла ограничивает описание, входящее в это пространство имен.
Класс формы
Внутри пространства имен описан класс формы fEditBinFile.
Описание состоит из заголовка, скобок {} и списка членов класса – полей, методов и т. д.
В нашем файле это описание имеет вид
public partial class fEditBinFile : Form
{
public fEditBinFile()
{
InitializeComponent();
}
}
Конструктор
Метод с тем же именем fEditbinFile, что и класс, называется конструктором. В нем инициализируются поля класса. В данном случае инициализация происходит через вызов метода InitializeComponent. Содержание InitializeComponent редактируется целиком и полностью дизайнером среды. Если пользователь добавляет свои поля и инициализирует их в конструкторе, то это делается после вызова InitializeComponent.
Содержимое метода InitializeComponent и некоторые другие члены класса fEditBinFile описаны в отдельном файле fEditBinFile. Desiner. cs, который содержит другую часть описания класса fEditBinFile. То, что описание одного класса разделено на две части, указано модификатором partial в заголовке класса.
Модификаторы доступа
Заголовок описания класса fEditBinFile и конструктора имеет так называемый модификатор доступа public.
Модификаторы указывают степень доступа: public – доступен любому внешнему пользователю, protected – доступен только так называемым классам-наследникам, private – доступен только членам самого класса. Из контекста видно, что сам класс и его конструктор доступен любому клиенту. Однако вскоре встретятся члены класса, в которых в основном будет использоваться модификатор private, либо не использоваться никакой модификатор доступа, что опять соответствует private.
Управляющие элементы и их свойства
Дальнейшие действия в построении приложения будут сводиться к редактированию файла fEditBinFile. cs. При этом визуальные изменения на форме и изменения, проводимые через окно Properties, будут отображаться дизайнером в коде второго файла формы fEditBinFile. Desiner. cs. В этот последний файл можно даже не заглядывать и, во всяком случае, не редактировать.
Откройте окно с изображением формы fEditBinFile. cs [Design]
Для выбора управляющих элементов и компонент, размещаемых на форме приложения, используется окно Toolbox. Если это окно отсутствует, откройте его через меню View.
1. В разделе Menus & Toolbars окна Toolbox находится компонента ToolStripContainer. Щелкните по этой компоненте, затем по полю формы. Эти действия помещают на форму компоненту из Toolbox. Перенесенный на форму управляющий элемент является объектом класса ToolStripContainer. Он представляет собой контейнер, в котором будут располагаться другие управляющие элементы. Контейнер ToolStripContainer состоит из пяти панелей – центральной панели и 4-ех панелей, расположенных по периметру. На центральную панель обычно помещают компоненты, используемые для изображения основной информации (графики, таблицы, текстовые редакторы и т. п.). Боковые панели слева и справа используются для размещения таких управляющих элементов как кнопки, списки выбора и т. п. На верхней панели размещают главное меню, а на нижней - компоненты строки статуса. Обратите внимание, что окно среды организовано именно таким образом.
2. После переноса элемента ToolStripContainer на форму щелкните по стрелке, расположенной в правой части верхней границы рамки. Появится небольшая панель ToolStripConainer Tasks, на которую выведены настройки структуры и внешнего вида компоненты ToolStripContainer. Следует выбрать команду Dock Fill In Form внизу панели. В этом случае компонента заполнит всю рабочую область окна, и ее размеры будут меняться синхронно с размерами окна.
3. Управляющий элемент получил по умолчанию имя toolStripContainer1. В этом можно убедиться разными способами.
a. В частности, взглянув в окно Properties. Окно Properties содержит свойства управляющих элементов, расположенных на форме и свойства самой формы. Эти свойства отображаются, если на центральной панели среды находится изображение окна fEditBinFile. cs [Design], но не изображение кода fEditBinFile. cs. В строке верхней части окна Properties находится имя того элемента, который выделен в изображении (для выделения элемента по нему достаточно щелкнуть мышкой). Среди свойств любого элемента есть свойство Name, содержащее это же имя.
b. В конце кода файла дизайнера fEditBinFile. Desiner. cs (откройте его из окна Solution Explorer) в качестве поля формы появилась строка private System. Windows. Forms. ToolStripContainer toolStripContainer1;. Это строка описания объекта toolStripContainer1 класса ToolStripContainer из пространства имен System. Windows. Forms (дизайнер обычно дает полное имя класса, несмотря на наличие строк using в начале файла кода). Убедитесь в этом, а так же в том, что, в частности, свойство Dock в окне Properties установлено в значение Fill. В теле метода InitializeComponent файла fEditBinFile. Desiner. cs установка свойства Dock выглядит как строка кода this. toolStripContainer1.Dock = System. Windows. Forms. DockStyle. Fill;. Здесь символ this может быть опущен, так как он указывает на объект того же класса формы, но дизайнер предпочитает полные имена.
4. Вернитесь к окну с изображением формы и закройте вкладку с кодом дизайнера fEditBinFile. Desiner. cs, чтобы случайно не повредить его. На центральную панель контейнера поместите элемент DataGridView из раздела Data окна Toolbox. Это таблица, в которую будут заноситься байты редактируемого потока.
a. Как и в предыдущем случае, воспользуйтесь командой Dock in parent container. Таблица заполнит всю центральную панель.
b. На той же панели DataGridView Tasks снимите флажки со свойств Enable Adding и Enable Deleting. Эти свойства по умолчанию позволяют (enable) пользователю в процессе счета выполнять добавление (adding) и стирание (deleting) строк таблицы. В приложении не предполагается возможность изменения пользователем этого параметра, так как число строк определяется объемом потока.
c. Команда AddColumn… открывает окно Add Column, в котором указываются свойства добавляемого в таблицу столбца. Добавьте в таблицу 5 столбцов, указав в их заголовках (свойство HeaderText) числа от 1 до 5.
На этой стадии можно скомпилировать проект командой prEditBinFile из меню Build и, если компиляция пройдет успешно, активировать проект командой Start Debugging (кнопка с зеленой стрелочкой).
5. Число строк в таблице и содержимое ячеек будет определяться динамически внутри кода приложения в зависимости от объема и содержания потока. Для установки этих параметров в коде формы можно добавить описание трех полей, определяющих верхний предел объема потока _maxLength (имена полей обычно предваряются знаком подчеркивания), нижний его предел _minLength и объект _rnd класса Random, который генерирует случайные числа:
int _maxLength=1000;
int _minLength=100;
Random _rnd=new Random();
Описание можно поместить в любом месте внутри скобок, ограничивающих тело класса fEditbinFile, но не внутри конструктора или какого-либо метода.
6. Для заполнения таблицы случайными байтами случайно выбранного объема ниже описания полей напишем метод
/// <summary>
/// Устанавливает случайный объем байтов и
/// заполняет таблицу случайными байтами.
/// </summary>
void Randomize()
{
// Задаем случайную длину потока в интервале [_minLength;_maxLength)
int streamLength = _rnd. Next(_maxLength - _minLength) + _minLength;
// Определяем число строк в таблице в зависимости от длины потока
dataGridView1.RowCount = streamLength / dataGridView1.ColumnCount;
// Если длина потока не делится нацело на число столбцов,
// добавляем одну строку
if (streamLength % dataGridView1.ColumnCount!= 0)
dataGridView1.RowCount++;
// Текущие номера строк и колонок в таблице
int row, col;
// В цикле по всем элементам потока заполняем ячейки таблицы слева направо
// и сверху вниз
for (int i = 0; i < streamLength; i++)
{
// Определяем текущий номер строки столбца
row = i / dataGridView1.ColumnCount; col = i % dataGridView1.ColumnCount;
// Устанавливаем заголовок строки от 1 и далее при смене строки
if (col == 0)
dataGridView1.Rows[row].HeaderCell. Value = (row + 1).ToString();
// Пишем в таблицу случайные значения байтов
dataGridView1[col, row].Value = _rnd. Next(256);
}
}
7. Метод Randomize() может быть вызван при загрузке формы. Для этого следует присоединить к форме обработчик события Load (загрузка). В окне Properties щелкните по иконке с изображением молнии. Это откроет панель, в которой перечислены обработчики событий формы. Одно из событий называется Load. Дважды щелкнув по полю, расположенному справа, получим в окне кода скелет метода с именем fEditBinFile_Load. Внутри (между фигурными скобками) следует написать вызов метода Randomize();
На этой стадии после активации командой Start Debugging проекта должна появиться таблица, заполненная байтами. Содержание таблицы можно редактировать.
8. Если необходимо, чтобы метод Randomize мог быть вызван пользователем в любое время, следует разместить на форме кнопку, щелчок по которой вызывает метод Randomize(). С этой целью
a. В окне Toolbox, в разделе Menus & Toolbars выбрать компоненту ToolStrip и перенести ее на левую панель контейнера;
b. Щелкнуть по стрелочке этого компонента и выбрать из появившегося списка кнопку (Button). По умолчанию эта кнопка получит имя toolStripButton1. Можно изменить ряд ее свойств через окно Properties
i. Имя (Name) установить в rndButton
ii. Свойство DisplayStyle изменить на Text, а свойство Image очистить командой Clear в появившемся окне после щелчка по кнопке с многоточием.
iii. Свойство Text заменить на Randomize.
iv. На вкладке событий (кнопка с молнией) выбрать событие Click и дважды щелкнуть по полю справа.
v. В открывшемся скелете обработчика rndButton_Click набрать вызов метода Randomize();
Активируйте проект командой Start Debugging. Убедитесь, что щелчок по кнопке Randomize действительно меняет содержимое и размеры таблицы.
9. Внесем некоторые усовершенствования в изображение таблицы. Для этого выделим таблицу dataGridView1 и войдем в окно Properties
a. Во-первых, заголовки строк, изображающие целые числа, плохо видны. Это можно исправить, изменив свойство RowHeadersWidthSizeMode. По умолчанию это свойство имеет значение EnabledResizing. Измените его на AutoSizeToDisplayedHeaders. Вновь активировать приложение, проверив эффект. Заголовки строк должны изображаться полностью.
b. Во-вторых, было бы приятней, чтобы цифры в заголовках столбцов изображались в середине ячеек. Для этого надо выбрать свойство ColumnHeadersDefaultCellStyle и в открывшемся после щелчка по кнопке с многоточием окне изменить свойство Alignment с MiddleLeft на MiddleCenter. Сделайте и проверьте эффект.
c. Такую же процедуру проделайте со свойством DefaultCellStyle. Убедитесь, что после нее все ячейки таблицы заполняются числами, изображаемыми в центре.
10. Для сохранения содержания таблицы в файле и, возможно, чтения из файлового потока следует поместить на форму меню с соответствующими командами:
a. В разделе Menus & Toolbars окна Toolbox выбрать компоненту класса MenuStrip и поместить ее на верхнюю панель контейнера. Далее
i. В появившемся окошке Type Here набрать имя меню File.
ii. В окошке ниже набрать Save
iii. В новом окошке ниже набрать Open
iv. Дважды щелкнуть по окошку Save. В окне кода должен появиться скелет обработчика saveToolStripMenuItemClick, который будет срабатывать каждый раз, когда будет выбрана команда Save меню File.
11. Теперь следует поместить на форму компоненту диалогового окна, позволяющего назвать файл и выбрать каталог для его сохранения. Для этого откройте окно Toolbox и из раздела Dialogs перенесите на форму компоненту SaveFileDialog. Объект примет имя saveFileDialog1.
12. Добавьте в начало файла ссылку на пространство имен System. IO, содержащее классы Stream, File, которые потребуются для работы с потоком
using System. IO;
13. Диалоговое окно saveFileDialog1 должно открываться при выборе команды Save меню File. После чего в файл должно записываться содержание таблицы. Все это обеспечит код, который следует поместить внутрь (между фигурными скобками) обработчика saveToolStripMenuItemClick
// Если выбор файла не был сделан, метод прекратит свою работу
if (saveFileDialog1.ShowDialog() != DialogResult. OK) return;
// Создается поток s для записи в файл,
// ассоциированный с файлом, открытом в диалоговом окне
using (Stream s = saveFileDialog1.OpenFile())
// в цикле по всем строкам и столбцам таблицы
for (int row = 0; row < dataGridView1.RowCount; row++)
for (int col = 0; col < dataGridView1.ColumnCount; col++)
{
// Если ячейка таблицы пустая, то цикл завершается.
// Ожидается, что это возможно только в том случае,
// когда последняя строка заполнена не до конца
if (dataGridView1[col, row].Value == null) break;
// Байты записываются из ячеек в поток
s. WriteByte(Convert. ToByte(dataGridView1[col, row].Value));
}
14. Проверьте работу приложения, активировав его и выбрав команду Save меню File. Назовите файл по своему желанию и поместите его в каталог документов, который откроется по умолчанию. Посмотрите содержание получившегося бинарного файла.
15. Теперь заполним таблицу, получая поток из файла. Для этого добавим к форме из окна Toolbox диалоговое окно OpenFileDialog, которое примет по умолчанию имя openFileDialog1.
16. Щелкните дважды по команде меню Open. Откроется обработчик openToolStripMenuItem_Click, внутрь которого поместите код, считывающий поток из файла и помещающий байты потока в таблицу dataGridView1
// Если выбор файла не был сделан, метод прекратит свою работу
if (openFileDialog1.ShowDialog() != DialogResult. OK) return;
// Рабочие переменные, временно хранящие строку и колонку таблицы
int row, col;
using (Stream s = File. OpenRead(openFileDialog1.FileName))
{
// Длина потока возвращается свойством Length
// определяем число строк в таблице
dataGridView1.RowCount = (int)s. Length / dataGridView1.ColumnCount;
// Если длина потока не делится нацело на число столбцов,
// добавляем одну строку
if (s. Length % dataGridView1.ColumnCount!= 0)
dataGridView1.RowCount++;
// В цикле по всему потоку информация из файла заносится в ячейки таблицы
for (int i = 0; i < s. Length; i++)
{
// Определяем текущий номер строки и колонки
row = i / dataGridView1.ColumnCount;
col = i % dataGridView1.ColumnCount;
// Указываем заголовок строки
if (col == 0)
dataGridView1.Rows[row].HeaderCell. Value = (row + 1).ToString();
// Заносим текущий байт в ячейку
dataGridView1[col, row].Value = s. ReadByte();
}
}
17. Проверьте работу приложения на этом этапе.
18. Имеет смысл на этом этапе немного оптимизировать код. Во-первых, видно, что обработчики загрузки формы fEditBinFile_Load и клика кнопки rndButton_Click выполняют один и тот же код – вызов метода Randomize(). Эти обработчики имеют одинаковое число параметров одного и того же типа object sender, EventArgs e. Говорят, что эти два метода имеют одинаковую сигнатуру. Так как их коды совпадают, то к обработке клика кнопки можно присоединить метод загрузки формы fEditBinFile_Load, а обработчик rndButton_Click убрать. Для этой редакции необходимо
a. Выбрать кнопку Randomize в окне дизайнера формы fEditBinFile [Design].
b. Открыть окно Properties.
c. Войти на панель событий (кнопка с молнией).
d. В строке события Click стереть ссылку rndButton_Click.
e. В той же строке щелкнуть по кнопке со стрелочкой, направленной вниз.
f. Выбрать из списка обработчик fEditBinFile_Load.
g. В редакторе кода fEditBinFile стереть обработчик rndButton_Click.
h. Проверить работу программы, испытав так же кнопку Randomize.
19. Во-вторых, код методов Randomize и openToolStripMenuItem_Click имеет много схожего. Ведь в обоих методах происходит заполнение таблицы информацией. Имеет смысл использовать этот факт, выделив схожие операторы в отдельный метод. К схожим операторам относятся
a. В методе Randomize
// Определяем число строк в таблице в зависимости от длины потока
dataGridView1.RowCount = streamLength / dataGridView1.ColumnCount;
// Если длина потока не делится нацело на число столбцов,
// добавляем одну строку
if (streamLength % dataGridView1.ColumnCount!= 0)
dataGridView1.RowCount++;
// Текущие номера строк и колонок в таблице
int row, col;
// В цикле по всем элементам потока
// заполняем ячейки таблицы слева направо и сверху вниз
for (int i = 0; i < streamLength; i++)
{
// Определяем текущий номер строки столбца
row = i / dataGridView1.ColumnCount;
col = i % dataGridView1.ColumnCount;
// Устанавливаем заголовок строки от 1 и далее при смене строки
if (col == 0)
dataGridView1.Rows[row].HeaderCell. Value = (row + 1).ToString();
// Пишем в таблицу случайные значения байтов
dataGridView1[col, row].Value = _rnd. Next(256);
}
b. В методе-обработчике openToolStripMenuItem_Click
// Длина потока возвращается свойством Length
// определяем число строк в таблице
dataGridView1.RowCount = (int)s. Length / dataGridView1.ColumnCount;
// Если длина потока не делится нацело на число столбцов,
// добавляем одну строку
if (s. Length % dataGridView1.ColumnCount!= 0)
dataGridView1.RowCount++;
// В цикле по всему потоку информация из файла
// заносится в ячейки таблицы
for (int i = 0; i < s. Length; i++)
{
// Определяем текущий номер строки и колонки
row = i / dataGridView1.ColumnCount;
col = i % dataGridView1.ColumnCount;
// Указываем заголовок строки
if (col == 0)
dataGridView1.Rows[row].HeaderCell. Value = (row + 1).ToString();
// Заносим текущий байт в ячейку
dataGridView1[col, row].Value = s. ReadByte();
}
20. Эти фрагменты кода можно оформить в виде отдельного метода с именем, например, ResetGrid. Но надо заметить, что в указанных фрагментах есть отличия. В методе Randomize длина потока содержится в локальной переменной streamLength и байты, помещаемые в таблицу, возвращаются методом _rnd. Next(256). В то же время, в методе openToolStripMenuItem_Click длина потока возвращается свойством s. Length потока, а байты возвращаются из потока методом s. ReadByte(). Что касается длины потока, то в новый метод ResetGrid можно ввести параметр типа int streamLength. И при вызове метода ResetGrid давать параметру разные значения: в случае Randomize вызывать ResetGrid(streamLength), в случае загрузки из файла ResetGrid( s. Length).
21. Второе отличие, касающееся самих байтов, обойти немного сложнее. Не желательно копировать получаемые байты в отдельный массив памяти для временного хранения, а затем передавать массив методу ResetGrid. Это расточительно при больших массивах.
22. Другой способ – добавить в метод ResetGrid "говорящий" параметр source (источник), назвав его тип Source, с двумя говорящими значениями random и fileStream. В зависимости от значения этого параметра байты будут создаваться случайным образом (random) или загружаться из файлового потока (fileStream). Тип Source надо предварительно описать внутри класса формы (вне описания методов, в любом месте) декларацией
/// <summary>
/// Тип перечисления, используемый
/// для определения источника байтов,
/// записываемых в таблицу
/// </summary>
enum Source { random, fileStream };
23. Теперь описание нового метода ResetGrid можно оформить в виде
/// <summary>
/// Заполняет таблицу байтами, полученными от источника
/// </summary>
/// <param name="streamLength">
/// Длина потока (число байтов)
/// </param>
/// <param name="source">
/// Источник потока
/// </param>
void ResetGrid(int streamLength, Source source)
{
// Определяем число строк в таблице в зависимости от длины потока
dataGridView1.RowCount = streamLength / dataGridView1.ColumnCount;
// Если длина потока не делится нацело на число столбцов,
// добавляем одну строку
if (streamLength % dataGridView1.ColumnCount!= 0)
dataGridView1.RowCount++;
// Текущие номера строк и колонок в таблице
int row, col;
// В цикле по всем элементам потока заполняем ячейки таблицы слева направо
// и сверху вниз
for (int i = 0; i < streamLength; i++)
{
// Определяем текущий номер строки столбца
row = i / dataGridView1.ColumnCount; col = i % dataGridView1.ColumnCount;
// Устанавливаем заголовок строки от 1 и далее при смене строки
if (col == 0)
dataGridView1.Rows[row].HeaderCell. Value = (row + 1).ToString();
// Пишем в таблицу либо случайные значения байтов,
// либо значения, взятые из потока s, в зависимости от значения
// параметра source
dataGridView1[col, row].Value =
// Это так называемое условное выражение
source == Source. fileStream? s. ReadByte() : _rnd. Next(256);
}
}
24. Компиляция командой Rebuild покажет, что объект s, используемый в условном выражении в конце метода ResetGrid не описан. Действительно, поток s описан в обработчике openToolStripMenuItem_Click в качестве локального объекта и не доступен коду метода ResetGrid. Беде можно помочь, если описать s как поле формы, добавив вне описания методов строку
/// <summary>
/// Поток ввода информации из файла
/// </summary>
Stream s;
25. Ошибка компиляции исчезнет. Теперь следует изменить содержание методов Randomize и openToolStripMenuItem_Click, убрав из них код, обобщенный методом ResetGrid, и внеся в них вызов этого метода с определенными параметрами.
26. Новая редакция методов Randomize и openToolStripMenuItem_Click будет выглядеть следующим образом
void Randomize()
{
// Задаем случайную длину потока в интервале [_minLength;_maxLength)
int streamLength = _rnd. Next(_maxLength - _minLength) + _minLength;
// Заносим в таблицу случайные байты
ResetGrid(streamLength, Source. random);
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
// Если выбор файла не был сделан, метод прекратит свою работу
if (openFileDialog1.ShowDialog() != DialogResult. OK) return;
// Создаем файловый поток байтов
s = File. OpenRead(openFileDialog1.FileName);
try
{
// Заполняем байтами таблицу
ResetGrid((int)s. Length, Source. fileStream);
}
finally
{
// Освобождаем поток и файл не зависимо от наличия ошибки
s. Close();
}
}
Здесь используются операторы try…finally. Они позволяют в блоке finally освободить поток и файл, используемые в операторах блока try, даже в том случае, если возникает ошибка при выполнении этих операторов.
27. Имя s не является подходящим для поля формы, так как оно ничего не говорит об этом поле. Лучше его переименовать в _fileStreamIn (входящий файловый поток) со знаком подчеркивания как это принято. Для переименования поля s (и вообще любых имен в приложениях) следует
a. Навести курсор на имя s в любом месте кода.
b. Войти в меню Refactor.
c. Вызвать команду Rename.
d. Набрать новое имя.
e. Подтвердить переименование.
f. Убедиться, что переименование сделано.
28. Активируйте проект, убедившись в его работоспособности.
29. Можно написать иную версию метода ResetGrid, выбрав другой тип второго параметра. Это немного упростит код, не требуя описания поля потока _fileStreamIn и описания типа Source в форме. Поток ввода s останется локальным в методе openToolStripMenuItem_Click, как это было в первой редакции. Останется и оператор using, что не потребует явного вызова метода Close потока. Для этого требуется новая редакция кода, отличная от проведенной в пунктах 22-27. Но не спешите ее делать, стирая только что сделанные изменения. Можно сделать новую редакцию, не теряя прежней!
30. Новая редакция предполагает следующие изменения, которые пока не следует проводить явно
a. Убрать описания типа Source и поля _fileStreamIn в форме.
b. Параметр source метода ResetGrid описать типом object, а не Source, как было в последней редакции.
c. Заменить условное выражение в конце кода метода ResetGrid, где используется параметр source
source == Source. fileStream? _fileStreamIn. ReadByte() : _rnd. Next(256);
новой версией
source == _rnd? _rnd. Next(256) : (source as Stream).ReadByte();
В этом новом выражении source является объектом класса object – предком всех других классов в кодах C#. Если вместо параметра source при вызове ResetGrid подставить объект _rnd, что должно быть сделано внутри метода Randomize, то байт будет получен из метода Next(256), как указано в условном выражении. В противном случае источником будет объект потока, т. е. класса Stream. Такой вызов ResetGrid должен быть сделан из метода-обработчика openToolStripMenuItem_Click. Однако, т. к. source по описанию имеет тип object, в котором нет метода ReadByte, то написать просто source. ReadByte() нельзя. Компилятор не пропустит такой код. Если же использовать оператор приведения типа as, как указано выше, то компиляция пройдет успешно.
d. В методе Randomize() следует изменить вызов метода ResetGrid с ResetGrid(streamLength, Source. random); на
ResetGrid(streamLength, _rnd);
e. В методе-обработчике openToolStripMenuItem_Click следует заменить фрагмент кода
_fileStreamIn = File. OpenRead(openFileDialog1.FileName);
try
{
// Заполняем байтами таблицу
ResetGrid((int)_fileStreamIn. Length, Source. fileStream);
}
finally
{
// Освобождаем поток и файл
_fileStreamIn. Close();
}
на более короткий
using (Stream s = File. OpenRead(openFileDialog1.FileName))
// Заполняем байтами таблицу
ResetGrid((int)s. Length, s);
31. Описанные изменения можно внести, не убирая прежний код, если использовать инструмент так называемой условной компиляции. Для этого в самом начале файла формы, перед строками с using System… наберите строки
// Использование параметров условной компиляции
#define version1
#undef version1
Первая строка #define version1 активирует некоторый параметр version1, который может быть произвольным идентификатором и который используется в дальнейшем. Вторая строка #undef version1 отменяет эту активацию. Две такие строки эквивалентны отсутствию какой-либо редакции кода. Однако их наличие позволит сохранить обе версии кода, описанные выше, и манипулировать ими по желанию программиста.
32. В коде следует провести следующую редакцию
a. В методе Randomize()
// Заносим в таблицу случайные байты
#if version1
ResetGrid(streamLength, Source. random);
#else
ResetGrid(streamLength, _rnd);
#endif
Другими словами, если параметр version1 описан, то будет компилироваться первая версия, стоящая между #if version1 и #else. В противном случае – вторая версия, стоящая между #else и #endif.
b. Описание типа Source и поля _fileStreamIn следует окружить условием так, чтобы эти описания имели вид
#if version1
/// <summary>
/// Тип перечисления, используемый
/// для определения источника байтов,
/// записываемых в таблицу
/// </summary>
enum Source { random, fileStream };
/// <summary>
/// Поток ввода информации из файла
/// </summary>
Stream _fileStreamIn;
#endif
c. В описании метода ResetGrid следует сделать условным тип второго параметра
void ResetGrid(int streamLength,
#if version1
Source
#else
object
#endif
source)
и изменить код в условном выражении
#if version1
source == Source. fileStream? _fileStreamIn. ReadByte() : _rnd. Next(256);
#else
source == _rnd? _rnd. Next(256) : (source as Stream).ReadByte();
#endif
d. В описании метода openToolStripMenuItem_Click изменение имеет вид
#if version1
_fileStreamIn = File. OpenRead(openFileDialog1.FileName);
try
{
// Заполняем байтами таблицу
ResetGrid((int) _fileStreamIn. Length, Source. fileStream);
}
finally
{
// Освобождаем поток и файл не зависимо от наличия ошибки
_fileStreamIn. Close();
}
#else
using (Stream s = File. OpenRead(openFileDialog1.FileName))
// Заполняем байтами таблицу
ResetGrid((int)s. Length, s);
#endif
33. В этой редакции компилироваться будет только код, который не удовлетворяет условию #if version1, а также код, стоящий вне директив условной компиляции. Компилируемый и не компилируемый код должны существенно различаться цветом. Скомпилируйте программу и проверьте ее работу. Для возврата к предыдущей версии кода достаточно закомментировать строку #undef version1, поставив перед ней двойной слэш //. Убедитесь, что выделился код, построенный в предыдущей версии. Вновь скомпилируйте и проверьте работу приложения.
34. Что произойдет, если при редактировании ячейки таблицы будет введена строка, которая не может быть байтом – либо содержит нецифровые символы, либо выходит за диапазон [0; 255]? Испытайте.
35. Для того чтобы избежать останова приложения по ошибке при неверном наборе строки, следует обработать два события таблицы – CellValidating и CellEndEdit. Для этого перейдите в окно дизайнера fEditBinFile [Design], выделите таблицу dataGridView1 и в окне Properties на вкладке событий (кнопка с молнией) найдите эти два события. Дважды щелкните по полю справа для первого и второго событий. Затем в полученные обработчики добавьте код так, чтобы результат выглядел следующим образом
private void dataGridView1_CellValidating(object sender,
DataGridViewCellValidatingEventArgs e)
{
// Описывает переменную, которая будет содержать значение введенного байта
byte newByte;
// При верном вводе происходит выход из метода
if (byte. TryParse(e. FormattedValue. ToString(), out newByte)
// В последней строке могут быть пустые ячейки.
// На пустой ячейке заканчивается поток.
// Пустые ячейки должны быть не редактируемыми,
// если длина потока неизменная
|| dataGridView1[e. ColumnIndex, e. RowIndex].ReadOnly) return;
// Если введенная величина не является байтом, ввод отменяется
e. Cancel = true;
// Сообщение, которое появится, если введенная строка байтом не является
dataGridView1.Rows[e. RowIndex].ErrorText =
"Набранная строка не является байтом!\n" +
"Нажмите esc, или наберите число в интервале [0;255]";
}
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
// Очистка строки ErrorText в конце редактирования ячейки
// обеспечит исчезновение объекта класса ErrorProvider (восклицательный знак)
dataGridView1.Rows[e. RowIndex].ErrorText = String. Empty;
}
36. Запрет на редактирование пустых ячеек в последней строке должен быть явно указан в методе ResetGrid. Поэтому в конец этого метода (после закрывающей скобки } цикла по i) следует добавить следующий код
// Пустые ячейки в последней строке делаются не редактируемыми,
// чтобы не влиять на длину редактируемого потока
if (streamLength % dataGridView1.ColumnCount!= 0)
for (col = streamLength % dataGridView1.ColumnCount;
col < dataGridView1.ColumnCount; col++)
dataGridView1[col, dataGridView1.RowCount - 1].ReadOnly = true;
Целью следующей редакции приложения будет добавление возможности представлять изображение байтов в таблице не только в десятичной (decimal), но и в двоичной (binary) и шестнадцатеричной (hexadecimal) системе счисления. Для этого … (Предлагается выполнить эту работу самостоятельно.)
Следующее консольное приложение посвящено форматированию текста.
Для работы с ним добавьте в существующее решение solWriteReadFiles через окно Solution Explorer проект типа ConsoleApplication, который назовите prWriteFormattedText. В него поместите код с комментариями, приведенный ниже
using System;
using System. Collections. Generic;
using System. Linq;
using System. Text;
using System. IO;
namespace prWriteFormattedText
{
class prWriteFormattedText
{
static void Main(string[] args)
{
double d = Math. PI;
// Формат изображения строк
Console. WriteLine("{0,-20};{0,20};", d);
Console. WriteLine("f:{0,20:f};g:{0,20:g};\ne:{0,20:e};r:{0,20:r}", d);
Console. WriteLine("f5:{0,20:f5};g1:{0,20:g1};\ne9:{0,20:e9};r3:{0,20:r3}", d);
Console. ReadLine();
// Опишем массив координат частиц
double[,] q;
// Введем число частиц
Console. Write("Введите число частиц n = ");
int n = int. Parse(Console. ReadLine());
// Определим массив координат
q = new double[3, n];
// Заполним массив случайными числами, лежащими в интервале [-1;1)
Random rnd = new Random();
for (int i = 0; i < 3; i++)
for (int a = 0; a < n; a++)
q[i, a] = 2 * rnd. NextDouble() - 1;
// Занесем массив в текстовой файл, форматируя размещение символов
using (TextWriter tw =
args. Length == 0 ? Console. Out : File. CreateText(args[0] + ".txt"))
{
// Заголовки столбцов
tw. WriteLine("\t|{0,-8}|{1,-8}|{2,-8}|", " X", " Y", " Z");
// Строка разделения
for (int i = 0; i < 36; i++)
tw. Write("-");
tw. WriteLine();
// Строки чисел-координат
for (int a = 0; a < n; a++)
{
// Заголовок текущей строки
tw. Write("{0,5} |", a + 1);
// Содержание текущей строки
for (int i = 0; i < 3; i++)
tw. Write(q[i, a] >= 0 ? " {0,-7:f4}|" : "{0,-8:f4}|", q[i, a]);
tw. WriteLine();
}
}
Console. ReadLine();
}
}
}


