PDA

View Full Version : Solved: Word form - add context menu for text boxes



JamJam11
03-01-2011, 04:12 PM
Hi,
I have a Word document that includes text boxes as part of a form. The right click context menu (copy, paste, cut etc) doesn't work with the text boxes.

I know how to create a context menu for a Userform in Word but have no idea how to do the same for text boxes in a regular Word document.

Can anyone help or point me to some VBA code which will do this?

Many thanks,
John

Frosty
03-02-2011, 08:04 AM
What version of Word are you using? And you just want to add a custom control when right-clicking on a text box?

JamJam11
03-02-2011, 09:18 AM
Hi Frosty,

I'm using Word 2003.

As you probably know when you right click a Textbox in a Word form (not a Userform) you don't receive the context menu which enables you to Cut, Copy or Paste.

I want to be able to provide the user with this functionality.

I did have this working in a Userform and stupidly thought I could use the same code to make it work on a regular Word document. When I tried it I did find the right click context menu appeared displaying Cut, Copy, Paste but when I selected an option nothing happened.

The code I used is as follows:

This bit is in a Class Module:


Option Explicit
Private Const mEDIT_CONTEXTMENU_NAME = "ajpiEditContextMenu"
Private Const mCUT_TAG = "CUT"
Private Const mCOPY_TAG = "COPY"
Private Const mPASTE_TAG = "PASTE"
Private m_cbrContextMenu As CommandBar
Private WithEvents m_txtTBox As msforms.TextBox
Private WithEvents m_cbtCut As CommandBarButton
Private WithEvents m_cbtCopy As CommandBarButton
Private WithEvents m_cbtPaste As CommandBarButton
Private m_objDataObject As DataObject
Private m_objParent As Object

Private Function m_CreateEditContextMenu() As CommandBar
'
' Build Context menu controls.
'
Dim cbrTemp As CommandBar
Const CUT_MENUID = 21
Const COPY_MENUID = 19
Const PASTE_MENUID = 22

Set cbrTemp = Application.CommandBars.Add(mEDIT_CONTEXTMENU_NAME, Position:=msoBarPopup)
With cbrTemp
With .Controls.Add(msoControlButton)
.Caption = "Cu&t"
.FaceId = CUT_MENUID
.Tag = mCUT_TAG
End With
With .Controls.Add(msoControlButton)
.Caption = "&Copy"
.FaceId = COPY_MENUID
.Tag = mCOPY_TAG
End With
With .Controls.Add(msoControlButton)
.Caption = "&Paste"
.FaceId = PASTE_MENUID
.Tag = mPASTE_TAG
End With
End With

Set m_CreateEditContextMenu = cbrTemp
End Function
Private Sub m_DestroyEditContextMenu()
On Error Resume Next
Application.CommandBars(mEDIT_CONTEXTMENU_NAME).Delete
Exit Sub
End Sub
Private Function m_GetEditContextMenu() As CommandBar
On Error Resume Next

Set m_GetEditContextMenu = Application.CommandBars(mEDIT_CONTEXTMENU_NAME)
If m_GetEditContextMenu Is Nothing Then
Set m_GetEditContextMenu = m_CreateEditContextMenu
End If

Exit Function

End Function
Private Function m_ActiveTextbox() As Boolean
'
' Make sure this instance is connected to active control
' May need to drill down through container controls to
' reach ActiveControl object
'
Dim objCtl As Object

Set objCtl = m_objParent.ActiveControl
Do While UCase(TypeName(objCtl)) <> "TEXTBOX"
If UCase(TypeName(objCtl)) = "MULTIPAGE" Then
Set objCtl = objCtl.Pages(objCtl.Value).ActiveControl
Else
Set objCtl = objCtl.ActiveControl
End If
Loop
m_ActiveTextbox = (StrComp(objCtl.Name, m_txtTBox.Name, vbTextCompare) = 0)

ErrActivetextbox:
Exit Function

End Function
Public Property Set Parent(RHS As Object)
Set m_objParent = RHS
End Property
Private Sub m_UseMenu()

Dim lngIndex As Long

