I am currently trying to read an Image from a MS Access database that has an OLE Object field and contains a valid bitmap (for test purposes, I created a image using MS Paint and saved it in 24bit bmp).
I am linking to this via DBGrid. In theory everything should work good and it should show the image, however I am getting a: "bitmap image not valid" error. I can understand if this is a JPEG and not .bmp, but that isn't the case. So my question is, what is wrong?
I dont necessarily have to use a DBImage, a normal TImage will also do just fine (might even be more preferable), but I'm not sure on how to assign a TImage to an OLE Object field in a MS Access Database. I Have tried, to no avail:
//Select photo from Image field
Image1.Picture := ADOTable1['Image'];
I've read most of the articles, such as about.com etc, regarding this matter, but still don't get any good results.
Any help would be greatly appreciated!
UPDATE: This worked for me:
Add to USES clause : JPEG, ADODB, DB
function JpegStartsInBlob
(PicField:TBlobField):integer;
var
bS : TADOBlobStream;
buffer : Word;
hx : string;
begin
Result := -1;
bS := TADOBlobStream.Create(PicField, bmRead);
try
while (Result = -1) and
(bS.Position + 1 < bS.Size) do
begin
bS.ReadBuffer(buffer, 1);
hx:=IntToHex(buffer, 2);
if hx = 'FF' then begin
bS.ReadBuffer(buffer, 1);
hx:=IntToHex(buffer, 2);
if hx = 'D8' then Result := bS.Position - 2
else if hx = 'FF' then
bS.Position := bS.Position-1;
end;
end;
finally
bS.Free
end;
end;
procedure TfrmOne.btnShowImageClick(Sender: TObject);
var
bS : TADOBlobStream;
Pic : TJPEGImage;
begin
bS := TADOBlobStream.Create(table1.FieldByName('Photo') as TBlobField, bmRead);
bS.Seek(JpegStartsInBlob(table1.FieldByName('Photo') as TBlobField),
soFromBeginning);
Pic := TJPEGImage.Create;
Pic.LoadFromStream(bS);
frmOne.Image1.Picture.Graphic := Pic;
Pic.Free;
bS.Free;
end;
what would return ADOTable1['Image'] ?
i don't like FieldVallues property u use, for you don't know the actual type and cannot control things using type checking.
I guess you'd better use Data.DB.TDataSet.FieldByName
The very type of TField object would hint you the kind of data it contains.
I don't know about Microsoft Jet (database engine of Excel and Access), but i think it stores raw BMP file data and some link to Paint application (or Gimp, or Photoshop, or whatever used to edit BMP) Kinf of TBlobField i think.
http://support.microsoft.com/kb/205635/en-us - this shows a snippet how to save file from OLE filed.
try to find a way to save field content into TFileStream.
Check that created file is really BMP file.
After that save blob into TMemoryStream and TBitmap.LoadFromStream
consider Data.DB.TBlobField.SaveToStream, Data.DB.TField.AsBytes
One more snippet from docs - explore those classes if u can use them
"Use TADOBlobStream to access or modify the value of a BLOB or memo field in an ADO dataset. BLOB fields are represented by TBlobField objects and descendants of TBlobField such as TGraphicField and TMemoField."
As resume:
1) get the type of data - ADOTable1.FieldByName.ClassName
Read about it, which methods properties would allwo u to get the data
2) try to save the data into file and analyze what is it
3) try to save that data into stream and re-use for loading picture
Related
I have problem with reading blob field from database which contains msword file and save it into file (.doc/.docx). What is moree this works great in Delphi 2010 but in Delphi Xe2 saved files are invalid.This is my code
dane.SQLtmp.Close;
dane.SQLtmp.SQL.Clear;
dane.SQLtmp.SQL.Add('select wydruk,typ,IdWydruku from wydruki where nazwa=:d0');
dane.SQLtmp.Params[0].AsString:=name;
dane.SQLtmp.Open;
if dane.SQLtmp.RecordCount> 0 then
begin
t:=TMemoryStream.Create;
t.Position:=0;
TblobField(dane.sqltmp.FieldByName('wydruk')).saveToStream(T);
T.SaveToFile('C:\FILE'+filetpe);
t.Free;
end;
Saving file into database:
dane.SQLtmp.Close;
dane.SQLtmp.SQL.Clear;
dane.SQLtmp.SQL.Add('insert into Wydruki (Nazwa,Operator,wydruk,opis,typ,rodzaj,podmiot,typsplaty,grupa,podgrupa)');
dane.Sqltmp.SQL.Add('VALUES (:d0,:d1,:d2,:d3,:d4,:d5,:d6,:d7,:d8,:d9)');
dane.SQLtmp.Params[0].AsString:=NazwaPliku; //File name
dane.SQLtmp.Params[1].AsInteger:=glowny.ID_operator;
t:=TMemoryStream.Create;
t.Position:=0;
t.LoadFromFile(OpenFile.FileName);
t.Position:=0;
dane.sqltmp.Params[2].LoadFromStream(t,ftBlob);
dane.SQLtmp.Params[3].AsString:=opis;
dane.SQLtmp.Params[4].AsString:=typ; // file type
// .
// .
// .
dane.SQLtmp.ExecSQL;
In Delphi 2010 it worked... :/
You need to use TBlobField.CreateBlobStream and copy to a TFileStream.
According to the documentation:
Call CreateBlobStream to obtain a stream for reading and writing the value of the field specified by the Field parameter. The Mode parameter indicates whether the stream will be used for reading the field's value (bmRead), writing the field's value (bmWrite), or modifying the field's value (bmReadWrite).
The Tip on the same documentation page says:
Tip: It is preferable to call CreateBlobStream rather than creating a blob stream directly in code. This ensures that the stream is appropriate to the dataset, and may also ensure that datasets that do not always store BLOB data in memory fetch the blob data before creating the stream.
Sample code based on yours above:
var
Blob: TStream;
Strm: TFileStream;
BlobFld: TBlobField;
begin
dane.SQLtmp.SQL.Text := 'select wydruk,typ,IdWydruku from wydruki where nazwa=:d0';
dane.SQLtmp.Params[0].AsString:=name;
dane.SQLtmp.Open;
BlobFld := dane.SQLtmp.FieldByName('wydruk') as TBlobField;
Blob := dane.SQLtmp.CreateBlobStream(BlobFld, bmRead);
try
Strm := TFileStream.Create('C:\FILE' + filetpe, fmCreate);
try
Strm.CopyFrom(Blob, Blob.Size);
finally
Strm.Free;
end;
finally
Blob.Free;
end;
end;
The problem was in Interbase components (IB) in Xe2. When I changed them to FireDac'c components my and Yours code works.
That's interesting, recently embarcadero's product have many bugs.
You can do this
query.SQL.Add('SELECT * FROM something');
query.Open;
query.FieldByName('file') as TBlobField).SaveToFile(SaveDialog1.FileName);
I am developing an application in Firemonkey (Delphi XE5) where I am using Fast report 4 for printing data. I am using TFrxUserDataSet to keep data and to print this, I am using MasterData band in fast report.
Now, I also need to print TBitamp with each row, so here the bitmap for each record will be different.
Does any body has any idea how can I do this?
Нou can load an external image file into a picture control in your report. I'm doing this with a script that is part of the report itself using the OnBeforePrint event as follows:
PROCEDURE Data2OnBeforePrint(Sender: TfrxComponent);
VAR
lFN : STRING;
lFP : STRING;
BEGIN
// Use the filename as found in the Media dataset fields
lFP := Trim(< Media."ImagePath">); // Images folder below Image Root Path
lFN := Trim(< Media."FileName1">); // Actual Image File Name
WITH Picture2 DO BEGIN
// NB: There is no checking in this example, it may be useful to do a
// couple of checks before trying to load the image, especially if
// the data is user entered
LoadFromFile(ImageRootPath + IncludeTrailingSlash(lFP) + lFN);
// Do whatever manipulations you want to with the loaded image...
AutoSize := False;
Width := 1620;
Height := 1080;
Top := 0;
Left := (1920 - Width) / 2;
HightQuality := True; // Note the typo in the property name... HighQuality?
KeepAspectRatio := True;
Transparent := True;
Stretched := NOT Picture3.AutoSize;
END;
END;
Note that I have added a few user functions like ImageRootPath IncludeTrailingSlash() to make the script easier. You could do similar to check for a valid file prior to attempting to load to avoid exceptions.
My devt environment is Delphi XE5 with FastReport FMX and it works just fine. I am in the midst of moving to XE6 and FR FMX 2, but am pretty sure this will work fine.
I'm using both old good MPGTools and own, simple method of setting ID3 Tag in my MP3 files. But both approaches are too old to support ID3Tag version 2. I'm looking for any solution that would allow my application, written in Delphi 7, to either remove ID3Tag from each file it process or to set it to exactly the same values as ID3Tag version 1 is set.
Currently I'm removing ID3Tagv2 manually, using quick keyboard combination in Winamp.
I don't use v2 or album art or all these "new" addition, so the quickiest way to get rid of ID3Tagv2 (if it exists in particular file) would be all I need.
Of course I've tried to search the Internet using Google, but either I've got bad day or I'm asking wrong question, because all the results I'm getting on above mentioned questions are fake result from search engine stealers like Software Informer etc.
As it happens, one of my projects sitting here that is awaiting completion (about 80%, I'm more a hobbyist when it comes to Delphi and had more pressing stuff come up, then I found a program I was able to download which fit my requirements precisely) is a full ID3 tag editor for MP3 files. While v1 was super-easy, v2 is much harder. You can refer to the standard document for v2.3 here.
But I will confine myself to the points addressed here.
You might want ID3v2 tags depending on the application. My portable MP3 player only accepts v2 tags, which is what pushed me to do the project in the first place.
ID3v2 tags are written at the beginning of files in a variable length manner with variable numbers of tags which may or may not be present. Fortunately, the full length of the data should be in the first record if it's an ID3v2 tagged file. Hence, read the file locate the length of the ID3v2 data, then rewrite the file without the ID3v2 data and the tags are removed. Having the data at the beginning makes this necessary and is indeed a frustration. Anything I do in the future to the code would involve trying to change data in place. Some very dirty code follows, which AFAIR worked, but you will need to clean up if you use (I'm sure some here will be content to point out exactly how I should). But test it well just to be sure. Also be sure to ask if I missed anything from the unit I copied this out of (it's a 19.3KB pas file) that you would need:
type
sarray = array[0..3] of byte;
psarray = ^sarray;
ID3v2Header = packed record
identifier: array[0..2] of char;
major_version: byte;
minor_version: byte;
flags: byte;
size: DWord;
end;
function size_decodeh(insize: DWord): DWord;
{ decodes the size headers only, which does not use bit 7 in each byte,
requires MSB conversion as well }
var
outdval: DWord;
outd, ind: psarray;
tnext2, pnext2: byte;
begin
outdval := 0;
outd := #outdval;
ind := #insize;
tnext2 := ind^[2] shr 1;
pnext2 := ind^[1] shr 2;
outd^[0] := ind^[3] or ((ind^[2] and $01) shl 7);
outd^[1] := tnext2 or ((ind^[1] and $03) shl 6);
outd^[2] := pnext2 or ((ind^[0] and $07) shl 5);
outd^[3] := ind^[0] shr 3;
Result := outdval;
end;
procedure ID3v2_LoadData(filename: string; var memrec: pointer;
var outsize: integer);
{ procedure loads ID3v2 data from "filename". Returns outsize = 0 if
there is no ID3v2 data }
var
infile: file;
v1h: ID3V2Header;
begin
assign(infile, filename);
reset(infile, 1);
// read main header to get id3v2 size
blockread(infile, v1h, sizeof(v1h));
// detect if there is id3v2 data
if v1h.identifier = 'ID3' then
begin
outsize := size_decodeh(v1h.size);
// read ID3v2 header data
getmem(memrec, outsize);
blockread(infile, memrec^, outsize);
Close(infile);
end
else
outsize := 0;
end;
function id3v2_erase(infilestr: string): boolean;
{ erase all ID3v2 data. Data are stored at the beginning of file, so file
must be rewritten }
const
tempfilename = 'TMp#!0X.MP3';
var
memrec: pointer;
outsize, dataread: integer;
IsID3v2: boolean;
databuffer: array[1..32768] of byte;
newfile, origfile: file;
begin
// reuse service routine to get information
Id3v2_loaddata(infilestr, memrec, outsize);
// is there ID3v2 data?
if outsize > 0 then
begin
// need to clean up after the service routine
freemem(memrec);
// get amount of data to erase
outsize := outsize + sizeof(Id3v2Header);
writeln('Data to delete is: ', outsize, ' bytes.');
// now rewrite the file
AssignFile(origfile, infilestr);
reset(origfile, 1);
AssignFile(newfile, tempfilename);
rewrite(newfile, 1);
Seek(origfile, outsize);
repeat
blockread(origfile, databuffer, sizeof(databuffer), dataread);
blockwrite(newfile, databuffer, dataread);
until dataread = 0;
CloseFile(origfile);
CloseFile(newfile);
// rename temp file and delete original
DeleteFile(infilestr);
RenameFile(tempfilename, infilestr);
IsID3v2 := true;
end
else
IsID3v2 := false;
Result := IsID3v2;
end;
Full editing capability that works in most all situations is obviously a tougher hill to climb than that, but all the details are there in that document I linked to. Hopefully this helps you out.
There are few libs that works fine with ID3V2. Back in 2006 I did a big research to find Delphi library that supports most of the Id3V2 specification for Delphi 7.
And I found these 2:
Audio Tools Library (was the best for that moment). I think that it even could read/write tags in Unicode. Here is the unit Id3V2.pas
JVCL has component to work with Id3V2 tags. But it didn't had Unicode support for non-unicode Delphi in 2006.
Btw, if you do not use JVCL yet, it's not worth to install more than 600 components just to get Id3V2 support.
So, take a look at Audio Tools Library.
could anyone help?
I've inherited some software written in Delphi 5 which allows member data and fields from a database (.ADT file) to be used merged in to word.
It works fine with all version of Word except 2010 where it won't load any documents and shows the error:
"That Method is not available on that object"
I have been told the solution is to replace the preset components OpWord and OpDataSet with Ole variants. I have done so with OpWord using:
wrdApp := CreateOleObject('Word.Application');
and the documents now load up but without any merge field data. Can anyone let me know how to extract this data from the database, as the OpDataSet seems to simply just point at the table?
Or can anyone suggest a better solution than the one I'm trying. I'm very new to Delphi so I'm in abit over my head
Edit: (Requested Info)
Sorry I have more details and code if required.
The components appear to belong to a library called OfficePartner along with TOpExcel,TOpOutlook and others.
The .doc is selected from a popup ListPane on Form30, opened and populated with merge field data from Table 4. Table 1 is the members database:
{Use Table4 as we can Set a range on it}
Table4.SetRange([Table1.FieldByName('Member Id').AsString],[Table1.FieldByName('Member Id').AsString]);
{Open Word}
OpWord1.Connected := True;
{Open the Test Document}
OpWord1.OpenDocument(DocumentDirectory + '\' + Form30.ListBox1.Items[Form30.ListBox1.ItemIndex]);
{Populate the Test Document}
OpWord1.ActiveDocument.MailMerge.OfficeModel := OpDataSetModel1;
OpWord1.ActiveDocument.PopulateMailMerge;
OpWord1.ActiveDocument.ExecuteMailMerge;
I hope this helps...
Here is a little procedure for word mail merge that I used way back for D6, it's a just snippet and you have to include in some class, I don't have Delphi anymore so can't compile to make sure that it works, anyway here it is, hope it helps:
procedure MailMergeWord;
var
WordApp: TWordApplication;
WordDoc: TWordDocument;
doc : WordDocument;
FileName: OleVariant;
xx: integer;
begin
WordApp := TWordApplication.Create(nil);
WordApp.ConnectKind := ckNewInstance;
WordDoc := TWordDocument.Create(WordApp);
FileName := 'TemplateDoc.doc';
doc := WordApp.Documents.Open(FileName,EmptyParam,EmptyParam,EmptyParam,EmptyParam
,EmptyParam,EmptyParam,EmptyParam,EmptyParam
,EmptyParam);
WordDoc.ConnectTo(Doc);
for xx := 1 to WordDoc.Fields.Count do
WordDoc.Fields.Item(xx).Result.Text := OnWordVariable(WordDoc.Fields.Item(xx).Code.Text);
WordDoc.PrintOut;
WordDoc.Free;
WordApp.Free;
end;
function OnWordVariable(varName: string): string;
begin
Result := 'Value based on variable name';
end;
While doing som Word automation from Delphi XE, I have two documents open simultaneously. I want to copy the contents of a given range of one document to another range in the other document. How can I do this?
Consider the following code:
procedure TForm1.ManipulateDocuments;
var
vDoc1,vDoc2 : TWordDocument;
vFilename : olevariant;
vRange1,vRange2 : Range;
begin
vDoc1 := TWordDocument.Create(nil);
vDoc2 := TWordDocument.Create(nil);
try
vFilename := 'c:\temp\test1.doc';
vDoc1.ConnectTo(FWordApp.Documents.Open(vFilename,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam));
vFilename := 'c:\temp\test2.doc';
vDoc2.ConnectTo(FWordApp.Documents.Open(vFilename,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam));
vRange1 := GetSourceRange(vDoc1);
vRange2 := GetDestinationRange(vDoc2);
vRange2.CONTENTS := vRange1.CONTENTS; //What should I substitute for CONTENTS?
finally
vDoc1.Free;
vDoc2.Free;
end;
end;
Is there something I could substitute for CONTENTS? I can't use text, since I want to copy formatting, bookmarks, field codes etc. Do I have to do it another way alltogether? Any suggestions?
I don't know a way for earlier versions of Word, but for newer versions (2007 and up) you can export a range from a document to a fragment file, and then import it from another document. If you want early binding, you might need to import the type library (msword.olb), I don't know if Delphi XE has it. Otherwise the code might look like this:
function GetTempFileName(Prefix: string): string;
begin
SetLength(Result, MAX_PATH);
GetTempPath(MAX_PATH, PChar(Result));
windows.GetTempFileName(PChar(Result), PChar(Prefix), 0, PChar(Result));
end;
procedure TForm2.Button1Click(Sender: TObject);
const
// wdFormatDocument = 0;
wdFormatRTF = $00000006;
var
WordApp : OleVariant;
fragment: string;
vDoc1, vDoc2: OleVariant;
vRange1, vRange2: OleVariant;
begin
try
WordApp := GetActiveOleObject('Word.Application');
except
WordApp := CreateOleObject('Word.Application');
end;
WordApp.Visible := True;
vDoc1 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test1.doc');
vRange1 := vDoc1.Range(20, 120); // the export range
fragment := GetTempFileName('frg');
vRange1.ExportFragment(fragment, wdFormatRTF);
try
vDoc2 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test2.doc');
vRange2 := vDoc2.Range(15, 15); // where to import
vRange2.ImportFragment(fragment);
finally
DeleteFile(fragment);
end;
end;
With my test, 'document' format threw an error (something like not being able to insert XML formatting), hence usage of RTF format.
edit:
With earlier versions, it seems to be possible to insert a named selection from one document to a selection in another document. The result seems not to be perfect regarding formatting if one of the selections happens to be in the middle of some text. But otherwise it seems to be working good.
...
WordApp.Visible := True;
vDoc1 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test1.doc');
vRange1 := vDoc1.Range(20, 188); // the transfer range
vDoc1.Bookmarks.Add('TransferSection', vRange1); // arbitrary bookmark name
vDoc2 := WordApp.Documents.Open(ExtractFilePath(Application.ExeName) + 'test2.doc');
vRange2 := vDoc2.Range(103, 104); // where to import the bookmark
vRange2.Select;
vDoc2.ActiveWindow.Selection.InsertFile(vDoc1.FullName, 'TransferSection');
vDoc1.Bookmarks.Item('TransferSection').Delete; // no need for the bookmark anymore
If you can use the Office Open XML-format (ie. the docx file format that was introduced in Word 2007), then you can do this without automation.
Word versions prior to 2007 must install a compatibility pack which will enable docx-files for Word 2003, 2002 and 2000.
The docx-file is actually a zip-file that contains several xml-files. Try to change the extension of a docx-file from .docx to .zip and open this file in eg. WinZip.
So... Unzip docx-file and grab the xml-part you need. As pure string or as a xml document. Then you can inject this xml-part into the other docx-file. You need to know where in the xml-structure to grab/insert the xml, though. This will depend on how well you know the document structure and how much editing the user is allowed to do in the document.
I don't know how Word will handle duplicate bookmark names etc with this approach.
It seems I found the canonical solution to this question while digged into similar problem. The FormattedText property of Range object is the exact what do you need. Just use:
vRange2.FormattedText := vRange1;
and the contents of vRange1 will be copied into vRange2. Also, this works too:
vRange2 := vRange1;
Though, the second statement doesn't copy the formatting.
Why not use the clipboard? If all the text is selected in vDoc1, then to copy this to the clipboard involves one simple call: vDoc1.copy. Similarly, copying the contents of the clipboard to the second document requires one simple call: vDoc2.paste. The clipboard buffer will hold all the formatting information.