Link to home
Start Free TrialLog in
Avatar of JohnnyPrez
JohnnyPrez

asked on

Extract RichText embedded images to file

Hi, a question like this was posted before, but not answered entirely:

I have a RichTextBox control.  I allow the user to enter images (such as a error message screenshot) into rich text via the normal RichText capabilities (RichTextBox.OLEObjects.Add or simply pasting the screenshot). This long hefty string is stored into my database. I am using Crystal Reports 8.5, which supports rich-text, but not rich-text embedded images. But they DO support images from a file.

Therefore, I need to extract my rich-text embedded image to a file.

NOTE: I have already developed a huge, nasty workaround that depends on the clipboard. So, alternately, if someone were to inform a method by which I could restore all possibilities of clipboard contents verbatim, then all side-effects of my method would be eliminated, and points would be rewarded to the user.

I'd appreciate any help you can offer.

--Johnny
Avatar of twalgrave
twalgrave

This may help:
http://www.hallogram.com/reportease/tedev/

Barring that have you considered alternative approaches such as :
1) When the user pastes a picture into the RTF, also paste it into a picturebox (of Course you could have acontrol array of pictureboxes to support multiple files)
2) Save the files directly to disk from the clipboard when Paste is performed when you are pasting to the RichTextBox
3) Allow your users to add the pictures to a picturebox instead of a RichTextBox but still allow them to put text into a multi-line textbox or RTB?


Avatar of JohnnyPrez

ASKER

Thanks for the suggestion.

I could intercept when the user pastes into the richtextbox, but the image ends up in the database (on another computer) anyway. If I saved to my hard drive, then the file would be inaccessible to users on other computers who want it. I'm not about to allow the user access to the server's harddrive.

--------------------
Here are additional methods I have considered and attempted:

---
1. Changing the database

I could create a new table for images, but then I'd be forced to flimsily enforce the existance of image records with and the richtext-embedded images. Actually, I wouldn't be able to compare the pictures at all since I can't get the embedded image's data in an intuitive manner.

There is support for more than one image in the RichTextBox, so adding a "screenshot" image column to the table in which the data is stored is out of the question.

Also, the current users of my program would like the current method to remain the same.

---
2. Parsing image information out of the richtext string.

I successfully wrote a module that creates a bitmap when given the correct information. In addition, I eventually was able to parsed through the large richtext string to get to the data, and create an image file with it (for any type of bitmap).  Doing this required knowing exactly how many characters to chop off from the beginning of the OLE object's data tag in the string.

GIF's and JPEGs, however, turned out to have a different format in the richtext string, so I was incapable of creating a single process that will extract all possible image types to file.

If I were to continue with this method, I would have to hardcode methods for creating an image for each image type.

---
3. OLE object + handles

I found numerous objects and libraries that would allow me to use the handle of an OLE object or just an OLE object.  But RichTextBox's OLEObjects collection stores it's own kind of OLEObject, and I can't pass it to the functions that want it.

Basically, this is a dead end because RichText's OLE Objects differ from actual ones. I can not get a handle to the object. RichText allows me to access the object's "verbs", which are only "open" and "edit".  There is no way to get to the object's data using verbs, unless I tell it to "Open", force select all, and use the clipboard "copy" routinue. Using the clipboard brings up the issue of clipboard recovery I mentioned before.

Also, I can not emulate OLE drag drop to another control (like a picturebox), because it requires a "DataObject", and the RichTextBox's OLEObject has no properties that I can use to fill the DataObject's properties.

---
4. Screen capture
Screen capture of the RichTextBox and/or painting the RichTextBox's contents onto another surface such as a picturebox will not work because the image could be larger than the richtextbox control.
--------------------

Also, I starting on making an automated PrintToFile method, but I stopped when I noticed that I would be printing an image onto a whole page, which would be the output to the file.

I'll gladly take any more suggestions or ideas that anybody has.

Johnny
If you used the array of picturebox method, you could put the pictures into the DB as BLOBS and retrieve them back out.
Right, but that method isn't an issue of how the images would be stored into the database at all... it's a matter of making sure that the image in the richtextbox control remains the same as the ones in the picturebox array.

