Nifty use of Hidden 'DesignMode' Property to allow dragging controls between userforms at runtime .

Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,806
Office Version
  1. 2016
Platform
  1. Windows
I have never seen a useful implementation of the little known Hidden DesignMode Property of the userform. here, I show an example that uses it to allow dragging & dropping controls beteween 2 userforms at runtime and allowing the controls to carry their respective Click events with them.

When the forms are first initialized, the dragging and dropping functionality is enabled. To disable this mode, just right click anywhere on each userform and click the menu that pops up .

The controls must be created at runtime otherwise the code errors out.

Workbook demo.

Here is the code in case the above link expires :


1- Add a blank UserForm to your Project and place in it the code below :

Code:
Option Explicit

Public WithEvents Btn As MSForms.CommandButton
Public oButton As MSForms.CommandButton
Private sFormIndex As String

Private Sub Btn_Click()

    MsgBox "You clicked on:  Button " & Btn.Caption
    
End Sub

Private Sub UserForm_Initialize()

      Set oButton = Controls.Add("Forms.CommandButton.1")
      Set Btn = oButton
      With oButton
          .Height = 30
          .Width = 30
          .Left = Me.InsideWidth / 4
          .Top = Me.InsideHeight / 4
          .BackColor = vbYellow
          .Font.Bold = True
      End With
      StartUpPosition = 0
      If UserForms.Count = 1 Then
          oButton.Caption = "B"
          sFormIndex = "-(B)"
          Caption = Name & sFormIndex
          Left = Application.Left + (Application.Width / 2)
          Top = Application.Top + (Application.Height / 4)
      ElseIf UserForms.Count = 2 Then
          oButton.Caption = "A"
          oButton.SetFocus
          sFormIndex = "-(A)"
          Caption = Name & sFormIndex
          Top = Application.Top + (Application.Height / 4)
          Left = UserForms(1).Width / 2 - UserForms(1).Left
    End If
    ShowGridDots = fmModeOff
    ShowToolbox = fmModeOff
    DesignMode = fmModeOn

End Sub

Private Sub UserForm_Deactivate()

    Caption = Name & sFormIndex
    
End Sub


Private Sub UserForm_AddControl(ByVal Control As MSForms.Control)

    On Error Resume Next
    Set oButton = Control
    Caption = Name & sFormIndex

End Sub

Private Sub UserForm_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, _
ByVal Control As MSForms.Control, ByVal Data As MSForms.DataObject, _
ByVal X As Single, ByVal Y As Single, ByVal State As MSForms.fmDragState, _
ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)

    On Error Resume Next
    If Effect = 0 Then Selected.Clear
    If State = fmDragStateOver Then
        Caption = "Draging ... Button " & _
        Selected.[_GetItemByIndex](0).Caption
    End If

End Sub

Private Sub UserForm_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, _
ByVal Control As MSForms.Control, ByVal Action As MSForms.fmAction, _
ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, _
ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)

    Dim lPtr As Long
    
    On Error Resume Next
    DesignMode = fmModeOff
    If Action = fmActionCopy Then
        Cancel = True
        DesignMode = fmModeOn
        Exit Sub
    End If
    lPtr = ObjPtr(Me)
    Application.OnTime Now, "'HookBtn " & lPtr & "'"
    DesignMode = fmModeOn

End Sub


Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)

    If Selected.Count <> 0 Then
        If GetAsyncKeyState(VBA.vbKeyControl) <> 0 Then
            DesignMode = fmModeOff
            UndoAction
            DesignMode = fmModeOn
        End If
    End If

End Sub

Private Sub UserForm_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)

    Dim lPtr As Long
    
    Selected.Clear
    If Button = 2 Then
        Application.SendKeys "{ESC}"
        lPtr = ObjPtr(Me)
        Application.OnTime Now, "'CreateRightClickMenu " & lPtr & "'"
    End If

End Sub
2- Add a Standard module and Place this code in it :

Code:
Option Explicit

Declare Function GetAsyncKeyState Lib "user32" _
(ByVal vKey As Long) As Integer


Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
pDest As Any, pSrc As Any, ByVal ByteLen As Long)

