Use Mac Automator to Copy Files to Newly Created Folders

How do get Mac Automator to automatically copy a set of files & folders anytime you create a new folder in a specific directory

This can be done in 4 automator steps, along with some minor tweaks.

1. Setup your source directory with the files & folders that you want placed in each new folder

2. Open Automator, choose “Folder Action”

3. At the top of the right pane, use the “Folder Actions receives file and folders added to” control to select your folder.

4. Add the “Path” variable to your workflow by choosing variables, Locations, “Path”. You can double-click or drag and drop.

5. Drag your first action, “Filter Finder Items” from the left onto your workflow.

6. Configure it to filter Find files where “All” of the following are true: Kind is Folder and Size is 0 KB.

7. Now add your second action, “Set Value of Variable” to your workflow. It should default to “Path” since you already added that variable.

8. Your third automator action is to grab your source files and folders you want to copy, do this by adding the “Get Specified Finder Items” action. You must “Ignore Input” on this action! Right click the “Get Specified Finder Items” action in your workflow and click “Ignore Input” or you’ll wind up pulling your hair out like I did. Use the Add button on this action to add your files & folders.

9. Finally, add your fourth automator action, which is “Copy Finder Items”. Drag the “Path” variable up from the bottom and drop it on the “To” area in the “Copy Finder Items” action.

Don’t forget to give your folder action a meaningful name and save it.

Try it out by creating a new folder or dropping an empty folder into your target folder.  If things worked out right, your source files & folders should be copied to the new folder automatically.

 

Learn to Code with Code Year

I’ve had many folks ask me how to get started coding over the years. There are so many resources available that it is sometimes difficult to point them in a single direction. Newcomers can easily be frustrated by having too many choices or by the girth of many programming books.

A great resource for anyone who is interested in programming, but needs help, is here. If you know someone intimidated by programming, or anyone who just wants to learn how to program on their own, there are unlimited resources on the Internet. The Code Academy is a shining example. It has interactive lessons with instant feedback, tips, help, and progress tracking. They will even award you with achievement badges. After you join the site, you’ll get a welcome email letting you know a lesson a week is going to be on your agenda.

You can skip the schedule and jump right in. Well, what are you waiting for? Get coding!

http://www.codecademy.com/

Context Awareness and Recursion Part 4 of 4

I finished up Part 3 in this series with some methods to display a hierarchical TreeView of Controls.  In the last post, I mentioned that we would continue with a demonstration of populating a menu of controls.  Hopefully at this point we’ve done this enough that you can adapt the methods to your own needs.  The code, although quite similar to the TreeView, gave me a bit of a headache but I believe to have come up with a usable solution.  The snag I ran into was wanting the top level menu item to reflect the form itself, but not have a submenu item that also pointed to the form.  Once I ironed that out, it was all downhill from there.

Once again, we’ll be using the ToolboxBitmapAttribute to spruce up our routine, or we would have some plain looking menus. The Hierarchical menu code below isn’t as polished as I was hoping it would be, but it gets the job done.  To use this example, create a new Windows Forms project and replace Form1 code with the code below:

Public Class Form1
    Dim ms As New MenuStrip
    Dim imglst As New ImageList

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        With ms
            .Items.Clear()
            .SuspendLayout()
            .ImageList = imglst
            .Parent = Me
            .Items.Add(GetControlMenu(Me))
            .ResumeLayout()
        End With

    End Sub

    Private Function GetControlMenu(ByVal root As Object, Optional ByVal pmnuitem As ToolStripMenuItem = Nothing)
        'pmnuitem ~ ParentMenuItem
        'cmnuitem ~ ChildMenuItem
        'For objects with no name
        Dim mnuName As String = IIf(root.name = "", "UnNamed " & root.name.GetType.Name, root.name)
        Dim cMnuItem As New ToolStripMenuItem

        If imglst.Images.ContainsKey(root.GetType.Name) = False Then
            'Add a new picture
            Dim bm As New ToolboxBitmapAttribute(root.GetType)
            imglst.Images.Add(root.GetType.Name, bm.GetImage(root.GetType, False))
        End If

        If pmnuitem Is Nothing Then
            pmnuitem = New ToolStripMenuItem
            With pmnuitem
                .Name = Me.Name & " Controls"
                .Text = .Name
                .Image = imglst.Images(root.GetType.Name)
            End With
            For Each item As Control In root.Controls
                GetControlMenu(item, pmnuitem)
            Next
        Else
            With cMnuItem
                .Name = mnuName
                .Text = mnuName & "(" & root.GetType.Name & ")"
                .Image = imglst.Images(root.GetType.Name)
            End With
            AddHandler cMnuItem.Click, AddressOf MenuItemClicked

            pmnuitem.DropDownItems.Add(cMnuItem)

            For Each item As Control In root.Controls
                GetControlMenu(item, cMnuItem)
            Next
        End If
        Return pmnuitem

    End Function

    Private Function MenuItemClicked(ByVal sender As System.Object, ByVal e As System.EventArgs)
        If TypeOf sender Is ToolStripMenuItem Then
            MsgBox("You clicked the " & sender.text & " menu item", MsgBoxStyle.Information, "Control Menu Demo")
            Return True
        Else
            Return False
        End If
    End Function

