Combining mouseEvents on label in VB.NET - vb.net

I want to combine MouseEnter with MousePressed on a label.
Public Sub populateGrid()
lblTest.BackColor()
lblTest.BackColor = System.Drawing.Color.Red
gbWorkflow.Controls.Add(lblTest)
For j As Integer = 1 To 40
For i As Integer = 1 To 20
Dim L As New Label
L.Size = New Size(30, 30)
L.Text = "L:" + i.ToString + j.ToString
L.BackColor = Color.AliceBlue
Dim x, y As Integer
Dim loc As Point = gbWorkflow.Location
y = loc.Y * (i * 8) '- (gbWorkflow.Height + L.Size.Height) * i
x = loc.X * (j * 8)
L.Location = New Point(x, y)
gbWorkflow.Controls.Add(L)
AddHandler L.MouseEnter, AddressOf L_Enter
AddHandler L.MouseLeave, AddressOf L_Leave
Next
Next
End Sub
Private Sub L_Enter(ByVal sender As Object, ByVal e As System.EventArgs)
Dim TheLabel As Label = CType(sender, Label)
TheLabel.BackColor = Color.Red
End Sub
My idea was to create a method that triggers on MouseDown and changes the value of a boolean variable. I would then use that variable as a condition to apply the changes to the labels. However, that doesnt seem to work...
How can I achieve this in the best way? Or, at all?
Pseudo-code:
When mouse enter label:
if left mousebutton is pressed then
do stuff with the label
Edit:
It also has to work when the mousebutton is still pressed and the cursor is dragged over several labels. All the labels that the cursor crosses while the left button is pressed should be changed.

The MouseDown event delivers what you are after. Sample code:
Private Sub L_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs)
If (e.Button = Windows.Forms.MouseButtons.Left) Then
'Do stuff
End If
End Sub
Adding the event handler:
AddHandler L.MouseDown, AddressOf L_MouseDown
--- UPDATE
As said, you cannot accomplish directly what you want but there are many alternative ways to deliver an equivalent performance. For example:
Boolean flag indicating whether one of the target labels has been clicked (MouseDown) + MouseEnter performing the modifications only if this flag is true. Sample code:
Private LWasClicked As Boolean = False
Private Sub L_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs)
If (e.Button = Windows.Forms.MouseButtons.Left And Not LWasClicked) Then
LWasClicked = True
End If
End Sub
Private Sub L_MouseEnter(sender As Object, e As System.EventArgs)
If (LWasClicked) Then
'Do stuff
End If
End Sub
With the code above, you can "activate the editing" by just clicking on any label (or on a specific one); once it is activated, you can just pass the mouse over any label and the actions will be performed. You will also have to set an event to de-activate this behaviour (example: new Click/MouseDown). As you can see, this delivers an equivalent performance to what you want and is compatible with how events work.
CLARIFICATION: I think that this (or any other alternative on these lines) delivers an excellent performance. If still you don't want that and prefer to do everything with the mouse-button pressed, you would have to rely on something different (e.g., position of the mouse on the screen, analysis triggered by other means; or even events from different threads). What is clear is that what you aim cannot be accomplished with one-thread events of different controls (a new event cannot be started before the previous one has ended).

Related

programmatically select and highlight a row of a ListView in VB.NET

