In Crystal Reports 2008, there's a "Graphic Location" image property where it can be set to a file path so when the report gets run, it uses the selected picture file instead of the one on the report. I tried setting this via the .NET API, but it's only working some of the time.
In the report itself, I've set Graphic Location to {#LogoPath}, then when I run the report via .NET API, I set {#LogoPath} to the filename of the picture. I've put the formula on the report itself, and it's indeed getting set to the correct filename, but the image on the report doesn't always update. It would consistently show up for some time, then consistently not show it again.
Here's what I ended up using, the code is in Delpi Prism. One of the awkward thing to deal with is if the image being replaced is different size to the image on the report, Crystal doesn't resize it correctly. The other problem was I needed to free the picture object manually otherwise Crystal sometimes doesn't display it on the report.
method SetShadingAndLogo(const AReport:ReportDocument);
var
LogoPath:String;
PicObj:PictureObject;
Logo:System.Drawing.Image;
PicRatio:Double;
ContWidth, ContHeight:Double;
ContainerRatio:Double;
NewDimension:Double;
PosAdj:Integer;
Scale:Double;
begin
for each Section:Section in AReport.ReportDefinition.Sections do
begin
for each RptObj:ReportObject in Section.ReportObjects do
begin
if RptObj.Name.StartsWith('LOGO', StringComparison.CurrentCultureIgnoreCase) and
(RptObj.Kind = ReportObjectKind.PictureObject) then
begin
//set to company logo
LogoPath := "C:\logo.jpg";
PicObj := RptObj as PictureObject;
if not System.IO.File.Exists(LogoPath) then
PicObj.ObjectFormat.EnableSuppress := true
else
begin
Logo := System.Drawing.Image.FromFile(LogoPath);
//work out the aspect ratios of the image and the container
PicRatio := Double(Logo.Width) / Double(Logo.Height);
//convert twips to pixels
//96 is the default dpi for Windows, but should really check Windows settings
//instead of hard coding
ContWidth := Double(TwipsToPx(PicObj.Width, 96));
ContHeight := Double(TwipsToPx(PicObj.Height, 96));
ContainerRatio := ContWidth / ContHeight;
// adjust the size of the container on the report to maintiain the original
// image's ratio
if PicRatio > ContainerRatio then
begin
// reset the vertical position to remain centred on the original location
// get the new height of the container (in pixels)
NewDimension := (ContWidth / Logo.Width) * Logo.Height;
// get the movement (in twips)
PosAdj := PxToTwips(Integer((ContHeight - NewDimension) / 2), Integer(Logo.VerticalResolution));
// adjust the position
PicObj.Top := PicObj.Top + PosAdj;
// picture is wider so adjust the height accordingly
// need to scale using the logo's dpi to resize correctly
Scale := Double(PicObj.Width) / Double(PxToTwips(Logo.Width, Integer(Logo.VerticalResolution)));
PicObj.Width := Integer(PicObj.Width * Scale);
PicObj.Height := Integer(PicObj.Height * Scale);
end
else
begin
// picture is taller and needs to be scaled to height
// reset the horizontal position to remain centred on the original location
// get the new width of the container (in pixels)
NewDimension := (ContHeight / Logo.Height) * Logo.Width;
// get the movement (in twips)
PosAdj := PxToTwips(Integer((ContWidth - NewDimension) / 2), Integer(Logo.VerticalResolution));
// adjust the position
PicObj.Left := PicObj.Left + PosAdj;
// picture is taller and needs to be scaled to height
// need to scale using the logo's dpi to resize correctly
Scale := Double(PicObj.Height) / Double(PxToTwips(Logo.Height, Integer(Logo.VerticalResolution)));
PicObj.Width := Integer(PicObj.Width * Scale);
PicObj.Height := Integer(PicObj.Height * Scale);
end;
//must free the logo, otherwise Crystal sometimes doesn't display it on report
Logo.Dispose;
for each fm:FormulaFieldDefinition in AReport.DataDefinition.FormulaFields do
begin
if fm.Name.Equals("LogoPath") then
fm.Text := """"+LogoPath+"""";
end;
end;
end;
end;
end;
end;
method TwipsToPx(const AValue, ADpi:Integer):Integer;
begin
//convert to twips using specified dpi, 96 is Windows' default dpi
Result := System.Convert.ToInt32(Double(AValue) * ADpi / 1440);
end;
method PxToTwips(const AValue, ADpi:Integer):Integer;
begin
//convert to pixels using specified dpi, 96 is Windows' default dpi
Result := System.Convert.ToInt32(Double(AValue) * 1440 / ADpi);
end;
Take a look at my Crystal Reports: Dynamic Images posting.
In Graphics Location formula, you might want to do like as follow:-
{?imageUrl}
You can set this parameter in your CS file dynamically by doing as follow:-
ParameterFields paramFields = new ParameterFields();
ParameterField paramField = new ParameterField();
ParameterDiscreteValue parameterValue = new ParameterDiscreteValue();
paramField.Name = "imageUrl";
parameterValue.Value = "**URL FOR IMAGE**";
paramField.CurrentValues.Add(parameterValue1);
paramFields.Add(paramField);
In Report, Create "imageUrl" Parameter field. By doing as follow :-
1) Go to report and open Field Explorer on your left. If its not there you can get it by going
Crystal Report >> Filed Explorer.
2) Right click on Parameter Field and click on "New"
3) Give Parameter name as "imageUrl". In "Value Option" set "Prompt With display only" and "Optional Prompt" to false.
This should work.
Let me know if it helps.
Related
How do I open an (dm4) image with annotations in a script in dm-script?
When a dm4 image has annotations (e.g. a scale bar or some text), this is displayed when I open the image via the menu (Ctrl + O). But when I open the same file in a script by openImage() they do not show up as shown below.
On the left there is the image opened via the menu, on the right is the exact same image opened by openImage(). It is missing the annotations.
The following example shows the same thing. The code adds text to an image, saves it and opens it again. The opened image does not show the annotations just as the images above:
String path = GetApplicationDirectory("current", 0);
path = PathConcatenate(path, "temp.dm4");
// get the current image
image img;
img.getFrontImage();
ImageDisplay display = img.ImageGetImageDisplay(0);
// add some test annotations
number height = img.ImageGetDimensionSize(1);
number padding = height / 100;
number font_size = height/10;
for(number y = padding; y + font_size + padding < height; y += font_size + padding){
Component annotation = NewTextAnnotation(padding, y, "Test", font_size);
annotation.componentSetForegroundColor(255, 255, 255);
display.ComponentAddChildAtEnd(annotation);
}
// save the current image
img.saveImage(path);
// show the saved image
image img2 = openImage(path);
img2.showImage();
You have a mistake in the second to last line.
By using = instead of := you are copying (the values only) from the opened image into a new image. You want to do
image img2 := openImage(path)
This is a rather typical mistake made when being new to scripting, because this is a "specialty" of the scripting language not found in other languages. It comes about because scripting aims to enable very simple scripts like Z = log(A) where new images (here Z) are created on-the-fly from processing existing images (here A).
So there needs to be a different operator when one wants to assign an image to a variable.
For further details, see the F1 help documentation here:
The same logic / source of bugs concerns the use of := instead of = when "finding" images, "creating new images" and cloning images (with meta data).
Note the differences when trying both:
image a := RealImage("Test",4,100,100)
ShowImage(a)
image b = RealImage("Test",4,100,100)
ShowImage(b)
and
image a := GetFrontImage()
a = 0
image b = GetFrontImage()
b = 0
and
image src := GetFrontImage()
image a := ImageClone( src )
showImage(a)
image b := ImageClone( src )
showImage(b)
This is my code to create appearance stream for a free text annotation.
cs.rectangle(bbox.getLeft() , bbox.getBottom(), bbox.getWidth(), bbox.getHeight());
cs.fill();
String[] text = new String[1];
text[0] = "BAC"
cs.setFontAndSize(BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED), pdfJSAnnotation.getFontSize());
cs.beginText();
cs.setLeading(fontSize + 1.75f);
cs.moveText(0, bbox.getHeight() - fontSize + .75f);
for (String s : text) {
if (s.equals("\n"))
cs.newlineText();
else
cs.showText(s);
}
cs.endText();
where cs is PdfAppearance, bbox is Rectangle. This works okay when pdf is portrait. however, im having problems when it is in landscape, say if page rotation is 270.
The text shown is vertical. and even if i use cs.transform() to rotate, it does not even rotate properly. I also tried to save the state, do a rotate then display text and then call cs.restoreState() after cs.endText() but the outcome is still not correct.
any ideas?
the rectangle is correct since the 1st 2 lines where it fills a rectangle shape is correctly displayed. it is the text i am having problems with.
The solution to this is:
- set appearance dimension to (height,width) since it is 270 degrees.
Then in the PdfAppearance object:
translate it to (height, 0);
rotate to 270
translate to (-height,-height);
(Tried to remove this post but there is no option available)
I have stuck with small problem.
Imagine two things: form, that should be covered - Cover-Form; and forms that will cover Cover-Form - Tiles.
My main goal is to cover my Cover-Form with Tiles. So it will looks like the tiles. I illustrate this idea with image below
Yellow color is a Cover-Form, brown forms - Tiles. On this image you can see that forms are positioned too close each other - there is no free space between them. That's what I need.
But when I try to reach the same effect, I just get non-satisfying result. It is presented on picture below
Second image has an offset after the last tile. It is happens because of different size of form. I don't know exactly what width my Cover-Form will have. I simply divide the whole width of Cover-Form into three parts. But if Cover-Form has width, for example, 173 pixels, each of my Tiles will have width equal 173/3=57.6 pixels, that will be round to 58, but 58*3=174 and it is bad.
Code below runs situation as on second image.
type
TTileArray = Array of Array of TPoint;
// This routine comes here from David's answer below and were changed by me
procedure EvenlySpacedTiles(PixelCountH, PixelCountV, TileCount: Integer; var ArrayOut: TTileArray);
var
X: Integer;
Y: Integer;
OldH: Integer;
OldV: Integer;
OldCount: Integer;
OldCount1: Integer;
TempInt: Integer;
begin
if (PixelCountH) or (PixelCountV) or(TileCount) = 0 then
Exit;
OldH := PixelCountH;
OldCount1 := TileCount;
for X:=Low(ArrayOut) to High(ArrayOut) do
begin
OldV := PixelCountV;
OldCount := TileCount;
TempInt := OldH div OldCount1;
Dec(OldH, TempInt);
Dec(OldCount1);
for Y:=Low(ArrayOut) to High(ArrayOut) do
begin
ArrayOut[X, Y] := Point(TempInt, OldV div OldCount);
Dec(OldV, ArrayOut[X, Y].Y);
Dec(OldCount);
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
F: TForm;
P: TForm;
Delta: Integer;
PrevLeft: Integer;
PrevTop: Integer;
X:Integer;
Y: Integer;
Arr: TTileArray;
IncLeft: Integer;
begin
Delta := 3;
F := TForm.Create(Application);
F.BorderStyle := Forms.bsNone;
F.SetBounds(0, 0, 173, 115);
F.Position := poDesktopCenter;
F.Color := $11DFEE;
F.Show;
SetLength(Arr, Delta, Delta);
EvenlySpacedTiles(F.Width, F.Height, Delta, Arr);
PrevLeft := F.Left;
PrevTop := F.Top;
IncLeft := 0;
for X:=Low(Arr) to High(Arr) do
begin
PrevTop := F.Top;
Inc(PrevLeft, IncLeft);
for Y:=Low(Arr) to High(Arr) do
begin
P := TForm.Create(Application);
P.FormStyle := fsStayOnTop;
P.BorderStyle := Forms.bsNone;
P.Color := Random($FFFFFF);//clSkyBlue;
P.Show;
P.Width := Arr[X, Y].X;
P.Height := Arr[X, Y].Y;
P.Left := PrevLeft;
P.Top := PrevTop;
P.Canvas.Rectangle(P.ClientRect);
Inc(PrevTop, Arr[X, Y].y);
IncLeft := Arr[X, Y].X;
end;
end;
end;
So there is my question: how can I adjust width of all tiles (3 per row) independently of cover form's width?
Thanks in advance.
Edited
P.S.
I modified some parts of code above. Now it works perfectly even with extremely small and large Cover-Form width - from 67 px. to 1237 px.
Of course there is a way to improve this code, but the main goal is achieved.
I think I will able to finish vertical Tiles placing tomorrow and publish this part there.
In many ways the comment by David gives me an idea how to do this. Thank you, David!
P.S.S.
I have read David's first comment diagonally, so I update code to work in another way, but the result still not good. You can see it on the picture below.
The first Tile has 57 px. width; the second one - 59 px.; the third Tile - only 31 px.
I just can't get how to place Tiles correctly using an algorithm suggested in David's comment.
P.S.S.S.
And again there is no result.
Right red line demonstrates a big size of the last tile. Each tile has width 58 px.
David wrote this:
173/3=58. 173-58=115. 115/2=58. 115-58=57. 57/1=57
I am able to calculate it in real life, but I am not able to implement it in the code.
Source code is updated.
P.S.S.S.S.
David's procedure doesn't do what it should do. Picture below illustrates it.
There are a gap between the first and the second Tile, and red line on the right side as on previous picture.
P.S.S.S.S.S.
Well, at this time the first part of my task is accomplished. The second one - is adding more tiles, but I don't sure if I really need them. And I am thankful for this to David Heffernan!! He spend so much time to explain me some things and I don't know how to say him more than simlply 'Thank you very much'. I am afraid, I am able just increase his reputation and accept his post as an answer. It really does the job!
On picture we can see the result I needed
P.S.S.S.S.S.S.
I have updated source code, so it can place tile and vertically too.
I would use a simple algorithm like this:
function EvenlySpacedColumns(PixelCount, ColumnCount: Integer): TArray<Integer>;
var
i: Integer;
begin
Assert(PixelCount>0);
Assert(ColumnCount>0);
SetLength(Result, ColumnCount);
for i := low(Result) to high(Result) do begin
Result[i] := PixelCount div ColumnCount;
dec(PixelCount, Result[i]);
dec(ColumnCount);
end;
end;
Here I use div which in effect uses division followed by truncation. But you could equally use Round(PixelCount / ColumnCount) if you would prefer. It's somewhat arbitrary so I personally would opt for integer arithmetic on the grounds that one should avoid floating point arithmetic if it is not necessary.
I have a MDI child form that when opening doesn't maximize at once.
I can see that the datagrid on it is created and then the form is maximized
I use the following constructor to create the form.
Properties:
FormStyle = fsMDIChild
Position = poDefaultPosOnly
WindowState = wsMaximized
constructor TfrmJsContacts.Create(aOwner: TComponent);
begin
WindowState := wsMaximized;
inherited Create(aOwner);
TimerDelay.Enabled := True;
end;
The problem is a visual problem so to speak - everything works as it should. It just looks wrong that the form is shown before it is maximized. What really could be nice was to have a AfterShow event to play with. I think that would help me in this situation.
MDIChild popups comes with a little animation.
If MDIChild.Position is set to
poDefaultPosOnly
poDefaultSizeOnly
poScreenCenter
poDesktopCenter
poMainFormCenter
poOwnerFormCenter
Top and Left are calculated at creating
assume Top = 300 and Left = 400 now
at on Show MDIChild are now part of MDIParent and so
Top = 300 and Left = 400 are now Inside MDIParent.
Means left top corner of MDIChild is now in the middle of MDIParent form.
with wsMaximized the animation is shown from middle right to top left.
Now use Position
poDesigned
poDefault
and with Top and Left set to 0 the animation will not longer be seen.
constructor TfrmJsContacts.Create(aOwner: TComponent);
begin
Top := 0;
Left := 0;
WindowState := wsMaximized;
inherited Create(aOwner);
....
end;
In Design mode when frmJsContacts is selected, go look at the Object Inspector.
IF Property WindowState is set to wsMaximized
You can see for a short time the whole form !
If the changes are as follows, can be seen for a short time only the title bar, not the whole form.
set, Object Inspector : Property WindowState to wsNormal.
move code WindowState := wsMaximized below inherited Create(aOwner).
code should like
constructor TfrmJsContacts.Create(aOwner: TComponent);
begin
Top := 0;
Left := 0;
inherited Create(aOwner);
WindowState := wsMaximized;
....
end;
Are it's possible to resize vb6 inner form, because if i use Form1.Height or Form1.Width it's including window border height and width, so i just can use this code in one window theme (ex. it's work best in WinXP with XP theme, but not work in WinXP with Classic theme, it's seen too long), any suggestion?
What you can do is compare the Width (the outside size) to the ScaleWidth (which is the inside size) to get the size on the non-client border. Likewise, you can compare the Height to the ScaleHeight to get the non-client size at the top and bottom. From that you can set your final height and width based on the inner (client area) size you want plus the non-client size.
Something like this could go in your Form_Load:
Const DesiredClientHeight as Single = 3435
Const DesiredClientWidth as Single = 3345
Dim fNonClientHoriz As Single, fNonClientVert As Single
fNonClientHoriz = Me.Width - Me.ScaleWidth
fNonClientVert = Me.Height- Me.ScaleHeight
Me.Width = DesiredClientWidth + fNonClientHoriz
Me.Height = DesiredClientHeight + fNonClientVert
Be aware that the form width and height are always in Twips, so if you change your scale mode to something other than twips you will need to account for that.