End Class

Before you start it up, you may want to drag a few random controls to the Form, possibly a container or two with some child controls.  Once you actually have some controls on the form, Press F5 to try it out.  I’ve included only a simple handler that provides a message box when you Click a menu item.  Like the TreeView example in Part 3 of this series, the menu items use the ToolBoxBitMapAttribute method of GetImage to use with the items.  This example isn’t as short as the TreeView example, but it has the same basic logic flow; it populates and object with items returned from a Function.  In this case, the menu item can’t be populated directly from the MenuStrip, so we have to create the top level MenuItem the first time through the routine.  I’d like to have been able to have the routine only call itself in one logic tree, but I couldn’t quite get that result.  Basically, the IF…THEN block is used to determine whether or not Object passed is Nothing, and if so create the root menu item.  Once that’s done, the pMnuItem is no longer nothing, so the next time around that logic tree is skipped.  I added a few controls to my test and yours should appear similar:

Screen Shot Menu

If you have read the first three entries of this series and still have questions, please feel free to post a comment on this blog.  I’d like to jump to the next and what I believe to be the final answer to the original taskings; the creation of a bread crumb display based on the CurrentControl.  Before we do that, get a new project ready because we are going to abandon the code from Part 1 and 2.   At the end of this series, I’m going to post a complete block of code that includes the menus, navigation, treeview, propertygrid, and all the other goodies all in one file.  Until then, lets build our “breadcrumb” style indicator.  Again, I’m coming up short with a practical reason why you would do this, but it may be useful in troubleshooting, auditing, or navigation of some sort.  Open your mind a bit; what if we were building an application with many types of custom controls; such as a drawing program or even a game?  A menu of objects may certainly come in handy!  You could use a popup menu (context) instead and set the object to the item that received the Right Click and just show it’s sub items.

Who’s Your Daddy?

And for that matter, who’s his daddy?  What about his?  Well, there’s a built in function that can tell us what object another object is owned by, and it’s no surprise that it’s the Parent property.  Each object, all the way up to the Form, has a parent.  It would be simple enough to retrieve an Object’s Parent, but we can’t know definitively how many Objects are in it’s ancestry unless we use, wait for it…recursion.  We can also get the Form that the Object is owned by using the [Object].FindForm.FindForm method.  What we are missing are the Objects between the Form and the Object itself.  Additionally, some controls can be orphaned.  We could instantiate a control, set the properties, and even add code for the control but it can’t be referenced as a member of a controls collection until it’s given a parent…how sad.  Fortunately for our purposes, we won’t need anything as complex as an Ahnentafel, but simply a direct line from the Form to the Control.

Back on track, we are going to build a breadcrumb type display, something that would look like this:

Form1 >> GroupBox1 >> GroupBox2 >> TextBox1

This would indicate that we have the Cursor hovering over TextBox1, that is a child of GroupBox2, that is a child of GroupBox1, that is a child of Form1.  In order to make this all work, we’ll need our GetChildAtPoint code back with the timer.  I cleaned it up a bit since Part 1 and Part 2 of this series, and simplified the layout a bit.  We’ll get to that in a bit, for now we need to discuss our BreadCrumb type display.  I’ve created the function, GetAncestry, that takes an Object and returns a string like discussed above.  Again, we use recursion.