I want to do something seemingly simple - programmatically select and highlight a row of a ListView in VB.NET.
VB.NET: How to dynamically select a list view item?
tells me what should to be all that is needed, but it isn't. The row is selected, but not highlighted.
http://vbcity.com/forums/t/28260.aspx
tells me about the "HideSelection" property and the .Focus() method (also referenced at Select programmatically a row of a Listview), which sounded hopeful, but the best I can get is the faint highlight mentioned, I want the full monty. I tried a Noddy example with just a ListView, in Details mode, FullRowSelection = true, HideSelection = False, one columnheader defined and then
ListView1.Items.Add("Red")
ListView1.Items.Add("Orange")
ListView1.Items.Add("Yellow")
ListView1.Items.Add("Green")
ListView1.Items(2).Selected = True
I get this
but I want this
I can simulate highlighting by adding these lines
ListView1.SelectedItems(0).BackColor = Color.CornflowerBlue
ListView1.SelectedItems(0).ForeColor = Color.White
but then how can I be sure to undo the artificial highlight if the row can be implicitly as well as explicitly deselected? Do I have to think of all the possible cases? That's too much work for what should be a simple operation. Plus, since I want to color-code my rows, there is an additional challenge that when I undo the highlight color, I have to figure out what color is appropriate at that point. Is there a better, simpler way?
You can access the Graphics object used to draw each item, and draw them yourself.
Make a new project with a Button and ListView. Paste the following code:
Form_Load to use multiple subitems
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.ListView1.OwnerDraw = True ' or else can't handle DrawItem event
ListView1.Columns.Add("ColumnHeader1")
ListView1.Columns.Add("ColumnHeader2")
ListView1.Columns.Add("ColumnHeader3")
Me.ListView1.Items.Add("Red")
Me.ListView1.Items.Add("Orange")
Me.ListView1.Items.Add("Yellow")
Me.ListView1.Items.Add("Green")
ListView1.Items(0).SubItems.Add("Strawberry")
ListView1.Items(0).SubItems.Add("Apple")
ListView1.Items(1).SubItems.Add("Pepper")
ListView1.Items(1).SubItems.Add("Apricot")
ListView1.Items(2).SubItems.Add("Plum")
ListView1.Items(2).SubItems.Add("Banana")
ListView1.Items(3).SubItems.Add("Apple")
ListView1.Items(3).SubItems.Add("Lime")
End Sub
Three handlers for the ListView's drawing related events. Code copied from this answer
Private Sub listView1_DrawColumnHeader(sender As Object, e As DrawListViewColumnHeaderEventArgs) Handles ListView1.DrawColumnHeader
e.DrawDefault = True
End Sub
Private Sub listView1_DrawSubItem(sender As Object, e As DrawListViewSubItemEventArgs) Handles ListView1.DrawSubItem
Const TEXT_OFFSET As Integer = 1
' I don't know why the text is located at 1px to the right. Maybe it's only for me.
Dim listView As ListView = DirectCast(sender, ListView)
' Check if e.Item is selected and the ListView has a focus.
If Not listView.Focused AndAlso e.Item.Selected Then
Dim rowBounds As Rectangle = e.SubItem.Bounds
Dim labelBounds As Rectangle = e.Item.GetBounds(ItemBoundsPortion.Label)
Dim leftMargin As Integer = labelBounds.Left - TEXT_OFFSET
Dim bounds As New Rectangle(rowBounds.Left + leftMargin, rowBounds.Top, If(e.ColumnIndex = 0, labelBounds.Width, (rowBounds.Width - leftMargin - TEXT_OFFSET)), rowBounds.Height)
Dim align As TextFormatFlags
Select Case listView.Columns(e.ColumnIndex).TextAlign
Case HorizontalAlignment.Right
align = TextFormatFlags.Right
Exit Select
Case HorizontalAlignment.Center
align = TextFormatFlags.HorizontalCenter
Exit Select
Case Else
align = TextFormatFlags.Left
Exit Select
End Select
TextRenderer.DrawText(e.Graphics, e.SubItem.Text, listView.Font, bounds, SystemColors.HighlightText, align Or TextFormatFlags.SingleLine Or TextFormatFlags.GlyphOverhangPadding Or TextFormatFlags.VerticalCenter Or TextFormatFlags.WordEllipsis)
Else
e.DrawDefault = True
End If
End Sub
Private Sub listView1_DrawItem(sender As Object, e As DrawListViewItemEventArgs) Handles ListView1.DrawItem
Dim listView As ListView = DirectCast(sender, ListView)
' Check if e.Item is selected and the ListView has a focus.
If Not listView.Focused AndAlso e.Item.Selected Then
Dim rowBounds As Rectangle = e.Bounds
Dim leftMargin As Integer = e.Item.GetBounds(ItemBoundsPortion.Label).Left
Dim bounds As New Rectangle(leftMargin, rowBounds.Top, rowBounds.Width - leftMargin, rowBounds.Height)
e.Graphics.FillRectangle(SystemBrushes.Highlight, bounds)
Else
e.DrawDefault = True
End If
End Sub
Button click handler to simulate item(2) selected
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.ListView1.Items(2).Selected = True
End Sub
This will draw the background color regardless of focus. You have a lot of control over other colors and fonts going this route too.
Here, the button has been clicked, to select item 2, while the button still has focus, and item 2 is selected.
Easiest thing,
Just allocate the LST_ItemIndex = lstList.FocusedItem.Index everytime you select a different item
and then fire the below whenever you want the highlight
If lstList.Items.Count > 0 Then
lstList.Items(LST_ItemIndex).Selected = True
lstList.Items(LST_ItemIndex).EnsureVisible()
End If

