netlib.narod.ru | < Назад | Оглавление | Далее > |
Хотя сегодня .NET Framework может казаться верхом совершенства, существует вероятность (хоть и небольшая), что когда-нибудь грешные разработчики Microsoft будут вынуждены добавить в класс SystemInformation одно – два свойства. Тогда мой класс SysInfoStrings следует обновить, чтобы включить в него эти дополнительные свойства, и все программы этой главы надо будет перекомпилировать с учетом новой версии класса.
А можно ли написать программу, которая автоматически использует все свойства класса SystemInformation, даже те, которых и на свете не было во время ее написания?
Да, можно. Чтобы понять, как это сделать, подумаем, где размещается код класса SystemInformation. Согласно документации на этот класс код хранится в файле System.Windows.Forms.dll. При запуске любой программы из этой главы ОС связывает ее с файлом System.Windows.Forms.dll, и программа может вызывать класс SystemInformation.
Но DLL — это не просто набор кодов. Кроме них, она содержит метаданные в двоичном формате, подробно описывающие классы, включенные в данный файл. Описываются все поля, свойства, методы и события этих классов. Компилятор С# использует эти данные при компиляции программ (вот почему надо устанавливать параметр Reference files). Документация .NET Framework также учитывает эти метаданные.
Так что имеет смысл ввести в программу возможность доступа к этим метаданным во время ее выполнения, динамически обновлять информацию о классах .NET и даже запускать некоторые методы этих классов. Это называется отражением (reflection), и концепция этого процесса заимствована из языка Java. Вообще-то об отражении следует рассказывать в отдельной главе, но оно так здорово подходит для этого приложения, что я не могу удержаться.
В качестве первого шага перепишем класс SysInfoStrings так, чтобы для получения имен свойств и их значений использовалось отражение.
SysInfoReflectionStrings.cs
//--------------------------------------------------------- // SysInfoReflectionStrings.cs (C) 2001 by Charles Petzold //--------------------------------------------------------- using Microsoft.Win32; using System; using System.Drawing; using System.Reflection; using System.Windows.Forms; class SysInfoReflectionStrings { // Поля static bool bValidInfo = false; static int iCount; static string[] astrLabels; static string[] astrValues; // Конструктор static SysInfoReflectionStrings() { SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(UserPreferenceChanged); SystemEvents.DisplaySettingsChanged += new EventHandler(DisplaySettingsChanged); } // Свойства public static string[] Labels { get { GetSysInfo(); return astrLabels; } } public static string[] Values { get { GetSysInfo(); return astrValues; } } public static int Count { get { GetSysInfo(); return iCount; } } // Обработчики событий static void UserPreferenceChanged(object obj, UserPreferenceChangedEventArgs ea) { bValidInfo = false; } static void DisplaySettingsChanged(object obj, EventArgs ea) { bValidInfo = false; } // Методы static void GetSysInfo() { if(bValidInfo) return; // Получаем информацию о свойствах для класса SystemInformation Type type = typeof(SystemInformation); PropertyInfo[] apropinfo = type.GetProperties(); // Подсчитываем количество статических доступных для чтения свойств iCount = 0; foreach (PropertyInfo pi in apropinfo) { if(pi.CanRead && pi.GetGetMethod().IsStatic) iCount++; } // Выделяем память для массивов строк astrLabels = new string[iCount]; astrValues = new string[iCount]; // Снова перебираем в цикле информацию свойств класса iCount = 0; foreach (PropertyInfo pi in apropinfo) { if(pi.CanRead && pi.GetGetMethod().IsStatic) { // Получаем имя и значение свойства astrLabels[iCount] = pi.Name; astrValues[iCount] = pi.GetValue(type, null).ToString(); iCount++; } } Array.Sort(astrLabels, astrValues); bValidInfo = true; } public static float MaxLabelWidth(Graphics grfx, Font font) { return MaxWidth(Labels, grfx, font); } public static float MaxValueWidth(Graphics grfx, Font font) { return MaxWidth(Values, grfx, font); } static float MaxWidth(string[] astr, Graphics grfx, Font font) { float fMax = 0; GetSysInfo(); foreach (string str in astr) fMax = Math.Max(fMax, grfx.MeasureString(str, font).Width); return fMax; } }
Львиную долю работы в этом классе выполняет метод GetSysInfo. Он получает имена свойств и их значения при первом обращении и при каждом их изменении. Оператор typeof языка С# получает тип класса SystemInformation, который сохраняется в переменной типа Туре. Одним из методов Туре является метод GetProperties, возвращающий массив объектов PropertyInfo. Каждый объект в этом массиве является свойством класса SystemInformation. Цикл foreach подсчитывает все свойства, которые одновременно являются статическими и считываемыми. (Знаю, что на данный момент все свойства класса SystemInformation являются статическими и считываемыми, но я пытаюсь сделать программу универсальной.)
Затем программа создает массивы для свойств и их значений и еще раз проходит по массиву PropertyInfo. Свойство Name объекта PropertyInfo содержит имя свойства класса SystemInformation; в нашем случае свойство Name возвращает строки типа «HighContrast» и «IconSize». Метод GetValue получает значение каждого свойства. Статический метод Sort класса Array упорядочивает оба массива (с именами и значениями) по именам свойств.
Функционально программа, использующая класс SysInfoReflectionStrings, является сочетанием SysInfoUpdate и SysInfoEfficient.
SysInfoReflection.cs
//-------------------------------------------------- // SysInfoReflection.cs (C) 2001 by Charles Petzold //-------------------------------------------------- using Microsoft.Win32; using System; using System.Drawing; using System.Windows.Forms; class SysInfoReflection: Form { protected int iCount; protected string[] astrLabels; protected string[] astrValues; protected float cxCol; protected int cySpace; public static void Main() { Application.Run(new SysInfoReflection()); } public SysInfoReflection() { Text = "System Information: Reflection"; BackColor = SystemColors.Window; ForeColor = SystemColors.WindowText; AutoScroll = true; SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(UserPreferenceChanged); SystemEvents.DisplaySettingsChanged += new EventHandler(DisplaySettingsChanged); UpdateAllInfo(); } void UserPreferenceChanged(object obj, UserPreferenceChangedEventArgs ea) { UpdateAllInfo(); Invalidate(); } void DisplaySettingsChanged(object obj, EventArgs ea) { UpdateAllInfo(); Invalidate(); } void UpdateAllInfo() { iCount = SysInfoReflectionStrings.Count; astrLabels = SysInfoReflectionStrings.Labels; astrValues = SysInfoReflectionStrings.Values; Graphics grfx = CreateGraphics(); SizeF sizef = grfx.MeasureString(" ", Font); cxCol = sizef.Width + SysInfoReflectionStrings.MaxLabelWidth(grfx, Font); cySpace = Font.Height; AutoScrollMinSize = new Size( (int) Math.Ceiling(cxCol + SysInfoReflectionStrings.MaxValueWidth(grfx, Font)), (int) Math.Ceiling(cySpace * iCount)); grfx.Dispose(); } protected override void OnPaint(PaintEventArgs pea) { Graphics grfx = pea.Graphics; Brush brush = new SolidBrush(ForeColor); Point pt = AutoScrollPosition; int iFirst = (int)((pea.ClipRectangle.Top - pt.Y) / cySpace); int iLast = (int)((pea.ClipRectangle.Bottom - pt.Y) / cySpace); iLast = Math.Min(iCount - 1, iLast); for (int i = iFirst; i <= iLast; i++) { grfx.DrawString(astrLabels[i], Font, brush, pt.X, pt.Y + i * cySpace); grfx.DrawString(astrValues[i], Font, brush, pt.X + cxCol, pt.Y + i * cySpace); } } }
Это моя самая последняя версия программы SystemInformation (до главы 6, в которой я добавлю в нее интерфейс для работы с клавиатурой).
netlib.narod.ru | < Назад | Оглавление | Далее > |