Исходники
Статьи
Языки программирования
.NET Delphi Visual C++ Borland C++ Builder C/С++ и C# Базы Данных MySQL MSSQL Oracle PostgreSQL Interbase VisualFoxPro Веб-Мастеру PHP HTML Perl Java JavaScript Протоколы AJAX Технология Ajax Освоение Ajax Сети Беспроводные сети Локальные сети Сети хранения данных TCP/IP xDSL ATM Операционные системы Windows Linux Wap Книги и учебники
Скрипты
Магазин программиста
|
ВведениеПеред началом работы обратите внимание на небольшое предупреждение: построение собственного элемента управления "с нуля" и рисование собственных изображений рекомендуется выполнять только тогда, когда другие возможности исчерпаны. Этот процесс не очень сложен с технической точки зрения (хотя и не прост), однако при этом необходимо учитывать столько деталей, что, вероятно, в созданном элементе управления не будет хватать, по крайней мере, части возможностей проектирования и доступности элементов управления, поставляемых с Microsoft® Visual Studio® .NET. Начав работу с одним из предоставленных элементов управления, наследуя от него функциональные возможности и надстраивая над ним дополнительную функциональность, разработчик может получить почти все функциональные возможности элемента управления бесплатно. Однако кроме этого предупреждения следует также заметить, что строить собственные элементы управления и особенно рисовать собственный интерфейс с помощью GDI+ очень интересно, поэтому любому разработчику нужно это попробовать. На практике никакого специфичного типа элемента управления, который лучше всего строить "с нуля", не существует, за исключением того, что, как правило, обычно это достаточно необычный элемент управления, который невозможно построить на основе существующего элемента. Далее будет рассматриваться пример построения связанного с данными просмотра эскизов, в достаточной степени отличающегося от любого другого элемента управления Microsoft Windows® Form, который нужно создавать "с нуля". Примечание Если требуется подобный элемент управления, который отличается от элементов управления Windows Forms, поставляемых с Microsoft.NET, и сложен с точки зрения реализации, то перед началом работы над ним можно также обратиться к сторонним поставщикам элементов управления. В обзорной статье по разработке элементов управления упоминалось, что одной из причин успеха Microsoft Visual Basic® было большое количество доступных сторонних элементов управления. Это относится и к Visual Basic .NET. Первоначально этот элемент управления должен был отвечать следующим требованиям:
В результате с минимальными усилиями был создан визуальный браузер для домашней библиотеки компакт-дисков (см. рис. 1).
Рис. 1. Пример использования связанного с данными просмотра эскизов Для этого элемента управления необходимо было построить собственный интерфейс с помощью GDI+ на основе ближайшего элемента управления Windows Form ListView. Этот элемент управления и некоторые другие, например, TextBox, ComboBox и ListBox, фактически являются общими элементами управления Windows, которые были просто обернуты кодом .NET для использования в приложениях Windows Forms. Эти элементы управления не позволяют полностью переопределить их процедуры рисования, поэтому возможностей настройки согласно определенным требованиям было недостаточно. Поддержка сложного связывания данныхДаже при том, что новый элемент управления не мог быть основан на существующем классе, он должен быть построен для максимально возможного многократного использования. Поэтому было решено создать базовый класс, инкапсулирующий основную работу для элемента управления со сложным связыванием данных. В процессе работы этот класс был построен и использовался для первой версии этого элемента управления. Собственный элемент управления, построенный этим способом, работал действительно хорошо, и созданный базовый класс мог использоваться многократно при построении окна списка и некоторых других элементов управления на основе GDI+, что привело к значительному сокращению кода, необходимого для каждого элемента. После завершения выяснилось, что можно было сократить работу еще больше: если бы это было такой хорошей идеей, то разработчики Windows Forms догадались бы об этом сами, и было бы установлено, что элементы управления ListBox и ComboBox основаны на общем классе ListControl, который почти в точности совпадает с классом, который был создан. Созданная версия работала прекрасно, однако элементы управления были переписаны для использования этого класса, который будет также использоваться в этой статье. С помощью класса ListControl выполняется почти вся работа по связыванию данных, что значительно сокращает объем требуемого кода. В рассматриваемый элемент управления добавим новое свойство, чтобы определить поле, хранящее путь к изображению. Другие свойства DataSource, DataMember, SelectedIndex, DisplayMember и ValueMember уже предоставлены. Над данными в этом элементе управления по-прежнему необходимо поработать (например, над циклом прохождения через элементы в процедурах рисования), однако это упрощается с помощью класса ListControl, предоставляющего экземпляр класса CurrencyManager как Me.DataManager. Этот объект обеспечивает доступ к списку элементов (Me.DataManager.List), текущей позиции в списке (Me.DataManager.Position) и классу PropertyDescriptorCollection, позволяющему обращаться к любому полю элемента списка (Me.DataManager.GetItemProperties). Рисование изображенийС помощью переопределения метода OnPaint базового класса можно принять прорисовку этого элемента управления на себя. Именно это делается в коде, который требуется, чтобы нарисовать страницу, заполненную эскизами изображений. Прежде началом рисования необходимо определить позицию каждого изображения (и связанный текст), информацию об используемом цвете и шрифте и количество одновременно видимых строк и столбцов. Определение настраиваемого размещенияЧтобы этот элемент управления был полезным, расположение и установка размеров эскизов должны конфигурироваться. Поэтому при определении процедуры рисования использовались переменные (см. рис. 2).
Примечания к рисунку: Рис. 2. Во время рисования собственного элемента управления перед началом кодирования определите размещение. Каждая из перечисленных ниже переменных может быть сконфигурирована с помощью открытого свойства элемента управления:
Помимо этого вид элемента управления может быть настроен с помощью свойств по умолчанию ForeColor, BackColor и Font, на каждое из которых код графики ссылается соответствующим образом. Это выполняется вручную, поэтому во время работы с графикой необходимо ссылаться на эти стандартные свойства, чтобы элемент управления вел себя должным образом. Вычисление числа строк и столбцов на страницеПоскольку все изображения могут не поместиться на одной странице, необходимо следить, какой элемент находится в настоящий момент в левом верхнем углу элемента управления, и рисовать элементы относительно данного элемента списка. Определение количества строк и столбцов на странице выполняется в событии изменения размеров элемента управления, поскольку эти значения необходимо рассчитывать каждый раз заново при увеличении или уменьшении размера элемента управления: Private Sub imageList_Resize(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Resize Dim new_rowsPerPage As Integer = (Me.Height - y) \ ((2 * y) + h) Dim new_colsPerPage As Integer = Me.Width \ ((2 * x) + w) If (new_rowsPerPage <> rowsPerPage) _ OrElse (new_colsPerPage <> colsPerPage) Then rowsPerPage = new_rowsPerPage colsPerPage = new_colsPerPage End If End Sub Чтобы избежать ненужной работы и нежелательного мерцания, необходим минимальный объем перерисовки элемента управления, но в этом случае каждое изменение размера потребует перерисовки. Иногда, как и в рассматриваемом случае, невозможно избежать перерисовки из-за дизайна пользовательского интерфейса. Нарисуйте две стрелки (одну наверху и одну внизу элемента управления), которые указывают на элементы, находящиеся вне экрана. Чтобы после каждого изменения размеров выполнялась перерисовка, можно было бы добавить вызов Me.Invalidate в рамках этой процедуры изменения размеров, но Windows Forms предоставляет другой метод, использующий стили элемента управления. Добавляя вызовы метода SetStyle в конструкторе (метод New) данного элемента управления, можно управлять его рисованием и обновлением с помощью Windows Forms. В этом случае установка стиля ResizeRedraw вызовет обновление при любом изменении элемента управления. Тем не менее, установим также стиль DoubleBuffer, превосходно позволяющий устранять нежелательное мерцание из элемента управления, нарисованного пользователем: Public Sub New() Me.SetStyle(ControlStyles.DoubleBuffer, True) Me.SetStyle(ControlStyles.ResizeRedraw, True) Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True) Me.SetStyle(ControlStyles.UserPaint, True) End Sub Примечание Двойная буферизация - это метод работы с графикой, при котором все изображение пользовательского интерфейса рисуется сначала в буфер (например, объект Image в памяти), а затем - в окне как цельное изображение. Она позволяет значительно уменьшить нежелательное мерцание по сравнению с последовательным выполнением всех отдельных графических команд непосредственно в окне. Согласно документации по .NET Framework для опций ControlStyles необходимо установить UserPaint и AllPaintingInWmPaint, чтобы воспользоваться всеми преимуществами двойной буферизации. Переопределение OnPaintНа практике работа с графикой выполняется путем переопределения OnPaint, которому в качестве параметра передается объект, включающий объект Graphics, который может использоваться для рисования на подложке элемента управления. В данной процедуре OnPaint выполняется цикл прохождения через элементы источника данных. При этом необходимо следить за позициями строк и столбцов и рисовать каждый элемент: Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim myList As IList Dim gr As Graphics = e.Graphics gr.FillRectangle(New SolidBrush(Me.BackColor), e.ClipRectangle) gr.InterpolationMode = scalingMode gr.SmoothingMode = SmoothingMode.Default ControlPaint.DrawBorder3D(gr, Me.DisplayRectangle, m_borderStyle) If Me.DataManager Is Nothing Then myList = Nothing Else myList = Me.DataManager.List End If If Not myList Is Nothing Then 'if there is any data Dim itemCount As Integer itemCount = myList.Count Dim itemsDisplayed As Integer 'current position in the list itemsDisplayed = currentTopLeftItem Dim i, j As Integer 'loop indexes Dim height, width As Integer For i = 0 To rowsPerPage - 1 For j = 0 To colsPerPage - 1 If itemsDisplayed < itemCount Then DrawOneItem(itemsDisplayed, i, j, gr) itemsDisplayed += 1 End If Next Next 'Прорисовка указателей "страница вниз/страница вверх" Dim webdingsFont As New Font("Webdings", 20, _ FontStyle.Regular, GraphicsUnit.Pixel) Dim textBrush As New SolidBrush(Me.ForeColor) If itemsDisplayed < itemCount - 1 Then 'Прорисовка стрелки вниз gr.DrawString("6", _ webdingsFont, textBrush, 0, Me.Height - 24) End If If currentTopLeftItem > 0 Then 'Прорисовка стрелки вверх gr.DrawString("5", webdingsFont, textBrush, 0, 0) End If End If End Sub Большая часть кода в этой процедуре рисования служит для определения позиции (строки и столбца) каждого элемента списка, и лишь незначительная часть кода фактически обрабатывает рисунок. Эту процедуру можно значительно очистить, переместив код рисования отдельного элемента в его собственную процедуру: Private Sub DrawOneItem(ByVal index As Integer, _ ByVal row As Integer, _ ByVal col As Integer, _ ByVal gr As Graphics) Dim textFont As Font = Me.Font Dim textBrush As New SolidBrush(Me.ForeColor) Dim myStringFormat As StringFormat = New StringFormat() myStringFormat.Alignment = StringAlignment.Center myStringFormat.FormatFlags = StringFormatFlags.LineLimit Dim imageURL As String = GetListItemImage(index) If imageURL = "" Then imageURL = m_GenericImage If imageURL <> "" Then If IO.File.Exists(imageURL) Then Dim myNewImage As New Bitmap(imageURL) 'Масштабирование изображения до необходимого определенного 'размера With myNewImage If .Height > h Then Height = h Width = CInt((h / .Height) * .Width) Else Height = .Height Width = .Width End If If Width > w Then Height = CInt((w / Width) * Height) Width = w End If End With Dim imageRect _ As New Rectangle((2 * x) + _ (col * ((2 * x) + w)) + ((w - Width) \ 2), _ (1 * y) + (row * ((2 * y) + h)) _ + ((h - Height) \ 2), _ Width, Height) gr.DrawImage(myNewImage, imageRect) Dim myNewPen As Pen If index = Me.DataManager.Position Then 'selected myNewPen = New Pen(Color.Yellow) myNewPen.Width = 4 Else myNewPen = New Pen(Color.Black) myNewPen.Width = 1 End If gr.DrawRectangle(myNewPen, imageRect) End If End If Dim textHeight As Integer = y * 2 gr.DrawString(Me.GetItemText(Me.DataManager.List.Item(index)), _ textFont, textBrush, _ New RectangleF((x) + (col * ((2 * x) + w)), _ 2 + (1 * y) + h + (row * ((2 * y) + h)), _ w + (2 * x), textHeight), myStringFormat) End Sub Удалив эту процедуру из основного кода OnPaint, легче рассматривать фактически выполненную работу GDI+. Если путь для изображения ссылается на реальный файл, сначала создается новый объект Bitmap с помощью этого пути как конструктора, и затем само изображение рисуется на элемент управления при помощи метода DrawImage объекта Graphics. Перед фактическим рисованием изображения выполняются небольшие математические расчеты для пропорционального масштабирования изображения в его целевое пространство. Примечание Объект Graphics представляет поверхность рисунка, поэтому эти те же методы могут использоваться в некоторых других ситуациях, например, при создании собственных файлов изображения, таких как bitmap или jpeg. Следующим шагом в рисовании элемента будет рисование прямоугольника заданного цвета и с заданной толщиной линий вокруг изображения для указания фокуса. Граница рисуется после рисования изображения так, чтобы она была сверху и не закрывалась изображением. Затем под изображением рисуется текстовая строка с помощью метода DrawString. С помощью перегрузки метода DrawString, который принимает прямоугольник размещения текста, текст может быть автоматически перенесен должным образом в рамках указанной области. Методу DrawString передается также объект StringFormat, позволяющий сконфигурировать детали рисования текста, такие как использование переноса на новую строку. В этом примере объект StringFormat сконфигурирован с флажком LineLimit, запрещающим рисование частично отсеченного текста, поэтому появится только текст, полностью находящийся в прямоугольнике размещения текста. Обработка нажатий клавиш и щелчков мышиРазработчик должен самостоятельно обработать всю навигацию в рамках этого элемента управления, поскольку он не базируется на существующем элементе управления как ListView. Поэтому было решено поддерживать следующее навигационное поведение:
Обработка нажатий клавишКод, поддерживающий перемещение с помощью клавиатуры, довольно прост; в нем используютс, я небольшие математические расчеты для определения строки и столбца, выбранных в настоящий момент. Весь код инкапсулирован в процедуру KeyPressed (вызываемую из события KeyDown элемента управления): Private Sub imageList_KeyDown(ByVal sender As Object, _ ByVal e As System.Windows.Forms.KeyEventArgs) _ Handles MyBase.KeyDown KeyPressed(e.KeyCode) End Sub Private Sub KeyPressed(ByVal Key As System.Windows.Forms.Keys) Try Dim m_oldTopItem As Integer = currentTopLeftItem Dim m_oldSelectedItem As Integer = Me.DataManager.Position Dim newPosition As Integer = m_oldSelectedItem Dim selectedRow As Integer Dim selectedColumn As Integer selectedRow = System.Math.Floor( _ (m_oldSelectedItem - currentTopLeftItem) / colsPerPage) selectedColumn = (m_oldSelectedItem - currentTopLeftItem) _ Mod colsPerPage If Not Me.DataManager.List Is Nothing Then Select Case Key Case Keys.Up If newPosition >= colsPerPage Then newPosition -= colsPerPage End If Case Keys.Down If Me.DataManager.Count - colsPerPage > _ newPosition Then newPosition += colsPerPage End If Case Keys.Left If selectedColumn = 0 Then RaiseEvent LeaveControl(Direction.Left) Else newPosition -= 1 End If Case Keys.Right If selectedColumn = (colsPerPage - 1) Then RaiseEvent LeaveControl(Direction.Right) Else newPosition += 1 End If Case Keys.PageDown If newPosition < Me.DataManager.Count Then newPosition += (rowsPerPage * colsPerPage) If newPosition >= Me.DataManager.Count Then newPosition = Me.DataManager.Count - 1 End If Else RaiseEvent LeaveControl(Direction.Down) End If Case Keys.PageUp If newPosition > 0 Then newPosition -= (rowsPerPage * colsPerPage) If newPosition < 0 Then newPosition = 0 End If Else RaiseEvent LeaveControl(Direction.Down) End If Case Keys.Enter, Keys.Return RaiseEvent ItemChosen(newPosition) End Select If newPosition < 0 Then newPosition = 0 If newPosition >= Me.DataManager.Count Then newPosition = Me.DataManager.Count - 1 End If If newPosition <> m_oldSelectedItem Then Me.DataManager.Position = newPosition End If End If Catch except As Exception Debug.WriteLine(except) End Try End Sub Поддержка мышиЧтобы элемент управления "просмотр эскизов" хорошо работал с мышью, были созданы два обработчика событий: открытое событие (ItemChosen, также вызываемое из KeyPressed, когда пользователь нажимает Enter или Return), которое может быть сгенерировано, и сервисная функция, обрабатывающая нажатия: Private Function HitTest(ByVal loc As Point) As Integer Dim i As Integer Dim found As Boolean = False i = 0 Do While i < Me.DataManager.Count And Not found If GetItemRect(i).Contains(loc) Then found = True Else i += 1 End If Loop If found Then Return i Else Return -1 End If End Function Private Sub dbThumbnailView_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Click Dim mouseLoc As Point = Me.PointToClient(Me.MousePosition()) Dim itemHit As Integer = HitTest(mouseLoc) If itemHit <> -1 Then Me.DataManager.Position = itemHit End If End Sub Private Sub dbThumbnailView_DoubleClick(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.DoubleClick Dim mouseLoc As Point = Me.PointToClient(Me.MousePosition()) Dim itemHit As Integer = HitTest(mouseLoc) If itemHit <> -1 Then RaiseEvent ItemChosen(itemHit) End If End Sub Обратите внимание, что событие Click не должно вызываться явно изнутри обработчика событий dbThumbnailView_Click. Элемент управления автоматически вызовет стандартное событие Click, которое может быть обработано пользователем элемента управления. Кроме того, событие Double-click будет сгенерировано для каждого двойного щелчка, однако событие ItemChosen произойдет, только если дважды щелкнуть на выбранном элементе. Определение события по умолчаниюРассматривая события, необходимо упомянуть один из тех "последних штрихов", облегчающих использование элемента управления. Если дважды щелкнуть по элементу управления, находясь в режиме его проектирования (в интегрированной среде разработки Visual Studio .NET), появится обработчик событий для одного из событий объекта. Эта возможность интегрированной среды разработки очень полезна, однако она хорошо работает только тогда, когда появляется наиболее общий обработчик событий. Добавив атрибут DefaultEvent к классу элемента управления, можно определить событие, выбираемое интегрированной средой разработки, если в режиме проектирования дважды щелкнуть на элементе управления: <DefaultEvent("ItemChosen")> _ Public Class dbThumbnailView Inherits ListControl Без атрибута DefaultEvent интегрированная среда разработки будет использовать любой атрибут DefaultEvent, который определен базовым классом или другими классами далее по цепочке наследования. В случае с данным элементом управления "просмотр эскизов" событие Click выбирается по умолчанию как событие по умолчанию класса Control, и данная база (ListControl) наследуется из Control. Некоторые проблемы и примечанияПервоначальный элемент управления не проектировался для использования клавиатуры или мыши, поэтому полоса прокрутки отсутствует. Там было достаточно только визуальных индикаторов (стрелки в левых нижнем и верхнем углах), однако в среде с использованием мыши можно добавить полосу прокрутки. Также в первой версии этого элемента управления не использовалась граница, и не поддерживалась мышь, но обе эти возможности были добавлены к версии, рассматриваемой в этой статье. ПримерЭтот элемент управления демонстрируется как часть того же примера (связанный с данными TreeView) и используется для отображения всех книг определенного автора или издателя (см. рис. 3). Код для этого типового приложения включен в загрузку для этой статьи.
Рис. 3. Это типовое приложение демонстрирует элемент управления "просмотр эскизов" и связанный с данными элемент управления Tree из примера 3. РезюмеИногда необходимо строить довольно специфичные элементы управления. Разработку собственного элемента управления нужно начинать "с нуля", включая написание кода для собственной графики. Если создается сложный связанный с данными элемент управления, такой как Grid, или некая форма ListBox, можно значительно ускорить разработку, базируя элемент управления на классе ListControl, как описано в этой статье.
|
Форум Программиста
Новости Обзоры Магазин Программиста Каталог ссылок Поиск Добавить файл Обратная связь Рейтинги
|