Enable other event while dragging an object

I'm developing a console where I want to drag a button to a grid:
To drag the button, I use the following procedure:
Public drag As Boolean = False
Public ptX As Integer = 0
Public ptY As Integer = 0
Public btn As Button
Private Sub MoveButton_MouseDown(sender As Object, e As MouseEventArgs) Handles MoveButton.MouseDown
drag = True
btn = CType(sender, Button)
ptX = e.X : ptY = e.Y
End Sub
Private Sub MoveButton_MouseMove(sender As Object, e As MouseEventArgs) Handles MoveButton.MouseMove
If drag Then
btn.Location = New Point(btn.Location.X + e.X - ptX, btn.Location.Y + e.Y - ptY)
Me.Refresh()
End If
End Sub
Private Sub MoveButton_MouseUp(sender As Object, e As MouseEventArgs) Handles MoveButton.MouseUp
drag = False
End Sub
So far, so good! This works fine for that matter.
However, I'm trying to highlight the cell while hoovering the button on it like this:
To do so, I tried to do the following:
Private Sub CellA1_MouseHover(sender As Object, e As EventArgs) Handles CellA1.MouseHover
If drag Then
CellA1.BackColor = Color.Red
End If
End Sub
Of course I can't do that, unless I, somehow, enable the CellA1.MouseHover event while dragging the MoveButton.
Can anyone help me with this?
If, however, you're having a struggling will to help me further, my last goal is to place the MoveButton on the red cell place:
But feel free to don't help me at all with this part of the procedure since I have no code to perform this yet.
Any help will be very appreciated. And, as always, thank you all in advance.
Since your mouse is not actually on the PictureBox when you drag the button it will never raise any mouse events. What you can do instead is to call the GetChildAtPoint() method of your form to get the control behind the button. Once you have that just verify that the name starts with "Cell" and change the back color.
To snap to the cell's location you'll need to indicate to MouseUp which cell we're currently at. Simply store the cell control in a variable and you can then just set yourButton.Location = currentCell.Location
Here are the changes I've made to your code, commented for clarity:
Public drag As Boolean = False
Public ptX As Integer = 0
Public ptY As Integer = 0
Public btn As Button
Public prevCtrl As Control = Nothing 'Store the previous Cell the button was dragged over.
' We need this to be able to reset the BackColor of the Cell,
' and also so that you can snap to its location once you drop the button.
Private Sub MoveButton_MouseDown(sender As Object, e As MouseEventArgs) Handles MoveButton.MouseDown
'No changes made here.
drag = True
btn = CType(sender, Button)
ptX = e.X : ptY = e.Y
End Sub
Private Sub MoveButton_MouseMove(sender As Object, e As MouseEventArgs) Handles MoveButton.MouseMove
If drag Then
btn.Location = New Point(btn.Location.X + e.X - ptX, btn.Location.Y + e.Y - ptY)
'Go 1 pixel up, or else GetChildAtPoint() will return the button instead of the control behind it.
Dim LookPoint As Point = Point.Subtract(btn.Location, New Size(0, 1))
'Get the control located below/behind the button.
Dim ControlBelow As Control = Me.GetChildAtPoint(LookPoint, GetChildAtPointSkip.Invisible Or GetChildAtPointSkip.Disabled) 'Ignore invisible or disabled controls.
'Check so that the previous cell is not also the current cell. If they're the same then we won't change anything.
If prevCtrl IsNot ControlBelow Then
'Ok, the current cell and the previous cell are not the same.
'Now check if there was any previous cell at all.
If prevCtrl IsNot Nothing Then
'There was a previous cell, but since the button
'is no longer hovering over it we reset its BackColor.
prevCtrl.BackColor = Color.White
prevCtrl = Nothing
End If
'Check that there infact is a control behind the button,
'and also check that its name starts with "Cell".
If ControlBelow IsNot Nothing AndAlso ControlBelow.Name.StartsWith("Cell", StringComparison.OrdinalIgnoreCase) Then
'The control behind the button is a valid Cell. Change its BackColor.
ControlBelow.BackColor = Color.Red
prevCtrl = ControlBelow 'The previous cell is now the current cell.
End If
End If
'Me.Refresh() - this is a very unnecessary call, it will just eat CPU. The form does not need to be redrawn at this point.
End If
End Sub
Private Sub MoveButton_MouseUp(sender As Object, e As MouseEventArgs) Handles MoveButton.MouseUp
'Check if we dragged the button. At this point prevCtrl is the current cell (if it's not Nothing).
If drag = True AndAlso prevCtrl IsNot Nothing Then
btn.Location = prevCtrl.Location 'Snap to the cell's location.
prevCtrl.BackColor = Color.White 'Reset the cell's BackColor.
prevCtrl = Nothing 'Reset this since we're no longer dragging the button.
End If
drag = False
End Sub
And it works like a charm!

