Win API can't retrieve palette from clipboard

A

active

Guess I'm looking for someone who likes to work difficult puzzles.

I can't seem to ever retrieve a palette handle from the clipboard.

Below is a simple test program that demonstrates the problem.

The program uses windows api to get a handle to a palette from the
clipboard.

The palette needs to be put there by another program - maybe Photoshop.

The returned handle is always zero.

Is Windows really capable of using the clipboard to pass a palette?

The crux of the code is this:



Private Sub button2_Click(ByVal sender As ...

Const CF_PALETTE As Integer = 9

If OpenClipboard(Me.Handle) Then

If IsClipboardFormatAvailable(CF_PALETTE) Then

Hpal = GetClipboardData(CF_PALETTE)

If Hpal.ToInt32 = 0 Then

Dim ex As Win32Exception = New Win32Exception(Marshal.GetLastWin32Error())

MessageBox.Show("GetClipboardData exited with Hpal=0 ...snip



To run the below code simply replace the code in a form with it and run the
form













Imports System.Drawing.Imaging

Imports System.ComponentModel

Imports System.Runtime.InteropServices

Public Class Form1

Inherits System.Windows.Forms.Form

Private WithEvents button1 As System.Windows.Forms.Button

Private WithEvents button2 As System.Windows.Forms.Button

Private WithEvents button3 As System.Windows.Forms.Button

Private components As System.ComponentModel.IContainer

Private Hpal As IntPtr ' HPALETTE

Friend WithEvents ToolTip1 As System.Windows.Forms.ToolTip

Friend WithEvents TextBox1 As System.Windows.Forms.TextBox

Friend WithEvents TextBox2 As System.Windows.Forms.TextBox

Friend WithEvents Timer1 As System.Windows.Forms.Timer

Public Declare Auto Function GetClipboardData Lib "user32.dll" (ByVal
wFormat As Integer) As IntPtr

Public Declare Auto Function IsClipboardFormatAvailable Lib "user32.dll"
(ByVal wFormat As Integer) As Boolean

Public Declare Auto Function OpenClipboard Lib "user32.dll" (ByVal hWnd As
IntPtr) As Boolean

Public Declare Auto Function CloseClipboard Lib "user32.dll" () As Boolean

Public Sub New()

InitializeComponent()

End Sub 'New

Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

If disposing Then

If Not (components Is Nothing) Then

components.Dispose()

End If

End If

MyBase.Dispose(disposing)

End Sub 'Dispose

#Region "Windows Form Designer generated code"

Private Sub InitializeComponent()

Me.components = New System.ComponentModel.Container

Me.button1 = New System.Windows.Forms.Button

Me.button2 = New System.Windows.Forms.Button

Me.button3 = New System.Windows.Forms.Button

Me.ToolTip1 = New System.Windows.Forms.ToolTip(Me.components)

Me.TextBox1 = New System.Windows.Forms.TextBox

Me.TextBox2 = New System.Windows.Forms.TextBox

Me.Timer1 = New System.Windows.Forms.Timer(Me.components)

Me.SuspendLayout()

'

'button1

'

Me.button1.Location = New System.Drawing.Point(186, 124)

Me.button1.Name = "button1"

Me.button1.Size = New System.Drawing.Size(130, 24)

Me.button1.TabIndex = 2

Me.button1.Text = "Copy File Palette"

Me.ToolTip1.SetToolTip(Me.button1, "Copy Palette from a GIF file to the
clipboard")

'

'button2

'

Me.button2.Location = New System.Drawing.Point(13, 124)

Me.button2.Name = "button2"

Me.button2.Size = New System.Drawing.Size(156, 24)

Me.button2.TabIndex = 2

Me.button2.Text = "Retrieve Clipboard Palette"

Me.ToolTip1.SetToolTip(Me.button2, "Retrieve a palette from the clipboard")

'

'button3

'

Me.button3.Location = New System.Drawing.Point(88, 167)

Me.button3.Name = "button3"

Me.button3.Size = New System.Drawing.Size(130, 24)

Me.button3.TabIndex = 2

Me.button3.Text = "Exit"

'

'TextBox1

'

Me.TextBox1.Location = New System.Drawing.Point(12, 24)

Me.TextBox1.Multiline = True

Me.TextBox1.Name = "TextBox1"

Me.TextBox1.Size = New System.Drawing.Size(304, 47)

Me.TextBox1.TabIndex = 3

Me.TextBox1.Text = "Palette can be placed on clipboard by using the 'Save
Palette"" button or by some " & _

"external program such as Photoshop"

'

'TextBox2

'

Me.TextBox2.Location = New System.Drawing.Point(13, 85)

Me.TextBox2.Name = "TextBox2"

Me.TextBox2.Size = New System.Drawing.Size(302, 20)

Me.TextBox2.TabIndex = 4

'

'Timer1

'

Me.Timer1.Enabled = True

'

'Form1

'

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)

Me.ClientSize = New System.Drawing.Size(328, 204)

Me.Controls.Add(Me.TextBox2)

Me.Controls.Add(Me.TextBox1)

Me.Controls.Add(Me.button1)

Me.Controls.Add(Me.button2)

Me.Controls.Add(Me.button3)

Me.Name = "Form1"

Me.Text = "Form1"

Me.ResumeLayout(False)

Me.PerformLayout()

End Sub 'InitializeComponent

#End Region

<STAThread()> Shared Sub Main()

Application.Run(New Form1)

End Sub 'Main

Private Sub button1_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles button1.Click

'Place a Palette on the clipboard

'TO DO

End Sub 'button1_Click

Private Sub button2_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles button2.Click

Const CF_PALETTE As Integer = 9

If OpenClipboard(Me.Handle) Then

If IsClipboardFormatAvailable(CF_PALETTE) Then

Hpal = GetClipboardData(CF_PALETTE)

If Hpal.ToInt32 = 0 Then

Dim ex As Win32Exception = New Win32Exception(Marshal.GetLastWin32Error())

MessageBox.Show("GetClipboardData exited with Hpal=0 and error code=" &
ex.NativeErrorCode, ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error)

End If

Dim NoErr As Boolean = CloseClipboard()

If Not NoErr Then

Dim ex As Win32Exception = New Win32Exception(Marshal.GetLastWin32Error())

MessageBox.Show("Close Clipboard exited with error code=" &
ex.NativeErrorCode, ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error)

End If

Else

MessageBox.Show("No Palette on Clipboard")

Dim NoErr As Boolean = CloseClipboard()

If Not NoErr Then

Dim ex As Win32Exception = New Win32Exception(Marshal.GetLastWin32Error())

MessageBox.Show("Close Clipboard exited with error code=" &
ex.NativeErrorCode, ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error)

End If

End If

Else

Dim exc As Win32Exception = New Win32Exception(Marshal.GetLastWin32Error())

MessageBox.Show("Open Clipboard exited with error code=" &
exc.NativeErrorCode, exc.Message, MessageBoxButtons.OK,
MessageBoxIcon.Error)

End If





End Sub 'button2_Click

Private Sub button3_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles button3.Click

Application.Exit()

End Sub 'button3_Click

Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs)
Handles Timer1.Tick

Dim DataO As DataObject = CType(Clipboard.GetDataObject(), DataObject)

If DataO.GetDataPresent(DataFormats.Palette, False) Then

Dim cp As ColorPalette = CType(DataO.GetData(DataFormats.Palette),
ColorPalette)

TextBox2.Text = "There is a Palette on the Clipboard"

Else

TextBox2.Text = "The Clipboard does not contain a Palette"

End If

End Sub

End Class 'Form1
 
M

Michael Phillips, Jr.

Why don't you write a test case that places the palette on the clipboard and
then retrieves the palette?

Here are the steps:
1) Use the Image class to open a .gif file
2) Grab the palette by using Image.Palette
3) Create a Window's GDI HPALETTE using P-Invoke CreatePalette
4) Open the clipboard using your Form's Window handle
5) Empty the clipboard
6) Use SetClipboardData(CF_PALETTE, hPal) to place your palette on the
clipboard and
optionally place your .gif on the clipboard using
SetClipboardData(CF_BITMAP, hBitmap) or SetClipboardData(CF_DIB, hDIB).
Ensure that you do not dispose of any handles as the clipboard will own
them.
7) Close the clipboard

