tags:

views:

202

answers:

2

Hi, I've got a set of controls in a line inside "FollowTableLayoutPanel1", contained within "TableLayoutPanel2". I have them resizing according to the position of the mouse cursor; the closer the mouse cursor is to the vertical centre of the control, the larger the control is. Because FollowTableLayoutPanel1 has its anchor property set to "Top", it recentres itself in TableLayoutPanel2.

That is where I am having a problem. It is possible for the recentring of FollowTableLayoutPanel1 to move a control one pixel further away from the mouse pointer, which causes the control to shrink, which causes FollowTableLayoutPanel1 to recentre, placing the control closer to the mouse pointer, which causes the control to grow, which causes FollowTableLayoutPanel1 to recentre, which moves the control further away from the mouse cursor, etc., etc., etc. The end result is that the whole setup judders and wobbles, constantly resizing.

Can anyone suggest a way I can suppress this juddering?

Full example code is provided below, and can be pasted directly into Form1 of a new project. Positioning the mouse cursor to correctly show the problem is left as an exercise for the reader :P

Public Class Form1
Private Sub myInitializeComponent()
    Me.components = New System.ComponentModel.Container
    Me.TableLayoutPanel2 = New System.Windows.Forms.TableLayoutPanel
    Me.FollowTableLayoutPanel1 = New FollowTableLayoutPanel
    Me.Button9 = New System.Windows.Forms.Button
    Me.Button8 = New System.Windows.Forms.Button
    Me.Button7 = New System.Windows.Forms.Button
    Me.Button6 = New System.Windows.Forms.Button
    Me.Button5 = New System.Windows.Forms.Button
    Me.Button3 = New System.Windows.Forms.Button
    Me.Button1 = New System.Windows.Forms.Button
    Me.Button2 = New System.Windows.Forms.Button
    Me.Button4 = New System.Windows.Forms.Button
    Me.TableLayoutPanel2.SuspendLayout()
    Me.FollowTableLayoutPanel1.SuspendLayout()
    Me.SuspendLayout()
    '
    'TableLayoutPanel2
    '
    Me.TableLayoutPanel2.ColumnCount = 1
    Me.TableLayoutPanel2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!))
    Me.TableLayoutPanel2.Controls.Add(Me.FollowTableLayoutPanel1, 0, 0)
    Me.TableLayoutPanel2.Location = New System.Drawing.Point(12, 115)
    Me.TableLayoutPanel2.Name = "TableLayoutPanel2"
    Me.TableLayoutPanel2.RowCount = 1
    Me.TableLayoutPanel2.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!))
    Me.TableLayoutPanel2.Size = New System.Drawing.Size(1194, 341)
    Me.TableLayoutPanel2.TabIndex = 2
    '
    'FollowTableLayoutPanel1
    '
    Me.FollowTableLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.FollowTableLayoutPanel1.AutoSize = True
    Me.FollowTableLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink
    Me.FollowTableLayoutPanel1.ColumnCount = 9
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button9, 0, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button8, 0, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button7, 0, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button6, 0, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button5, 0, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button3, 2, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button1, 0, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button2, 1, 0)
    Me.FollowTableLayoutPanel1.Controls.Add(Me.Button4, 3, 0)
    Me.FollowTableLayoutPanel1.Location = New System.Drawing.Point(259, 0)
    Me.FollowTableLayoutPanel1.Margin = New System.Windows.Forms.Padding(0)
    Me.FollowTableLayoutPanel1.Name = "FollowTableLayoutPanel1"
    Me.FollowTableLayoutPanel1.RowCount = 1
    Me.FollowTableLayoutPanel1.RowStyles.Add(New System.Windows.Forms.RowStyle)
    Me.FollowTableLayoutPanel1.Size = New System.Drawing.Size(675, 50)
    Me.FollowTableLayoutPanel1.TabIndex = 1
    Me.FollowTableLayoutPanel1.Text = "{X=0,Y=0}" & Global.Microsoft.VisualBasic.ChrW(9) & Global.Microsoft.VisualBasic.ChrW(9) & "00:00:00.0090009"
    '
    'Button9
    '
    Me.Button9.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button9.Location = New System.Drawing.Point(225, 0)
    Me.Button9.Margin = New System.Windows.Forms.Padding(0)
    Me.Button9.Name = "Button9"
    Me.Button9.Size = New System.Drawing.Size(75, 50)
    Me.Button9.TabIndex = 5
    Me.Button9.Text = "{Width=75, Height=50}"
    Me.Button9.UseVisualStyleBackColor = True
    '
    'Button8
    '
    Me.Button8.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button8.Location = New System.Drawing.Point(300, 0)
    Me.Button8.Margin = New System.Windows.Forms.Padding(0)
    Me.Button8.Name = "Button8"
    Me.Button8.Size = New System.Drawing.Size(75, 50)
    Me.Button8.TabIndex = 4
    Me.Button8.Text = "{Width=75, Height=50}"
    Me.Button8.UseVisualStyleBackColor = True
    '
    'Button7
    '
    Me.Button7.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button7.Location = New System.Drawing.Point(375, 0)
    Me.Button7.Margin = New System.Windows.Forms.Padding(0)
    Me.Button7.Name = "Button7"
    Me.Button7.Size = New System.Drawing.Size(75, 50)
    Me.Button7.TabIndex = 3
    Me.Button7.Text = "{Width=75, Height=50}"
    Me.Button7.UseVisualStyleBackColor = True
    '
    'Button6
    '
    Me.Button6.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button6.Location = New System.Drawing.Point(0, 0)
    Me.Button6.Margin = New System.Windows.Forms.Padding(0)
    Me.Button6.Name = "Button6"
    Me.Button6.Size = New System.Drawing.Size(75, 50)
    Me.Button6.TabIndex = 2
    Me.Button6.Text = "{Width=75, Height=50}"
    Me.Button6.UseVisualStyleBackColor = True
    '
    'Button5
    '
    Me.Button5.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button5.Location = New System.Drawing.Point(75, 0)
    Me.Button5.Margin = New System.Windows.Forms.Padding(0)
    Me.Button5.Name = "Button5"
    Me.Button5.Size = New System.Drawing.Size(75, 50)
    Me.Button5.TabIndex = 1
    Me.Button5.Text = "{Width=75, Height=50}"
    Me.Button5.UseVisualStyleBackColor = True
    '
    'Button3
    '
    Me.Button3.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button3.Location = New System.Drawing.Point(525, 0)
    Me.Button3.Margin = New System.Windows.Forms.Padding(0)
    Me.Button3.Name = "Button3"
    Me.Button3.Size = New System.Drawing.Size(75, 50)
    Me.Button3.TabIndex = 0
    Me.Button3.Text = "{Width=75, Height=50}"
    Me.Button3.UseVisualStyleBackColor = True
    '
    'Button1
    '
    Me.Button1.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button1.Location = New System.Drawing.Point(150, 0)
    Me.Button1.Margin = New System.Windows.Forms.Padding(0)
    Me.Button1.Name = "Button1"
    Me.Button1.Size = New System.Drawing.Size(75, 50)
    Me.Button1.TabIndex = 0
    Me.Button1.Text = "{Width=75, Height=50}"
    Me.Button1.UseVisualStyleBackColor = True
    '
    'Button2
    '
    Me.Button2.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button2.Location = New System.Drawing.Point(450, 0)
    Me.Button2.Margin = New System.Windows.Forms.Padding(0)
    Me.Button2.Name = "Button2"
    Me.Button2.Size = New System.Drawing.Size(75, 50)
    Me.Button2.TabIndex = 0
    Me.Button2.Text = "{Width=75, Height=50}"
    Me.Button2.UseVisualStyleBackColor = True
    '
    'Button4
    '
    Me.Button4.Anchor = System.Windows.Forms.AnchorStyles.Top
    Me.Button4.Location = New System.Drawing.Point(600, 0)
    Me.Button4.Margin = New System.Windows.Forms.Padding(0)
    Me.Button4.Name = "Button4"
    Me.Button4.Size = New System.Drawing.Size(75, 50)
    Me.Button4.TabIndex = 0
    Me.Button4.Text = "{Width=75, Height=50}"
    Me.Button4.UseVisualStyleBackColor = True
    '
    'Form1
    '
    Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
    Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
    Me.ClientSize = New System.Drawing.Size(1218, 577)
    Me.Controls.Add(Me.TableLayoutPanel2)
    Me.Name = "Form1"
    Me.Text = "Form1"
    Me.TableLayoutPanel2.ResumeLayout(False)
    Me.TableLayoutPanel2.PerformLayout()
    Me.FollowTableLayoutPanel1.ResumeLayout(False)
    Me.ResumeLayout(False)