Button Array - how to pass a parameter to shared handler

I have a bit of code where i have a dynamically created array or buttons with staff pictures on them, as well as the staff's name. I've added one handler to handle any button click from any of the buttons. where i am stuck is, if you look at the code below, it all works fine, and if you click any of the buttons you get the "aha" test message. but i want the name of the staff clicked on (so btnArray(i).Text) to be passed to the handler for further processing. I tried adding a ByVal parameter to the handler but that caused an error. what's the correct way to do this? As i said, the code below works for me, i just am at a loss as to how to add the extra functionality.
Dim btnArray(staffcount) As System.Windows.Forms.Button
For i As Integer = 1 To staffcount - 1
btnArray(i) = New System.Windows.Forms.Button
btnArray(i).Visible = True
btnArray(i).Width = 80
btnArray(i).Height = 101
btnArray(i).BackgroundImage = Image.FromFile(picloc(i))
btnArray(i).BackgroundImageLayout = ImageLayout.Stretch
btnArray(i).Text = staffname(i)
Dim who As String
who = btnArray(i).Text
AddHandler btnArray(i).Click, AddressOf Me.theButton_Click
btnArray(i).ForeColor = Color.White
btnArray(i).TextAlign = ContentAlignment.BottomCenter
Dim fnt As Font
fnt = btnArray(i).Font
btnArray(i).Font = New Font(fnt.Name, 10, FontStyle.Bold)
FlowLayoutPanel1.Controls.Add(btnArray(i))
Next i
End Sub
Private Sub theButton_Click()
MsgBox("aha")
End Sub
First, correct the signature of your shared handler.
Private Sub theButton_Click(sender As Object, e As EventArgs)
End Sub
Once that is done getting the text of the button clicked is a simple matter.
Private Sub theButton_Click(sender As Object, e As EventArgs)
Dim textOfButtonClicked As String = DirectCast(sender, Button).Text
MessageBox.Show(textOfButtonClicked)
End Sub
The sender is the button that was clicked. Since signatures use objects for the sender the DirectCast 'changes' it to button and you then can access the .Text property of the button.
If there are more manipulations you want to perform on the clicked button you could do it this way
Private Sub theButton_Click(sender As Object, e As EventArgs)
Dim whBtn As Button = DirectCast(sender, Button) ' get reference to button clicked
Dim textOfButtonClicked As String = whBtn.Text
MessageBox.Show(textOfButtonClicked)
'e.g. change the color
whBtn.BackColor = Color.LightYellow
End Sub

Update text in dynamically created label