Use the rest of your code to retrieve the palette and/or image from the
clipboard

Use the free Windows's clipboard viewer to verify that your palette is on
the clipboard. It will display all of the colors in the palette. It will
also display the .gif image.

Use FormatMessage to display a human readable error message if your palette
handle is null.
 
A

active

6) Use SetClipboardData(CF_PALETTE, hPal) to place your palette on the
clipboard and
optionally place your .gif on the clipboard using
SetClipboardData(CF_BITMAP, hBitmap) or SetClipboardData(CF_DIB, hDIB).

Will the latter also place a palette on the clipboard ?


Thanks again
 
M

Michael Phillips, Jr.

6) Use SetClipboardData(CF_PALETTE, hPal) to place your palette on the
clipboard and
optionally place your .gif on the clipboard using
SetClipboardData(CF_BITMAP, hBitmap) or SetClipboardData(CF_DIB, hDIB).
Will the latter also place a palette on the clipboard ?

No. You must explicitly place a palette on the clipboard with
SetClipboardData(CF_PALETTE, hPal).
You would only place a palette on the clipboard if the bitmap is 8bpp or
less. For that case, You would place the HBITMAP and the HPALETTE on the
clipboard.

If you place a DIB on the clipboard via SetClipboardData(CF_DIB, hDIB), then
no palette is necessary as it is included in the DIB.
 