End Sub
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
Friend WithEvents Button3 As System.Windows.Forms.Button
Friend WithEvents Button4 As System.Windows.Forms.Button
Friend WithEvents FollowTableLayoutPanel1 As FollowTableLayoutPanel
Friend WithEvents TableLayoutPanel2 As System.Windows.Forms.TableLayoutPanel
Friend WithEvents Button9 As System.Windows.Forms.Button
Friend WithEvents Button8 As System.Windows.Forms.Button
Friend WithEvents Button7 As System.Windows.Forms.Button
Friend WithEvents Button6 As System.Windows.Forms.Button
Friend WithEvents Button5 As System.Windows.Forms.Button

Public Sub New()
    ' This call is required by the Windows Form Designer.
    InitializeComponent()
    myInitializeComponent()
    ' Add any initialization after the InitializeComponent() call.
End Sub

Private Sub FollowTableLayoutPanel1_TimerTick() Handles FollowTableLayoutPanel1.TimerTick
    Me.Text = Me.FollowTableLayoutPanel1.Text
End Sub

Private Sub TableLayoutPanel1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TableLayoutPanel2.MouseMove
    Me.FollowTableLayoutPanel1.parentMouseMove(e.Location)
End Sub
Private Sub TableLayoutPanel1_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles TableLayoutPanel2.MouseLeave
    Me.FollowTableLayoutPanel1.parentMouseLeave()
