Clipboard Issue copying from Outlook.

  • Thread starter Thread starter Anthony
  • Start date Start date
A

Anthony

I have an Outlook COM Add-in written in VB6. It must run on Outlook
versions 2000-2003. It currently saves the clipboard data, uses the
clipboard to add images to custom commandbar buttons and restores the
clipboard so the users' information is preserved.

This all works well _except_ when multiple documents (of the same type,
say Word 2003) are copied from a mail message from within Outlook (the
preview pane or an inspector). The user presses 'New'(Mail Message) in
Outlook and my custom toolbar is added to the inspector (including usage
of the clipboard). When the user pastes the attachments into this new
email they _see_ all of the correct document filenames but the contents
of all the files are the same as the first attachment (they should be
unique). If these documents were copied from Windows Explorer they paste
into the new email without issue.

I have narrowed the issue down to a difference in available clipboard
formats between a Copy from within Outlook and a Copy from Windows
Explorer - the Outlook copy lacks the CF_HDROP data format but instead
has the FileContents and FileGroupDescriptor types instead.

Has anyone seen this before? I either need a better way to use the
clipboard (so as to re-constitute the documents correctly) or avoid
using the clipboard for New MailItems. I attempted to create my
Inspector toolbar in the Outlook Explorer, on start-up, so I could copy
it (without the need to use the clipboard) when an inspector was opened
but I do not think this is possible).

Sorry for so many words.

Regards,
Anthony

The essence of the clipboard code.

Private Type clipContents
formatType As Long
dataGlobal As Long
clipSize As Long
End Type

Private m_clipContents() As clipContents
Private m_clipCount As Integer

Public Sub StoreEx()
On Error GoTo err_Handler

Dim p_clipGlobal As Long
Dim p_clipData As Long
Dim p_clipSize As Long

Dim p_savedGlobal As Long
Dim p_savedData As Long
Dim p_tmpFormat As Long

Erase m_clipContents
m_clipCount = 0

'// Open the clipboard with the current process as the owner
If OpenClipboard(0) = 0 Then
Exit Sub
End If
p_tmpFormat = EnumClipboardFormats(0)
Do Until p_tmpFormat = 0
'// Obtain a handle the the memory owned by the clipboard
p_clipGlobal = GetClipboardData(p_tmpFormat)
If p_clipGlobal > 0 Then
p_clipData = GlobalLock(p_clipGlobal)

'// If we don't have a handle then we barf
If p_clipData > 0 Then '// Failure
'// Identify the amount of memory we need to make a copy
of the data
p_clipSize = GlobalSize(p_clipGlobal)
If p_clipSize > 0 Then
'// Allocate enough memory to copy into
p_savedGlobal = GlobalAlloc(GMEM_MOVEABLE,
p_clipSize)
If p_savedGlobal > 0 Then
'// Retrieve a pointer to the first byte of the
memory block we just allocated (This also protects this piece of memory
being destroyed inadvertantly)
p_savedData = GlobalLock(p_savedGlobal)
If p_savedData > 0 Then
'// Copy the clipboard data into our memory
CopyMemory ByVal p_savedData, ByVal
p_clipData, p_clipSize
Dim p_Iclip As clipContents
With p_Iclip
.formatType = p_tmpFormat
.dataGlobal = p_savedGlobal
.clipSize = p_clipSize
End With

ReDim Preserve m_clipContents(m_clipCount +
1)
m_clipContents(m_clipCount) = p_Iclip
m_clipCount = m_clipCount + 1
GlobalUnlock (p_savedGlobal)
End If
End If
End If
GlobalUnlock (p_clipGlobal)
End If
End If
p_tmpFormat = EnumClipboardFormats(p_tmpFormat)
Loop
'// Release the clipboard
CloseClipboard
Exit Sub
err_Handler:
CloseClipboard
CleanUp
End Sub

Public Sub RestoreEx()
Dim p_cnt As Integer
On Error GoTo err_Handler

'// Open the clipboard with the current process as the owner
If OpenClipboard(0) > 0 Then
If EmptyClipboard > 0 Then
For p_cnt = 0 To m_clipCount - 1
'// Place our piece of memory in the clipboard
If SetClipboardData(m_clipContents(p_cnt).formatType,
m_clipContents(p_cnt).dataGlobal) = 0 Then
' Err.Raise Err.LastDllError, "Clipboard",
"SetClipboardData: " & _
DecodeAPIErrors(Err.LastDllError) '// Failure
End If
Next p_cnt
End If
End If

CloseClipboard

Exit Sub
err_Handler:
CloseClipboard
CleanUp
End Sub
 
