I need to extend a combobox to disable some listitems (i.e splits - "----------").
I reckon some APIs will be involved, and it could get complicated. That'll not put me off though.
If anyone with usefull info on the subject could post it, I'd really appreciate it.
Thanks
Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning. - Rich Cook
I'm really not sure if there is a definite way to do that, if I'm wrong ( which I doubt, I apolgise )
I did some thinking. Let me explain.
Why not show a visible cue to the user that a certain item is selectable and a certain item is not selectable. For example, an item which contains your split ( "-----" ) will not be selectable, so, what I did in my example here is to make all items which is equals to "-----" red, and all normal items black. Another thing to be considered is some logic about what to do if an item "-----" is selected. You can perhaps cancel all the neceassary things / exit the sub, or whatever. In my example, I just reset the Selected index to 0.
Here is my code :
Code:
'Add one combobox to the form
Public Class Form1
Private ComboItemArr() As String = {"One", "-----", "Two", "-----", _
"Three", "-----", "Four", "-----", "Five", "-----"} 'Array for combobox
Protected Sub Combobox1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ComboBox1.DrawItem
If e.Index < 0 Then
e.DrawBackground()
e.DrawFocusRectangle()
Exit Sub
End If
Dim EnabledColor As Color = Color.White
' get a square using the bounds height
Dim SizeRect As Rectangle = New Rectangle(2, e.Bounds.Top + 2, e.Bounds.Width, e.Bounds.Height - 2)
Dim ComboBrush As Brush
' call these methods first
e.DrawBackground()
e.DrawFocusRectangle()
' change brush color if item is selected
If e.State = Windows.Forms.DrawItemState.Selected Then
ComboBrush = Brushes.White
Else
ComboBrush = Brushes.Black
End If
For i As Integer = 0 To 9
If ComboItemArr(e.Index).ToString = "-----" Then
e.Graphics.DrawRectangle(New Pen(Color.Red), SizeRect)
e.Graphics.FillRectangle(New SolidBrush(Color.White), SizeRect)
e.Graphics.DrawString(ComboItemArr(e.Index), ComboBox1.Font, Brushes.Red, e.Bounds.Height + 5, ((e.Bounds.Height - ComboBox1.Font.Height) \ 2) + e.Bounds.Top)
Else
' draw a rectangle and fill it
e.Graphics.DrawRectangle(New Pen(Color.White), SizeRect)
e.Graphics.FillRectangle(New SolidBrush(Color.White), SizeRect)
e.Graphics.DrawString(ComboItemArr(e.Index), ComboBox1.Font, Brushes.Black, e.Bounds.Height + 5, ((e.Bounds.Height - ComboBox1.Font.Height) \ 2) + e.Bounds.Top)
End If
Next
' UNCOMMENT THIS, If you want to draw a border as well
'SizeRect.Inflate(1, 1)
'e.Graphics.DrawRectangle(Pens.Green, SizeRect)
End Sub
Protected Sub Combobox1_MeasureItem(ByVal sender As Object, ByVal e As System.Windows.Forms.MeasureItemEventArgs) Handles ComboBox1.MeasureItem
Dim myRandom As New Random
e.ItemHeight = myRandom.Next(20, 40)
End Sub
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
For i As Integer = 0 To 9
ComboBox1.Items.Add(ComboItemArr(i))
Next i
End Sub
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
If ComboBox1.Items(ComboBox1.SelectedIndex).ToString = "-----" Then
ComboBox1.SelectedIndex = 0
Exit Sub
Else
MessageBox.Show(ComboBox1.SelectedItem.ToString())
End If
End Sub
End Class
Because this is an interesting topic, I decided to attach my project as well.
Have a look at it, maybe it's all you'll need.
I hope my post was helpful.
Last edited by HanneSThEGreaT; December 19th, 2008 at 03:18 AM.
Imports System.ComponentModelPublic Class MyCombo
Inherits System.Windows.Forms.ComboBox
#Region "Declaration:Enum"
Enum ListDisplayStyle
eSystem
eIcon
eExtended
End Enum
#End Region#Region "Declaration:Variables"
Private _ImageList As ImageList
Private _DropDownListStyle As ListDisplayStyle
Private _TempText As String
#End Region
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
DrawMode = DrawMode.OwnerDrawFixed
'Add any initialization after the InitializeComponent() call
End Sub
'UserControl overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
End Sub
#End Region
#Region "Implementation:Properties"
Public Property DropDownListStyle() As ListDisplayStyle
Get
Return _DropDownListStyle
End Get
Set(ByVal Value As ListDisplayStyle)
_DropDownListStyle = Value
Value = Nothing
End Set
End Property
#End Region
#Region "Implementation:Methods"
Public Sub AddItem(ByVal ItemText As String, Optional ByVal ItemIcon As Integer = -1, Optional ByVal ItemFont As Font = Nothing)
If ItemFont Is Nothing Then
ItemFont = Me.Font
End If
MyBase.Items.Add(New ComboItem(ItemText, ItemIcon, ItemFont))
End Sub
#End Region
#Region "Implementation:Overrides"
Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
Dim oComboItem As ComboItem
Dim iLeft As Integer = e.Bounds.Left
Dim rctIconRectangle As Rectangle
Dim colorForeGround As Color
Dim iTop As Integer = e.Bounds.Top
Try
If e.Index = -1 Then Return
oComboItem = CType(Me.Items(e.Index), ComboItem)
If oComboItem.Enabled And oComboItem.Text.Substring(0, 1) <> "-" Then
e.DrawFocusRectangle()
e.DrawBackground()
colorForeGround = e.ForeColor
Else
colorForeGround = SystemColors.GrayText
End If
If _ImageList.ImageSize.Height > Me.ItemHeight Then
rctIconRectangle = New Rectangle(e.Bounds.Left, e.Bounds.Top, Me.ItemHeight, Me.ItemHeight)
Else
rctIconRectangle = New Rectangle(New Point(e.Bounds.Left, e.Bounds.Top), _ImageList.ImageSize)
End If
If _DropDownListStyle = ListDisplayStyle.eExtended Then
Dim rctBar As Rectangle = rctIconRectangle
rctBar.Width += 4
rctBar.Height += 4
e.Graphics.FillRectangle(New SolidBrush(SystemColors.Control), rctBar)
iLeft += rctBar.Width
_ImageList.Draw(e.Graphics, (rctBar.Width - rctIconRectangle.Width) / 2, rctIconRectangle.Top + (rctBar.Height - rctIconRectangle.Height) / 2, rctIconRectangle.Width, rctIconRectangle.Height, oComboItem.IconIndex)
End If
If _DropDownListStyle = ListDisplayStyle.eIcon Then
If oComboItem.IconIndex > -1 Then
iLeft += rctIconRectangle.Width
_ImageList.Draw(e.Graphics, rctIconRectangle.Left, rctIconRectangle.Top, rctIconRectangle.Width, rctIconRectangle.Height, oComboItem.IconIndex)
End If
End If
If oComboItem.Text.Substring(0, 1) = "-" Then
e.Graphics.DrawLine(New Pen(Me.ForeColor), 0, e.Bounds.Top + CInt(Me.ItemHeight / 2), Me.DropDownWidth, e.Bounds.Top + CInt(Me.ItemHeight / 2))
Else
e.Graphics.DrawString(oComboItem.Text, oComboItem.Font, New SolidBrush(colorForeGround), iLeft, iTop)
End If
Catch
If oComboItem.Text.Substring(0, 1) = "-" Then
e.Graphics.DrawLine(New Pen(Me.ForeColor), 0, e.Bounds.Top + CInt(Me.ItemHeight / 2), Me.DropDownWidth, e.Bounds.Top + CInt(Me.ItemHeight / 2))
Else
e.Graphics.DrawString(oComboItem.Text, oComboItem.Font, New SolidBrush(e.ForeColor), iLeft, iTop)
End If
End Try
MyBase.OnDrawItem(e)
oComboItem = Nothing
rctIconRectangle = Nothing
End Sub
Protected Overrides Sub OnMeasureItem(ByVal e As System.Windows.Forms.MeasureItemEventArgs)
MyBase.OnMeasureItem(e)
End Sub
Protected Overrides Sub OnDropDown(ByVal e As System.EventArgs)
_TempText = Me.Text
End Sub
Protected Overrides Sub OnSelectionChangeCommitted(ByVal e As System.EventArgs)
MyBase.OnSelectionChangeCommitted(e)
If Not Me.SelectedItem.Enabled Or SelectedItem.Text.substring(0, 1) = "-" Then
Me.Text = _TempText
End If
End Sub
#End Region
#Region "Definition:Class_ComboItem"
Private Class ComboItem
#Region "Declaration:Variable"
Private _Text As String
Private _Font As Font
Private _IconIndex As Integer = -1
Private _IconFile As String
Private _Icon As Icon
Private _Enabled As Boolean = True
#End Region
#Region "Implementation:Properties"
Public Sub New(ByVal ItemText As String, ByVal ItemIconIndex As Integer, ByVal ItemFont As Font)
_Text = ItemText
_IconIndex = ItemIconIndex
_Font = ItemFont
End Sub
Public Property Text() As String
Get
Return _Text
End Get
Set(ByVal Value As String)
_Text = Value
End Set
End Property
Public Property Font() As Font
Get
Return _Font
End Get
Set(ByVal Value As Font)
_Font = Value
End Set
End Property
Public Property IconIndex() As Integer
Get
Return _IconIndex
End Get
Set(ByVal Value As Integer)
_IconIndex = Value
End Set
End Property
Public Property IconFile() As String
Get
Return _IconFile
End Get
Set(ByVal Value As String)
_IconFile = Value
End Set
End Property
Public Property Icon() As Icon
Get
Return _Icon
End Get
Set(ByVal Value As Icon)
_Icon = Value
End Set
End Property
Public Property Enabled() As Boolean
Get
Return _Enabled
End Get
Set(ByVal Value As Boolean)
_Enabled = Value
End Set
End Property
Public Overrides Function ToString() As String
Return _Text
End Function
#End Region
End Class
#End Region
End Class
Your code is also very helpful Hannes. I think now, we could build a super dooper combo, so far hitherto unknown to the people in this area
Last edited by HairyMonkeyMan; November 27th, 2007 at 11:35 AM.
Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning. - Rich Cook
Hey guys I found this topic interesting too.
I'll try to come up with a general API example.
Hey Hannes, are you setting the selected index back to 0, or -1?
I guess it doesn't matter at that point, but I just wanted to be sure of what you are saying.
Do you simply handle the error if it's -1?
I'm using version 2008, so maybe it's a little different.
Hey thanx guys, just glad I could help a little
In my example, I set the SelectedIndex back to 0, but I think best would be to set it to -1, then catch the error & exit the sub. The whole reasoning behind this, is because when someone selects an unwanted item ( which we don't want ), it still gets displayed inside the textbox of the combo, which it mustn't.
If I understand your problem correctly, you just need to be able to set items to be disabled so that they are not selectable?
I had a similar problem a few days ago, so I wrote my own combobox that allows you to do just that. I am planning on writing up an article and submitting it to codeproject.com, but I can send you the .dll for it if you would like. I am about 98% complete (still doing testing and tweaking a few things) but it is quite usable.
Here are some helpful API demos, for generally dealing with the combobox on almost ANY application.
Since there are many paths of logic possible, I've left it open to the readers to come up with different ideas.
The ListBox constants start with the prefix LB_ but I've only included CB_ constants here.
It should be easy to disable a combo item, by placing a crude mouse hook, or message hook, in combination with one or more examples below.
Code:
Public Class Form1
' Const CB_GETEDITSEL As Int32 = 320 'Combobox
' Const CB_LIMITTEXT As Int32 = 321
' Const CB_SETEDITSEL As Int32 = 322
Const CB_ADDSTRING As Int32 = 323
Const CB_DELETESTRING As Int32 = 324
' Const CB_DIR As Int32 = 325
Const CB_GETCOUNT As Int32 = 326
Const CB_GETCURSEL As Int32 = 327
Const CB_GETLBTEXT As Int32 = 328
Const CB_GETLBTEXTLEN As Int32 = 329
Const CB_INSERTSTRING As Int32 = 330
Const CB_RESETCONTENT As Int32 = 331
Const CB_FINDSTRING As Int32 = 332
Const CB_SELECTSTRING As Int32 = 333
Const CB_SETCURSEL As Int32 = 334
Const CB_SHOWDROPDOWN As Int32 = 335
'Const CB_GETITEMDATA As Int32 = 336
'Const CB_SETITEMDATA As Int32 = 337
'Const CB_GETDROPPEDCONTROLRECT As Int32 = 338
'Const CB_SETITEMHEIGHT As Int32 = 339
'Const CB_GETITEMHEIGHT As Int32 = 340
'Const CB_SETEXTENDEDUI As Int32 = 341
'Const CB_GETEXTENDEDUI As Int32 = 342
Const CB_GETDROPPEDSTATE As Int32 = 343
Const CB_FINDSTRINGEXACT As Int32 = 344
'Const CB_SETLOCALE As Int32 = 345
'Const CB_GETLOCALE As Int32 = 346
'Const CB_GETTOPINDEX As Int32 = 347
'Const CB_SETTOPINDEX As Int32 = 348
'Const CB_GETHORIZONTALEXTENT As Int32 = 349
'Const CB_SETHORIZONTALEXTENT As Int32 = 350
'Const CB_GETDROPPEDWIDTH As Int32 = 351
'Const CB_SETDROPPEDWIDTH As Int32 = 352
'Const CB_INITSTORAGE As Int32 = 354
'Const CB_MSGMAX As Int32 = 354
Private Declare Function apiSendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As String) As Int32
Private Declare Function apiFindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Int32 'Retrieves the handle to the top-level window whose class name and window name match the specified strings. This function does not search child windows.
Private Declare Function apiFindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Int32, ByVal hWnd2 As Int32, ByVal lpsz1 As String, ByVal lpsz2 As String) As Int32 'The FindWindowEx function retrieves the handle to a window whose class name and window name match the specified strings. The function searches child windows, beginning with the one following the given child window.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim hwnd As Int32 = apiFindWindow(Nothing, Me.Text)
'You'll need to get the class name of your combobox first to insert below. Here it is "WindowsForms10.COMBOBOX.app.0.378734a", but this will vary from simply "COMBOBOX".
Dim cwnd As Int32 = apiFindWindowEx(hwnd, 0, "WindowsForms10.COMBOBOX.app.0.378734a", Nothing)
' Dim ewnd As Int32 = apiFindWindowEx(cwnd, 0, "Edit", Nothing) 'The editable window
'simply returns the item count
MessageBox.Show(apiSendMessage(cwnd, CB_GETCOUNT, 0, Nothing))
'Finds the specified string item(lParam), and returns the item's index. CB_FINDSTRING similar
'MessageBox.Show(apiSendMessage(cwnd, CB_FINDSTRINGEXACT, 0, "two"))
'Returns the index of the currently selected item by the user
'MessageBox.Show(apiSendMessage(cwnd, CB_GETCURSEL, 0, Nothing))
'Selects a specified string item position(wParam) > 0, and returns nonzero if success
'MessageBox.Show(apiSendMessage(cwnd, CB_SETCURSEL, 2, Nothing))
'Selects a specified string item(lParam), and returns it's index
'MessageBox.Show(apiSendMessage(cwnd, CB_SELECTSTRING, 0, "three"))
'Add a string item at the specified position(wParam), and returns the total item count
'MessageBox.Show(apiSendMessage(cwnd, CB_ADDSTRING, 0, "New item"))
'inserts a string item at the specified position(wParam) > 0, and returns non zero upon success.
'MessageBox.Show(apiSendMessage(cwnd, CB_INSERTSTRING, 1, "inserted"))
'deletes a string item at the specified position(wParam), and returns the total item count
'MessageBox.Show(apiSendMessage(cwnd, CB_DELETESTRING, 0, Nothing))
'returns the index of the specified string item(lParam)
' MessageBox.Show(apiSendMessage(cwnd, CB_GETLBTEXT, 0, "two"))
'returns the length of the specified string item position(wParam).
'MessageBox.Show(apiSendMessage(cwnd, CB_GETLBTEXTLEN, 4, Nothing))
'Drop the combobox down, making it visible to the user, and returns nonzero upon success.
'apiSendMessage(cwnd, CB_SHOWDROPDOWN, 1, Nothing)
'Drop the combobox down, and see if it's dropped down or not
'apiSendMessage(cwnd, CB_SHOWDROPDOWN, 1, Nothing)
'MessageBox.Show(apiSendMessage(cwnd, CB_GETDROPPEDSTATE, 0, Nothing)) 'return nonzero if dropped.
'reset all item content to empty string, returns nonzero if success
'MessageBox.Show(apiSendMessage(cwnd, CB_RESETCONTENT, 0, Nothing))
End Sub
End Class
Here is the project in a zip below:
Last edited by TT(n); November 29th, 2007 at 01:21 AM.
I had a similar problem a few days ago, so I wrote my own combobox that allows you to do just that. I am planning on writing up an article and submitting it to codeproject.com, but I can send you the .dll for it if you would like. I am about 98% complete (still doing testing and tweaking a few things) but it is quite usable.
Welcome to the forums!
Just a note, you can also submit articles to CodeGuru, and an article based on this topic will surely be welcomed
Here's more details : http://www.codeguru.com/edit-article.php
Originally Posted by TT(n)
Here are some helpful API demos, for generally dealing with the combobox on almost ANY application.
Since there are many paths of logic possible, I've left it open to the readers to come up with different ideas.
The ListBox constants start with the prefix LB_ but I've only included CB_ constants here.
It should be easy to disable a combo item, by placing a crude mouse hook, or message hook, in combination with one or more examples below.
Welcome to the forums!
Just a note, you can also submit articles to CodeGuru, and an article based on this topic will surely be welcomed
Here's more details : http://www.codeguru.com/edit-article.php
Thanks, and thanks =-). I am new to this site, didn't know you could submit articles. I believe I have finished up the project, so I will start writing up the article soon. Probably on lunch today and over the weekend, hopefully i'll be able to get it done by the beginning of next week.
* The Best Reasons to Target Windows 8
Learn some of the best reasons why you should seriously consider bringing your Android mobile development expertise to bear on the Windows 8 platform.