I found a solution here on how to set an image for a DataGridViewButtonColumn. I set the FlatStyle of the column to Flat and now I want to get rid of the boarder around the button (the Delete button) but there does not seem to be a BorderSize property for DataGridViewButtonColumn which I would have set to 0.
To draw the button image, I use
If dgvPaymentList.Columns(e.ColumnIndex).Name = "xDelete" AndAlso e.RowIndex >= 0 Then
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
e.Graphics.DrawImage(My.Resources.delete16White, CInt((e.CellBounds.Width / 2) - (My.Resources.delete16White.Width / 2)) + e.CellBounds.X, CInt((e.CellBounds.Height / 2) - (My.Resources.delete16White.Height / 2)) + e.CellBounds.Y)
e.Handled = True
End If
Any help?
A DataGridViewButtonCell with the FlatStyle property set to FlatStyle.Flat uses the cell's ForeColor and SelectionForeColor to draw the border part. You can find that in the source code, how the border color is selected and passed to the draw border method. Hence, the easiest workaround to get rid of the border is to assign the cell's BackColor to the ForeColor and SelectionForeColor properties.
Use the designer to apply that or through the code:
Public Class YourForm
Private ReadOnly img As Bitmap
Sub New()
InitializeComponent()
img = My.Resources.delete16White
Dim c = Color.FromArgb(64, 64, 64)
' Your button column...
xDelete.DefaultCellStyle.BackColor = c
xDelete.DefaultCellStyle.SelectionBackColor = c
xDelete.DefaultCellStyle.ForeColor = c
xDelete.DefaultCellStyle.SelectionForeColor = c
End Sub
Protected Overrides Sub OnClosed(e As EventArgs)
MyBase.OnClosed(e)
img.Dispose()
End Sub
Private Sub dgvPaymentList_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles dgvPaymentList.CellPainting
If e.RowIndex >= 0 AndAlso e.ColumnIndex = dgvcButton.Index Then
Dim r = e.CellBounds
Dim imgRec = New Rectangle(
r.X + (r.Width - img.Width) \ 2,
r.Y + (r.Height - img.Height) \ 2,
img.Width, img.Height)
e.Paint(e.ClipBounds, DataGridViewPaintParts.All)
e.Graphics.DrawImage(img, imgRec)
e.Handled = True
End If
End Sub
End Class
Alternatively, takeover and draw the whole thing yourself the way you like:
Private Sub dgvPaymentList_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles dgvPaymentList.CellPainting
If e.RowIndex >= 0 AndAlso e.ColumnIndex = xDelete.Index Then
Dim r = e.CellBounds
Dim imgRec = New Rectangle(
r.X + (r.Width - img.Width) \ 2,
r.Y + (r.Height - img.Height) \ 2,
img.Width, img.Height)
e.PaintBackground(e.ClipBounds, False)
If e.CellBounds.Contains(dgvPaymentList.PointToClient(MousePosition)) Then
Dim backColor As Color
If MouseButtons = MouseButtons.Left Then
backColor = Color.FromArgb(104, 104, 104)
Else
backColor = Color.FromArgb(84, 84, 84)
End If
Using br As New SolidBrush(backColor)
e.Graphics.FillRectangle(br, r)
End Using
End If
e.Graphics.DrawImage(img, imgRec)
e.Handled = True
End If
End Sub
Without the full code it's a little hard to help you...
Try removing the following line:
e.Paint(e.CellBounds, DataGridViewPaintParts.All)
In fact, if the column was obtained with a code similar to this
Dim btn As New DataGridViewButtonColumn() With {
.HeaderText = "Example",
.Text = "Click Me",
.UseColumnTextForButtonValue = True,
.FlatStyle = FlatStyle.Flat 'Or others
}
DataGridView1.Columns.Add(btn)
the e.Paint line will draw the button (border + fillcolor + text). Without it, however, the button design will not be rendered.
You may need to draw a rectangle behind the image for the background color.
Related
I'm currently working on a WinForms App in vb.net, where you can select different data in a combobox. The items inside these combobox can be edited or deleted, so heres my question: Is there a way to add icons, e.g. a pencil and a trash, for each item to show the user "Click here to edit" or "Click here to delete"?
In my head it looks kind of the following picture:
Thank you very much :)
I created a new combo box control by inheriting a class from ComboBox.
Imports System.ComponentModel
Public Class ComboBoxEx
Inherits ComboBox
...
End Class
The idea is to use the DrawMode DrawMode.OwnerDrawFixed and to do all the drawing in code. This allows us to draw the images representing the clickable buttons. I added two images as resources to the project (My.Resources.pencil and My.Resources.Trash_16x16, yours might have different names).
Const IconSize = 20
Dim stringFormat As StringFormat = New StringFormat() With {.LineAlignment = StringAlignment.Center}
Public Sub New()
DrawMode = DrawMode.OwnerDrawFixed
DropDownStyle = ComboBoxStyle.DropDownList
ItemHeight = 21
End Sub
Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
e.DrawBackground()
If e.Index >= 0 Then
Dim g As Graphics = e.Graphics
Dim brushColor = If(((e.State And DrawItemState.Selected) <> 0),
SystemColors.Highlight,
e.BackColor)
Using brush As Brush = New SolidBrush(brushColor)
g.FillRectangle(brush, e.Bounds)
End Using
Using textBrush As Brush = New SolidBrush(e.ForeColor)
g.DrawString(Items(e.Index).ToString(), e.Font, textBrush, e.Bounds, stringFormat)
End Using
' Skip the default item at index = 0 and the text box area (DrawItemState.ComboBoxEdit)
If e.Index > 0 And (e.State And DrawItemState.ComboBoxEdit) = 0 Then
Dim image = My.Resources.pencil
Dim point = New Point(
Width - 2 * IconSize + (IconSize - image.Width) \ 2,
e.Bounds.Y + (ItemHeight - image.Height) \ 2)
g.DrawImage(image, point)
image = My.Resources.Trash_16x16
point = New Point(
Width - IconSize + (IconSize - image.Width) \ 2,
e.Bounds.Y + (ItemHeight - image.Height) \ 2)
g.DrawImage(image, point)
End If
End If
e.DrawFocusRectangle()
End Sub
This was the visual part. Now we must detect mouse clicks on the buttons in the drop down and also raise events when they are clicked.
Dim isDroppedDown As Boolean
Public Event Button1Clicked()
Public Event Button2Clicked()
Protected Overrides Sub OnDropDown(e As EventArgs)
isDroppedDown = True
MyBase.OnDropDown(e)
End Sub
Protected Overrides Sub OnDropDownClosed(e As EventArgs)
isDroppedDown = False
MyBase.OnDropDownClosed(e)
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Const WM_COMMAND = &H111
If LicenseManager.UsageMode = LicenseUsageMode.Runtime And isDroppedDown And
m.Msg = WM_COMMAND And (CType(m.WParam, Int64) >> 16) = 1 Then
Dim button = ButtonClicked()
' If the user clicked a button (skipping default item)
If button <> 0 And SelectedIndex > 0 Then
m.Result = New IntPtr(1)
If button = 1 Then
RaiseEvent Button1Clicked()
Else
RaiseEvent Button2Clicked()
End If
Return
End If
End If
MyBase.WndProc(m)
End Sub
Private Function ButtonClicked() As Integer
Dim pos = PointToClient(MousePosition)
If pos.X > Size.Width - IconSize Then
Return 2
ElseIf pos.X > Size.Width - 2 * IconSize Then
Return 1
End If
Return 0
End Function
After compiling your project, this new ComboBoxEx appears in the winforms toolbox and you can drag and drop it to your form.
In the form you can then handle the button events
Private Sub ComboBoxEx1_Button1Clicked() Handles ComboBoxEx1.Button1Clicked
Label1.Text = $"Pen clicked. Item = {ComboBoxEx1.SelectedItem.ToString()}"
End Sub
Private Sub ComboBoxEx1_Button2Clicked() Handles ComboBoxEx1.Button2Clicked
Label1.Text = $"Trash bin clicked. Item = {ComboBoxEx1.SelectedItem.ToString()}"
End Sub
You may have to tweak the icon size, text size etc. to fit your needs.
I currently have buttons in panel that is located in every cell of a tablelayoutpanel. I want to be able to hold my left mouse button down and drag across the tablelayoutpanel and highlight all the buttons as my mouse goes over in red.
My code to create the Panel and buttons. The tablelayout panel is already created.
Private Sub LoadHandMatrix(HandMatrix As TableLayoutPanel)
Dim Hands As New List(Of String)()
Dim NRow As Integer = 12
Dim NCol As Integer = 12
Dim HandBtnArray((NRow + 1) * (NCol + 1) - 1) As Button
Dim FrequencyBtnArray((NRow + 1) * (NCol + 1) - 1) As Button
Dim PanelArray((NRow + 1) * (NCol + 1) - 1) As Panel
Hands = MatrixHands()
With HandMatrix
.Height = 1800
.Width = 1800
End With
For i As Integer = 0 To HandBtnArray.Length - 1
PanelArray(i) = New Panel
PanelArray(i).Name = "p" + Str(i)
PanelArray(i).Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or AnchorStyles.Left Or AnchorStyles.Right
PanelArray(i).BackColor = Color.White
HandMatrix.Controls.Add(PanelArray(i), i Mod (NCol + 1), i \ (NCol + 1))
AddHandler PanelArray(i).Click, AddressOf panel_Click
FrequencyBtnArray(i) = New Button
FrequencyBtnArray(i).Name = "f" + Str(i)
FrequencyBtnArray(i).TextAlign = ContentAlignment.TopLeft
FrequencyBtnArray(i).FlatStyle = FlatStyle.Flat
FrequencyBtnArray(i).FlatAppearance.BorderSize = 0
FrequencyBtnArray(i).FlatAppearance.MouseOverBackColor = Color.White
FrequencyBtnArray(i).FlatAppearance.MouseDownBackColor = Color.White
FrequencyBtnArray(i).Text = 0
FrequencyBtnArray(i).Height = 30
FrequencyBtnArray(i).Width = 45
FrequencyBtnArray(i).Location = New Point(87, 102)
FrequencyBtnArray(i).Font = New Font("Segoe UI", 6)
FrequencyBtnArray(i).BackColor = Color.White
FrequencyBtnArray(i).ForeColor = Color.Black
PanelArray(i).Controls.Add(FrequencyBtnArray(i))
HandBtnArray(i) = New Button()
HandBtnArray(i).Name = "h" + Str(i)
HandBtnArray(i).Text = Hands(i)
HandBtnArray(i).FlatStyle = FlatStyle.Flat
HandBtnArray(i).FlatAppearance.BorderSize = 0
HandBtnArray(i).FlatAppearance.MouseOverBackColor = Color.Yellow
HandBtnArray(i).BackColor = Color.White
HandBtnArray(i).TextAlign = ContentAlignment.TopCenter
HandBtnArray(i).Font = New Font("Segoe UI", 14)
HandBtnArray(i).Dock = DockStyle.Fill
PanelArray(i).Controls.Add(HandBtnArray(i))
AddHandler HandBtnArray(i).MouseDown, AddressOf X_MouseDown
Next
End Sub
I added an event handeler "AddHandler HandBtnArray(i).MouseDown, AddressOf X_MouseDown" for the mouse down event located at the bottom of the code.
Public Sub X_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs)
sender.backcolor = Color.Red
End Sub
This event only works when I let off the mouse button. I included a picture of my hand matrix
I want to for example click on AA in the top left and hold and drag my mouse over the different hands and change the backcolor as the mouse goes over them while holding the left mouse button. I tried using the on enter event and it worked well but obviously it just changes everything without clicking the mouse. Also tried working with MouseEventArgs but ran into problems with EventArgs.
Each question can contain only one question. I will address the first question only. Ask a new question for the other question.
Notice in the MouseDown event procedure.
sender As Object
An Object does not have a BackColor property. This would be obvious if you had Option Strict On which you should always have it On. You can find this setting in the Project Properties on the Compile tab.
To solve this problem, cast sender to Button, then assign the color.
Private Sub X_MouseDown(sender As Object, e As MouseEventArgs) Handles Button2.MouseDown
Dim b = DirectCast(sender, Button)
b.BackColor = Color.Red
End Sub
I am new to VB and run into some problems.
I have created sub routine that will automatically add a control to a panelcontrol each time i click on the button, so it can create as many as i want.
Here is the code for the subroutine.
Private Sub CreateControl()
'CREATE TEXTBOX ITEMNO
Dim i_Itemno As Integer = TextEditItemno.Length
ReDim Preserve TextEditItemno(i_Itemno)
TextEditItemno(i_Itemno) = New TextEdit
With TextEditItemno(i_Itemno)
.Name = "Txtitemno" & i_Itemno.ToString()
If TextEditItemno.Length < 2 Then
.SetBounds(0, 0, 32, 20)
Else
.Left = TextEditItemno(i_Itemno - 1).Left
.Top = TextEditItemno(i_Itemno - 1).Top + TextEditItemno(i_Itemno - 1).Height + 4
.Size = TextEditItemno(i_Itemno - 1).Size
End If
.Tag = i_Itemno
End With
AddHandler TextEditItemno(i_Itemno).TextChanged, AddressOf TextEditItemno_TextChanged
PanelControl5.Controls.Add(TextEditItemno(i_Itemno))
'CREATE TEXTBOX PRICE
Dim i_Price As Integer = TextEditPrice.Length
ReDim Preserve TextEditPrice((i_Price))
Dim PriceX As Int16 = LblHarga.Location.X
TextEditPrice(i_Price) = New TextEdit
With TextEditPrice(i_Price)
.Name = "Txtprice" & i_Price.ToString()
If TextEditSatuan.Length < 2 Then
.SetBounds(PriceX, 0, 70, 20)
Else
.Left = TextEditPrice(i_Price - 1).Left
.Top = TextEditPrice(i_Price - 1).Top + TextEditPrice(i_Price - 1).Height + 4
.Size = TextEditPrice(i_Price - 1).Size
End If
.Tag = i_Price
End With
AddHandler TextEditPrice(i_Price).TextChanged, AddressOf TextEditPrice_TextChanged
PanelControl5.Controls.Add(TextEditPrice(i_Price))
End Sub
And i call it in a button click.
Private Sub btnNew_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnNew.Click
CreateControl()
End Sub
Now what i am looking for is how to loop and get the value of those textboxes no matter how many textboxex i have create.
For i As Integer = 0 To TextEditItemno.Length - 1
' code to get the value of each textbox
Next
Thank you
This code goes in to your loop and gets the value of each textbox based on i.
Dim Text as String = TextEditItemno(i).Text
You may also be better served by using a List(of Textbox) rather than an array of textboxes. You don't need to worry about redimming the array, you can just do MyListOfTextboxes.Add(TheNewTextBox). You can still retrieve the value of each textbox the same way as the array.
Is it possible to use an image as the checkbox "checked" indicator square?
I know I can use a background image, but that goes behind the label aswell and also it is not possible (as far as I know) to align it.
How can I use an image instead of the square and leave the label and all other customization as they are?
Thanks in advance!
You look like this?
Dim frm As New Form
frm.Size = New Size(320, 200)
Dim iList As New ImageList
iList.Images.Add(Image.FromFile("check.png"), Color.White)
iList.Images.Add(Image.FromFile("uncheck.png"), Color.White)
Dim chk As New CheckBox
chk.Text = "Check Box With Image"
chk.AutoSize = False
chk.Size = New Size(350, 20)
chk.ImageList = iList
chk.ImageIndex = 1
chk.CheckAlign = ContentAlignment.MiddleRight
chk.ImageAlign = ContentAlignment.MiddleLeft
chk.TextImageRelation = TextImageRelation.ImageBeforeText
chk.Location = New Point(32, 32)
frm.Controls.Add(chk)
AddHandler chk.CheckStateChanged,
Sub(sender1 As Object, e1 As EventArgs)
chk.ImageIndex = IIf(chk.Checked, 0, 1)
End Sub
frm.ShowDialog()
UPDATE #1: Actually, #brahm solution's below is much better than mine!
UPDATE #2: Actually, it's not. Now I see how he did it: he's moving the checkbox out of sight by placing it way off the visible Form's area. Not a great solution...
The ideal solution would be to subclass the CheckBox control and do your own rendering by overriding the OnPaint method.
An easier, although probably messier solution, would be to place a PictureBox over the check box and control the image in the PictureBox through the CheckBox's CheckedChange event.
Another option:
You could still use the CheckBox in button mode (Appearance = Button), as you suggested, but then add a label right next to it.
Then, handle the Click event on the Label to toggle the Checked state of the CheckBox. Then end result should provide you exactly what you are looking for.
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Windows.Forms.VisualStyles
Public Class ImageCheckBox
Public State As CheckBoxState = CheckBoxState.UncheckedNormal
Public Hot As Boolean = False
Public Pressed As Boolean = False
Public ImageDictionary As Dictionary(Of CheckBoxState, Image) = New Dictionary(Of CheckBoxState, Image)
Private Const PaddingModifier As Integer = 2
Sub New()
Me.New(New Dictionary(Of CheckBoxState, Image) From {
{CheckBoxState.CheckedDisabled, My.Resources.form_checkbox_checked},
{CheckBoxState.CheckedHot, My.Resources.form_checkbox_checked},
{CheckBoxState.CheckedNormal, My.Resources.form_checkbox_checked},
{CheckBoxState.CheckedPressed, My.Resources.form_checkbox_checked},
{CheckBoxState.UncheckedDisabled, My.Resources.form_checkbox_unchecked},
{CheckBoxState.UncheckedHot, My.Resources.form_checkbox_unchecked},
{CheckBoxState.UncheckedNormal, My.Resources.form_checkbox_unchecked},
{CheckBoxState.UncheckedPressed, My.Resources.form_checkbox_unchecked}})
End Sub
Sub New(imageDictionary As Dictionary(Of CheckBoxState, Image))
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.ImageDictionary = imageDictionary
End Sub
Sub CheckBox_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
'Return if the specific Image is not found
If Not ImageDictionary.ContainsKey(State) Then Return
'Get the Size of the CheckBox
Dim glyphSize As Size = CheckBoxRenderer.GetGlyphSize(e.Graphics, State)
'Get the Location of the CheckBox in relation to the Alignment of it
Dim glyphLocation As Point
Select Case Me.CheckAlign
Case Drawing.ContentAlignment.TopLeft
glyphLocation = New Point(Me.Padding.Left, Me.Padding.Top)
Exit Select
Case Drawing.ContentAlignment.TopCenter
glyphLocation = New Point(Me.Padding.Left + (Me.Width - glyphSize.Width) / 2, Me.Padding.Top)
Exit Select
Case Drawing.ContentAlignment.TopRight
glyphLocation = New Point(Me.Padding.Left + Me.Width - glyphSize.Width, Me.Padding.Top)
Exit Select
Case Drawing.ContentAlignment.MiddleLeft
glyphLocation = New Point(Me.Padding.Left, Me.Padding.Top + (Me.Height - glyphSize.Height) / 2)
Exit Select
Case Drawing.ContentAlignment.MiddleCenter
glyphLocation = New Point(Me.Padding.Left + (Me.Width - glyphSize.Width) / 2, Me.Padding.Top + (Me.Height - glyphSize.Height) / 2)
Exit Select
Case Drawing.ContentAlignment.MiddleRight
glyphLocation = New Point(Me.Padding.Left + Me.Width - glyphSize.Width, Me.Padding.Top + (Me.Height - glyphSize.Height) / 2)
Exit Select
Case Drawing.ContentAlignment.BottomLeft
glyphLocation = New Point(Me.Padding.Left, Me.Padding.Top + Me.Height - glyphSize.Height)
Exit Select
Case Drawing.ContentAlignment.BottomCenter
glyphLocation = New Point(Me.Padding.Left + (Me.Width - glyphSize.Width) / 2, Me.Padding.Top + Me.Height - glyphSize.Height)
Exit Select
Case Drawing.ContentAlignment.BottomRight
glyphLocation = New Point(Me.Padding.Left + Me.Width - glyphSize.Width, Me.Padding.Top + Me.Height - glyphSize.Height)
Exit Select
End Select
'Set the drawing Area
Dim glyphRectangle As Rectangle = New Rectangle(glyphLocation, glyphSize)
'Enlarge the Rectangle to completely hide default symbol
Dim clearRectangle As Rectangle = New Rectangle(glyphLocation.X - PaddingModifier,
glyphLocation.Y - PaddingModifier,
glyphSize.Width + 2 * PaddingModifier,
glyphSize.Height + 2 * PaddingModifier)
'Draw the Parent Background over the default CheckBox to clear it
CheckBoxRenderer.DrawParentBackground(e.Graphics, clearRectangle, Me)
Debug.WriteLine(State)
'Finally draw the custom CheckBox image on the position of the default one
e.Graphics.DrawImage(ImageDictionary(State), glyphRectangle)
End Sub
Sub CheckBox_MouseClick(sender As Object, e As EventArgs) Handles Me.MouseClick
Me.Checked = Not Me.Checked
End Sub
Sub CheckBox_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
Me.Pressed = True
End Sub
Sub CheckBox_MouseUp(sender As Object, e As MouseEventArgs) Handles Me.MouseUp
Me.Pressed = False
End Sub
Sub CheckBox_MouseEnter(sender As Object, e As EventArgs) Handles Me.MouseEnter
Me.Hot = True
End Sub
Sub CheckBox_MouseLeave(sender As Object, e As EventArgs) Handles Me.MouseLeave
Me.Hot = False
End Sub
Public Sub updateState() Handles Me.MouseClick, Me.MouseDown, Me.MouseUp, Me.MouseEnter, Me.MouseLeave, Me.EnabledChanged
Debug.WriteLine(Me.Checked & " " & Me.Enabled & " " & Me.Hot & " " & Me.Pressed)
Me.State = CurrentState()
Me.Refresh()
Debug.WriteLine(State)
End Sub
Public Function CurrentState() As CheckBoxState
If (Me.Checked) Then
If (Not Me.Enabled) Then Return CheckBoxState.CheckedDisabled
If (Me.Pressed) Then Return CheckBoxState.CheckedPressed
If (Me.Hot) Then Return CheckBoxState.CheckedHot
Return CheckBoxState.CheckedNormal
Else
If (Not Me.Enabled) Then Return CheckBoxState.UncheckedDisabled
If (Me.Pressed) Then Return CheckBoxState.UncheckedPressed
If (Me.Hot) Then Return CheckBoxState.UncheckedHot
Return CheckBoxState.UncheckedNormal
End If
End Function
End Class
I also had this problem with a mute/unmute audiotrack i first went for the CheckBox but deceided to just use the PictureBox click event and used .location to get the New Point overlay the other PictureBox and enable the visibility of the one or the other box, works fine for a complete newb that i am :-)
Picture of the PictureBox in the Designer
Private Sub PictureBoxMute_Click(sender As Object, e As EventArgs) Handles PictureBoxMute.Click
PictureBoxMute.Visible = False
PictureBoxUnmute.Location = New Point(590, 433)
PictureBoxUnmute.Visible = True
Volume = 0
myplayer.Volume = Volume
End Sub
Private Sub PictureBoxUnmute_Click(sender As Object, e As EventArgs) Handles PictureBoxUnmute.Click
PictureBoxUnmute.Visible = False
PictureBoxMute.Visible = True
Volume = 1
myplayer.Volume = Volume
End Sub
Is there an easy way to get the nearest control to a control of choice?
I have a picture box and some other moving controls. I want to delete the nearest control to my picture box.
So I have to get the position of all controls and delete that with the Location nearest to the Location of my picture box. I'm not sure about how to do that the best way.
If "closest" in your case indeed means "with the Location nearest to the Location of my picture box", then the easiest would be:
Me.Controls.Remove((From c In Me.Controls.Cast(Of Control)() Order By c.Location.Distance(PictureBox1.Location) Select c).Skip(1).Take(1)(0))
, where Distance is defined in a module like this:
<System.Runtime.CompilerServices.Extension()> _
Public Function Distance(ByVal p1 As Point, ByVal p2 As Point) As Integer
Return (p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y)
End Function
Here's an entire sample form that calculates "closest" by checking if the supplied control is in 1 of 8 regions relative to the base control. Half of the code is setup trying to mimic the scenario you described. The MainButton_Click and below is the meat of the work.
Option Explicit On
Option Strict On
Public Class Form1
Private MainPB As PictureBox
Private OtherPB As List(Of PictureBox)
Private WithEvents MainButton As Button
Private Rnd As New Random()
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Setup the form with sample data
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
Me.MainButton = New Button()
Me.MainButton.Text = "Update"
Me.MainButton.Left = 1
Me.MainButton.Top = 20
Me.Controls.Add(Me.MainButton)
Me.Width = 1000
Me.Height = 1000
MainPB = New PictureBox()
MainPB.BackColor = Color.Red
MainPB.Width = 100
MainPB.Height = 100
MainPB.Left = (Me.Width \ 2) - (MainPB.Width \ 2)
MainPB.Top = (Me.Height \ 2) - (MainPB.Height \ 2)
Me.Controls.Add(MainPB)
Me.OtherPB = New List(Of PictureBox)
For I = 0 To 50
Me.OtherPB.Add(New PictureBox())
With Me.OtherPB(I)
.BackColor = Color.Transparent
.BorderStyle = BorderStyle.FixedSingle
.Width = 50
.Height = 50
End With
SetRandomPbLocation(Me.OtherPB(I))
Me.Controls.Add(Me.OtherPB(I))
Next
End Sub
Private Sub SetRandomPbLocation(ByVal pb As PictureBox)
'Just sets a random location for the picture boxes and ensures that it doesn't overlap with the center PB
Do While True
pb.Left = Rnd.Next(1, Me.Width - pb.Width)
pb.Top = Rnd.Next(1, Me.Height - pb.Height)
If (pb.Right < Me.MainPB.Left OrElse pb.Left > Me.MainPB.Right) AndAlso (pb.Top > Me.MainPB.Bottom OrElse pb.Bottom < Me.MainPB.Top) Then
Exit Do
End If
Loop
End Sub
Private Sub MainButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MainButton.Click
'Randomizes the location of the picture boxes
For Each PB In Me.OtherPB
SetRandomPbLocation(PB)
Next
'Will hold the closest control after the loop
Dim ClosestPB As Control = Nothing
Dim ClosestD, TempD As Double
For Each PB In Me.OtherPB
'Reset the control's background color
PB.BackColor = Color.Transparent
'Calculate the distance
TempD = GetDistanceBetweenToControls(PB, Me.MainPB)
If ClosestPB Is Nothing Then 'If this is the first time through the loop then just use this control as the closest
ClosestPB = PB
ClosestD = TempD
ElseIf TempD < ClosestD Then 'Otherwise if this control is closer than the current closest
ClosestPB = PB
ClosestD = TempD
End If
Next
'Set the closest controls background color so that we can see it
ClosestPB.BackColor = Color.Blue
End Sub
Private Shared Function GetDistanceBetweenToControls(ByVal controlToCheck As Control, ByVal baseControl As Control) As Double
If controlToCheck.Bottom < baseControl.Top Then
If controlToCheck.Right < baseControl.Left Then 'Above and to the left
Return DistanceBetweenTwoPoints(New Point(controlToCheck.Right, controlToCheck.Bottom), baseControl.Location)
ElseIf controlToCheck.Left > baseControl.Right Then 'above and to the right
Return DistanceBetweenTwoPoints(New Point(controlToCheck.Left, controlToCheck.Bottom), New Point(baseControl.Right, baseControl.Top))
Else 'Above
Return baseControl.Top - baseControl.Bottom
End If
ElseIf controlToCheck.Top > baseControl.Bottom Then
If controlToCheck.Right < baseControl.Left Then 'Below and to the left
Return DistanceBetweenTwoPoints(New Point(controlToCheck.Right, controlToCheck.Top), New Point(baseControl.Left, baseControl.Bottom))
ElseIf controlToCheck.Left > baseControl.Right Then 'Below and to the right
Return DistanceBetweenTwoPoints(controlToCheck.Location, New Point(baseControl.Right, baseControl.Bottom))
Else 'Below
Return controlToCheck.Top - baseControl.Bottom
End If
Else
If controlToCheck.Right < baseControl.Left Then 'Left
Return baseControl.Left - controlToCheck.Right
ElseIf controlToCheck.Left > baseControl.Right Then 'Right
Return controlToCheck.Left - baseControl.Right
End If
End If
End Function
Private Shared Function DistanceBetweenTwoPoints(ByVal point1 As Point, ByVal point2 As Point) As Double
'Standard distance formula
Return Math.Sqrt((Math.Abs(point2.X - point1.X) ^ 2) + (Math.Abs(point2.Y - point1.Y) ^ 2))
End Function
End Class