A

active

I think the bottom line is this question:

If the display driver is not in an 8-bit mode can a palette be retrieved
from the clipboard.

It appears to me that the answer it that it can not be retrieved.

Is that true??


That is really what I wish to do, retrieve a palette placed there by IE or
photoshop and display it.




Thanks in advance
 
M

Michael Phillips, Jr.

It appears to me that the answer it that it can not be retrieved.
Is that true??

No! See my response to your in the other thread.

You code is not correct!

I created a simple test case based upon Bob Powell's TransparentGifCreator
VB code.

I added support for placing a palette on the clipboard and retrieving it
with no issues.

If you are still stuck, I will post the code. You would learn more if you
struggle with it yourself.
 
A

active

If you are still stuck, I will post the code. You would learn more if you
struggle with it yourself.
I sure like to see it.

I've basically given up and just wrote a program to get the palette by
reading the DIB stream. I haven't displayed it yet but I do have the byte
array and it looks OK.

But I'd like to see how to get around some compiler errors I get when I use
the Declares you suggested.


Thanks for you interest in my problem
 
M

Michael Phillips, Jr.

Here you go:

I started with the TransparentGifCreator VB code from BobPowell.net
http://www.bobpowell.net/giftransparency.htm

I added the following to Bob's code:

'Native API Declarations
Public Declare Auto Function CreatePalette Lib "gdi32.dll" (ByVal
lpLogPalette As IntPtr) As IntPtr
Public Declare Auto Function SetClipboardData Lib "user32.dll" (ByVal
wFormat As Integer, ByVal hPal As IntPtr) As IntPtr
Public Declare Auto Function GetClipboardData Lib "user32.dll" (ByVal
wFormat As Integer) As IntPtr
Public Declare Auto Function EmptyClipboard Lib "user32.dll" () As Boolean
Public Declare Auto Function OpenClipboard Lib "user32.dll" (ByVal hWnd As
IntPtr) As Boolean
Public Declare Auto Function IsClipboardFormatAvailable Lib "user32.dll"
(ByVal wFormat As Integer) As Boolean
Public Declare Auto Function CloseClipboard Lib "user32.dll" () As Boolean
Public Declare Auto Function GetObject Lib "gdi32.dll" (ByVal hObject As
IntPtr, ByVal nCount As Integer, ByRef nEntries As Integer) As Integer
Public Declare Auto Function GetPaletteEntries Lib "gdi32.dll" ( _
ByVal hPal As IntPtr, _
ByVal iStartIndex As Integer, _
ByVal nEntries As Integer, _
ByVal lppe As IntPtr) As Integer

' Structure Declarations
<StructLayout(LayoutKind.Sequential)> Public Structure LOGPALETTE
Public palVersion As UShort
Public palNumEntries As UShort
Public palPalEntry() As PALETTEENTRY
End Structure

<StructLayout(LayoutKind.Sequential)> Public Structure PALETTEENTRY
Public peRed As Byte
Public peGreen As Byte
Public peBlue As Byte
Public peFlags As Byte
End Structure