What I usually do when I use the clipboard for that is first I use the
clipboard from the Win32 API. I also make sure to clear the clipboard after
saving the original contents and after my image work. I also create the
button images on the clipboard using custom registered formats for Office
buttons: "Toolbar Button Face" and "Toolbar Button Mask". I use GDI+ for
copying the bitmaps onto the clipboard and then when finished I remove the
custom formats I registered for the clipboard. I've found that prevents
interference with other things.
 
Thankyou for taking the time to respond. Unfortunately, I think I have
all of your suggestions in place. I would be happy to generate an add-in
which demonstrates the behaviour if you are willing to take this
further, I realise it probably goes beyond the service offered here, but
I can't see how this issue would not effect all (Outlook) add-ins _if_ I
have the code correctly implemented.
the clipboard from the Win32 API.

This is an example of one of the functions I use from the API
TextViewer.
"Private Declare Function OpenClipboard Lib "user32" (ByVal hWnd As
Long) As Long"
contents and after my image work.

I am now doing this, I call EmptyClipboard just before CloseClipboard in
my StoreEx procedure. It made no difference.
registered formats for Office
buttons: "Toolbar Button Face" and "Toolbar Button Mask".
I use GDI+ for copying the bitmaps onto the clipboard and then when
finished I remove the
custom formats I registered for the clipboard.

We do this already. The code is based on the following Microsoft
Knowledge Base article.
http://support.microsoft.com/kb/288771

This KB code was researched and implemented by a colleague but I think
this code removes the custom formats from the clipboard as you have
suggested - maybe you could confirm?.
' Delete the mask and clean up (a copy is on the clipboard).
DeleteObject hbmButtonMask
If bDeletePal Then DeleteObject hPal
ReleaseDC 0, hdcScreen

At this point, I am happy for any suggestions you might care to offer.

Regards,
Anthony
 
I don't know if this will help but these are some of the relevant procedures
I use:

Public Sub CopyBitmapAsButtonFace(ByVal hBmpSrc As Long, ByVal clrMaskColor
As OLE_COLOR)
Dim hPal As Long
Dim hDC As Long
Dim hPrevBitmap As Long
Dim hNewPalette As Long
Dim hbmButtonImage As Long
Dim hbmButtonFace As Long
Dim hbmButtonMask As Long
Dim bDeletePal As Boolean
Dim lMaskClr As Long
Dim LP As LOGPALETTE256
Dim nNumPalEntries As Long

On Error GoTo EH

If hBmpSrc Then
Call GetCommandbarBackground

hDC = CreateCompatibleDC(0&)

If hPal = 0 Then
hPal = CreateHalftonePalette(hDC)
bDeletePal = True
End If

' Translate the OLE_COLOR value to a GDI COLORREF value based on the
palette.
OleTranslateColor clrMaskColor, hPal, lMaskClr

hbmButtonFace = CopyBitmap(hBmpSrc, cbmDIB)

hbmButtonImage = CreateButtonImage(hBmpSrc, hDC, clrMaskColor)

hbmButtonMask = CreateButtonMask(hBmpSrc, lMaskClr, hDC, hPal)

Call CopyBitmapsToClipboard(hbmButtonImage, hbmButtonFace,
hbmButtonMask, hDC)

End If ' hBmpSrc

ExitNow:
DeleteObject hbmButtonMask
DeleteObject hbmButtonImage
DeleteObject hbmButtonFace

If bDeletePal Then DeleteObject hPal
Call DeleteDC(hDC)

Exit Sub

EH:
Err.Clear
Resume ExitNow
End Sub

Private Sub CopyBitmapsToClipboard(ByVal hbmImage As Long, ByVal hbmFace As
Long, _
ByVal hbmMask As Long, ByVal hdcTarget As Long)

Dim cfBtnFace As Long
Dim cfBtnMask As Long
Dim hGMemImage As Long
Dim hGMemFace As Long
Dim hGMemMask As Long

Dim blnDefaultToolbarButtonFace As Boolean
Dim blnDefaultToolbarButtonMask As Boolean

On Error Resume Next

' Open the clipboard.
If OpenClipboard(0) Then
' Get the cf for button face and mask.
If strToolbarButtonFace = "" Then
strToolbarButtonFace = "Toolbar Button Face"
blnDefaultToolbarButtonFace = True
End If

If strToolbarButtonMask = "" Then
strToolbarButtonMask = "Toolbar Button Mask"
blnDefaultToolbarButtonMask = True
End If


' Get the cf for button face and mask.
'cfBtnFace = RegisterClipboardFormat("Toolbar Button Face")
'cfBtnMask = RegisterClipboardFormat("Toolbar Button Mask")
cfBtnFace = RegisterClipboardFormat(strToolbarButtonFace)
cfBtnMask = RegisterClipboardFormat(strToolbarButtonMask)

