Практикум прикладного программирования на C# в среде VS.NET 2008

Работа с файлами и каталогами

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

Все необходимые для выполнения данной работы программы можно найти в Setup.zip.

Для работы с файлами библиотека .NET Framework имеет достаточное количество классов. Познакомимся с ними. Простейшим уровнем файловых операций является получение информации о файлах и каталогах, смена каталогов, получение сведений о размерности файлов, времени создания, режиме доступа, удаления, переименования, копирования и т.д. без их открытия. Для этого имеются следующие библиотечные классы пространства имен System.IO ( input, output ) сборки mscorlib.dll:

  • Directory, File - предоставляют статические методы (объекта-типа) для сбора информации о файлах и каталогах локального компьютера или сервера
  • DriveInfo, DirectoryInfo, FileInfo - предоставляют экземплярные методы и свойства (объектов-экземпляров) для тех же целей
  • Классы Directory и File

    Если взглянуть на классы Directory и File через панель Object Browse r, то можно убедиться, что эти классы имеют только статические методы, приведенные ниже. Предполагается, что если есть методы чтения Get, то есть и дуальные методы Set, которые в таблицах не указаны.

    Некоторые методы класса Directory
    Метод Описание
    CreateDirectory() Создает каталог вместе с указанным путем
    Delete() Удаляет пустой каталог, если второй необязательный параметр не указан или равен false, то каталог с содержимым метод удалять отказывается. Если второй параметр указан и равен true, то удаляет каталог со всем содержимым
    Exists() Возвращает флаг существования каталога
    GetCreationTime(), GetLastAccessTime(), GetLastWriteTime() Возвращают структуру DateTime, которая содержит время создания каталога, время последнего чтения или записи
    GetDirectories(), GetFiles(), GetLogicalDrives() Возвращает массив строк, по одной на каждый подкаталог, файл или устройство в указанном каталоге (в зависимости от используемого метода). Методы имеют перегрузку, задающую шаблон (фильтр) поиска (например, My*.txt, My???.*, *My.exe, *my*). Регистр в шаблонах поиска значения не имеет
    GetParent() Анализирует переданную строку и возвращает имя родительского каталога
    GetCurrentDirectory() Возвращает текущий каталог с полным путем
    Move() Принимает два строки - исходный каталог и целевой каталог с полными путями. Перемещает исходный каталог в указанное место в пределах одного дискового устройства (иначе, - копирует)
    GetAccessControl() Возвращает объект со списком контроля доступа Windows

    Некоторые методы класса File
    Метод Описание
    Copy() Копирует один файл в другой с возможной заменой содержимого существующего файла (если задан третий необязательный параметр true )
    Delete() Удаляет указанный файл, но не возбуждает исключение, если файл не найден
    Exists() Проверяет существование файла и возвращает флаг
    GetAttributes() Возвращает атрибуты указанного файла
    GetCreationTime(), GetLastAccessTime(), GetLastWriteTime() Возвращают структуру DateTime, которая содержит время создания файла, время последнего чтения или записи
    Move() Перемещает или переименовывает файлы
    Create(), CreateText() Создают файл
    Open(), OpenText(), OpenRead(), OpenWrite() Открывают файл, открывают только для чтения, открывают только для записи
    ReadAllText(), ReadAllLines(), ReadAllBytes() Читают весь файл и возвращают его содержимое в виде строки, массива строк или массива байт
    WriteAllText(), WriteAllLines(), WriteAllBytes() Пишут весь файл за один прием, использую принятую строку, массив строк или массив байт. Если файл существует, то он перезаписывается
    GetAccessControl() Возвращает объект со списком контроля доступа Windows

    Упражнение 1. Получение списка файлов

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

  • Создайте новые решение и проект командой File/New/Project в соответствии со снимком
  • Командой Project/App1 Properties преобразуйте проект в консольное приложение настройкой списка Output type
  • Откройте файл Program.cs и добавьте в него код, выделенный в следующем листинге
  • using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
        
    // Добавляем пространство имен
    using System.IO;
        
    namespace App1
    {
        class Program  // Убрали слово static!!!
        {
            [STAThread]
            static void Main()
            {
                // Вызов нашей функции
                (new Program()).ConsoleOutput();// Через экземпляр - для куража!
        
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        
            // Вывод информации в консольное окно
            void ConsoleOutput()
            {
                // Настраиваем консоль
                Console.Clear();
                Console.ForegroundColor = ConsoleColor.White;
                Console.CursorVisible = false;
                Console.WindowWidth = 70;// 80 знакомест - по умолчанию
                Console.WindowHeight = 20;
                Console.Title = "Список файлов каталога сборки";
        
                string dir = Directory.GetCurrentDirectory();  // Каталог сборки
                string[] fileList = Directory.GetFiles(dir);   // Список файлов
        
                int i = 0;                                      // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    Console.WriteLine("{0}) {1}", ++i, path);
                }
        
                Console.WriteLine();
                i = 0;                                          // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    string file = path.Substring(path.LastIndexOf('\\') + 1);// Экранируем символ
                    Console.WriteLine("{0}) {1}", ++i, file);
                }
        
                Console.WriteLine();
                i = 0;                                          // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    string file = path.Substring(path.LastIndexOf(@"\") + 1);// Неинтерпретируемая строка
                    Console.WriteLine("{0}) {1}", ++i, file);
                }
        
                Console.WriteLine();
                i = 0;                                          // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    string file = Path.GetFileName(path);       // Сразу чистое имя файла
                    Console.WriteLine("{0}) {1}", ++i, file);
                }
            }
        }
    }

    Обратите внимание, что мы убрали перед объявлением класса ключевое слово static (для разнообразия). Поэтому нам пришлось создавать экземпляр класса, чтобы вызвать экземплярный метод вывода в консольное окно. Поскольку нам понадобился только одноразовый вызов метода, то не обязательно запоминать адрес экземпляра в ссылочной переменной, что мы и не стали делать.

  • Запустите приложение - консольное окно наполняется вариантами вывода имен файлов
  • Вначале мы распечатали полные имена файлов, которые вернул метод Directory. GetFiles(). Такие имена иногда называют квалифицированными ( the fully qualified file name ), поскольку в файловой системе компьютера они однозначно определяют файлы как единицы информации. Для получения укороченного имени ( the relative file name ) мы использовали варианты, которых для решения одной и той же задачи обычно можно придумать несколько - обычно именно с таким запасом устроена библиотека .NET Framework.

    Вывод в консольное окно мы организовали до того, как приложение войдет в цикл сообщений Windows, иначе консольное окно вначале появилось бы пустым и заполнилось бы только после закрытия диалогового окна. Но тогда все равно пришлось бы ставить задержку консольного окна, например, методом Console.ReadLine(), чтобы увидеть результаты до закрытия приложения.

    Теперь настроим окно Windows Forms.

  • Перейдите в режим Design файла Form1.c s и поместите из панели Toolbox на макет формы элемент ListBox, который настройте через панель Properties в соответствии с приведенной таблицей свойств
  • Значения свойств элементов управления макета Form1.cs
    Компонент Свойство Значение
    ListBox (Name) listBox1
    Dock Fill
  • Вызовите правой кнопкой мыши для макета формы контекстное меню и выполните команду View Code, чтобы перейти в кодовую часть формы. Заполните конструктор формы так
  • using System;
    using System.Windows.Forms;
        
    // Дополнительное подключение пространств имен
    using System.IO;
        
    namespace App1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
        
                // Привязка массива к списку для отображения и печать результатов
                String[] fileList = Directory.GetFiles(Directory.GetCurrentDirectory());
                for (int i = 0; i < fileList.Length; i++)
                    fileList[i] = String.Format("{0}) {1}", i + 1, Path.GetFileName(fileList[i]));
                listBox1.DataSource = fileList;
                listBox1.SelectionMode = SelectionMode.None;
            }
        }
    }
  • Запустите проект - результат должен быть таким (два окна на одном снимке)
  • Это приложение можно было бы довести до ума, дополнив диалоговым окном смены каталога, но мы пока рассматриваем инструменты пространства имен System.IO и отложем эту мысль на потом.

    Классы DirectoryInfo и FileInfo

    Эти классы прописаны в библиотеке с ключевым словом sealed (запечатанный), запрещающим использование в качестве базовых, и поэтому они не могут иметь наследников. В отличие от классов Directory и File, которые содержат только статические методы, эти классы содержат еще и свойства, а методы в них являются экземплярными. Это позволяет создавать целевые объекты данных, содержащие информацию о файловой системе компьютера.

    Классы DirectoryInfo и FileInfo имеют общего родителя - абстрактный класс FileSystemInfo, который наделяет их базовым набором методов и свойств. Некоторые из этих членов приведены в таблице.

    Некоторые члены базового класса FileSystemInfo
    Член Описание
    Attributes Свойство позволяет извлекать и устанавливать атрибуты для файла или каталога, используя комбинацию значений перечисления FileAttributes:
  • Archive - элемент помечен как архивный
  • Compressed - элемент сжат. Разрешено читать, но устанавливается только через механизмы ОС
  • Device - атрибут зарезервирован и пока не используется
  • Directory - элемент является каталогом
  • Encrypted - элемент зашифрован . Для файла это значит, что все данные в нем зашифрованы. Для каталога это означает, что режим шифрования включен по умолчанию для вновь. создаваемых файлов и подкаталогов. Разрешено читать, но устанавливается только через механизмы ОС
  • Hidden - элемент скрыт
  • Normal - элемент без атрибутов. Этот атрибут устанавливается по умолчанию, когда сняты все другие атрибуты
  • NotContentIndexed - элемент не будет индексирован службой индексации содержимого операционной системы
  • Offline - файл находится в автономном состоянии и в данный момент недоступен
  • ReadOnly - элемент доступен только для чтения
  • System - элемент, используемый исключительно операционной системой
  • Temporary - элемент помечен как временный и может быть удален после завершения работы приложения
  • ReparsePoint, SparseFile - только для файловой системы NTFS
  • CreationTime, LastAccessTime, LastWriteTime Свойство типа DateTime, определяющее временные характеристики файловой единицы (время создания, время последнего доступа и время последнего обновления)
    CreationTimeUtc, LastAccessTimeUtc, LastWriteTimeUtc То же, что предыдущее, только по Гринвичу
    Exists Абстрактное свойство только для чтения, значение которого true, если для созданного объекта-наследника данных файл или каталог существует
    FullName Свойство get типа String, содержащее полное квалифицированное имя файла или каталога
    Name Абстрактное свойство-заготовка только для чтения, предназначенная для хранения имени файла без пути, но с расширением, или имени каталога
    Extension Свойство get типа String, содержит только расширение файла
    Delete() Абстрактная заготовка для удаления файла или каталога в производных классах
    Refresh() Синхронизирует созданный ранее объект данных каталога или файла с последними изменениями файловой системы, выполненными через другие инструменты

    Экземплярные классы DirectoryInfo и FileInfo не только реализуют унаследованные члены, но и дополняют (расширяют) базовый класс FileSystemInfo своими уникальными членами. Некоторые из них приведены в таблицах ниже.

    Некоторые уникальные члены класса DirectoryInfo
    Член Описание
    Create() Создает каталог, указанный ранее в конструкторе при создании экземпляра DirectoryInfo
    CreateSubdirectory() Создает подкаталог
    Delete() Удаляет пустой каталог
    Delete(bool recursive) Удаляет каталог вместе с деревом подкаталогов, если аргумент равен true
    GetDirectories() Возвращает список объектов данных подкаталогов типа DirectoryInfo для текущего каталога. Имеет перегрузки, которые допускают использование шаблонов поиска, удовлетворяющих требуемому условию
    GetFiles() Возвращает список объектов данных типа FileInfo для файлов текущего каталога. Метод имеет перегрузки, позволяющие использовать стандартные символы шаблонов поиска '*' и '?'. Шаблон '?' представляет любой единственный символ, а шаблон '*' - любую последовательность символов из нуля и более, например, *.txt ищет все текстовые файлы
    Parent Свойство возвращает объект данных DirectoryInfo родительского каталога для указанного подкаталога
    Root Свойство возвращает объект данных DirectoryInfo, представляющий корневой каталог

    Некоторые уникальные члены класса FileInfo
    Член Описание
    Directory Возвращает экземпляр класса DirectoryInfo, представляющий каталог размещения файла
    DirectoryName Возвращает строку с полным именем родительского каталога
    Exists Возвращает флаг, определяющий, существует ли указанный файл
    IsReadOnly Булево свойство определяет или устанавливает атрибут файла "только для чтения"
    Length Длинное целое, возвращающее размер файла в байтах
    Name Переопределенное свойство содержит имя файла
    CopyTo() Копирует связанный с объектом данных файл в новое место с указанным именем и возвращает новый объект данных FileInfo, представляющий новую копию. Перегрузка с флагом разрешает перезапись, если целевой файл уже существует
    Create() Создает файл и возвращает объект-оболочку потока FileStream, который можно использовать для записи
    CreateText() Создает файл и возвращает объект-оболочку потока StreamWriter, который можно использовать для записи
    AppendText() Создает и возвращает поток StreamWriter для добавления текста в файл, связанный с объектом FileInfo
    Open(System.IO.FileMode) Возвращает объект FileStream и открывает файл в заданном режиме FileMode:
  • Append - добавлять в конец файла
  • Create - если файл несуществует, то создать, иначе очистить существующий
  • CreateNew - создает и открывает новый файл. Если файл уже существует, выдает исключение
  • Open - требует открыть файл в режиме, установленном перечислением FileAccess. Если файл не существует, выдается исключение FileNotFoundException
  • OpenOrCreate - существующий файл открыть, иначе создать файл
  • Truncate - открыть файл с очисткой до нулевой длины
  • Open(System.IO.FileMode, System.IO.FileAccess) Возвращает объект FileStream. Представляет вариации открытия файла с вариациями доступа, определяемыми перечислением FileAccess:
  • Read - только для чтения
  • ReadWrite - для чтения и записи
  • Write - только для записи
  • Open(System.IO.FileMode, System.IO.FileAccess, System.IO.FileShare) Возвращает объект FileStream. Представляет вариации открытия файла с вариациями доступа, включая возможность одновременного доступа других объектов-потоков FileStream к данному файлу. Права других потоков при совместном доступе определяются перечислением FileShare:
  • Delete - разрешено удаление файла
  • None - не разрешать совместное использование файла
  • Read - разрешить сторонним потокам только чтение
  • ReadWrite - разрешить совместные чтение и запись
  • Write - разрешить совместную запись
  • OpenRead() Создает поток FileStream только для чтения и возвращает его
    OpenWrite() Создает поток FileStream только для записи и возвращает его
    OpenText() Создает и возвращает поток StreamReader для чтения в кодировке UTF8 из существующего текстового файла
    Encrypt() Шифрует файл
    Decrypt() Дешифрует файл
    Delete() Удаляет файл физически
    Replace() Заменяет содержимое указанного файла содержимым файла, связанного с текущим FileInfo. Целевой файл удаляется, а вместо него создается резервная копия с указанным именем, но параметры целевого файла возвращаются методом в виде объекта FileInfo

    Экземплярные классы DirectoryInfo и FileInfo имеют только параметризованные конструкторы, ожидающие строку каталога или файла с полным путем. Путь не обязательно должен соответствовать реально существующему файлу или каталогу. Если указанный путь не существует, то свойство Exists объекта будет иметь значение false.

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

    Если указанный файл или каталог не существует, то их можно создать. Ниже приведен пример такой функции.

    static void CreateFile(string fileName) // Нужно полное имя файла
            {
                string file = Path.GetFileName(fileName);       // Только имя файла
                string dir = Path.GetDirectoryName(fileName);   // Только полный путь
                string path = Path.Combine(dir, file);          // Опять fileName
                /*
                Console.WriteLine("fileName = {0}", fileName);
                Console.WriteLine("file = {0}", file);
                Console.WriteLine("dir = {0}", dir);
                Console.WriteLine("path = {0}", path);
                */
                DirectoryInfo myDir = new DirectoryInfo(dir);
                FileInfo myFile = new FileInfo(fileName);
                if (!myDir.Exists)      // Если каталог не существует
                {
                    myDir.Create();     // Создать каталог    
                    myFile.Create();    // Создать файл
                }
                else if (!myFile.Exists)// Если файл не существует
                    myFile.Create();    // Создать файл
            }

    Вызов функции CreateFile() может быть таким

    Program.CreateFile(@"C:\Tmp111\TTT\fileName.txt");

    Методы GetFiles() и GetDirectories() классов Directory или DirectoryInfo с заданными шаблонами поиска или без них осуществляют поиск информации только для текущего каталога. Чтобы пройтись по всем вложенным подкаталогам, нужно применить рекурсивную логику поиска. В следующем примере используется рекурсивный вызов функции для подсчета размера файлов, находящихся в указанном каталоге и во всех подчиненных ему подкаталогах файлового дерева.

    static long GetDirectorySize(DirectoryInfo directory, bool includeSubdirectories)
            {
                Console.WriteLine(directory.FullName);
                long totalSize = 0;
        
                // Просуммировать размеры файлов в текущем каталоге
                FileInfo[] files = directory.GetFiles();
                foreach (FileInfo file in files)
                    totalSize += file.Length;
        
                // Рекурсивно обходим подкаталоги
                if (includeSubdirectories)
                {
                    DirectoryInfo[] dirs = directory.GetDirectories();
                    foreach (DirectoryInfo dir in dirs)
                        totalSize += GetDirectorySize(dir, true);
                }
        
                return totalSize;
            }

    Вызов функции GetDirectorySize() может быть таким

    String path = @"E:\Tmp";
                long totalSize = Program.GetDirectorySize(new DirectoryInfo(path), true);
                Console.WriteLine(totalSize);

    Упражнение 2. Использование атрибутов

    Свойство Attributes, наследуемое экземплярными классами DirectoryInfo и FileInfo от класса FileSystemInfo, устанавливает особенность поведения операционной системы по отношению к элементам файловой системы. Оно обеспечивает доступ к двухбайтному базовому слову, каждый бит которого соответствует определенной поведенческой установке, закрепленной за файлом или каталогом.

    Метод ToString() свойства Attributes возвращает строку с именами атрибутов элемента, разделенными запятыми. Следующий код распечатывает названия атрибутов всех файлов заданного каталога

    static void Main(string[] args)
            {
                String currentDirectory = @"E:\Tmp\XX";//Directory.GetCurrentDirectory();  // Каталог сборки
                //Console.WriteLine(currentDirectory);
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files = directory.GetFiles(); // Список файлов
                foreach (FileInfo file in files)
                {
                    Console.WriteLine("\nАтрибуты файла {0}", file.Name);
                    String[] attributes = file.Attributes.ToString().Split(',');
                    for (int i = 0; i < attributes.Length; i++)
                        Console.WriteLine(attributes[i].Trim());
                }
        
                Console.ReadLine();
            }

    Для работы с атрибутами используются побитовые операции. Элементы перечисления FileAttributes представляют собой слово (2 байта), в котором только один бит равен единице. Их можно считать побитовыми масками, с помощью которых можно установить или прочитать атрибут. Некоторые атрибуты не могут быть установлены программно. Например, атрибуты FileAttributes. Encrypted и FileAttributes.Compressed устанавливаются самой операционной системой через специальные механизмы.

    Прежде, чем двигаться дальше, вспомним поразрадные операции C#. Они позволяют работать с данными на уровне бит. Все поразрядные операции используются только с целыми типами данных ( byte, sbyte, int, uint, short, ushort, long, ulong, char, decimal ), у которых в машинном представлении нет плавающей точки.

    Существует 6 поразрядных операций, проиллюстрированных в следующих таблицах

    Операция Наименование Описание
    поразрядное И Бит результата устанавливается в 1, если соответствующие биты обеих операндов равны 1
    | поразрядное ИЛИ Бит результата устанавливается в 1, если соответствующий бит хотя бы одного операнда равен 1
    ^ поразрядное ИСКЛЮЧАЮЩЕЕ ИЛИ Бит результата устанавливается в 1, если соответствующиий бит одного и только одного операнда равен 1
    ~ поразрядное НЕ Унарная операция, которая применяется только к одному операнду. Меняет каждый бит с 1 на 0 и наоборот
    << поразрядный сдвиг влево Сдвигает биты первого операнда влево на количество позиций, заданных вторым операндом. При этом освобождающиеся справа биты заполняются нулями
    >> поразрядный сдвиг вправо Сдвигает биты первого операнда вправо на количество позиций, заданных вторым операндом. При этом освобождающиеся слева биты заполняются нулями

    Шестнадцатиричное представление байта
    binary 0x
    0000 0000 0x00
    0000 0001 0x01
    0000 0010 0x02
    0000 0011 0x03
    0000 0100 0x04
    0000 0101 0x05
    0000 0110 0x06
    0000 0111 0x07
    0000 1000 0x08
    0000 1001 0x09
    0000 1010 0x0A
    0000 1011 0x0B
    0000 1100 0x0C
    0000 1101 0x0D
    0000 1110 0x0E
    0000 1111 0x0F

    Пример поразрядных операций
    a mask a | mask a ^ mask ~mask
    a 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0
    mask 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0
    result 1 0 0 0 0 0 1 0 1 1 1 1 1 0 1 0 0 1 1 1 1 0 0 0 0 0 1 1 0 1 0 1
    result ^ mask 1 0 1 1 0 0 1 0

    Пример операций сдвига
    a << 5 a >> 5
    a 1 1 0 1 0 0 0 1 1 1 0 0 1 1 1 1 1 1 0 1 0 0 0 1 1 1 0 0 1 1 1 1
    result 0 0 1 1 1 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 1 0

    Операции поразрядного присваивания
    = Операция присваивания поразрядного И
    | = Операция присваивания поразрядного ИЛИ
    ^ = Операция присваивания поразрядного исключающего ИЛИ
    << = Операция присваивания сдвига влево
    >> = Операция присваивания сдвига вправо

    Следующее упражнение иллюстрирует побитовую арифметику с атрибутами.

  • Добавьте к решению FilesAndDirectory проект консольного приложения App2 и назначьте его стартовым в Solution Explorer командой контекстного меню Set as StartUp Project
  • Заполните файл Program.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
        
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                String currentDirectory = @"E:\Tmp\XX";//Directory.GetCurrentDirectory();  // Каталог сборки
                //Console.WriteLine(currentDirectory);
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files1 = directory.GetFiles("*.txt"); // Список текстовых файлов
        
                // Создаем маску с атрибутами
                FileAttributes mask = FileAttributes.Archive
                    | FileAttributes.ReadOnly
                    | FileAttributes.System
                    | FileAttributes.Offline
                    | FileAttributes.NotContentIndexed
                    | FileAttributes.Hidden
                    | FileAttributes.Temporary
                    | FileAttributes.Device;
        
                // Устанавливаем для всех файлов указанные атрибуты 
                foreach (FileInfo file in files1)
                {
                    // Добавляем атрибуты
                    file.Attributes |= mask;
                }
        
                // Создаем новый объект для чистоты эксперимента и печатаем атрибуты
                FileInfo[] files2 = directory.GetFiles("*.txt"); // Список текстовых файлов
                foreach (FileInfo file in files2)
                {
                    Console.WriteLine("\nАтрибуты файла {0}", file.Name);
                    String[] attributes = file.Attributes.ToString().Split(',');
                    for (int i = 0; i < attributes.Length; i++)
                        Console.WriteLine(attributes[i].Trim());
                }
        
                // Определяем, имеет ли первый файл коллекции files2 атрибут ReadOnly 
                if ((files2[0].Attributes  FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                    Console.WriteLine(Environment.NewLine   // Новая строка
                        + "Файл {0} имеет атрибут ReadOnly", files2[0].Name);
                else
                    Console.WriteLine("\n"                  // Новая строка
                        + "Файл {0} не имеет атрибут ReadOnly", files2[0].Name);
        
                // Исключаем из первого объекта добавленные ранее атрибуты
                foreach (FileInfo file in files1)
                {
                    // Исключаем атрибуты
                    file.Attributes = ~mask; // Побитовое умножение на инвертированную маску
                }
        
                // Печатаем результат через второй объект
                // Если открыть комментарные скобки - результат будет неверным!!!
                //*
                for (int i = 0; i < files2.Length; i++)
                    files2[i].Refresh();    // Приводим в соответствие с состоянием файлов
                //*/
                foreach (FileInfo file in files2)
                {
                    Console.WriteLine("\nАтрибуты файла {0}", file.Name);
                    String[] attributes = file.Attributes.ToString().Split(',');
                    for (int i = 0; i < attributes.Length; i++)
                        Console.WriteLine(attributes[i].Trim());
                }
        
                // Определяем, имеет ли первый файл коллекции files2 атрибут ReadOnly 
                if ((files2[0].Attributes  FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                    Console.WriteLine(Environment.NewLine   // Новая строка
                        + "Файл {0} имеет атрибут ReadOnly", files2[0].Name);
                else
                    Console.WriteLine("\n"                  // Новая строка
                        + "Файл {0} не имеет атрибут ReadOnly", files2[0].Name);
        
                Console.ReadLine();
            }
        }
    }
  • Запустите приложение - вывод программы для нескольких файлов будет примерно следующим
  • Атрибуты файла Test.txt
    ReadOnly
    Hidden
    System
    Archive
    Temporary
    Offline
    NotContentIndexed
    	
    Атрибуты файла xx.txt
    ReadOnly
    Hidden
    System
    Archive
    Temporary
    Offline
    NotContentIndexed
    	
    Файл Test.txt имеет атрибут ReadOnly
    	
    Атрибуты файла Test.txt
    Normal
    	
    Атрибуты файла xx.txt
    Normal
    	
    Файл Test.txt не имеет атрибут ReadOnly

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

    Класс DriveInfo

    Экземплярный класс, который извлекает информацию о дисковом устройстве компьютера. Имя устройства задается в конструкторе класса при создании экземпляра и представляет собой строку с символом от " a " до " z " в любом регистре. Ниже приведены члены класса DriveInfo.

    Члены класса DriveInfo
    Член Описание
    TotalFreeSpace Возвращает объем свободного пространства в байтах
    AvailableFreeSpace Возвращает объем свободного пространства в байтах, доступного для данного процесса
    DriveFormat Свойство возвращает имя файловой системы ( NTFS или FAT32 )
    DriveType Возвращает значение одноименного перечисления DriveType:
  • CDRom - устройство оптического диска, такое как CD или DVD-ROM
  • Fixed - фиксированный диск
  • Network - сетевой
  • NoRootDirectory - у диска нет корневого каталога
  • Ram - устройство RAM
  • Removable - сменное устройство хранения данных, такое как флэшка
  • Unknown - тип диска неопределен
  • IsReady Определяет готовность устройства к чтению или записи. Например, если нет компакт-диска в CD -приводе, свойство IsReady вернет false. Фиксированные устройства всегда готовы
    Name Возвращает строку с именем диска, например, C: или E:
    RootDirectory Возвращает объект DirectoryInfo с информацией о корневом каталоге диска
    TotalSize Получает полный размер пространства памяти на диске
    VolumeLabel Свойство, извлекающее или устанавливающее метку тома
    GetDrives() Статический метод, который возвращает массив объектов DriveInfo, представляющих все логические устройства данного компьютера

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

    Упражнение 3. Мониторинг дисковых устройств компьютера

    Проиллюстрируем использование класса DriveInfo.

  • Добавьте к решению консольное приложение App3 и назначьте его стартовым
  • Заполните файл Program.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
    using DEBUG = System.Diagnostics.Debug;// Псевдоним класса
        
    namespace App3
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Получаем список всех логических устройств компьютера
                DriveInfo[] drives = DriveInfo.GetDrives();
        
                // Перебираем список и печатаем свойства
                bool rootFlag = false; // Чтобы напечатать только раз
                foreach (DriveInfo drive in drives)
                {
                    String message = String.Format("Устройство '{0}'", drive.Name);
                    // Если готово, извлекаем характеристики
                    if (drive.IsReady)
                    {
                        message += " ГОТОВО" + Environment.NewLine;
                        if (!rootFlag)
                        {
                            message += String.Format("RootDirectory: {0}\n", drive.RootDirectory);
                            rootFlag = !rootFlag;// Отключаем
                        }
                        message += String.Format("VolumeLabel: {0}\n", drive.VolumeLabel);
                        message += String.Format("DriveType: {0}\n", drive.DriveType.ToString());
                        message += String.Format("DriveFormat: {0}\n", drive.DriveFormat);
                        message += String.Format("TotalSize: {0}\n", drive.TotalSize);
                        message += String.Format("AvailableFreeSpace: {0}\n", drive.AvailableFreeSpace);
                        message += String.Format("TotalFreeSpace: {0}\n", drive.TotalFreeSpace);
                    }
                    else
                        message += " НЕГОТОВО\n";
        
                    DEBUG.WriteLine(message);
                }
        
                Console.ReadLine();
            }
        }
    }

    Обратите внимание, что псевдонимы можно определять не только для пространств имен, но и для классов. Свойство RootDirectory экземпляра DriveInfo для любого устройства возвращает одно и то же значение, поэтому мы используем его только раз (чтобы не мешало). Если мы хотим исследовать только одно устройство и знаем его имя, то можно создать объект DriveInfo с помощью параметризованного конструктора, указав в качестве аргумента строку с именем, например, так

    DriveInfo drive = new DriveInfo("C");

    Имя устройства задается только символом от a до z в любом регистре и без дополнительных символов слэша и двоеточия. А далее все равно нужно проверить его готовность IsReady, прежде чем обращаться к другим свойствам.

  • Удостовертесь, что включен режим Debug оболочки (выпадающий список над рабочей областью)
  • Запустите проект и откройте панель Output. На моем компьютере получился такой результат
  • Устройство 'C:\' ГОТОВО
    RootDirectory: C:\
    VolumeLabel: 18125
    DriveType: Fixed
    DriveFormat: NTFS
    TotalSize: 31457275904
    AvailableFreeSpace: 473616384
    TotalFreeSpace: 473616384
    	
    Устройство 'D:\' ГОТОВО
    VolumeLabel: 
    DriveType: Removable
    DriveFormat: FAT32
    TotalSize: 261105664
    AvailableFreeSpace: 244381696
    TotalFreeSpace: 244381696
    	
    Устройство 'E:\' ГОТОВО
    VolumeLabel: Data
    DriveType: Fixed
    DriveFormat: NTFS
    TotalSize: 462356992000
    AvailableFreeSpace: 425508450304
    TotalFreeSpace: 425508450304
    	
    Устройство 'F:\' ГОТОВО
    VolumeLabel: ГАИ - 24.01.2010
    DriveType: CDRom
    DriveFormat: CDFS
    TotalSize: 161966080
    AvailableFreeSpace: 0
    TotalFreeSpace: 0
    	
    Устройство 'G:\' НЕГОТОВО
    	
    Устройство 'H:\' НЕГОТОВО
    	
    Устройство 'I:\' НЕГОТОВО
    	
    Устройство 'J:\' НЕГОТОВО
    	
    Устройство 'K:\' ГОТОВО
    VolumeLabel: 
    DriveType: Removable
    DriveFormat: FAT32
    TotalSize: 31994216448
    AvailableFreeSpace: 7246184448
    TotalFreeSpace: 7246184448

    Вывод информации мы организовали в панель Output оболочки, поэтому консольное окно будет пустым.

  • Включите режим Release оболочки и повторите запуск проекта
  • Мы видим, что отладочный код работает только в режиме Debug и автоматически исключается компилятором в режиме Release из готовой исполнимой сборки приложения. Это очень удобно при тестировании проектов.

    Упражнение 4. Получение метаданных из сборок

    Иногда может возникнуть необходимость получить дополнительную информацию о файле, включающую в себя авторские права, номер версии, наименование компании-производителя, информацию о торговой марке и т.д. Эта информация вшивается в сборку при компиляции EXE - или DLL -файлов (если укажем). Данные отображаются в диалоговом окне свойств, вызванном в Windows Explorer при выделении исполнимого файла правой кнопкой мыши и выбора опции Свойства, например

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

  • Добавьте к решению командой File/Add/New Project новый проект консольного приложения с именем App4 и назначьте его стартовым
  • В панели Solution Explorer откройте на редактирование файл AssemblyInfo.cs и ознакомьтесь с его содержимым, автоматически сгенерированным оболочкой
  • using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
        
    // General Information about an assembly is controlled through the following 
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyTitle("App4")]
    [assembly: AssemblyDescription("")]
    [assembly: AssemblyConfiguration("")]
    [assembly: AssemblyCompany("")]
    [assembly: AssemblyProduct("App4")]
    [assembly: AssemblyCopyright("Copyright ©  2010")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyCulture("")]
        
    // Setting ComVisible to false makes the types in this assembly not visible 
    // to COM components.  If you need to access a type in this assembly from 
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible(false)]
        
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid("749a9e12-8b91-41d5-942a-e174fe60800f")]
        
    // Version information for an assembly consists of the following four values:
    //
    //      Major Version
    //      Minor Version 
    //      Build Number
    //      Revision
    //
    // You can specify all the values or you can default the Build and Revision Numbers 
    // by using the '*' as shown below:
    // [assembly: AssemblyVersion("1.0.*")]
    [assembly: AssemblyVersion("1.0.0.0")]
    [assembly: AssemblyFileVersion("1.0.0.0")]

    Мы видим набор атрибутов в глобальном пространстве имен, определяющих для сборки дополнительную информацию, которую называют метаданными (данные о данных). Ее можно редактировать в файле AssemblyInfo.cs напрямую, но лучше это делать через страницу свойств проекта.

  • Выполните команду Project/App4 Properties меню оболочки и выделите вкладку Application страницы свойств проекта
  • Щелкните на кнопке Assembly Information, чтобы вызвать диалоговое окно редактирования метаданных сборки с текстовыми полями:
  • Title - название
  • Description - описание
  • Company - производитель
  • Product - наименование продукта
  • Copyright - авторское право
  • Trademark - торговая марка
  • Assembly Version - версия сборки
  • File Version - версия файла
  • Neutral Language - язык по умолчанию
  • Заполните диалоговое окно свойств как показано на рисунке
  • Вновь откройте файл AssemblyInfo.cs и убедитесь, что его содержимое изменилось
  • Извлечь информацию из сборки можно программно. Для этого предназначен класс System.Diagnostics. FileVersionInfo. Продемонстрируем его использование на примере.

  • Заполните файл Program.cs приложения App4 следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
    using DEBUG = System.Diagnostics.Debug; // Псевдоним класса
    using SD = System.Diagnostics;          // Создаем alias пространства имен (необязательно!)
    using System.Diagnostics;               // Подключаем пространство имен напрямую
        
    namespace App4
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Локальные (стековые) переменные инициализировать обязательно
                String currentDirectory = Directory.GetCurrentDirectory();  // Каталог сборки
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files = directory.GetFiles("*.exe"); // Список файлов по шаблону
                // Создаем массив для информации обо всех файлах
                SD.FileVersionInfo[] info =
                    new System.Diagnostics.FileVersionInfo[files.Length];
        
                // Извлекаем из всех файлов дополнительную информацию
                for (int i = 0; i < files.Length; i++)
                {
                    info[i] = FileVersionInfo.GetVersionInfo(files[i].FullName);// Просто 'Name' мало!
                }
        
                // Накапливаем результат для печати
                String message = String.Empty;
                for (int i = 0; i < info.Length; i++)
                {
                    // Используем отражение для сокращения кода (и куража!)
                    System.Reflection.PropertyInfo[] props = // Общедоступные свойства
                        info[i].GetType().GetProperties();
                    for (int j = 0; j < props.Length; j++)
                    {
                        message += props[j].Name + ":\t";   // Имя свойства, ':' и отступ
                        // Выполняем свойство, чтобы извлечь его значение
                        Object prop = props[j].GetGetMethod().Invoke(info[i], null);
                        if (prop is String) // Динамическая идентификация типа (необязательно)
                            message += prop.ToString();
                        else if (prop != null)
                            message += String.Format("{0}", prop);
        
                        message += Environment.NewLine; // Новая строка
                    }
        
                    message += "\r\n";     // Новая строка
                }
        
                Console.Write(message + "\n");  // Печатаем на консоль (Write и новая строка)
                DEBUG.WriteLine(message);       // Печатаем в панель Output оболочки
        
                Console.ReadLine();         // Задержка консоли
            }
        }
    }

    В приведенном коде мы применили методику отражения типов ( Reflection - рефлексию), чтобы не перебирать все свойства явно. Кроме того, использовали разные способы новой строки, для разнообразия.

  • Запустите приложение - вывод информации для файлов каталога исполнимой сборки данного проекта будет таким
  • Comments:	Читает метаданные (Description)
    CompanyName:	Рога и копыта (Company)
    FileBuildPart:	0
    FileDescription:	Приложение App4 (Title)
    FileMajorPart:	1
    FileMinorPart:	0
    FileName:	E:\Tmp\FilesAndDirectory\App4\bin\Debug\App4.exe
    FilePrivatePart:	5678
    FileVersion:	1.0.0.5678
    InternalName:	App4.exe
    IsDebug:	False
    IsPatched:	False
    IsPrivateBuild:	False
    IsPreRelease:	False
    IsSpecialBuild:	False
    Language:	Независимо от языка
    LegalCopyright:	Copyright ©  2010 (Copyright)
    LegalTrademarks:	Ни-Бэ-Ни-Мэ (Trademark)
    OriginalFilename:	App4.exe
    PrivateBuild:	
    ProductBuildPart:	0
    ProductMajorPart:	1
    ProductMinorPart:	0
    ProductName:	Суперприложение (Product)
    ProductPrivatePart:	5678
    ProductVersion:	1.0.0.5678
    SpecialBuild:	
    	
    Comments:	vshost.exe
    CompanyName:	Microsoft Corporation
    FileBuildPart:	21022
    FileDescription:	vshost.exe
    FileMajorPart:	9
    FileMinorPart:	0
    FileName:	E:\Tmp\FilesAndDirectory\App4\bin\Debug\App4.vshost.exe
    FilePrivatePart:	8
    FileVersion:	9.0.21022.8
    InternalName:	vshost.exe
    IsDebug:	False
    IsPatched:	False
    IsPrivateBuild:	False
    IsPreRelease:	False
    IsSpecialBuild:	False
    Language:	Независимо от языка
    LegalCopyright:	© Microsoft Corporation. All rights reserved.
    LegalTrademarks:	
    OriginalFilename:	vshost.exe
    PrivateBuild:	
    ProductBuildPart:	21022
    ProductMajorPart:	9
    ProductMinorPart:	0
    ProductName:	Microsoft (R) Visual Studio (R) 2008
    ProductPrivatePart:	8
    ProductVersion:	9.0.21022.8
    SpecialBuild:

    Здесь, на первый взгляд, казалось бы, наблюдается парадокс - исполнимая сборка проверяет саму себя! Ну и что, порядок ее создания таков: компилятор транслирует код и записывает его в файл. Затем оболочка загружает файл в память и запускает процесс CLR, который анализирует указанный нами файл на диске в соответствии с заложенным алгоритмом. Указали бы другой исполнимый или библиотечный файл, процесс проверял бы его. Конечно, можно было бы сделать и диалог для выбора файла, но не в этом суть данного упражнения.

    В каталоге сборки bin оболочка создает еще один файл App4.vshost.exe, который используется для оптимизации отладки, поддержки политик безопасности, более детального контроля за работой приложения. Его создание для любого из режимов ( Debug и Release ) можно отключить в настройках оболочки.

  • Откройте окно свойств проекта командой Project/App4 Properties и на вкладке Debug сбросьте внизу флажок Enable the Visual Studio hosting process
  • В панели Solution Explorer щелкните на локальной пиктограмме Show All Files, разверните дерево каталогов проекта и удалите файл App4.vshost.exe
  • Запустите приложение - теперь метаданные выводятся только для файла самой сборки App4.exe, поскольку файл App4.vshost.exe уже не создается
  • В панели Solution Explorer командой Exclude From Project контекстного меню для файла AssemblyInfo.cs исключите его из текущего проекта и вновь запустите приложение
  • Мы видим, что метаданные в сборку на сей раз не попали.

  • Верните через панель Solution Explorer файл AssemblyInfo.cs обратно в проект командой контекстного меню Include In Project, переименуйте его в произвольное имя (расширение .cs должно остаться!) и вновь запустите приложение - метаданные есть, значит имя файла с атрибутами дополнительной информации для компилятора несущественно, он их находит из общего входного потока компиляции
  • Откройте файл AssemblyInfo.cs, командой Edit/Select All выделите все его содержимое и скопируйте в верхнюю часть файла Program.cs перед оператором ' namespace App4 '
  • Удалите в панели Solution Explorer файл AssemblyInfo.cs физически командой Delete контекстного меню
  • Запустите проект - метаданные существуют и их можно добалять непосредственно в коде в глобальном пространстве имен
  • Можно задаться вопросом, а куда же вшивается дополнительная информация в сборке? Давайте посмотрим, куда. Для этого проанализируем сборку дизассемблером ILDasm, который поставляется вместе с оболочкой и находится по адресу (например, на моем компьютере)

    C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe
  • Раскройте меню Tools оболочки и запустите ILDasm
  • Если такой команды не окажется, то ее следует добавить. Для этого:

  • Выполните команду меню Tools/External Tools (внешние инструменты) и заполните диалоговое окно External Tools как показано на снимке
  • Теперь осталось воспользоваться инструментом ILDasm и проанализировать нашу сборку App4.exe на предмет просмотра ранее введенных атрибутов.

  • Запустите дизассемблер ILDasm и командой File/Open откройте файл E:\Tmp\FilesAndDirectory\App4\bin\Debug\App4.exe (у меня такой...)
  • Выполните команду View/MetaInfo/Show! (или выполните жест Ctrl+M ) - в конце документа окна MetaInfo будет секция Assembly, в которой и находятся введенные нами ранее атрибуты сборки
  • Вот примерно так будет выглядеть секция Assembly (несколько укороченная) в документе окна MetaInfo

    Assembly
    -------------------------------------------------------
    	Token: 0x20000001
    	Name : App4
    	Public Key    :
    	Hash Algorithm : 0x00008004
    	Version: 1.0.0.1234
    	Major Version: 0x00000001
    	Minor Version: 0x00000000
    	Build Number: 0x00000000
    	Revision Number: 0x000004d2
    	Locale: <null>
    	Flags : [none] (00000000)
    	CustomAttribute #1 (0c000001)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000001
    		CustomAttributeName: System.Reflection.AssemblyTitleAttribute :: instance void .ctor(class System.String)
    		Length: 38
    		Value : 01 00 21 d0 9f d1 80 d0  b8 d0 bb d0 be d0 b6 d0 >  !             <
                          : b5 d0 bd d0 b8 d0 b5 20  41 70 70 34 20 28 54 69 >        App4 (Ti<
                          : 74 6c 65 29 00 00                                >tle)            <
    		ctor args: ("Приложение App4 (Title)")
    	
    	CustomAttribute #2 (0c000002)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000002
    		CustomAttributeName: System.Reflection.AssemblyDescriptionAttribute :: instance void .ctor(class System.String)
    		Length: 52
    		Value : 01 00 2f d0 a7 d0 b8 d1  82 d0 b0 d0 b5 d1 82 20 >  /             <
                          : d0 bc d0 b5 d1 82 d0 b0  d0 b4 d0 b0 d0 bd d0 bd >                <
                          : d1 8b d0 b5 20 28 44 65  73 63 72 69 70 74 69 6f >     (Descriptio<
                          : 6e 29 00 00                                      >n)              <
    		ctor args: ("Читает метаданные (Description)")
    	
    	CustomAttribute #4 (0c000004)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000004
    		CustomAttributeName: System.Reflection.AssemblyCompanyAttribute :: instance void .ctor(class System.String)
    		Length: 39
    		Value : 01 00 22 d0 a0 d0 be d0  b3 d0 b0 20 d0 b8 20 d0 >  "             <
                          : ba d0 be d0 bf d1 8b d1  82 d0 b0 20 28 43 6f 6d >            (Com<
                          : 70 61 6e 79 29 00 00                             >pany)           <
    		ctor args: ("Рога и копыта (Company)")
    	
    	CustomAttribute #5 (0c000005)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000005
    		CustomAttributeName: System.Reflection.AssemblyProductAttribute :: instance void .ctor(class System.String)
    		Length: 45
    		Value : 01 00 28 d0 a1 d1 83 d0  bf d0 b5 d1 80 d0 bf d1 >  (             <
                          : 80 d0 b8 d0 bb d0 be d0  b6 d0 b5 d0 bd d0 b8 d0 >                <
                          : b5 20 28 50 72 6f 64 75  63 74 29 00 00          >  (Product)     <
    		ctor args: ("Суперприложение (Product)")
    	
    	CustomAttribute #6 (0c000006)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000006
    		CustomAttributeName: System.Reflection.AssemblyCopyrightAttribute :: instance void .ctor(class System.String)
    		Length: 35
    		Value : 01 00 1e 43 6f 70 79 72  69 67 68 74 20 c2 a9 20 >   Copyright    <
                          : 20 32 30 31 30 20 28 43  6f 70 79 72 69 67 68 74 > 2010 (Copyright<
                          : 29 00 00                                         >)               <
    		ctor args: ("Copyright ©  2010 (Copyright)")
    	
    	CustomAttribute #7 (0c000007)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000007
    		CustomAttributeName: System.Reflection.AssemblyTrademarkAttribute :: instance void .ctor(class System.String)
    		Length: 36
    		Value : 01 00 1f d0 9d d0 b8 2d  d0 91 d1 8d 2d d0 9d d0 >       -    -   <
                          : b8 2d d0 9c d1 8d 20 28  54 72 61 64 65 6d 61 72 > -     (Trademar<
                          : 6b 29 00 00                                      >k)              <
    		ctor args: ("Ни-Бэ-Ни-Мэ (Trademark)")
    	
    	CustomAttribute #10 (0c00000a)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a00000c
    		CustomAttributeName: System.Reflection.AssemblyFileVersionAttribute :: instance void .ctor(class System.String)
    		Length: 15
    		Value : 01 00 0a 31 2e 30 2e 30  2e 35 36 37 38 00 00    >   1.0.0.5678   <
    		ctor args: ("1.0.0.5678")
    	
    	CustomAttribute #11 (0c00000b)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a00000d
    		CustomAttributeName: System.Resources.NeutralResourcesLanguageAttribute :: instance void .ctor(class System.String)
    		Length: 10
    		Value : 01 00 05 72 75 2d 52 55  00 00                   >   ru-RU        <
    		ctor args: ("ru-RU")

    Более подробная информация о сборках приведена, например, в http://www.intuit.ru/department/internet/prdevwebms/19/

    Упражнение 5. Работа с классом Path

    Статический класс System.IO. Path содержит статические методы и поля, которые выполняют общие задачи манипуляции с путями. Путем называется строка, которая предшествует укороченному имени файла или каталога перед конечным обратным слэшем ' \ ' (разделителем составного имени).

    Статические методы и поля класса Path
    Метод Описание
    GetDirectoryName() Возвращает имя каталога по полному имени файла
    GetFullPath() Возвращает полное имя файла по укороченному имени
    GetFileName() Возвращает укороченное имя файла по полному имени
    GetFileNameWithoutExtension() Возвращает укороченное имя файла без расширения
    GetExtension() Возвращает расширение файла с разделителем '.'
    Combine() Объединяет путь и имя файла, вставляя между ними разделитеь '\'
    GetPathRoot() Возвращает имя устройства, например, 'C:\'
    GetRandomFileName() Генерирует имя файла случайным образом базового формата DOS
    GetTempFileName() Генерирует заготовку полного имени временного файла для размещения в стандартном каталоге, который (каталог), возможно, еще предстоит создать
    GetTempPath() Генерирует заготовку полного имени каталога для размещения временных файлов, который, возможно, еще предстоит создать
    AltDirectorySeparatorChar, DirectorySeparatorChar, PathSeparator, VolumeSeparatorChar

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

    '/' '\' ';' ':'

  • Добавьте к решению проект ( Console Application ) с именем App5 и назначьте его стартовым. Для этого выполните через меню оболочки вначале команду File/Add/New Project, а затем сразу команду Project/Set as StartUp Project (пока созданный проект оболочка считает текущим)
  • Заполните файл Program.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
    using DEBUG = System.Diagnostics.Debug; // Псевдоним класса
        
    namespace App5
    {
        class Program
        {
            static void Main(string[] args)
            {
                String currentDirectory = Directory.GetCurrentDirectory();  // Каталог сборки
                String message = "Без использования Path\n".ToUpper();
                message += "currentDirectory: " + currentDirectory + "\n";
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files = directory.GetFiles("*.exe"); // Список файлов по шаблону
                FileInfo file;
                if (files.Length > 0)
                    file = files[0];    // Берем первый попавшийся для анализа
                else
                    return;             // Нечего анализировать
        
                string fullName = file.FullName;
                string name = file.Name;
                message += "fullName: " + fullName + "\n";
                message += string.Format("name: {0}\n", name);
        
                message += string.Format("\nС использованием Path\n").ToUpper();
                String path;
                message += "path = Path.GetDirectoryName(fullName): "
                    + (path = Path.GetDirectoryName(fullName)) + "\n";
                message += "Path.GetFullPath(name): "
                    + Path.GetFullPath(name) + "\n";
                message += "Path.GetFileName(fullName): "
                    + Path.GetFileName(fullName) + "\n";
                message += "Path.GetFileNameWithoutExtension(fullName): "
                    + Path.GetFileNameWithoutExtension(fullName) + "\n";
                message += "Path.GetExtension(fullName): "
                    + Path.GetExtension(fullName) + "\n";
                message += "Path.Combine(path, name): "
                    + Path.Combine(path, name) + "\n";
                message += "Path.GetPathRoot(path): "
                    + Path.GetPathRoot(path) + "\n";
                message += "Path.GetPathRoot(fullName): "
                    + Path.GetPathRoot(fullName) + "\n";
                message += "Path.GetRandomFileName(): "
                    + Path.GetRandomFileName() + "\n";
                message += "Path.GetRandomFileName(): "
                    + Path.GetRandomFileName() + "\n";
                message += "Path.GetRandomFileName(): "
                    + Path.GetRandomFileName() + "\n";
                message += "Path.GetTempFileName(): "
                    + Path.GetTempFileName() + "\n";
                message += "Path.GetTempFileName(): "
                    + Path.GetTempFileName() + "\n";
                message += "Path.GetTempPath(): "
                    + Path.GetTempPath() + "\n";
                message += "AltDirectorySeparatorChar: '"
                    + Path.AltDirectorySeparatorChar + "'\n";
                message += "DirectorySeparatorChar: '"
                    + Path.DirectorySeparatorChar + "'\n";
                message += "PathSeparator: '"
                    + Path.PathSeparator + "'\n";
                message += "VolumeSeparatorChar: '"
                    + Path.VolumeSeparatorChar + "'\n";
        
                String unsafeFileName = "..\\..\\..\\" + name;    // Опасное имя файла
                String safeFileName = Path.GetFileName(unsafeFileName);
                message += "\nОпасное имя файла: " + unsafeFileName + "\n";
                message += "Безопасное имя файла: " + safeFileName + "\n";
                message += "Полное опасное имя: " + Path.Combine(path, unsafeFileName) + Environment.NewLine;
                message += "Полное опасное имя: " + Path.GetFullPath(Path.Combine(path, unsafeFileName)) + "\n";
                message += "Полное безопасное имя: " + Path.Combine(path, safeFileName);
        
                // Печать результатов
                Console.WriteLine(message); // На консоль
                DEBUG.WriteLine(message);   // На панель Output
        
                Console.ReadLine(); // Задержка окна
            }
        }
    }
  • Запустите проект - результат должен быть примерно таким
  • БЕЗ ИСПОЛЬЗОВАНИЯ PATH
    currentDirectory: E:\Tmp\FilesAndDirectory\App5\bin\Debug
    fullName: E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe
    name: App5.exe
    	
    С ИСПОЛЬЗОВАНИЕМ PATH
    path = Path.GetDirectoryName(fullName): E:\Tmp\FilesAndDirectory\App5\bin\Debug
    Path.GetFullPath(name): E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe
    Path.GetFileName(fullName): App5.exe
    Path.GetFileNameWithoutExtension(fullName): App5
    Path.GetExtension(fullName): .exe
    Path.Combine(path, name): E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe
    Path.GetPathRoot(path): E:\
    Path.GetPathRoot(fullName): E:\
    Path.GetRandomFileName(): epviufbp.0k1
    Path.GetRandomFileName(): tyzaz4fb.so2
    Path.GetRandomFileName(): ucnivb2v.idh
    Path.GetTempFileName(): C:\Users\CHETKOB\AppData\Local\Temp\tmp15B4.tmp
    Path.GetTempFileName(): C:\Users\CHETKOB\AppData\Local\Temp\tmp15B5.tmp
    Path.GetTempPath(): C:\Users\CHETKOB\AppData\Local\Temp\
    AltDirectorySeparatorChar: '/'
    DirectorySeparatorChar: '\'
    PathSeparator: ';'
    VolumeSeparatorChar: ':'
    	
    Опасное имя файла: ..\..\..\App5.exe
    Безопасное имя файла: App5.exe
    Полное опасное имя: E:\Tmp\FilesAndDirectory\App5\bin\Debug\..\..\..\App5.exe
    Полное опасное имя: E:\Tmp\FilesAndDirectory\App5.exe
    Полное безопасное имя: E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe

    Упражнение 6. Простой просмотрщик файлов

    Построим диалоговое приложение, позволяющее перемещаться по файловой системе и просматривать характеристики файлов. С оформлением пользовательского интерфейса сильно стараться не будем, а уделим больше внимания функциональности.

  • Добавьте к решению командой File/Add/New Project новый проект оконного приложения Windows Forms с именем App6 и назначьте его стартовым
  • В режиме Design выделите форму и установите для нее в панели Properties значения свойств
  • Text='Простой просмотрщик файлов', Size/Width=700, Size/Height=300
  • В режиме Design выделите форму и поместите из панели Toolbox дочерний компонент Panel (двойным щелчком на нем, или перетащите курсором). Установите для него в панели Properties значения свойств
  • Dock=Top, Size/Height=40
  • Опять выделите саму форму, щелкнув на ее заголовке (или свободном месте клиентской области), поместите еще один дочерний элемент Panel и установите для него свойство
  • Dock=Fill
  • Через выпадающий список панели Properties выделите объект panel1, поместите на него дочерний компонент Button и настройте свойства так:
  • (Name)=btnUp, Location/X=13, Location/Y=9, Text='Вверх'
  • Снова выделите на форме объект panel1 и добавьте на него текстовую метку Label со свойствами:
  • (Name)=lblCurrentDir, AutoSize=True, Location/X=103, Location/Y=12, Text='Текущий каталог: '
  • Выделите объект panel2, поместите на него дочерний компонент SplitContainer и сместите у него разделитель влево, чтобы ширина его левой панели ( Panel1 ) была меньше ширины правой ( Panel2 ) и составляла от нее примерно одну четверть
  • Выделите левую панель ( Panel1 ) объекта splitContainer1 и поместите в нее компонент ListBox со свойствами:
  • (Name)=listDir, Dock=Fill
  • Выделите правую панель ( Panel2 ) объекта splitContainer1 и поместите в нее компонент DataGridView со свойствами:
  • (Name)=gridFile, Dock=Fill, ColumnHeadersHeightSizeMode=AutoSize
  • На этом разработка пользовательского интерфейса закончена. Остальное, если возникнет необходимость, мы донастроим в процедурном коде, а пока форма будет выглядеть примерно так

  • Щелкните правой кнопкой мыши в любом месте на форме и выполните команду контекстного меню View Code, чтобы открыть кодовую часть - файл Form1.cs
  • Заполните файл Form1.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
        
    using System.IO;
        
    namespace App6
    {
        public partial class Form1 : Form
        {
            Panel fileDetails;// Поле, инициализируется неявно нулевым адресом: null
        
            public Form1()
            {
                InitializeComponent();
        
                // Программные донастройки
                gridFile.ReadOnly = true;       // Только для просмотра
                gridFile.SelectionMode =
                    DataGridViewSelectionMode.FullRowSelect;// Выделять всю строку
        
                // Начальная информация
                currentPath = Directory.GetCurrentDirectory();
                ShowDirectoryContents(currentPath);
                fileDetails = splitContainer1.Panel2;
                fileDetails.AutoScroll = true;
            }
        
            String currentPath;// Поле, инициализируется неявно пустой строкой
            void ShowDirectoryContents(String path)
            {
                // Получить объект справки текущего каталога
                DirectoryInfo curDir = new DirectoryInfo(path);
        
                // Получить для текущего каталога 
                // объекты справки файлов и подкаталогов
                FileInfo[] files = curDir.GetFiles();
                DirectoryInfo[] dirs = curDir.GetDirectories();
                lblCurrentDir.Text = "Текущий каталог: " + path;
        
                // Привязываем к элементам отображения
                listDir.DataSource = dirs;
                listDir.DisplayMember = "Name"; // Отображаемый вектор
                listDir.ValueMember = "Name";   // Скрытый вектор
                gridFile.DataSource = files;    // Показываем все
            }
        
            private void btnUp_Click(object sender, EventArgs e)
            {
                // Уже "Мой компьютер" и выше некуда
                if (currentPath == String.Empty)
                    return;
                // Хотим подняться выше текущего корня,
                // поэтому выводим доступные устройства
                else if (currentPath == Path.GetPathRoot(currentPath))
                {
                    System.Collections.ArrayList array =
                        new System.Collections.ArrayList();
                    DriveInfo[] drives = DriveInfo.GetDrives();
                    // Фильтруем готовые устройства
                    foreach (DriveInfo drive in drives)
                    {
                        if (drive.IsReady)
                        {
                            String name = drive.Name;
                            name = name.Split('\\')[0];// Убираем слэш
                            array.Add(name);
                        }
                    }
        
                    listDir.DataSource = array; // Привязываем список доступных устройств
                    gridFile.DataSource = null; // Нет файлов
                    currentPath = String.Empty; // Пути нет
                    lblCurrentDir.Text = "Мой компьютер";   // Меняем заголовок метки
        
                    return;
                }
                // Находимся ниже корня текущего устройства
                // и есть куда подниматься
                else
                {
                    String path = Path.Combine(currentPath, "..");
                    currentPath = Path.GetFullPath(path);   // Этажом выше без '..'
                    ShowDirectoryContents(currentPath);
                }
            }
        
            private void listDir_DoubleClick(object sender, EventArgs e)
            {
                String dir = listDir.SelectedValue.ToString();// Извлекаем из списка
                currentPath = Path.Combine(currentPath, dir);
                if (currentPath.IndexOf('\\') == -1)
                    currentPath += "\\";    // Если только что спустились в корень
                ShowDirectoryContents(currentPath);
            }
        
            private void gridFile_Click(object sender, EventArgs e)
            {
                if (gridFile.Rows.Count == 0)
                    return; // Нечего выделять
        
                // Локальные переменные инициализировать обязательно, а поля нет
                int selectIndex = gridFile.CurrentCell.RowIndex;
                DataGridViewRow row = gridFile.Rows[selectIndex];
                DataGridViewCellCollection cells = gridFile.Rows[selectIndex].Cells;
                String message = "";
        
                // Извлекаем информацию из текущей строки
                for (int i = 0; i < gridFile.Columns.Count; i++)
                {
                    String columnName = gridFile.Columns[i].Name;
                    String columnValue = row.Cells[i].Value.ToString();
                    message += columnName + ":  " + columnValue + "\n";
                }
        
                // Создаем окно и выводим информацию
                Form frm = new Form();
                frm.Width = 500;
                frm.Height = 320;
                frm.Text = "Подробности";   // Заголовок окна
                frm.MinimizeBox = false;    // Убираем системные кнопки
                frm.MaximizeBox = false;    // Убираем системные кнопки
                frm.ShowInTaskbar = false;  // Не отображать в панели задач
                frm.StartPosition = FormStartPosition.CenterParent;// По центру родителя
        
                // Промежуточная панель - для скролирования
                Panel panel = new Panel();
                panel.BackColor = System.Drawing.Color.White;
                panel.Dock = DockStyle.Fill;
                panel.AutoScroll = true;
                frm.Controls.Add(panel);// Отдаем окну
        
                // Отображающая текстовая метка
                Label lbl = new Label();
                lbl.AutoSize = true;
                lbl.Parent = panel;     // Отдаем панели
                // panel.Controls.Add(lbl);// Вариант - отдаем панели
                lbl.Text = message;
        
                // Заряжаем дополнительные окна вывода раньше диалога
                Console.Clear(); Console.Write(message); // !!!!!!!!!!!!!!!!!!!!
                System.Diagnostics.Debug.Write(message + Environment.NewLine);
        
                frm.ShowDialog();   // Показываем окно в модальном режиме
            }
        }
    }
  • Запустите приложение - пока оно не реагирует на действия пользователя, поскольку интерфейсные элементы не присоединены к своим обработчикам
  • В режиме Design выделите кнопку btnUp, зайдите в панель Properties, щелкните на пиктограмме Events, раскройте список поля для события Click и выберите обработчик btnUp_Click
  • Подобным же образом присоедините к событию DoubleClick объекта списка listDir обработчик listDir_DoubleClick, и к событию Click объекта сетки gridFile - обработчик gridFile_Click
  • Запустите приложение - при щелчке на строке сетки выбрасывается исключение
  • Причина в том, что в коде обработчика gridFile_Click делается попытка вывода информации в консольное окно, которое не создано. Если мы не хотим использовать консольное окно, то следует закомментировать или удалить строку

    Console.Clear(); Console.Write(message); // !!!!!!!!!!!!!!!!!!!!

    Но мы добавим к приложению консольное окно, чтобы вспомнить, как это делается.

  • В панели Solution Explorer для узла App6 вызовите контекстное меню и выполните команду Properies
  • Другой способ - в панели Solution Explorer выделить узел проекта App6 (или любой член проекта App6 ) и выполнить команду главного меню оболочки Project/App6 Properties.

  • На вкладке Application окна настроек проекта в выпадающем списке Output type установите значение Console Application
  • Запустите и испытайте разработанное приложение
  • Один из рабочих моментов может выглядеть так

    Еще один канал вывода подробностей, предусмотренный нами, - панель Output оболочки.

  • Разберитесь с кодом
  • Упражнение 7. Генератор случайных номеров

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

  • Добавьте к решению командой File/Add/New Project новый проект типа Windows Forms Application с именем App7 и назначьте его стартовым
  • Командой Project/App7 Properties вызовите диалоговое окно настройки проекта и в выпадающем списке Output type вкладки Application установите значение Console Application
  • Разработайте интерфейс диалогового окна в соответствии с таблицей свойств
  • Таблица свойств диалогового окна
    Элемент Свойство Значение Пояснения
    Form FormBorderStyle FixedDialog Фиксированный размер
    Size 375; 385 Ширина и высота окна
    MaximizeBox False Скрыть системную кнопку
    MinimizeBox False Скрыть системную кнопку
    Text Генератор случайных номеров Заголовок окна
    StartPosition CenterScreen Начальное положение
    Font Microsoft Sans Serif; 7,8pt Родительский шрифт (по умолчанию)
    Label AutoSize False Подстройка размера под содержимое
    BackColor Info Цвет фона
    Text Генерирует список номеров случайным образом Содержимое
    Font Arial; 12pt; style=Bold Шрифт
    ForeColor Blue Цвет текста
    TextAlign MiddleCenter Центрирование текста
    Location 16; 8 Позиция относительно клиентской области формы
    Label Text Куда разместить файл:
    Button (Name) btnDirTarget Имя экземпляра кнопки
    Text Browse Метка кнопки
    TextBox (Name) txtDirTarget Путь к файлу
    Label Text Имя текстового файла: Метка для txtFileName
    TextBox (Name) txtFileName Текстовое поле для имени файла
    Text ResultNumber.txt Имя файла по умолчанию
    Label Text Нижняя граница диапазона: Текстовая метка
    TextBox (Name) txtMin Текстовое поле для нижней границы генерируемых номеров
    Text 0 Значение по умолчанию
    Label Text Постоянный шаг: Текстовая метка
    TextBox (Name) txtStep Дискрета приращения диапазона
    Text 250 Шаг по умолчанию
    Label Text Количество точек: Текстовая метка
    TextBox (Name) txtCount Число генерируемых номеров
    Text 2000 Значение по умолчанию
    Label Text Верхняя граница диапазона: Метка
    TextBox (Name) txtMax Расчет верхней границы
    Text Пусто
    ReadOnly True Только для чтения
    Button (Name) btnExecute Кнопка выполнения
    Text Execute Метка кнопки

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

  • Запустите приложение - пользовательский интерфейс будет выглядеть так
  • Создадим и заполним обработчики элементов управления в процедурном коде файла Form1.cs.

  • Добавьте в начало файла Form1.cs подключение библиотечного пространства имен System. IO для классов ввода-вывода
  • using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
        
    // Подключение дополнительных пространств имен
    using System.IO;
  • В режиме Design двойным щелчком на заголовке формы (или ином незанятом компонентами месте) создайте обработчик начальной загрузки и заполните его так
  • int min, step, count, max;
            FolderBrowserDialog dialogFolder = new FolderBrowserDialog();
            private void Form1_Load(object sender, EventArgs e)
            {
                if (txtDirTarget.Text == String.Empty ||
                    Directory.Exists(txtDirTarget.Text) == false)
                {
                    dialogFolder.RootFolder =
                        System.Environment.SpecialFolder.MyComputer;
                    dialogFolder.SelectedPath = "";
                    txtDirTarget.Text = String.Empty;
                }
                else
                {
                    dialogFolder.SelectedPath = txtDirTarget.Text;
                }
        
                dialogFolder.Description = 
                    "Выберите место размещения текстового файла";
        
                min = Convert.ToInt32(txtMin.Text);
                step = Convert.ToInt32(txtStep.Text);
                count = Convert.ToInt32(txtCount.Text);
                max = min + step * count;
                txtMax.Text = max.ToString();
            }

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

  • В режиме Design выделите кнопку btnDirTarget, откройте панель Properties, щелкните на пиктограмме Events, найдите событие Click и двойным щелчком на правой ячейке сетки свойств создайте обработчик, который заполните так
  • private void btnDirTarget_Click(object sender, EventArgs e)
            {
                dialogFolder.ShowDialog();
                txtDirTarget.Text = dialogFolder.SelectedPath;
            }

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

    Здесь есть одна тонкость. Текстовое поле может редактироваться пользователем и, таким образом, может содержать недостоверный путь. Можно, конечно, создавать каталог программно, если он не существует, но для тренировки мы просто заблокируем это поле для редактирования. Если присвоить txtDirTarget свойство ReadOnly=True, то оно будет серым и некрасивым. Другая возможность - просто запретить ввод символов в это поле, что мы сейчас и сделаем.

  • Выделите текстовое поле txtDirTarget, в панели Properties установите режим Events и двойным щелчком на поле значений события KeyPress создайте обработчик со следующим кодом
  • private void txtDirTarget_KeyPress(object sender, KeyPressEventArgs e)
            {
                e.Handled = true;
            }
  • Запустите приложение, выберите папку и попробуйте редактировать поле - стрелки и клавиша Delete продолжают функционировать
  • Добъем это поле окончательно.

  • Выделите текстовое поле txtDirTarget, в панели Properties установите режим Events и двойным щелчком на поле значений события KeyDown создайте обработчик со следующим кодом
  • private void txtDirTarget_KeyDown(object sender, KeyEventArgs e)
            {
                e.Handled = true;
            }
  • Запустите приложение - теперь поле полностью нередактируется, хотя и принимает курсор и фокус ввода
  • Таким образом, мы видим, что с помощью событий KeyDown и KeyPress можно контролировать пользовательский ввод в экземпляр текстового поля. Применим этот факт для фильтрации цифровых клавиш в текстовых полях txtMin, txtStep и txtCount, которые должны принимать только целые числа. Создадим для них общие обработчики, поскольку условие ограничения по вводу только цифр для них общее. Но прежде нам нужно узнать, в какой последовательности срабатывают события KeyDown и KeyPress.

  • В режиме Design выделите (можно частично) с помощью лассо курсора (по русски - просто обведите, а используемое здесь словоблудие, чтобы привыкали к терминам) сразу 3 упомянутых текстовых поля и в панели Properties/Events задайте для них имена обработчиков, как показано на снимке
  • Заполните созданные обработчики кодом вывода информации на консоль
  • private void txt_KeyDown(object sender, KeyEventArgs e)
            {
                Console.WriteLine("KeyDown");
            }
        
            private void txt_KeyPress(object sender, KeyPressEventArgs e)
            {
                Console.WriteLine("KeyPress");
            }
  • Запустите приложение - получится последовательность
  • KeyDown
    KeyPress

    Зная эту последовательность срабатывания, можно запрограммировать фильтр для пропуска только цифр.

  • Заполните общие обработчики следующим кодом
  • bool isNumber;
            private void txt_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
            {
                isNumber =
                    e.KeyCode >= Keys.D0  e.KeyCode <= Keys.D9 // keyboard - основная клавиатура
                    || e.KeyCode >= Keys.NumPad0  e.KeyCode <= Keys.NumPad9 // keypad - дополнительная клавиатура
                    || e.KeyCode == Keys.Back;
            }
        
            private void txt_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
            {
                // Запрещаем в текстовом поле лишние символы
                if (!isNumber)
                    e.Handled = true;
            }

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

    Создадим еще один обработчик, в котором 'на лету' будет пересчитываться значение последнего текстового поля txtMax по данным других полей. Но прежде добавим маркеры исходных текстовых полей в код начальной инициализации, чтобы в общем обработчике можно было их идентифицировать (различать) и индивидуально обрабатывать.

  • Добавьте в функцию Form1_Load() следующий код
  • private void Form1_Load(object sender, EventArgs e)
            {
                ...................................................
        
                // Маркируем элементы для распознавания в общем обработчике
                txtMin.Name = "Min";
                txtStep.Name = "Step";
                txtCount.Name = "Count";
            }
  • В режиме Design выделите все три поля txtMin, txtStep и txtCount одновременно и создайте для них в панели Properties для события TextChanged общий обработчик с именем (нужно вручную ввести в поле) result_TextChanged, который заполните так
  • private void result_TextChanged(object sender, EventArgs e)
            {
                // Следим, чтобы редактируемое поле не оказалось пустым
                TextBox txtBox = (TextBox)sender;// Приводим ссылку к типу элемента
                if (txtBox.Text == String.Empty)
                {
                    switch (txtBox.Name)
                    {
                        case "Min":
                            txtMin.Text = "0";
                            break;
                        case "Step":
                            txtStep.Text = "0";
                            break;
                        case "Count":
                            txtCount.Text = "0";
                            break;
                    }
                }
        
                // Конвертируем разными способами
                min = Convert.ToInt32(txtMin.Text);
                step = Int32.Parse(txtStep.Text);
                int.TryParse(txtCount.Text, out count);
        
                max = min + step * count;
                txtMax.Text = max.ToString();
            }

    В этом коде скрыт один коварный нюанс, на который следует обратить внимание. Дело в том, что текстовое поле способно принимать сколь угодно длинные тексты. А в нашем коде обработчика result_TextChanged набор цифр преобразуется в целый тип Int32, имеющий конечную длину. Соответственно, длинный ввод пользователя может вызвать исключение. Не погружаясь глубоко в эту проблему, в которой нужно было бы еще учесть операции умножения предельно допустимых значений ввода (или изменить тип int на long ), просто ограничим количество вводимых цифр барьером, способным уместиться в целом типе. Это явно приближенный подход, но просто для примера, только для тренировки!

  • Модифицируйте обработчик txt_KeyPress( ) полей ввода цифр так
  • Было

    private void txt_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
            {
                // Запрещаем в текстовом поле лишние символы
                if (!isNumber)
                    e.Handled = true;
            }

    Стало

    private void txt_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
            {
                TextBox txtBox = sender as TextBox;// Приведение ссылки 
                int maxLength = int.MaxValue.ToString().Length - 1;// Длина ввода (с запасом)
        
                // Запрещаем в текстовом поле лишние символы 
                // и ограничиваем длину пользовательского ввода
                if (!isNumber || txtBox.Text.Length >= maxLength)
                    e.Handled = true;
            }

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

  • В режиме Design двойным щелчком на кнопке btnExecute создайте обработчик события по умолчанию, который заполните так
  • private void btnExecute_Click(object sender, System.EventArgs e)
            {
                String fileName = txtFileName.Text; // Копируем ссылку для удобства
        
                // Заполнено ли текстовое поле
                if (fileName.Length <= 0 || txtDirTarget.Text == String.Empty)
                {
                    MessageBox.Show("Вы не задали имя файла или каталога",
                        "Ошибка",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Stop);
                    return;
                }
        
                // Есть ли расширение txt
                int pos0 = fileName.LastIndexOf('.');// Ищем с конца
                if (pos0 == -1 || !String.Equals(fileName.Substring(pos0), ".txt"))
                {
                    MessageBox.Show("Вы не задали расширение .txt",
                        "Ошибка",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Stop);
                    return;
                }
        
                // Не допускать составное имя
                int pos1 = fileName.IndexOf('.');   // Ищем с начала
                if (pos0 != pos1)   // Если точек несколько
                {
                    MessageBox.Show("Задайте одно расширение .txt",
                        "Ошибка",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Stop);
                    return;
                }
        
                // Добавляем путь = полное имя файла
                fileName = Path.Combine(txtDirTarget.Text, fileName);
        
                if (!File.Exists(fileName))
                {
                    FileStream stream = File.Create(fileName);// Создать файл
                    stream.Close(); // Освободить ресурс
                }
        
                Console.Clear();    // Очистить консоль
                Random rand = new Random();// Генератор случайных чисел по равномерному закону
                int result;
                StreamWriter writeFile = new StreamWriter(fileName, false, 
                    System.Text.ASCIIEncoding.GetEncoding(1251));
                for (int i = 0; i < count; i++)
                {
                    result = min + i * step + rand.Next(step);
                    Console.WriteLine(result.ToString());   // Выводим на консоль
                    writeFile.WriteLine(result.ToString()); // Выводим в файл
                }
                writeFile.WriteLine("--------");    // Отделяем итог
                writeFile.WriteLine(count.ToString() + " шт.");
        
                writeFile.Close();   // Освободить ресурс
            }
  • Запустите приложение - все должно работать
  • В этом месте мне пришла мысль добавить еще одно текстовое поле для ввода серии (как в паспортах). Для этого нужно немного переделать пользовательский интерфейс формы. Заодно вспомним групповые операции выравнивания визуального конструктора форм.

  • В режиме Design не меняя размеров формы сдвиньте (стрелкой 'Вниз') кнопку Execute немного вниз
  • Охватите лассо курсора четыре нижних текстовых поля вместе с метками (до 'Имя текстового файла:') и тоже стрелкой 'Вниз' сдвиньте их вниз
  • Выделите текстовую метку 'Имя текстового файла:' вместе с ее полем, выполните Copy и сразу Paste
  • Выделите все пять текстовых полей и щелкните на кнопке Make Vertical Spacing Equal панели инструментов Layout, чтобы равномерно распределить элементы
  • То же самое действие примените к текстовым меткам
  • Настройте новые элементы в соответствии с стаблицей свойств
  • Элемент Свойство Значение
    Label Text Серия:
    TextBox ( Name ) txtSeries
    Text Пусто
  • Запустите приложение - интерфейс должен выглядеть примерно так
  • Теперь осталось включить значение серии в вывод программы.

  • Отредактируйте фрагмент кода обработчика btnExecute_Click( ) так
  • Было

    for (int i = 0; i < count; i++)
           {
           		result = min + i * step + rand.Next(step);
                   Console.WriteLine(result.ToString());   // Выводим на консоль
                   writeFile.WriteLine(result.ToString()); // Выводим в файл
    	}

    Стало

    for (int i = 0; i < count; i++)
           	{
    		result = min + i * step + rand.Next(step);
                   Console.WriteLine(txtSeries.Text.Trim() + result.ToString());   // Выводим на консоль
                   writeFile.WriteLine(txtSeries.Text.Trim() + result.ToString()); // Выводим в файл
    	}

    Сохранение состояния (параметров) приложения

    В завершение, на примере последнего упражнения, добавим средства сохранения состояния, чтобы при новом запуске прежние настройки текстовых полей и текущий каталог остались. Существует несколько (рассмотрим три) стандартных способов решения этой задачи и все они основаны на использовании абстрактного класса ApplicationSettingsBase:

  • Программный - вручную создать класс, производный от ApplicationSettingsBase, и привязать в коде нужный параметр к сохраняемому свойству формы или элемента управления
  • Воспользоваться страницей Settings конструктора проектов (вызывается командой Project/App7 Properties ). Страница автоматически генерируется оболочкой при создании заготовки проекта и базируется на файле Properties\Settings.Designer.cs.
  • Воспользоваться панелью Properties для формы или элемента управления и привязать нужный параметр непосредственно к его свойству.
  • Два последних способа относятся к режиму разработки приложения (не к режиму выполнения). Их подробное описание можно найти в документации по ссылке: http://msdn.microsoft.com/ru-ru/library/a65txexh(VS.90).aspx

    Способ 1.

    Вначале рассмотрим программный способ (мы же программисты!).

  • Командой Project/Add Class добавьте новый файл с именем MySettings.cs, который заполните так
  • using System;
    using System.Configuration;
        
    namespace App7
    {
        class MySettings : ApplicationSettingsBase
        {
            // Параметр типа 'Приложение' менять нельзя во время работы приложения
            // Параметр типа 'Пользователь' можно менять во время работы приложения
            //[ApplicationScopedSetting()]// Область действия - 'Приложение'
            [UserScopedSettingAttribute()]       // Область действия - 'Пользователь' 
            [DefaultSettingValueAttribute("0")]  // Значение параметра по умолчанию
            public String Min
            {
                get
                {
                    return ((String)this["Min"]);
                }
                set
                {
                    this["Min"] = (String)value;
                }
            }
        
            [UserScopedSettingAttribute()]
            [DefaultSettingValueAttribute("250")]
            public String Step
            {
                get
                {
                    return ((String)this["Step"]);
                }
                set
                {
                    this["Step"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("2000")]
            public String Count
            {
                get
                {
                    return ((String)this["Count"]);
                }
                set
                {
                    this["Count"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("ResultNumber.txt")]
            public String FileName
            {
                get
                {
                    return ((String)this["FileName"]);
                }
                set
                {
                    this["FileName"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("")]
            public String DirTarget
            {
                get
                {
                    return ((String)this["DirTarget"]);
                }
                set
                {
                    this["DirTarget"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("")]
            public String Series
            {
                get
                {
                    return ((String)this["Series"]);
                }
                set
                {
                    this["Series"] = (String)value;
                }
            }
        }
    }

    Здесь мы определили общедоступные свойства с ассоциативными именами (чтобы лучше различать) и прикрепили к каждому из них аттрибуты, определяющие область видимости (действия) параметра и значение по умолчанию. Параметр типа UserScopedSetting (или UserScopedSettingAttribute - полное имя) пользователь может менять во время работы приложения, а типа ApplicationScopedSetting - нет (например, строки подключения).

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

  • Модифицируйте в файле Form1.cs конструктор класса формы приложения следующим образом
  • // Объявили и инициализировали поле для объекта параметров
            MySettings settings = new MySettings(); 
            public Form1()
            {
                InitializeComponent();
        
                // Привязка именованных параметров к 
                // сохраняемым свойствам элементов управления
                txtMin.DataBindings.Add(new Binding("Text", settings, "Min"));
                txtStep.DataBindings.Add(new Binding("Text", settings, "Step"));
                txtCount.DataBindings.Add(new Binding("Text", settings, "Count"));
                txtFileName.DataBindings.Add(new Binding("Text", settings, "FileName"));
                txtDirTarget.DataBindings.Add(new Binding("Text", settings, "DirTarget"));
                txtSeries.DataBindings.Add(new Binding("Text", settings, "Series"));
            }
  • Запустите/завершите приложение - пока значения текстовых полей не сохраняются
  • Причина здесь в том, что привязанные параметры нужно явно записывать при завершении приложения. Обычно это делается в обработчике события FormClosing формы.

  • Выделите форму в режиме Design, перейдите в панель Properies/Events и двойным щелчком на поле события FormClosing создайте для него обработчик, который заполните так
  • private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                settings.Save();
            }
  • Запустите/завершите приложение - значения текстовых полей сохраняются, кроме поля txtDirTarget
  • Нестабильность в сохранении значения текстового поля txtDirTarget объясняется тем, что мы его заполняем программно и элемент только показывает присвоенное значение, а не принимает его, пока не получит фокус ввода. Здесь можно ввести дополнительную переменную, в ней хранить значение текстового поля и ее же сохранять. Но чтобы много не переделывать, можно просто программно передавать фокус ввода текстовому полю каждый раз после получения им нового значения от диалога выбора каталога.

  • Дополните обработчик кнопки btnDirTarget строкой передачи фокуса текстовому полю txtDirTarget так
  • private void btnDirTarget_Click(object sender, EventArgs e)
            {
                dialogFolder.ShowDialog();
                txtDirTarget.Text = dialogFolder.SelectedPath;
                txtDirTarget.Focus();// Без этого не сохраняет параметр в settings
            }

    Последний оператор устанавливает на текстовое поле txtDirTarget фокус ввода, без чего значение Text не сохраняется в установках приложения при его закрытии (если только явно не щелкнуть по нему курсором).

  • Запустите/завершите приложение - теперь состояние приложения сохраняется полностью
  • Код, помещенный нами ранее в начало функции Form1_Load(), выполняет проверку на случай первого запуска приложения или когда между запусками сохраненное значение каталога изменилось. Чтобы испытать его работу, можно добавить такой обработчик двойного щелчка формы

    private void Form1_DoubleClick(object sender, EventArgs e)
            {
                settings.DirTarget = "";
                txtDirTarget.Text = "";
                dialogFolder.SelectedPath = "";
                dialogFolder.RootFolder =
                    System.Environment.SpecialFolder.MyComputer;
            }

    Способ 2.

    При создании нового проекта мастер оболочки попутно создает заготовки файлов Settings.settings и Settings.Designer.cs, в которые на этапе проектирования можно записать для компилятора исходные данные параметров, подлежащих сохранению. Эти файлы находятся в служебной папке Properties каталога проекта. Для удобства выполнения такой работы оболочка имеет вкладку (страницу) Settings панели конструктора проектов.

    Чтобы продолжить рассмотрение Способа 2, необходимо вначале нейтрализовать весь код Способа 1, поскольку сохранять мы будем те же самые свойства текстовых полей.

  • В панели Solution Explorer выделите файл MySettings.cs и командой Exclude From Project контекстного меню (либо в разделе Project меню оболочки) исключите его из проекта
  • Закомментируйте (не удаляйте!) в конструкторе класса формы код привязки сохраняемых параметров к свойствам элементов
  • // Объявили и инициализировали поле для объекта параметров
            //MySettings settings = new MySettings(); 
            public Form1()
            {
                InitializeComponent();
        
                // Привязка именованных параметров к 
                // сохраняемым свойствам элементов управления
                /*////////////////////////////////////////////////////
                txtMin.DataBindings.Add(new Binding("Text", settings, "Min"));
                txtStep.DataBindings.Add(new Binding("Text", settings, "Step"));
                txtCount.DataBindings.Add(new Binding("Text", settings, "Count"));
                txtFileName.DataBindings.Add(new Binding("Text", settings, "FileName"));
                txtDirTarget.DataBindings.Add(new Binding("Text", settings, "DirTarget"));
                txtSeries.DataBindings.Add(new Binding("Text", settings, "Series"));
                ////////////////////////////////////////////////////*/
            }
  • Командой Project/App7 Properties откройте панель конструктора проектов на вкладке Settings и настройте ее как показано на снимке
  • Если щелкнуть на пиктограмме View Code вкладки Settings, то оболочка создаст в корневом каталоге проекта файл Settings.cs с еще одной частью класса Settings (первая часть находится в Properties\Settings.Designer.cs ) для более тонкого управления параметрами.

  • Сохраните документ командой File/Save All - оболочка создала конфигурационный файл приложения app.config и добавила в него наши настройки
  • Теперь осталось подключить эти настройки к коду приложения.

  • Модифицируйте в файле Form1.cs члены класса формы следующим образом
  • // Объявили и инициализировали поле для объекта параметров
            //MySettings settings = new MySettings(); 
            Properties.Settings settings = new App7.Properties.Settings();
            public Form1()
            {
                InitializeComponent();
        
                // Привязка именованных параметров к 
                // сохраняемым свойствам элементов управления
                /*////////////////////////////////////////////////////
                txtMin.DataBindings.Add(new Binding("Text", settings, "Min"));
                txtStep.DataBindings.Add(new Binding("Text", settings, "Step"));
                txtCount.DataBindings.Add(new Binding("Text", settings, "Count"));
                txtFileName.DataBindings.Add(new Binding("Text", settings, "FileName"));
                txtDirTarget.DataBindings.Add(new Binding("Text", settings, "DirTarget"));
                txtSeries.DataBindings.Add(new Binding("Text", settings, "Series"));
                ////////////////////////////////////////////////////*/
        
                // Чтение параметров
                //*////////////////////////////////////////////////////
                txtMin.Text = settings.Min;
                txtStep.Text = settings.Step;
                txtCount.Text = settings.Count;
                txtFileName.Text = settings.FileName;
                txtDirTarget.Text = settings.DirTarget;
                txtSeries.Text = settings.Series;
                ////////////////////////////////////////////////////*/
            }
        
            private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                // Запись параметров
                //*////////////////////////////////////////////////////
                settings.Min = txtMin.Text;
                settings.Step = txtStep.Text;
                settings.Count = txtCount.Text;
                settings.FileName = txtFileName.Text;
                settings.DirTarget = txtDirTarget.Text;
                settings.Series = txtSeries.Text;
                ////////////////////////////////////////////////////*/
        
                settings.Save();
            }
  • Запустите проект в режиме Release, измените настройки и закройте приложение - параметры сохраняются
  • В панели Solution Explorer щелкните на пиктограмме Show All Files - к исполнимой сборке добавился файл конфигурации App7.exe.config
  • Теперь без этого файла приложение работать не будет и при развертывании приложения на других компьютерах файл придется включать в установочный пакет (это не страшно, только бы не забыть!). Файл App7.exe.config имеет такое же содержимое (откройте его!), что и app.config, но носит имя приложения.

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

    Способ 3.

    Этот способ выполняет создание и привязку параметров через панель свойств и подробно описан в документации http://msdn.microsoft.com/ru-ru/library/wabtadw6%28v=VS.90%29.aspx

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

  • Командой Project/App7 Properties откройте панель конструктора проектов на вкладке Settings и удалите прежние параметры (по одному или сразу все)
  • Через Solution Explorer удалите файл app.config из корневого узла текущего проекта
  • В режиме Design выделите элемент txtMin, в панели Properties раскройте узел ApplicationSettings и щелкните на кнопке с многоточием в поле свойства PropertyBinding
  • Выделите привязываемое свойство Text элемента txtMin и в раскрывающемся списке выполните ссылку New для вызова окна создания нового параметра
  • Заполните окно как показано на снимке
  • Аналогичным образом создайте и настройте параметры и для остальных элементов со значениями из предыдущих способов
  • Обратите внимание, что начальные значения параметров (если они есть) подставляются оболочкой из свойств компонентов, которые мы определяли при их создании. В итоге мы получим точно такой же результат, что и в предыдущем способе. Поэтому код в классе Form1, связанный с настройками, менять не будем - он у нас уже есть.

  • Запустите проект - параметры сохраняются, но опять при развертывании (распространении программы) нужно таскать и файл app.config (или точно такой же App7.exe.config )
  • Итак, в данном упражнении мы спроектировали инструмент, который может оказаться удобным при формировании некоторых номеров случайным образом (чтобы враги уж совсем не угадали, можно еще и зашифровать). При этом применили знания по теме данной работы. Из расссмотренных способов мне больше нравится первый. Чтобы к нему вернуться, нужно вновь добавить в проект файл MySettings.cs и удалить из него все, связанное с двумя последними способами.

    Следует заметить, что содержимое файла Settings.Designer.cs в папке Properties проекта, полученное после настроек с использованием любого из последних двух способов, почти полностью идентично содержимому файла MySettings.cs, который мы формировали вручную в первом способе. Отсюда мораль: если не хотим таскать за собой файл app.config, то вначале быстренько создаем параметры через оболочку, затем перетаскиваем созданный ею код в свой класс. После этого опять удаляем все параметры (как перед выполнением способа 3) и файл конфигурации, а используем уже наш класс с параметрами программным образом. (Что называется, - 'немного подъехать' на возможностях оболочки!)

    Страницы:

    Все необходимые для выполнения данной работы программы можно найти в Setup.zip.

    Для работы с файлами библиотека .NET Framework имеет достаточное количество классов. Познакомимся с ними. Простейшим уровнем файловых операций является получение информации о файлах и каталогах, смена каталогов, получение сведений о размерности файлов, времени создания, режиме доступа, удаления, переименования, копирования и т.д. без их открытия. Для этого имеются следующие библиотечные классы пространства имен System.IO ( input, output ) сборки mscorlib.dll:

  • Directory, File - предоставляют статические методы (объекта-типа) для сбора информации о файлах и каталогах локального компьютера или сервера
  • DriveInfo, DirectoryInfo, FileInfo - предоставляют экземплярные методы и свойства (объектов-экземпляров) для тех же целей
  • Классы Directory и File

    Если взглянуть на классы Directory и File через панель Object Browse r, то можно убедиться, что эти классы имеют только статические методы, приведенные ниже. Предполагается, что если есть методы чтения Get, то есть и дуальные методы Set, которые в таблицах не указаны.

    Некоторые методы класса Directory
    Метод Описание
    CreateDirectory() Создает каталог вместе с указанным путем
    Delete() Удаляет пустой каталог, если второй необязательный параметр не указан или равен false, то каталог с содержимым метод удалять отказывается. Если второй параметр указан и равен true, то удаляет каталог со всем содержимым
    Exists() Возвращает флаг существования каталога
    GetCreationTime(), GetLastAccessTime(), GetLastWriteTime() Возвращают структуру DateTime, которая содержит время создания каталога, время последнего чтения или записи
    GetDirectories(), GetFiles(), GetLogicalDrives() Возвращает массив строк, по одной на каждый подкаталог, файл или устройство в указанном каталоге (в зависимости от используемого метода). Методы имеют перегрузку, задающую шаблон (фильтр) поиска (например, My*.txt, My???.*, *My.exe, *my*). Регистр в шаблонах поиска значения не имеет
    GetParent() Анализирует переданную строку и возвращает имя родительского каталога
    GetCurrentDirectory() Возвращает текущий каталог с полным путем
    Move() Принимает два строки - исходный каталог и целевой каталог с полными путями. Перемещает исходный каталог в указанное место в пределах одного дискового устройства (иначе, - копирует)
    GetAccessControl() Возвращает объект со списком контроля доступа Windows

    Некоторые методы класса File
    Метод Описание
    Copy() Копирует один файл в другой с возможной заменой содержимого существующего файла (если задан третий необязательный параметр true )
    Delete() Удаляет указанный файл, но не возбуждает исключение, если файл не найден
    Exists() Проверяет существование файла и возвращает флаг
    GetAttributes() Возвращает атрибуты указанного файла
    GetCreationTime(), GetLastAccessTime(), GetLastWriteTime() Возвращают структуру DateTime, которая содержит время создания файла, время последнего чтения или записи
    Move() Перемещает или переименовывает файлы
    Create(), CreateText() Создают файл
    Open(), OpenText(), OpenRead(), OpenWrite() Открывают файл, открывают только для чтения, открывают только для записи
    ReadAllText(), ReadAllLines(), ReadAllBytes() Читают весь файл и возвращают его содержимое в виде строки, массива строк или массива байт
    WriteAllText(), WriteAllLines(), WriteAllBytes() Пишут весь файл за один прием, использую принятую строку, массив строк или массив байт. Если файл существует, то он перезаписывается
    GetAccessControl() Возвращает объект со списком контроля доступа Windows

    Упражнение 1. Получение списка файлов

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

  • Создайте новые решение и проект командой File/New/Project в соответствии со снимком
  • Командой Project/App1 Properties преобразуйте проект в консольное приложение настройкой списка Output type
  • Откройте файл Program.cs и добавьте в него код, выделенный в следующем листинге
  • using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
        
    // Добавляем пространство имен
    using System.IO;
        
    namespace App1
    {
        class Program  // Убрали слово static!!!
        {
            [STAThread]
            static void Main()
            {
                // Вызов нашей функции
                (new Program()).ConsoleOutput();// Через экземпляр - для куража!
        
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        
            // Вывод информации в консольное окно
            void ConsoleOutput()
            {
                // Настраиваем консоль
                Console.Clear();
                Console.ForegroundColor = ConsoleColor.White;
                Console.CursorVisible = false;
                Console.WindowWidth = 70;// 80 знакомест - по умолчанию
                Console.WindowHeight = 20;
                Console.Title = "Список файлов каталога сборки";
        
                string dir = Directory.GetCurrentDirectory();  // Каталог сборки
                string[] fileList = Directory.GetFiles(dir);   // Список файлов
        
                int i = 0;                                      // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    Console.WriteLine("{0}) {1}", ++i, path);
                }
        
                Console.WriteLine();
                i = 0;                                          // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    string file = path.Substring(path.LastIndexOf('\\') + 1);// Экранируем символ
                    Console.WriteLine("{0}) {1}", ++i, file);
                }
        
                Console.WriteLine();
                i = 0;                                          // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    string file = path.Substring(path.LastIndexOf(@"\") + 1);// Неинтерпретируемая строка
                    Console.WriteLine("{0}) {1}", ++i, file);
                }
        
                Console.WriteLine();
                i = 0;                                          // Счетчик
                foreach (string path in fileList)               // Выводим список
                {
                    string file = Path.GetFileName(path);       // Сразу чистое имя файла
                    Console.WriteLine("{0}) {1}", ++i, file);
                }
            }
        }
    }

    Обратите внимание, что мы убрали перед объявлением класса ключевое слово static (для разнообразия). Поэтому нам пришлось создавать экземпляр класса, чтобы вызвать экземплярный метод вывода в консольное окно. Поскольку нам понадобился только одноразовый вызов метода, то не обязательно запоминать адрес экземпляра в ссылочной переменной, что мы и не стали делать.

  • Запустите приложение - консольное окно наполняется вариантами вывода имен файлов
  • Вначале мы распечатали полные имена файлов, которые вернул метод Directory. GetFiles(). Такие имена иногда называют квалифицированными ( the fully qualified file name ), поскольку в файловой системе компьютера они однозначно определяют файлы как единицы информации. Для получения укороченного имени ( the relative file name ) мы использовали варианты, которых для решения одной и той же задачи обычно можно придумать несколько - обычно именно с таким запасом устроена библиотека .NET Framework.

    Вывод в консольное окно мы организовали до того, как приложение войдет в цикл сообщений Windows, иначе консольное окно вначале появилось бы пустым и заполнилось бы только после закрытия диалогового окна. Но тогда все равно пришлось бы ставить задержку консольного окна, например, методом Console.ReadLine(), чтобы увидеть результаты до закрытия приложения.

    Теперь настроим окно Windows Forms.

  • Перейдите в режим Design файла Form1.c s и поместите из панели Toolbox на макет формы элемент ListBox, который настройте через панель Properties в соответствии с приведенной таблицей свойств
  • Значения свойств элементов управления макета Form1.cs
    Компонент Свойство Значение
    ListBox (Name) listBox1
    Dock Fill
  • Вызовите правой кнопкой мыши для макета формы контекстное меню и выполните команду View Code, чтобы перейти в кодовую часть формы. Заполните конструктор формы так
  • using System;
    using System.Windows.Forms;
        
    // Дополнительное подключение пространств имен
    using System.IO;
        
    namespace App1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
        
                // Привязка массива к списку для отображения и печать результатов
                String[] fileList = Directory.GetFiles(Directory.GetCurrentDirectory());
                for (int i = 0; i < fileList.Length; i++)
                    fileList[i] = String.Format("{0}) {1}", i + 1, Path.GetFileName(fileList[i]));
                listBox1.DataSource = fileList;
                listBox1.SelectionMode = SelectionMode.None;
            }
        }
    }
  • Запустите проект - результат должен быть таким (два окна на одном снимке)
  • Это приложение можно было бы довести до ума, дополнив диалоговым окном смены каталога, но мы пока рассматриваем инструменты пространства имен System.IO и отложем эту мысль на потом.

    Классы DirectoryInfo и FileInfo

    Эти классы прописаны в библиотеке с ключевым словом sealed (запечатанный), запрещающим использование в качестве базовых, и поэтому они не могут иметь наследников. В отличие от классов Directory и File, которые содержат только статические методы, эти классы содержат еще и свойства, а методы в них являются экземплярными. Это позволяет создавать целевые объекты данных, содержащие информацию о файловой системе компьютера.

    Классы DirectoryInfo и FileInfo имеют общего родителя - абстрактный класс FileSystemInfo, который наделяет их базовым набором методов и свойств. Некоторые из этих членов приведены в таблице.

    Некоторые члены базового класса FileSystemInfo
    Член Описание
    Attributes Свойство позволяет извлекать и устанавливать атрибуты для файла или каталога, используя комбинацию значений перечисления FileAttributes:
  • Archive - элемент помечен как архивный
  • Compressed - элемент сжат. Разрешено читать, но устанавливается только через механизмы ОС
  • Device - атрибут зарезервирован и пока не используется
  • Directory - элемент является каталогом
  • Encrypted - элемент зашифрован . Для файла это значит, что все данные в нем зашифрованы. Для каталога это означает, что режим шифрования включен по умолчанию для вновь. создаваемых файлов и подкаталогов. Разрешено читать, но устанавливается только через механизмы ОС
  • Hidden - элемент скрыт
  • Normal - элемент без атрибутов. Этот атрибут устанавливается по умолчанию, когда сняты все другие атрибуты
  • NotContentIndexed - элемент не будет индексирован службой индексации содержимого операционной системы
  • Offline - файл находится в автономном состоянии и в данный момент недоступен
  • ReadOnly - элемент доступен только для чтения
  • System - элемент, используемый исключительно операционной системой
  • Temporary - элемент помечен как временный и может быть удален после завершения работы приложения
  • ReparsePoint, SparseFile - только для файловой системы NTFS
  • CreationTime, LastAccessTime, LastWriteTime Свойство типа DateTime, определяющее временные характеристики файловой единицы (время создания, время последнего доступа и время последнего обновления)
    CreationTimeUtc, LastAccessTimeUtc, LastWriteTimeUtc То же, что предыдущее, только по Гринвичу
    Exists Абстрактное свойство только для чтения, значение которого true, если для созданного объекта-наследника данных файл или каталог существует
    FullName Свойство get типа String, содержащее полное квалифицированное имя файла или каталога
    Name Абстрактное свойство-заготовка только для чтения, предназначенная для хранения имени файла без пути, но с расширением, или имени каталога
    Extension Свойство get типа String, содержит только расширение файла
    Delete() Абстрактная заготовка для удаления файла или каталога в производных классах
    Refresh() Синхронизирует созданный ранее объект данных каталога или файла с последними изменениями файловой системы, выполненными через другие инструменты

    Экземплярные классы DirectoryInfo и FileInfo не только реализуют унаследованные члены, но и дополняют (расширяют) базовый класс FileSystemInfo своими уникальными членами. Некоторые из них приведены в таблицах ниже.

    Некоторые уникальные члены класса DirectoryInfo
    Член Описание
    Create() Создает каталог, указанный ранее в конструкторе при создании экземпляра DirectoryInfo
    CreateSubdirectory() Создает подкаталог
    Delete() Удаляет пустой каталог
    Delete(bool recursive) Удаляет каталог вместе с деревом подкаталогов, если аргумент равен true
    GetDirectories() Возвращает список объектов данных подкаталогов типа DirectoryInfo для текущего каталога. Имеет перегрузки, которые допускают использование шаблонов поиска, удовлетворяющих требуемому условию
    GetFiles() Возвращает список объектов данных типа FileInfo для файлов текущего каталога. Метод имеет перегрузки, позволяющие использовать стандартные символы шаблонов поиска '*' и '?'. Шаблон '?' представляет любой единственный символ, а шаблон '*' - любую последовательность символов из нуля и более, например, *.txt ищет все текстовые файлы
    Parent Свойство возвращает объект данных DirectoryInfo родительского каталога для указанного подкаталога
    Root Свойство возвращает объект данных DirectoryInfo, представляющий корневой каталог

    Некоторые уникальные члены класса FileInfo
    Член Описание
    Directory Возвращает экземпляр класса DirectoryInfo, представляющий каталог размещения файла
    DirectoryName Возвращает строку с полным именем родительского каталога
    Exists Возвращает флаг, определяющий, существует ли указанный файл
    IsReadOnly Булево свойство определяет или устанавливает атрибут файла "только для чтения"
    Length Длинное целое, возвращающее размер файла в байтах
    Name Переопределенное свойство содержит имя файла
    CopyTo() Копирует связанный с объектом данных файл в новое место с указанным именем и возвращает новый объект данных FileInfo, представляющий новую копию. Перегрузка с флагом разрешает перезапись, если целевой файл уже существует
    Create() Создает файл и возвращает объект-оболочку потока FileStream, который можно использовать для записи
    CreateText() Создает файл и возвращает объект-оболочку потока StreamWriter, который можно использовать для записи
    AppendText() Создает и возвращает поток StreamWriter для добавления текста в файл, связанный с объектом FileInfo
    Open(System.IO.FileMode) Возвращает объект FileStream и открывает файл в заданном режиме FileMode:
  • Append - добавлять в конец файла
  • Create - если файл несуществует, то создать, иначе очистить существующий
  • CreateNew - создает и открывает новый файл. Если файл уже существует, выдает исключение
  • Open - требует открыть файл в режиме, установленном перечислением FileAccess. Если файл не существует, выдается исключение FileNotFoundException
  • OpenOrCreate - существующий файл открыть, иначе создать файл
  • Truncate - открыть файл с очисткой до нулевой длины
  • Open(System.IO.FileMode, System.IO.FileAccess) Возвращает объект FileStream. Представляет вариации открытия файла с вариациями доступа, определяемыми перечислением FileAccess:
  • Read - только для чтения
  • ReadWrite - для чтения и записи
  • Write - только для записи
  • Open(System.IO.FileMode, System.IO.FileAccess, System.IO.FileShare) Возвращает объект FileStream. Представляет вариации открытия файла с вариациями доступа, включая возможность одновременного доступа других объектов-потоков FileStream к данному файлу. Права других потоков при совместном доступе определяются перечислением FileShare:
  • Delete - разрешено удаление файла
  • None - не разрешать совместное использование файла
  • Read - разрешить сторонним потокам только чтение
  • ReadWrite - разрешить совместные чтение и запись
  • Write - разрешить совместную запись
  • OpenRead() Создает поток FileStream только для чтения и возвращает его
    OpenWrite() Создает поток FileStream только для записи и возвращает его
    OpenText() Создает и возвращает поток StreamReader для чтения в кодировке UTF8 из существующего текстового файла
    Encrypt() Шифрует файл
    Decrypt() Дешифрует файл
    Delete() Удаляет файл физически
    Replace() Заменяет содержимое указанного файла содержимым файла, связанного с текущим FileInfo. Целевой файл удаляется, а вместо него создается резервная копия с указанным именем, но параметры целевого файла возвращаются методом в виде объекта FileInfo

    Экземплярные классы DirectoryInfo и FileInfo имеют только параметризованные конструкторы, ожидающие строку каталога или файла с полным путем. Путь не обязательно должен соответствовать реально существующему файлу или каталогу. Если указанный путь не существует, то свойство Exists объекта будет иметь значение false.

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

    Если указанный файл или каталог не существует, то их можно создать. Ниже приведен пример такой функции.

    static void CreateFile(string fileName) // Нужно полное имя файла
            {
                string file = Path.GetFileName(fileName);       // Только имя файла
                string dir = Path.GetDirectoryName(fileName);   // Только полный путь
                string path = Path.Combine(dir, file);          // Опять fileName
                /*
                Console.WriteLine("fileName = {0}", fileName);
                Console.WriteLine("file = {0}", file);
                Console.WriteLine("dir = {0}", dir);
                Console.WriteLine("path = {0}", path);
                */
                DirectoryInfo myDir = new DirectoryInfo(dir);
                FileInfo myFile = new FileInfo(fileName);
                if (!myDir.Exists)      // Если каталог не существует
                {
                    myDir.Create();     // Создать каталог    
                    myFile.Create();    // Создать файл
                }
                else if (!myFile.Exists)// Если файл не существует
                    myFile.Create();    // Создать файл
            }

    Вызов функции CreateFile() может быть таким

    Program.CreateFile(@"C:\Tmp111\TTT\fileName.txt");

    Методы GetFiles() и GetDirectories() классов Directory или DirectoryInfo с заданными шаблонами поиска или без них осуществляют поиск информации только для текущего каталога. Чтобы пройтись по всем вложенным подкаталогам, нужно применить рекурсивную логику поиска. В следующем примере используется рекурсивный вызов функции для подсчета размера файлов, находящихся в указанном каталоге и во всех подчиненных ему подкаталогах файлового дерева.

    static long GetDirectorySize(DirectoryInfo directory, bool includeSubdirectories)
            {
                Console.WriteLine(directory.FullName);
                long totalSize = 0;
        
                // Просуммировать размеры файлов в текущем каталоге
                FileInfo[] files = directory.GetFiles();
                foreach (FileInfo file in files)
                    totalSize += file.Length;
        
                // Рекурсивно обходим подкаталоги
                if (includeSubdirectories)
                {
                    DirectoryInfo[] dirs = directory.GetDirectories();
                    foreach (DirectoryInfo dir in dirs)
                        totalSize += GetDirectorySize(dir, true);
                }
        
                return totalSize;
            }

    Вызов функции GetDirectorySize() может быть таким

    String path = @"E:\Tmp";
                long totalSize = Program.GetDirectorySize(new DirectoryInfo(path), true);
                Console.WriteLine(totalSize);

    Упражнение 2. Использование атрибутов

    Свойство Attributes, наследуемое экземплярными классами DirectoryInfo и FileInfo от класса FileSystemInfo, устанавливает особенность поведения операционной системы по отношению к элементам файловой системы. Оно обеспечивает доступ к двухбайтному базовому слову, каждый бит которого соответствует определенной поведенческой установке, закрепленной за файлом или каталогом.

    Метод ToString() свойства Attributes возвращает строку с именами атрибутов элемента, разделенными запятыми. Следующий код распечатывает названия атрибутов всех файлов заданного каталога

    static void Main(string[] args)
            {
                String currentDirectory = @"E:\Tmp\XX";//Directory.GetCurrentDirectory();  // Каталог сборки
                //Console.WriteLine(currentDirectory);
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files = directory.GetFiles(); // Список файлов
                foreach (FileInfo file in files)
                {
                    Console.WriteLine("\nАтрибуты файла {0}", file.Name);
                    String[] attributes = file.Attributes.ToString().Split(',');
                    for (int i = 0; i < attributes.Length; i++)
                        Console.WriteLine(attributes[i].Trim());
                }
        
                Console.ReadLine();
            }

    Для работы с атрибутами используются побитовые операции. Элементы перечисления FileAttributes представляют собой слово (2 байта), в котором только один бит равен единице. Их можно считать побитовыми масками, с помощью которых можно установить или прочитать атрибут. Некоторые атрибуты не могут быть установлены программно. Например, атрибуты FileAttributes. Encrypted и FileAttributes.Compressed устанавливаются самой операционной системой через специальные механизмы.

    Прежде, чем двигаться дальше, вспомним поразрадные операции C#. Они позволяют работать с данными на уровне бит. Все поразрядные операции используются только с целыми типами данных ( byte, sbyte, int, uint, short, ushort, long, ulong, char, decimal ), у которых в машинном представлении нет плавающей точки.

    Существует 6 поразрядных операций, проиллюстрированных в следующих таблицах

    Операция Наименование Описание
    поразрядное И Бит результата устанавливается в 1, если соответствующие биты обеих операндов равны 1
    | поразрядное ИЛИ Бит результата устанавливается в 1, если соответствующий бит хотя бы одного операнда равен 1
    ^ поразрядное ИСКЛЮЧАЮЩЕЕ ИЛИ Бит результата устанавливается в 1, если соответствующиий бит одного и только одного операнда равен 1
    ~ поразрядное НЕ Унарная операция, которая применяется только к одному операнду. Меняет каждый бит с 1 на 0 и наоборот
    << поразрядный сдвиг влево Сдвигает биты первого операнда влево на количество позиций, заданных вторым операндом. При этом освобождающиеся справа биты заполняются нулями
    >> поразрядный сдвиг вправо Сдвигает биты первого операнда вправо на количество позиций, заданных вторым операндом. При этом освобождающиеся слева биты заполняются нулями

    Шестнадцатиричное представление байта
    binary 0x
    0000 0000 0x00
    0000 0001 0x01
    0000 0010 0x02
    0000 0011 0x03
    0000 0100 0x04
    0000 0101 0x05
    0000 0110 0x06
    0000 0111 0x07
    0000 1000 0x08
    0000 1001 0x09
    0000 1010 0x0A
    0000 1011 0x0B
    0000 1100 0x0C
    0000 1101 0x0D
    0000 1110 0x0E
    0000 1111 0x0F

    Пример поразрядных операций
    a mask a | mask a ^ mask ~mask
    a 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0
    mask 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 0
    result 1 0 0 0 0 0 1 0 1 1 1 1 1 0 1 0 0 1 1 1 1 0 0 0 0 0 1 1 0 1 0 1
    result ^ mask 1 0 1 1 0 0 1 0

    Пример операций сдвига
    a << 5 a >> 5
    a 1 1 0 1 0 0 0 1 1 1 0 0 1 1 1 1 1 1 0 1 0 0 0 1 1 1 0 0 1 1 1 1
    result 0 0 1 1 1 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 1 0

    Операции поразрядного присваивания
    = Операция присваивания поразрядного И
    | = Операция присваивания поразрядного ИЛИ
    ^ = Операция присваивания поразрядного исключающего ИЛИ
    << = Операция присваивания сдвига влево
    >> = Операция присваивания сдвига вправо

    Следующее упражнение иллюстрирует побитовую арифметику с атрибутами.

  • Добавьте к решению FilesAndDirectory проект консольного приложения App2 и назначьте его стартовым в Solution Explorer командой контекстного меню Set as StartUp Project
  • Заполните файл Program.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
        
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                String currentDirectory = @"E:\Tmp\XX";//Directory.GetCurrentDirectory();  // Каталог сборки
                //Console.WriteLine(currentDirectory);
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files1 = directory.GetFiles("*.txt"); // Список текстовых файлов
        
                // Создаем маску с атрибутами
                FileAttributes mask = FileAttributes.Archive
                    | FileAttributes.ReadOnly
                    | FileAttributes.System
                    | FileAttributes.Offline
                    | FileAttributes.NotContentIndexed
                    | FileAttributes.Hidden
                    | FileAttributes.Temporary
                    | FileAttributes.Device;
        
                // Устанавливаем для всех файлов указанные атрибуты 
                foreach (FileInfo file in files1)
                {
                    // Добавляем атрибуты
                    file.Attributes |= mask;
                }
        
                // Создаем новый объект для чистоты эксперимента и печатаем атрибуты
                FileInfo[] files2 = directory.GetFiles("*.txt"); // Список текстовых файлов
                foreach (FileInfo file in files2)
                {
                    Console.WriteLine("\nАтрибуты файла {0}", file.Name);
                    String[] attributes = file.Attributes.ToString().Split(',');
                    for (int i = 0; i < attributes.Length; i++)
                        Console.WriteLine(attributes[i].Trim());
                }
        
                // Определяем, имеет ли первый файл коллекции files2 атрибут ReadOnly 
                if ((files2[0].Attributes  FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                    Console.WriteLine(Environment.NewLine   // Новая строка
                        + "Файл {0} имеет атрибут ReadOnly", files2[0].Name);
                else
                    Console.WriteLine("\n"                  // Новая строка
                        + "Файл {0} не имеет атрибут ReadOnly", files2[0].Name);
        
                // Исключаем из первого объекта добавленные ранее атрибуты
                foreach (FileInfo file in files1)
                {
                    // Исключаем атрибуты
                    file.Attributes = ~mask; // Побитовое умножение на инвертированную маску
                }
        
                // Печатаем результат через второй объект
                // Если открыть комментарные скобки - результат будет неверным!!!
                //*
                for (int i = 0; i < files2.Length; i++)
                    files2[i].Refresh();    // Приводим в соответствие с состоянием файлов
                //*/
                foreach (FileInfo file in files2)
                {
                    Console.WriteLine("\nАтрибуты файла {0}", file.Name);
                    String[] attributes = file.Attributes.ToString().Split(',');
                    for (int i = 0; i < attributes.Length; i++)
                        Console.WriteLine(attributes[i].Trim());
                }
        
                // Определяем, имеет ли первый файл коллекции files2 атрибут ReadOnly 
                if ((files2[0].Attributes  FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                    Console.WriteLine(Environment.NewLine   // Новая строка
                        + "Файл {0} имеет атрибут ReadOnly", files2[0].Name);
                else
                    Console.WriteLine("\n"                  // Новая строка
                        + "Файл {0} не имеет атрибут ReadOnly", files2[0].Name);
        
                Console.ReadLine();
            }
        }
    }
  • Запустите приложение - вывод программы для нескольких файлов будет примерно следующим
  • Атрибуты файла Test.txt
    ReadOnly
    Hidden
    System
    Archive
    Temporary
    Offline
    NotContentIndexed
    	
    Атрибуты файла xx.txt
    ReadOnly
    Hidden
    System
    Archive
    Temporary
    Offline
    NotContentIndexed
    	
    Файл Test.txt имеет атрибут ReadOnly
    	
    Атрибуты файла Test.txt
    Normal
    	
    Атрибуты файла xx.txt
    Normal
    	
    Файл Test.txt не имеет атрибут ReadOnly

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

    Класс DriveInfo

    Экземплярный класс, который извлекает информацию о дисковом устройстве компьютера. Имя устройства задается в конструкторе класса при создании экземпляра и представляет собой строку с символом от " a " до " z " в любом регистре. Ниже приведены члены класса DriveInfo.

    Члены класса DriveInfo
    Член Описание
    TotalFreeSpace Возвращает объем свободного пространства в байтах
    AvailableFreeSpace Возвращает объем свободного пространства в байтах, доступного для данного процесса
    DriveFormat Свойство возвращает имя файловой системы ( NTFS или FAT32 )
    DriveType Возвращает значение одноименного перечисления DriveType:
  • CDRom - устройство оптического диска, такое как CD или DVD-ROM
  • Fixed - фиксированный диск
  • Network - сетевой
  • NoRootDirectory - у диска нет корневого каталога
  • Ram - устройство RAM
  • Removable - сменное устройство хранения данных, такое как флэшка
  • Unknown - тип диска неопределен
  • IsReady Определяет готовность устройства к чтению или записи. Например, если нет компакт-диска в CD -приводе, свойство IsReady вернет false. Фиксированные устройства всегда готовы
    Name Возвращает строку с именем диска, например, C: или E:
    RootDirectory Возвращает объект DirectoryInfo с информацией о корневом каталоге диска
    TotalSize Получает полный размер пространства памяти на диске
    VolumeLabel Свойство, извлекающее или устанавливающее метку тома
    GetDrives() Статический метод, который возвращает массив объектов DriveInfo, представляющих все логические устройства данного компьютера

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

    Упражнение 3. Мониторинг дисковых устройств компьютера

    Проиллюстрируем использование класса DriveInfo.

  • Добавьте к решению консольное приложение App3 и назначьте его стартовым
  • Заполните файл Program.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
    using DEBUG = System.Diagnostics.Debug;// Псевдоним класса
        
    namespace App3
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Получаем список всех логических устройств компьютера
                DriveInfo[] drives = DriveInfo.GetDrives();
        
                // Перебираем список и печатаем свойства
                bool rootFlag = false; // Чтобы напечатать только раз
                foreach (DriveInfo drive in drives)
                {
                    String message = String.Format("Устройство '{0}'", drive.Name);
                    // Если готово, извлекаем характеристики
                    if (drive.IsReady)
                    {
                        message += " ГОТОВО" + Environment.NewLine;
                        if (!rootFlag)
                        {
                            message += String.Format("RootDirectory: {0}\n", drive.RootDirectory);
                            rootFlag = !rootFlag;// Отключаем
                        }
                        message += String.Format("VolumeLabel: {0}\n", drive.VolumeLabel);
                        message += String.Format("DriveType: {0}\n", drive.DriveType.ToString());
                        message += String.Format("DriveFormat: {0}\n", drive.DriveFormat);
                        message += String.Format("TotalSize: {0}\n", drive.TotalSize);
                        message += String.Format("AvailableFreeSpace: {0}\n", drive.AvailableFreeSpace);
                        message += String.Format("TotalFreeSpace: {0}\n", drive.TotalFreeSpace);
                    }
                    else
                        message += " НЕГОТОВО\n";
        
                    DEBUG.WriteLine(message);
                }
        
                Console.ReadLine();
            }
        }
    }

    Обратите внимание, что псевдонимы можно определять не только для пространств имен, но и для классов. Свойство RootDirectory экземпляра DriveInfo для любого устройства возвращает одно и то же значение, поэтому мы используем его только раз (чтобы не мешало). Если мы хотим исследовать только одно устройство и знаем его имя, то можно создать объект DriveInfo с помощью параметризованного конструктора, указав в качестве аргумента строку с именем, например, так

    DriveInfo drive = new DriveInfo("C");

    Имя устройства задается только символом от a до z в любом регистре и без дополнительных символов слэша и двоеточия. А далее все равно нужно проверить его готовность IsReady, прежде чем обращаться к другим свойствам.

  • Удостовертесь, что включен режим Debug оболочки (выпадающий список над рабочей областью)
  • Запустите проект и откройте панель Output. На моем компьютере получился такой результат
  • Устройство 'C:\' ГОТОВО
    RootDirectory: C:\
    VolumeLabel: 18125
    DriveType: Fixed
    DriveFormat: NTFS
    TotalSize: 31457275904
    AvailableFreeSpace: 473616384
    TotalFreeSpace: 473616384
    	
    Устройство 'D:\' ГОТОВО
    VolumeLabel: 
    DriveType: Removable
    DriveFormat: FAT32
    TotalSize: 261105664
    AvailableFreeSpace: 244381696
    TotalFreeSpace: 244381696
    	
    Устройство 'E:\' ГОТОВО
    VolumeLabel: Data
    DriveType: Fixed
    DriveFormat: NTFS
    TotalSize: 462356992000
    AvailableFreeSpace: 425508450304
    TotalFreeSpace: 425508450304
    	
    Устройство 'F:\' ГОТОВО
    VolumeLabel: ГАИ - 24.01.2010
    DriveType: CDRom
    DriveFormat: CDFS
    TotalSize: 161966080
    AvailableFreeSpace: 0
    TotalFreeSpace: 0
    	
    Устройство 'G:\' НЕГОТОВО
    	
    Устройство 'H:\' НЕГОТОВО
    	
    Устройство 'I:\' НЕГОТОВО
    	
    Устройство 'J:\' НЕГОТОВО
    	
    Устройство 'K:\' ГОТОВО
    VolumeLabel: 
    DriveType: Removable
    DriveFormat: FAT32
    TotalSize: 31994216448
    AvailableFreeSpace: 7246184448
    TotalFreeSpace: 7246184448

    Вывод информации мы организовали в панель Output оболочки, поэтому консольное окно будет пустым.

  • Включите режим Release оболочки и повторите запуск проекта
  • Мы видим, что отладочный код работает только в режиме Debug и автоматически исключается компилятором в режиме Release из готовой исполнимой сборки приложения. Это очень удобно при тестировании проектов.

    Упражнение 4. Получение метаданных из сборок

    Иногда может возникнуть необходимость получить дополнительную информацию о файле, включающую в себя авторские права, номер версии, наименование компании-производителя, информацию о торговой марке и т.д. Эта информация вшивается в сборку при компиляции EXE - или DLL -файлов (если укажем). Данные отображаются в диалоговом окне свойств, вызванном в Windows Explorer при выделении исполнимого файла правой кнопкой мыши и выбора опции Свойства, например

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

  • Добавьте к решению командой File/Add/New Project новый проект консольного приложения с именем App4 и назначьте его стартовым
  • В панели Solution Explorer откройте на редактирование файл AssemblyInfo.cs и ознакомьтесь с его содержимым, автоматически сгенерированным оболочкой
  • using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
        
    // General Information about an assembly is controlled through the following 
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyTitle("App4")]
    [assembly: AssemblyDescription("")]
    [assembly: AssemblyConfiguration("")]
    [assembly: AssemblyCompany("")]
    [assembly: AssemblyProduct("App4")]
    [assembly: AssemblyCopyright("Copyright ©  2010")]
    [assembly: AssemblyTrademark("")]
    [assembly: AssemblyCulture("")]
        
    // Setting ComVisible to false makes the types in this assembly not visible 
    // to COM components.  If you need to access a type in this assembly from 
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible(false)]
        
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid("749a9e12-8b91-41d5-942a-e174fe60800f")]
        
    // Version information for an assembly consists of the following four values:
    //
    //      Major Version
    //      Minor Version 
    //      Build Number
    //      Revision
    //
    // You can specify all the values or you can default the Build and Revision Numbers 
    // by using the '*' as shown below:
    // [assembly: AssemblyVersion("1.0.*")]
    [assembly: AssemblyVersion("1.0.0.0")]
    [assembly: AssemblyFileVersion("1.0.0.0")]

    Мы видим набор атрибутов в глобальном пространстве имен, определяющих для сборки дополнительную информацию, которую называют метаданными (данные о данных). Ее можно редактировать в файле AssemblyInfo.cs напрямую, но лучше это делать через страницу свойств проекта.

  • Выполните команду Project/App4 Properties меню оболочки и выделите вкладку Application страницы свойств проекта
  • Щелкните на кнопке Assembly Information, чтобы вызвать диалоговое окно редактирования метаданных сборки с текстовыми полями:
  • Title - название
  • Description - описание
  • Company - производитель
  • Product - наименование продукта
  • Copyright - авторское право
  • Trademark - торговая марка
  • Assembly Version - версия сборки
  • File Version - версия файла
  • Neutral Language - язык по умолчанию
  • Заполните диалоговое окно свойств как показано на рисунке
  • Вновь откройте файл AssemblyInfo.cs и убедитесь, что его содержимое изменилось
  • Извлечь информацию из сборки можно программно. Для этого предназначен класс System.Diagnostics. FileVersionInfo. Продемонстрируем его использование на примере.

  • Заполните файл Program.cs приложения App4 следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
    using DEBUG = System.Diagnostics.Debug; // Псевдоним класса
    using SD = System.Diagnostics;          // Создаем alias пространства имен (необязательно!)
    using System.Diagnostics;               // Подключаем пространство имен напрямую
        
    namespace App4
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Локальные (стековые) переменные инициализировать обязательно
                String currentDirectory = Directory.GetCurrentDirectory();  // Каталог сборки
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files = directory.GetFiles("*.exe"); // Список файлов по шаблону
                // Создаем массив для информации обо всех файлах
                SD.FileVersionInfo[] info =
                    new System.Diagnostics.FileVersionInfo[files.Length];
        
                // Извлекаем из всех файлов дополнительную информацию
                for (int i = 0; i < files.Length; i++)
                {
                    info[i] = FileVersionInfo.GetVersionInfo(files[i].FullName);// Просто 'Name' мало!
                }
        
                // Накапливаем результат для печати
                String message = String.Empty;
                for (int i = 0; i < info.Length; i++)
                {
                    // Используем отражение для сокращения кода (и куража!)
                    System.Reflection.PropertyInfo[] props = // Общедоступные свойства
                        info[i].GetType().GetProperties();
                    for (int j = 0; j < props.Length; j++)
                    {
                        message += props[j].Name + ":\t";   // Имя свойства, ':' и отступ
                        // Выполняем свойство, чтобы извлечь его значение
                        Object prop = props[j].GetGetMethod().Invoke(info[i], null);
                        if (prop is String) // Динамическая идентификация типа (необязательно)
                            message += prop.ToString();
                        else if (prop != null)
                            message += String.Format("{0}", prop);
        
                        message += Environment.NewLine; // Новая строка
                    }
        
                    message += "\r\n";     // Новая строка
                }
        
                Console.Write(message + "\n");  // Печатаем на консоль (Write и новая строка)
                DEBUG.WriteLine(message);       // Печатаем в панель Output оболочки
        
                Console.ReadLine();         // Задержка консоли
            }
        }
    }

    В приведенном коде мы применили методику отражения типов ( Reflection - рефлексию), чтобы не перебирать все свойства явно. Кроме того, использовали разные способы новой строки, для разнообразия.

  • Запустите приложение - вывод информации для файлов каталога исполнимой сборки данного проекта будет таким
  • Comments:	Читает метаданные (Description)
    CompanyName:	Рога и копыта (Company)
    FileBuildPart:	0
    FileDescription:	Приложение App4 (Title)
    FileMajorPart:	1
    FileMinorPart:	0
    FileName:	E:\Tmp\FilesAndDirectory\App4\bin\Debug\App4.exe
    FilePrivatePart:	5678
    FileVersion:	1.0.0.5678
    InternalName:	App4.exe
    IsDebug:	False
    IsPatched:	False
    IsPrivateBuild:	False
    IsPreRelease:	False
    IsSpecialBuild:	False
    Language:	Независимо от языка
    LegalCopyright:	Copyright ©  2010 (Copyright)
    LegalTrademarks:	Ни-Бэ-Ни-Мэ (Trademark)
    OriginalFilename:	App4.exe
    PrivateBuild:	
    ProductBuildPart:	0
    ProductMajorPart:	1
    ProductMinorPart:	0
    ProductName:	Суперприложение (Product)
    ProductPrivatePart:	5678
    ProductVersion:	1.0.0.5678
    SpecialBuild:	
    	
    Comments:	vshost.exe
    CompanyName:	Microsoft Corporation
    FileBuildPart:	21022
    FileDescription:	vshost.exe
    FileMajorPart:	9
    FileMinorPart:	0
    FileName:	E:\Tmp\FilesAndDirectory\App4\bin\Debug\App4.vshost.exe
    FilePrivatePart:	8
    FileVersion:	9.0.21022.8
    InternalName:	vshost.exe
    IsDebug:	False
    IsPatched:	False
    IsPrivateBuild:	False
    IsPreRelease:	False
    IsSpecialBuild:	False
    Language:	Независимо от языка
    LegalCopyright:	© Microsoft Corporation. All rights reserved.
    LegalTrademarks:	
    OriginalFilename:	vshost.exe
    PrivateBuild:	
    ProductBuildPart:	21022
    ProductMajorPart:	9
    ProductMinorPart:	0
    ProductName:	Microsoft (R) Visual Studio (R) 2008
    ProductPrivatePart:	8
    ProductVersion:	9.0.21022.8
    SpecialBuild:

    Здесь, на первый взгляд, казалось бы, наблюдается парадокс - исполнимая сборка проверяет саму себя! Ну и что, порядок ее создания таков: компилятор транслирует код и записывает его в файл. Затем оболочка загружает файл в память и запускает процесс CLR, который анализирует указанный нами файл на диске в соответствии с заложенным алгоритмом. Указали бы другой исполнимый или библиотечный файл, процесс проверял бы его. Конечно, можно было бы сделать и диалог для выбора файла, но не в этом суть данного упражнения.

    В каталоге сборки bin оболочка создает еще один файл App4.vshost.exe, который используется для оптимизации отладки, поддержки политик безопасности, более детального контроля за работой приложения. Его создание для любого из режимов ( Debug и Release ) можно отключить в настройках оболочки.

  • Откройте окно свойств проекта командой Project/App4 Properties и на вкладке Debug сбросьте внизу флажок Enable the Visual Studio hosting process
  • В панели Solution Explorer щелкните на локальной пиктограмме Show All Files, разверните дерево каталогов проекта и удалите файл App4.vshost.exe
  • Запустите приложение - теперь метаданные выводятся только для файла самой сборки App4.exe, поскольку файл App4.vshost.exe уже не создается
  • В панели Solution Explorer командой Exclude From Project контекстного меню для файла AssemblyInfo.cs исключите его из текущего проекта и вновь запустите приложение
  • Мы видим, что метаданные в сборку на сей раз не попали.

  • Верните через панель Solution Explorer файл AssemblyInfo.cs обратно в проект командой контекстного меню Include In Project, переименуйте его в произвольное имя (расширение .cs должно остаться!) и вновь запустите приложение - метаданные есть, значит имя файла с атрибутами дополнительной информации для компилятора несущественно, он их находит из общего входного потока компиляции
  • Откройте файл AssemblyInfo.cs, командой Edit/Select All выделите все его содержимое и скопируйте в верхнюю часть файла Program.cs перед оператором ' namespace App4 '
  • Удалите в панели Solution Explorer файл AssemblyInfo.cs физически командой Delete контекстного меню
  • Запустите проект - метаданные существуют и их можно добалять непосредственно в коде в глобальном пространстве имен
  • Можно задаться вопросом, а куда же вшивается дополнительная информация в сборке? Давайте посмотрим, куда. Для этого проанализируем сборку дизассемблером ILDasm, который поставляется вместе с оболочкой и находится по адресу (например, на моем компьютере)

    C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe
  • Раскройте меню Tools оболочки и запустите ILDasm
  • Если такой команды не окажется, то ее следует добавить. Для этого:

  • Выполните команду меню Tools/External Tools (внешние инструменты) и заполните диалоговое окно External Tools как показано на снимке
  • Теперь осталось воспользоваться инструментом ILDasm и проанализировать нашу сборку App4.exe на предмет просмотра ранее введенных атрибутов.

  • Запустите дизассемблер ILDasm и командой File/Open откройте файл E:\Tmp\FilesAndDirectory\App4\bin\Debug\App4.exe (у меня такой...)
  • Выполните команду View/MetaInfo/Show! (или выполните жест Ctrl+M ) - в конце документа окна MetaInfo будет секция Assembly, в которой и находятся введенные нами ранее атрибуты сборки
  • Вот примерно так будет выглядеть секция Assembly (несколько укороченная) в документе окна MetaInfo

    Assembly
    -------------------------------------------------------
    	Token: 0x20000001
    	Name : App4
    	Public Key    :
    	Hash Algorithm : 0x00008004
    	Version: 1.0.0.1234
    	Major Version: 0x00000001
    	Minor Version: 0x00000000
    	Build Number: 0x00000000
    	Revision Number: 0x000004d2
    	Locale: <null>
    	Flags : [none] (00000000)
    	CustomAttribute #1 (0c000001)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000001
    		CustomAttributeName: System.Reflection.AssemblyTitleAttribute :: instance void .ctor(class System.String)
    		Length: 38
    		Value : 01 00 21 d0 9f d1 80 d0  b8 d0 bb d0 be d0 b6 d0 >  !             <
                          : b5 d0 bd d0 b8 d0 b5 20  41 70 70 34 20 28 54 69 >        App4 (Ti<
                          : 74 6c 65 29 00 00                                >tle)            <
    		ctor args: ("Приложение App4 (Title)")
    	
    	CustomAttribute #2 (0c000002)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000002
    		CustomAttributeName: System.Reflection.AssemblyDescriptionAttribute :: instance void .ctor(class System.String)
    		Length: 52
    		Value : 01 00 2f d0 a7 d0 b8 d1  82 d0 b0 d0 b5 d1 82 20 >  /             <
                          : d0 bc d0 b5 d1 82 d0 b0  d0 b4 d0 b0 d0 bd d0 bd >                <
                          : d1 8b d0 b5 20 28 44 65  73 63 72 69 70 74 69 6f >     (Descriptio<
                          : 6e 29 00 00                                      >n)              <
    		ctor args: ("Читает метаданные (Description)")
    	
    	CustomAttribute #4 (0c000004)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000004
    		CustomAttributeName: System.Reflection.AssemblyCompanyAttribute :: instance void .ctor(class System.String)
    		Length: 39
    		Value : 01 00 22 d0 a0 d0 be d0  b3 d0 b0 20 d0 b8 20 d0 >  "             <
                          : ba d0 be d0 bf d1 8b d1  82 d0 b0 20 28 43 6f 6d >            (Com<
                          : 70 61 6e 79 29 00 00                             >pany)           <
    		ctor args: ("Рога и копыта (Company)")
    	
    	CustomAttribute #5 (0c000005)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000005
    		CustomAttributeName: System.Reflection.AssemblyProductAttribute :: instance void .ctor(class System.String)
    		Length: 45
    		Value : 01 00 28 d0 a1 d1 83 d0  bf d0 b5 d1 80 d0 bf d1 >  (             <
                          : 80 d0 b8 d0 bb d0 be d0  b6 d0 b5 d0 bd d0 b8 d0 >                <
                          : b5 20 28 50 72 6f 64 75  63 74 29 00 00          >  (Product)     <
    		ctor args: ("Суперприложение (Product)")
    	
    	CustomAttribute #6 (0c000006)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000006
    		CustomAttributeName: System.Reflection.AssemblyCopyrightAttribute :: instance void .ctor(class System.String)
    		Length: 35
    		Value : 01 00 1e 43 6f 70 79 72  69 67 68 74 20 c2 a9 20 >   Copyright    <
                          : 20 32 30 31 30 20 28 43  6f 70 79 72 69 67 68 74 > 2010 (Copyright<
                          : 29 00 00                                         >)               <
    		ctor args: ("Copyright ©  2010 (Copyright)")
    	
    	CustomAttribute #7 (0c000007)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a000007
    		CustomAttributeName: System.Reflection.AssemblyTrademarkAttribute :: instance void .ctor(class System.String)
    		Length: 36
    		Value : 01 00 1f d0 9d d0 b8 2d  d0 91 d1 8d 2d d0 9d d0 >       -    -   <
                          : b8 2d d0 9c d1 8d 20 28  54 72 61 64 65 6d 61 72 > -     (Trademar<
                          : 6b 29 00 00                                      >k)              <
    		ctor args: ("Ни-Бэ-Ни-Мэ (Trademark)")
    	
    	CustomAttribute #10 (0c00000a)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a00000c
    		CustomAttributeName: System.Reflection.AssemblyFileVersionAttribute :: instance void .ctor(class System.String)
    		Length: 15
    		Value : 01 00 0a 31 2e 30 2e 30  2e 35 36 37 38 00 00    >   1.0.0.5678   <
    		ctor args: ("1.0.0.5678")
    	
    	CustomAttribute #11 (0c00000b)
    	-------------------------------------------------------
    		CustomAttribute Type: 0a00000d
    		CustomAttributeName: System.Resources.NeutralResourcesLanguageAttribute :: instance void .ctor(class System.String)
    		Length: 10
    		Value : 01 00 05 72 75 2d 52 55  00 00                   >   ru-RU        <
    		ctor args: ("ru-RU")

    Более подробная информация о сборках приведена, например, в http://www.intuit.ru/department/internet/prdevwebms/19/

    Упражнение 5. Работа с классом Path

    Статический класс System.IO. Path содержит статические методы и поля, которые выполняют общие задачи манипуляции с путями. Путем называется строка, которая предшествует укороченному имени файла или каталога перед конечным обратным слэшем ' \ ' (разделителем составного имени).

    Статические методы и поля класса Path
    Метод Описание
    GetDirectoryName() Возвращает имя каталога по полному имени файла
    GetFullPath() Возвращает полное имя файла по укороченному имени
    GetFileName() Возвращает укороченное имя файла по полному имени
    GetFileNameWithoutExtension() Возвращает укороченное имя файла без расширения
    GetExtension() Возвращает расширение файла с разделителем '.'
    Combine() Объединяет путь и имя файла, вставляя между ними разделитеь '\'
    GetPathRoot() Возвращает имя устройства, например, 'C:\'
    GetRandomFileName() Генерирует имя файла случайным образом базового формата DOS
    GetTempFileName() Генерирует заготовку полного имени временного файла для размещения в стандартном каталоге, который (каталог), возможно, еще предстоит создать
    GetTempPath() Генерирует заготовку полного имени каталога для размещения временных файлов, который, возможно, еще предстоит создать
    AltDirectorySeparatorChar, DirectorySeparatorChar, PathSeparator, VolumeSeparatorChar

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

    '/' '\' ';' ':'

  • Добавьте к решению проект ( Console Application ) с именем App5 и назначьте его стартовым. Для этого выполните через меню оболочки вначале команду File/Add/New Project, а затем сразу команду Project/Set as StartUp Project (пока созданный проект оболочка считает текущим)
  • Заполните файл Program.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.Text;
        
    using System.IO;
    using DEBUG = System.Diagnostics.Debug; // Псевдоним класса
        
    namespace App5
    {
        class Program
        {
            static void Main(string[] args)
            {
                String currentDirectory = Directory.GetCurrentDirectory();  // Каталог сборки
                String message = "Без использования Path\n".ToUpper();
                message += "currentDirectory: " + currentDirectory + "\n";
                DirectoryInfo directory = new DirectoryInfo(currentDirectory);
                FileInfo[] files = directory.GetFiles("*.exe"); // Список файлов по шаблону
                FileInfo file;
                if (files.Length > 0)
                    file = files[0];    // Берем первый попавшийся для анализа
                else
                    return;             // Нечего анализировать
        
                string fullName = file.FullName;
                string name = file.Name;
                message += "fullName: " + fullName + "\n";
                message += string.Format("name: {0}\n", name);
        
                message += string.Format("\nС использованием Path\n").ToUpper();
                String path;
                message += "path = Path.GetDirectoryName(fullName): "
                    + (path = Path.GetDirectoryName(fullName)) + "\n";
                message += "Path.GetFullPath(name): "
                    + Path.GetFullPath(name) + "\n";
                message += "Path.GetFileName(fullName): "
                    + Path.GetFileName(fullName) + "\n";
                message += "Path.GetFileNameWithoutExtension(fullName): "
                    + Path.GetFileNameWithoutExtension(fullName) + "\n";
                message += "Path.GetExtension(fullName): "
                    + Path.GetExtension(fullName) + "\n";
                message += "Path.Combine(path, name): "
                    + Path.Combine(path, name) + "\n";
                message += "Path.GetPathRoot(path): "
                    + Path.GetPathRoot(path) + "\n";
                message += "Path.GetPathRoot(fullName): "
                    + Path.GetPathRoot(fullName) + "\n";
                message += "Path.GetRandomFileName(): "
                    + Path.GetRandomFileName() + "\n";
                message += "Path.GetRandomFileName(): "
                    + Path.GetRandomFileName() + "\n";
                message += "Path.GetRandomFileName(): "
                    + Path.GetRandomFileName() + "\n";
                message += "Path.GetTempFileName(): "
                    + Path.GetTempFileName() + "\n";
                message += "Path.GetTempFileName(): "
                    + Path.GetTempFileName() + "\n";
                message += "Path.GetTempPath(): "
                    + Path.GetTempPath() + "\n";
                message += "AltDirectorySeparatorChar: '"
                    + Path.AltDirectorySeparatorChar + "'\n";
                message += "DirectorySeparatorChar: '"
                    + Path.DirectorySeparatorChar + "'\n";
                message += "PathSeparator: '"
                    + Path.PathSeparator + "'\n";
                message += "VolumeSeparatorChar: '"
                    + Path.VolumeSeparatorChar + "'\n";
        
                String unsafeFileName = "..\\..\\..\\" + name;    // Опасное имя файла
                String safeFileName = Path.GetFileName(unsafeFileName);
                message += "\nОпасное имя файла: " + unsafeFileName + "\n";
                message += "Безопасное имя файла: " + safeFileName + "\n";
                message += "Полное опасное имя: " + Path.Combine(path, unsafeFileName) + Environment.NewLine;
                message += "Полное опасное имя: " + Path.GetFullPath(Path.Combine(path, unsafeFileName)) + "\n";
                message += "Полное безопасное имя: " + Path.Combine(path, safeFileName);
        
                // Печать результатов
                Console.WriteLine(message); // На консоль
                DEBUG.WriteLine(message);   // На панель Output
        
                Console.ReadLine(); // Задержка окна
            }
        }
    }
  • Запустите проект - результат должен быть примерно таким
  • БЕЗ ИСПОЛЬЗОВАНИЯ PATH
    currentDirectory: E:\Tmp\FilesAndDirectory\App5\bin\Debug
    fullName: E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe
    name: App5.exe
    	
    С ИСПОЛЬЗОВАНИЕМ PATH
    path = Path.GetDirectoryName(fullName): E:\Tmp\FilesAndDirectory\App5\bin\Debug
    Path.GetFullPath(name): E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe
    Path.GetFileName(fullName): App5.exe
    Path.GetFileNameWithoutExtension(fullName): App5
    Path.GetExtension(fullName): .exe
    Path.Combine(path, name): E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe
    Path.GetPathRoot(path): E:\
    Path.GetPathRoot(fullName): E:\
    Path.GetRandomFileName(): epviufbp.0k1
    Path.GetRandomFileName(): tyzaz4fb.so2
    Path.GetRandomFileName(): ucnivb2v.idh
    Path.GetTempFileName(): C:\Users\CHETKOB\AppData\Local\Temp\tmp15B4.tmp
    Path.GetTempFileName(): C:\Users\CHETKOB\AppData\Local\Temp\tmp15B5.tmp
    Path.GetTempPath(): C:\Users\CHETKOB\AppData\Local\Temp\
    AltDirectorySeparatorChar: '/'
    DirectorySeparatorChar: '\'
    PathSeparator: ';'
    VolumeSeparatorChar: ':'
    	
    Опасное имя файла: ..\..\..\App5.exe
    Безопасное имя файла: App5.exe
    Полное опасное имя: E:\Tmp\FilesAndDirectory\App5\bin\Debug\..\..\..\App5.exe
    Полное опасное имя: E:\Tmp\FilesAndDirectory\App5.exe
    Полное безопасное имя: E:\Tmp\FilesAndDirectory\App5\bin\Debug\App5.exe

    Упражнение 6. Простой просмотрщик файлов

    Построим диалоговое приложение, позволяющее перемещаться по файловой системе и просматривать характеристики файлов. С оформлением пользовательского интерфейса сильно стараться не будем, а уделим больше внимания функциональности.

  • Добавьте к решению командой File/Add/New Project новый проект оконного приложения Windows Forms с именем App6 и назначьте его стартовым
  • В режиме Design выделите форму и установите для нее в панели Properties значения свойств
  • Text='Простой просмотрщик файлов', Size/Width=700, Size/Height=300
  • В режиме Design выделите форму и поместите из панели Toolbox дочерний компонент Panel (двойным щелчком на нем, или перетащите курсором). Установите для него в панели Properties значения свойств
  • Dock=Top, Size/Height=40
  • Опять выделите саму форму, щелкнув на ее заголовке (или свободном месте клиентской области), поместите еще один дочерний элемент Panel и установите для него свойство
  • Dock=Fill
  • Через выпадающий список панели Properties выделите объект panel1, поместите на него дочерний компонент Button и настройте свойства так:
  • (Name)=btnUp, Location/X=13, Location/Y=9, Text='Вверх'
  • Снова выделите на форме объект panel1 и добавьте на него текстовую метку Label со свойствами:
  • (Name)=lblCurrentDir, AutoSize=True, Location/X=103, Location/Y=12, Text='Текущий каталог: '
  • Выделите объект panel2, поместите на него дочерний компонент SplitContainer и сместите у него разделитель влево, чтобы ширина его левой панели ( Panel1 ) была меньше ширины правой ( Panel2 ) и составляла от нее примерно одну четверть
  • Выделите левую панель ( Panel1 ) объекта splitContainer1 и поместите в нее компонент ListBox со свойствами:
  • (Name)=listDir, Dock=Fill
  • Выделите правую панель ( Panel2 ) объекта splitContainer1 и поместите в нее компонент DataGridView со свойствами:
  • (Name)=gridFile, Dock=Fill, ColumnHeadersHeightSizeMode=AutoSize
  • На этом разработка пользовательского интерфейса закончена. Остальное, если возникнет необходимость, мы донастроим в процедурном коде, а пока форма будет выглядеть примерно так

  • Щелкните правой кнопкой мыши в любом месте на форме и выполните команду контекстного меню View Code, чтобы открыть кодовую часть - файл Form1.cs
  • Заполните файл Form1.cs следующим кодом
  • using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
        
    using System.IO;
        
    namespace App6
    {
        public partial class Form1 : Form
        {
            Panel fileDetails;// Поле, инициализируется неявно нулевым адресом: null
        
            public Form1()
            {
                InitializeComponent();
        
                // Программные донастройки
                gridFile.ReadOnly = true;       // Только для просмотра
                gridFile.SelectionMode =
                    DataGridViewSelectionMode.FullRowSelect;// Выделять всю строку
        
                // Начальная информация
                currentPath = Directory.GetCurrentDirectory();
                ShowDirectoryContents(currentPath);
                fileDetails = splitContainer1.Panel2;
                fileDetails.AutoScroll = true;
            }
        
            String currentPath;// Поле, инициализируется неявно пустой строкой
            void ShowDirectoryContents(String path)
            {
                // Получить объект справки текущего каталога
                DirectoryInfo curDir = new DirectoryInfo(path);
        
                // Получить для текущего каталога 
                // объекты справки файлов и подкаталогов
                FileInfo[] files = curDir.GetFiles();
                DirectoryInfo[] dirs = curDir.GetDirectories();
                lblCurrentDir.Text = "Текущий каталог: " + path;
        
                // Привязываем к элементам отображения
                listDir.DataSource = dirs;
                listDir.DisplayMember = "Name"; // Отображаемый вектор
                listDir.ValueMember = "Name";   // Скрытый вектор
                gridFile.DataSource = files;    // Показываем все
            }
        
            private void btnUp_Click(object sender, EventArgs e)
            {
                // Уже "Мой компьютер" и выше некуда
                if (currentPath == String.Empty)
                    return;
                // Хотим подняться выше текущего корня,
                // поэтому выводим доступные устройства
                else if (currentPath == Path.GetPathRoot(currentPath))
                {
                    System.Collections.ArrayList array =
                        new System.Collections.ArrayList();
                    DriveInfo[] drives = DriveInfo.GetDrives();
                    // Фильтруем готовые устройства
                    foreach (DriveInfo drive in drives)
                    {
                        if (drive.IsReady)
                        {
                            String name = drive.Name;
                            name = name.Split('\\')[0];// Убираем слэш
                            array.Add(name);
                        }
                    }
        
                    listDir.DataSource = array; // Привязываем список доступных устройств
                    gridFile.DataSource = null; // Нет файлов
                    currentPath = String.Empty; // Пути нет
                    lblCurrentDir.Text = "Мой компьютер";   // Меняем заголовок метки
        
                    return;
                }
                // Находимся ниже корня текущего устройства
                // и есть куда подниматься
                else
                {
                    String path = Path.Combine(currentPath, "..");
                    currentPath = Path.GetFullPath(path);   // Этажом выше без '..'
                    ShowDirectoryContents(currentPath);
                }
            }
        
            private void listDir_DoubleClick(object sender, EventArgs e)
            {
                String dir = listDir.SelectedValue.ToString();// Извлекаем из списка
                currentPath = Path.Combine(currentPath, dir);
                if (currentPath.IndexOf('\\') == -1)
                    currentPath += "\\";    // Если только что спустились в корень
                ShowDirectoryContents(currentPath);
            }
        
            private void gridFile_Click(object sender, EventArgs e)
            {
                if (gridFile.Rows.Count == 0)
                    return; // Нечего выделять
        
                // Локальные переменные инициализировать обязательно, а поля нет
                int selectIndex = gridFile.CurrentCell.RowIndex;
                DataGridViewRow row = gridFile.Rows[selectIndex];
                DataGridViewCellCollection cells = gridFile.Rows[selectIndex].Cells;
                String message = "";
        
                // Извлекаем информацию из текущей строки
                for (int i = 0; i < gridFile.Columns.Count; i++)
                {
                    String columnName = gridFile.Columns[i].Name;
                    String columnValue = row.Cells[i].Value.ToString();
                    message += columnName + ":  " + columnValue + "\n";
                }
        
                // Создаем окно и выводим информацию
                Form frm = new Form();
                frm.Width = 500;
                frm.Height = 320;
                frm.Text = "Подробности";   // Заголовок окна
                frm.MinimizeBox = false;    // Убираем системные кнопки
                frm.MaximizeBox = false;    // Убираем системные кнопки
                frm.ShowInTaskbar = false;  // Не отображать в панели задач
                frm.StartPosition = FormStartPosition.CenterParent;// По центру родителя
        
                // Промежуточная панель - для скролирования
                Panel panel = new Panel();
                panel.BackColor = System.Drawing.Color.White;
                panel.Dock = DockStyle.Fill;
                panel.AutoScroll = true;
                frm.Controls.Add(panel);// Отдаем окну
        
                // Отображающая текстовая метка
                Label lbl = new Label();
                lbl.AutoSize = true;
                lbl.Parent = panel;     // Отдаем панели
                // panel.Controls.Add(lbl);// Вариант - отдаем панели
                lbl.Text = message;
        
                // Заряжаем дополнительные окна вывода раньше диалога
                Console.Clear(); Console.Write(message); // !!!!!!!!!!!!!!!!!!!!
                System.Diagnostics.Debug.Write(message + Environment.NewLine);
        
                frm.ShowDialog();   // Показываем окно в модальном режиме
            }
        }
    }
  • Запустите приложение - пока оно не реагирует на действия пользователя, поскольку интерфейсные элементы не присоединены к своим обработчикам
  • В режиме Design выделите кнопку btnUp, зайдите в панель Properties, щелкните на пиктограмме Events, раскройте список поля для события Click и выберите обработчик btnUp_Click
  • Подобным же образом присоедините к событию DoubleClick объекта списка listDir обработчик listDir_DoubleClick, и к событию Click объекта сетки gridFile - обработчик gridFile_Click
  • Запустите приложение - при щелчке на строке сетки выбрасывается исключение
  • Причина в том, что в коде обработчика gridFile_Click делается попытка вывода информации в консольное окно, которое не создано. Если мы не хотим использовать консольное окно, то следует закомментировать или удалить строку

    Console.Clear(); Console.Write(message); // !!!!!!!!!!!!!!!!!!!!

    Но мы добавим к приложению консольное окно, чтобы вспомнить, как это делается.

  • В панели Solution Explorer для узла App6 вызовите контекстное меню и выполните команду Properies
  • Другой способ - в панели Solution Explorer выделить узел проекта App6 (или любой член проекта App6 ) и выполнить команду главного меню оболочки Project/App6 Properties.

  • На вкладке Application окна настроек проекта в выпадающем списке Output type установите значение Console Application
  • Запустите и испытайте разработанное приложение
  • Один из рабочих моментов может выглядеть так

    Еще один канал вывода подробностей, предусмотренный нами, - панель Output оболочки.

  • Разберитесь с кодом
  • Упражнение 7. Генератор случайных номеров

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

  • Добавьте к решению командой File/Add/New Project новый проект типа Windows Forms Application с именем App7 и назначьте его стартовым
  • Командой Project/App7 Properties вызовите диалоговое окно настройки проекта и в выпадающем списке Output type вкладки Application установите значение Console Application
  • Разработайте интерфейс диалогового окна в соответствии с таблицей свойств
  • Таблица свойств диалогового окна
    Элемент Свойство Значение Пояснения
    Form FormBorderStyle FixedDialog Фиксированный размер
    Size 375; 385 Ширина и высота окна
    MaximizeBox False Скрыть системную кнопку
    MinimizeBox False Скрыть системную кнопку
    Text Генератор случайных номеров Заголовок окна
    StartPosition CenterScreen Начальное положение
    Font Microsoft Sans Serif; 7,8pt Родительский шрифт (по умолчанию)
    Label AutoSize False Подстройка размера под содержимое
    BackColor Info Цвет фона
    Text Генерирует список номеров случайным образом Содержимое
    Font Arial; 12pt; style=Bold Шрифт
    ForeColor Blue Цвет текста
    TextAlign MiddleCenter Центрирование текста
    Location 16; 8 Позиция относительно клиентской области формы
    Label Text Куда разместить файл:
    Button (Name) btnDirTarget Имя экземпляра кнопки
    Text Browse Метка кнопки
    TextBox (Name) txtDirTarget Путь к файлу
    Label Text Имя текстового файла: Метка для txtFileName
    TextBox (Name) txtFileName Текстовое поле для имени файла
    Text ResultNumber.txt Имя файла по умолчанию
    Label Text Нижняя граница диапазона: Текстовая метка
    TextBox (Name) txtMin Текстовое поле для нижней границы генерируемых номеров
    Text 0 Значение по умолчанию
    Label Text Постоянный шаг: Текстовая метка
    TextBox (Name) txtStep Дискрета приращения диапазона
    Text 250 Шаг по умолчанию
    Label Text Количество точек: Текстовая метка
    TextBox (Name) txtCount Число генерируемых номеров
    Text 2000 Значение по умолчанию
    Label Text Верхняя граница диапазона: Метка
    TextBox (Name) txtMax Расчет верхней границы
    Text Пусто
    ReadOnly True Только для чтения
    Button (Name) btnExecute Кнопка выполнения
    Text Execute Метка кнопки

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

  • Запустите приложение - пользовательский интерфейс будет выглядеть так
  • Создадим и заполним обработчики элементов управления в процедурном коде файла Form1.cs.

  • Добавьте в начало файла Form1.cs подключение библиотечного пространства имен System. IO для классов ввода-вывода
  • using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
        
    // Подключение дополнительных пространств имен
    using System.IO;
  • В режиме Design двойным щелчком на заголовке формы (или ином незанятом компонентами месте) создайте обработчик начальной загрузки и заполните его так
  • int min, step, count, max;
            FolderBrowserDialog dialogFolder = new FolderBrowserDialog();
            private void Form1_Load(object sender, EventArgs e)
            {
                if (txtDirTarget.Text == String.Empty ||
                    Directory.Exists(txtDirTarget.Text) == false)
                {
                    dialogFolder.RootFolder =
                        System.Environment.SpecialFolder.MyComputer;
                    dialogFolder.SelectedPath = "";
                    txtDirTarget.Text = String.Empty;
                }
                else
                {
                    dialogFolder.SelectedPath = txtDirTarget.Text;
                }
        
                dialogFolder.Description = 
                    "Выберите место размещения текстового файла";
        
                min = Convert.ToInt32(txtMin.Text);
                step = Convert.ToInt32(txtStep.Text);
                count = Convert.ToInt32(txtCount.Text);
                max = min + step * count;
                txtMax.Text = max.ToString();
            }

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

  • В режиме Design выделите кнопку btnDirTarget, откройте панель Properties, щелкните на пиктограмме Events, найдите событие Click и двойным щелчком на правой ячейке сетки свойств создайте обработчик, который заполните так
  • private void btnDirTarget_Click(object sender, EventArgs e)
            {
                dialogFolder.ShowDialog();
                txtDirTarget.Text = dialogFolder.SelectedPath;
            }

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

    Здесь есть одна тонкость. Текстовое поле может редактироваться пользователем и, таким образом, может содержать недостоверный путь. Можно, конечно, создавать каталог программно, если он не существует, но для тренировки мы просто заблокируем это поле для редактирования. Если присвоить txtDirTarget свойство ReadOnly=True, то оно будет серым и некрасивым. Другая возможность - просто запретить ввод символов в это поле, что мы сейчас и сделаем.

  • Выделите текстовое поле txtDirTarget, в панели Properties установите режим Events и двойным щелчком на поле значений события KeyPress создайте обработчик со следующим кодом
  • private void txtDirTarget_KeyPress(object sender, KeyPressEventArgs e)
            {
                e.Handled = true;
            }
  • Запустите приложение, выберите папку и попробуйте редактировать поле - стрелки и клавиша Delete продолжают функционировать
  • Добъем это поле окончательно.

  • Выделите текстовое поле txtDirTarget, в панели Properties установите режим Events и двойным щелчком на поле значений события KeyDown создайте обработчик со следующим кодом
  • private void txtDirTarget_KeyDown(object sender, KeyEventArgs e)
            {
                e.Handled = true;
            }
  • Запустите приложение - теперь поле полностью нередактируется, хотя и принимает курсор и фокус ввода
  • Таким образом, мы видим, что с помощью событий KeyDown и KeyPress можно контролировать пользовательский ввод в экземпляр текстового поля. Применим этот факт для фильтрации цифровых клавиш в текстовых полях txtMin, txtStep и txtCount, которые должны принимать только целые числа. Создадим для них общие обработчики, поскольку условие ограничения по вводу только цифр для них общее. Но прежде нам нужно узнать, в какой последовательности срабатывают события KeyDown и KeyPress.

  • В режиме Design выделите (можно частично) с помощью лассо курсора (по русски - просто обведите, а используемое здесь словоблудие, чтобы привыкали к терминам) сразу 3 упомянутых текстовых поля и в панели Properties/Events задайте для них имена обработчиков, как показано на снимке
  • Заполните созданные обработчики кодом вывода информации на консоль
  • private void txt_KeyDown(object sender, KeyEventArgs e)
            {
                Console.WriteLine("KeyDown");
            }
        
            private void txt_KeyPress(object sender, KeyPressEventArgs e)
            {
                Console.WriteLine("KeyPress");
            }
  • Запустите приложение - получится последовательность
  • KeyDown
    KeyPress

    Зная эту последовательность срабатывания, можно запрограммировать фильтр для пропуска только цифр.

  • Заполните общие обработчики следующим кодом
  • bool isNumber;
            private void txt_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
            {
                isNumber =
                    e.KeyCode >= Keys.D0  e.KeyCode <= Keys.D9 // keyboard - основная клавиатура
                    || e.KeyCode >= Keys.NumPad0  e.KeyCode <= Keys.NumPad9 // keypad - дополнительная клавиатура
                    || e.KeyCode == Keys.Back;
            }
        
            private void txt_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
            {
                // Запрещаем в текстовом поле лишние символы
                if (!isNumber)
                    e.Handled = true;
            }

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

    Создадим еще один обработчик, в котором 'на лету' будет пересчитываться значение последнего текстового поля txtMax по данным других полей. Но прежде добавим маркеры исходных текстовых полей в код начальной инициализации, чтобы в общем обработчике можно было их идентифицировать (различать) и индивидуально обрабатывать.

  • Добавьте в функцию Form1_Load() следующий код
  • private void Form1_Load(object sender, EventArgs e)
            {
                ...................................................
        
                // Маркируем элементы для распознавания в общем обработчике
                txtMin.Name = "Min";
                txtStep.Name = "Step";
                txtCount.Name = "Count";
            }
  • В режиме Design выделите все три поля txtMin, txtStep и txtCount одновременно и создайте для них в панели Properties для события TextChanged общий обработчик с именем (нужно вручную ввести в поле) result_TextChanged, который заполните так
  • private void result_TextChanged(object sender, EventArgs e)
            {
                // Следим, чтобы редактируемое поле не оказалось пустым
                TextBox txtBox = (TextBox)sender;// Приводим ссылку к типу элемента
                if (txtBox.Text == String.Empty)
                {
                    switch (txtBox.Name)
                    {
                        case "Min":
                            txtMin.Text = "0";
                            break;
                        case "Step":
                            txtStep.Text = "0";
                            break;
                        case "Count":
                            txtCount.Text = "0";
                            break;
                    }
                }
        
                // Конвертируем разными способами
                min = Convert.ToInt32(txtMin.Text);
                step = Int32.Parse(txtStep.Text);
                int.TryParse(txtCount.Text, out count);
        
                max = min + step * count;
                txtMax.Text = max.ToString();
            }

    В этом коде скрыт один коварный нюанс, на который следует обратить внимание. Дело в том, что текстовое поле способно принимать сколь угодно длинные тексты. А в нашем коде обработчика result_TextChanged набор цифр преобразуется в целый тип Int32, имеющий конечную длину. Соответственно, длинный ввод пользователя может вызвать исключение. Не погружаясь глубоко в эту проблему, в которой нужно было бы еще учесть операции умножения предельно допустимых значений ввода (или изменить тип int на long ), просто ограничим количество вводимых цифр барьером, способным уместиться в целом типе. Это явно приближенный подход, но просто для примера, только для тренировки!

  • Модифицируйте обработчик txt_KeyPress( ) полей ввода цифр так
  • Было

    private void txt_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
            {
                // Запрещаем в текстовом поле лишние символы
                if (!isNumber)
                    e.Handled = true;
            }

    Стало

    private void txt_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
            {
                TextBox txtBox = sender as TextBox;// Приведение ссылки 
                int maxLength = int.MaxValue.ToString().Length - 1;// Длина ввода (с запасом)
        
                // Запрещаем в текстовом поле лишние символы 
                // и ограничиваем длину пользовательского ввода
                if (!isNumber || txtBox.Text.Length >= maxLength)
                    e.Handled = true;
            }

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

  • В режиме Design двойным щелчком на кнопке btnExecute создайте обработчик события по умолчанию, который заполните так
  • private void btnExecute_Click(object sender, System.EventArgs e)
            {
                String fileName = txtFileName.Text; // Копируем ссылку для удобства
        
                // Заполнено ли текстовое поле
                if (fileName.Length <= 0 || txtDirTarget.Text == String.Empty)
                {
                    MessageBox.Show("Вы не задали имя файла или каталога",
                        "Ошибка",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Stop);
                    return;
                }
        
                // Есть ли расширение txt
                int pos0 = fileName.LastIndexOf('.');// Ищем с конца
                if (pos0 == -1 || !String.Equals(fileName.Substring(pos0), ".txt"))
                {
                    MessageBox.Show("Вы не задали расширение .txt",
                        "Ошибка",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Stop);
                    return;
                }
        
                // Не допускать составное имя
                int pos1 = fileName.IndexOf('.');   // Ищем с начала
                if (pos0 != pos1)   // Если точек несколько
                {
                    MessageBox.Show("Задайте одно расширение .txt",
                        "Ошибка",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Stop);
                    return;
                }
        
                // Добавляем путь = полное имя файла
                fileName = Path.Combine(txtDirTarget.Text, fileName);
        
                if (!File.Exists(fileName))
                {
                    FileStream stream = File.Create(fileName);// Создать файл
                    stream.Close(); // Освободить ресурс
                }
        
                Console.Clear();    // Очистить консоль
                Random rand = new Random();// Генератор случайных чисел по равномерному закону
                int result;
                StreamWriter writeFile = new StreamWriter(fileName, false, 
                    System.Text.ASCIIEncoding.GetEncoding(1251));
                for (int i = 0; i < count; i++)
                {
                    result = min + i * step + rand.Next(step);
                    Console.WriteLine(result.ToString());   // Выводим на консоль
                    writeFile.WriteLine(result.ToString()); // Выводим в файл
                }
                writeFile.WriteLine("--------");    // Отделяем итог
                writeFile.WriteLine(count.ToString() + " шт.");
        
                writeFile.Close();   // Освободить ресурс
            }
  • Запустите приложение - все должно работать
  • В этом месте мне пришла мысль добавить еще одно текстовое поле для ввода серии (как в паспортах). Для этого нужно немного переделать пользовательский интерфейс формы. Заодно вспомним групповые операции выравнивания визуального конструктора форм.

  • В режиме Design не меняя размеров формы сдвиньте (стрелкой 'Вниз') кнопку Execute немного вниз
  • Охватите лассо курсора четыре нижних текстовых поля вместе с метками (до 'Имя текстового файла:') и тоже стрелкой 'Вниз' сдвиньте их вниз
  • Выделите текстовую метку 'Имя текстового файла:' вместе с ее полем, выполните Copy и сразу Paste
  • Выделите все пять текстовых полей и щелкните на кнопке Make Vertical Spacing Equal панели инструментов Layout, чтобы равномерно распределить элементы
  • То же самое действие примените к текстовым меткам
  • Настройте новые элементы в соответствии с стаблицей свойств
  • Элемент Свойство Значение
    Label Text Серия:
    TextBox ( Name ) txtSeries
    Text Пусто
  • Запустите приложение - интерфейс должен выглядеть примерно так
  • Теперь осталось включить значение серии в вывод программы.

  • Отредактируйте фрагмент кода обработчика btnExecute_Click( ) так
  • Было

    for (int i = 0; i < count; i++)
           {
           		result = min + i * step + rand.Next(step);
                   Console.WriteLine(result.ToString());   // Выводим на консоль
                   writeFile.WriteLine(result.ToString()); // Выводим в файл
    	}

    Стало

    for (int i = 0; i < count; i++)
           	{
    		result = min + i * step + rand.Next(step);
                   Console.WriteLine(txtSeries.Text.Trim() + result.ToString());   // Выводим на консоль
                   writeFile.WriteLine(txtSeries.Text.Trim() + result.ToString()); // Выводим в файл
    	}

    Сохранение состояния (параметров) приложения

    В завершение, на примере последнего упражнения, добавим средства сохранения состояния, чтобы при новом запуске прежние настройки текстовых полей и текущий каталог остались. Существует несколько (рассмотрим три) стандартных способов решения этой задачи и все они основаны на использовании абстрактного класса ApplicationSettingsBase:

  • Программный - вручную создать класс, производный от ApplicationSettingsBase, и привязать в коде нужный параметр к сохраняемому свойству формы или элемента управления
  • Воспользоваться страницей Settings конструктора проектов (вызывается командой Project/App7 Properties ). Страница автоматически генерируется оболочкой при создании заготовки проекта и базируется на файле Properties\Settings.Designer.cs.
  • Воспользоваться панелью Properties для формы или элемента управления и привязать нужный параметр непосредственно к его свойству.
  • Два последних способа относятся к режиму разработки приложения (не к режиму выполнения). Их подробное описание можно найти в документации по ссылке: http://msdn.microsoft.com/ru-ru/library/a65txexh(VS.90).aspx

    Способ 1.

    Вначале рассмотрим программный способ (мы же программисты!).

  • Командой Project/Add Class добавьте новый файл с именем MySettings.cs, который заполните так
  • using System;
    using System.Configuration;
        
    namespace App7
    {
        class MySettings : ApplicationSettingsBase
        {
            // Параметр типа 'Приложение' менять нельзя во время работы приложения
            // Параметр типа 'Пользователь' можно менять во время работы приложения
            //[ApplicationScopedSetting()]// Область действия - 'Приложение'
            [UserScopedSettingAttribute()]       // Область действия - 'Пользователь' 
            [DefaultSettingValueAttribute("0")]  // Значение параметра по умолчанию
            public String Min
            {
                get
                {
                    return ((String)this["Min"]);
                }
                set
                {
                    this["Min"] = (String)value;
                }
            }
        
            [UserScopedSettingAttribute()]
            [DefaultSettingValueAttribute("250")]
            public String Step
            {
                get
                {
                    return ((String)this["Step"]);
                }
                set
                {
                    this["Step"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("2000")]
            public String Count
            {
                get
                {
                    return ((String)this["Count"]);
                }
                set
                {
                    this["Count"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("ResultNumber.txt")]
            public String FileName
            {
                get
                {
                    return ((String)this["FileName"]);
                }
                set
                {
                    this["FileName"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("")]
            public String DirTarget
            {
                get
                {
                    return ((String)this["DirTarget"]);
                }
                set
                {
                    this["DirTarget"] = (String)value;
                }
            }
        
            [UserScopedSetting()]
            [DefaultSettingValue("")]
            public String Series
            {
                get
                {
                    return ((String)this["Series"]);
                }
                set
                {
                    this["Series"] = (String)value;
                }
            }
        }
    }

    Здесь мы определили общедоступные свойства с ассоциативными именами (чтобы лучше различать) и прикрепили к каждому из них аттрибуты, определяющие область видимости (действия) параметра и значение по умолчанию. Параметр типа UserScopedSetting (или UserScopedSettingAttribute - полное имя) пользователь может менять во время работы приложения, а типа ApplicationScopedSetting - нет (например, строки подключения).

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

  • Модифицируйте в файле Form1.cs конструктор класса формы приложения следующим образом
  • // Объявили и инициализировали поле для объекта параметров
            MySettings settings = new MySettings(); 
            public Form1()
            {
                InitializeComponent();
        
                // Привязка именованных параметров к 
                // сохраняемым свойствам элементов управления
                txtMin.DataBindings.Add(new Binding("Text", settings, "Min"));
                txtStep.DataBindings.Add(new Binding("Text", settings, "Step"));
                txtCount.DataBindings.Add(new Binding("Text", settings, "Count"));
                txtFileName.DataBindings.Add(new Binding("Text", settings, "FileName"));
                txtDirTarget.DataBindings.Add(new Binding("Text", settings, "DirTarget"));
                txtSeries.DataBindings.Add(new Binding("Text", settings, "Series"));
            }
  • Запустите/завершите приложение - пока значения текстовых полей не сохраняются
  • Причина здесь в том, что привязанные параметры нужно явно записывать при завершении приложения. Обычно это делается в обработчике события FormClosing формы.

  • Выделите форму в режиме Design, перейдите в панель Properies/Events и двойным щелчком на поле события FormClosing создайте для него обработчик, который заполните так
  • private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                settings.Save();
            }
  • Запустите/завершите приложение - значения текстовых полей сохраняются, кроме поля txtDirTarget
  • Нестабильность в сохранении значения текстового поля txtDirTarget объясняется тем, что мы его заполняем программно и элемент только показывает присвоенное значение, а не принимает его, пока не получит фокус ввода. Здесь можно ввести дополнительную переменную, в ней хранить значение текстового поля и ее же сохранять. Но чтобы много не переделывать, можно просто программно передавать фокус ввода текстовому полю каждый раз после получения им нового значения от диалога выбора каталога.

  • Дополните обработчик кнопки btnDirTarget строкой передачи фокуса текстовому полю txtDirTarget так
  • private void btnDirTarget_Click(object sender, EventArgs e)
            {
                dialogFolder.ShowDialog();
                txtDirTarget.Text = dialogFolder.SelectedPath;
                txtDirTarget.Focus();// Без этого не сохраняет параметр в settings
            }

    Последний оператор устанавливает на текстовое поле txtDirTarget фокус ввода, без чего значение Text не сохраняется в установках приложения при его закрытии (если только явно не щелкнуть по нему курсором).

  • Запустите/завершите приложение - теперь состояние приложения сохраняется полностью
  • Код, помещенный нами ранее в начало функции Form1_Load(), выполняет проверку на случай первого запуска приложения или когда между запусками сохраненное значение каталога изменилось. Чтобы испытать его работу, можно добавить такой обработчик двойного щелчка формы

    private void Form1_DoubleClick(object sender, EventArgs e)
            {
                settings.DirTarget = "";
                txtDirTarget.Text = "";
                dialogFolder.SelectedPath = "";
                dialogFolder.RootFolder =
                    System.Environment.SpecialFolder.MyComputer;
            }

    Способ 2.

    При создании нового проекта мастер оболочки попутно создает заготовки файлов Settings.settings и Settings.Designer.cs, в которые на этапе проектирования можно записать для компилятора исходные данные параметров, подлежащих сохранению. Эти файлы находятся в служебной папке Properties каталога проекта. Для удобства выполнения такой работы оболочка имеет вкладку (страницу) Settings панели конструктора проектов.

    Чтобы продолжить рассмотрение Способа 2, необходимо вначале нейтрализовать весь код Способа 1, поскольку сохранять мы будем те же самые свойства текстовых полей.

  • В панели Solution Explorer выделите файл MySettings.cs и командой Exclude From Project контекстного меню (либо в разделе Project меню оболочки) исключите его из проекта
  • Закомментируйте (не удаляйте!) в конструкторе класса формы код привязки сохраняемых параметров к свойствам элементов
  • // Объявили и инициализировали поле для объекта параметров
            //MySettings settings = new MySettings(); 
            public Form1()
            {
                InitializeComponent();
        
                // Привязка именованных параметров к 
                // сохраняемым свойствам элементов управления
                /*////////////////////////////////////////////////////
                txtMin.DataBindings.Add(new Binding("Text", settings, "Min"));
                txtStep.DataBindings.Add(new Binding("Text", settings, "Step"));
                txtCount.DataBindings.Add(new Binding("Text", settings, "Count"));
                txtFileName.DataBindings.Add(new Binding("Text", settings, "FileName"));
                txtDirTarget.DataBindings.Add(new Binding("Text", settings, "DirTarget"));
                txtSeries.DataBindings.Add(new Binding("Text", settings, "Series"));
                ////////////////////////////////////////////////////*/
            }
  • Командой Project/App7 Properties откройте панель конструктора проектов на вкладке Settings и настройте ее как показано на снимке
  • Если щелкнуть на пиктограмме View Code вкладки Settings, то оболочка создаст в корневом каталоге проекта файл Settings.cs с еще одной частью класса Settings (первая часть находится в Properties\Settings.Designer.cs ) для более тонкого управления параметрами.

  • Сохраните документ командой File/Save All - оболочка создала конфигурационный файл приложения app.config и добавила в него наши настройки
  • Теперь осталось подключить эти настройки к коду приложения.

  • Модифицируйте в файле Form1.cs члены класса формы следующим образом
  • // Объявили и инициализировали поле для объекта параметров
            //MySettings settings = new MySettings(); 
            Properties.Settings settings = new App7.Properties.Settings();
            public Form1()
            {
                InitializeComponent();
        
                // Привязка именованных параметров к 
                // сохраняемым свойствам элементов управления
                /*////////////////////////////////////////////////////
                txtMin.DataBindings.Add(new Binding("Text", settings, "Min"));
                txtStep.DataBindings.Add(new Binding("Text", settings, "Step"));
                txtCount.DataBindings.Add(new Binding("Text", settings, "Count"));
                txtFileName.DataBindings.Add(new Binding("Text", settings, "FileName"));
                txtDirTarget.DataBindings.Add(new Binding("Text", settings, "DirTarget"));
                txtSeries.DataBindings.Add(new Binding("Text", settings, "Series"));
                ////////////////////////////////////////////////////*/
        
                // Чтение параметров
                //*////////////////////////////////////////////////////
                txtMin.Text = settings.Min;
                txtStep.Text = settings.Step;
                txtCount.Text = settings.Count;
                txtFileName.Text = settings.FileName;
                txtDirTarget.Text = settings.DirTarget;
                txtSeries.Text = settings.Series;
                ////////////////////////////////////////////////////*/
            }
        
            private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                // Запись параметров
                //*////////////////////////////////////////////////////
                settings.Min = txtMin.Text;
                settings.Step = txtStep.Text;
                settings.Count = txtCount.Text;
                settings.FileName = txtFileName.Text;
                settings.DirTarget = txtDirTarget.Text;
                settings.Series = txtSeries.Text;
                ////////////////////////////////////////////////////*/
        
                settings.Save();
            }
  • Запустите проект в режиме Release, измените настройки и закройте приложение - параметры сохраняются
  • В панели Solution Explorer щелкните на пиктограмме Show All Files - к исполнимой сборке добавился файл конфигурации App7.exe.config
  • Теперь без этого файла приложение работать не будет и при развертывании приложения на других компьютерах файл придется включать в установочный пакет (это не страшно, только бы не забыть!). Файл App7.exe.config имеет такое же содержимое (откройте его!), что и app.config, но носит имя приложения.

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

    Способ 3.

    Этот способ выполняет создание и привязку параметров через панель свойств и подробно описан в документации http://msdn.microsoft.com/ru-ru/library/wabtadw6%28v=VS.90%29.aspx

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

  • Командой Project/App7 Properties откройте панель конструктора проектов на вкладке Settings и удалите прежние параметры (по одному или сразу все)
  • Через Solution Explorer удалите файл app.config из корневого узла текущего проекта
  • В режиме Design выделите элемент txtMin, в панели Properties раскройте узел ApplicationSettings и щелкните на кнопке с многоточием в поле свойства PropertyBinding
  • Выделите привязываемое свойство Text элемента txtMin и в раскрывающемся списке выполните ссылку New для вызова окна создания нового параметра
  • Заполните окно как показано на снимке
  • Аналогичным образом создайте и настройте параметры и для остальных элементов со значениями из предыдущих способов
  • Обратите внимание, что начальные значения параметров (если они есть) подставляются оболочкой из свойств компонентов, которые мы определяли при их создании. В итоге мы получим точно такой же результат, что и в предыдущем способе. Поэтому код в классе Form1, связанный с настройками, менять не будем - он у нас уже есть.

  • Запустите проект - параметры сохраняются, но опять при развертывании (распространении программы) нужно таскать и файл app.config (или точно такой же App7.exe.config )
  • Итак, в данном упражнении мы спроектировали инструмент, который может оказаться удобным при формировании некоторых номеров случайным образом (чтобы враги уж совсем не угадали, можно еще и зашифровать). При этом применили знания по теме данной работы. Из расссмотренных способов мне больше нравится первый. Чтобы к нему вернуться, нужно вновь добавить в проект файл MySettings.cs и удалить из него все, связанное с двумя последними способами.

    Следует заметить, что содержимое файла Settings.Designer.cs в папке Properties проекта, полученное после настроек с использованием любого из последних двух способов, почти полностью идентично содержимому файла MySettings.cs, который мы формировали вручную в первом способе. Отсюда мораль: если не хотим таскать за собой файл app.config, то вначале быстренько создаем параметры через оболочку, затем перетаскиваем созданный ею код в свой класс. После этого опять удаляем все параметры (как перед выполнением способа 3) и файл конфигурации, а используем уже наш класс с параметрами программным образом. (Что называется, - 'немного подъехать' на возможностях оболочки!)

    Вернуться к учебному плану