' Enum
Public Enum CLIPFORMAT
CF_TEXT = 1
CF_BITMAP = 2
CF_METAFILEPICT = 3
CF_SYLK = 4
CF_DIF = 5
CF_TIFF = 6
CF_OEMTEXT = 7
CF_DIB = 8
CF_PALETTE = 9
CF_PENDATA = 10
CF_RIFF = 11
CF_WAVE = 12
CF_UNICODETEXT = 13
CF_ENHMETAFILE = 14
CF_HDROP = 15
CF_LOCALE = 16
CF_MAX = 17
CF_OWNERDISPLAY = &H80
CF_DSPTEXT = &H81
CF_DSPBITMAP = &H82
CF_DSPMETAFILEPICT = &H83
CF_DSPENHMETAFILE = &H8E
End Enum

1) Add two additional buttons to the form.
One labeled Copy the other labeled Paste
2) Enter the code below to handle the button events


Friend WithEvents Button4 As System.Windows.Forms.Button 'timer1_Tick
' Copy palette to clipboard
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button4.Click
Dim lp As New LOGPALETTE
Dim pe(255) As PALETTEENTRY

lp.palNumEntries = cp.Entries.Length
lp.palVersion = &H300

For i As Integer = 0 To lp.palNumEntries - 1
pe(i).peBlue = cp.Entries(i).B
pe(i).peGreen = cp.Entries(i).G
pe(i).peRed = cp.Entries(i).R
pe(i).peFlags = 0
Next i

lp.palPalEntry = pe

Dim cb As Integer = Marshal.SizeOf(GetType(UShort)) * 2 +
Marshal.SizeOf(GetType(PALETTEENTRY)) * lp.palNumEntries
Dim pLp As IntPtr = Marshal.AllocHGlobal(cb)
Dim ptr As IntPtr = pLp

If 0 <> pLp Then
Dim s(1) As Short
s(0) = lp.palVersion
s(1) = lp.palNumEntries
Marshal.Copy(s, 0, ptr, s.Length)

ptr = CType((ptr.ToInt32() + Marshal.SizeOf(GetType(Short))
* 2), IntPtr)

For i As Integer = 0 To lp.palNumEntries - 1
Marshal.StructureToPtr(lp.palPalEntry(i), ptr, True)
ptr = CType(ptr.ToInt32() +
Marshal.SizeOf(GetType(PALETTEENTRY)), IntPtr)
Next
End If

Dim hpal As New IntPtr
hpal = CreatePalette(pLp)

If 0 <> hpal Then
If True = OpenClipboard(Me.Handle) Then
EmptyClipboard()
SetClipboardData(CLIPFORMAT.CF_PALETTE, hpal)
CloseClipboard()
End If
End If
Marshal.FreeHGlobal(pLp)
End Sub

Friend WithEvents Button5 As System.Windows.Forms.Button

' Paste palette from clipboard
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button5.Click
Dim hPal As IntPtr
Dim nEntries As Integer
If True = OpenClipboard(Me.Handle) Then
If True = IsClipboardFormatAvailable(CLIPFORMAT.CF_PALETTE)
Then
hPal = GetClipboardData(CLIPFORMAT.CF_PALETTE)
End If
CloseClipboard()
End If

Dim lp As IntPtr
Dim nRetrieved As Integer
Dim pe(256) As PALETTEENTRY

If 0 <> hPal Then
GetObject(hPal, Marshal.SizeOf(GetType(UShort)), nEntries)
If nEntries > 0 Then
lp =
Marshal.AllocHGlobal(Marshal.SizeOf(GetType(PALETTEENTRY)) * nEntries)
End If

nRetrieved = GetPaletteEntries(hPal, 0, nEntries, lp)

If nRetrieved > 0 And 0 <> lp Then
Dim ptr As IntPtr = lp
For i As Integer = 0 To nEntries - 1
pe(i) = Marshal.PtrToStructure(ptr,
GetType(PALETTEENTRY))
ptr = CType(ptr.ToInt32() +
Marshal.SizeOf(GetType(PALETTEENTRY)), IntPtr)
' assign palette entry to managed palette
Dim clr As Color = Color.FromArgb(255, pe(i).peRed,
pe(i).peGreen, pe(i).peBlue)
cp.Entries(i) = clr
Next
End If
End If

Marshal.FreeHGlobal(lp)
Me.Invalidate()
End Sub
 
A

active

Great. I had a little trouble at first but I now believe it is because
Photoshop might be doing something strange.

If I use Photoshop's "Copy" it appears to place a CF_PALETTE on the
clipboard but the handle is NULL!

If I use your code to place the CF_PALETTE on the clipboard it works great.

Need to find other ways to place a CF_PALETTE on the clipboard for more
testing.

Thanks a lot for the help
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top