A user is able to edit a picture contents from inside the richtextbox (as well as resize it). I'm not sure if I can disable this functionality, but even deleting a picture causes a problem.

Say I have 3 pictures in both the richtextbox and the picturebox array. If the user deletes a picture, how will I know which is which? The only value linking the two together is the picture's index. Keep in mind that the user can change the order of the images, and I need to reflect the order in the picturebox array and when I print.

Using the picturebox array doesn't really solve the problem of getting the object out of the richtextbox, it just works around it by mimicing the richtextbox's actions (like pasting the image). If it comes to that, I'll probably just give up, remove the richtextbox, and change the program. If this happens, I'll give you the points anyway.

I'm still open to suggestions.

--Johnny
Yes you can disable the OLE actions on the picture.  I answered a question on it.

I don't think you're fully understanding what I'm proposing.  If you combine the methods I'm talking about I think you can make a fairly simple solution.

What you display to the user is NOT what you save to the DB.  What you save to the DB is in hidden objects on the form.

You can use a hidden RichTextBox to save the portions of Text/RichText only that will be used to store to the DB.  The pictures are also stored in invisible pictureboxes.   Each picture's tag property holds the position (selposition) where the picture was inserted into the RichTextBox.  The pictureBoxes is where you get the images to BLOB into the DB.

So the DB looks like this:
RTF Table
pk_UniqueID
RTFText     TextOnly

RTFPics Table
pk_UniquePicID
fk_UniqueID
PICBLOB
InsertLocationInRTF
PicOrder

So you get something like this:

RTFTable
12345
<alsdkjqsdlkasjdlkajsd.....> IN RTF FORM.

rtfPics Table
1
12345
42302942340098ad09dd09d80909d80a089a0909c09a980b98b08
17 - the 17th character in the text
0

1
12345
9d80909d80a089a0909c09a980b98b08
27 - the 17th character in the text
1
...
Not trying to force this on you, I can just see this as being a possibly fairly simple procedure.


The only other thing I can think of is to use MSWord and save that RTF document as a BLOB on the DB and then extracting it out to file, readining it into word, then into your RichTextBox.

The catch to the method is not a problem of how the images are stored in the database. I could come across problems without ever having to hit the database:

I'm still assuming that by your suggestion, when a user inserts a picture, it's going to end up in the richtextbox (as well as the picturebox array).  The pictureboxes also would store the position that the picture was added by using their tag property.

But, if I add three pictures, then I have three pictures in the pictureboxes and three in the richtextbox. What happens if the user deletes the second image or even moves it to another location in the string? The problem that this workaround is trying to fix would require a large workaround for situations like these...

OLE objects show up as spaces in the richtextbox.text property, and determining where in the string they are after the user has edited the text would require a massive amount of user action tracking. As an example, if they select a large blob of text, including an image somewhere in there, and press delete. I would have to compare the previous and the current richtextbox.text string along with the previous and current richtextbox.OLEObjects collection.

Deleting is just an example. Users are able to reposition, resize, and align pictures. Probably more.

But I might be able to use a MSWord control to extract the OLEObjects (by copying the richtextbox contents into the MSWord control), or even replace the RichTextBox control. I'll see if this works now. Thanks for the suggestion.

--Johnny
JohnnyPrez,
I see your point on the User actions.  It seems fairly straightforward on the surface but you are correct that a great deal of code would need to be put into place and you could quite possibly miss one of the things a user could do.
I'd just like to say I hate Crystal Reports.

I've decided that I will make a C++ DLL that will save and restore clipboard contents. Can I give myself points for that? :)

--Johnny
I like CR, but I've heard a lot of people that don't.  Sometimes it's not very intuitive, but I've gotten it to dance like a monkey in a circus where I didn't think it could.  The latest version with the drill-down reports and crosstab capabilities is pretty nice.  The only thing I haven't been able to do with it to date is to link the crosstab with the drill-down data.  I called Crystal and they knew it couldn't do that...perhaps next release.

Sorry, can't give yourself points :P, but you can get the points on this question refunded back to you.  Simply place a zero point question in the community support/cleanup area asking for a question delete.  Don't forget to copy the link URL to this question so you can put it in the comments section so they will know which question to go to.
Nah, I think people might want to know how to do this...  I'll go ahead and give away my massive (yet fun) workaround. *sigh*