If blnDefaultToolbarButtonFace Then
strToolbarButtonFace = ""
End If

If blnDefaultToolbarButtonMask Then
strToolbarButtonMask = ""
End If

'If hbmImage Then
' hGMemImage = DIBSectionToPackedDIB(hbmImage)
'
' If hGMemImage Then
' If SetClipboardData(vbCFDIB, hGMemImage) = 0 Then Call
GlobalFree(hGMemImage)
' End If
'End If

If hbmFace Then
hGMemFace = DIBSectionToPackedDIB(hbmFace)

If hGMemFace Then
If SetClipboardData(cfBtnFace, hGMemFace) = 0 Then Call
GlobalFree(hGMemFace)
End If
End If

If hbmMask Then
hGMemMask = DIBSectionToPackedDIB(hbmMask)

If hGMemMask Then
If SetClipboardData(cfBtnMask, hGMemMask) = 0 Then Call
GlobalFree(hGMemMask)
End If
End If ' hbmMask

' We're done.
CloseClipboard
End If ' OpenClipboard(0)

Err.Clear
End Sub

Private Function CreateButtonMask(ByVal hbmSource As Long, ByVal nMaskColor
As Long, _
ByVal hdcTarget As Long, ByVal hPal As Long) As Long

Dim hdcSource As Long
Dim hdcMask As Long
Dim hbmSourceOld As Long
Dim hbmMaskOld As Long
Dim hpalSourceOld As Long
Dim uBM As bitmap

Dim hbmMask As Long

On Error Resume Next

' Get some information about the bitmap handed to us.
GetObjectAPI hbmSource, 24, uBM

' Check the size of the bitmap given.
If uBM.bmWidth < 1 Or uBM.bmWidth > 30000 Then
Exit Function
End If

If uBM.bmHeight < 1 Or uBM.bmHeight > 30000 Then
Exit Function
End If

' Create a compatible DC, load the palette and the bitmap.
hdcSource = CreateCompatibleDC(hdcTarget)
hpalSourceOld = SelectPalette(hdcSource, hPal, True)
RealizePalette hdcSource
hbmSourceOld = SelectObject(hdcSource, hbmSource)

' Create a black and white mask the same size as the image.
hbmMask = CreateBitmap(uBM.bmWidth, uBM.bmHeight, 1, 1, ByVal 0)

' Create a compatble DC for it and load it.
hdcMask = CreateCompatibleDC(hdcTarget)
hbmMaskOld = SelectObject(hdcMask, hbmMask)

' All you need to do is set the mask color as the background color
' on the source picture, and set the forground color to white, and
' then a simple BitBlt will make the mask for you.
SetBkColor hdcSource, nMaskColor
SetTextColor hdcSource, vbWhite
BitBlt hdcMask, 0, 0, uBM.bmWidth, uBM.bmHeight, hdcSource, 0, 0,
vbSrcCopy

' Clean up the memory DCs.
SelectObject hdcMask, hbmMaskOld
DeleteDC hdcMask

SelectObject hdcSource, hbmSourceOld
SelectObject hdcSource, hpalSourceOld
DeleteDC hdcSource

CreateButtonMask = hbmMask

Err.Clear
End Function

' ===================================================================
' CopyButtonMaskToClipboard -- Internal helper function
' ===================================================================
Private Sub CopyButtonMaskToClipboard(ByVal hbmMask As Long, _
ByVal hdcTarget As Long)

Dim cfBtnFace As Long
Dim cfBtnMask As Long
Dim hGMemFace As Long
Dim hGMemMask As Long
Dim lpData As Long
Dim lpData2 As Long
Dim hMemTmp As Long
Dim cbSize As Long
Dim arrBIHBuffer(50) As Byte
Dim arrBMDataBuffer() As Byte
Dim uBIH As BITMAPINFOHEADER

Dim blnDefaultToolbarButtonFace As Boolean
Dim blnDefaultToolbarButtonMask As Boolean

On Error Resume Next

uBIH.biSize = 40

' Get the BITMAPHEADERINFO for the mask.
GetDIBits hdcTarget, hbmMask, 0, 0, ByVal 0&, uBIH, 0
CopyMemory arrBIHBuffer(0), uBIH, 40

' Make sure it is a mask image.
If uBIH.biBitCount <> 1 Then
Exit Sub
End If

If uBIH.biSizeImage < 1 Then
Exit Sub
End If

' Create a temp buffer to hold the bitmap bits.
ReDim Preserve arrBMDataBuffer(uBIH.biSizeImage + 4) As Byte

' Open the clipboard.
If Not CBool(OpenClipboard(0)) Then
Exit Sub
End If

