Avatar of AztecGod
AztecGod

asked on 

How to Save a Dragged DIB as a File

I am creating an "application" in MS Access 2003. I want a user to be able to drag a device independent bitmap (DIB) onto a treeview and have it automatically save that DIB as a file.

I know I can use Data.GetData(VbCFDIB) in the OLEDragDrop event to get the data, but I can't figure out how to save that to a file that will be recognized as a true DIB - regardless of size or color depth.

Can anybody help me out?
Microsoft AccessSystem Programming

Avatar of undefined
Last Comment
Rick_Rickards
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

If you're talking about the Tree Control from the Active X Control known as Common Controls (mscomctl.ocx) then the answer is not easily if at all.  The Tree Control in availed by this ActiveX control uses one of it's own controls known as the ImageControl which stores all the images available to the tree control in advance (loaded while in design view).  In theory one could create their own Class Object as a wrapper for the .dll to avail your own version of the tree control that MSComctl.OCX avails but that would be a significant undertaking requiring a lot more knowledge than just how to martial a .dib from one place to another.

In short, some other Tree Control than the one provided by Common Controls is probably the best way to deliver on this need, although I'd open that question up to the floor because as of yet I have not found such a control.

It is probably also worth noting that the Tree Control used by Common controls will scale all images to a particular set of dimensions which is determined by the first image loaded into the Image Control.  I know, probably more bad news but that is what it does.
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

In looking at your question a little more closely it seemed to me that one thing you might be after, (alternatively), is to have the tree control recognize the .dib dropped onto one of its branches and then save that someplace on the drive?  If this is the case a solution is not so out of reach but not simple either.  Before I go on the first thing I think everyone will need to know is what Tree Control is being used.
Avatar of AztecGod
AztecGod

ASKER

I don't think the tree control is really significant to the issue. The key is how to save the OLEDragDrop data to a useful file. I should get pretty much the same data from any OLEDragDrop operation, shouldn't I?
I'm not trying to load the resulting file as an image in the tree control. I am trying to save it to a file, then list the file in the tree control.

Does that clarify my question, maybe?
Avatar of AztecGod
AztecGod

ASKER

Oops! Sorry. I am using the MSComctlLib.TreeCtrl.2 version (the one from Windows Common Controls 6.0 (SP6) - that is, the one from MSCOMCTL.OCX
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

So are you trying to get the .dib to be an icon that the tree uses or are you trying to get the tree to store the .dib elsewhere?  If the latter case is so, I presume the tree contains the path where you want the file to be saved?
Avatar of AztecGod
AztecGod

ASKER

Thanks for being so fast to respond, Rick. :)

I am trying to save the file elsewhere. I add a node to the tree to indicate where the DIB was dropped. Later, it will get imported into a BLOB field in the backend database, but that's not an issue for me. It's just saving the thing to a file that's got me stumped. I seems like it aught to be simple, but so am I, so I can't figure it out.
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

Check out the following KB

http://support.microsoft.com/kb/194975

Microsoft provides several functions there to read and write BLOB's to and from files, from files to fields in a recordset and back again.  In your case the function named BlobToFile() may be the one you're interested in.  If that doesn't get you where you want to go just let me know.

Rick
Avatar of AztecGod
AztecGod

ASKER

Thanks, Rick. What I really need to know is how to save the DIB to a file. I already have the rest worked out. Any idea how to save the data from .GetData(VbCFDIB) into a file to create a valid .dib file?
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

Sure but I need to know a little more about what .GetData(vbCFDIB) is returning.  Is it a byte array or something else?  A DIB is at the end of the day just that, a byte array formated with some header information followed by the image data itself, little bit weird in that it's layed out upside down, left to right in GBR instead of RGB but in any case I'd need to start by knowing what .GetData(vbCFDIB) is returning, (what data type) Array of bytes or ???.
Avatar of AztecGod
AztecGod

ASKER

Ah. Now we get to the crux of my problem. That question is the exact beginning of what I don't know. I know to use GetData to get the data, but I don't know what comes next, or even how to tell the answer to your question. Do you have any actual examples of using GetData with a dropped DIB?

Thanks!
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

No worry, we can still figure it out.  .GetData is no doubt a method you're seeing somewhere and it no doubt belongs to some object, (I'm just not sure what or where).

If you open any module and press F2 you can open the object browser you can type in GetData.  I assume you're using that now so presumably and with any luck it's part of an object library you've referenced but that gets into the kind of information I'd need to know absent the answer to my first question (what does it return).  

Below is a screen shot of what the Object Browser with the areas I'd need information on.  I randomly picked Instr() as a function to illustrate how this is done.  It will tell us what Library you're method belongs to along with other information that will help answer the first question.
ObjectBrowser.PNG
Avatar of AztecGod
AztecGod

ASKER