Private bDragAndDrop As Boolean
Private oCmbarCtl As CommandBarControl

Public Sub ShowTheForms()

    Dim oUFrm1 As UserForm1
    Dim oUFrm2 As UserForm1
    
    Set oUFrm1 = New UserForm1
    Set oUFrm2 = New UserForm1
    oUFrm1.Show vbModeless
    oUFrm2.Show vbModeless

End Sub

Public Sub HookBtn(ByVal Ptr As Long)

    Dim oTempObj As Object
    
    On Error Resume Next
    CopyMemory oTempObj, Ptr, 4
    With oTempObj
        .Selected.Clear
        .DesignMode = fmModeOff
        Set .oButton = .Selected.[_GetItemByIndex](0)
        Set .Btn = .oButton
        .DesignMode = fmModeOn
    End With
    CopyMemory oTempObj, 0&, 4

End Sub


Public Sub CreateRightClickMenu(ByVal Ptr As Long)

    Dim objCmb As CommandBar
    Dim oTempObj As Object
    
    On Error Resume Next
        CommandBars("DesignCmb").Delete
    On Error GoTo 0
    Set objCmb = Application.CommandBars.Add(, msoBarPopup, , True)
    CopyMemory oTempObj, Ptr, 4
    With objCmb
        .Name = "DesignCmb"
        Set oCmbarCtl = .Controls.Add(msoControlButton)
        With oCmbarCtl
            If oTempObj.DesignMode = fmModeOff Then
                bDragAndDrop = True
                .Caption = "EnableDrageAndDrop"
            Else
                .Caption = "DisableDrageAndDrop"
                bDragAndDrop = False
            End If
            .OnAction = "'ToggleDragAndDropMode " & Ptr & "'"
        End With
        .ShowPopup
    End With
    CopyMemory oTempObj, 0&, 4

End Sub


Public Sub ToggleDragAndDropMode(ByVal Ptr As Long)

    Dim oTempObj As Object
    
    CopyMemory oTempObj, Ptr, 4
    Select Case bDragAndDrop
        Case True
            oTempObj.DesignMode = fmModeOn
            oCmbarCtl.Caption = "DisableDrageAndDrop"
        Case Else
            oTempObj.DesignMode = fmModeOff
            oCmbarCtl.Caption = "EnableDrageAndDrop"
    End Select
    CopyMemory oTempObj, 0&, 4

End Sub
 

Excel Facts

Excel Joke
Why can't spreadsheets drive cars? They crash too often!
Nice work Jaafar!

I haven't actually tried your code, yet, but I like it when people try to go beyond the boundaries of the spec :)

Do you have any practical use for this method in mind?
 
Upvote 0
Nice work Jaafar!

I haven't actually tried your code, yet, but I like it when people try to go beyond the boundaries of the spec :)

Do you have any practical use for this method in mind?

Thanks Hermanito.

No. I have no pratctical use for this method at the moment but it may come in handy in the future - Perhaps this could be further developped into a little excel game or something.

As usual, this was just a case of trying to explore some obscure areas and learn something new
 
Upvote 0
Thanks Jaafar - nice lesson!
Here is solution without API: Drag_Controls_between_Forms.zip

Create UserForm1, add controls on it including of some command buttons for dragging.
Put this code into Module1:
Here is solution without API: Drag_Controls_between_Forms.zip
Create UserForm1, add controls on it including of some command buttons for dragging.

Put this code into Module1:
Rich (BB code):

' Code of Module1
Option Explicit
Public Form1 As UserForm1, Form2 As UserForm1

' Start button's code
Sub FormShow()
  Set Form1 = New UserForm1
  Set Form2 = New UserForm1
  Form1.Show 0
  Form2.Show 0
End Sub

' Deselect controls
Sub Deselect()
  On Error Resume Next
  Form1.Selected.Clear
  Form2.Selected.Clear
End Sub


The code of UserForm1:
Rich (BB code):

' Code of UserForm1
' Note: Create manually UserForm1 and place some command buttons on it
Option Explicit