If strToolbarButtonFace = "" Then
strToolbarButtonFace = "Toolbar Button Face"
blnDefaultToolbarButtonFace = True
End If

If strToolbarButtonMask = "" Then
strToolbarButtonMask = "Toolbar Button Mask"
blnDefaultToolbarButtonMask = True
End If

' Get the cf for button face and mask.
'cfBtnFace = RegisterClipboardFormat("Toolbar Button Face")
'cfBtnMask = RegisterClipboardFormat("Toolbar Button Mask")
cfBtnFace = RegisterClipboardFormat(strToolbarButtonFace)
cfBtnMask = RegisterClipboardFormat(strToolbarButtonMask)

If blnDefaultToolbarButtonFace Then
strToolbarButtonFace = ""
End If

If blnDefaultToolbarButtonMask Then
strToolbarButtonMask = ""
End If

' Open DIB on the clipboard and make a copy of it for the button face.
hMemTmp = GetClipboardData(CF_DIB)
If hMemTmp <> 0 Then
cbSize = GlobalSize(hMemTmp)
hGMemFace = GlobalAlloc(&H2002, cbSize)
If hGMemFace <> 0 Then
lpData = GlobalLock(hMemTmp)
lpData2 = GlobalLock(hGMemFace)
CopyMemory ByVal lpData2, ByVal lpData, cbSize
GlobalUnlock hGMemFace
GlobalUnlock hMemTmp

If SetClipboardData(cfBtnFace, hGMemFace) = 0 Then
GlobalFree hGMemFace
End If
End If
End If

' Now get the mask bits and the rest of the header.
GetDIBits hdcTarget, hbmMask, 0, uBIH.biSizeImage, _
arrBMDataBuffer(0), arrBIHBuffer(0), 0

' Copy them to global memory and set it on the clipboard.
hGMemMask = GlobalAlloc(&H2002, uBIH.biSizeImage + 50)
If hGMemMask <> 0 Then
lpData = GlobalLock(hGMemMask)
CopyMemory ByVal lpData, arrBIHBuffer(0), 48
CopyMemory ByVal (lpData + 48), _
arrBMDataBuffer(0), uBIH.biSizeImage
GlobalUnlock hGMemMask

If SetClipboardData(cfBtnMask, hGMemMask) = 0 Then
GlobalFree hGMemMask
End If
End If

' We're done.
CloseClipboard

Err.Clear
End Sub
 
Hi Ken,

Sorry to labour this, my customers actually reported the issue so I
would like to get to the bottom of it if I can.

Your approach was different to the one I was using. I actually
implemented it (with the help of Mike D Sutton - EDAIS)
DIBSectionToPackedDIB and all. But the same behaviour exhibits.
Are you attaching custom CommandBars to Inspectors? It's not the
inspector, it's pasting the images to the commandbar controls. Can you
please try something for me with your own add-in in the following order?

1. Copy two Word Documents from an email.
2. Open a new Inspector (where a custom toolbar is created and the
images pasted)
3. Paste the documents(into the inspector, mine will not paste into
Windows Explorer - red flag)
4. Open the second Document, are the contents correct?

Kind Regards,
Anthony
 
I don't follow your steps. What does Windows Explorer and a red flag? have
to do with things?

When I copy an attached Word doc from an email and then open a new message
the doc doesn't paste at all into the message if I'm using WordMail. If I
copy attachments from an email after opening a new email then both docs
paste correctly and have their own contents.

So what I'm doing isn't preserving the clipboard contents for copied Word
docs, I'd just make that a FAQ entry and tell users to copy after opening
the new email item. No big deal.

You must use the clipboard for images in WordMail items, Mask and Picture
won't work because Word is subclassed by Outlook and therefore you would be
passing IPictureDisp objects for the images across process boundaries, an
exception. So you always have to use the clipboard for that except when in
WordMail for Outlook 2007, where you just use the ribbon instead.
 
Some clarification
I meant I can _only_ paste the files into an Inspector _not_ into
Windows Explorer, therefore this was an indication that the clipboard
was corrupted even though it looks fine when pasted into Outlook.
It must be two or more of the same filetype (two .doc or two .jpg,
etc...) not just one. You must open the files and compare them to see
the contents of the first file in the subsequent files.
the new email item. No big deal.
It is starting to look like Documentation is my only solution. :(
Picture won't work because Word is subclassed by Outlook and therefore
you would be passing IPictureDisp objects for the images across process
boundaries, an
exception. So you always have to use the clipboard for that except when
in WordMail for Outlook 2007, where you just use the ribbon instead.
Yes, thanks.

Thanks for taking the time to respond to my initial question, I know
this was convoluted. I will probably document as the solution.

Regards,
Anthony
 
Back
Top