End Sub
End Class
Public Class FollowTableLayoutPanel
Inherits TableLayoutPanel
Public WithEvents animTimer As Timer
Private Class ControlSize
    Private _sizing As Boolean
    Private _bigSize As Size
    Public ReadOnly Property BigSize() As Size
        Get
            Return _bigSize
        End Get
    End Property
    Private _smallSize As Size
    Public ReadOnly Property SmallSize() As Size
        Get
            Return _smallSize
        End Get
    End Property
    Private WithEvents _thisControl As Control
    Public ReadOnly Property ThisControl() As Control
        Get
            Return _thisControl
        End Get
    End Property
    Public Sub New(ByVal thisControl As Control)
        Me._sizing = False
        Me._thisControl = thisControl
        Me._bigSize = New Size(thisControl.Width * 2, thisControl.Height * 2)
        Me._smallSize = thisControl.Size
    End Sub
    Public Sub Resize(ByVal sizeTo As Size)
        Me._sizing = True
        Me._thisControl.Size = sizeTo
        Me._sizing = False
    End Sub
    Private Sub _thisControl_SizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles _thisControl.SizeChanged
        If Not Me._sizing Then
            Me._bigSize = New Size(ThisControl.Width * 2, ThisControl.Height * 2)
            Me._smallSize = ThisControl.Size
        End If
    End Sub
End Class
Private sizeDict As List(Of ControlSize)
Public Sub New()
    MyBase.New()
    Me.sizeDict = New List(Of ControlSize)
    Me.DoubleBuffered = True
    Me.animTimer = New Timer()
    Me.animTimer.Interval = 10
    Me.animTimer.Start()
