방법: 소유자가 그린 목록 상자 만들기

업데이트: 2007년 11월

.NET Compact Framework를 사용하여 소유자가 그린 목록 상자를 만들 수 있으나, 전체 .NET Framework를 사용하여 만드는 것만큼 쉽지는 않습니다. .NET Compact Framework에서는 목록 상자에 대한 DrawMode, DrawItem, 기타 그리기 멤버 및 기타 컨트롤 등을 지원하지 않지만 사용자가 해당 기능을 프로그래밍할 수 있습니다. 이 예제에서는 사용자 지정 컨트롤 클래스를 제공하여 소유자가 그린 목록 상자를 만들고 해당 컨트롤을 글꼴 선택용 목록 상자로 구현합니다.

이 샘플에는 FontListBox가 파생된 기본 클래스인 OwnerDrawnListBox가 포함되어 있습니다. OwnerDrawnListBox의 핵심 기능에는 다음이 포함됩니다.

  • 세로 스크롤 막대, 항목을 포함하는 배열, 목록 상자를 생성하기 위한 비트맵 등을 제공합니다.

  • 항목 높이 속성을 제공하고, 컨트롤 높이가 지정된 경우 그릴 수 있는 항목 수를 결정합니다.

  • 현재 스크롤 막대, 항목 높이 및 마우스 포인터 좌표 값에 의해 선택된 항목을 결정하는 SelectedIndex 속성을 제공합니다.

  • SelectedIndexChanged 이벤트를 제공합니다.

  • 항목이 표시될 수 있도록 합니다.

  • 필요하지 않으면 스크롤 막대를 제거합니다.

  • 위로 이동 및 아래로 이동 키를 사용하여 키보드 작동을 제공합니다.

FontListBox 클래스는 OwnerDrawnListBox에서 상속합니다. 여기에는 다음과 같은 구현과 기술이 포함됩니다.

  • 적절한 항목 높이를 결정합니다.

  • 재정의된 OnPaint 메서드를 사용하여 항목을 그리고 선택된 항목을 강조 표시합니다. 깜박임을 방지하기 위해 이 메서드는 컨트롤의 그래픽 특징이 모두 그려질 때까지 해당 컨트롤을 표시하지 않습니다.

  • 선택된 글꼴 이름을 포함하는 SelectedFaceName 속성을 제공합니다.

DrawFontList 폼은 다음 작업을 수행합니다.

  • FontListBox의 새 인스턴스를 만들고 컨트롤 컬렉션에 추가합니다.

  • SelectedIndexChanged 이벤트에 대한 이벤트 처리기를 추가합니다.

  • FontListBox를 글꼴 목록으로 채웁니다.

  • 선택된 글꼴의 샘플을 표시하기 위한 레이블을 제공합니다.

소유자가 그린 목록 상자를 만들려면

  1. 프로젝트에 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;
        }
    }
    
  2. OwnerDrawnListBox)의 구현인 FontListBox 클래스를 프로젝트에 추가합니다.

    ' 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);
        }
    }
    
  3. 폼에서 두 개의 Label 컨트롤을 추가하고 descLabel과 sampleLabel로 명명합니다. 후자는 FontListBox에서 선택된 글꼴 예를 보여 줍니다.

  4. 폼에 대한 FontListBox의 인스턴스를 만듭니다.

  5. 다음 코드를 폼에 대한 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";
    
  6. 선택된 글꼴을 sampleLabel 컨트롤에 표시하는 코드를 FontListBox 컨트롤의 SelectedChangedEvent 이벤트에 추가합니다.

    ' 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);
    }
    

코드 컴파일

이 예제에는 다음과 같은 네임스페이스에 대한 참조가 필요합니다.

참고 항목

개념

사용자 지정 컨트롤 개발

.NET Compact Framework 방법 항목