How about you just say something simple and quick like this:
=========================================================
Ok, so you wanna get RichText-embedded images out of a richtext string through code without the user seeing it? Easy! Just follow these steps and your Uber-hack will be implemented in no time!

______________________________________
Step #1: Put the string into a RichTextBox control.

Simple enough, put the large richtext string you have into the [RICHTEXTBOX].TextRTF property!

---------------------

   [RICHTEXTBOX].TextRTF = [RICHTEXTSTRING]

---------------------

______________________________________
Step #2: Allow the RichTextBox to refresh.

Think that the RichTextBox will refresh it's properties immediately? No, of course not, silly. You must let Form_Load complete successfully. And no, you can't use a "DoEvents" here, the richtextbox is very pushy. You need to add a timer to your form, so that Form_Load can complete successfully, before you're allowed to get the the information that you'll need in the following steps! Put all remaining code for this Uber-hack into your timer's event.

You can set your form to invisible by default, and make it visible when you're done.

______________________________________
Step #3: Select an OLEObject

Well, ya see, OLEObjects have a list of available actions called "Verbs". You can call these verbs for the OLEObjects and make them do stuff. Pictures have the ability to edit and open.

What you need to do is select each of the OLE objects, then cut them, then use the clipboard data to save it to file. How do you select them? Easy! You can tell the OLE object to "Edit!". Just find which number the verb is for "Edit" (my memory tells me that it's either 0 or 1) by using "FetchVerbs". Then you can say:

---------------------

   Call [RICHTEXTBOX].OLEObjects([OBJECTNUMBER]).DoVerb([VERBNUMBER])

---------------------

(NOTE: I've had problems with this method periodically crashing with no error message on larger pictures on invisible forms. If this is the case, you can choose to select each character of the RichTextBox's "Text" property, copy it, and check to see if the clipboard contents are a picture rather than text. This works because embedded objects are actually converted into just one space in the "Text" property. Also, if you use this method, you do not need to send CTRL+A, since that would select all the richtextbox contents.)

Ok, now the richtextbox has it selected! Now you have to select the whole thing! So, send keyboard events to simulate CTRL+A! This will take four keyboard events, CTRL (down), A (down), A(up), CTRL (up). Here's the declaration for the keyboardevent API.

---------------------

   Private Declare Sub keybd_event Lib "user32" _
     (ByVal bVk As Byte, ByVal bScan As Byte, _
     ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

---------------------

To use this API correctly, refer to microsoft's documentation:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/UserInput/KeyboardInput/KeyboardInputReference/KeyboardInputFunctions/keybd_event.asp

______________________________________
Step #4: Get the data to somewhere you can use it.

Well! Now everything's selected! What now? Well, of course, you've got to copy it! Use the keybd_event api again to do CTRL+C. Don't forget all four function calls.

NOTE: The clipboard won't refresh automatically. You need a a "DoEvents" immediately after sending the copying keystrokes.

---------------------

   DoEvents

---------------------


______________________________________
Step #5: Save the data, finally.

You can use the following line of code to save the image in the clipboard to file, as long as you're certain the contents are a bitmap.

SavePicture Clipboard.GetData(vbCFBitmap), "[FILENAME]"
______________________________________

It's that easy! But there's one side-effect left. Your clipboard gets erased! So, how do you keep that from happening in Visual Basic 6.0? Well, you can't directly. Your clipboard capabilities limit you, and you will not be able to copy the contents verbatim. You need to create an external method of saving and restoring the clipboard, I would suggest a C++ DLL, with one entry point being "SaveClipboard" and the other being "RestoreClipboard". The purpose of these two is self explanatory...

I have not done this yet, so I can give no tips on the specifics. But, for some code to save all clipboard contents correctly, see this link:

https://www.experts-exchange.com/questions/10207126/Clipboard-OLE.html


=========================================================


If you say this, you might just end up with some points! :)

--Johnny
this
ASKER CERTIFIED SOLUTION
Avatar of twalgrave
twalgrave

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I couldn't have said it better myself!! :)
LOL and thanks for sharing!