Практическое руководство. Создание окна списка, внешний вид которого определяется владельцем
Обновлен: Ноябрь 2007
В платформе .NET Compact Framework можно создать окно списка, внешний вид которого определяется владельцем, хотя в полной версии платформы .NET Framework эта операция выполняется проще. Платформа .NET Compact Framework не поддерживает свойство DrawMode, событие DrawItem и другие члены для рисования окна списка и других элементов управления, однако данные функции можно запрограммировать. В данном примере приведен класс пользовательского элемента управления, который можно использовать для создания окна списка, внешний вид которого определяется владельцем, а затем этот элемент управления реализуется в виде окна списка для выбора шрифтов.
В данном пример присутствует базовый класс OwnerDrawnListBox, от которого наследует FontListBox. Базовая функциональность OwnerDrawnListBox включает следующее:
Вертикальная полоса прокрутки, массив для хранения элементов и растровое изображение для построения окна списка.
Свойство высоты элемента и определение числа элементов, которое можно отобразить при заданной высоте элемента управления.
Свойство SelectedIndex, которое определяет текущий элемент по положению полосы прокрутки, высоте элемента и координатам указателя мыши.
Событие SelectedIndexChanged.
Обеспечение видимости элементов.
Удаление полосы прокрутки, когда она не используется.
Операции с использованием клавиш перемещения вверх и вниз на клавиатуре.
Класс FontListBox наследует из OwnerDrawnListBox. Включает следующие реализации и методы:
Определение правильной высоты элемента.
Отрисовка элементов с помощью переопределенного метода OnPaint, при этом выбранный элемент отрисовывается как выделенный. В целях предотвращения появления мерцания данный метод не отображает элемент управления до тех пор, пока не будут отрисованы все графические функции.
Свойство SelectedFaceName, содержащее название выбранного шрифта.
Форма DrawFontList выполняет следующие действия:
Создает экземпляр FontListBox и добавляет его коллекцию элементов управления.
Добавляет обработчик событий для события SelectedIndexChanged.
Заполняет FontListBox списком шрифтов.
Предоставляет метки для отображения образца выбранного шрифта.
Создание окна списка, внешний вид которого определяется владельцем
Добавьте в проект класс пользовательского элемента управления OwnerDrawnListBox.
' Base custom control for DrawFontList Class OwnerDrawnListBox Inherits Control Private Const SCROLL_WIDTH As Integer = 20 Private itemH As Integer = -1 Private selIndex As Integer = -1 Private offScreenBitmap As Bitmap Private vs As VScrollBar Private itemsAList As ArrayList Public Sub New() Me.vs = New VScrollBar Me.vs.Parent = Me Me.vs.Visible = False Me.vs.SmallChange = 1 AddHandler Me.vs.ValueChanged, AddressOf Me.ScrollValueChanged Me.itemsAList = New ArrayList End Sub Public ReadOnly Property Items() As ArrayList Get Return Me.itemsAList End Get End Property Protected ReadOnly Property OffScreen() As Bitmap Get Return Me.offScreenBitmap End Get End Property Protected ReadOnly Property VScrollBar() As VScrollBar Get Return Me.vs End Get End Property Friend Event SelectedIndexChanged As EventHandler Protected Overridable Sub OnSelectedIndexChanged(ByVal e As EventArgs) RaiseEvent SelectedIndexChanged(Me, e) End Sub Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs) Me.SelectedIndex = Me.vs.Value + e.Y / Me.ItemHeight ' Invalidate the control so we can draw the item as selected. Me.Refresh() End Sub ' Get or set index of selected item. Public Property SelectedIndex() As Integer Get Return Me.selIndex End Get Set(ByVal Value As Integer) Me.selIndex = Value RaiseEvent SelectedIndexChanged(Me, Nothing) End Set End Property Protected Sub ScrollValueChanged(ByVal o As Object, ByVal e As EventArgs) Me.Refresh() End Sub Protected Overridable Property ItemHeight() As Integer Get Return Me.itemH End Get Set(ByVal Value As Integer) Me.itemH = Value End Set End Property ' If the requested index is before the first visible index then set the ' first item to be the requested index. If it is after the last visible ' index, then set the last visible index to be the requested index. Public Sub EnsureVisible(ByVal index As Integer) If index < Me.vs.Value Then Me.vs.Value = index Me.Refresh() ElseIf index >= Me.vs.Value + Me.DrawCount Then Me.vs.Value = index - Me.DrawCount + 1 Me.Refresh() End If End Sub ' Need to set focus to the control when it ' is clicked so that keyboard events occur. Protected Overrides Sub OnClick(ByVal e As EventArgs) Me.Focus() MyBase.OnClick(e) End Sub ' Selected item moves when you use the keyboard up/down keys. Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs) Select Case e.KeyCode Case Keys.Down If Me.SelectedIndex < Me.vs.Maximum Then Me.SelectedIndex = Me.SelectedIndex + 1 EnsureVisible(Me.SelectedIndex + 1) Me.Refresh() End If Case Keys.Up If Me.SelectedIndex > Me.vs.Minimum Then Me.SelectedIndex = Me.SelectedIndex - 1 EnsureVisible(Me.SelectedIndex - 1) Me.Refresh() End If Case Keys.PageDown Me.SelectedIndex = Math.Min(Me.vs.Maximum, Me.SelectedIndex + Me.DrawCount) EnsureVisible(Me.SelectedIndex) Me.Refresh() Case Keys.PageUp Me.SelectedIndex = Math.Max(Me.vs.Minimum, Me.SelectedIndex - Me.DrawCount) EnsureVisible(Me.SelectedIndex) Me.Refresh() Case Keys.Home Me.SelectedIndex = 0 EnsureVisible(Me.SelectedIndex) Me.Refresh() Case Keys.End Me.SelectedIndex = Me.itemsAList.Count - 1 EnsureVisible(Me.SelectedIndex) Me.Refresh() End Select MyBase.OnKeyDown(e) End Sub ' Calculate how many items we can draw given the height of the control. Protected ReadOnly Property DrawCount() As Integer Get If Me.vs.Value + Me.vs.LargeChange > Me.vs.Maximum Then Return Me.vs.Maximum - Me.vs.Value + 1 Else Return Me.vs.LargeChange End If End Get End Property Protected Overrides Sub OnResize(ByVal e As EventArgs) Dim viewableItemCount As Integer = Me.ClientSize.Height / Me.ItemHeight Me.vs.Bounds = New Rectangle(Me.ClientSize.Width - SCROLL_WIDTH, 0, SCROLL_WIDTH, Me.ClientSize.Height) ' Determine if scrollbars are needed If Me.itemsAList.Count > viewableItemCount Then Me.vs.Visible = True Me.vs.LargeChange = viewableItemCount Me.offScreenBitmap = New Bitmap(Me.ClientSize.Width - SCROLL_WIDTH - 1, Me.ClientSize.Height - 2) Else Me.vs.Visible = False Me.vs.LargeChange = Me.itemsAList.Count Me.offScreenBitmap = New Bitmap(Me.ClientSize.Width - 1, Me.ClientSize.Height - 2) End If Me.vs.Maximum = Me.itemsAList.Count - 1 End Sub End Class
// Base custom control for DrawFontList class OwnerDrawnListBox : Control { const int SCROLL_WIDTH = 20; int itemHeight = -1; int selectedIndex = -1; Bitmap offScreen; VScrollBar vs; ArrayList items; public OwnerDrawnListBox() { this.vs = new VScrollBar(); this.vs.Parent = this; this.vs.Visible = false; this.vs.SmallChange = 1; this.vs.ValueChanged += new EventHandler(this.ScrollValueChanged); this.items = new ArrayList(); } public ArrayList Items { get { return this.items; } } protected Bitmap OffScreen { get { return this.offScreen; } } protected VScrollBar VScrollBar { get { return this.vs; } } public event EventHandler SelectedIndexChanged; // Raise the SelectedIndexChanged event protected virtual void OnSelectedIndexChanged(EventArgs e) { if(this.SelectedIndexChanged != null) this.SelectedIndexChanged(this, e); } protected override void OnMouseDown(MouseEventArgs e) { this.SelectedIndex = this.vs.Value + (e.Y / this.ItemHeight); // Invalidate the control so we can draw the item as selected. this.Refresh(); } // Get or set index of selected item. public int SelectedIndex { get { return this.selectedIndex; } set { this.selectedIndex = value; if (this.SelectedIndexChanged != null) this.SelectedIndexChanged(this, EventArgs.Empty); } } protected void ScrollValueChanged(object o, EventArgs e) { this.Refresh(); } protected virtual int ItemHeight { get { return this.itemHeight; } set { this.itemHeight = value; } } // If the requested index is before the first visible index then set the // first item to be the requested index. If it is after the last visible // index, then set the last visible index to be the requested index. public void EnsureVisible(int index) { if(index < this.vs.Value) { this.vs.Value = index; this.Refresh(); } else if(index >= this.vs.Value + this.DrawCount) { this.vs.Value = index - this.DrawCount + 1; this.Refresh(); } } // Need to set focus to the control when it // is clicked so that keyboard events occur. protected override void OnClick(EventArgs e) { this.Focus(); base.OnClick(e); } // Selected item moves when you use the keyboard up/down keys. protected override void OnKeyDown(KeyEventArgs e) { switch(e.KeyCode) { case Keys.Down: if(this.SelectedIndex < this.vs.Maximum) { EnsureVisible(++this.SelectedIndex); this.Refresh(); } break; case Keys.Up: if(this.SelectedIndex > this.vs.Minimum) { EnsureVisible(--this.SelectedIndex); this.Refresh(); } break; case Keys.PageDown: this.SelectedIndex = Math.Min(this.vs.Maximum, this.SelectedIndex + this.DrawCount); EnsureVisible(this.SelectedIndex); this.Refresh(); break; case Keys.PageUp: this.SelectedIndex = Math.Max(this.vs.Minimum, this.SelectedIndex - this.DrawCount); EnsureVisible(this.SelectedIndex); this.Refresh(); break; case Keys.Home: this.SelectedIndex = 0; EnsureVisible(this.SelectedIndex); this.Refresh(); break; case Keys.End: this.SelectedIndex = this.items.Count - 1; EnsureVisible(this.SelectedIndex); this.Refresh(); break; } base.OnKeyDown(e); } // Calculate how many items we can draw given the height of the control. protected int DrawCount { get { if(this.vs.Value + this.vs.LargeChange > this.vs.Maximum) return this.vs.Maximum - this.vs.Value + 1; else return this.vs.LargeChange; } } protected override void OnResize(EventArgs e) { int viewableItemCount = this.ClientSize.Height / this.ItemHeight; this.vs.Bounds = new Rectangle(this.ClientSize.Width - SCROLL_WIDTH, 0, SCROLL_WIDTH, this.ClientSize.Height); // Determine if scrollbars are needed if(this.items.Count > viewableItemCount) { this.vs.Visible = true; this.vs.LargeChange = viewableItemCount; this.offScreen = new Bitmap(this.ClientSize.Width - SCROLL_WIDTH - 1, this.ClientSize.Height - 2); } else { this.vs.Visible = false; this.vs.LargeChange = this.items.Count; this.offScreen = new Bitmap(this.ClientSize.Width - 1, this.ClientSize.Height - 2); } this.vs.Maximum = this.items.Count - 1; } }
Добавьте в проект класс FontListBox (являющийся реализацией класса OwnerDrawnListBox).
' Derive an implementation of the ' OwnerDrawnListBox class Class FontListBox Inherits OwnerDrawnListBox Private Const FONT_SIZE As Integer = 10 Private Const DRAW_OFFSET As Integer = 5 Public Sub New() ' Determine what the item height should be ' by adding 30% padding after measuring ' the letter A with the selected font. Dim g As Graphics = Me.CreateGraphics() Me.ItemHeight = Fix(g.MeasureString("A", Me.Font).Height * 1.3) g.Dispose() End Sub ' Return the name of the selected font. Public ReadOnly Property SelectedFaceName() As String Get Return CStr(Me.Items(Me.SelectedIndex)) End Get End Property ' Determine what the text color should be ' for the selected item drawn as highlighted Function CalcTextColor(ByVal backgroundColor As Color) As Color If backgroundColor.Equals(Color.Empty) Then Return Color.Black End If Dim sum As Integer = backgroundColor.R + backgroundColor.G + backgroundColor.B If sum > 256 Then Return Color.Black Else Return Color.White End If End Function Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim font As Font Dim fontColor As Color ' The base class contains a bitmap, offScreen, for constructing ' the control and is rendered when all items are populated. ' This technique prevents flicker. Dim gOffScreen As Graphics = Graphics.FromImage(Me.OffScreen) gOffScreen.FillRectangle(New SolidBrush(Me.BackColor), Me.ClientRectangle) Dim itemTop As Integer = 0 ' Draw the fonts in the list. Dim n As Integer For n = Me.VScrollBar.Value To (Me.VScrollBar.Value + DrawCount) - 1 ' If the font name contains "dings" it needs to be displayed ' in the list box with a readable font with the default font. If CStr(Me.Items(n)).ToLower().IndexOf("dings") <> -1 Then font = New Font(Me.Font.Name, FONT_SIZE, FontStyle.Regular) Else font = New Font(CStr(Me.Items(n)), FONT_SIZE, FontStyle.Regular) End If ' Draw the selected item to appear highlighted If n = Me.SelectedIndex Then gOffScreen.FillRectangle(New SolidBrush(SystemColors.Highlight), 1, itemTop + 1, Me.ClientSize.Width - IIf(Me.VScrollBar.Visible, Me.VScrollBar.Width, 2), Me.ItemHeight) ' If the scroll bar is visible, subtract the scrollbar width ' otherwise subtract 2 for the width of the rectangle fontColor = CalcTextColor(SystemColors.Highlight) Else fontColor = Me.ForeColor End If ' Draw the item gOffScreen.DrawString(CStr(Me.Items(n)), font, New SolidBrush(fontColor), DRAW_OFFSET, itemTop) itemTop += Me.ItemHeight Next n ' Draw the list box e.Graphics.DrawImage(Me.OffScreen, 1, 1) gOffScreen.Dispose() End Sub ' Draws the external border around the control. Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs) e.Graphics.DrawRectangle(New Pen(Color.Black), 0, 0, Me.ClientSize.Width - 1, Me.ClientSize.Height - 1) End Sub End Class
// Derive an implementation of the // OwnerDrawnListBox class class FontListBox : OwnerDrawnListBox { const int FONT_SIZE = 10; const int DRAW_OFFSET = 5; public FontListBox() { // Determine what the item height should be // by adding 30% padding after measuring // the letter A with the selected font. Graphics g = this.CreateGraphics(); this.ItemHeight = (int)(g.MeasureString("A", this.Font).Height * 1.3); g.Dispose(); } // Return the name of the selected font. public string SelectedFaceName { get { return (string)this.Items[this.SelectedIndex]; } } // Determine what the text color should be // for the selected item drawn as highlighted Color CalcTextColor(Color backgroundColor) { if(backgroundColor.Equals(Color.Empty)) return Color.Black; int sum = backgroundColor.R + backgroundColor.G + backgroundColor.B; if(sum > 256) return Color.Black; else return Color.White; } protected override void OnPaint(PaintEventArgs e) { Font font; Color fontColor; // The base class contains a bitmap, offScreen, for constructing // the control and is rendered when all items are populated. // This technique prevents flicker. Graphics gOffScreen = Graphics.FromImage(this.OffScreen); gOffScreen.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle); int itemTop = 0; // Draw the fonts in the list. for(int n = this.VScrollBar.Value; n < this.VScrollBar.Value + DrawCount; n++) { // If the font name contains "dings" it needs to be displayed // in the list box with a readable font with the default font. if(((string)this.Items[n]).ToLower().IndexOf("dings") != -1) font = new Font(this.Font.Name, FONT_SIZE, FontStyle.Regular); else font = new Font((string)this.Items[n], FONT_SIZE, FontStyle.Regular); // Draw the selected item to appear highlighted if(n == this.SelectedIndex) { gOffScreen.FillRectangle(new SolidBrush(SystemColors.Highlight), 1, itemTop + 1, // If the scroll bar is visible, subtract the scrollbar width // otherwise subtract 2 for the width of the rectangle this.ClientSize.Width - (this.VScrollBar.Visible ? this.VScrollBar.Width : 2), this.ItemHeight); fontColor = CalcTextColor(SystemColors.Highlight); } else fontColor = this.ForeColor; // Draw the item gOffScreen.DrawString((string)this.Items[n], font, new SolidBrush(fontColor), DRAW_OFFSET, itemTop); itemTop += this.ItemHeight; } // Draw the list box e.Graphics.DrawImage(this.OffScreen, 1, 1); gOffScreen.Dispose(); } // Draws the external border around the control. protected override void OnPaintBackground(PaintEventArgs e) { e.Graphics.DrawRectangle(new Pen(Color.Black), 0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1); } }
В форме добавьте два элемента управления Label и назовите их descLabel и sampleLabel; последний из них отображает образец шрифта, выбранного в классе FontListBox.
Создайте для формы экземпляр класса FontListBox.
Добавьте для формы в конструкторе события Load следующий код.
' Create a new instance of FontListBox. myListBox.Parent = Me ' Configure the event handler for the SelectedIndexChanged event. AddHandler myListBox.SelectedIndexChanged, AddressOf Me.SelectedIndexChanged ' Draw the bounds of the FontListBox. myListBox.Bounds = New Rectangle(5, 5, 150, 100) ' Add fonts to list, repeat the list 4 times ' so that the scroll bar can be demonstrated. Dim n As Integer For n = 0 To 3 myListBox.Items.Add("Bookdings") myListBox.Items.Add("Courier New") myListBox.Items.Add("Frutiger Linotype") myListBox.Items.Add("Tahoma") Next n ' Labels to show the selected font from the list box. Assumes ' instances of these labels have been declared for the form. descLabel.Parent = Me descLabel.Text = "Font Sample:" descLabel.Bounds = New Rectangle(10, myListBox.Bottom + 20, Me.ClientSize.Width - 10, 30) ' Assumes an instance sampleLabel is declared for the form. sampleLabel.Parent = Me sampleLabel.Bounds = New Rectangle(10, descLabel.Bottom, Me.ClientSize.Width - 10, 30) sampleLabel.Text = "AaBbCc 123"
// Create a new instance of FontListBox. box = new FontListBox(); box.Parent = this; // Configure the event handler for the SelectedIndexChanged event. box.SelectedIndexChanged += new EventHandler(this.SelectedIndexChanged); // Draw the bounds of the FontListBox. box.Bounds = new Rectangle(5, 5, 150, 100); // Add fonts to list, repeat the list 4 times // so that the scroll bar can be demonstrated. for(int n = 0; n < 4; n++) { box.Items.Add("Bookdings"); box.Items.Add("Courier New"); box.Items.Add("Frutiger Linotype"); box.Items.Add("Tahoma"); } // Labels to show the selected font and a sample. Label desc = new Label(); desc.Parent = this; desc.Text = "Font Sample:"; desc.Bounds = new Rectangle(10, box.Bottom + 20, this.ClientSize.Width - 10, 30); sample = new Label(); sample.Parent = this; sample.Bounds = new Rectangle(10, desc.Bottom, this.ClientSize.Width - 10, 30); sample.Text = "AaBbCc 123";
Добавьте код для события SelectedChangedEvent элемента управления FontListBox, который отображает выбранный шрифт в элементе управления sampleLabel.
' Event handler for SelectedIndexChanged to ' display a sample of the selected font. ' Assumes that an instace of myListBox has been declared for the form. Friend Sub SelectedIndexChanged(ByVal o As Object, ByVal e As EventArgs) sampleLabel.Font = New Font(myListBox.SelectedFaceName, 12, FontStyle.Regular) End Sub
// Event handler for SelectedIndexChanged to // display a sample of the selected font. private void SelectedIndexChanged(object o, EventArgs e) { sample.Font = new Font(box.SelectedFaceName, 12, FontStyle.Regular); }
Компиляция кода
Для этого примера требуются ссылки на следующие пространства имен: