BackgroundWorker and shared class - vb.net

I have a NumericUpDown control in the main thread.
I create an instance of a public class_A that stores the NumericUpDown value.
I create a BackgroundWorker that runs a separate thread.
In the BackgroundWorker thread I create an instance of a class_B that recalls the argument from the instance of class_A.
I don't understand why the instance of the class_A just created before, its result as Nothing.
Here is the code:
Imports System.ComponentModel
Public Class Form1
Dim WithEvents bgw As New BackgroundWorker
Dim WithEvents bgw2 As New BackgroundWorker
Dim lSide As Label
Public nudSide As NumericUpDown
Dim bCalculate As Button
Dim bCalculate2 As Button
Dim tbLog As TextBox
Dim calc As calc
Public calc2 As calc2
Public Delegate Function d_getSide() As Double
Public getSide As New d_getSide(AddressOf rungetSide)
Public Side As c_Side
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Size = New Size(400, 160)
bgw.WorkerSupportsCancellation = True
bgw2.WorkerSupportsCancellation = True
lSide = New Label
With lSide
.Text = "Side"
.Size = New Size(40, 20)
.Location = New Point(10, 10)
End With
Me.Controls.Add(lSide)
nudSide = New NumericUpDown
With nudSide
.Size = New Size(40, 20)
.Location = New Point(lSide.Location.X + lSide.Size.Width, lSide.Location.Y)
.DecimalPlaces = 0
.Minimum = 1
.Maximum = 100
.Increment = 1
.Value = 1
End With
Me.Controls.Add(nudSide)
bCalculate = New Button
With bCalculate
.Text = "Calculate"
.Size = New Size(60, 20)
.Location = New Point(nudSide.Location.X + nudSide.Size.Width + 40, nudSide.Location.Y)
AddHandler .Click, AddressOf bCalculate_Click
End With
Me.Controls.Add(bCalculate)
bCalculate2 = New Button
With bCalculate2
.Text = "Calculate 2"
.Size = New Size(60, 20)
.Location = New Point(bCalculate.Location.X + bCalculate.Size.Width + 10, bCalculate.Location.Y)
AddHandler .Click, AddressOf bCalculate2_Click
End With
Me.Controls.Add(bCalculate2)
tbLog = New TextBox
With tbLog
.Size = New Size(250, 60)
.Location = New Point(lSide.Location.X, lSide.Location.Y + 40)
.Multiline = True
.ScrollBars = ScrollBars.Vertical
End With
Me.Controls.Add(tbLog)
End Sub
Private Sub bCalculate_Click()
bgw.RunWorkerAsync(nudSide.Value)
End Sub
Private Sub bgw_Dowork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
'example 1)
'passing argument throught backGroundWorker
calc = New calc(e.Argument)
End Sub
Private Sub bgw_Runworkercompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
getResult()
End Sub
Private Sub bCalculate2_Click()
'here i create an instance of the Side class (expose the side property)
Side = New c_Side
bgw2.RunWorkerAsync()
End Sub
Private Sub bgw2_Dowork(sender As Object, e As DoWorkEventArgs) Handles bgw2.DoWork
'example 2)
' in the backgroundworker thread i create an instance of the class calc2
calc2 = New calc2()
End Sub
Private Sub bgw2_Runworkercompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bgw2.RunWorkerCompleted
getResult2()
End Sub
Private Sub write(ByVal message As String)
With tbLog
.SelectionStart = .Text.Length
.SelectedText = vbCrLf & message
End With
End Sub
Private Sub getResult()
tbLog.Clear()
write("area = " & calc.area & " cm^2")
write("volume = " & calc.volume & " cm^3")
End Sub
Private Sub getResult2()
tbLog.Clear()
write("area = " & calc2.area & " cm^2")
write("volume = " & calc2.volume & " cm^3")
End Sub
Public Function rungetSide() As Double
If Me.InvokeRequired Then
Me.Invoke(getSide)
Else
Return Side.Side
End If
Return Side.Side
End Function
End Class
Class calc
Sub New(ByVal Side As Double)
_area = Side ^ 2
_volume = Side ^ 3
End Sub
Private _area As Double
Public Property area As Double
Get
Return Math.Round(_area, 2)
End Get
Set(value As Double)
_area = Math.Round(value, 2)
End Set
End Property
Private _volume As Double
Public Property volume As Double
Get
Return Math.Round(_volume, 2)
End Get
Set(value As Double)
_volume = Math.Round(value, 2)
End Set
End Property
End Class
Public Class calc2
Sub New()
'the constructor, recall the value from the instance (public) of the class 'Side' just built in the main thread
'but i don't understand why the instance it's nothing
_area = Form1.Side.Side ^ 2
_volume = Form1.Side.Side ^ 3
End Sub
Private _area As Double
Public Property area As Double
Get
Return Math.Round(_area, 2)
End Get
Set(value As Double)
_area = Math.Round(value, 2)
End Set
End Property
Private _volume As Double
Public Property volume As Double
Get
Return Math.Round(_volume, 2)
End Get
Set(value As Double)
_volume = Math.Round(value, 2)
End Set
End Property
End Class
Public Class c_Side
Sub New()
_Side = Form1.nudSide.Value
'_Side = Form1.rungetSide
End Sub
Private _Side As Double
Public Property Side As Double
Get
Return Math.Round(_Side, 2)
End Get
Set(value As Double)
_Side = Math.Round(value, 2)
End Set
End Property
End Class
What I'm looking for is to create an instance of class_A in the main thread and store the NumericUpDown value, and in a separate thread (BackgroundWorker) create an instance of class_B and obtain the value of the NumericUpDown control, just before stored in the instance of class_A.

I've found the solution.
Just declare the variable as Public Shared
Public Shared Side As c_Side
So it's visible from all the application, and so, from all the threads.
So when i start the thread (or when i want or when i need), i backup all the values of the UI controls in a Public Shared instance of a Public Class, that can be 'read' from the backGroundWorker thread.

Related

Custom treeview VB.net

I'm looking to create a custom treeview control in VB.net, what I need to do is have a standard treeview control displaying the system file strucure for example, but with an extra icon on the right on the folder/file name that only appears on hover over of the node. So, for instance, if I hover over the folder Sup2 as shown in the pic, then the orange icon appears
I've done some research and from what I can see, I have to override the onpaint event to make this happen but I'm not sure exactly how to do this. I also need to add an onclick event to that new orange icon.
This is not professional but is worth giving a try..
I created a class that inherited from a Treeview control and overrode the constructor - changing the default width and height, setting the DrawMode to TreeViewDrawMode.OwnerDrawText.
Next, I handled the TreeView.DrawNode event, using a PictureBox to display the Image and changing its location based on the currently highlighted item.
I also handled the Click event of the PictureBox.Under that event, You can do whatever you want with the highlighted node.
I used images from My.Resources for ImageList.Images(0) and PictureBox.Image
Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim trv As New MyTreeView
trv.Nodes.Add("Suppliers")
trv.Nodes(0).Nodes.Add("Sup 1")
trv.Nodes(0).Nodes.Add("Sup 2")
trv.Nodes(0).Nodes.Add("Sup 3")
trv.Nodes(0).Nodes.Add("Sup 4")
trv.Nodes(0).Nodes.Add("Sup 5")
Controls.Add(trv)
End Sub
End Class
'Class Starts Here
Public Class MyTreeView
Inherits TreeView
WithEvents myImage As PictureBox
Dim activeItem As TreeNode 'Variable to store active TreeNode
Public Sub New()
MyBase.New() 'Call the base class constructor
'And set some values
Height = 300
Width = 300
Location = New Point(50, 50)
DrawMode = TreeViewDrawMode.OwnerDrawText 'Very neccesary
AddHandler DrawNode, AddressOf MyTreeViewDrawNode
'Add event handlers
AddHandler AfterCollapse, AddressOf MyTreeViewCollapsed
'Set HotTracking event to true to allow for MouseHover
HotTracking = True
ImageList = new ImageList
ImageList.Images.Add(My.Resources.FolderImage)
ImageIndex = 0
Font = New Font(Font.FontFamily, 10)
'Initialize picturebox
myImage = New PictureBox() With
{
.Image = My.Resources.editPencilImage,
.SizeMode = PictureBoxSizeMode.Zoom,
.Size = New Size(10, 10),
.Visible = False
}
Controls.Add(myImage)
End Sub
Private Sub MyTreeViewCollapsed(sender As Object, e As TreeViewEventArgs)
myImage.Visible = False
End Sub
Sub ImageClicked(sender As Object, e As EventArgs) Handles myImage.Click
If (Not activeItem Is Nothing) Then
MessageBox.Show("Clicked Item - " & activeItem.Text)
End If
End Sub
Private Sub MyTreeViewDrawNode(sender As Object, e As DrawTreeNodeEventArgs)
e.DrawDefault = True
If (e.State = TreeNodeStates.Hot) Then
myImage.Visible = True
activeItem = e.Node
Dim tmpSize = TextRenderer.MeasureText(e.Node.Text, Font)
myImage.Location = New Point(e.Node.Bounds.Location.X + tmpSize.Width, e.Node.Bounds.Location.Y)
End If
End Sub
End Class
I think the following example will give you some hints and tips.
Option Explicit On
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Runtime.InteropServices
Public Class TreeViewEx
Inherits TreeView
#Region "API"
Private Const TVM_SETEXTENDEDSTYLE As Integer = &H1100 + 44
Private Const TVS_EX_DOUBLEBUFFER As Integer = &H4
<DllImport("user32.dll")>
Private Shared Function SendMessage(ByVal hWnd As IntPtr,
ByVal msg As Integer,
ByVal wp As IntPtr,
ByVal lp As IntPtr) As IntPtr
End Function
#End Region
#Region "Private Fields"
Private ReadOnly RightImage As Bitmap
Private ReadOnly NSF As StringFormat
Private HoverNode As TreeNode
Private RightImageRect As Rectangle
#End Region
#Region "Constructors"
Sub New()
DrawMode = TreeViewDrawMode.OwnerDrawText
RightImage = New Bitmap(My.Resources.Modify)
NSF = New StringFormat With {
.Alignment = StringAlignment.Near,
.LineAlignment = StringAlignment.Center,
.Trimming = StringTrimming.EllipsisCharacter,
.FormatFlags = StringFormatFlags.NoWrap
}
End Sub
#End Region
#Region "Paint"
Protected Overrides Sub OnDrawNode(e As DrawTreeNodeEventArgs)
MyBase.OnDrawNode(e)
If e.Node Is Nothing Then Return
Dim rect As Rectangle = e.Bounds : rect.Inflate(0, 1)
If Not ClientRectangle.IntersectsWith(rect) Then
Return
End If
Dim G As Graphics = e.Graphics
G.SmoothingMode = SmoothingMode.HighQuality
G.TextRenderingHint = TextRenderingHint.ClearTypeGridFit
'Option1: If you want to draw different background color for the selected node.
'If (e.State And TreeNodeStates.Selected) = TreeNodeStates.Selected Then
' Using br As New SolidBrush(Color.LightSteelBlue) '<- suit yourself!
' G.FillRectangle(br, rect)
' End Using
'Else
' Using br As New SolidBrush(If(e.Node.BackColor.Equals(Color.Empty), BackColor, e.Node.BackColor))
' G.FillRectangle(br, rect)
' End Using
'End If
'Option2: If you don't want Option1.
Using br As New SolidBrush(If(e.Node.BackColor.Equals(Color.Empty), BackColor, e.Node.BackColor))
G.FillRectangle(br, rect)
End Using
Using br As New SolidBrush(If(e.Node.ForeColor.Equals(Color.Empty), ForeColor, e.Node.ForeColor))
G.DrawString(e.Node.Text, If(e.Node.NodeFont, Font), br, rect, NSF)
End Using
If ReferenceEquals(e.Node, HoverNode) Then
RightImageRect = New Rectangle(rect.Right + 5,
rect.Y + ((rect.Height - RightImage.Height) / 2),
rect.Height - 4, rect.Height - 4)
G.DrawImage(RightImage,
RightImageRect,
New Rectangle(0, 0, RightImage.Width, RightImage.Height),
GraphicsUnit.Pixel)
End If
End Sub
#End Region
#Region "Other Events"
'You need this to reduce the flickering.
Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
SendMessage(
Handle,
TVM_SETEXTENDEDSTYLE,
IntPtr.op_Explicit(TVS_EX_DOUBLEBUFFER),
IntPtr.op_Explicit(TVS_EX_DOUBLEBUFFER)
)
MyBase.OnHandleCreated(e)
End Sub
Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
MyBase.OnMouseMove(e)
Dim node = GetNodeAt(e.Location)
If node IsNot Nothing Then
'Avoid unnecessary Invalidate() calls.
If Not ReferenceEquals(node, HoverNode) Then
HoverNode = node
Invalidate()
End If
Else
'Avoid unnecessary Invalidate() calls.
If HoverNode IsNot Nothing Then
HoverNode = Nothing
Invalidate()
End If
End If
End Sub
Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
MyBase.OnMouseDown(e)
If e.Button = MouseButtons.Left AndAlso
RightImageRect.Contains(e.Location) Then
'Notify the container to do something.
OnEditButtonClicked()
End If
End Sub
Protected Overrides Sub OnMouseLeave(e As EventArgs)
MyBase.OnMouseLeave(e)
Invalidate()
End Sub
Protected Overrides Sub Dispose(disposing As Boolean)
MyBase.Dispose(disposing)
If disposing Then
RightImage.Dispose()
NSF.Dispose()
End If
End Sub
#End Region
#Region "Custom Events"
Public Class EditButtonClickArgs
Inherits EventArgs
Public Property Node As TreeNode
Sub New(node As TreeNode)
Me.Node = node
End Sub
End Class
''' <summary>
''' Raised when the right image is clicked.
''' </summary>
Public Event EditButtonClicked As EventHandler(Of EditButtonClickArgs)
''' <summary>
''' Raises the <see cref="EditButtonClicked"/> events.
''' </summary>
Protected Overridable Sub OnEditButtonClicked()
RaiseEvent EditButtonClicked(Me, New EditButtonClickArgs(HoverNode))
End Sub
#End Region
End Class
In the Form that contains the new TreeViewEx control, you can handle the EditButtonClicked to do the necessary:
Public Class Form1
Inherits Form
Private Sub TreeViewEx1_EditButtonClicked(sender As Object, e As TreeViewEx.EditButtonClickArgs) Handles TreeViewEx1.EditButtonClicked
'Do something with the e.Node
End Sub
End Class
And here's a quick demo:
Good luck.

