I am trying to replicate resizing with splitters as is done by MS SQL Managment Studio like described in this question.
So I have a ScrollBox with many panel-and-splitter pairs, vertically stacked upon each other. When I want to enlarge a panel with the corresponding splitter, it limits the possible growth to the remaining size in the scroll box. I cannot drag the splitter beyond the client size of the scroll box.
Is there anyone that can help me solving this problem?
I have tried to enlarge VertScrollBar.Range of the scroll box, without success:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
tDataBlock = class(TComponent)
fPanel: TPanel;
fLabel: TLabel;
fSplitter: TSplitter;
fOwner: TWinControl;
published
property Panel: TPanel read fPanel write fPanel;
property Text: TLabel read fLabel write fLabel;
property Owner: TWinControl read fOwner write fOwner;
public
constructor Create(Owner: TWinControl; var t: integer);
end;
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
BlockCount: integer;
procedure ConfigureScreen;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ConfigureScreen;
var i: integer;
TotalHeight: integer;
begin
TotalHeight := 0;
for I := 0 to ScrollBox1.ComponentCount - 1 do begin
if ScrollBox1.Components[i] is TPanel then
TotalHeight := TotalHeight + TPanel(ScrollBox1.Components[i]).Height;
if ScrollBox1.Components[i] is TSplitter then
TotalHeight := TotalHeight + TSplitter(ScrollBox1.Components[i]).Height;
end;
ScrollBox1.VertScrollBar.Range := TotalHeight;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
db: tDataBlock;
t: integer;
begin
t := 0;
BlockCount := 0;
for I := 0 to 3 do begin
db := tDataBlock.Create(ScrollBox1, t);
Inc(BlockCount);
end;
ConfigureScreen;
end;
{ tDataBlock }
constructor tDataBlock.Create(Owner: TWinControl; var t: integer);
begin
fOwner := Owner;
fPanel := TPanel.Create(Owner);
fPanel.Parent := Owner;
fPanel.Height := 150;
fPanel.Top := t;
fPanel.Align := alTop;
fPanel.AlignWithMargins := false;
fPanel.Color := clRed;
fPanel.ParentBackground := false;
fPanel.BorderWidth := 0;
fPanel.BorderStyle := bsNone;
fPanel.Ctl3D := false;
fPanel.AutoSize := false;
fPanel.UseDockManager := false;
t := fPanel.Top + Panel.Height + 1;
fLabel := TLabel.Create(self);
fLabel.Parent := fPanel;
fLabel.Align := altop;
fLabel.Caption := inttostr(fPanel.Height);
fLabel.Font.Size := 10;
fSplitter := TSplitter.Create(Owner);
fSplitter.Parent:= Owner;
fSplitter.Height := 3;
fsplitter.Top := t;
fSplitter.AutoSnap := false;
fSplitter.AlignWithMargins := false;
fSplitter.MinSize := 1;
fSplitter.Align := alTop;
t := fSplitter.Top + fSplitter.Height + 1;
end;
end.
As SilverWarior mentioned, the purpose of TSplitter is to divide client area, not to resize the neighboring controls to any size you want. To achieve your goal you need to change its behavior by some tricks like the one NGLN suggested. But you may experience some side effects if you do so since other parts of code may need ClientRect of ScrollBox.
Other option is to simply use mouse events to simulate the behavior of TSplitter. I changed your code a little and used a TPanel instead. But it’s just a quick start, you may need some more coding, for example to remove the flicker ;).
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
type
tDataBlock = class(TComponent)
private
fPanel: TPanel;
fLabel: TLabel;
fResizingPanel: TPanel;
fOwner: TWinControl;
IsResizing: Boolean;
StartHeight, StartY: Integer;
procedure ResizingPanelMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
procedure ResizingPanelMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure ResizingPanelMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
published
property Panel: TPanel read fPanel write fPanel;
property Text: TLabel read fLabel write fLabel;
property Owner: TWinControl read fOwner write fOwner;
public
constructor Create(Owner: TWinControl; var t: Integer);
end;
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
BlockCount: Integer;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
db: tDataBlock;
t: Integer;
begin
t := 0;
BlockCount := 0;
for I := 0 to 3 do
begin
db := tDataBlock.Create(ScrollBox1, t);
Inc(BlockCount);
end;
end;
{ tDataBlock }
constructor tDataBlock.Create(Owner: TWinControl; var t: Integer);
begin
fOwner := Owner;
fPanel := TPanel.Create(Owner);
fPanel.Parent := Owner;
fPanel.Height := 150;
fPanel.Top := t;
fPanel.Align := alTop;
fPanel.AlignWithMargins := False;
fPanel.Color := clRed;
fPanel.ParentBackground := False;
fPanel.BorderWidth := 0;
fPanel.BorderStyle := bsNone;
fPanel.Ctl3D := False;
fPanel.AutoSize := False;
fPanel.UseDockManager := False;
fPanel.Constraints.MinHeight := 50;
// fPanel.DoubleBuffered := True;
t := fPanel.Top + Panel.Height + 1;
fLabel := TLabel.Create(self);
fLabel.Parent := fPanel;
fLabel.Align := alTop;
fLabel.Caption := inttostr(fPanel.Height);
fLabel.Font.Size := 10;
fResizingPanel := TPanel.Create(Owner);
fResizingPanel.Parent := Panel;
fResizingPanel.Height := 10;
fResizingPanel.Align := alBottom;
fResizingPanel.AlignWithMargins := False;
fResizingPanel.ParentBackground := False;
fResizingPanel.Cursor := crVSplit;
fResizingPanel.OnMouseDown := ResizingPanelMouseDown;
fResizingPanel.OnMouseMove := ResizingPanelMouseMove;
fResizingPanel.OnMouseUp := ResizingPanelMouseUp;
t := fResizingPanel.Top + fResizingPanel.Height + 1;
end;
procedure tDataBlock.ResizingPanelMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
IsResizing := True;
StartHeight := Panel.Height;
StartY := fResizingPanel.ClientOrigin.Y + Y;
end;
procedure tDataBlock.ResizingPanelMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
begin
if IsResizing then
Panel.Height := StartHeight + fResizingPanel.ClientOrigin.Y + Y - StartY;
end;
procedure tDataBlock.ResizingPanelMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
IsResizing := False;
end;
end.
Delphi's default VCL TSplitter does not support resizing beyond the size of the parent client rect. (I would opt for such a change if the parent has scroll bars, but that aside.) The reason lies in TSplitter.MouseDown wherein a private field FMaxSize is set, depending on the ClientHeight of the parent.
A solution that might work just for TScrollBox is to fool TSplitter by returning a different ClientHeight of its parent, as follows:
type
TScrollBox = class(Vcl.Forms.TScrollBox)
protected
function GetClientRect: TRect; override;
end;
implementation
function TScrollBox.GetClientRect: TRect;
begin
Result := inherited GetClientRect;
if GetCaptureControl is TSplitter then
Result.Bottom := Screen.DesktopHeight;
end;
Related
I am trying to create a browser-style TabControl with a small close button on every tab in FireMonkey FM2.
Since there are no TTabsheet and TPageControl components in FM2, I could not use the answer from "How to implement a close button for a TTabsheet of a TPageControl". This code gives too many undeclared functions and variables that are not longer supported in FM2, I guess.
I don't want to use any third-part components because you never know if they are going to support the next version of Delphi :)
I can provide the full code that works fine in Delphi XE3 VCL (but not FireMonkey):
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Math, Vcl.Themes;
type
TFormMain = class(TForm)
PageControlCloseButton: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
TabSheet3: TTabSheet;
procedure FormCreate(Sender: TObject);
procedure PageControlCloseButtonMouseLeave(Sender: TObject);
procedure PageControlCloseButtonMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure PageControlCloseButtonMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure PageControlCloseButtonMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure PageControlCloseButtonDrawTab(Control: TCustomTabControl; TabIndex: Integer; const Rect: TRect; Active: Boolean);
private
FCloseButtonsRect: array of TRect;
FCloseButtonMouseDownIndex: Integer;
FCloseButtonShowPushed: Boolean;
public
{ Public declarations }
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
var
I: Integer;
begin
PageControlCloseButton.TabWidth := 150;
PageControlCloseButton.OwnerDraw := True;
//should be done on every change of the page count
SetLength(FCloseButtonsRect, PageControlCloseButton.PageCount);
FCloseButtonMouseDownIndex := -1;
for I := 0 to Length(FCloseButtonsRect) - 1 do
begin
FCloseButtonsRect[I] := Rect(0, 0, 0, 0);
end;
end;
procedure TFormMain.PageControlCloseButtonDrawTab(Control: TCustomTabControl; TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
CloseBtnSize: Integer;
PageControl: TPageControl;
TabCaption: TPoint;
CloseBtnRect: TRect;
CloseBtnDrawState: Cardinal;
CloseBtnDrawDetails: TThemedElementDetails;
const
UseThemes: boolean=true;
begin
PageControl := Control as TPageControl;
if InRange(TabIndex, 0, Length(FCloseButtonsRect) - 1) then
begin
CloseBtnSize := 14;
TabCaption.Y := Rect.Top + 3;
if Active then
begin
CloseBtnRect.Top := Rect.Top + 4;
CloseBtnRect.Right := Rect.Right - 5;
TabCaption.X := Rect.Left + 6;
end
else
begin
CloseBtnRect.Top := Rect.Top + 3;
CloseBtnRect.Right := Rect.Right - 5;
TabCaption.X := Rect.Left + 3;
end;
CloseBtnRect.Bottom := CloseBtnRect.Top + CloseBtnSize;
CloseBtnRect.Left := CloseBtnRect.Right - CloseBtnSize;
FCloseButtonsRect[TabIndex] := CloseBtnRect;
PageControl.Canvas.FillRect(Rect);
PageControl.Canvas.TextOut(TabCaption.X, TabCaption.Y, PageControl.Pages[TabIndex].Caption);
if not UseThemes then
begin
if (FCloseButtonMouseDownIndex = TabIndex) and FCloseButtonShowPushed then
CloseBtnDrawState := DFCS_CAPTIONCLOSE + DFCS_PUSHED
else
CloseBtnDrawState := DFCS_CAPTIONCLOSE;
DrawFrameControl(PageControl.Canvas.Handle,
FCloseButtonsRect[TabIndex], DFC_CAPTION, CloseBtnDrawState);
end
else
begin
Dec(FCloseButtonsRect[TabIndex].Left);
if (FCloseButtonMouseDownIndex = TabIndex) and FCloseButtonShowPushed then
CloseBtnDrawDetails := ThemeServices.GetElementDetails(twCloseButtonPushed)
else
CloseBtnDrawDetails := ThemeServices.GetElementDetails(twCloseButtonNormal);
ThemeServices.DrawElement(PageControl.Canvas.Handle, CloseBtnDrawDetails,
FCloseButtonsRect[TabIndex]);
end;
end;
end;
procedure TFormMain.PageControlCloseButtonMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
I: Integer;
begin
if Button = mbLeft then
begin
for I := 0 to Length(FCloseButtonsRect) - 1 do
begin
if PtInRect(FCloseButtonsRect[I], Point(X, Y)) then
begin
FCloseButtonMouseDownIndex := I;
FCloseButtonShowPushed := True;
PageControlCloseButton.Repaint;
end;
end;
end;
end;
procedure TFormMain.PageControlCloseButtonMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
var
Inside: Boolean;
begin
if (ssLeft in Shift) and (FCloseButtonMouseDownIndex >= 0) then
begin
Inside := PtInRect(FCloseButtonsRect[FCloseButtonMouseDownIndex], Point(X, Y));
if FCloseButtonShowPushed <> Inside then
begin
FCloseButtonShowPushed := Inside;
PageControlCloseButton.Repaint;
end;
end;
end;
procedure TFormMain.PageControlCloseButtonMouseLeave(Sender: TObject);
var
PageControl: TPageControl;
begin
FCloseButtonShowPushed := False;
PageControlCloseButton.Repaint;
end;
procedure TFormMain.PageControlCloseButtonMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
PageControl: TPageControl;
begin
if (Button = mbLeft) and (FCloseButtonMouseDownIndex >= 0) then
begin
if PtInRect(FCloseButtonsRect[FCloseButtonMouseDownIndex], Point(X, Y)) then
begin
PageControlCloseButton.Pages[PageControlCloseButton.ActivePageIndex].TabVisible := false;
PageControlCloseButton.ActivePageIndex := 0;
FCloseButtonMouseDownIndex := -1;
PageControlCloseButton.Repaint;
end;
end;
end;
end.
On github there is an open source component that extend the base FMX TTabControl at this link https://github.com/jkour/neTabControl where you can understand how to do it by your self.
Using: Delphi XE2 Update 4.1, 32-bit VCL application, Windows 8
If DragMode is set to dmAutomatic the the OnStartDrag event is called; however if the DragMode is set to dmManual, the OnStartDrag event is bypassed.
Is this by design? How to ensure that OnStartDrag event is called?
EDIT: Code posted on request. The event in question is TTableDesigner.LblStartDrag which is not being executed after a call to BeginDrag (in TTableDesigner.LblOnMouseDown) .
unit uTableDesigner;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Graphics, JvCaptionPanel,
StdCtrls, ExtCtrls;
type
TMyTable = record
TableName: String;
TableFields: TStrings;
TableObject: Pointer;
end;
PMyTable = ^TMyTable;
TTableDesigner = class(TCustomControl)
procedure CreateWnd; override;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure LblOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure LblDragDrop(Sender, Source: TObject; X, Y: Integer);
procedure LblDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
procedure LblEndDrag(Sender, Target: TObject; X, Y: Integer);
procedure LblStartDrag(Sender: TObject; var DragObject: TDragObject);
// procedure Paint; override;
private
{ Private declarations }
FTableList: TList;
FCaptionPanelList: TList;
FPanelSlot_Left: Integer;
FPanelSlot_Top: Integer;
FStartDragPnl: TJvCaptionPanel;
FDragHoverPnl: TJvCaptionPanel;
FEndDragPnl: TJvCaptionPanel;
procedure HighlightPanelLabel(ALabel: TLabel);
protected
{ Protected declarations }
public
{ Public declarations }
procedure AddTable(const ATableName: String; const AFields: TStrings);
procedure DeleteTable(const ATableName: String);
procedure DeleteAllTables;
published
{ Published declarations }
property Align;
property Visible;
property Color;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TTableDesigner]);
end;
constructor TTableDesigner.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FTableList := TList.Create;
FCaptionPanelList := TList.Create;
FPanelSlot_Left := 40;
FPanelSlot_Top := 40;
end;
destructor TTableDesigner.Destroy;
begin
DeleteAllTables;
FTableList.Free;
FCaptionPanelList.Free;
inherited;
end;
procedure TTableDesigner.CreateWnd;
begin
inherited;
end;
procedure TTableDesigner.AddTable(const ATableName: String; const AFields: TStrings);
var
pnl: TJvCaptionPanel;
c, h, j: Integer;
lbl: TLabel;
MyTable: PMyTable;
begin
pnl := TJvCaptionPanel.Create(Self);
pnl.Parent := Self;
pnl.Color := clWhite;
pnl.Caption := ATableName;
pnl.CaptionPosition := dpTop;
pnl.Left := FPanelSlot_Left;
pnl.Top := FPanelSlot_Top;
// FPanelSlot_Left := FPanelSlot_Left + pnl.Width + 40;
// if FPanelSlot_Left > ClientWidth - 100 then
// begin
// FPanelSlot_Left := 40;
//
// j := 0;
// for c := 0 to FTableList.Count - 1 do
// if j < TJvCaptionPanel(TMyTable(FTableList.Items[c]^).TableObject).Height then
// j := TJvCaptionPanel(TMyTable(FTableList.Items[c]^).TableObject).Height;
//
// FPanelSlot_Top := FPanelSlot_Top + j + 40;
// end;
h := 0;
for c := 0 to AFields.Count - 1 do
begin
lbl := TLabel.Create(pnl);
lbl.Parent := pnl;
lbl.Align := alTop;
lbl.Caption := AFields[c];
lbl.Transparent := False;
lbl.ParentColor := False;
lbl.DragKind := dkDrag;
lbl.OnMouseDown := LblOnMouseDown;
lbl.OnDragDrop := LblDragDrop;
lbl.OnDragOver := LblDragOver;
lbl.OnEndDrag := LblEndDrag;
lbl.OnStartDrag := LblStartDrag;
// lbl.DragMode := dmAutomatic;
h := h + lbl.Height + 4;
end;
pnl.ClientHeight := pnl.CaptionHeight + h;
MyTable := AllocMem(SizeOf(TMyTable));
Initialize(MyTable^);
MyTable.TableName := ATableName;
MyTable.TableFields := TStringList.Create;
MyTable.TableFields.Assign(AFields);
MyTable.TableObject := pnl;
FTableList.Add(MyTable);
end;
procedure TTableDesigner.DeleteTable(const ATableName: String);
var
c: Integer;
begin
for c := 0 to FTableList.Count - 1 do
if TMyTable(FTableList.Items[c]^).TableName = ATableName then
begin
TJvCaptionPanel(TMyTable(FTableList.Items[c]^).TableObject).Free;
TMyTable(FTableList.Items[c]^).TableFields.Free;
Finalize(TMyTable(FTableList.Items[c]^));
FreeMem(FTableList.Items[c]);
FTableList.Delete(c);
Break;
end;
end;
procedure TTableDesigner.DeleteAllTables;
var
c: Integer;
begin
for c := FTableList.Count - 1 downto 0 do
begin
TJvCaptionPanel(TMyTable(FTableList.Items[c]^).TableObject).Free;
TMyTable(FTableList.Items[c]^).TableFields.Free;
Finalize(TMyTable(FTableList.Items[c]^));
FreeMem(FTableList.Items[c]);
FTableList.Delete(c);
end;
end;
procedure TTableDesigner.HighlightPanelLabel(ALabel: TLabel);
var
pnl: TJvCaptionPanel;
c: Integer;
begin
pnl := TJvCaptionPanel(ALabel.Parent);
for c := 0 to pnl.ControlCount - 1 do
if pnl.Controls[c] = ALabel then
TLabel(pnl.Controls[c]).Color := clHighlight
else
TLabel(pnl.Controls[c]).Color := pnl.Color;
end;
procedure TTableDesigner.LblOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
HighlightPanelLabel(TLabel(Sender));
BeginDrag(False, 4);
end;
procedure TTableDesigner.LblDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
FEndDragPnl := TJvCaptionPanel(TLabel(Sender).Parent);
FEndDragPnl.Color := clWhite;
end;
procedure TTableDesigner.LblDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
FDragHoverPnl := TJvCaptionPanel(TLabel(Sender).Parent);
FDragHoverPnl.Color := clGreen;
Accept := True;
end;
procedure TTableDesigner.LblEndDrag(Sender, Target: TObject; X, Y: Integer);
begin
TJvCaptionPanel(TLabel(Sender).Parent).Color := clPurple;
end;
procedure TTableDesigner.LblStartDrag(Sender: TObject; var DragObject: TDragObject);
begin
FStartDragPnl := TJvCaptionPanel(TLabel(Sender).Parent);
FStartDragPnl.Color := clRed;
end;
// procedure TTableDesigner.Paint;
// var
// c: Integer;
// begin
// inherited;
//
// // Canvas.Pen.Mode := pmBlack;
// // Canvas.Pen.Color := clBlack;
// // Canvas.Pen.Style := psSolid;
// // Canvas.Pen.Width := 1;
// // Canvas.MoveTo(50, 50);
// // Canvas.LineTo(500, 500);
//
// end;
end.
You're in a method of 'TTableDesigner', if you do not qualify a method 'Self' is implied. So the 'BeginDrag' call applies to the TableDesigner object.
You'd rather call 'TLabel(Sender).BeginDrag(..'.
I'm trying to make a cropping tool that will look as follow:
Original Image:
Crop tool - This is what I want:
Notice that the cropping area is showing the original colors, and around the colors are dim.
What I did is to place a TShape over my TImage with properties:
object Shape1: TShape
Brush.Color = clSilver
Pen.Mode = pmMask
Pen.Style = psDot
end
I plan to use the TShape to make the re-sizing/coping control.
This is how it looks in Delphi:
As you can see, it does not looks good (colors palette looks dithered), but the main problem that I need the dim area to be around the crop area, not in the center. I have tried to cover the whole TImage with another TShpae, tried different Pen.Mode combinations but there are no good results, and I think my method/approach is bad.
Do you have any ideas on how to achieve the desired behavior?
a little part is missing here, but should not be a problem to add...
unit Unit3;
// 20121108 by Thomas Wassermann
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, jpeg;
type
TForm3 = class(TForm)
Image1: TImage;
PaintBox1: TPaintBox;
procedure FormCreate(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private-Deklarationen }
FDownPoint, FCurrentPoint: TPoint;
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
uses Math;
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
begin
PaintBox1.BringToFront;
end;
type
pRGBQuadArray = ^TRGBQuadArray;
TRGBQuadArray = ARRAY [0 .. $EFFFFFF] OF TRGBQuad;
Procedure SetAlpha(bmp: TBitMap; Alpha: Byte; R: TRect);
var
pscanLine32: pRGBQuadArray;
i, j: Integer;
begin
bmp.PixelFormat := pf32Bit;
bmp.HandleType := bmDIB;
bmp.ignorepalette := true;
bmp.alphaformat := afDefined;
for i := 0 to bmp.Height - 1 do
begin
pscanLine32 := bmp.Scanline[i];
for j := 0 to bmp.Width - 1 do
begin
if (j >= R.Left) and (j <= R.Right) and (i >= R.Top) and (i <= R.Bottom) then
begin
pscanLine32[j].rgbReserved := 0;
pscanLine32[j].rgbBlue := 0;
pscanLine32[j].rgbRed := 0;
pscanLine32[j].rgbGreen := 0;
end
else
begin
pscanLine32[j].rgbReserved := Alpha;
pscanLine32[j].rgbBlue := Alpha;
pscanLine32[j].rgbRed := Alpha;
pscanLine32[j].rgbGreen := Alpha;
end;
end;
end;
end;
procedure TForm3.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FDownPoint.X := X;
FDownPoint.Y := Y;
FCurrentPoint := FDownPoint;
PaintBox1.Invalidate;
end;
procedure TForm3.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if ssLeft in Shift then
begin
FCurrentPoint.X := X;
FCurrentPoint.Y := Y;
PaintBox1.Invalidate;
end;
end;
procedure TForm3.PaintBox1Paint(Sender: TObject);
var
bmp: TBitMap;
SelRect: TRect;
begin
bmp := TBitMap.Create;
try
bmp.Width := PaintBox1.Width;
bmp.Height := PaintBox1.Height;
if (FCurrentPoint.X = FDownPoint.X) and (FCurrentPoint.Y = FDownPoint.Y) then
SelRect := PaintBox1.BoundsRect
else
begin
SelRect.Left := Min(FCurrentPoint.X, FDownPoint.X);
SelRect.Top := Min(FCurrentPoint.Y, FDownPoint.Y);
SelRect.Right := Max(FCurrentPoint.X, FDownPoint.X);
SelRect.Bottom := Max(FCurrentPoint.Y, FDownPoint.Y);
end;
SetAlpha(bmp, 140, SelRect);
PaintBox1.Canvas.Draw(0, 0, bmp);
finally
bmp.Free;
end;
end;
end.
The attempt on this solution is to use a overlying paintbox, same clientrect as the image, for all the drawing and selection. By using the coordinates generated by mouse/down/move a semitransparent bitmap is created, which is full transparent in the selected rect. After generation it's painted on the paintbox. Further paintings could be done there e.g. frames, anchors, crosshair. Any user action would have to be caught in mousedown, depending of the selected part ,e.g. an anchor a sizing of the rect could be done.
Usually I'd prefer GDI+ for requests like this, but as shown, no additional units are required. Source: http://www.bummisoft.de/download/transparenteauswahl.zip
Is it possible to Alpha Blend or implement a similar effect for a VCL control on a TForm?
For example, consider the following screenshot where two TPanels are placed on a TForm in addition to other controls. Both the panels are made draggable (See How to Move and Resize Controls at Run Time).
Now, is it possible to make these panels translucent while dragging so that you can see what is underneath? (as shown in the second image which was produced by image manipulation)
The VCL gives you the opportunity to specify a drag image list to be used during drag-and-drop, here's a quick example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TPanel = class(Vcl.ExtCtrls.TPanel)
protected
function GetDragImages: TDragImageList; override;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
CheckBox1: TCheckBox;
Edit1: TEdit;
Label1: TLabel;
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
procedure PanelStartDrag(Sender: TObject; var DragObject: TDragObject);
procedure PanelEndDrag(Sender, Target: TObject; X, Y: Integer);
private
FDragImages: TDragImageList;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TPanel }
function TPanel.GetDragImages: TDragImageList;
begin
Result := (Owner as TForm1).FDragImages;
end;
type
TControlProc = reference to procedure(Control: TControl);
procedure IterateControls(Control: TControl; Proc: TControlProc);
var
I: Integer;
begin
if Assigned(Control) then
Proc(Control);
if Control is TWinControl then
for I := 0 to TWinControl(Control).ControlCount - 1 do
IterateControls(TWinControl(Control).Controls[I], Proc);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDragImages := nil;
// set display drag image style
IterateControls(Self,
procedure(Control: TControl)
begin
Control.ControlStyle := Control.ControlStyle + [csDisplayDragImage];
end
);
end;
procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
Accept := Source is TPanel;
end;
procedure TForm1.PanelEndDrag(Sender, Target: TObject; X, Y: Integer);
begin
FreeAndNil(FDragImages);
end;
procedure TForm1.PanelStartDrag(Sender: TObject; var DragObject: TDragObject);
var
Image: TBitmap;
begin
if not (Sender is TPanel) then
Exit;
Image := TBitmap.Create;
try
Image.PixelFormat := pf32bit;
Image.Width := TControl(Sender).Width;
Image.Height := TControl(Sender).Height;
TPanel(Sender).PaintTo(Image.Canvas, 0, 0);
FDragImages := TDragImageList.Create(nil);
FDragImages.Width := Image.Width;
FDragImages.Height := Image.Height;
FDragImages.SetDragImage(FDragImages.Add(Image, nil), 0, 0);
FDragImages.ShowDragImage;
except
Image.Free;
FreeAndNil(FDragImages);
raise;
end;
end;
end.
You can do this in Delphi, too. The basic idea is to place the control into an autosized, borderles form with alpha blending enabled.
According to the article you linked to, in the MouseDown event add the following lines:
P := TWinControl(Sender).ClientToScreen(Point(0,0));
frm := TForm.Create(nil);
TWinControl(Sender).Parent := frm;
frm.BorderStyle := bsNone;
frm.AlphaBlend := true;
frm.AlphaBlendValue := 128;
frm.AutoSize := true;
frm.Left := P.X;
frm.Top := P.Y;
frm.Position := poDesigned;
frm.Show;
In the MouseMove event set the Left and Top properties of the controls parent:
GetCursorPos(newPos);
Screen.Cursor := crSize;
Parent.Left := Parent.Left - oldPos.X + newPos.X;
Parent.Top := Parent.Top - oldPos.Y + newPos.Y;
oldPos := newPos;
and in the MouseUp event release the form, set the controls parent back to the original parent and translate the screen position to the new position relative to it:
frm := TWinControl(Sender).Parent;
P := Point(frm.Left, frm.Top);
P := ScreenToClient(P);
TWinControl(Sender).Parent := Self;
TWinControl(Sender).Left := P.X;
TWinControl(Sender).Top := P.Y;
frm.Free;
Screen.Cursor := crDefault;
ReleaseCapture;
inReposition := False;
To implement a drag operation displaying the image of the control, you must create a TDragControlObject descendent and implement the GetDragImages method, from here you must ensure to add the csDisplayDragImage value to the ControlStyle property of the controls to drag.
You can find a very good article about this topic here Implementing Professional Drag & Drop In VCL/CLX Applications
How can I implement a close button for a TTabsheet of a TPageControl like Firefox?
Edit:
Delphi Version: Delphi 2010
OS: Windows XP and up
Now with Theme support (include Windows, UxTheme, Themes units)!
type
TFormMain = class(TForm)
{...}
private
FCloseButtonsRect: array of TRect;
FCloseButtonMouseDownIndex: Integer;
FCloseButtonShowPushed: Boolean;
{...}
end;
{...}
procedure TFormMain.FormCreate(Sender: TObject);
var
I: Integer;
begin
PageControlCloseButton.TabWidth := 150;
PageControlCloseButton.OwnerDraw := True;
//should be done on every change of the page count
SetLength(FCloseButtonsRect, PageControlCloseButton.PageCount);
FCloseButtonMouseDownIndex := -1;
for I := 0 to Length(FCloseButtonsRect) - 1 do
begin
FCloseButtonsRect[I] := Rect(0, 0, 0, 0);
end;
end;
procedure TFormMain.PageControlCloseButtonDrawTab(Control: TCustomTabControl;
TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
CloseBtnSize: Integer;
PageControl: TPageControl;
TabCaption: TPoint;
CloseBtnRect: TRect;
CloseBtnDrawState: Cardinal;
CloseBtnDrawDetails: TThemedElementDetails;
begin
PageControl := Control as TPageControl;
if InRange(TabIndex, 0, Length(FCloseButtonsRect) - 1) then
begin
CloseBtnSize := 14;
TabCaption.Y := Rect.Top + 3;
if Active then
begin
CloseBtnRect.Top := Rect.Top + 4;
CloseBtnRect.Right := Rect.Right - 5;
TabCaption.X := Rect.Left + 6;
end
else
begin
CloseBtnRect.Top := Rect.Top + 3;
CloseBtnRect.Right := Rect.Right - 5;
TabCaption.X := Rect.Left + 3;
end;
CloseBtnRect.Bottom := CloseBtnRect.Top + CloseBtnSize;
CloseBtnRect.Left := CloseBtnRect.Right - CloseBtnSize;
FCloseButtonsRect[TabIndex] := CloseBtnRect;
PageControl.Canvas.FillRect(Rect);
PageControl.Canvas.TextOut(TabCaption.X, TabCaption.Y, PageControl.Pages[TabIndex].Caption);
if not UseThemes then
begin
if (FCloseButtonMouseDownIndex = TabIndex) and FCloseButtonShowPushed then
CloseBtnDrawState := DFCS_CAPTIONCLOSE + DFCS_PUSHED
else
CloseBtnDrawState := DFCS_CAPTIONCLOSE;
Windows.DrawFrameControl(PageControl.Canvas.Handle,
FCloseButtonsRect[TabIndex], DFC_CAPTION, CloseBtnDrawState);
end
else
begin
Dec(FCloseButtonsRect[TabIndex].Left);
if (FCloseButtonMouseDownIndex = TabIndex) and FCloseButtonShowPushed then
CloseBtnDrawDetails := ThemeServices.GetElementDetails(twCloseButtonPushed)
else
CloseBtnDrawDetails := ThemeServices.GetElementDetails(twCloseButtonNormal);
ThemeServices.DrawElement(PageControl.Canvas.Handle, CloseBtnDrawDetails,
FCloseButtonsRect[TabIndex]);
end;
end;
end;
procedure TFormMain.PageControlCloseButtonMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
I: Integer;
PageControl: TPageControl;
begin
PageControl := Sender as TPageControl;
if Button = mbLeft then
begin
for I := 0 to Length(FCloseButtonsRect) - 1 do
begin
if PtInRect(FCloseButtonsRect[I], Point(X, Y)) then
begin
FCloseButtonMouseDownIndex := I;
FCloseButtonShowPushed := True;
PageControl.Repaint;
end;
end;
end;
end;
procedure TFormMain.PageControlCloseButtonMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
var
PageControl: TPageControl;
Inside: Boolean;
begin
PageControl := Sender as TPageControl;
if (ssLeft in Shift) and (FCloseButtonMouseDownIndex >= 0) then
begin
Inside := PtInRect(FCloseButtonsRect[FCloseButtonMouseDownIndex], Point(X, Y));
if FCloseButtonShowPushed <> Inside then
begin
FCloseButtonShowPushed := Inside;
PageControl.Repaint;
end;
end;
end;
procedure TFormMain.PageControlCloseButtonMouseLeave(Sender: TObject);
var
PageControl: TPageControl;
begin
PageControl := Sender as TPageControl;
FCloseButtonShowPushed := False;
PageControl.Repaint;
end;
procedure TFormMain.PageControlCloseButtonMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
PageControl: TPageControl;
begin
PageControl := Sender as TPageControl;
if (Button = mbLeft) and (FCloseButtonMouseDownIndex >= 0) then
begin
if PtInRect(FCloseButtonsRect[FCloseButtonMouseDownIndex], Point(X, Y)) then
begin
ShowMessage('Button ' + IntToStr(FCloseButtonMouseDownIndex + 1) + ' pressed!');
FCloseButtonMouseDownIndex := -1;
PageControl.Repaint;
end;
end;
end;
Looks like:
It's often a good idea to implement this yourself, as the other answers have suggested. Just in case you are already using Raize Components, though, this feature is supported "out of the box". Just set TRzPageControl.ShowCloseButtonOnActiveTab := true, and handle the OnClose event. The component takes care of placement for a variety of tab layouts/orientations/shapes/colors.
[just a happy customer]
What I have done in the past is just put a TBitBtn with a graphic in the upper right hand corner of the TPageControl. The trick i the parent of the TBitBtn is the same as the TPageControl, so it isn't actually on one of the tab sheets. Then in the click even for that button:
PageControl1.ActivePage.Free;
When the current TTabControl is freed it notifies the TPageControl that owns it.
I have changed a little this example:
- created class TCloseTabSheet
- this class has property OnClose: TNotifyEvent, which will be called if assigned
- if TabSheet of of TPageControl isn't that class then there is no close button
- if it is then Button showed. When you press close button it calls OnClose
- now you dont need to control the array FCloseButtonsRect, cause this Rects stored at TCloseTabSheet
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, Themes, Math, ExtCtrls, StdCtrls;
type TCloseTabSheet=class(TTabSheet)
private
protected
FCloseButtonRect: TRect;
FOnClose: TNotifyEvent;
procedure DoClose; virtual;
public
constructor Create(AOwner:TComponent); override;
destructor Destroy; override;
property OnClose:TNotifyEvent read FOnClose write FOnClose;
end;
type
TMainForm = class(TForm)
PageControlCloseButton: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
TabSheet3: TTabSheet;
procedure FormCreate(Sender: TObject);
procedure PageControlCloseButtonDrawTab(Control: TCustomTabControl; TabIndex: Integer;
const Rect: TRect; Active: Boolean);
procedure PageControlCloseButtonMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure PageControlCloseButtonMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
procedure PageControlCloseButtonMouseLeave(Sender: TObject);
procedure PageControlCloseButtonMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure CloseTabeProc(Sender: TObject);
private
FCloseButtonMouseDownTab: TCloseTabSheet;
FCloseButtonShowPushed: Boolean;
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
constructor TCloseTabSheet.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
FCloseButtonRect:=Rect(0, 0, 0, 0);
end;
destructor TCloseTabSheet.Destroy;
begin
inherited Destroy;
end;
procedure TCloseTabSheet.DoClose;
begin
if Assigned(FOnClose) then FOnClose(Self);
Free;
end;
procedure TMainForm.CloseTabeProc(Sender: TObject);
begin
ShowMessage('close');
end;
procedure TMainForm.FormCreate(Sender: TObject);
var I: Integer;
NT:TCloseTabSheet;
begin
PageControlCloseButton.TabWidth := 150;
PageControlCloseButton.OwnerDraw := True;
NT:=TCloseTabSheet.Create(PageControlCloseButton);
NT.Caption:='TabSheet4';
NT.PageControl:=PageControlCloseButton;
NT.OnClose:=CloseTabeProc;
FCloseButtonMouseDownTab := nil;
end;
procedure TMainForm.PageControlCloseButtonDrawTab(Control: TCustomTabControl;
TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
CloseBtnSize: Integer;
PageControl: TPageControl;
TabSheet:TCloseTabSheet;
TabCaption: TPoint;
CloseBtnRect: TRect;
CloseBtnDrawState: Cardinal;
CloseBtnDrawDetails: TThemedElementDetails;
begin
PageControl := Control as TPageControl;
TabCaption.Y := Rect.Top + 3;
if Active then
begin
CloseBtnRect.Top := Rect.Top + 4;
CloseBtnRect.Right := Rect.Right - 5;
TabCaption.X := Rect.Left + 6;
end
else
begin
CloseBtnRect.Top := Rect.Top + 3;
CloseBtnRect.Right := Rect.Right - 5;
TabCaption.X := Rect.Left + 3;
end;
if PageControl.Pages[TabIndex] is TCloseTabSheet then
begin
TabSheet:=PageControl.Pages[TabIndex] as TCloseTabSheet;
CloseBtnSize := 14;
CloseBtnRect.Bottom := CloseBtnRect.Top + CloseBtnSize;
CloseBtnRect.Left := CloseBtnRect.Right - CloseBtnSize;
TabSheet.FCloseButtonRect := CloseBtnRect;
PageControl.Canvas.FillRect(Rect);
PageControl.Canvas.TextOut(TabCaption.X, TabCaption.Y,
PageControl.Pages[TabIndex].Caption);
if not ThemeServices.ThemesEnabled then
begin
if (FCloseButtonMouseDownTab = TabSheet) and FCloseButtonShowPushed then
CloseBtnDrawState := DFCS_CAPTIONCLOSE + DFCS_PUSHED
else
CloseBtnDrawState := DFCS_CAPTIONCLOSE;
Windows.DrawFrameControl(PageControl.Canvas.Handle,
TabSheet.FCloseButtonRect, DFC_CAPTION, CloseBtnDrawState);
end
else
begin
Dec(TabSheet.FCloseButtonRect.Left);
if (FCloseButtonMouseDownTab = TabSheet) and FCloseButtonShowPushed then
CloseBtnDrawDetails := ThemeServices.GetElementDetails(twCloseButtonPushed)
else
CloseBtnDrawDetails := ThemeServices.GetElementDetails(twCloseButtonNormal);
ThemeServices.DrawElement(PageControl.Canvas.Handle, CloseBtnDrawDetails,
TabSheet.FCloseButtonRect);
end;
end else begin
PageControl.Canvas.FillRect(Rect);
PageControl.Canvas.TextOut(TabCaption.X, TabCaption.Y,
PageControl.Pages[TabIndex].Caption);
end;
end;
procedure TMainForm.PageControlCloseButtonMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
I: Integer;
PageControl: TPageControl;
TabSheet:TCloseTabSheet;
begin
PageControl := Sender as TPageControl;
if Button = mbLeft then
begin
for I := 0 to PageControl.PageCount - 1 do
begin
if not (PageControl.Pages[i] is TCloseTabSheet) then Continue;
TabSheet:=PageControl.Pages[i] as TCloseTabSheet;
if PtInRect(TabSheet.FCloseButtonRect, Point(X, Y)) then
begin
FCloseButtonMouseDownTab := TabSheet;
FCloseButtonShowPushed := True;
PageControl.Repaint;
end;
end;
end;
end;
procedure TMainForm.PageControlCloseButtonMouseLeave(Sender: TObject);
var
PageControl: TPageControl;
begin
PageControl := Sender as TPageControl;
FCloseButtonShowPushed := False;
PageControl.Repaint;
end;
procedure TMainForm.PageControlCloseButtonMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
var
PageControl: TPageControl;
Inside: Boolean;
begin
PageControl := Sender as TPageControl;
if (ssLeft in Shift) and Assigned(FCloseButtonMouseDownTab) then
begin
Inside := PtInRect(FCloseButtonMouseDownTab.FCloseButtonRect, Point(X, Y));
if FCloseButtonShowPushed <> Inside then
begin
FCloseButtonShowPushed := Inside;
PageControl.Repaint;
end;
end;
end;
procedure TMainForm.PageControlCloseButtonMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
PageControl: TPageControl;
begin
PageControl := Sender as TPageControl;
if (Button = mbLeft) and Assigned(FCloseButtonMouseDownTab) then
begin
if PtInRect(FCloseButtonMouseDownTab.FCloseButtonRect, Point(X, Y)) then
begin
FCloseButtonMouseDownTab.DoClose;
FCloseButtonMouseDownTab := nil;
PageControl.Repaint;
end;
end;
end;
end.