Резюме
В этой статье рассматриваются основы наследования из другого элемента
управления и добавления собственных свойств, методов и событий наряду с
добавлением собственного кода; каждый пример разработки элемента
управления Microsoft Windows Forms следует читать вместе с обзорной
статьей на смежную тему. (12 печатных страниц)
Исходный код
WinFormControls.exe можно загрузить из MSDN Code Center.
Эта статья является второй статьей в серии из пяти статей по
разработке элементов управления в Microsoft®
.NET:
-
Разработка пользовательских элементов управления Windows с помощью
Visual Basic .NET (обзор)
- Добавление проверки правильности регулярного выражения
-
Объединение нескольких элементов управления в один
-
Расширение элемента управления TreeView
-
Рисование собственных элементов управления с помощью GDI+
Содержание
Введение
В этой статье рассматриваются два примера: создание пользовательского
TextBox, выполняющего проверку правильности регулярного выражения, и
создание расширителя функциональности (extender provider), использующего
альтернативный способ проверки правильности регулярного выражения.
Пример 1a: добавление проверки правильности регулярного выражения к
элементу управления TextBox
В
обзорной статье было показано, как с помощью создания нового
элемента управления и при минимальном объеме работы со стороны
разработчика быстро расширить элемент управления TextBox, чтобы он
принимал только числовые данные. В приведенном примере было только
проиллюстрировано, как быстро создать элемент управления с помощью
наследования, поэтому применение примера было весьма ограничено. В
первом примере будет показано, как построить элемент управления,
достигающий той же цели более изящным способом. Вместо того, чтобы
просто расширить этот элемент управления для поддержки формата валюты и
других числовых форматов, лучше создать более универсальный элемент
управления, ограничивающий текстовый ввод. Чтобы точно выполнить
поставленную задачу, при построении (Web)-приложения ASP.NET
элемент управления RegularExpressionValidator, документированный в
Общем справочнике .NET Framework, уже был предоставлен. Этот элемент
управления позволяет ограничить вводимый текст любым форматом, который
можно описать регулярным выражением, т.е. фактически любым форматом.
Поскольку версия этого элемента управления не была предоставлена для
использования в Windows Forms, отличным примером пользовательского
элемента управления, который можно построить, является Windows Form
TextBox, проверяющий достоверность его содержания на основе регулярного
выражения.
Краткое обсуждение регулярных выражений
Прежде чем перейти к разработке элемента управления, требуется более
подробно изучить регулярные выражения. Ниже приводится несколько
полезных ресурсов:
-
Регулярные выражения .NET Framework
В этом разделе документации по .NET Framework содержится введение в
концепцию и цель регулярных выражений, а также справочник по
использованию релевантных классов в Framework.
- Примеры регулярных
выражений на Web-сайте библиотеки регулярных выражений
Ознакомившись с материалом в документации по .NET Framework, на этом
Web-сайте можно найти большой список типовых регулярных выражений,
которые можно использовать.
Что требуется построить?
Чтобы создать проверяющий достоверность элемент управления TextBox,
необходимо выполнить несколько шагов. Сначала нужно создать новый пустой
элемент управления, который наследуется из TextBox. Затем нужно добавить
новое свойство, в котором будет храниться проверяющее достоверность
регулярное выражение, и написать закрытую функцию, проверяющую
содержание TextBox на основе свойства регулярного выражения. Наконец, с
помощью переопределения события OnValidating данного элемента управления
можно проверить, допустимо ли содержание элемента управления, и отменить
событие.
Шаг 1: создание нового пользовательского элемента управления
Если элементы управления разрабатываются для себя или для других, их
рекомендуется встроить в их собственный проект для совместного
использования в качестве откомпилированной сборки (DLL в данном случае),
а не передавать код. Учитывая это, создадим новый проект Windows Control
Library для хранения нового элемента управления, затем в том же самом
решении создадим проект Windows Application для тестирования элемента
управления. По умолчанию при создании нового проекта Windows Control
Library новый пустой пользовательский элемент управления будет добавлен
как точка старта. В этом случае пользовательский элемент управления не
будет использоваться, поскольку он наследуется из существующего элемента
управления, и поэтому его файл можно удалить и просто добавить новый
файл класса.
Примечание Также можно
открыть код для пользовательского элемента управления и, удалив его,
добавить собственный код вместо удаления элемента и добавления нового
файла класса.
Затем, чтобы определить наследование этого нового класса из класса
TextBox, оператор Inherits добавляется к пустому классу Microsoft®
Visual Studio® .NET:
Public Class RegExTextBox
Inherits System.Windows.Forms.TextBox
End Class
С помощью этого небольшого кода была создана собственная версия
TextBox путем наследования из существующего элемента управления.
Поскольку собственный код не добавлялся, этот новый элемент управления
выглядит и ведет себя точно так же, как и обычный TextBox; наследование
ничего не изменит, пока не будут добавлены код или атрибуты.
Шаг 2: добавление нового свойства
В следующем шаге построения этого элемента управления необходимо
добавить новое свойство String, в котором будет храниться оператор
регулярного выражения. Назовем его RegularExpression, и, кроме свойства,
создадим также внутреннюю переменную m_RegularExpression, в которой
будет храниться значение свойства. Хотя этот класс описывает элемент
управления Windows Form, процедура добавления нового свойства не
отличается от процедуры его добавления к любому другому классу Microsoft
Visual Basic® .NET:
Private m_RegularExpression As String
Public Property RegularExpression() As String
Get
Return m_RegularExpression
End Get
Set(ByVal Value As String)
m_RegularExpression = Value
End Set
End Property
Чтобы использовать это новое свойство и проверить достоверность
содержания TextBox, используется
пространство имен System.Text.RegularExpressions. Следуя примерам в
справочной документации, можно создать новый
объект RegEx, определяющий свойство RegularExpression в
конструкторе, и затем использовать его для проверки соответствия
содержания TextBox выражению:
'для упрощения кода Imports System.Text.RegularExpressions добавлен в
'начало класса
Private Function IsValid(ByVal Contents As String) As Boolean
Dim myRegEx As New Regex(RegularExpression)
Dim myMatch As Match
myMatch = myRegEx.Match(Contents)
Return myMatch.Success
End Function
Если бы в этой точке был возможен тест элемента управления, можно
было бы найти ошибку в этом коде, поскольку если где-нибудь в строке
будет найдено соответствие, будет возвращено значение True. При
использовании этого кода регулярное выражение для поиска пятизначных
почтовых индексов найдет соответствие первых пяти цифр в строке
"23434fred", несмотря на дополнительный текст, добавленный в конце.
Фактически же необходимо проверить точное соответствие строки выражению.
Для правильной работы кода необходимо добавить проверку на совпадение
найденного соответствия с полной строкой. В то же время эта проверка
заключена в обертку очень простого блока обработки ошибок, при котором
недействительное RegularExpression возвращает значение False:
'для упрощения кода Imports System.Text.RegularExpressions добавлен в
'начало класса
Private Function IsValid(ByVal Contents As String) As Boolean
Try
Dim myRegEx As New Regex(RegularExpression)
Dim myMatch As Match
myMatch = myRegEx.Match(Contents)
If myMatch.Success _
AndAlso myMatch.Index = 0 _
AndAlso myMatch.Length = Contents.Length Then
Return True
Else
Return False
End If
Catch
'Ошибка при анализе шаблона.
Return False
End Try
End Function
Этот код возвратит значение True, только если соответствие найдено и
равно длине всей строки содержания, что и требовалось получить.
Шаг 3: переопределение метода OnValidating
Элементы управления Windows Forms располагают различными методами и
событиями, используемыми при проверке правильности, поэтому уместно
встроить эту новую проверку RegularExpression в существующую модель с
помощью проверки содержания TextBox
методом OnValidating. Этот метод будет вызываться всегда, когда
элемент управления теряет фокус, а
свойство CausesValidation элемента управления равно True. Чтобы этот
элемент управления был полезным, даже если проверка правильности не
используется (CausesValidation = False), также добавляется свойство
только для чтения (Valid), поддерживающее непосредственную проверку
правильности содержания:
Public ReadOnly Property Valid() As Boolean
Get
Return IsValid(Text)
End Get
End Property
Protected Overrides Sub OnValidating(ByVal e As _
System.ComponentModel.CancelEventArgs)
If Not Valid() Then
e.Cancel = True
End If
MyBase.OnValidating(e)
End Sub
Объединив весь этот код, можно получить законченный элемент
управления, который можно протестировать. Постройте проект элемента
управления, затем войдите в проект тестирования (в приложение Windows,
добавленное к этому же решению), добавьте ссылку на созданный проект
RegExTextBox и настройте панель инструментов, чтобы показать новый
элемент управления. После добавления нового TextBox к Windows Form
стандартное окно свойств может использоваться для редактирования
свойства RegularExpression элемента управления. Некоторые примеры
регулярных выражений для тестирования приведены ниже:
- простой почтовый индекс, проверка пятизначных чисел: "\d{5}";
- формат даты, проверка MM/DD/YYYY: "\d{1,2}\/\d{1,2}/\d{4}".
Обратите внимание, что в данном примере проверяется не значение, а
только формат. Поэтому "34/58/2341" будет рассматриваться как
совершенно правильное значение, не являясь при этом правильным
значением даты.
Распределение свойств по категориям и добавление точечного рисунка
панели инструментов
При использовании элемента управления в другом проекте возникают
некоторые косметические недостатки, которые необходимо исправить, чтобы
элемент управления выглядел профессионально. Первая проблема: если не
определить конкретную категорию, свойства появляются внизу списка
Properties в категории Misc согласно поведению по умолчанию.
Присвоение свойств категориям
Добавление атрибута к определению свойства позволяет легко
распределить свойства по категориям или даже скрыть их в окне
Properties. У данного элемента управления имеется два свойства: Valid и
RegularExpression. Присвоим RegularExpression категории "Behavior"
(поведение). Здесь нет реальных ограничений на присвоение конкретного
свойства, можно даже создать собственную категорию. Для определения
соответствующей категории будет использоваться атрибут
System.ComponentModel.Category; однако обратите внимание, что он
принимает как параметр только строку, поэтому имя категории необходимо
написать правильно:
<System.ComponentModel.Category("Behavior")> _
Public Property RegularExpression() As String
Get
…
Автор статьи, как канадец, сразу "правильно" написал слово "behavior"
(поведение) и закончил новую категорию в окне Properties.
Рис. 1. Можно использовать любое имя категории, оно появится как
раздел окна свойств.
Независимо от правильности написания имени категории можно заметить,
что у нового свойства отсутствует описание (которое обычно выводится в
нижней области окна Properties). Для этого, как можно предположить,
используется другой атрибут.
Атрибут System.ComponentModel.Description принимает в качестве
параметра строку, которая выводится всегда, когда это свойство выбрано в
окне Properties:
Private Const regularExpressionDescription As String _
= "The Regular Expression used to validate the contents of this TextBox"
<System.ComponentModel.Category("Behavior"), _
System.ComponentModel.Description(regularExpressionDescription)> _
Public Property RegularExpression() As String
Get
В отличие от RegularExpression, при разработке свойство Valid не
очень полезно (это свойство - только для чтения и вычисляется на
основании содержания TextBox), поэтому оно будет скрыто в окне
Properties в любой категории. Для этого можно использовать другой
атрибут
System.ComponentModel.Browsable, который принимает булевский
параметр, определяющий вывод свойства в окне Properties:
<System.ComponentModel.Browsable(False)> _
Public ReadOnly Property Valid() As Boolean
Get
Return IsValid(Text)
End Get
End Property
Добавление точечного рисунка на панели инструментов
Другая проблема с видом нового элемента управления состоит в том, что
значок на панели инструментов не был определен. Поэтому вместо этого
используется значок элемента управления по умолчанию. Как и категории
свойств, значок на панели инструментов добавляется с помощью атрибутов,
в данном случае с помощью
атрибута ToolboxBitmap. Этот атрибут позволяет определить путь к
фактическому файлу точечной графики (16-x-16) или точечный рисунок на
панели инструментов, который внедрен в сборку как ресурс. В этом примере
определяется только путь к файлу, и используется точечный рисунок на
панели инструментов, созданный в программе Paint:
<ToolboxBitmap("C:\mytoolboxbitmap.bmp")> _
Public Class regexTextBox
Inherits System.Windows.Forms.TextBox
Пример 1b: альтернативный способ добавления проверки правильности
регулярного выражения
Построение собственной версии элемента управления TextBox - это один
из способов добавления дополнительных функциональных возможностей.
Однако в .NET подобного результата можно достигнуть также с помощью объекта
расширителя функциональности. Расширители функциональности,
например, компоненты ErrorProvider или ToolTip, которые уже существуют в
Windows Forms, позволяют динамически добавлять свойства к другим
элементам управления. С помощью такого компонента можно добавить
свойство RegularExpression ко всем элементам управления TextBox в данной
форме и предоставить службы проверки правильности любому элементу
управления, имеющему значение для этого нового свойства. В этом
конкретном случае конечный результат не отличается от построения
собственного варианта TextBox, однако подход с использованием
расширителей функциональности хорошо работает для любых функциональных
возможностей, которые требуется применить сразу для всей группы
элементов управления.
Создание собственного расширителя функциональности
В отличие от других элементов управления, расширители
функциональности создаются путем реализации интерфейса, а не через
наследование. Этот интерфейс (IExtenderProvider) определяет только один
метод CanExtend, который, работая в сочетании с различными атрибутами
уровня класса, описывает функциональные возможности данного расширителя.
Поскольку расширителем функциональности необходимо управлять в среде
Windows Forms, он, в дополнение к реализации интерфейса
IExtenderProvider, должен наследоваться из компонента или элемента
управления:
Public Class regexRevisted
Inherits System.ComponentModel.Component
Implements System.ComponentModel.IExtenderProvider
End Class
Добавление атрибутов для каждого свойства
После объявления класса необходимо добавить атрибут для каждого
свойства, которое требуется предоставить другим элементам управления. В
данном случае добавим свойство ValidationExpression, хранящее регулярные
выражения для каждого элемента управления TextBox, и свойство Valid,
возвращающее значение True или False, которое указывает, выполняется ли
текущее ValidationExpression. Требуется добавить атрибут
System.ComponentModel.ProvideProperty, имеющий два параметра для своего
конструктора: имя нового свойства, которое необходимо предоставить, и
тип элемента управления, к которому это новое свойство будет добавлено.
Итак, в этом случае добавим следующий код:
<ProvideProperty("ValidationExpression", GetType(TextBox)), _
ProvideProperty("Valid", GetType(TextBox))> _
Public Class regexRevisted
Примечание Документация
по ProvideProperty весьма запутана; предполагалось, что второй параметр
- это тип свойства, однако он не работает с помощью GetType(String). К
счастью, эта ошибка была обнаружена довольно быстро, когда код был
передан в Microsoft, и параметр был изменен на GetType(TextBox).
Создание процедур свойств
Для каждого свойства необходимо определить использование атрибутов, а
также предоставить процедуры Get и Set для просмотра и редактирования
свойства во всех других элементах управления. Эти процедуры связаны с
предоставляемыми свойствами только именем:
Public Function GetValidationExpression(ByVal o As TextBox) As String
Public Sub SetValidationExpression(ByVal o As TextBox, _
ByVal ValidationExpression As String)
В этих процедурах свойств набор значений должен быть сохранен для
каждого элемента управления, что позволяет отслеживать, какое значение
свойства было применено к тому или иному элементу управления. Для
реализации этого в коде был добавлен параметр
NameValueCollection, который для каждого элемента управления
отслеживает свойство ValidationExpression, устанавливающее какое-либо
значение. NameValueCollection позволяет связать коллекцию строковых
значений со строковыми ключевыми значениями, что очень похоже на объект
Dictionary, однако имеет строгий контроль типов для работы только со
строками. В этом случае отдельный элемент ValidationExpression был
связан с именем соответствующего элемента управления. Имя элемента
управления должно быть уникальным в пределах одной формы, поэтому
необходимо убедиться в отсутствии конфликтов:
Dim RegExpressions As New _
System.Collections.Specialized.NameValueCollection()
Public Function GetValidationExpression(ByVal o As TextBox) As String
Return RegExpressions.Get(o.Name)
End Function
Public Sub SetValidationExpression(ByVal o As TextBox, _
ByVal ValidationExpression As String)
RegExpressions.Set(o.Name, ValidationExpression)
End Sub
В дополнение к установке и получению значения регулярного выражения
добавим также обработчик событий к любому элементу управления,
устанавливающему непустое значение (указание на то, что элементу
управления требуется проверка правильности). Этот обработчик событий
позволяет перехватить событие OnValidation каждого TextBox и с помощью
ExtenderProvider проверить достоверность содержания TextBox точно так
же, как это делает RegExTextBox (см. Пример 1a). Если проверка
правильности оказалась неуспешной, генерируется событие
ExtenderProvider, позволяющее одному обработчику событий перехватывать
все ошибки проверки правильности связанных элементов управления:
Public Sub SetValidationExpression(ByVal o As TextBox, _
ByVal ValidationExpression As String)
RegExpressions.Set(o.Name, ValidationExpression)
If ValidationExpression <> "" Then
AddHandler o.Validating, _
New System.ComponentModel.CancelEventHandler(AddressOf OnValidating)
Else
RemoveHandler o.Validating, _
New System.ComponentModel.CancelEventHandler(AddressOf OnValidating)
End If
End Sub
Private Sub OnValidating(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs)
Dim tb As TextBox
If TypeOf sender Is TextBox Then
tb = CType(sender, TextBox)
Dim validationExpression As String
validationExpression = RegExpressions.Get(tb.Name)
If validationExpression <> "" Then
If Not IsValid(validationExpression, tb.Text) Then
RaiseEvent ValidationError(tb, _
New System.EventArgs())
End If
End If
End If
End Sub
Обратите внимание, что обработчик следует удалить, если свойство
ValidationExpression установлено на пустую строку; обработка события или
прочая работа с элементом управления, который не работает с данным
расширителем, бессмысленна. Теперь свойство Valid немного проще
реализовать, поскольку оно является свойством только для чтения и
требует только функцию GetValid, которую необходимо создать. Эта функция
(фактически вызывающая другую функцию IsValid) проверяет содержание
определенного TextBox на основе его свойства ValidationExpression и
возвращает значение True или False согласно соответствию содержания
выражению:
Private Function IsValid(ByVal validationExpression As String, _
ByVal input As String) As Boolean
Dim reCheck As New Regex(validationExpression)
Dim myMatch As Match
myMatch = reCheck.Match(input)
If Not (myMatch.Success AndAlso _
myMatch.Index = 0 AndAlso _
myMatch.Length = input.Length) Then
Return False
Else
Return True
End If
End Function
Public Function GetValid(ByVal o As TextBox) As Boolean
Dim validationExpression As String
validationExpression = RegExpressions.Get(o.Name)
If validationExpression <> "" Then
Return IsValid(validationExpression, o.Text)
Else
Return True
End If
End Function
Реализация функции CanExtend
Хотя логичнее сначала создать эту функцию, необходимую интерфейсу
IExtenderProvider, который будет запрашивать эту функцию до тех пор,
пока она не будет добавлена, последней частью кода было добавление
реализации CanExtend. Ключевой задачей CanExtend является возвращение
значения True или False для каждого проверяемого элемента управления,
указывающего на необходимость применения двух новых свойств к данному
элементу управления. В случае с данным ExtenderProvider требуется
применить эти свойства к каждому TextBox, поэтому необходимо просто
проверить тип предоставленного элемента управления и возвратить значение
True, если это элемент управления TextBox, или False в противном случае.
Приняв решение обрабатывать элемент управления, также добавим пустой
элемент в NameValueCollection как метку-заполнитель для возможного
ValidationExpression:
Public Function CanExtend(ByVal extendee As Object) As Boolean _
Implements System.ComponentModel.IExtenderProvider.CanExtend
If TypeOf extendee Is System.Windows.Forms.TextBox Then
RegExpressions.Set(CType(extendee, TextBox).Name, "")
Return True
Else
Return False
End If
End Function
Последние штрихи
Как и в прежнем элементе управления, всегда имеется несколько
дополнительных атрибутов, которые можно установить, или дополнительный
код, который можно добавить, чтобы элемент управления был более полезен
или выглядел более профессионально. В случае с данным ExtenderProvider
случайный вызов процедур Get и Set для двух новых свойств нежелателен,
поэтому их можно скрыть от Microsoft Intellisense®
с помощью атрибута
EditorBrowsableAttribute:
<EditorBrowsableAttribute(EditorBrowsableState.Never)> _
Public Function GetValid(ByVal o As TextBox) As Boolean
Дополнительная информация о ExtenderProvider содержится в
пошаговом разборе этого процесса в документации по .NET Framework,
код для этого примера является частью загрузки для этой статьи. После
размещения в Windows Form готовый ExtenderProvider, называющийся
regexRevisited, добавляет к каждому TextBox два свойства -
ValidationExpression и Valid. Конечный результат и действия разработчика
очень похожи на замену каждого TextBox пользовательским регулярным
выражением TextBox, созданным ранее, однако весь код содержится в одном
компоненте.
Резюме
В результате обе версии этого примера позволяют выполнить проверку
правильности TextBox с помощью регулярных выражений, однако для этого
используются различные способы. Рекомендовать тот или иной метод
довольно сложно, поскольку они оба работают, и код отличается
незначительно. Однако разработчику необходимо знать оба способа
добавления возможностей к элементам управления Windows Forms, чтобы
выбрать наиболее подходящее решение для того или иного проекта.