I'm working on a proof of concept type situation that will eventually be tied to a scheduling database. As a Test I created this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'AddButton("test")
addLots()
End Sub
Private Sub AddLots()
Dim x As Integer
For x = 0 To 10
Dim b As New Button
Dim newLabel As New Label
newLabel.Location = New Point(100, x * 20)
newLabel.Name = x
newLabel.BorderStyle = BorderStyle.Fixed3D
newLabel.Text = newLabel.Name
Me.Controls.Add(newLabel)
Me.Controls.Add(b)
b.Location = New Point(20, x * 20)
b.Text = x
b.Tag = x
b.Name = x
AddHandler b.Click, AddressOf Button_Click
Next
End Sub
Private Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim B As Button = sender
MsgBox(B.Name)
End Sub
For this proof of concept, I simply want label 1 text to be updated when I press button 1 seems like a simple process but it's kicking my butt.
As for any object, to affect a Label you will need a reference to it. As it stands, the only reference you have is via the Controls collection of the parent control you added the Label to, i.e. the form itself. You could loop through the Controls of the form and as soon as you find a Label then you know you have the first one, or you could call OfType and First or FirstOrDefault. That assumes that there are no other Label controls on the form.
You might also consider using a dedicated parent control so that you know it will only contain the Label controls you created at run time. The obvious choice would be a TableLayoutPanel because it will handle the layout for you too.
If accessing the dynamic controls via a Controls collection is an issue then keep your own collection. Declare a member variable of type List(Of Label) and add each Label you create to it. You can then access your control from that collection and know that there are no other controls in there to get mixed up with.
By the way, if you're creating those controls at run time then they won't be automatically disposed when the form is. Make sure that you dispose them yourself and also use RemoveHandler for each AddHandler you used.
Actually, looking closer at your code, I just realised that there's a 1:1 correspondence between the Button and Label controls. It would make sense to use that. Two options are to assign the corresponding Label to the Tag of each Button or else use a Dictionary(Of Button, Label) assigned to a member variable to store the relationships. That way, you can then use the sender in the event handler, which will be the Button that was clicked, to get the corresponding Label.
Option 1.
Creating the Label:
Dim btn As New Button
Dim lbl As New Label
btn.Tag = lbl
In the event handler:
Dim btn = DirectCast(sender, Button)
Dim lbl = DirectCast(btn.Tag, Label)
Option 2.
At class level:
Private labelsByButton As New Dictionary(Of Button, Label)
Creating the Label:
Dim btn As New Button
Dim lbl As New Label
Me.labelsByButton.Add(btn, lbl)
In the event handler:
Dim btn = DirectCast(sender, Button)
Dim lbl = Me.labelsByButton(btn)
I added this to the button click event. Doesn't seem very efficient as I will eventually have 30-40 buttons and controls on the form but it works.
Private Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim B As Button = sender
Dim lblToChange As Integer = B.Name
For Each objCtrl As Control In Me.Controls
If TypeOf objCtrl Is Label Then
Dim Lbl As Label = DirectCast(objCtrl, Label)
If Lbl.Name = lblToChange Then
Lbl.Text = "This ONe"
End If
End If
Next
End Sub

Simple VB, handling many buttons

In my VB solution I have a lot of buttons arranged in a grid. When the program is loaded one of the buttons is randomly set to be the right one (You have to click that one to win). Can anybody point my in the right direction? Because there must be a better way than manually coding every single button, and creating an event handler for every single button.
You don't have to give me a working example, just an overall idea of how this is done.
Thanks.
If you want to arrange the buttons in a grid, either use the TableLayoutPanel or add the buttons directly to the form and calculate their positions. The TableLayoutPanel is useful if you want to arrange the buttons automatically when the form resizes, otherwise adding the buttons directly seems easier to me.
Add the buttons to an array defined at form level to make them easily accessible
Public Const NColumns As Integer = 5, NRows As Integer = 4
Private buttons As Button(,) = New Button(NColumns - 1, NRows - 1) {}
You can add the buttons easily in loops
For ix As Integer = 0 To NColumns - 1
For iy As Integer = 0 To NRows - 1
Dim btn = New Button()
btn.Text = String.Format("{0:d2}{1:d2}", ix, iy)
btn.Location = New Point(leftMargin + ix * xDistance,
topMargin + iy * yDistance)
btn.Size = New Size(buttonWidth, buttonHeight)
AddHandler btn.Click, Addressof Button_Clicked
buttons(ix, iy) = btn
Controls.Add(btn)
Next
Next
You can determine the winning button with a random generator. Define it as form member, not as local variable.
Private randomGenrator As System.Random = New System.Random()
Determine the coordinates
Dim xWins = randomGenrator.Next(NColumns) 'Returns a number between 0 and NColumns-1
Dim yWins = randomGenrator.Next(NRows)
The click handler looks like this
Private Sub Button Button_Clicked(sender As Object, e As EventArgs)
If sender = buttons(xWins, yWins) Then
'You win
Else
'You loose
End
End Sub
First, you said you want a grid of buttons, so you have to have a FlowLayoutPanel control in your form in order to let the buttons you want to add, to be arranged automatically.
Second, you have to make use of a for loop, r any kind of look, in order to add the buttons to be added to previously added 'FlowLayoutPanel'.
class Answers
Dim strAnswerText as string
Dim AnswerFlag as Boolean
End Class
Sub LoadForm(byval a_Answers as Answer())
Dim i as Integer = 0
Dim b as Button
For(i=0;i<NUM_OF_BUTTONS;i++)
b = New Button()
b.Text = "Choice -" & i & "- " & a_Answers(i).strAnswerText
b.Tag = a_Answers(i).AnswerFlag
'Supposing that the FlowLayoutPanel control name is fl
AddHandler b.Click, Addressof Clicked
fl.controls.Add(b)
End For
End Sub
Sub Button Clicked(sender as object, e as EventArgs)
if sender.Tag = True
'True answer
else
'Wrong answer
end if
End Sub