Private Function GetAncestry(ByVal obj As Control, Optional ByVal crumbtrail As String = "") As String

        'AKA WhosYerDaddy, and WhosHisDaddy, etc...
        'takes a control name, calls itself recursively until parent=me (thisform)
        'the string will have to be built right to left until we get me (thisform)

        'Nothing to do
        If obj Is Me Then Return MyClass.Name

        'check for NoName
        Dim crumb As String = obj.Name
        If crumb = "" Then crumb = "Unnamed " & obj.GetType.Name

        If obj.Parent Is Me Then 'all done
            Return Me.Name & " >> " & crumb & crumbtrail
        Else 'still looking up the tree
            Return GetAncestry(obj.Parent, " >> " & crumb & crumbtrail)
        End If

End Function

The GetAncestry Function returns a string, and can be called during the Timer Tick Event or just about any other time you want to pass it a Control name and get back a your breadcrumb or Ancestry string.   Since we start with an empty string, the the Function builds it from scratch.  I made the crumbtrail optional so you don’t have to call it with an empty string.  To use the Function, use the syntax String=GetAncestry(Object).  In the Timer1_Tick event, I’m calling it with:

lblBreadCrumb.Text = "You are here: " & GetAncestry(CurrentControl)

You could easily change the “ >> ” to another delimiter and display it top to bottom with a Split if you desire, or simply use it in a label at the top of the form for a view of “Where Am I?” on this form, like I’ve shown here.

Providing Some Usefulness

None of the code in this series of posts would be much use if we didn’t actually do something with the information returned by the functions.  What you do with the information depends on your original need for actually wanting to know what control is currently under the mouse cursor.  It is ultimately up to you.  Maybe you’d like to offer a custom help solution, audit control utilization, temporarily draw to a control, allow a user to identify a control for troubleshooting, etc…  You are only limited by your imagination.

I’ve wrapped up the routines provided in this series into a single file; the hierarchical treeview, hierarchical menu, context labels, property grid, and a few other goodies.  Our example code now includes it’s own context awareness copy to clipboard (F3) functionality as an example.  I put all of the code into a single form class you can use right away.  The screen shot below was taken while the mouse was hovering over the form’s title bar and pressing F3, then pasting directly into this blog.

Screen Shot, Full App

The next images were obtained by positioning the cursor over a single control and pressing F3, then pasting into this Blog. 

image  image image

These are just a few examples on the practical use of our CurrentControl Function, I can certainly see a use for application documentation.  Another use for the CurrentControl Function could be launching some custom help tool, as an example only I’ve handled the F1 KeyDown with the following code:

Case Keys.F1 'Launch first google hit on MSDN based on the control type
                System.Diagnostics.Process.Start _
                ("http://www.google.com/search?btnI=I%27m+Feeling+Lucky&q=" & _
                 obj.GetType.ToString & "%20site:msdn.microsoft.com")

That’s about it for this series.  I’ve covered the basic tasks identified in the first post, and unless I stop adding features we’ll wind up with a useful application that actually does something.  The cleaned up and final code can be found in the code block below:

'Demonstration of using recursion, control collections, GetChildAtPoint
'Jason L Stenklyft original code only as usual
'01 Jan 2009
'For more information visit www.stenklyft.com or email jason@stenklyft.com

Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms

Public Class frmMain
    Inherits Form