Why is this event handler method being repeatedly called?

The DataGridViewComboBoxColumn control can be difficult to work with. I've been fighting various permutations of this code for two long days, so I've decided to throw in the towel and seek some assistance.
The latest incarnation of weirdness is a ComboBox event handler that fires an increasing number of times for a single user action. Oddly, the rate of increase is an exact doubling of the count just preceding (i.e. 1, 2, 4, 8, 16, 32, 64 etc.)
To start things off, I'll explain what I'm trying to accomplish and also clarify some terminology.
I have a Dictionary(Of Integer, String). In my domain rules, I'm calling its Key property Channel and its Value property Label. I'm mapping each KeyValuePair to a third String value called Target. The Dictionary(Of Integer, String) items are fixed—they exist as a visual aid for the user, so he can easily select the Target from a List(Of String).
I've settled on a DataGridView control to provide this functionality. I'm using three columns, like so:
Note that already-mapped Target list items are displayed in a nearly-invisible color, so as to discourage the user from trying to use them again. (This is where the event handler problem comes in—when an already-mapped Target is selected for mapping to a different Label.)
I'm including my full code base below, but for a quick look here's the event handler that repeats:
Private Sub ComboBox_SelectionChangeCommitted(Sender As ComboBox, e As EventArgs)
' '
' Look for other labels that have already been mapped to this target '
' '
If Me.OtherTargetCells.Any(Function(Cell) Cell.FormattedValue = Sender.Text) Then
If Me.IsInteractiveChange Then
MsgBox("Target [] is already mapped to Label []. If you want to map Target [] to Label [], you must first set Label [] to [Not mapped].", MsgBoxStyle.Exclamation, Me.DataGridView.FindForm.Text)
Me.IsInteractiveChange = False
Sender.SelectedIndex = 0
Me.IsInteractiveChange = True
End If
End If
End Sub
And here's how I'm wiring it all up:
Public Sub New()
Task.Run(Sub()
Dim oHandler As DataGridViewEditingControlShowingEventHandler
While Me.DataGridView Is Nothing
End While
oHandler = New DataGridViewEditingControlShowingEventHandler(AddressOf DataGridView_EditingControlShowing)
RemoveHandler Me.DataGridView.EditingControlShowing, oHandler
AddHandler Me.DataGridView.EditingControlShowing, oHandler
End Sub)
End Sub
Private Sub DataGridView_EditingControlShowing(Sender As DataGridView, e As DataGridViewEditingControlShowingEventArgs)
Dim oComboBox As ComboBox
If TypeOf e.Control Is ComboBox Then
oComboBox = e.Control
oComboBox.DrawMode = DrawMode.OwnerDrawFixed
RemoveHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
AddHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
RemoveHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
AddHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
End If
End Sub
The repeat count multiplies when I select an already-mapped Target from a different list than previously (e.g. selecting twice from SCC doesn't increase the count, but selecting from SCC and then Scale does.)
I've tried many, many, many possible solutions for this—too many to list here and most of which I just don't remember—but none with any success.
What can I do to constrain the handler to fire only once for each selection change?
Mapping.TargetsColumn.vb
Namespace Mapping
Public Class TargetsColumn
Inherits DataGridViewComboBoxColumn
Public Sub New()
Task.Run(Sub()
Dim oHandler As DataGridViewEditingControlShowingEventHandler
While Me.DataGridView Is Nothing
End While
oHandler = New DataGridViewEditingControlShowingEventHandler(AddressOf DataGridView_EditingControlShowing)
RemoveHandler Me.DataGridView.EditingControlShowing, oHandler
AddHandler Me.DataGridView.EditingControlShowing, oHandler
End Sub)
End Sub
Private Sub DataGridView_EditingControlShowing(Sender As DataGridView, e As DataGridViewEditingControlShowingEventArgs)
Dim oComboBox As ComboBox
If TypeOf e.Control Is ComboBox Then
oComboBox = e.Control
oComboBox.DrawMode = DrawMode.OwnerDrawFixed
RemoveHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
AddHandler oComboBox.DrawItem, New DrawItemEventHandler(AddressOf ComboBox_DrawItem)
RemoveHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
AddHandler oComboBox.SelectionChangeCommitted, New EventHandler(AddressOf ComboBox_SelectionChangeCommitted)
End If
End Sub
Private Sub ComboBox_DrawItem(Sender As ComboBox, e As DrawItemEventArgs)
Dim sThisTarget As String
Dim oForeColor As Color
Dim _
iSeparatorBottom,
iSeparatorRight,
iSeparatorLeft As Integer
Dim _
oSeparatorStart,
oSeparatorStop As Point
sThisTarget = DirectCast(Me.Items(e.Index), Target).Value
iSeparatorBottom = e.Bounds.Bottom - 2
iSeparatorRight = e.Bounds.Right
iSeparatorLeft = e.Bounds.Left
e.DrawBackground()
If e.Index = 0 Then
oSeparatorStart = New Point(iSeparatorLeft, iSeparatorBottom)
oSeparatorStop = New Point(iSeparatorRight, iSeparatorBottom)
oForeColor = SystemColors.HotTrack
e.Graphics.FillRectangle(SystemBrushes.Control, e.Bounds)
e.Graphics.DrawLine(SystemPens.ControlDark, oSeparatorStart, oSeparatorStop)
Else
If Me.OtherTargets.Contains(sThisTarget) Then
oForeColor = SystemColors.ControlLight
Else
oForeColor = e.ForeColor
End If
End If
Using oBrush As New SolidBrush(oForeColor)
e.Graphics.DrawString(sThisTarget, e.Font, oBrush, e.Bounds)
End Using
If e.State.HasFlag(DrawItemState.Focus) Then e.DrawFocusRectangle()
Me.DataGridView.FindForm.Text = sThisTarget
End Sub
Private Sub ComboBox_SelectionChangeCommitted(Sender As ComboBox, e As EventArgs)
' '
' Look for other labels that have already been mapped to this target '
' '
If Me.OtherTargetCells.Any(Function(Cell) Cell.FormattedValue = Sender.Text) Then
If Me.IsInteractiveChange Then
MsgBox("Target [] is already mapped to Label []. If you want to map Target [] to Label [], you must first set Label [] to [Not mapped].", MsgBoxStyle.Exclamation, Me.DataGridView.FindForm.Text)
Me.IsInteractiveChange = False
Sender.SelectedIndex = 0
Me.IsInteractiveChange = True
End If
End If
End Sub
Private ReadOnly Property OtherTargets As List(Of String)
Get
Return Me.OtherTargetCells.Select(Function(Cell) DirectCast(Cell.FormattedValue, String)).ToList
End Get
End Property
Private ReadOnly Property CurrentTargetCell As DataGridViewCell
Get
Return Me.AllTargetCells(Me.DataGridView.CurrentRow.Index)
End Get
End Property
Private ReadOnly Property AllTargetCells As List(Of DataGridViewCell)
Get
Dim oAllCells As IEnumerable(Of DataGridViewCell)
Dim oRows As IEnumerable(Of DataGridViewRow)
oRows = Me.DataGridView.Rows.Cast(Of DataGridViewRow)
oAllCells = oRows.SelectMany(Function(Row) Row.Cells.Cast(Of DataGridViewCell))
Return oAllCells.Where(Function(Cell) TypeOf Cell Is DataGridViewComboBoxCell).ToList
End Get
End Property
Private ReadOnly Property OtherTargetCells As List(Of DataGridViewCell)
Get
Return Me.AllTargetCells.Where(Function(Cell) Cell.RowIndex <> Me.RowIndex).ToList
End Get
End Property
Private ReadOnly Property RowIndex As Integer
Get
Return Me.DataGridView.CurrentRow.Index
End Get
End Property
Private IsInteractiveChange As Boolean = True
Private ReadOnly ComboBoxes As New Dictionary(Of Integer, ComboBox)
End Class
End Namespace
Form1.vb
Public Class Form1
Inherits Form
Public Sub New()
Dim oColTargets As Mapping.TargetsColumn
Dim oTargets As IEnumerable(Of String)
Dim oQuery As Func(Of Target, Boolean)
Dim sChannel As String
Dim oTarget As Target
Dim oMaps As Dictionary(Of Integer, String)
Dim oMap As Map
Dim _
oColChannels,
oColLabels As DataGridViewTextBoxColumn
Me.InitializeComponent()
Me.Targets.Add(New Target("Not mapped"))
sChannel = String.Empty
oQuery = Function(Target) Target.Value = sChannel
'oTargets = Reader.Client.Create.Call(Function(Service As Reader.IService) Service.GetChannelTargets)'
oTargets = New List(Of String) From {"Scale", "SCC", "CO", "O2"}
oTargets.ToList.ForEach(Sub(Target)
Me.Targets.Add(New Target(Target))
End Sub)
'oMaps = Reader.Client.Create.Call(Function(Service As Reader.IService) Service.GetChannelMaps)'
oMaps = New Dictionary(Of Integer, String) From {{3, "Test"}, {7, "SCC"}, {8, "Scale"}, {9, "CO"}, {10, "O2"}}
oMaps.ToList.ForEach(Sub(Map)
sChannel = Map.Value
If Me.Targets.Any(oQuery) Then
oTarget = Me.Targets.Single(oQuery)
Else
oTarget = Me.Targets.First
End If
oMap = New Map With {
.Channel = Map.Key,
.Label = Map.Value,
.Target = oTarget
}
Me.Maps.Add(oMap)
End Sub)
oColChannels = New DataGridViewTextBoxColumn With {
.DataPropertyName = NameOf(Map.Channel),
.AutoSizeMode = DataGridViewAutoSizeColumnMode.ColumnHeader,
.HeaderText = NameOf(Map.Channel),
.ReadOnly = True,
.Name = NameOf(oColChannels)
}
oColLabels = New DataGridViewTextBoxColumn With {
.DataPropertyName = NameOf(Map.Label),
.AutoSizeMode = DataGridViewAutoSizeColumnMode.ColumnHeader,
.HeaderText = NameOf(Map.Label),
.ReadOnly = True,
.Name = NameOf(oColLabels)
}
oColTargets = New Mapping.TargetsColumn With {
.DataPropertyName = NameOf(Map.Target),
.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
.DisplayMember = NameOf(Target.Value),
.ValueMember = NameOf(Target.Self),
.HeaderText = NameOf(Map.Target),
.DataSource = Me.Targets,
.Name = NameOf(oColTargets)
}
dgvMapping.AutoGenerateColumns = False
dgvMapping.Columns.AddRange({oColChannels, oColLabels, oColTargets})
For Each oColumn As DataGridViewColumn In dgvMapping.Columns
oColumn.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
If oColumn.Index = 0 Then
oColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
End If
Next
dgvMapping.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize
dgvMapping.DataSource = New BindingList(Of Map)(Me.Maps)
If dgvMapping.RowCount = 0 Then
dgvMapping.Height = 150
Else
dgvMapping.Height = ((dgvMapping.RowCount + 0) * dgvMapping.Rows(0).Height) + dgvMapping.ColumnHeadersHeight
End If
End Sub
Private Sub Form1_FormClosing(Sender As Form1, e As FormClosingEventArgs) Handles Me.FormClosing
Dim oPolicy As Target = Me.Maps.First.Target
Dim sName As String = Me.Maps.First.Channel
End Sub
Private Sub _dgvMapping_DataError(Sender As DataGridView, e As DataGridViewDataErrorEventArgs) Handles dgvMapping.DataError
MsgBox(e.Exception.Message, MsgBoxStyle.Critical, Me.Text)
End Sub
Private Targets As New BindingList(Of Target)
Private Maps As New List(Of Map)
End Class
Public Class Map
Public Property Channel As Integer
Public Property Label As String
Public Property Target As Target
End Class
Public Class Target
Public Sub New(Target As String)
Me.Value = Target
End Sub
Public ReadOnly Property Self As Target
Get
Return Me
End Get
End Property
Public ReadOnly Property Value As String
End Class
Form1.Designer.vb
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.'
<System.Diagnostics.DebuggerNonUserCode()>
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
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.'
<System.Diagnostics.DebuggerStepThrough()>
Private Sub InitializeComponent()
Me.dgvMapping = New System.Windows.Forms.DataGridView()
CType(Me.dgvMapping, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
' '
'dgvMapping'
' '
Me.dgvMapping.AllowUserToAddRows = False
Me.dgvMapping.AllowUserToDeleteRows = False
Me.dgvMapping.AllowUserToOrderColumns = True
Me.dgvMapping.AllowUserToResizeColumns = False
Me.dgvMapping.AllowUserToResizeRows = False
Me.dgvMapping.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize
Me.dgvMapping.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter
Me.dgvMapping.Location = New System.Drawing.Point(12, 12)
Me.dgvMapping.Name = "dgvMapping"
Me.dgvMapping.RowHeadersVisible = False
Me.dgvMapping.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect
Me.dgvMapping.Size = New System.Drawing.Size(250, 150)
Me.dgvMapping.TabIndex = 0
' '
'Form1'
' '
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(800, 450)
Me.Controls.Add(Me.dgvMapping)
Me.Font = New System.Drawing.Font("Segoe UI", 8.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.Name = "Form1"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
Me.Text = "Form1"
CType(Me.dgvMapping, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)
End Sub
Friend WithEvents dgvMapping As DataGridView
End Class
Fixed.
I was instantiating a new event handler object for each AddHandler/RemoveHandler call.
When I removed the instantiations and used simple expressions instead, the ComboBoxes started behaving correctly.
Public Sub New()
Task.Run(Sub()
While Me.DataGridView Is Nothing
End While
RemoveHandler Me.DataGridView.EditingControlShowing, AddressOf DataGridView_EditingControlShowing
AddHandler Me.DataGridView.EditingControlShowing, AddressOf DataGridView_EditingControlShowing
End Sub)
End Sub
Private Sub DataGridView_EditingControlShowing(Sender As Object, e As DataGridViewEditingControlShowingEventArgs)
Dim oComboBox As ComboBox
If TypeOf e.Control Is ComboBox Then
oComboBox = e.Control
oComboBox.DrawMode = DrawMode.OwnerDrawFixed
RemoveHandler oComboBox.DrawItem, AddressOf ComboBox_DrawItem
AddHandler oComboBox.DrawItem, AddressOf ComboBox_DrawItem
RemoveHandler oComboBox.SelectionChangeCommitted, AddressOf ComboBox_SelectionChangeCommitted
AddHandler oComboBox.SelectionChangeCommitted, AddressOf ComboBox_SelectionChangeCommitted
End If
End Sub
I had to relax the Sender parameter types to Object in the event handler methods, but that didn't carry any serious consequence.
Private Sub DataGridView_EditingControlShowing(Sender As Object, e As DataGridViewEditingControlShowingEventArgs)
End Sub
Private Sub ComboBox_DrawItem(Sender As Object, e As DrawItemEventArgs)
End Sub
Private Sub ComboBox_SelectionChangeCommitted(Sender As Object, e As EventArgs)
End Sub
For what it's worth: I generally prefer to constrain the Sender parameter to the calling type, for more efficient coding, but that wasn't possible in this case. Nevertheless, the only impact was the need to cast the Sender in one place in one method body:
Dim oQuery = Function(Cell) Cell.FormattedValue = DirectCast(Sender, ComboBox).Text
It works as expected now.

VB InvokeRequired doesn't appear to detect different threads

I have a logging function that get called from different threads. The logging function uses InvokeRequired to determine if the caller is in the same thread as the textbox was created in. If all the code is put into the same class it works fine. But when separated out InvokeRequired always returns false.
Imports System.Threading
Public Class Test_Main
Dim tt_object As Test_Thread = Test_Thread.Get_Test_Thread_Class()
Private Shared logger_lock As New Object
Delegate Sub WriteLogMessage_Callback(text As String)
Private Sub btn_start_Click(sender As Object, e As EventArgs) Handles btn_start.Click
WriteLogMessage("From - btn_start_Click")
tt_object.Start_Threads()
End Sub
Public Sub WriteLogMessage(ByVal message As String)
If (Me.InvokeRequired) Then
Dim d As New WriteLogMessage_Callback(AddressOf WriteLogMessage)
Me.Invoke(d, New Object() {[message]})
Else
SyncLock logger_lock
log_box.Text += System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff") & " " & message & vbCrLf
log_box.SelectionStart = log_box.Text.Length
log_box.ScrollToCaret()
End SyncLock
End If
Application.DoEvents()
End Sub
End Class
Public Class Test_Thread
Private Shared _thisInstance As Test_Thread = Nothing
Private t_1 As Thread
Private t_2 As Thread
Protected Sub New() 'Class constructor
t_1 = New Thread(AddressOf Thread_1)
t_2 = New Thread(AddressOf Thread_2)
End Sub
Public Shared Function Get_Test_Thread_Class() As Test_Thread
If _thisInstance Is Nothing Then 'Initialize object if it hasn't laready been done
_thisInstance = New Test_Thread()
End If
Return _thisInstance 'Return the object instance
End Function
Public Sub Start_Threads()
Dim counter As Integer = 0
Test_Main.WriteLogMessage("BEGIN - Start_Threads")
t_1.Start()
t_2.Start()
Do While counter < 50
Application.DoEvents()
Thread.Sleep(500)
counter += 1
Loop
t_1.Abort()
t_2.Abort()
Test_Main.WriteLogMessage("END - Start_Threads")
End Sub
Public Sub Thread_1()
Dim counter As Integer = 0
Test_Main.WriteLogMessage("BEGIN - Thread_1")
Do While True
Test_Main.WriteLogMessage("Thread_1: " & counter)
counter += 1
Thread.Sleep(1000)
Loop
End Sub
Public Sub Thread_2()
Dim counter As Integer = 0
Test_Main.WriteLogMessage("BEGIN - Thread_2")
Do While True
Test_Main.WriteLogMessage("Thread_2: " & counter)
counter += 1
Thread.Sleep(1500)
Loop
End Sub
End Class
I solve it with the following code! Thanks for all the help.
Imports System.Threading
Public Class Test_Main
Private Sub btn_start_Click(sender As Object, e As EventArgs) Handles btn_start.Click
Dim log_object As Logger = Logger.Get_Logger_Class(log_box)
Dim tt_object As Test_Thread = Test_Thread.Get_Test_Thread_Class(log_object)
log_object.WriteLogMessage("From - btn_start_Click")
tt_object.Start_Threads()
End Sub
End Class
Public Class Logger
Private Shared _thisInstance As Logger = Nothing
Private log_box As TextBox
Delegate Sub WriteLogMessage_Callback(text As String)
Private Shared logger_lock As New Object
Public Shared Function Get_Logger_Class(log_box_addr As TextBox) As Logger
If _thisInstance Is Nothing Then 'Initialize object if it hasn't laready been done
_thisInstance = New Logger()
_thisInstance.log_box = log_box_addr
End If
Return _thisInstance 'Return the object instance
End Function
Public Sub WriteLogMessage(ByVal message As String)
If (log_box.InvokeRequired) Then
Dim d As New WriteLogMessage_Callback(AddressOf WriteLogMessage)
log_box.Invoke(d, New Object() {[message]})
Else
SyncLock logger_lock
Me.log_box.Text += System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff") & " " & message & vbCrLf
Me.log_box.SelectionStart = log_box.Text.Length
Me.log_box.ScrollToCaret()
End SyncLock
End If
Application.DoEvents()
End Sub
End Class
Public Class Test_Thread
Private Shared _thisInstance As Test_Thread = Nothing
Private log_addr As Logger
Public t_1 As Thread
Public t_2 As Thread
Protected Sub New() 'Class constructor
t_1 = New Thread(AddressOf Thread_1)
t_2 = New Thread(AddressOf Thread_2)
End Sub
Public Shared Function Get_Test_Thread_Class(logger_addr As Logger) As Test_Thread
If _thisInstance Is Nothing Then 'Initialize object if it hasn't laready been done
_thisInstance = New Test_Thread()
_thisInstance.log_addr = logger_addr
End If
Return _thisInstance 'Return the object instance
End Function
Public Sub Start_Threads()
Dim counter As Integer = 0
log_addr.WriteLogMessage("BEGIN - Start_Threads")
t_1.Start()
t_2.Start()
Do While counter < 50
Application.DoEvents()
Thread.Sleep(500)
counter += 1
Loop
t_1.Abort()
t_2.Abort()
log_addr.WriteLogMessage("END - Start_Threads")
End Sub
Public Sub Thread_1()
Dim counter As Integer = 0
log_addr.WriteLogMessage("BEGIN - Thread_1")
Do While True
log_addr.WriteLogMessage("Thread_1: " & counter)
counter += 1
Thread.Sleep(1000)
Loop
End Sub
Public Sub Thread_2()
Dim counter As Integer = 0
log_addr.WriteLogMessage("BEGIN - Thread_2")
Do While True
log_addr.WriteLogMessage("Thread_2: " & counter)
counter += 1
Thread.Sleep(1500)
Loop
End Sub
End Class

access to new dynamically controls in vb.net

First of all excuse me for my poor grammar and vocabulary :)
please see this source and run it:
Public Class Form1
Public pointX As Integer
Public pointY As Integer = 32
Public dynamicText As TextBox
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
pointX = 330
For i = 1 To 4
dynamicText = New Windows.Forms.TextBox
dynamicText.Name = "T" + Trim(Str(i))
dynamicText.Text = ""
dynamicText.Location = New Point(pointX, pointY)
dynamicText.Size = New Size(100, 20)
Me.Controls.Add(dynamicText)
pointX = pointX - 106
Next
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
pointX = 330
pointY = pointY + 26
For i = 1 To 4
dynamicText = New Windows.Forms.TextBox
dynamicText.Name = "T" + Trim(Str(i))
dynamicText.Text = ""
dynamicText.Location = New Point(pointX, pointY)
dynamicText.Size = New Size(100, 20)
Me.Controls.Add(dynamicText)
pointX = pointX - 106
AddHandler dynamicText.Click, AddressOf printHello1
Next
End Sub
Private Sub printHello1(ByVal sender As System.Object, ByVal e As System.EventArgs)
MsgBox(dynamicText.Name)
If dynamicText.Name = "T1" Then MsgBox("Oh! this is T1")
End Sub
End Class
why If never is not true?!
why MsgBox(dynamicText.Name) always return T4?!
i want all controlls to be access by name or array of names.
please help me thank you. :)
The global variable dynamicText takes the value of the last TextBox added in the loop inside the Button1_Click event. This happens to be the control named T4. You don't really need a global variable in this case. You can cast the sender parameter to a TextBox instance because the sender parameter is the control that has raised the event.
Private Sub printHello1(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim txt = CType(sender, "TextBox")
if txt IsNot Nothing then
MsgBox(txt.Name)
If txt.Name = "T1" Then MsgBox("Oh! this is T1")
End If
End Sub
You also don't need to recreate the controls again in the button click event. The action executed in the form load event is enough (You could add the AddHandler there). Global variables are dangerous, avoid them when possible.
See if this is acceptable. Place a panel at the bottom of your form, set Dock to Bottom, add a single button to the panel and a TextBox. Place a FlowLayoutPanel onto the form, Dock = Fill, AutoScroll = True.
The code below creates the amount of TextBox controls as inputted into TextBox. Each newly created TextBox a click event is added with simple logic.
Form code
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim count As Integer = 0
If Integer.TryParse(TextBox1.Text, count) Then
Dim demo = New TextBoxCreate(FlowLayoutPanel1, "Demos", count)
demo.CreateTextBoxes()
End If
End Sub
End Class
Class code (add a new class to the project, name it TextBoxCreate.vb)
Public Class TextBoxCreate
Public Property TextBoxes As TextBox()
Public Property TextBoxBaseName As String
Public Property TextBoxCount As Integer
Public Property ParentControl As Control
Public Sub New(
ByVal ParentControl As Control,
ByVal BaseName As String,
ByVal Count As Integer)
Me.ParentControl = ParentControl
Me.TextBoxBaseName = BaseName
Me.TextBoxCount = Count
End Sub
Public Sub CreateTextBoxes()
Dim Base As Integer = 10
TextBoxes = Enumerable.Range(0, TextBoxCount).Select(
Function(Indexer)
Dim b As New TextBox With
{
.Name = String.Concat(TextBoxBaseName, Indexer + 1),
.Text = (Indexer + 1).ToString,
.Width = 150,
.Location = New Point(25, Base),
.Parent = Me.ParentControl,
.Visible = True
}
AddHandler b.Click, Sub(sender As Object, e As EventArgs)
Dim tb As TextBox = CType(sender, TextBox)
If tb.Name = TextBoxBaseName & "1" Then
tb.Text = "Got it"
Else
MessageBox.Show(tb.Name)
End If
End Sub
Me.ParentControl.Controls.Add(b)
Base += 30
Return b
End Function).ToArray
End Sub
End Class

vb.net: How to prevent the dropdown of a checked combobox from closing after checking or unchecking an item

I would like to prevent the dropdown list from closing when the user checks or unchecks a checkbox in a checked combobox.
I have copied some Microsoft code to created a checked combobox. As it didn't work out-of-the-box, I did some customizing.
Here's my code:
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class CheckedCombobox
Inherits ComboBox
Public Event ItemCheck(ByVal sender As Object, ByVal e As System.Windows.Forms.ItemCheckEventArgs)
<Browsable(False)> _
Public Overloads ReadOnly Property Items() As ComboBox.ObjectCollection
Get
Return MyBase.Items
End Get
End Property
Private WithEvents _ItemCollection As New ObservableCollection(Of String)
Public Property ItemCollection As ObservableCollection(Of String)
Get
Return _ItemCollection
End Get
Set(value As ObservableCollection(Of String))
_ItemCollection = value
End Set
End Property
Private _ItemDictionary As New Dictionary(Of String, Boolean)
Public ReadOnly Property ItemDictionary As Dictionary(Of String, Boolean)
Get
Return _ItemDictionary
End Get
End Property
Public ReadOnly Property CheckedItemCollection As List(Of String)
Get
Return New List(Of String)(From item In ItemDictionary Where item.Value = True Select item.Key)
End Get
End Property
Public ReadOnly Property UnCheckedItemCollection As List(Of String)
Get
Return New List(Of String)(From item In ItemDictionary Where item.Value = False Select item.Key)
End Get
End Property
Public Sub setCheckState(ByVal key As String, ByVal checkstate As Boolean)
_ItemDictionary(key) = checkstate
End Sub
Public Function getCheckState(ByVal key As String)
Return (_ItemDictionary(key))
End Function
Public Sub New()
Me.DrawMode = Windows.Forms.DrawMode.OwnerDrawVariable
End Sub
Protected Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
For Each item In ItemCollection
_ItemDictionary.Add(item, False)
Next
End Sub
Private Sub ItemsChanged(ByVal sender As Object, ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs) Handles _ItemCollection.CollectionChanged
Select Case e.Action
Case Specialized.NotifyCollectionChangedAction.Add
If e.NewStartingIndex = ItemDictionary.Count Then
_ItemDictionary.Add(e.NewItems(0), False)
MyBase.Items.Add(e.NewItems(0))
End If
Case Specialized.NotifyCollectionChangedAction.Remove
_ItemDictionary.Remove(MyBase.Items(e.OldStartingIndex))
MyBase.Items.RemoveAt(e.OldStartingIndex)
Case Specialized.NotifyCollectionChangedAction.Move
Dim _item As Object = MyBase.Items(e.OldStartingIndex)
MyBase.Items.RemoveAt(e.OldStartingIndex)
MyBase.Items.Insert(e.NewStartingIndex, _item)
Case Specialized.NotifyCollectionChangedAction.Replace
Throw New Exception("Not implemented yet!")
Case Specialized.NotifyCollectionChangedAction.Reset
Dim _checkeditems As New List(Of String)(CheckedItemCollection)
MyBase.Items.Clear()
MyBase.Items.AddRange(_ItemCollection.ToArray)
_ItemDictionary.Clear()
For Each item In _ItemCollection
_ItemDictionary.Add(item, _checkeditems.Contains(item))
Next
End Select
Me.Invalidate()
End Sub
Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
e.DrawBackground()
Dim p As Point = e.Bounds.Location
If e.Index >= 0 Then
p.Offset(1, 1)
If getCheckState(MyBase.Items(e.Index)) Then
CheckBoxRenderer.DrawCheckBox(e.Graphics, p, VisualStyles.CheckBoxState.CheckedNormal)
Else
CheckBoxRenderer.DrawCheckBox(e.Graphics, p, VisualStyles.CheckBoxState.UncheckedNormal)
End If
p.Offset(12, 0)
e.Graphics.DrawString(MyBase.GetItemText(Me.Items(e.Index)), e.Font, New SolidBrush(e.ForeColor), p.X, p.Y)
End If
If e.State = DrawItemState.Selected Then
e.DrawFocusRectangle()
End If
MyBase.OnDrawItem(e)
End Sub
Private Sub checkedChanged(ByVal index As Integer)
Dim checked As Boolean = _ItemDictionary(MyBase.Items.Item(index))
If checked Then
_ItemDictionary(MyBase.Items.Item(index)) = False
RaiseEvent ItemCheck(Me, New ItemCheckEventArgs(index, CheckState.Unchecked, CheckState.Checked))
Else
_ItemDictionary(MyBase.Items.Item(index)) = True
RaiseEvent ItemCheck(Me, New ItemCheckEventArgs(index, CheckState.Checked, CheckState.Unchecked))
End If
Me.Invalidate()
End Sub
Private n As nWindow = Nothing
Private Const WM_CTLCOLORLISTBOX As Integer = &H134
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
If m.Msg = WM_CTLCOLORLISTBOX Then
If n Is Nothing Then
n = New nWindow(Me)
n.AssignHandle(m.LParam)
AddHandler n.checkedChanged, AddressOf checkedChanged
End If
End If
End Sub
Private Sub CheckedCombobox_Click(sender As Object, e As System.EventArgs) Handles Me.SelectedIndexChanged
Debugger.Break()
End Sub
End Class
Public Class nWindow
Inherits NativeWindow
Private Const WM_LBUTTONDOWN As Integer = &H201
Private _combobox As CheckedCombobox
Public Event checkedChanged(ByVal index As Integer)
Public Sub New(ByVal cb As CheckedCombobox)
_combobox = cb
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = WM_LBUTTONDOWN Then
Dim itemHeight As Integer = _combobox.ItemHeight
If New Point(m.LParam.ToInt32).Y \ itemHeight <= _combobox.Items.Count - 1 And New Point(m.LParam.ToInt32).Y \ itemHeight >= 0 Then
If New Point(m.LParam.ToInt32).X >= 1 And New Point(m.LParam.ToInt32).X <= 11 Then
RaiseEvent checkedChanged(_combobox.SelectedIndex)
End If
End If
End If
MyBase.WndProc(m)
End Sub
End Class
The code below seems to work correctly. It does what I set out to do.
(Note: I have only shown the changed routines)
Private Sub checkedChanged(ByVal index As Integer)
Dim checked As Boolean = _ItemDictionary(MyBase.Items.Item(index))
If checked Then
_ItemDictionary(MyBase.Items.Item(index)) = False
RaiseEvent ItemCheck(Me, New ItemCheckEventArgs(index, CheckState.Unchecked, CheckState.Checked))
Else
_ItemDictionary(MyBase.Items.Item(index)) = True
RaiseEvent ItemCheck(Me, New ItemCheckEventArgs(index, CheckState.Checked, CheckState.Unchecked))
End If
Me.SelectedIndex = -1
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = WM_LBUTTONDOWN Then
Dim itemHeight As Integer = _combobox.ItemHeight
If New Point(m.LParam.ToInt32).Y \ itemHeight <= _combobox.Items.Count - 1 And New Point(m.LParam.ToInt32).Y \ itemHeight >= 0 Then
If New Point(m.LParam.ToInt32).X >= 1 And New Point(m.LParam.ToInt32).X <= 11 Then
RaiseEvent checkedChanged(_combobox.SelectedIndex)
Return
End If
End If
End If
MyBase.WndProc(m)
End Sub
I am not hundred percent sure about not calling MyBase.WndProc(), but I did not notice any side-effects yet.