I'm developping a modular dashboard in Visual Basic that starts as an empty container than i can add modules to populate it.
The parent form is an MDI container and every module is a child MDI form that have fixed size.
I'd like to snap all of that child form to a grid both when the user creates a new one and when moves one of it inside the container (like they are magnetized to that grid).
How can i do that? Thanks
If I well understand you need something like this:
Private Class ImaginaryGrid
' You can change Columns and Rows as you want
Shared Rows As Integer = 3
Shared Cols As Integer = 3
Private Shared Function SnapToGrid(target As Form) As Point
Dim AllW As Integer = Screen.PrimaryScreen.WorkingArea.Width
Dim AllH As Integer = Screen.PrimaryScreen.WorkingArea.Height
Dim parent = target.MdiParent
If parent IsNot Nothing Then
AllW = target.MdiParent.ClientSize.Width
AllH = target.MdiParent.ClientSize.Height
End If
Dim currentPoint As Point = target.Location
Dim stepW As Integer = CInt(AllW / Cols)
Dim stepH As Integer = CInt(AllH / Rows)
Dim targetCol As Integer = CInt(currentPoint.X \ stepW)
Dim targetRow As Integer = CInt(currentPoint.Y \ stepH)
Dim newX As Integer = targetCol * stepW
Dim newY As Integer = targetRow * stepH
target.Location = New Point(newX, newY)
target.Width = stepW
target.Height = stepH
End Function
Shared Sub AttachFormToStayInGrid(frm As Form)
AddHandler frm.ResizeEnd, Sub()
SnapToGrid(frm)
End Sub
SnapToGrid(frm)
End Sub
End Class
Usage:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
ImaginaryGrid.AttachFormToStayInGrid(Me)
End Sub
Take changes (if needed) as comments below shows:
Here's a beefed up version based on the same idea as G3nt_M3caj's suggestion:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ImaginaryGrid.Client = Me.Controls.OfType(Of MdiClient).FirstOrDefault
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim frm As New Form
ImaginaryGrid.AttachFormToStayInGrid(frm)
End Sub
Private Class ImaginaryGrid
Public Shared WithEvents Client As MdiClient
Public Shared FixedChildSize As New Size(250, 150)
Private Shared Function SnapToGrid(target As Form) As Rectangle
Dim colX As Integer = target.Location.X / FixedChildSize.Width
Dim colY As Integer = target.Location.Y / FixedChildSize.Height
Dim newX As Integer = colX * FixedChildSize.Width
Dim newY As Integer = colY * FixedChildSize.Height
Return New Rectangle(New Point(newX, newY), FixedChildSize)
End Function
Shared Sub AttachFormToStayInGrid(frm As Form)
frm.Size = FixedChildSize
frm.FormBorderStyle = FormBorderStyle.FixedSingle
frm.MdiParent = Client.Parent
frm.Show()
SnapChild(frm)
AddHandler frm.ResizeEnd, Sub()
SnapChild(frm)
End Sub
AddHandler frm.LocationChanged, Sub()
If frm.WindowState = FormWindowState.Normal Then
snapRectangle = SnapToGrid(frm)
Client.Refresh()
End If
End Sub
End Sub
Private Shared Sub SnapChild(ByVal frm As Form)
If frm.WindowState = FormWindowState.Normal Then
Dim rc As Rectangle = SnapToGrid(frm)
frm.Bounds = rc
snapRectangle = Nothing
Client.Refresh()
End If
End Sub
Private Shared snapRectangle? As Rectangle
Private Shared Sub Client_Paint(sender As Object, e As PaintEventArgs) Handles Client.Paint
If snapRectangle.HasValue Then
e.Graphics.DrawRectangle(Pens.Black, snapRectangle)
End If
End Sub
End Class
End Class
Related
I've been Having trouble with making a sub that runs when my datagridview cell is doubleclicked. It is caused because the datagridview is programmatically created, rather than created by the designer. I have found a help website i will include that appears to be related to the issue.
Public Class seattemplatecreator
Dim alphabet() As Char = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()
Private WithEvents dgv_flightTemplate As DataGridView
'help from https://it.toolbox.com/question/event-for-dynamically-created-command-button-043008
Public Sub init(ByVal dgv01 As DataGridView)
dgv_flightTemplate = dgv01
End Sub
Private Sub dgv_flightTemplate_CellMouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles dgv_flightTemplate.CellMouseDoubleClick
MsgBox("workwd")
End Sub
Private Sub btn_createflight_Click(sender As Object, e As EventArgs) Handles btn_createflight.Click
'used https://social.msdn.microsoft.com/Forums/vstudio/en-US/e222f438-f060-4e61-ab28-523d02db91b2/how-to-programmatically-create-datagridview-with-empty-columns-and-rows?forum=vbgeneral
'to help with this part for automatically generating the datagridview
MsgBox(alphabet(0))
Dim dgv_flightTemplate As New DataGridView
Dim c As Integer = txb_columns.Text
Dim r As Integer = txb_rows.Text
For colcount As Integer = 0 To c - 1
Dim nc As New DataGridViewTextBoxColumn
nc.Name = "Seating Column"
dgv_flightTemplate.Columns.Add(nc)
Next
dgv_flightTemplate.Rows.Add(r)
For x = 0 To r - 1
dgv_flightTemplate.Rows(x).HeaderCell.Value = alphabet(x).ToString
Next
Me.Controls.Add(dgv_flightTemplate)
dgv_flightTemplate.Location = New Point(400, 400)
dgv_flightTemplate.AllowUserToAddRows = False
dgv_flightTemplate.AllowUserToDeleteRows = False
dgv_flightTemplate.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders
dgv_flightTemplate.AutoResizeRows()
dgv_flightTemplate.AutoSize = True
End Sub
End Class
https://it.toolbox.com/question/event-for-dynamically-created-command-button-043008
Edit: Olivier Jacot-Descombes response was perfect all that was needed was run the "Init" sub.
Public Class seattemplatecreator
Dim alphabet() As Char = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()
Private WithEvents dgv_flightTemplate As DataGridView
'help from https://it.toolbox.com/question/event-for-dynamically-created-command-button-043008
Public Sub init(ByVal dgv01 As DataGridView)
dgv_flightTemplate = dgv01
End Sub
Private Sub dgv_flightTemplate_CellMouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles dgv_flightTemplate.CellMouseDoubleClick
MsgBox("workwd")
End Sub
Private Sub btn_createflight_Click(sender As Object, e As EventArgs) Handles btn_createflight.Click
'used https://social.msdn.microsoft.com/Forums/vstudio/en-US/e222f438-f060-4e61-ab28-523d02db91b2/how-to-programmatically-create-datagridview-with-empty-columns-and-rows?forum=vbgeneral
'to help with this part for automatically generating the datagridview
MsgBox(alphabet(0))
Dim dgv_flightTemplate As New DataGridView
Dim c As Integer = txb_columns.Text
Dim r As Integer = txb_rows.Text
For colcount As Integer = 0 To c - 1
Dim nc As New DataGridViewTextBoxColumn
nc.Name = "Seating Column"
dgv_flightTemplate.Columns.Add(nc)
Next
dgv_flightTemplate.Rows.Add(r)
For x = 0 To r - 1
dgv_flightTemplate.Rows(x).HeaderCell.Value = alphabet(x).ToString
Next
Me.Controls.Add(dgv_flightTemplate)
dgv_flightTemplate.Location = New Point(400, 400)
dgv_flightTemplate.AllowUserToAddRows = False
dgv_flightTemplate.AllowUserToDeleteRows = False
dgv_flightTemplate.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders
dgv_flightTemplate.AutoResizeRows()
dgv_flightTemplate.AutoSize = True
init(dgv_flightTemplate)
End Sub
End Class
Any help would be greatly appreciated
Thanks, Taine
You are not calling Init. But instead, you could as well directly assign to the field
dgv_flightTemplate = New DataGridView 'Note: No Dim here.
But even if you are creating the columns dynamically, you could add a grid with the designer. Just call
dgv_flightTemplate.Columns.Clear()
before adding columns.
So I've been fiddling with this for a while and I don't know if I'm not understanding how the BackgroundWorker works and/or I'm using it wrong or if I'm missing something.
Basically what I'm trying to do is call a BackgroundWorker from a DragDrop function where the user can drop a set of images into the form. The BackgroundWorder then copies the images to a temp location thumbnails are pulled and turned into PictureBoxes and the PictureBoxes are added to a collection. Once the BackgroundWorker is completed the function runs to add all the picture boxes to the form.
All of this is working properly except the progress. The progress function doesn't like to fire until near the end (after almost all the pictures have been copied) during this time the UI is locked (which I'm sure is why the progress function isn't firing) I just can't figure out why the UI is locking.
I've stepped through the code and the ReportProgress method is being called ever loop but the ProgressReported function isn't called until near the end.
HELP! LOL
this is the ControlClass for my copying and creating thumbnails
Imports System.ComponentModel
Imports System.IO
Namespace ThumbnailViewer
Public Class ThumbnailControl
Inherits FlowLayoutPanel
Private ImageExtensions As List(Of String) = New List(Of String) From {".JPG", ".JPE", ".BMP", ".GIF", ".PNG"}
Private tempStoragePath As String = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\tempPhotos"
Private WithEvents bkWPhotos As New BackgroundWorker
Public Property iThumbList As List(Of PictureBox)
Public Property sImageList As List(Of String(,))
Private PopupPrg As PopUpProgress.PopUpProgressControl
Public Sub New()
Me.AutoScroll = True
Me.AllowDrop = True
Me.DoubleBuffered = True
iThumbList = New List(Of PictureBox)()
sImageList = New List(Of String(,))()
AddHandler Me.DragDrop, AddressOf ThumbnailViewerControl_DragDrop
AddHandler Me.DragEnter, AddressOf ThumbnailViewerControl_DragEnter
If Not Directory.Exists(tempStoragePath) Then Directory.CreateDirectory(tempStoragePath)
bkWPhotos.WorkerReportsProgress = True
bkWPhotos.WorkerSupportsCancellation = True
End Sub
Public Sub BackGroundWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles bkWPhotos.DoWork
AddImage(e.Argument)
End Sub
Public Sub BackGroundWorkder_Progress(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles bkWPhotos.ProgressChanged
PopupPrg.SetProgress(e.ProgressPercentage)
End Sub
Public Sub BackGroundWorker_Complete(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles bkWPhotos.RunWorkerCompleted
For Each i As PictureBox In iThumbList
Me.Controls.Add(i)
Next
PopupPrg.Destory()
Me.Cursor = Cursors.Default
End Sub
Public Sub AddImage(ByVal files As String())
Dim fImage As Image
Dim prg As Integer = 0
For Each f As String In files
If ImageExtensions.Contains(Path.GetExtension(f).ToUpperInvariant()) Then
bkWPhotos.ReportProgress(prg)
fImage = Image.FromFile(f)
File.Copy(f, tempStoragePath & "\" & Path.GetFileName(f), True)
sImageList.Add({{tempStoragePath & "\" & Path.GetFileName(f), fImage.Size.Width, fImage.Size.Height}})
Dim t As PictureBox = MakeThumbnail(fImage)
prg = prg + 1
GC.GetTotalMemory(True)
End If
Next
End Sub
Public Function MakeThumbnail(ByVal inImage As Image) As PictureBox
Dim thumb As PictureBox = New PictureBox()
thumb.Size = ScaleImage(inImage.Size, 200)
thumb.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
thumb.SizeMode = PictureBoxSizeMode.Zoom
AddHandler thumb.MouseEnter, AddressOf thumb_MouseEnter
AddHandler thumb.MouseLeave, AddressOf thumb_MouseLeave
AddHandler thumb.DoubleClick, AddressOf thumb_DoubleClick
thumb.Image = inImage.GetThumbnailImage(thumb.Width - 2, thumb.Height - 2, Nothing, New IntPtr())
iThumbList.Add(thumb)
Return thumb
End Function
Private Sub thumb_DoubleClick(ByVal sender As Object, ByVal e As EventArgs)
Dim previewForm As Form = New Form()
Dim index As Integer = Me.Controls.GetChildIndex(CType(sender, PictureBox))
Dim img As Image = Image.FromFile(sImageList(index)(0, 0))
previewForm.FormBorderStyle = FormBorderStyle.SizableToolWindow
previewForm.MinimizeBox = False
previewForm.Size = ScaleImage(img.Size, Screen.GetWorkingArea(Me).Height / 4 * 3)
previewForm.StartPosition = FormStartPosition.CenterScreen
Dim view As PictureBox = New PictureBox()
view.Dock = DockStyle.Fill
view.Image = Image.FromFile(sImageList(index)(0, 0))
view.SizeMode = PictureBoxSizeMode.Zoom
previewForm.Controls.Add(view)
previewForm.ShowDialog()
End Sub
Private Sub thumb_MouseLeave(ByVal sender As Object, ByVal e As EventArgs)
CType(sender, PictureBox).Invalidate()
End Sub
Private Sub thumb_MouseEnter(ByVal sender As Object, ByVal e As EventArgs)
Dim rc = (CType(sender, PictureBox)).ClientRectangle
rc.Inflate(-2, -2)
ControlPaint.DrawBorder((CType(sender, PictureBox)).CreateGraphics(), (CType(sender, PictureBox)).ClientRectangle, Color.Red, ButtonBorderStyle.Solid)
ControlPaint.DrawBorder3D((CType(sender, PictureBox)).CreateGraphics(), rc, Border3DStyle.Bump)
End Sub
Private Sub ThumbnailViewerControl_DragEnter(ByVal sender As Object, ByVal e As DragEventArgs)
If e.Data.GetDataPresent(DataFormats.FileDrop) Then e.Effect = DragDropEffects.Copy Else e.Effect = DragDropEffects.None
End Sub
Private Sub ThumbnailViewerControl_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs)
If e.Data.GetDataPresent(DataFormats.FileDrop) Then
Dim files As String() = CType(e.Data.GetData(DataFormats.FileDrop), String())
Me.Cursor = Cursors.WaitCursor
PopupPrg = New PopUpProgress.PopUpProgressControl(Me, files.Count)
bkWPhotos.RunWorkerAsync(files)
End If
End Sub
Public Function ScaleImage(ByVal oldImage As Size, ByVal TargetHeight As Integer) As Size
Dim NewHeight As Integer = TargetHeight
Dim NewWidth As Integer = NewHeight / oldImage.Height * oldImage.Width
NewHeight = NewWidth / oldImage.Width * oldImage.Height
Return New Size(NewWidth, NewHeight)
End Function
End Class
End Namespace
.... FacePalm.. I figured it out. Apparently during my testing (before I decided to use this control and a background worker, I had added another drag drop function in another area of my code that was being called first. It was taking all the dragged images and turning them into Image data types. The rest of the function was commented out which is why I didn't notice it before because I was only stepping though the classes functions not the functions in the main UI. but it makes perfect sense now, the backgroundworker and the UI function were kicking off at the same time but while the UI thread was processing the Image data typing the report progress calls were stacking up.
After removing that secondary function it works exactly as it should, UI remains fully functional Images and PictureBoxes are processed in the background and the Progressbar updates properly and remains functional as well.
I'm trying to add pictureboxes dynamically in vb.net.
If i play with the vars, changing the "i" value i can add the images and the event to the last picturebox created (i can only click the last images).
But when i use the code below, it says the there's something out of boundaries ( Index outside the bounds of the matrix ).
What am i doing wrong? Tks
Imports System.IO
Public Class FormMain
Dim Path1 As String = Path.GetDirectoryName(Application.ExecutablePath) & "\Source\Images\1.png"
Dim Path2 As String = Path.GetDirectoryName(Application.ExecutablePath) & "\Source\Images\2.png"
Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CreateImages()
End Sub
Dim i As Integer
Dim Logo(i) As PictureBox
Sub CreateImages()
Dim i As Integer = TextBoxNumberImages.Text
For i = 0 To i - 1
Logo(i) = New PictureBox
Logo(i).Name = "Image" + Str(i)
Panel1.Controls.Add(Logo(i))
Logo(i).Image = Image.FromFile(Path1)
Logo(i).SizeMode = PictureBoxSizeMode.StretchImage
AddHandler Logo(i).Click, AddressOf _Click
Next
End Sub
'------ADD EVENT----
Dim IsImageSelected(i) As Boolean
Private Sub _Click(ByVal sender As Object, ByVal e As EventArgs)
If IsImageSelected(i) = False Then
Logo(i).Image = Image.FromFile(Path2)
IsImageSelected(i) = True
Else
Logo(i).Image = Image.FromFile(Path1)
IsImageSelected(i) = False
End If
End Sub
----EDIT----
I just changed the var declaration to inside of the function:
Sub CreateImages()
Dim i As Integer = TextBoxNumberImages.Text
Dim Logo(i) As PictureBox
For i = 0 To i - 1
Logo(i) = New PictureBox
Logo(i).Name = "Image" + Str(i)
Panel1.Controls.Add(Logo(i))
Logo(i).Image = Image.FromFile(Path1)
Logo(i).SizeMode = PictureBoxSizeMode.StretchImage
AddHandler Logo(i).Click, AddressOf _Click
Next
End Sub
Now it creates the images the way i want, but i can't access the pictureboxes in the event. Help?
Don't use an array, use a List(Of PictureBox) instead. You could also store the selected state in the Tag() of the PictureBox. To get a reference to the PictureBox that was clicked, cast the Sender parameter. All together it would look something like this:
Private Logo As New List(Of PictureBox)
Sub CreateImages()
Dim i As Integer = TextBoxNumberImages.Text
For i = 0 To i - 1
Dim pb As New PictureBox
pb = New PictureBox
pb.Tag = False ' <-- initial not selected state
pb.Name = "Image" + Str(i)
Panel1.Controls.Add(pb)
pb.Image = Image.FromFile(Path1)
pb.SizeMode = PictureBoxSizeMode.StretchImage
AddHandler pb.Click, AddressOf _Click
Logo.Add(pb)
Next
End Sub
Private Sub _Click(ByVal sender As Object, ByVal e As EventArgs)
Dim pb As PictureBox = DirectCast(sender, PictureBox)
Dim selected As Boolean = DirectCast(pb.Tag, Boolean)
If selected = False Then
pb.Image = Image.FromFile(Path2)
Else
pb.Image = Image.FromFile(Path1)
End If
pb.Tag = Not selected ' toggle selected state
End Sub
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
In my Application i want to move the control one side another side. That control inside the tablelayoutpanel. I would like to drag the control which is inside the panel and the panel is inside the table layout panel so first i remove the control form panel and add the control in form after that i drag the button control its make the issue not clearly dragging. (that means drag fast its not working properly). My Code is
Private Sub HandleDraggableControlMouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Button2.MouseDown
Dim target As Control = TryCast(sender, Control)
Dim xWidth, xHeight As Integer
If (Not target Is Nothing) Then
xWidth = sender.Width
xHeight = sender.Height
sender.Parent.Controls.Remove(sender)
sender.Dock = DockStyle.None
sender.Width = xWidth
sender.Height = xHeight
Me.Controls.Add(sender)
Dim pt As Point = Me.PointToClient(target.PointToScreen(Point.Empty))
target.Location = pt
target.Parent = Me
target.BringToFront()
Me.isMouseDown = True
Me.cachedControlPos = pt
Me.cachedMousePos = Control.MousePosition
End If
End Sub
Private Sub HandleDraggableControlMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Button2.MouseMove
If (Me.isMouseDown) Then
Dim target As Control = TryCast(sender, Control)
If (Not target Is Nothing) Then
Dim x As Integer = (Me.cachedControlPos.X + (Control.MousePosition.X - Me.cachedMousePos.X))
Dim y As Integer = (Me.cachedControlPos.Y + (Control.MousePosition.Y - Me.cachedMousePos.Y))
target.Location = New Point(x, y)
'c2 = (c1 + (m2 - m1))
End If
End If
End Sub
Private Sub HandleDraggableControlMouseUp(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Button2.MouseUp
Me.cachedControlPos = Point.Empty
Me.cachedMousePos = Point.Empty
Me.isMouseDown = False
End Sub
My problem is if drag the control fast the control not moved cursor only moved why?. What am doing wrong in my coding?. How is solve the problem?
You need to add the difference between the current mouse position m2 and the cached mouse position m1 to a cached control position c1 to give you the current control position c2.
c2 = (c1 + (m2 - m1))
Something like this:
sender.Location = New Point(
(cachedControlLocation.X + (e.X - startX)),
(cachedControlLocation.Y + (e.Y - startY))
)
Here's a sample form to show you how it works:
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.ClientSize = New Size(800, 600)
Me.panel1 = New Panel() With {.Bounds = New Rectangle(10, 10, 300, 300), .BackColor = Color.Red}
Me.panel2 = New Panel() With {.Bounds = New Rectangle(10, 10, 200, 200), .BackColor = Color.Green}
Me.panel3 = New Panel() With {.Bounds = New Rectangle(10, 10, 100, 100), .BackColor = Color.Blue}
Me.panel2.Controls.Add(Me.panel3)
Me.panel1.Controls.Add(Me.panel2)
Me.Controls.Add(Me.panel1)
End Sub
Private Sub HandleDraggableControlMouseDown(sender As Object, e As MouseEventArgs) Handles panel1.MouseDown, panel2.MouseDown, panel3.MouseDown
Dim target As Control = TryCast(sender, Control)
If (Not target Is Nothing) Then
Dim pt As Point = Me.PointToClient(target.PointToScreen(Point.Empty))
target.Parent = Me
target.BringToFront()
target.Location = pt
Me.isMouseDown = True
Me.cachedControlPos = pt
Me.cachedMousePos = Control.MousePosition
End If
End Sub
Private Sub HandleDraggableControlMouseMove(sender As Object, e As MouseEventArgs) Handles panel1.MouseMove, panel2.MouseMove, panel3.MouseMove
If (Me.isMouseDown) Then
Dim target As Control = TryCast(sender, Control)
If (Not target Is Nothing) Then
Dim x As Integer = (Me.cachedControlPos.X + (Control.MousePosition.X - Me.cachedMousePos.X))
Dim y As Integer = (Me.cachedControlPos.Y + (Control.MousePosition.Y - Me.cachedMousePos.Y))
target.Location = New Point(x, y)
'c2 = (c1 + (m2 - m1))
End If
End If
End Sub
Private Sub HandleDraggableControlMouseUp(sender As Object, e As MouseEventArgs) Handles panel1.MouseUp, panel2.MouseUp, panel3.MouseUp
Me.cachedControlPos = Point.Empty
Me.cachedMousePos = Point.Empty
Me.isMouseDown = False
End Sub
Private cachedMousePos As Point
Private cachedControlPos As Point
Private isMouseDown As Boolean
Private WithEvents panel1 As Panel
Private WithEvents panel2 As Panel
Private WithEvents panel3 As Panel
End Class
Update 1
It's important that you set the new location after you've changed the parent and moved it to the front.
target.Parent = Me
target.BringToFront()
target.Location = pt '<---
Update 2
So I've narrowed it down to what's causing this issue, and it turns out to be the Selectable control style. You can verify this by subclassing the button class and remove the style in the constructor.
Public Class UIButton
Inherits Button
Public Sub New()
MyBase.SetStyle(ControlStyles.Selectable, False)
End Sub
End Class
So how can we fix this? Well, AFAIK there's no easy solution. It's to be expected that a selectable control will process the mouse messages in a different way than those who can't. The only way I can think of (and it might be a dirty one) is to subclass the control(s) and intercept the mouse messages. The following code is not a final solution, so use it with caution.
Public Class UIButton
Inherits Button
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case WM.LBUTTONDOWN
Dim dw As New DWORD With {.value = m.LParam}
Dim vk As Integer = m.WParam.ToInt32()
MyBase.OnMouseDown(New MouseEventArgs(Windows.Forms.MouseButtons.Left, 0, dw.loword, dw.hiword, 0))
Debug.WriteLine("X={0}, Y={1}", dw.loword, dw.hiword)
Exit Select
Case WM.MOVE
Dim dw As New DWORD With {.value = m.LParam}
Dim vk As Integer = m.WParam.ToInt32()
If (vk = Keys.LButton) Then
MyBase.OnMouseMove(New MouseEventArgs(Windows.Forms.MouseButtons.Left, 0, dw.loword, dw.hiword, 0))
Debug.WriteLine("X={0}, Y={1}", dw.loword, dw.hiword)
End If
Exit Select
Case WM.LBUTTONUP
Dim dw As New DWORD With {.value = m.LParam}
Dim vk As Integer = m.WParam.ToInt32()
MyBase.OnMouseUp(New MouseEventArgs(Windows.Forms.MouseButtons.Left, 0, dw.loword, dw.hiword, 0))
Debug.WriteLine("X={0}, Y={1}", dw.loword, dw.hiword)
Exit Select
End Select
MyBase.WndProc(m)
End Sub
Private Enum WM As Integer
MOVE = &H200
LBUTTONDOWN = &H201
LBUTTONUP = &H202
End Enum
<StructLayout(LayoutKind.Explicit)> _
Private Structure DWORD
<FieldOffset(0)> Public value As Integer
<FieldOffset(0)> Public loword As Short
<FieldOffset(2)> Public hiword As Short
End Structure
End Class