#Region "Declarations"
    Enum enumRetImageTypes
        retImage
        retKeyName
    End Enum

    Public Class CoordLbl
        Inherits System.Windows.Forms.Label
        Sub New()
            Dock = DockStyle.Top
            AutoSize = True
            Padding = New Padding(3)
            Text = "Uninitiated"
        End Sub
    End Class
    Public Class GrpHdrLbl
        Inherits System.Windows.Forms.Label
        Sub New()
            Dock = DockStyle.Top
            ForeColor = Color.FromKnownColor(KnownColor.ActiveCaptionText)
            BackColor = Color.FromKnownColor(KnownColor.ActiveCaption)
            Padding = New Padding(0, 3, 0, 3)
            Text = "Uninitiated"
        End Sub
    End Class

    Private pnlLeft, pnlright As New Panel
    Private lblMouseMove, lblCursorPos, lblCurrentControlName, _
    lblCurrentControlType, lblCurrentControlLoc, lblCursorOnCurrent, _
    lblCurrentControlImage, lblCurrentControlHandle As New CoordLbl
    Public CurrentControl As Object
    Public LastControl As Object
    Private pg As New PropertyGrid
    Private btnCreateMDIChild As New Button
    Private btnRebuildMenu As New Button
    Private btnRebuildTVW As New Button
    Private chkPI As New CheckBox
    Public tmr As New Timer
    Private lblBreadCrumb As New Label
    Private lbltvwHeader, lblControlHeader, lblPropHeader As New GrpHdrLbl
    Private ms As New MenuStrip
    Private imglst As New ImageList
    Private tvwDocOutline As New TreeView
    Private ts As New ToolStrip
    Private mnuHelp, mnuAbout As New ToolStripMenuItem
    Private tslbl As New System.Windows.Forms.ToolStripLabel
    Private iMDIClientPTR As New IntPtr 'for use painting mdi client

#End Region

#Region "Load"
    Public Sub New()

        With mnuAbout
            .Text = "&About"
        End With

        With mnuHelp
            .Text = "&Help"
            .DropDown.Items.Add(mnuAbout)
        End With

        With ts
            .Dock = DockStyle.Bottom
            .Name = "ts"
            .GripStyle = ToolStripGripStyle.Hidden
            .Items.Add(tslbl)
            .SendToBack()
            .Tag = "Toolstrip"
        End With

        With tslbl
            .Name = "ts"
            .Text = "Tag Info Displays Here"
            .Alignment = ToolStripItemAlignment.Right

        End With

        With Me
            .KeyPreview = True
            .Text = "Control Recursion and GetChildAtPoint Demo"
            .Controls.Add(pnlLeft)
            .Controls.Add(pnlright)
            .Controls.Add(ts)
            .Size = New Size(800, 600)
            .Name = "frmMain"
            .Tag = "This is the main form's Tag"
        End With

        With lbltvwHeader
            .Text = "Document Outline"
        End With

        With lblControlHeader
            .Text = "Control && Coordinates"
        End With

        With lblPropHeader
            .Text = "Properties"
        End With

        With lblCurrentControlImage
            .ImageAlign = ContentAlignment.MiddleRight
            .TextAlign = ContentAlignment.MiddleLeft
            .Text = "Current Control Image:      "
        End With

        With tvwDocOutline
            .Dock = DockStyle.Fill
            .ImageList = imglst
            .Margin = New Padding(3)
            .Name = "tvwDocOutline"
        End With

        With tmr
            .Interval = 100
            .Enabled = False
        End With

        With chkPI
            .Dock = DockStyle.Top
            .Text = "Enable &Property Inspector"
        End With

        With btnCreateMDIChild
            .Dock = DockStyle.Top
            .Name = "btnCreateMDIChild"
            .Text = "Create MDI Child"
            .Margin = New Padding(6)
        End With

        With btnRebuildMenu
            .Dock = DockStyle.Top
            .Name = "btnRebuildMenu"
            .Text = "Rebuild Menu"
            .Margin = New Padding(6)
        End With

        With btnRebuildTVW
            .Dock = DockStyle.Top
            .Name = "btnRebuildTVW"
            .Text = "Rebuild TreeView"
            .Margin = New Padding(6)
        End With

        With pg
            .Dock = DockStyle.Fill
            .Name = "pg"
        End With

        With pnlLeft
            .Dock = DockStyle.Left
            .Name = "pnlgbLeft"
            .Padding = New Padding(3)
            .Width = Me.Width * 0.3
            With .Controls
                .Add(lbltvwHeader)
                .Add(lblCurrentControlHandle)
                .Add(lblCurrentControlImage)
                .Add(lblCurrentControlType)
                .Add(lblCurrentControlName)
                .Add(lblCurrentControlLoc)
                .Add(lblCursorOnCurrent)
                .Add(lblMouseMove)
                .Add(lblCursorPos)
                .Add(btnRebuildTVW)
                .Add(btnRebuildMenu)
                .Add(btnCreateMDIChild)
                .Add(chkPI)
                .Add(lblControlHeader)
                .Add(tvwDocOutline)
            End With
        End With

        With pnlright
            .Text = "Properties"
            .Name = "pnlRight"
            .Dock = DockStyle.Right
            .Width = Me.Width * 0.3
            .Padding = New Padding(3)
            With .Controls
                .Add(pg)
                .Add(lblPropHeader)
            End With
        End With

        With lblBreadCrumb
            .Dock = DockStyle.Top
            .Name = "lblBreadCrumb"
            .Text = "You Are Here: " & Me.Name
            .TextAlign = ContentAlignment.TopLeft
            .Height = 32 'Tall enough for two lines on my screen, adjust as req.
            .AutoEllipsis = True
            .AutoSize = False
            .Parent = Me
        End With

        With ms
            .Items.Clear()
            .SuspendLayout()
            .ImageList = imglst
            .Name = "ms"
            .Parent = Me
            .Items.Add(GetControlMenu(Me))
            .Items.Add(mnuHelp)
            .ResumeLayout()
        End With

        tvwDocOutline.BringToFront()
        FillTreeView(tvwDocOutline, Me)

        AddHandler btnCreateMDIChild.Click, AddressOf NewMDIChild
        AddHandler chkPI.CheckedChanged, AddressOf CheckChanged
        AddHandler tmr.Tick, AddressOf timertick
        AddHandler btnRebuildMenu.Click, AddressOf RebuildMenu
        AddHandler btnRebuildTVW.Click, AddressOf RebuildTVW
        AddHandler mnuAbout.Click, AddressOf mnuabout_Click
        AddHandler MyBase.KeyDown, AddressOf frmKeyDown
        AddHandler MyBase.MouseMove, AddressOf FormMouseMove
    End Sub

    <STAThread()> _
    Shared Sub main()
        Application.EnableVisualStyles()
        Application.Run(New frmMain)
    End Sub

    Private Sub InitializeComponent()
        Me.SuspendLayout()
        '
        'frmMain
        '
        Me.ClientSize = New System.Drawing.Size(578, 273)
        Me.Name = "frmMain"
        Me.ResumeLayout(False)

    End Sub