Unfortunately, I am using MS Access, and so the treecontrol doesn't really pass the proper object in the procedure. Here's what I have to work with...

Private Sub tvwDocumentContent_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)

If you look in MSDN for VB6, it shows the OLEDragDrop method as properly returning Data as a DataObject, instead of just an Object. I know the "Object" being passed really is the DataObject (or something similar) because it responds to DataObject methods and properties, such as .Files, .GetFormat, and even .GetData. I am already using the .GetData to get text and rich text too. So, the question is how to take the DIB data returned from the .GetData method of the DataObject, and put it in a file.

I know I haven't exactly answered what you've asked, because I can't quite match up what's available to me with how you've asked the question. But, have I given you enough to go on?

Thanks, Rick.

:)
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

I believe you have given me enough information to put it together.  A proper explanation will be a little long so I thought it best to start of with a condensed illustration of how quickly this can be done.  The rest is to explain what we are doing why as the brief code below probably deserves a better explanation.

Private Sub XTree_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
    Dim var As Variant
    If Data.GetFormat(8) Then                                     'Does Data contain a DIB (if we try to extract a DIB and it's not a DIB the next GetData(8) would crash
        var = Data.GetData(8)                                            'Extract the DIB (I believe 8 is the constant discriminating a DIB from other data types)
        Open "C:\MyDIB" For Binary Access Write As #1     'Open a file up a Binary file for Writing
        Put #1, 1, var                                                          'Write the DIB (byte array) to a file
        Close #1                                                                  'Close the File
    End If
End Sub

