I haven't attempted this because I wouldn't know where to begin.
Is it possible to add a FMX TSwitch into a FMX TListViewitem?
Any help/suggestions would be much appreciated.
Thanks,
You first have to keep in mind the whole design of the TListView control. It's meant to be very lightweight for when it contains a large number of items. You may have a million items, you surely don't want a million switch controls instantiated. Therefore, it's not meant for you to embed controls in each item as a container, such as the TListBox allows.
That being said, it's assumed that you perform minimal drawing on each individual list item to be consistent with the design of the TListView. This requires creating virtual objects inherited from TListItemObject to be associated with each item. These objects are what allow the existing built-in elements of any item, such as the accessory or bitmap.
Here's a very rough demo I threw together to get you started, you'd need to change the drawing how you need it to look.
Start a new FMX application, drop a TListView, and use this unit in place of your main form's unit:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
FMX.ListView, FMX.Controls.Presentation, FMX.StdCtrls;
type
TListItemSwitch = class(TListItemSimpleControl)
private
FIsChecked: Boolean;
FOnSwitch: TNotifyEvent;
procedure SetIsChecked(const AValue: Boolean);
protected
function MouseDown(const Button: TMouseButton; const Shift: TShiftState; const MousePos: TPointF): Boolean;
override;
procedure DoSwitch; virtual;
public
constructor Create(const AOwner: TListItem); override;
destructor Destroy; override;
procedure Render(const Canvas: TCanvas; const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates;
const SubPassNo: Integer = 0); override;
public
property IsChecked: Boolean read FIsChecked write SetIsChecked;
property OnSwitch: TNotifyEvent read FOnSwitch write FOnSwitch;
end;
TForm1 = class(TForm)
ListView1: TListView;
procedure ListView1UpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{ TListItemSwitch }
constructor TListItemSwitch.Create(const AOwner: TListItem);
begin
inherited;
end;
destructor TListItemSwitch.Destroy;
begin
inherited;
end;
procedure TListItemSwitch.DoSwitch;
begin
FIsChecked:= not FIsChecked;
if Assigned(OnSwitch) then
OnSwitch(Self);
end;
function TListItemSwitch.MouseDown(const Button: TMouseButton;
const Shift: TShiftState; const MousePos: TPointF): Boolean;
begin
if (Button = TMouseButton.mbLeft) and Enabled then begin
DoSwitch;
end;
inherited;
end;
procedure TListItemSwitch.Render(const Canvas: TCanvas;
const DrawItemIndex: Integer; const DrawStates: TListItemDrawStates;
const SubPassNo: Integer);
var
R, R2: TRectF;
begin
inherited;
R:= Self.LocalRect;
R2:= R;
Canvas.BeginScene;
try
Canvas.Stroke.Kind:= TBrushKind.None;
Canvas.Fill.Kind:= TBrushKind.Solid;
Canvas.Fill.Color:= TAlphaColorRec.Skyblue;
Canvas.FillRect(R, 8, 8,
[TCorner.TopLeft, TCorner.TopRight, TCorner.BottomLeft, TCorner.BottomRight],
1.0, TCornerType.Round);
if IsChecked then begin
R2.Left:= R.Right - 20;
R2.Width:= 20;
end else begin
R2.Left:= R.Left;
R2.Width:= 20;
end;
Canvas.Fill.Color:= TAlphaColorRec.Black;
Canvas.FillRect(R2, 8, 8,
[TCorner.TopLeft, TCorner.TopRight, TCorner.BottomLeft, TCorner.BottomRight],
1.0, TCornerType.Round);
finally
Canvas.EndScene;
end;
end;
procedure TListItemSwitch.SetIsChecked(const AValue: Boolean);
begin
FIsChecked:= AValue;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
var
I: TListViewItem;
begin
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
I:= ListView1.Items.Add;
end;
procedure TForm1.ListView1UpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
var
S: TListItemSwitch;
begin
S:= AItem.Objects.FindObject('Switch') as TListItemSwitch;
if S = nil then begin
S:= TListItemSwitch.Create(AItem);
S.Name:= 'Switch';
S.Align:= TListItemAlign.Trailing;
S.VertAlign:= TListItemAlign.Center;
S.Width:= 50;
S.Height:= 20;
S.IsChecked:= False;
end;
end;
end.
NOTE: This was written in Delphi 10 Seattle.
Your only other options I believe are to either:
Instantiate a TSwitch for each item and render it using the same method as above (Very sloppy, I do not recommend)
Figure out how to implement the drawing of the standard TSwitch using styles, again using the same method as above (which is probably the best option for performance and visual adaption)
Resort to a TListBox instead, depending on how you intend to use the list (which would be very heavy on a large number of items)
I went a little more in-depth about the differences between a TListView and a TListBox in Firemonkey in a separate question / answer.
Related
I'm Running Delphi Dx Seattle
In Delphi VCL's TPageControl there is a onChanging event where you could stop the page control from changing tab's
procedure TForm1.pgc1Changing(Sender: TObject; var AllowChange: Boolean);
begin
if MessageDlg('do you want to change tab?', mtConfirmation, [mbyes, mbno], 0, mbNo) = mrno then
AllowChange := false;
end;
Is there a way for Delphi Firemonkey TTabControl to replicate this?
e.g.if you have two tabs and clicking on the other tab pops up a question 'do you want to change tab?'
if you click No nothing happens click yes then it changes
This is the code i ended up using - it works with windows and android haven't tested with IOS
Ended up having to override some of the components procedures
TTabItem = class(FMX.TabControl.TTabItem)
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
end;
TTabControl = class(FMX.TabControl.TTabControl)
function GetTabIndex : integer;
public
procedure SetTabIndexv2(const Value: Integer);
property TabIndex: Integer read GetTabIndex write SetTabIndexv2 default -1;
end;
Here is an example of the full code
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TabControl,
FMX.Controls.Presentation, FMX.StdCtrls;
type
TTabItem = class(FMX.TabControl.TTabItem)
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
end;
TTabControl = class(FMX.TabControl.TTabControl)
function GetTabIndex : integer;
public
procedure SetTabIndexv2(const Value: Integer);
property TabIndex: Integer read GetTabIndex write SetTabIndexv2 default -1;
end;
TForm1 = class(TForm)
tbc1: TTabControl;
tbtm1: TTabItem;
tbtm2: TTabItem;
btn1: TButton;
lblTab1: TLabel;
lblTab2: TLabel;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TTabItem.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Single);
begin
if (self.TabControl.ActiveTab <> self) and
((Button = TMouseButton.mbLeft) or (ssDouble in Shift)) then begin
MessageDlg('[Tab Item] do you want to do this?', System.UITypes.TMsgDlgType.mtInformation,
[System.UITypes.TMsgDlgBtn.mbYes, System.UITypes.TMsgDlgBtn.mbNo], 0, procedure (const AResult: TModalResult)
begin
begin
case AResult of
mrYes: self.TabControl.ActiveTab := self;
mrNo:;
end;
end;
end);
end else begin
inherited;
end;
end;
procedure TForm1.btn1Click(Sender: TObject);
begin
if tbc1.TabIndex = 0 then
tbc1.TabIndex := 1
else
tbc1.TabIndex := 0;
end;
{ TTabControl }
function TTabControl.GetTabIndex: integer;
begin
result := FMX.TabControl.TTabControl(Self).TabIndex;
end;
procedure TTabControl.SetTabIndexv2(const Value: Integer);
begin
if self.TabIndex <> value then begin
MessageDlg('[tabcontrol] do you want to do this?', System.UITypes.TMsgDlgType.mtInformation,
[System.UITypes.TMsgDlgBtn.mbYes, System.UITypes.TMsgDlgBtn.mbNo], 0, procedure (const AResult: TModalResult)
begin
begin
case AResult of
mrYes: begin
FMX.TabControl.TTabControl(Self).TabIndex := value;
end;
mrNo : ;
end;
end;
end);
end;
end;
end.
if you can see any way to improve it please let me know
I need to add a button (maybe TSpeedButton?) on every item of ComboBox. When one clicks the button the corresponding item is deleted from the list. For example:
I've seen similar discussion on SpeedButtons in string grids (here: TStringGrid with SpeedButtons), but I don't know how to implement all those things on ComboBox. Could you please give me some advice or links for further reading on the topic.
Besides the user experience comments aside, to which I agree, a solution to the question isn't really that hard.
You can do this by setting the Style property to csOwnerDrawFixed, drawing the items yourself in the OnDrawItem event, and deleting the selected item in the OnSelect event for example, as follows:
unit Unit1;
interface
uses
Winapi.Windows, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls,
Vcl.Imaging.PNGIMage;
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure ComboBox1Select(Sender: TObject);
private
FDeleteGraphic: TPNGImage;
FDeleteRect: TRect;
end;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
begin
ComboBox1.Canvas.FillRect(Rect);
if Index >= 0 then
ComboBox1.Canvas.TextOut(Rect.Left + 2, Rect.Top, ComboBox1.Items[Index]);
if (odSelected in State) and not (odComboBoxEdit in State) then
begin
FDeleteRect := Rect;
FDeleteRect.Left := FDeleteRect.Right - FDeleteGraphic.Width;
ComboBox1.Canvas.Draw(FDeleteRect.Left, FDeleteRect.Top, FDeleteGraphic);
end;
end;
procedure TForm1.ComboBox1Select(Sender: TObject);
var
MousePos: TPoint;
begin
MousePos := ComboBox1.ScreenToClient(Mouse.CursorPos);
MousePos.Offset(0, -ComboBox1.Height);
if PtInRect(FDeleteRect, MousePos) then
begin
ComboBox1.Items.Delete(ComboBox1.ItemIndex);
ComboBox1.Invalidate;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDeleteGraphic := TPNGImage.Create;
FDeleteGraphic.LoadFromFile('H:\Icons\FamFam Common\Delete.png');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FDeleteGraphic.Free;
end;
end.
With this result:
You might want to (re)store the previous ItemIndex setting. Customize to your wishes.
I'm using TeeChart with Delphi XE5 and utilizing the BubbleSeries component to show X/Y/Radius bubbles in a chart.
I'm building the chart using an list of objects that I have, calculating X/Y/Radius values for these objects on the fly and inserting them using the TBubbleSeries.AddBubble method.
The problem is when I want to perform some action on the objects when the corresponding bubble is hovered/clicked/etc. I use the TChartSeries.Clicked method to find out which bubble is clicked, but the index I get returned is only usable for finding out the xy/radius values of the bubble, not which object originated it.
Maybe I'm missing something simple, because this seems to be something that any charting library should handle easily. I tried using the returned index from AddBubble method, but this index is only valid until another call to AddBubble is performed, at which point, the internal list seems to be re-ordered.
Edit: Was asked for some code, here it is!
procedure TBubbleReportForm.ChartMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
Device: TDevice;
begin
Index := BubbleSeries.Clicked(X,Y);
if Index = -1 then
begin
BubbleChart.ShowHint := False;
Exit;
end;
// This does not work as indexing does seems to correspond to the order which the bubbles was added.
Device := FDevices[Index];
BubbleChart.Hint := Device.Name;
BubbleChart.ShowHint := True;
end;
procedure TBubbleReportForm.FormCreate(Sender: TObject);
var
Device: TDevice;
begin
BubbleChart.OnMouseMove := ChartMouseMove;
// FDevices is of TObjectList type.
for Device in FDevices do
begin
BubbleSeries.AddBubble(Device.CalculateXVal,Device.CalculateYVal,Device.CalculateRadius);
end;
end;
I would use a a Generic TObjectList. Or an descendant og a TObjectList.
First Iimpelment your BoubleObject, and a list of them. In the following example I've just used a dummy implementation:
unit BubbleU;
interface
uses
System.Generics.Collections, System.SysUtils, Vcl.Graphics;
{$M+}
type
TBubble = class
private
FX: Double;
FRadius: Double;
FY: Double;
FLabelText: String;
FColor: TColor;
FIndex: Integer;
FChartIndex: Integer;
procedure SetChartIndex(const Value: Integer);
protected
procedure DoCalculation;
public
constructor Create(aIndex: Integer); reintroduce;
published
property X: Double read FX;
property Y: Double read FY;
property Radius: Double read FRadius;
property LabelText: String read FLabelText;
property Color: TColor read FColor;
property ChartIndex: Integer read FChartIndex write SetChartIndex;
end;
TBubbleList = class(TObjectList<TBubble>)
public
function ElementFormChartIndex(ChartIndex: Integer): TBubble; overload;
end;
implementation
{ TBubble }
constructor TBubble.Create(aIndex: Integer);
begin
inherited Create;
FIndex := aIndex;
DoCalculation;
end;
procedure TBubble.DoCalculation;
begin
FX := FIndex;
FY := FIndex;
FRadius := 1;
FColor := clRed;
FLabelText := 'Index: ' + FIndex.ToString;
end;
procedure TBubble.SetChartIndex(const Value: Integer);
begin
FChartIndex := Value;
end;
{ TBubbleList }
function TBubbleList.ElementFormChartIndex(ChartIndex: Integer): TBubble;
var
Element : TBubble;
begin
for Element in Self do
if Element.FChartIndex = ChartIndex then
Exit(element);
Exit(nil);
end;
end.
Next Extend your TBubbleSeries
unit BubbleSeriesExtention;
interface
uses
System.Classes, System.SysUtils,
VclTee.BubbleCh,
BubbleU;
type
TBubbleSeries = class(VclTee.BubbleCh.TBubbleSeries)
strict private
FBoubleList: TBubbleList;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function AddBubble(aBubble: TBubble): Integer; reintroduce;
published
property BoubleList : TBubbleList read FBoubleList;
end;
implementation
{ TBubbleSeries }
function TBubbleSeries.AddBubble(aBubble: TBubble): Integer;
begin
aBubble.ChartIndex := Inherited AddBubble(aBubble.X, aBubble.Y, aBubble.Radius, aBubble.LabelText, aBubble.Color);
FBoubleList.Add(aBubble);
Result := aBubble.ChartIndex;
end;
constructor TBubbleSeries.Create(AOwner: TComponent);
begin
inherited;
FBoubleList := TBubbleList.Create(True);
end;
destructor TBubbleSeries.Destroy;
begin
FreeAndNil(FBoubleList);
inherited;
end;
end.
Finally Use it in your from:
Add BubbleSeriesExtention toh the uses list AFTER VclTee.BubbleCh
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus, VclTee.TeEngine,
VclTee.Series, VclTee.BubbleCh, Vcl.ExtCtrls, VclTee.TeeProcs, VclTee.Chart,
BubbleU, BubbleSeriesExtention;
And use it:
type
TForm4 = class(TForm)
Chart1: TChart;
BubbleSeries: TBubbleSeries;
procedure FormCreate(Sender: TObject);
procedure Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
Bouble: TBubble;
begin
Index := BubbleSeries.Clicked(X, Y);
if index < 0 then
exit;
Bouble := BubbleSeries.BoubleList.ElementFormChartIndex(Index);
Caption := Bouble.LabelText;
end;
procedure TForm4.FormCreate(Sender: TObject);
var
i: Integer;
begin
//Add dummy data
for i := 0 to 9 do
BubbleSeries.AddBubble(TBubble.Create(i));
end;
end.
this solution has this advantage that you have acces to your Object all the time and when your BubbleSeries are destroyes so is your objects for calculating elements in it. and gives you a kind of garbage collection
You can exploit unused AXLabel argument like this:
for DevIndex := 0 to DeviceCount - 1 do begin
Device := FDevices[DevIndex];
BubbleSeries.AddBubble(Device.CalculateXVal,Device.CalculateYVal, Device.CalculateRadius, IntToStr(DevIndex));
end;
// to avoid labels' text ox X-Axis:
Chart1.BottomAxis.LabelStyle := talValue;
//in Clicked:
DeviceIndex := StrToInt(BubbleSeries.Labels[Index]);
I am looking for recommended solution to style a TGrid cell that is being drawn by the OnGetValue call (that is called to paint the cells in view). For background, an excellent response by Mike, showed how to simply apply a tAlign property when the cell is created; but my next challenge is colouring the cell contents.
Previous posting/answer
The objective is to change the cell attributes (Font, style, colour etc...) of the value I am about to return as the cell "Value". In the example below; it would be applying a style to the OnGetValue "value" that is being returned. It may well be that we have to do this via a FM Stylesheet; or can we get directly to the TText attributes? Ideally, both scenarios would be great - but at this stage I will take either solution... (;->
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Objects, FMX.Grid,
FMX.Layouts, FMX.Edit;
type
TForm1 = class(TForm)
Grid1: TGrid;
Button1: TButton;
StyleBook1: TStyleBook;
procedure Grid1GetValue(Sender: TObject; const Col, Row: Integer;
var Value: Variant);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TStringColNum = class(TStringColumn)
private
function CreateCellControl: TStyledControl; override;
published
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
function TStringColNum.CreateCellControl: TStyledControl;
begin
Result:=TTextCell.Create(Self);
TTextCell(Result).TextAlign := TTextAlign.taTrailing;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Grid1.AddObject(TStringColumn.Create(Self));
Grid1.AddObject(TStringColNum.Create(Self)); // Right Aligned column?
Grid1.RowCount:=5000;
Grid1.ShowScrollBars:=True;
end;
procedure TForm1.Grid1GetValue(Sender: TObject; const Col, Row: Integer;
var Value: Variant);
begin
if Col=0 then
Value:='Row '+IntToStr(Row);
if Col=1 then
Value := 'Row '+IntToStr(Row);
// Apply style based on value ?
end;
end.
Many thanks in advance,
Ian.
Firstly, an apology. In my answer to your last question, CreateCellControl should have called inherited to create the cell. I've amended my answer.
As for this question, I've uploaded my blog posting on FireMonkey Cells - http://monkeystyler.com/blog/entry/firemonkey-grid-basics-custom-cells-and-columns - it covers the stuff from the previous answer, and also covers creating custom cell controls. You'll need to read that before your proceed. I'll wait.
...
Back now? Good.
Following on from the example in the blog post.
Except, that I've updated the TFinancialCell to inherit directly from TTextCell (which of course is a TEdit), which makes far more sense and is far simpler to style.
So, update the TFinancialCell:
type TFinancialCell = class(TTextCell)
private
FIsNegative: Boolean;
FIsImportant: Boolean;
protected
procedure SetData(const Value: Variant); override;
procedure ApplyStyle;override;
procedure ApplyStyling;
public
constructor Create(AOwner: TComponent); override;
published
property IsNegative: Boolean read FIsNegative;
property IsImportant: Boolean read FIsImportant;
end;
Code for the above:
procedure TFinancialCell.ApplyStyle;
var T: TFMXObject;
begin
inherited;
ApplyStyling;
end;
procedure TFinancialCell.ApplyStyling;
begin
if IsNegative then
FontFill.Color := claRed
else
FontFill.Color := claBlack;
Font.Style := [TFontStyle.fsItalic];
if IsImportant then
Font.Style := [TFontStyle.fsBold]
else
Font.Style := [];
if Assigned(Font.OnChanged) then
Font.OnChanged(Font);
Repaint;
end;
constructor TFinancialCell.Create(AOwner: TComponent);
begin
inherited;
TextAlign := TTextAlign.taTrailing;
end;
procedure TFinancialCell.SetData(const Value: Variant);
var F: Single;
O: TFMXObject;
S: String;
begin
S := Value;
FIsImportant := S[1] = '#';
if IsImportant then
S := Copy(Value,2,MaxInt)
else
S := Value;
F := StrToFloat(S);
inherited SetData(Format('%m', [F]));
FIsNegative := F < 0;
ApplyStyling;
end;
And finally, update the GetValue event handler:
procedure TForm1.Grid1GetValue(Sender: TObject; const Col, Row: Integer;
var Value: Variant);
var Cell: TStyledControl;
begin
if Col = 0 then
Value := Row
else if Col = 1 then
begin
Value := FloatToStr(Data[Row]);
if Value > 30 then
Value := '#'+Value;
end;
end;
Code above is fine for versions before XE4, but for XE4 and XE5 does not work. Color and style of text is not changed.
This is a fixed code for XE4 and XE5:
procedure TFinancialCell.ApplyStyling;
begin
StyledSettings := [TStyledSetting.ssFamily, TStyledSetting.ssSize];
if IsNegative then
FontColor := claRed
else
FontColor := claBlack;
Font.Style := [TFontStyle.fsItalic];
if IsImportant then
Font.Style := [TFontStyle.fsBold]
else
Font.Style := [];
if Assigned(Font.OnChanged) then
Font.OnChanged(Font);
Repaint;
end;
I am trying to simulate a drop down menu for a TButton, as shown below:
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
begin
APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
PopupMenu.Popup(APoint.X, APoint.Y);
end;
procedure TForm1.Button1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
begin
DropMenuDown(Button1, PopupMenu1);
// ReleaseCapture;
end;
end;
The problem is that when the menu is dropped down, if I click the button again I would like the menu to close, but instead it drops down again.
I am looking for a solution specifically for generic Delphi TButton not any 3rd Party equivalent.
After reviewing the solution provided by Whiler & Vlad, and comparing it to the way WinSCP implements the same thing, I'm currently using the following code:
unit ButtonMenus;
interface
uses
Vcl.Controls, Vcl.Menus;
procedure ButtonMenu(Control: TControl; PopupMenu: TPopupMenu);
implementation
uses
System.Classes, WinApi.Windows;
var
LastClose: DWord;
LastPopupControl: TControl;
LastPopupMenu: TPopupMenu;
procedure ButtonMenu(Control: TControl; PopupMenu: TPopupMenu);
var
Pt: TPoint;
begin
if (Control = LastPopupControl) and (PopupMenu = LastPopupMenu) and (GetTickCount - LastClose < 100) then begin
LastPopupControl := nil;
LastPopupMenu := nil;
end else begin
PopupMenu.PopupComponent := Control;
Pt := Control.ClientToScreen(Point(0, Control.ClientHeight));
PopupMenu.Popup(Pt.X, Pt.Y);
{ Note: PopupMenu.Popup does not return until the menu is closed }
LastClose := GetTickCount;
LastPopupControl := Control;
LastPopupMenu := PopupMenu;
end;
end;
end.
It has the advantage of not requiring any code changes to the from, apart from calling ButtonMenu() in the onClick handler:
procedure TForm1.Button1Click(Sender: TObject);
begin
ButtonMenu(Button1, PopupMenu1);
end;
Following our (Vlad & I) discussion, you use a variable to know when the popup was last opened to choose if you display the popupmenu or cancel the mouse event:
unit Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.StdCtrls;
type
TForm4 = class(TForm)
PopupMenu1: TPopupMenu;
Button1: TButton;
fgddfg1: TMenuItem;
fdgdfg1: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
cMenuClosed: Cardinal;
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
begin
APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
PopupMenu.Popup(APoint.X, APoint.Y);
end;
procedure TForm4.Button1Click(Sender: TObject);
begin
DropMenuDown(Button1, PopupMenu1);
cMenuClosed := GetTickCount;
end;
procedure TForm4.Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and not ((cMenuClosed + 100) < GetTickCount) then
begin
ReleaseCapture;
end;
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
cMenuClosed := 0;
end;
end.