End Sub
Protected Overrides Sub OnControlAdded(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlAdded(e)
    e.Control.Text = String.Empty
    sizeDict.Add(New ControlSize(e.Control))
    AddHandler e.Control.MouseMove, AddressOf ControlIn_MouseMove
    AddHandler e.Control.MouseLeave, AddressOf ControlIn_MouseLeave
End Sub
Protected Overrides Sub OnControlRemoved(ByVal e As System.Windows.Forms.ControlEventArgs)
    MyBase.OnControlRemoved(e)
    For Each controlSizeIn As ControlSize In Me.sizeDict
        If controlSizeIn.ThisControl Is e.Control Then
            sizeDict.Remove(controlSizeIn)
            Exit For
        End If
    Next
    RemoveHandler e.Control.MouseMove, AddressOf ControlIn_MouseMove
    RemoveHandler e.Control.MouseLeave, AddressOf ControlIn_MouseLeave
End Sub
Public Event TimerTick()
Private Sub animTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles animTimer.Tick
    Me.SuspendLayout()
    moveButton()
    Me.PerformLayout()
    Me.ResumeLayout()
    RaiseEvent TimerTick()
End Sub
Private Sub moveButton()
    Static lastTime As Long = DateTime.Now.Ticks
    If mouseLocation.X <> 0 Then
        For Each csIn As ControlSize In Me.sizeDict
            Dim controlIn As Control = csIn.ThisControl
            Dim controlCentrePoint As New Point(CInt(controlIn.Left + (controlIn.Width / 2)), CInt(controlIn.Top + (controlIn.Height / 2)))

            Dim differenceX As Integer = Math.Abs(controlCentrePoint.X - mouseLocation.X)
            Dim setDifferenceX As Integer = csIn.BigSize.Width - differenceX
            Dim setTargetX As Integer = If(setDifferenceX < csIn.SmallSize.Width, csIn.SmallSize.Width, setDifferenceX)

            Dim targetChangeX As Integer = CInt((Math.Abs(setTargetX - controlIn.Width)) / 5)
            Dim setChangeX As Integer = If(targetChangeX = 0, 1, targetChangeX)
            If setTargetX < controlIn.Width AndAlso controlIn.Width <= csIn.BigSize.Width Then
                Dim setX As Integer = controlIn.Width - setChangeX
                csIn.Resize(New Size(setX, CInt(csIn.SmallSize.Height * (setX / csIn.SmallSize.Width))))
            ElseIf setTargetX > controlIn.Width AndAlso controlIn.Width >= csIn.SmallSize.Width Then
                Dim setX As Integer = controlIn.Width + setChangeX
                csIn.Resize(New Size(setX, CInt(csIn.SmallSize.Height * (setX / csIn.SmallSize.Width))))
            End If

            controlIn.Text = controlIn.Size.ToString
        Next
    Else
        For Each csIn As ControlSize In Me.sizeDict
            Dim controlIn As Control = csIn.ThisControl
            If csIn.SmallSize.Width < controlIn.Width AndAlso controlIn.Width <= csIn.BigSize.Width Then
                Dim targetChangeX As Integer = CInt((Math.Abs(csIn.SmallSize.Width - controlIn.Width)) / 5)
                Dim setChangeX As Integer = If(targetChangeX = 0, 1, targetChangeX)
                Dim setX As Integer = controlIn.Width - setChangeX
                Dim setY As Integer = CInt(csIn.SmallSize.Height * (setX / csIn.SmallSize.Width))
                csIn.Resize(New Size(setX, setY))
            End If
            controlIn.Text = controlIn.Size.ToString
        Next
    End If
    Dim nowTicks As Long = DateTime.Now.Ticks
    Dim ts As New TimeSpan(nowTicks - lastTime)
    lastTime = nowTicks
    Me.Text = Me.mouseLocation.ToString & vbTab & vbTab & ts.ToString
End Sub
Private mouseLocation As Point
Private Sub Form1_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseLeave
    mouseLocation = New Point(0, 0)
End Sub
Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
    mouseLocation = e.Location
End Sub
Private Sub ControlIn_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    mouseLocation = New Point(CInt(e.X + CType(sender, Control).Left), CInt(e.Y + CType(sender, Control).Top))
End Sub
Private Sub ControlIn_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs)
    mouseLocation = New Point(0, 0)
End Sub
Public Sub parentMouseMove(ByVal mouseCoordinates As Point)
    mouseLocation = New Point(mouseCoordinates.X - Me.Left, mouseCoordinates.Y - Me.Top)
End Sub
Public Sub parentMouseLeave()
    mouseLocation = New Point(0, 0)
End Sub
End Class
A: 

Assuming you have good reason to not let windows handle the resizing on it's own, I would not draw anything till the user releases the mousebutton. Draw it once, no juddering. If they really need a guide, draw a dashed box similar to old style 'shadow' windows resizing.

Rob Elliott
Um... Did you mean to answer someone else's question?
Frosty840
No, but I did misunderstand what you wanted. :)
Rob Elliott
+1  A: 

Are you trying to implement a fish eye effect similar to the dock in OSX? Instead of handling mouse events on each individual control level, try to handle mouse on the parent level. When parent receive mouse move, calculate new positions for all controls and adjust them.

I've been using following approach to calculate positions for fish eye effect. Basically the idea is to scale/shift elements dependent on position from the mouse. pt is coordinate of mouse. target.Pos is unscaled coordinates. ScaledPos is new scaled coordinates

double dx = target.Pos.X + target.Pos.Width / 2 - pt.X;
double scale = DistanceToScale(Math.Sqrt(dx * dx + dy * dy));
double xRel = pt.X - target.Pos.X;
double dxTarget = xRel * scale - xRel;

target.ScaledPos = new Rect(target.Pos.X - dxTarget,
                            target.Pos.Y,
                            target.Pos.Width * scale,
                            target.Pos.Height);

You have to calculate positions for all elements. DistanceToScale function defines how fast scale changes from maximum scale (when mouse is over the top of the element) to 1 (items are not scaled). Function should be smooth to avoid jumping.

double DistanceToScale(double distance)
{
    double dblScale = _c1 / (1.0 + (distance * distance * _c2));
    if (dblScale < 1.0)
    {
        dblScale = 1.0;
    }

    return dblScale;
}

where c1 and c2

_c1 = maximun_scale;
_c2 = (c1 / scale_at_distance_dx) - 1 / (dx ^ 2);
AlexEzh
Yup. Doing that. Need ideas on how to prevent side-effects occurring between one frame's repositioning and the next.
Frosty840
Don't use layouts. Instead manually position controls where you want them to be and adjust the coordinates when applying effect.
AlexEzh
Yes, but even if I do that, then the calculation being done will *still* produce the unwanted wobble effect.
Frosty840