#End Region
    
#Region "TreeView Code"
    Private Sub RebuildTVW(ByVal sender As System.Object, ByVal e As System.EventArgs)

        FillTreeView(tvwDocOutline, Me)

    End Sub

    Function GetControlsForTreeNode(ByVal root As System.Object, Optional ByRef pnode As TreeNode = Nothing)

        Dim newpnode As New TreeNode
        If pnode Is Nothing Then pnode = New TreeNode

        Dim nodName As String
        If root.name = "" Then
            nodName = "UnNamed "
        Else
            nodName = root.name
        End If

        newpnode = pnode.Nodes.Add(nodName, nodName & " " & root.GetType.Name, GetObjectImage(root, enumRetImageTypes.retKeyName), GetObjectImage(root, enumRetImageTypes.retKeyName))

        For Each item As Control In root.Controls
            GetControlsForTreeNode(item, newpnode)
        Next
        Return newpnode

    End Function

    Sub FillTreeView(ByVal tvw As TreeView, ByVal root As System.Object)
        With tvw
            .Nodes.Clear()
            .ImageList = imglst
            .Nodes.Add(GetControlsForTreeNode(root))
            .Nodes(0).ExpandAll()
            .SelectedNode = .Nodes(0)
        End With
    End Sub

#End Region

#Region "MDI Functions"
    Private Function NewMDIChild(ByVal sender As System.Object, ByVal e As System.EventArgs)

        Try
            If Me.IsMdiContainer = False Then lblMouseMove.Text = "Form MouseMove Unavailable"
            Me.IsMdiContainer = True
            Dim frm As New Form
            Dim btn As New Button
            Dim lbl As New Label

            With frm
                .BringToFront()
                .MdiParent = Me
                .Name = "MDIChild" & Me.MdiChildren.Count.ToString
                .Text = "New MDI Child Form #" & Me.MdiChildren.Count
            End With

            With btn
                .Text = "Child of " & frm.Name
                .Name = "btn" & frm.Name
                .Location = (New Point(12, 12))
                .AutoSize = True
                .AutoSizeMode = Windows.Forms.AutoSizeMode.GrowAndShrink
                .Parent = frm
            End With

            With lbl
                .Text = "Child of " & frm.Name
                .Name = "lbl" & frm.Name
                .Location = New Point(12, 40)
                .Parent = frm
            End With
            AddHandler frm.Paint, AddressOf drawgrid
            frm.Show()

        Catch ex As Exception
            MsgBox(ex.Message)
            Return False
        End Try

        Return True

    End Function
    Public Sub drawgrid(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
        ControlPaint.DrawGrid(e.Graphics, Me.ClientRectangle, New Size(8, 8), Color.White)
    End Sub

#End Region

#Region "Meat and Potatoes"
    Private Sub FormMouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs)
        lblMouseMove.Text = "Form MouseMove: " & e.Location.ToString
    End Sub

    Private Sub timertick(ByVal sender As System.Object, ByVal e As System.EventArgs)

        CurrentControl = Me.GetChildAtPoint(Me.PointToClient(Cursor.Position))

        If CurrentControl Is Nothing Then CurrentControl = Me

        'This is how we launch the recursion function, only if the CurrentControl has children
        If CurrentControl.Controls.Count > 0 Then
            CurrentControl = GetCurrentControl(CurrentControl)
        End If

        'Bailout and don't keep updating the status/images if the currentcontrol
        'is the same as the lastcontrol.  Keep the CPU Cooler!
        If CurrentControl Is LastControl Then
            lblCursorPos.Text = "Cursor Position: " & Cursor.Position.ToString
            Exit Sub
        Else
            LastControl = CurrentControl
        End If

        'Stupid Stuff you can do
        ActuallyDoSomething(CurrentControl)

        lblCurrentControlName.Text = "Current Control Name: " & CurrentControl.Name.ToString
        lblCurrentControlType.Text = "Current Control Type: " & CurrentControl.GetType.Name
        lblCurrentControlHandle.Text = "Current Control Handle: " & CurrentControl.handle.ToString
        lblCurrentControlLoc.Text = "Current Control Location: " & CurrentControl.location.ToString
        lblCursorPos.Text = "Cursor Position: " & Cursor.Position.ToString

        lblCurrentControlImage.Image = GetObjectImage(CurrentControl, enumRetImageTypes.retImage)

        pg.SelectedObject = CurrentControl
        lblBreadCrumb.Text = "You Are Here: " & GetAncestry(CurrentControl)
        'Display the tag of the control, if no tag display the name & type
        If CurrentControl.tag = "" Then
            tslbl.Text = CurrentControl.name & _
                         " (" & CurrentControl.GetType.Name & ")"
        Else
            tslbl.Text = CurrentControl.tag
        End If

    End Sub

    Private Function CheckChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Try
            tmr.Enabled = sender.checked
            If sender.checked Then
                sender.text = "Disable &Property Inspector"
            Else
                sender.text = "Enable &Property Inspector"
            End If
        Catch ex As Exception

        End Try
        Return Nothing
    End Function

    Private Function ActuallyDoSomething(ByVal c As Control)

        'You can use this area, or just user the Timer Tick event, to actually
        'do something now you have a handle to the control under the cursor
        'based on the timer tick.  Just one of many options.

        'Just in case we need a reference to the MDI Client area later.
        'this is here, and not during the intial load, because the form 
        'isn't an mdi parent initially.

        If TypeOf c Is MdiClient Then
            c.Name = Me.Name & "_MDIClientArea"
            c.Tag = "I'm the MDI Client, last entered at " & Now.ToUniversalTime
            iMDIClientPTR = c.Handle
            Return Nothing
        End If


        Return Nothing

    End Function

    Private Function GetCurrentControl(ByVal PassedCtrl As Control) As Control

        Dim childControl As Control
        childControl = PassedCtrl.GetChildAtPoint(PassedCtrl.PointToClient(Cursor.Position))

        If childControl Is Nothing Then
            lblCursorOnCurrent.Text = "Position in Control: " & _
            PassedCtrl.PointToClient(Cursor.Position).ToString

            Return PassedCtrl
            'Kick it back you are in a container, but you are not hovering
            'over a control
        Else
            If childControl.Controls.Count > 0 Then
                'You are in a container, and hovering over a child control
                'that is also a container
                Return GetCurrentControl(childControl)
            Else
                lblCursorOnCurrent.Text = "Position in Control: " & _
                childControl.PointToClient(Cursor.Position).ToString
                Return childControl
                'You are in a container, and hovering over a control
            End If
        End If

    End Function

    Private Function GetAncestry(ByVal obj As Control, Optional ByVal crumbtrail As String = "") As String
        'AKA WhosYerDaddy, and WhosHisDaddy, etc...
        'takes a control name, calls itself recursively until parent=me (thisform)
        'the string will have to be built right to left until we get me (thisform)

        'Nothing to do
        If obj Is Me Then Return MyClass.Name

        'check for NoName
        Dim crumb As String = obj.Name
        If crumb = "" Then crumb = "Unnamed " & obj.GetType.Name

        If obj.Parent Is Me Then 'all done
            Return Me.Name & " >> " & crumb & crumbtrail
        Else 'still looking up the tree
            Return GetAncestry(obj.Parent, " >> " & crumb & crumbtrail)
        End If

    End Function

    Private Function GetObjectImage(ByVal obj As System.Object, ByVal opt As enumRetImageTypes)
        'Returns or Adds then Returns an image or imagekey name
        'from the imagelist for the obj type.  Sometimes you need an image,
        'sometimes you need just a keyname
        '
        If imglst.Images.ContainsKey(obj.GetType.Name) = False Then
            'Add a new picture
            Dim bm As ToolboxBitmapAttribute
            If TypeOf (obj) Is System.Windows.Forms.Form Then
                'This is a hack In My Opinion, I can't get a FORM image
                'unless I pull one from a new instance of a form...
                'if I use the current class, I always get a gear.
                'At least it works, any form type uses the bitmapattribute from a 
                'NEW form.  The same thing needs to be done in the menu.
                bm = New ToolboxBitmapAttribute(New Form().GetType)
            Else
                bm = New ToolboxBitmapAttribute(obj.GetType)
            End If
            imglst.Images.Add(obj.GetType.Name, bm.GetImage(obj.GetType, True))
        End If
        Select Case opt 'Uses custom enum
            Case enumRetImageTypes.retKeyName
                Return obj.GetType.Name
            Case Else 'enumRetImageTypes.retImage
                Return imglst.Images(obj.GetType.Name)
        End Select
    End Function

    Private Sub frmKeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs)
        'dont call the handler unless the inspector is on and currentcontrol isnot nothing
        If chkPI.Checked And CurrentControl IsNot Nothing Then
            ContextHandler(CurrentControl, e)
        End If
    End Sub

    Private Function ContextHandler(ByVal obj As System.Object, ByVal e As KeyEventArgs)

        'This routine handles all keydown events on the form, which has the keypreview set to true
        Select Case e.KeyCode
            Case Keys.F1 'Launch first google hit on MSDN based on the control type
                System.Diagnostics.Process.Start("http://www.google.com/search?btnI=I%27m+Feeling+Lucky&q=" & obj.GetType.ToString & "%20site:msdn.microsoft.com")
            Case Keys.F2 'Copy the text of the control to the clipboard
                Clipboard.SetText(obj.Text)
            Case Keys.F3, Keys.F4 'Why don't you take a picture, it will last longer!
                'Maybe this could be used to capture images for your application 
                'documentation?
                Dim g As Graphics = Graphics.FromHwnd(obj.handle)
                Dim bm As Bitmap = New Bitmap(obj.width, obj.height, g)
                obj.DrawToBitmap(bm, New Rectangle(0, 0, bm.Width, bm.Height))

                If e.KeyCode = Keys.F4 Then
                    Dim dlg As New SaveFileDialog
                    With dlg
                        .AddExtension = True
                        .DefaultExt = "png"
                        .FileName = obj.name.ToString
                        .Filter = "Portable Network Graphics (*.png)|*.png|All Files (*.*)|*.*"
                        .Title = "Save captured image as"
                        .CheckPathExists = True
                        .OverwritePrompt = True
                    End With
                    If dlg.ShowDialog = Windows.Forms.DialogResult.OK Then
                        bm.Save(dlg.FileName, System.Drawing.Imaging.ImageFormat.Png)
                        'This could be extended to multiple formats
                    End If
                Else ' F3
                    Clipboard.SetImage(bm)
                End If
                g = Nothing
                bm = Nothing

            Case Keys.F5
            Case Keys.F6
            Case Keys.F7
            Case Keys.F8
            Case Keys.F9

        End Select
        Return True
    End Function