The DataObject is part of the Common Controls library and is one of the parameters passed on the OLEDragDrop event for the Tree Control (as you mentioned).  In fact this information is visible via the Object Browser (Screen shots below) although I'll admit that the information availed via the Object Browser in regards to the GetData method is not nearly enough.  I'm a little unclear as to how you're exactly you're dropping a DIB onto the Tree Control but assuming you have a way and it is in fact a DIB (From the Data Control's perspective) then I believe this will do the trick.  

When something is dropped in the TreeView Control the DataObject contains information about what just got dropped.  This information can come in different formats and in VB there are constants like vbCFLink, vbCFText, vbCFBitmap, vbCFMetafile, vbCFEMetafile, vbCFDIB, vbCFPalette, vbCFFiles and  vbCFRTF that GetData uses the discriminate between the different types of data.  Presuming that MSCOMCTL.OCX is using the same constants (while I haven't done DIBs before other types like vbCFText seem to be the same) then we're set if only we know the actual values of those Constants since VBA and the CommonControls library doesn't avail them.

The Code snippet below provides the actual number for the pertinent constants involved.  Also provided is an Enumeration that I called ClipboarFormat so you can take advantage of intellisense instead of having to remember the constants.  If you include both sets of code then either approach will work (I illustrated all three, literal value, constant & enumeration) below.

Before you use the .GetData() method to get your DIB you need to make sure that the DataObject does in fact contain a DIB.  Bear in mind that the OLEDragDrop event will fire for things other than just DIB data.  Hence the first step is to use the DataObject's .GetFormat() method to confirm that your Data is in fact DIB data and not something else.  GetFormat() returns a boolean value indicating whether the data type you specified is the type you're looking for.  Without doing this first your code will be vulnerable to crashing with the following Error.

Run-time error '461' "Specified format doesn't match the format of data"

This happens if you attempt to extract one type of data when in fact something else is really there.

The procedure named WriteFile() is just a very simple way to push your DIB (a byte array) out to a file.  You could actually use it for a lot more than just DIBs (Byte Arrays).

Well that's about it but I suspect many will want to know how to get the DIB data out of that file and use it someplace else.  With that thought in mind I added the proecedure SampleDisplayDib().  All it's doing is opening a file named "C:\MyDib" and pushing that data into an Image Control in Forms!Form1!Image1 but placing the data in the .PictureData property of the Image Control.

Well that's about it.  I am curious, however, as to where you're dragging and subsequently dropping your DIB from.  Even so, the answer didn't require knowing that.  Assuming that the DataControl does in fact have a DIB in it then this should get you there.
Global Const vbCFLink As Integer = &HBF00    'DDE conversation information
Global Const vbCFText As Integer = 1         'Text (.txt files)
Global Const vbCFBitmap As Integer = 2       'Bitmap (.bmp files)
Global Const vbCFMetafile As Integer = 3     'Metafile (.wmf files)
Global Const VbCFEMetafile As Integer = 14   'Enhanced metafile (.emf files)
Global Const vbCFDIB As Integer = 8          'Device-independent bitmap (DIB)
Global Const vbCFPalette As Integer = 9      'Color palette
Global Const vbCFFiles As Integer = 15       'List of files
Global Const vbCFRTF As Integer = -16639     'Rich text format (.rtf files)
 
Enum ClipboardFormat
    Link = &HBF00       'DDE conversation information
    Text = 1            'Text (.txt files)
    BitMap = 2          'Bitmap (.bmp files)
    MetaFile = 3        'Metafile (.wmf files)
    EMetaFile = 14      'Enhanced metafile (.emf files)
    DIB = 8             'Device-independent bitmap (DIB)
    Palette = 9         'Color palette
    Files = 15          'List of files
    RTF = -16639        'Rich text format (.rtf files)
End Enum
 
 
Private Sub XTree_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
    Dim var As Variant
    If Data.GetFormat(8) Then
        var = Data.GetData(8)                   'Using the literal value
        WriteFile "C:\MyDIB", var
    End If
End Sub
   
Private Sub XTree_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
    'Using the constant illustrated above
    If Data.GetFormat(vbCFDIB) Then
        var = Data.GetData(vbCFDIB)
        WriteFile "C:\MyDIB", var
    End If
End Sub
 
Private Sub XTree_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
    'Using the Enumeration illustrated above
    Dim var As Variant  
    If Data.GetFormat(ClipboardFormat.DIB) Then
        var = Data.GetData(ClipboardFormat.DIB)
        WriteFile "C:\MyDIB", var
    End If
End Sub
 
Sub WriteFile(strFile as string, var as Variant)
    Dim var As Variant        
    Open strFile For Binary Access Write As #1
    Put #1, 1, var
    Close #1    
Exit Sub
 
Sub SampleDisplayDib()
    Dim var as Variant
    Open "C:\MyDib" For Binary Access Read As #1
    Get #1, 1, var2
    Close #1
    Forms!Form1!Image1.PictureData = var
End Sub 

Open in new window

TvwOLEDragDrop.PNG
DataObject.GetData.PNG
Avatar of AztecGod
AztecGod

ASKER


Clearly, we're much closer to the solution. But, it doesn't quite work yet. As to how I am getting a dib, I am dragging it from Adobe Photoshop. In my test case, I am dragging just a small picture (see attached file).

Here are the relevant portions of my code...

Enum DragDropDataTypes
     VbCFText = 1 ' Text (.txt files)
     VbCFBitmap = 2 ' Bitmap (.bmp files)
     VbCFMetafile = 3 ' metafile (.wmf files)
     VbCFEMetafile = 14 ' Enhanced metafile (.emf files)
     VbCFDIB = 8 ' Device-independent bitmap (DIB)
     VbCFPalette = 9 ' Color palette
     VbCFFiles = 15 ' List of files
     VbCFRTF = -16639 ' Rich text format (.rtf files)
End Enum

Function GetDataType(ByRef DataObject As Object) As DragDropDataTypes
Dim dt As DragDropDataTypes
Dim cnt As Byte

    On Error Resume Next
    For cnt = 1 To 7
        dt = Choose(cnt, VbCFRTF, VbCFText, VbCFFiles, VbCFDIB, VbCFBitmap, VbCFEMetafile, VbCFMetafile)
        If DataObject.GetFormat(dt) Then
            GetDataType = dt
            Exit For
        End If
        'Prevents us from thinking we got something when we didn't
        dt = 0
    Next cnt
   
    GetDataType = dt
End Function


Public Function SaveBinToFile(ByRef Bin As Variant, Optional ByVal Extension As String, Optional ByRef Size As Long) As String
'Dim Strm As New ADODB.Stream
Dim Destination As String
Dim FileNumber
       
    On Error GoTo errSaveBinToFile
    Destination = GetTmpFileName(Extension)
   
    FileNumber = FreeFile
    Open Destination For Binary Access Write As #FileNumber
    Put #FileNumber, 1, Bin
    Close #FileNumber
   
    SaveBinToFile = Destination
       
errSaveBinToFile:
    Err.Clear
    Exit Function

End Function


Private Sub tvwDocumentContent_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
Dim dt As DragDropDataTypes

            dt = GetDataType(Data)
            Select Case dt
                Case VbCFRTF
                    FileName = TEXT_SaveStringToFile(Data.GetData(dt), "doc")
                Case VbCFDIB
                    DropData = Data.GetData(dt)
                    FileName = SaveBinToFile(DropData, "dib")
            End Select
End Sub

I can see from what you put that I was pretty much on track, but my first problem was that I was using Write instead of Put in the routine to write the file. That's fixed, and now I do get a file. BUT, the file is always only 6 bytes long and has no data.

Any ideas? Is the value being returned maybe a pointer? If yes, how would I copy it?


One-Acorn.png
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

I'd probably start by putting....

Option Explicit

at the top of the module to make sure all variables are explicitly declared (this can introduce hard to find buts if you don't)

Second, let's test a couple things.  Declare DropData as a Variant and then find out how many bytes of data you get.  Three lines added to your code above (code snippet below) and they each have a comment to make it clear where and what they are.

Also, the V
Private Sub tvwDocumentContent_OLEDragDrop(Data As Object, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
    Dim dt As DragDropDataTypes
    Dim DropData As Variant     'Declare the variable '
    Dim FileName as String      'Declare the variable '
    dt = GetDataType(Data)
    Select Case dt
    Case vbCFRTF
        FileName = TEXT_SaveStringToFile(Data.GetData(dt), "doc")
    Case vbCFDIB
        DropData = Data.GetData(dt)
        MsgBox UBound(DropData)     'For debug purposes (let's see how much data you got - will give you a byte count - 1 '
        FileName = SaveBinToFile(DropData, "dib")    '
    End Select
End Sub

Open in new window

Avatar of AztecGod
AztecGod

ASKER

I already had Option Explicit and DropData as Variant. I was just trying to keep the volume down. UBound doesn't play. It returns a data type mismatch error.
I get Len(DropData) = 9, though. (Its value is 369429664 in this case, by the way).
This is why I was thinking it might be a pointer. Thanks for continuing to plug at this! :)
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

Sounds like you're getting an .HWnd Value.  Your number, in Hex, works out to be (16 05 0C A0).

If the number stays the same between each attempt but then changes if the Window or perhaps the Adobe Photoshop is closed and reopened that would suggest more strongly that it is in fact a Windows Handel.  Unfortunately, knowing that doesn't get us much closer to the DIB since that would not appear to be the kind of information the DataObject is being passed.

Shame to have worked out all the details on how to write out the DIB only to discover that the object never actually had one to save.  :(
Avatar of AztecGod
AztecGod

ASKER

I really don't think it's a window handle. I tried it five more times from the same application window to the same instance of my treeview. Here are the values I got (most recent first, if that matters).

 2013597156
-939191216
 402987063
 50662224
-1912271615

So, do you know of anyone who has successfully dragged and dropped a dib? Where can we go with this?

Thanks,
Doug
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

Been giving this some additional thought and the more I think about it the more, (is it and Hwnd Address, nagh... HDC aka "Handel for Device Context" getting closer I think.

I do think you're on the right track when you say pointer but more specifically what I think you have is a memory address where the DIB is actually located.  This makes more sense when you think about it as a program like Adobe Photoshop that is trying to hand of DIB in this way could quickly end up with a lot of overhead if it actually tried to avail the entire DIB.  By availing the memory address instead the overhead is obviously cut down a lot, although it begs the question as to how one is to get to the DIB itself.  It would also explain why GetData() thinks its looking at a DIB when in fact it provides a LongInteger instead.  You might try running DropData passed VarType()...

MsgBox VarType(DropData)

... to confirm exactly what data type you are dealing with but I think it's a reasonable assumption that it will say it's a Long Integer which is in fact the Memory Address where the DIB resides.  I mention this in that it at least avails a less ambiguous and far less general question as the search for an answer moves forward.

If you would confirm the data type and also find out what will cause the number it provides to change.  For example, if you're pulling the image from some window can you change the image without changing the window.  If so it would offer some indication as to what the number represents.  Knowing whether we're looking at Hwnd, HDC or a memory address is another step closer to the answer you seek.  Again, upon reflection I'm betting you have the memory address of the DIB itself, (that at least explains the number you're getting and why GetData() thinks its a DIB.
Avatar of AztecGod
AztecGod

ASKER

Yep! It's a Long data type. As to what changes it - every single time I do it, it changes, even dragging the same thing, from the same instance to the same instance. I think this rules out both the hWnd and the hDC, right? So, on the assumption it's a memory address, how do we procede?
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

At this moment I'm not sure but it's an interesting question so I'll give it some more thought.  Seems as though we're about one epiphany from the answer.
Avatar of AztecGod
AztecGod

ASKER

Thank you for your patience, which I will repay by awaiting a response silently. :)
ASKER CERTIFIED SOLUTION
Avatar of Rick_Rickards
Rick_Rickards
Flag of United States of America image

Blurred text
THIS SOLUTION IS ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
Microsoft Access
Microsoft Access

Microsoft Access is a rapid application development (RAD) relational database tool. Access can be used for both desktop and web-based applications, and uses VBA (Visual Basic for Applications) as its coding language.

226K
Questions
--
Followers
--
Top Experts
Get a personalized solution from industry experts
Ask the experts
Read over 600 more reviews

TRUSTED BY

IBM logoIntel logoMicrosoft logoUbisoft logoSAP logo
Qualcomm logoCitrix Systems logoWorkday logoErnst & Young logo
High performer badgeUsers love us badge
LinkedIn logoFacebook logoX logoInstagram logoTikTok logoYouTube logo