I am creating my own OnAdvancedDrawItem to change the color of the MainMenu. It works well but I get an annoying white line at the bottom.
It disappears when running the mouse over the menu but comes back when another application is selected. How can I get rid of it?
Here is my basic code for the background coloring.
unit MenMain;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, Menus, ImgList, StdCtrls;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
File2: TMenuItem;
Edit1: TMenuItem;
Window1: TMenuItem;
procedure Window1AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Window1AdvancedDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState);
begin
with TMenuItem(Sender) do
begin
with ACanvas do
begin
Brush.Color := clMoneyGreen;
Inc(ARect.Bottom,1);
FillRect(ARect);
Font.Color := clBlue;
DrawText(ACanvas.Handle, PChar(Caption),Length(Caption),ARect, DT_SINGLELINE or DT_VCENTER);
end;
end;
end;
end.
The ARect parameter of the OnAdvancedDrawItem event handler is the rcItem of the DRAWITEMSTRUCT that's passed to the WM_DRAWITEM message. The documentation has this to say about the rectangle:
A rectangle that defines the boundaries of the control to be drawn.
This rectangle is in the device context specified by the hDC member.
The system automatically clips anything that the owner window draws in
the device context for combo boxes, list boxes, and buttons, but does
not clip menu items. When drawing menu items, the owner window must
not draw outside the boundaries of the rectangle defined by the rcItem
member.
So although the device context is not clipped to the rectangle, you're responsible for not drawing outside of it. That happens when you execute Inc(ARect.Bottom,1); before filling the rectangle.
You can change the color of the grey area. Use this in OnCreate and OnCanResize
global var - fMenuBrushHandle: THandle;
var
lMenuInfo: TMenuInfo;
lMenuColor: TColor;
begin
lMenuColor := clRed;
DeleteObject(fMenuBrushHandle);
fMenuBrushHandle := CreateSolidBrush(ColorToRGB(lMenuColor));
FillChar(lMenuInfo, SizeOf(lMenuInfo), 0);
lMenuInfo.cbSize := SizeOf(lMenuInfo);
lMenuInfo.hbrBack := fMenuBrushHandle;
lMenuInfo.fMask := MIM_BACKGROUND;
SetMenuInfo(MainMenu1.Handle, lMenuInfo);
end;
or
global var - FBrush: TBrush;
var
lMenuInfo: TMenuInfo;
begin
if not Assigned(FBrush) then
FBrush := TBrush.Create;
FBrush.Color := clRed;
FBrush.Style := bsSolid;
lMenuInfo.cbSize := SizeOf(lMenuInfo);
lMenuInfo.fMask := MIM_BACKGROUND;
lMenuInfo.hbrBack := FBrush.Handle;
SetMenuInfo(MainMenu1.Handle, lMenuInfo);
end;
or even draw bitmap
global var
fMenuHandle:THandle;
fBitmap:Tbitmap;
var
lMenuInfo:TMenuInfo;
begin
if Assigned(fBitmap) then
fBitmap.Free;
fBitmap:=TBitmap.Create;
fBitmap.Width:=21;
fBitmap.Height:=Form1.Width;
DeleteObject(fMenuHandle);
fMenuHandle:=CreatePatternBrush(fBitmap.Handle);
Fillchar(lMenuInfo,SizeOf(lMenuInfo),0);
lMenuInfo.cbSize:=SizeOf(lMenuInfo);
lMenuInfo.fMask:=MIM_BACKGROUND;
lMenuInfo.hbrBack:=fMenuHandle;
SetMenuInfo(MainMenu1.Handle,lMenuInfo);
end;
Related
This question already has answers here:
How to capture the screen and mouse pointer using Windows APIs?
(2 answers)
How can I capture screen under my own window excluding my own window
(4 answers)
How do I capture desktop screenshot behind full screen form?
(1 answer)
Screenshot behind a full screen Form results in a black screen
(1 answer)
Closed 1 year ago.
I'm modifying an open-source Delphi magnifier application to meet my needs. It's very simple and only contains a TImage control to show the zoomed screen.
When I run it, it looks like this:
Basically, when the user moves the cursor, the app copies the corresponding rectangle and draws it on the TImage to give a zooming effect.
However, the problems are:
It doesn't show the zoomed cursor (Windows Magnifier does that)
It can't get the screen portion underneath the Main Form (Windows Magnifier does that).
How can I implement these two features? I have no clues right now.
My final goal is to make it run in full screen and still zoom, just like Windows Magnifier does.
Below is the code I have.
UNIT uZoom;
INTERFACE
USES
ShellApi, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, ExtCtrls, Buttons, System.Actions, Vcl.ActnList;
TYPE
TMainForm = CLASS(TForm)
img: TImage;
timer: TTimer;
ActionList1: TActionList;
inc_factor: TAction;
dec_factor: TAction;
PROCEDURE FormResize(Sender: TObject);
PROCEDURE FormDestroy(Sender: TObject);
PROCEDURE timerTimer(Sender: TObject);
PROCEDURE inc_factorExecute(Sender: TObject);
PROCEDURE FormCreate(Sender: TObject);
PROCEDURE dec_factorExecute(Sender: TObject);
PRIVATE
PUBLIC
END;
VAR
MainForm: TMainForm;
VAR
factor: integer;
IMPLEMENTATION
{$R *.DFM}
PROCEDURE TMainForm.FormResize(Sender: TObject);
BEGIN
img.Picture := NIL;
END;
PROCEDURE TMainForm.inc_factorExecute(Sender: TObject);
BEGIN
factor := factor + 1;
OutputDebugString(PChar(inttostr(factor)));
Invalidate;
END;
PROCEDURE TMainForm.dec_factorExecute(Sender: TObject);
BEGIN
factor := factor - 1;
IF factor = 0 THEN
factor := 1;
OutputDebugString(PChar(inttostr(factor)));
Invalidate;
END;
PROCEDURE TMainForm.FormCreate(Sender: TObject);
BEGIN
factor := 1;
OutputDebugString(PChar(inttostr(factor)));
Invalidate;
END;
PROCEDURE TMainForm.FormDestroy(Sender: TObject);
BEGIN
timer.Interval := 0;
END;
PROCEDURE TMainForm.timerTimer(Sender: TObject);
VAR
srcRect, destRect, fmrRect: TRect;
iWidth, iHeight, DmX, DmY: integer;
C: TCanvas;
curPos: TPoint;
BEGIN
// Determines whether the specified window is minimized (iconic).
IF IsIconic(Application.Handle) THEN
exit;
// Retrieves a handle to the desktop window. The desktop window covers the entire screen.
// The desktop window is the area on top of which other windows are painted.
VAR
hDesktop: Hwnd := GetDesktopWindow;
// Retrieves the position of the mouse cursor, in screen coordinates.
GetCursorPos(curPos);
fmrRect := Rect(MainForm.Left, MainForm.Top, MainForm.Left + MainForm.Width, MainForm.Top + MainForm.Height);
// The PtInRect function determines whether the specified point lies within the specified rectangle.
// A point is within a rectangle if it lies on the left or top side or is within all four sides.
// A point on the right or bottom side is considered outside the rectangle.
IF NOT PtInRect(fmrRect, curPos) THEN
BEGIN
img.Visible := True;
iWidth := img.Width;
iHeight := img.Height;
destRect := Rect(0, 0, iWidth, iHeight);
VAR dx: real := iWidth / (factor * 4);
VAR dy: real := iHeight / (factor * 4);
srcRect := Rect(curPos.x, curPos.y, curPos.x, curPos.y);
InflateRect(srcRect, Round(dx), Round(dy));
IF srcRect.Left < 0 THEN
OffsetRect(srcRect, -srcRect.Left, 0);
IF srcRect.Top < 0 THEN
OffsetRect(srcRect, 0, -srcRect.Top);
IF srcRect.Right > Screen.DesktopWidth THEN
OffsetRect(srcRect, -(srcRect.Right - Screen.DesktopWidth), 0);
IF srcRect.Bottom > Screen.DesktopHeight THEN
OffsetRect(srcRect, 0, -(srcRect.Bottom - Screen.DesktopHeight));
C := TCanvas.Create;
TRY
C.Handle := GetDC(GetDesktopWindow);
img.Canvas.CopyRect(destRect, C, srcRect);
FINALLY
ReleaseDC(hDesktop, C.Handle);
C.Free;
END;
END;
END;
END.
Edit: Scroll to question bottom to see answered working code.
I am trying to change the colour of the menu bar on a Form.
I found this site with some advice:
https://www.experts-exchange.com/questions/20150240/Color-on-the-MainMenu.html
I will paste the code itself below.
Unfortunately, it doesn't quite work as I would like. The shortcomings are:
The colour only applies to the menu items, the remaining space to the right of the last menu item is grey. I have set the Form colour to be the same as the menu, but it doesn't change this.
Some of the entries in each menu drop-down should be disabled, and if I don't apply the colouring code they are correctly shown disabled. Applying the colour changes removes this visual effect, and their colour is the same as all the other entries in the menu drop-down.
My questions are:
Is there a pre-rolled menu object out there that will allow me to easily colour the menu bar, including the empty space to the right, and that preserves properties like showing disabled?
If not, could someone point me in the right direction as to what additional changes I need to make to the code that could fix the problems above?
I am a total newbie to Delphi (and coding, really) but if I can get the names of things to look up then I can Google and take it from there.
I'm using Delphi 10.3.
Code copied from the link above:
type
TForm1 = class(TForm)
.....
procedure FormCreate(Sender: TObject);
public
procedure DrawMenuItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean);
end;
...
procedure TForm1.DrawMenuItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean);
var
S: String;
begin
with ACanvas do
begin
S := TMenuItem(Sender).Caption;
if Selected then
Brush.Color := clHighLight
else
Brush.Color := clLime;
FillRect(ARect);
DrawText(ACanvas.Handle, PChar(S), Length(S), ARect, DT_SINGLELINE or DT_VCENTER);
end;
end;
procedure AllOnDrawTo(M: TMenuItem; P: TMenuDrawItemEvent);
var
I: Integer;
begin
M.OnDrawItem := P;
for I := 0 to M.Count-1 do
AllOnDrawTo(M.Items[I], P);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
for I := 0 to MM.Items.Count -1 do
AllOnDrawTo(MM.Items[I], DrawMenuItem);
end;
UPDATE:
#tom-brunberg gave me the required additions in a comment. Below is the updated code to implement both items I requested. I have kept the original code because I think it is interesting to see the contrast between the two options.
type
TForm1 = class(TForm)
.....
procedure FormCreate(Sender: TObject);
public
procedure AdvancedDrawMenuItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; State: TOwnerDrawState);
end;
...
procedure TForm1.AdvancedDrawMenuItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; State: TOwnerDrawState);
var
S: String;
begin
with ACanvas do
begin
S := TMenuItem(Sender).Caption;
// Set the highlight colour when the menu item is selected. Grey highlight if disabled.
if odSelected in State then
if odDisabled in State then
Brush.Color := clBtnFace
else
Brush.Color := clGradientActiveCaption
else
Brush.Color := clGradientInactiveCaption;
// Set the colour of the menu item textm, grey if disabled
if odDisabled in State then
Font.Color := clGray
else
Font.Color := clBlack;
// this line fill rest of the top of the form the same colour as the menu. If its the LAST menu item fill rect all way to the right. My example has 8 menu items
if (Parent = nil) and (TMenuItem(Sender).MenuIndex = 8) and not (odSelected in State) then
ARect.Right := Width;
FillRect(ARect);
DrawText(ACanvas.Handle, PChar(S), Length(S), ARect, DT_SINGLELINE or DT_VCENTER);
end;
end;
procedure AdvancedAllOnDrawTo(M: TMenuItem; P: TAdvancedMenuDrawItemEvent);
var
I: Integer;
begin
M.OnAdvancedDrawItem := P;
for I := 0 to M.Count-1 do
AdvancedAllOnDrawTo(M.Items[I], P);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
for I := 0 to MM.Items.Count -1 do
AdvancedAllOnDrawTo(MM.Items[I], AdvancedDrawMenuItem);
end;
I don't have a full answer for you, but you did say that you can 'google from there'.
Your code applies a custom drawing routine to the menu items only. If you also want to draw the menu bar itself you need to have a custom drawing routine for that. The standard TMenu OwenerDraw allows you to receive events for the menu items. The Menu does have a Window Handle, which means you can paint to it, ideally you want it to stop itself from overpainting any changes you make. Have a look at the source code for the TMenu painting (I haven't had time to do that) and see if you can spot what you need to override to paint it.
TMenu wil be wrapping the generic Windows handling for a menu, so you may be able to find out how Windows allows you to draw the menu and then implement that. (That's a fair amount of googling!)
As for the enabled/disabled feedback You can draw anything you like in the on draw event. If you want to visually display something different when the TMenuItem is disabled, check if it's disabled and then draw what you want.
I have problem with creating a component. I want to have an image and simple label on the center of this image. It have to be a component because I will create it dynamically form the code. How to do this? I don't know how to merge two components into one.
If you want to implement it as an own Component the fastest way might be to inherit from TImage, which would offer all Properties needed for images and Override the Paint method, accessing the canvas of the ancestor, this will not make any chances on the Bitmap. The short example is not dealing with Stretch, you will have to implement it on your own.
unit CaptionImage;
interface
uses Windows, Classes, Controls, ExtCtrls, Graphics, PNGIMage, jpeg;
type
// maybe we want to do some more action on the Canvas without manipulation the Bitmap
TOnAfterpaintEvent = Procedure(Sender: TObject; Canvas: TCanvas) of object;
TGraphicControl = Class(Controls.TGraphicControl) // make canvas accessable
public
Property Canvas;
End;
TCaptionImage = Class(ExtCtrls.TImage)
private
ICanvas: TCanvas;
FOnAfterPaint: TOnAfterpaintEvent;
function GetFont: TFont;
published
public
procedure Paint; override;
published
Property OnAfterPaint: TOnAfterpaintEvent Read FOnAfterPaint Write FOnAfterPaint;
Property Caption;
Property Font: TFont read GetFont;
End;
implementation
function TCaptionImage.GetFont: TFont;
begin
Result := TGraphicControl(Self).Canvas.Font;
end;
procedure TCaptionImage.Paint;
var
s: String;
r: TRect;
begin
inherited;
r := ClientRect;
s := Caption;
ICanvas := TGraphicControl(Self).Canvas;
ICanvas.Brush.Style := bsClear;
ICanvas.Textrect(r, s, [tfVerticalCenter, tfCenter, tfSingleLine]);
if Assigned(FOnAfterPaint) then
FOnAfterPaint(Self, ICanvas);
end;
end.
an example usage would be:
procedure TForm5.Button1Click(Sender: TObject);
begin
With TCaptionImage.Create(self) do
begin
Parent := self;
AutoSize := true;
Font.Color := clBlue;
Font.Size := 20;
Picture.LoadFromFile('C:\temp\Bild 1.png');
Caption := 'Test';
end;
end;
I've implemented custom drag images with no problem.
I inherite a class from TDragControlObject and override its GetDragImages function and
add bitmap to TDragImageList, making the white pixels transparent.
It works, white pixels are invisible (transparent) but the remaining bitmap is not opaque.
Is there a way to change this behavior of dragobject?
You can use ImageList_SetDragCursorImage. This is normally used to provide a merged image of the drag image with a cursor image, and then, normally, you hide the real cursor to prevent confusion (showing two cursors).
The system does not blend the cursor image with the background as it does with the drag image. So, if you provide the same drag image as the cursor image, at the same offset, and do not hide the actual cursor, you'll end up with an opaque drag image with a cursor. (Similarly, an empty drag image could be used but I find the former design easier to implement.)
The below sample code (XE2) is tested with W7x64 and in a VM with XP.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button2MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Button2StartDrag(Sender: TObject; var DragObject: TDragObject);
procedure Button2EndDrag(Sender, Target: TObject; X, Y: Integer);
private
FDragObject: TDragObject;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
commctrl;
{$R *.dfm}
type
TMyDragObject = class(TDragObjectEx)
private
FDragImages: TDragImageList;
FImageControl: TWinControl;
protected
function GetDragImages: TDragImageList; override;
public
constructor Create(ImageControl: TWinControl);
destructor Destroy; override;
end;
constructor TMyDragObject.Create(ImageControl: TWinControl);
begin
inherited Create;
FImageControl := ImageControl;
end;
destructor TMyDragObject.Destroy;
begin
FDragImages.Free;
inherited;
end;
function TMyDragObject.GetDragImages: TDragImageList;
var
Bmp: TBitmap;
Pt: TPoint;
begin
if not Assigned(FDragImages) then begin
Bmp := TBitmap.Create;
try
Bmp.PixelFormat := pf32bit;
Bmp.Canvas.Brush.Color := clFuchsia;
// 2px margin at each side just to show image can have transparency.
Bmp.Width := FImageControl.Width + 4;
Bmp.Height := FImageControl.Height + 4;
Bmp.Canvas.Lock;
FImageControl.PaintTo(Bmp.Canvas.Handle, 2, 2);
Bmp.Canvas.Unlock;
FDragImages := TDragImageList.Create(nil);
FDragImages.Width := Bmp.Width;
FDragImages.Height := Bmp.Height;
Pt := Mouse.CursorPos;
MapWindowPoints(HWND_DESKTOP, FImageControl.Handle, Pt, 1);
FDragImages.DragHotspot := Pt;
FDragImages.Masked := True;
FDragImages.AddMasked(Bmp, clFuchsia);
finally
Bmp.Free;
end;
end;
Result := FDragImages;
end;
//--
procedure TForm1.Button2MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
(Sender as TWinControl).BeginDrag(False);
// OnStartDrag is called during the above call so FDragImages is
// assigned now.
// The below is the only difference with a normal drag image implementation.
ImageList_SetDragCursorImage(
(FDragObject as TMyDragObject).GetDragImages.Handle, 0, 0, 0);
end;
procedure TForm1.Button2StartDrag(Sender: TObject; var DragObject: TDragObject);
begin
DragObject := TMyDragObject.Create(Sender as TWinControl);
DragObject.AlwaysShowDragImages := True;
FDragObject := DragObject;
end;
end.
Screen shot for above code:
(Note that the actual cursor was crNoDrop but the capture software used the default one.)
If you want to see what the system really does with the images, change the above ImageList_SetDragCursorImage call to proide a hot spot, e.g.
ImageList_SetDragCursorImage(
(FDragObject as TMyDragObject).GetDragImages.Handle, 0, 15, 15);
// ShowCursor(False); // optional
now you'll be able to see both the semi-transparent and opaque images at the same time.
I have an object consisting of a TFrame, on it a TPanel and on that a TImage. A bitmap is assigned to the TImage containing a piano roll. This frame-object is put on a TImage, containing an image that contains a grid. See the image for an example.
Question: Is it possible to make the frame partially transparent, so that the background image containing the grid (on the main form) is vaguely visible? Ideally the amount of transparency can be set by the user. The bitmap is 32 bit deep but experimenting with the alpha channel did not help. The panel is not strictly necessary. It is used to quickly have a border around the object. I could draw that on the image.
Update 1 A small code example is added. The main unit draws a background with vertical lines. The second unit contains a TFrame and a TImage upon it that draws a horizontal line. What I would like to see is that the vertical lines partially shine thru the TFrame Image.
Update 2 What I did not specify in my original question: the TFrame is part of a much bigger application and behaves independently. It would help if the transparency issue could be handled by the TFrame itself.
///////////////// Main unit, on mouse click draw lines and plot TFrame
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls,
Unit2;
type
TForm1 = class(TForm)
Image1: TImage;
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var background: TBitmap;
f: TFrame2;
i, c: Int32;
begin
background := TBitmap.Create;
background.Height := Image1.Height;
background.Width := Image1.Width;
background.Canvas.Pen.Color := clBlack;
for i := 0 to 10 do
begin
c := i * background.Width div 10;
background.Canvas.MoveTo (c, 0);
background.Canvas.LineTo (c, background.Height);
end;
Image1.Picture.Assign (background);
Application.ProcessMessages;
f := TFrame2.Create (Self);
f.Parent := Self;
f.Top := 10;
f.Left := 10;
f.plot;
end;
end.
///////////////////Unit containing the TFrame
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TFrame2 = class(TFrame)
Image1: TImage;
procedure plot;
end;
implementation
{$R *.dfm}
procedure TFrame2.plot;
var bitmap: TBitmap;
begin
bitmap := TBitmap.Create;
bitmap.Height := Image1.Height;
bitmap.Width := Image1.Width;
bitmap.PixelFormat := pf32Bit;
bitmap.Canvas.MoveTo (0, bitmap.Height div 2);
bitmap.Canvas.LineTo (bitmap.Width, bitmap.Height div 2);
Image1.Picture.Assign (bitmap);
end;
end.
Update 3 I had hoped for that there would be some message or API call that would result in a solution that the control could make itself partially transparent, like the WMEraseBkGnd message does for complete transparency. In their solutions both Sertac and NGLN both point at simulating transparency with the AlphaBlend function. This function merges two bitmaps and thus requires a knowledge of the background image. Now my TFrame has an extra property: BackGround: TImage that is assigned by the parent control. That gives the desired result (it's sooo professional to see it working :-)
RRUZ points to the Graphics32 library. What I've seen it produces fantastic results, for me the learning curve is too steep.
Thank you all for your help!
Here's another solution that copies the background image to the top image and AlphaBlends the bitmap over it while preserving opacity of black dots:
unit1:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Unit2, ExtCtrls, ComCtrls, StdCtrls;
type
TForm1 = class(TForm)
Clip_View1: TClip_View;
TrackBar1: TTrackBar;
Label1: TLabel;
procedure TrackBar1Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
TrackBar1.Min := 0;
TrackBar1.Max := 255;
TrackBar1.Position := 255;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
Label1.Caption := IntToStr(TrackBar1.Position);
Clip_View1.Transparency := TrackBar1.Position;
end;
end.
unit2:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TClip_View = class(TFrame)
Image1: TImage;
Panel1: TPanel;
Image2: TImage;
protected
procedure SetTransparency(Value: Byte);
private
FTopBmp: TBitmap;
FTransparency: Byte;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Transparency: Byte read FTransparency write SetTransparency;
end;
implementation
{$R *.dfm}
{ TClip_View }
constructor TClip_View.Create(AOwner: TComponent);
begin
inherited;
Image1.Left := 0;
Image1.Top := 0;
Image1.Picture.LoadFromFile(ExtractFilePath(Application.ExeName) + '..\..\back.bmp');
Image1.Picture.Bitmap.PixelFormat := pf32bit;
Image1.Width := Image1.Picture.Bitmap.Width;
Image1.Height := Image1.Picture.Bitmap.Height;
FTopBmp := TBitmap.Create;
FTopBmp.LoadFromFile(ExtractFilePath(Application.ExeName) + '..\..\top.bmp');
FTopBmp.PixelFormat := pf32bit;
Image2.SetBounds(1, 1, FTopBmp.Width, FTopBmp.Height);
Panel1.SetBounds(20, 20, Image2.Width + 2, Image2.Height + 2);
Image2.Picture.Bitmap.SetSize(Image2.Width, Image2.Height);
Image2.Picture.Bitmap.Canvas.Draw(0, 0, FTopBmp);
end;
destructor TClip_View.Destroy;
begin
FTopBmp.Free;
inherited;
end;
procedure TClip_View.SetTransparency(Value: Byte);
var
Bmp: TBitmap;
R: TRect;
X, Y: Integer;
Pixel: PRGBQuad;
BlendFunction: TBlendFunction;
begin
if Value <> FTransparency then begin
FTransparency := Value;
R := Image2.BoundsRect;
OffsetRect(R, Panel1.Left, + Panel1.Top);
Image2.Picture.Bitmap.Canvas.CopyRect(Image2.ClientRect,
Image1.Picture.Bitmap.Canvas, R);
Bmp := TBitmap.Create;
Bmp.SetSize(FTopBmp.Width, FTopBmp.Height);
Bmp.PixelFormat := pf32bit;
Bmp.Assign(FTopBmp);
try
for Y := 0 to Bmp.Height - 1 do begin
Pixel := Bmp.ScanLine[Y];
for X := 0 to Bmp.Width - 1 do begin
if (Pixel.rgbBlue <> 0) and (Pixel.rgbGreen <> 0) and
(Pixel.rgbRed <> 0) then begin
Pixel.rgbBlue := MulDiv(Pixel.rgbBlue, Value, $FF);
Pixel.rgbGreen := MulDiv(Pixel.rgbGreen, Value, $FF);
Pixel.rgbRed := MulDiv(Pixel.rgbRed, Value, $FF);
Pixel.rgbReserved := Value;
end else // don't touch black pixels
Pixel.rgbReserved := $FF;
Inc(Pixel);
end;
end;
BlendFunction.BlendOp := AC_SRC_OVER;
BlendFunction.BlendFlags := 0;
BlendFunction.SourceConstantAlpha := 255;
BlendFunction.AlphaFormat := AC_SRC_ALPHA;
AlphaBlend(Image2.Picture.Bitmap.Canvas.Handle,
0, 0, Image2.Picture.Bitmap.Width, Image2.Picture.Bitmap.Height,
Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height,
BlendFunction);
finally
Bmp.Free;
end;
end;
end;
end.
At launch time:
Apply transparency:
Hide the frame and use Frame.PaintTo. For example, as follows:
unit Unit1;
interface
uses
Windows, Classes, Graphics, Controls, Forms, Unit2, JPEG, ExtCtrls;
type
TForm1 = class(TForm)
Image1: TImage; //Align = alClient, Visible = False
Frame21: TFrame2; //Visible = False
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
private
FBlendFunc: TBlendFunction;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.FormPaint(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
try
Bmp.Width := Frame21.Width;
Bmp.Height := Frame21.Height;
Frame21.PaintTo(Bmp.Canvas, 0, 0);
Canvas.StretchDraw(ClientRect, Image1.Picture.Graphic);
with Frame21 do
Windows.AlphaBlend(Canvas.Handle, Left, Top, Left + Width, Top + Height,
Bmp.Canvas.Handle, 0, 0, Width, Height, FBlendFunc);
finally
Bmp.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FBlendFunc.BlendOp := AC_SRC_OVER;
FBlendFunc.BlendFlags := 0;
FBlendFunc.SourceConstantAlpha := 255 div 2;
FBlendFunc.AlphaFormat := 0;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Invalidate;
end;
end.
The frame unit:
unit Unit2;
interface
uses
Windows, Classes, Controls, Forms, JPEG, ExtCtrls;
type
TFrame2 = class(TFrame)
Image1: TImage; //Align = alClient
Panel1: TPanel; //Align = alClient, BevelWidth = 5
end;
implementation
{$R *.dfm}
end.
Result:
Rewrite the above for your specific situation, ideally painting on a TPaintBox getting rid of the image component on the main form. But when the only significant element of the frame is the image, then I would stop using that too, and begin painting everything myself.
I would use a TPaintBox instead. In its OnPaint event, draw your grid first, then alpha-blend your roll image on top. No need to use any TImage, TPanel, or TFrame components at all.