For lngIndex = 1 To m_cbrContextMenu.Controls.Count
Select Case m_cbrContextMenu.Controls(lngIndex).Tag
Case mCUT_TAG
Set m_cbtCut = m_cbrContextMenu.Controls(lngIndex)
Case mCOPY_TAG
Set m_cbtCopy = m_cbrContextMenu.Controls(lngIndex)
Case mPASTE_TAG
Set m_cbtPaste = m_cbrContextMenu.Controls(lngIndex)
End Select
Next

End Sub
Public Property Set TBox(RHS As msforms.TextBox)
Set m_txtTBox = RHS
End Property

Private Sub Class_Initialize()

Set m_objDataObject = New DataObject
Set m_cbrContextMenu = m_GetEditContextMenu

If Not m_cbrContextMenu Is Nothing Then
m_UseMenu
End If

End Sub
Private Sub Class_Terminate()
Set m_objDataObject = Nothing
m_DestroyEditContextMenu

End Sub

Private Sub m_cbtCopy_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
' check active textbox is this instance of CTextBox_ContextMenu
If m_ActiveTextbox() Then
With m_objDataObject
.Clear
.SetText m_txtTBox.SelText
.PutInClipboard
End With
End If

End Sub
Private Sub m_cbtCut_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
' check active textbox is this instance of CTextBox_ContextMenu
If m_ActiveTextbox() Then
With m_objDataObject
.Clear
.SetText m_txtTBox.SelText
.PutInClipboard
m_txtTBox.SelText = vbNullString
End With
End If

End Sub

Private Sub m_cbtPaste_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)

' check active textbox is this instance of CTextBox_ContextMenu
On Error GoTo ErrPaste

If m_ActiveTextbox() Then
With m_objDataObject
.GetFromClipboard
m_txtTBox.SelText = .GetText
End With
End If

ErrPaste:
Exit Sub
End Sub

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

If Button = 2 Then
' right click
m_cbrContextMenu.ShowPopup
End If
End Sub


This code is in the main Word document VBA:


Private Sub Document_Open()

Dim clsContextMenu As CTextBox_ContextMenu
Set m_colContextMenus = New Collection

Set clsContextMenu = New CTextBox_ContextMenu
With clsContextMenu
Set .TBox = Me.txtReqDate
Set .Parent = Me
End With
m_colContextMenus.Add clsContextMenu, CStr(m_colContextMenus.Count + 1)

Set clsContextMenu = New CTextBox_ContextMenu
With clsContextMenu
Set .TBox = Me.txtPubDate
Set .Parent = Me
End With
m_colContextMenus.Add clsContextMenu, CStr(m_colContextMenus.Count + 1)

Set clsContextMenu = New CTextBox_ContextMenu
With clsContextMenu
Set .TBox = Me.txtBusArea
Set .Parent = Me
End With
m_colContextMenus.Add clsContextMenu, CStr(m_colContextMenus.Count + 1)

Set clsContextMenu = New CTextBox_ContextMenu
With clsContextMenu
Set .TBox = Me.txtJobRef
Set .Parent = Me
End With
m_colContextMenus.Add clsContextMenu, CStr(m_colContextMenus.Count + 1)
End Sub



My textboxes are named:

txtReqDate
txtPubDate
txtBusArea
txtJobRefAny thoughts on how I can convert the above code to work with the main Word document rather than a Userform?

Thanks,
John

Frosty
03-02-2011, 10:06 AM
Sorry, I'm having trouble duplicating your problem.

I have a word 2003 document, open in Word 2003, with protected for fillin fields turned on, and I am able to right-click to cut/copy/paste from one text field to another.

You're confusing me with your userform vs. word form nomenclature. You mean a userform created in the VBA interface, vs. a document with ActiveDocument.ProtectionType = wdAllowOnlyFormFields?

JamJam11
03-02-2011, 10:40 AM
Hi Frosty,

Sorry for any confusion with this.

Some background... originally I had a Userform created in the Word VBA interface. I included the code in my earlier post to enable the user to Copy/Paste in textboxes on the Userform and this all worked fine.

I then changed my mind and dropped the Userform and decided to go with the form being located in the body of a standard Word document. I've done this before but feedback from users has made me realise I needed to offer a right click Copy/Paste function.

At present, during testing, my document is protected for 'Filling in forms' only.

When I right click on a textbox the context menu appears offering Cut, Copy, Paste.