#End Region

#Region "Menu Code"

    Private Function GetControlMenu(ByVal root As Object, Optional ByVal pmnuitem As ToolStripMenuItem = Nothing)
        'pmnuitem ~ ParentMenuItem
        'cmnuitem ~ ChildMenuItem
        'For objects with no name

        Dim mnuName As String
        If root.name = "" Then
            mnuName = "UnNamed"
        Else
            mnuName = root.name
        End If

        Dim cMnuItem As New ToolStripMenuItem

        If pmnuitem Is Nothing Then
            pmnuitem = New ToolStripMenuItem
            With pmnuitem
                .Name = Me.Name & " Controls"
                .Text = .Name
                .Image = GetObjectImage(root, enumRetImageTypes.retImage)
            End With
            For Each item As Control In root.Controls

                GetControlMenu(item, pmnuitem)
            Next
        Else
            With cMnuItem
                .Name = mnuName
                .Text = mnuName & " " & root.GetType.Name
                .Image = GetObjectImage(root, enumRetImageTypes.retImage)
            End With
            AddHandler cMnuItem.Click, AddressOf MenuItemClicked

            pmnuitem.DropDownItems.Add(cMnuItem)

            For Each item As Control In root.Controls
                GetControlMenu(item, cMnuItem)
            Next
        End If
        Return pmnuitem

    End Function

    Private Function MenuItemClicked(ByVal sender As System.Object, ByVal e As System.EventArgs)
        If TypeOf sender Is ToolStripMenuItem Then
            MsgBox("You clicked the " & sender.text & " menu item", MsgBoxStyle.Information, "Control Menu Demo")
            Return True
        Else
            Return False
        End If
    End Function

    Private Sub RebuildMenu(ByVal sender As System.Object, ByVal e As System.EventArgs)
        With ms
            .Items.Clear()
            .SuspendLayout()
            .Items.Add(GetControlMenu(Me))
            .ResumeLayout()
        End With
    End Sub