' Prepare UserForm for drag-drop of its commandbuttons
Private Sub UserForm_Initialize()
  
  Dim i&, X As MSForms.CommandButton, id As String
  
  ' Set id with userform's number for the captions
  id = " (" & UserForms.Count & ")"
  
  ' Duplicate all form's command buttons
  With Me.Controls
    For i = 1 To .Count
      If TypeOf .Item(i - 1) Is MSForms.CommandButton Then
        Set X = .Item(i - 1)
        ' Create run-time button
        With Controls.Add("Forms.CommandButton.1")
          ' Copy original button's properties to run-time button
          .Height = X.Height
          .Width = X.Width
          .Left = X.Left
          .Top = X.Top
          .BackColor = X.BackColor
          .Font.Bold = X.Font.Bold
          ' Add id to the caption
          .Caption = X.Caption & id
        End With
        ' Move original buttons away
        X.Enabled = False
        X.Top = -1000
      End If
    Next
  End With
  
  ' Set the Form's properties
  i = UserForms.Count
  With Me
    .DesignMode = fmModeOn
    .Caption = .Name & id
    .StartUpPosition = 0
    .Top = Windows(1).Top + Windows(1).Height / 2 - .Height / 2
    If i = 1 Then
      Set Form1 = Me
      .Left = Windows(1).Left + Windows(1).Width / 2 + .Width / 2
    ElseIf i = 2 Then
      Set Form2 = Me
      .Left = Windows(1).Left + Windows(1).Width / 2 - 1.25 * .Width
    End If
  End With
  
End Sub

' Deselect all controls after dropping
Private Sub UserForm_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Control As MSForms.Control, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
  Application.OnTime Now, "Deselect"
End Sub

' Suppress Userform design right-click menu
Private Sub UserForm_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
  Application.SendKeys "{ESC}"
End Sub

' Unload both forms
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  On Error Resume Next
  Unload Form1
  Unload Form2
End Sub
Regards,
Vlad
 
Last edited:
Upvote 0
I apologise for misprints in post above.
Here is the updated link to the demo workbook: Drag_Controls_between_Forms.zip


Vladdimir.

Thanks for the followup. I see your code is very similar to mine except for the pre-existence of design-mode commandbuttons.

The reason I used the CopyMemory API was just to pass the calling UserForm object pointer to the OnTime routines. As for the GetAsyncKeyState API , I used it only to prevent Copying the Controls when holding down the Ctl Key.
 
Upvote 0
Jaafar, yes the method is the same and based on your idea & code.
But it is without API because there are other ways to copy controls even with hooking of Ctrl- key pressing, and because the API calls are not safety sometimes.
And in my version the buttons are not linked to event class module because it is not the main in idea, I think :-)

It is clear that the code is just for demonstrating of idea, but if you are interested in testing then below are some behaviors of your code:

1. Right Click on button, drag on same form, release Right Click – menu Move/Copy/Cancel appears. The Copy action is not blocked in this case.
2. Click on button, press Ctrl key, drag to another form, release Ctrl key, drop - the button disappears.
3. The same with Ctrl-Shift, i.e: Click on button, press Ctrl-Shift, drag & drop on another form - the button disappears.
4. Simultaneous Right & Left Click on button, drag on same form, release Right & Left Click – one of three menus pop-ups: Move/Copy/Cancel or Form’s menu Grid/Properties/etc or menu with Cut/Copy/Delete/Font/etc. For cancel at latter case the button can stay selected.
5. Right Click on button, wait, release Right Click – depending of delay one of two menus pop-ups: Move/Copy/Cancel or Form’s menu Grid/Properties/etc
6. Hang of Excel sometimes happens at some manipulating including the described above. Сode interrupting was not used but for more safety I would add Application.EnableCancelKey = xlDisabled/xlInterrupt before/after of CopyMemory API call.

Thanks for the new vector of learning!
Vlad
 
Last edited:
Upvote 0
Thanks Vladimir for further testing the exmaple.

Indeed, except for point#2 ,Which was intended to prevent copying the control, I missed all the other described mis-behaviours but I guess these are easy to work around.
 
Upvote 0

Forum statistics

Threads
1,224,564
Messages
6,179,544
Members
452,925
Latest member
duyvmex

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top