When I choose any option from the context menu I receive the following error message:

Run-time error '438':
Object doesn't support this property or method.

When I select debug I am taken to the code in the Class Module and the following line is highlighted:


Set objCtl = m_objParent.ActiveControl


This line is located in this section of the code:


Private Function m_ActiveTextbox() As Boolean


Apologies for not mentioning the error message before.

Does this help?
Many thanks again for trying to assist me; it is appreciated.
John

Frosty
03-02-2011, 10:51 AM
I think you need to upload the document and the code you're talking about.

Word natively provides the ability to right-click and use cut/copy/paste on fillin fields in word documents which are protected for fillin-fields.

So... you shouldn't need to debug anything. That functionality already exists.

However, if you are running code on a protected document, you will definitely run in to some issues, depending on what you're doing.

What I have typically done on protected documents that I need to run code on is to unprotect and reprotect after my processing is done.

However, I don't think you need to do any of that, if you've gone away from the methodology of using a userform to capture end-user input, and have simply moved to a word "form" document.

That said-- especially in Word 2003... there are so many little quirks to dealing with protected word documents, that I would generally try to stick to the userform methodology (document/application events you may rely on will not fire on protected documents or if a protected document happens to be the active document, etc).

Bottom line: you probably need to upload a sample file to explain. I can read your code, but I don't know what you really want to do from your explanation that isn't already provided in native word.

Frosty
03-02-2011, 10:57 AM
By the way... I like your solution on providing a context menu in a user form. I just don't think it helps you anymore, unless you're trying to do something other than copy/cut/paste.

JamJam11
03-02-2011, 11:09 AM
Hi again,

I attach a simple example of the type of thing I am trying to get working.

The password to remove protection is test.

If you open the document and try to Cut or Copy in the textboxes you will receive the error message. Trying to Paste doesn't seem to do anything even when there is text on the Clipboard.

Cheers,
John

5582

Frosty
03-02-2011, 12:11 PM
John,

I think the info you're missing is that the MSForms textbox control in the document is considered first and foremost a field, and to get down to the property you need to access, you need to go through the OLEFormat property.

However, it's going to take a little bit of thought on how to apply this to your document. The simple reference you're looking for on that particular error is probably something along the lines of:

Set objCtrl = Selection.Range.Fields(1).OLEFormat.Object

However, that deep in the nesting, relying on the Selection object may not be the optimal solution.

There's a good bit going on in your class that I'm not sure is totally necessary in this context... but you've also given me a simplified document with a pretty complicated class object.

Hope this helps.

JamJam11
03-02-2011, 04:12 PM
Hi Frosty,

Thanks for your last post, it looks like you've provided the information I needed to solve this problem.

In this section of the Class Module:


Private Function m_ActiveTextbox() As Boolean


I changed this line:


Set objCtl = m_objParent.ActiveControl


to this:


Set objCtl = Selection.Range.Fields(1).OLEFormat.Object


Having done this it all seems to work!

I can Cut, Copy and Paste using the menu which appears when right clicking in a textbox.

I've tested it over and over again and not had any problems.

So thank you so much for your help. I've been trying to fix this for several days.

All the best,
John
:)

Frosty
03-02-2011, 04:24 PM
Several days! That must mean I'm brilliant. Or... you've done a good job coding, and you just needed some fresh eyes to look over your project.

Thanks for posting the code, I got to learn something by seeing the process you'd developed. Sorry I wasn't as explicit as you were in posting the solution, but I was worried that I didn't fully understand all that was going on.

It seemed like, at least in this case, that the Selection object would give you the hook to get to your "ActiveControl" replacement, but the m_ActiveTextbox function is so buried it made me a little wary of saying "this is the solution."

I'm not a fan of referencing something which easily could have been changed by the time you got there.

It would probably be safer to pass the object down through the calling routines, but you'd have to weigh the cost/benefit on that.

Glad to have helped.

- Jason

JamJam11
03-03-2011, 02:38 AM
Hi Jason,
As far as I'm concerned you are 'brilliant'. I was beginning to think I'd never get it working. Lost count of the hours I've spent on Google and VBA forums looking for a solution.

The form is a project for work and therefore I have a deadline looming and this one thing was holding me back.

But now it's sorted!

Many thanks again.

John