#End Region

#Region "AboutBox Code"
    Public Sub OKButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Me.DialogResult = Windows.Forms.DialogResult.OK
        sender.findform.close()
    End Sub
    Private Sub mnuabout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)

        Dim frmabout As New Form
        Dim lbl As New Label
        Dim btnok As New Button
        Dim frmSize As New Size(400, 320)

        Dim strAbout As String = _
        "Demonstration of using recursion, control collections, and GetChildAtPoint.  Copyright © Stenklyft Enterprises 2009.  Original code (as usual).  Created 01 Jan 2009.  For more information visit blog.stenklyft.com or email jason@stenklyft.com."
        strAbout += vbCrLf + "The purpose of this code is to accompany the posts in my blog titled ""Context Awareness and Recursion"".  It demonstrates showing the control, however nested, that is currently under the mouse cursor.  Using a timer tick is critical in this example."
        strAbout += vbCrLf + "Possible uses: Custom help, theme/skins, accessibility, Auditing, Troubleshooting, etc...Imagination is the only limit!"

        With btnok
            .AutoSize = True
            .Text = "&OK"
            .Anchor = AnchorStyles.Right + AnchorStyles.Bottom
            .Name = "btnOK"
            .NotifyDefault(True)
            .Location = New Point(frmSize.Width - 16 - .Width, _
                                        frmSize.Height - 8 - .Height - _
                                        (New Form().Height - _
                                        New Form().ClientSize.Height))
        End With

        AddHandler btnok.Click, AddressOf OKButton_Click

        With lbl
            .AutoSize = False
            .Anchor = AnchorStyles.Top
            .Text = strAbout
            .Dock = DockStyle.Top
            .Height = 260
        End With

        With frmabout
            .Size = frmSize
            .AcceptButton = btnok
            .Padding = New Padding(12)
            .MinimizeBox = False
            .MaximizeBox = False
            .Text = "About " & My.Application.Info.ProductName
            .FormBorderStyle = Windows.Forms.FormBorderStyle.FixedDialog
            .Controls.Add(btnok)
            .Controls.Add(lbl)
            .ShowDialog(Me)
        End With

    End Sub

#End Region

End Class

If you have any questions please feel free to leave a comment.  Thanks for reading!