ComboBox は比較的よく使うコントロールなので、エントリも短時間で済むだろうと思っていたのですが...
こんな面白いもの(拡張 ComboBox の作成)を見つけたので、
VB のコードに変換してみたり、使ってみたりと、なんじゃかんじゃやっていたら予想より時間がかかってしまいました。
拡張 ComboBox のサンプルコードを見てみると、デザイナ上で Items にどわーっとリストを記述しています。
なので、HandleCreated 時にリスト幅の計算をやったりする処理が走って問題ないとおもうんですが、
実際に業務で使う場合は、動的にリストを生成する方が多いと思います。
なので、私も DB から動的な値とってきて DataSource にセットして、DisplayMemberChanged 時にとりあえずやっていますが、
ほかに適切なイベントがあるやもしれません。
ひょっとしたら、UpdateDropDownWidth を外部に公開したほうが良いと思う方もいるやもしれません。
あと、どぼん!さんのページでこのようなものを見つけました。
ComboBoxが上下矢印キーで項目を変更できないようにする
そういう要望は結構あるんでしょか...
■参考文献
ComboBox クラス
AutoCompleteStringCollection クラス
ComboBoxが上下矢印キーで項目を変更できないようにする
拡張 ComboBox の作成
■実行画像
ComboBox5 ドロップダウン時
BetterComboBox ドロップダウン時
Imports System.Runtime.InteropServices 'Better ComboBox の為
Public Class ComboBoxTest
Private Sub ComboBoxTest_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'' DataSource を指定する
Dim dt As DataTable = New DataTable
dt.Columns.Add("ID", GetType(Integer))
dt.Columns.Add("NAME", GetType(String))
dt.Rows.Add(New Object() {1, "ふじこ"})
dt.Rows.Add(New Object() {2, "エデン"})
dt.Rows.Add(New Object() {3, "オット"})
Me.ComboBox1.DataSource = dt
Me.ComboBox1.DisplayMember = dt.Columns(1).ColumnName
Me.ComboBox1.ValueMember = dt.Columns(0).ColumnName
' 未選択状態にする
Me.ComboBox1.SelectedIndex = -1
'' Items を追加する
' ユーザーの編集を拒否する
Me.ComboBox2.DropDownStyle = ComboBoxStyle.DropDownList
Me.ComboBox2.Items.Clear()
Me.ComboBox2.Items.Add("わんくま")
Me.ComboBox2.Items.Add("wankuma")
Me.ComboBox2.Items.Add("ワンクマ")
' wankuma を選択状態にする
Me.ComboBox2.SelectedItem = "wankuma"
'' オートコンプリートを設定する(FileSystem)
Me.ComboBox4.AutoCompleteMode = AutoCompleteMode.Suggest
Me.ComboBox4.AutoCompleteSource = Windows.Forms.AutoCompleteSource.FileSystem
'' オートコンプリートを設定する(カスタムソース)
Me.ComboBox4.Items.Clear()
Me.ComboBox4.Items.Add("shiba")
Me.ComboBox4.Items.Add("dachshund")
Me.ComboBox4.Items.Add("otto")
Me.ComboBox4.AutoCompleteMode = AutoCompleteMode.Suggest
Me.ComboBox4.AutoCompleteSource = Windows.Forms.AutoCompleteSource.CustomSource
Dim autoCompleteSource As AutoCompleteStringCollection = New AutoCompleteStringCollection
autoCompleteSource.Add("shiba")
autoCompleteSource.Add("dachshund")
autoCompleteSource.Add("otto")
Me.ComboBox4.AutoCompleteCustomSource = autoCompleteSource
'' BetterComboBox 比較用
Const CONNECTION_STRING As String = "Data Source=(local);Initial Catalog=NorthWind;Integrated Security=SSPI;"
Dim source As DataTable = New DataTable
Dim connection As SqlClient.SqlConnection = New SqlClient.SqlConnection(CONNECTION_STRING)
Try
connection.Open()
Dim da As SqlClient.SqlDataAdapter = New SqlClient.SqlDataAdapter
da.SelectCommand = New SqlClient.SqlCommand
da.SelectCommand.Connection = connection
da.SelectCommand.CommandText = "SELECT Address FROM Customers"
da.Fill(source)
Finally
If Not connection Is Nothing Then
connection.Close()
connection.Dispose()
End If
End Try
' 普通の ComboBox
Me.ComboBox5.Items.Clear()
Me.ComboBox5.DataSource = source
Me.ComboBox5.DisplayMember = "Address"
' BetterComboBox 使用
Dim bComboBox As BetterComboBox = New BetterComboBox
Me.Controls.Add(bComboBox)
bComboBox.Location = New Point(12, 140)
bComboBox.DataSource = source
bComboBox.DisplayMember = "Address"
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'' ComboBox4 の AutoCompleteCustomSource に 値を追加する。
Me.ComboBox4.Items.Add(Me.ComboBox4.Text)
Me.ComboBox4.AutoCompleteCustomSource.Add(Me.ComboBox4.Text)
End Sub
End Class
''' <summary>
''' BetterComboBox
''' </summary>
''' <remarks>
''' http://www.microsoft.com/japan/msdn/net/winforms/bettercombobox.aspx
''' </remarks>
Public Class BetterComboBox
Inherits ComboBox
#Region "SetWindowPos"
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function SetWindowPos( _
ByVal hWnd As IntPtr, _
ByVal hWndInsertAfter As IntPtr, _
ByVal x As Integer, _
ByVal y As Integer, _
ByVal cx As Integer, _
ByVal cy As Integer, _
ByVal uFlags As Integer) _
As Boolean
End Function
' サイズ変更なし
Private Const SWP_NOSIZE As Integer = &H1
' リストが描画されようとしている時の親コンテナへのメッセージ
Private Const WM_CTLCOLORLISTBOX As Integer = &H134
#End Region
'//UpdateDropDownWidth でチェックする既定の幅を格納します
Private initialDropDownWidth As Integer = 0
Private components As System.ComponentModel.IContainer = Nothing
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing AndAlso Not Me.components Is Nothing Then
Me.components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
Private Sub InitializeComponent()
Me.Name = "BetterComboBox"
End Sub
Public Sub New()
Me.InitializeComponent()
Me.initialDropDownWidth = Me.DropDownWidth
'AddHandler Me.HandleCreated, New EventHandler(AddressOf Me.BetterComboBox_HandleCreated)
AddHandler Me.DisplayMemberChanged, New EventHandler(AddressOf Me.BetterComboBox_HandleCreated)
End Sub
Public Sub BetterComboBox_HandleCreated(ByVal sender As Object, ByVal e As System.EventArgs)
Me.UpdateDropDownWidth()
End Sub
Private Sub UpdateDropDownWidth()
'//GDI+ 描画面を作成して、文字列の幅を測定します
Dim ds As System.Drawing.Graphics = Me.CreateGraphics()
'//最大幅の項目の値を保持する Float 変数
Dim maxWidth As Single = 0
'//各項目を反復処理して
'//DisplayMember 文字列の最大幅を測定します
For Each item As Object In Me.Items
maxWidth = Math.Max(maxWidth, ds.MeasureString(item.ToString(), Me.Font).Width)
Next
'//空白文字用のバッファを
'//テキストに追加します
maxWidth += 30
'//maxWidth を四捨五入して int にキャストします
Dim newWidth As Integer = Convert.ToInt32(Decimal.Round(Convert.ToDecimal(maxWidth), 0))
'//幅が画面よりも大きい場合には
'//画面内に収まるようにします
If newWidth > Screen.GetWorkingArea(Me).Width Then
newWidth = Screen.GetWorkingArea(Me).Width
End If
'//新しく計算した幅よりも小さい場合にのみ
'//既定の幅を変更します
If newWidth > initialDropDownWidth Then
Me.DropDownWidth = newWidth
End If
'//描画面をクリーンアップします
ds.Dispose()
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = WM_CTLCOLORLISTBOX Then
'//画面内に収まるようにします
Dim left As Integer = Me.PointToScreen(New Point(0, 0)).X
'//ドロップダウンが画面の右端からはみ出す場合のみ、以下の処理を実行します
If Me.DropDownWidth > Screen.PrimaryScreen.WorkingArea.Width - left Then
'//ComboBox の現在の位置とサイズを取得します
Dim comboRect As Rectangle = Me.RectangleToScreen(Me.ClientRectangle)
Dim dropHeight As Integer = 0
Dim topOfDropDown As Integer = 0
Dim leftOfDropDown As Integer = 0
'//ドロップ リストの高さを計算します
Dim i As Integer = 0
While (i < Me.Items.Count AndAlso i < Me.MaxDropDownItems)
dropHeight += Me.ItemHeight
i += 1
End While
'//ドロップ リストが画面の下端からはみ出す場合には
'//ドロップダウンの上位置を設定します
If dropHeight > Screen.PrimaryScreen.WorkingArea.Height - _
Me.PointToScreen(New Point(0, 0)).Y Then
topOfDropDown = comboRect.Top - dropHeight - 2
Else
topOfDropDown = comboRect.Bottom
End If
'//移動先の左位置を計算します
leftOfDropDown = comboRect.Left - (Me.DropDownWidth - _
(Screen.PrimaryScreen.WorkingArea.Width - left))
'//ドロップダウンの配置とサイズを決定します
'//SWP_NOSIZE フラグを使用する場合、cx および cy パラメータは無視されます
SetWindowPos(m.LParam, _
IntPtr.Zero, _
leftOfDropDown, _
topOfDropDown, _
0, _
0, _
SWP_NOSIZE)
End If
End If
MyBase.WndProc(m)
End Sub
End Class