I'm using Delphi 10 Seattle to build a multi device project with firemonkey.
My project has a ListBox, and I fill it runtime with ListBoxItems. I want to add the LongTap gesture to the ListBoxItems.
I have already tried this:
gestureManager := TGestureManager.Create(nil);
listBoxItem.Touch.GestureManager := gestureManager;
listBoxItem.Touch.InteractiveGestures := [TInteractiveGesture.LongTap];
listBoxItem.OnGesture := ListBoxItemGesture;
But the onGesture method doesn't get called. If I add the gestureManager to the Form in the designer and call the same onGesture method it does get called.
Gestures don't work with controls inside ScrollBox and descendants (I don't know, why). You should use ListBox.Touch, ListBox.OnGesture and analyze Selected property:
ListBox1.Touch.GestureManager := FmyGestureManager;
ListBox1.Touch.InteractiveGestures := [TInteractiveGesture.LongTap];
ListBox1.OnGesture := ListBox1Gesture;
procedure THeaderFooterForm.ListBox1Gesture(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean);
begin
if (Sender = ListBox1) and Assigned(ListBox1.Selected) then
begin
lblMenuToolBar.Text := 'Handled' + ListBox1.Selected.Text;
Handled := True;
end;
end;
Or, more complex method - find item by gesture location:
procedure THeaderFooterForm.ListBox1Gesture(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean);
var
c: IControl;
ListBox: TListBox;
lbxPoint: TPointF;
ListBoxItem: TListBoxItem;
begin
c := ObjectAtPoint(EventInfo.Location);
if Assigned(c) then
if Assigned(c.GetObject) then
if c.GetObject is TListBox then
begin
ListBox := TListBox(c.GetObject);
lbxPoint := ListBox.AbsoluteToLocal(EventInfo.Location);
ListBoxItem := ListBox.ItemByPoint(lbxPoint.X, lbxPoint.Y);
if Assigned(ListBoxItem) then
lblMenuToolBar.Text := 'Handled ' + ListBoxItem.Text;
Handled := True;
end;
end;
The right solution is much more banal:
for i:=0 to pred(ListBox1.items.count)do
begin
ListBox1.ItemByIndex(i).Touch.GestureManager:=GestureManager1;
ListBox1.ItemByIndex(i).Touch.InteractiveGestures :=
[TInteractiveGesture.LongTap, TInteractiveGesture.DoubleTap];
ListBox1.ItemByIndex(i).OnGesture := ListBoxitemGesture;
ListBox1.ItemByIndex(i).HitTest:=true;
end;
Related
In a 32-bit Delphi 11 VCL Application on Windows 10, when RIGHT-CLICKING any menu item, I need to get the name of the clicked MenuItem.
I use a TApplicationEvents component and this code to get notified when I click on any menu item:
procedure TformMain.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
case Msg.message of
Winapi.Messages.WM_COMMAND:
begin
CodeSite.Send('TformMain.ApplicationEvents1Message: WM_COMMAND');
end;
end;
end;
However:
How to get notified only when RIGHT-clicking the menu item?
How to get the NAME of the clicked MenuItem?
Each TMenu (i.e. TMainMenu or TPopupMenu) offers a method FindItem, which allows you to find an item by varying criteria. In your case the correct call for the form's main menu would be
TheMenuItem := Menu.FindItem(Msg.wParam, fkCommand);
Since I have several forms in my application and several (popup) menus on each of these forms, a special solution is needed here:
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
case Msg.message of
Winapi.Messages.WM_COMMAND:
begin
// Todo: Check HERE for RightMouseButtonDown - BUT HOW? (Or how to check HERE for modifier keys?)
var ThisMenuItem := GetMenuItem(Msg.wParam);
if Assigned(ThisMenuItem) then
begin
CodeSite.Send('TForm1.ApplicationEvents1Message: Clicked MenuItem Name', ThisMenuItem.Name);
end;
end;
end;
end;
function TForm1.GetMenuItem(const aWParam: NativeUInt): TMenuItem;
var
ThisMenuItem: TMenuItem;
begin
Result := nil;
var ThisForm := Screen.ActiveForm; // works on any form in the application
for var i := 0 to ThisForm.ComponentCount - 1 do
begin
if ThisForm.Components[i] is TMenu then
begin
ThisMenuItem := TMenu(ThisForm.Components[i]).FindItem(aWParam, fkCommand);
if Assigned(ThisMenuItem) then
begin
Result := ThisMenuItem;
EXIT;
end;
end;
end;
end;
I use a custom listview component and I need it to have a popupmenu item "copy data to clipboard". If there is no assigned popup, I create one and add the menuitem, if there is already a menu assigned, add the item to the current popup. Tried to put the code in the constructor, but then I realized, that popupmenu is still not created or associated to my listview. So any idea when to create my default item?
constructor TMyListView.Create(AOwner: TComponent);
var
FpopupMenu: TPopupMenu;
begin
inherited;
.....
FPopUpMenuItem := TMenuItem.Create(self);
FPopUpMenuItem.Caption := 'Copy data to clipboard';
FPopUpMenuItem.OnClick := PopupMenuItemClick;
if assigned(PopupMenu) then begin
popupMenu.Items.Add(FPopUpMenuItem);
end
else begin
FpopupMenu := TPopupMenu.Create(self);
FpopupMenu.Items.Add(FPopUpMenuItem);
PopupMenu := FpopupMenu;
end;
...
end;
Override the virtual TControl.DoContextPopup() method, eg:
type
TMyListView = class(TListView)
protected
...
procedure DoContextPopup(MousePos: TPoint; var Handled: Boolean); override;
...
end;
procedure TMyListView.DoContextPopup(MousePos: TPoint; var Handled: Boolean);
var
LPopupMenu: TPopupMenu;
LItem: TMenuItem;
function IsSameEvent(const E1, E2: TNotifyEvent): Boolean;
begin
Result := (TMethod(E1).Code = TMethod(E2).Code) and
(TMethod(E1).Data = TMethod(E2).Data);
end;
begin
inherited DoContextPopup(MousePos, Handled);
if Handled then Exit;
LPopupMenu := PopupMenu;
if not Assigned(LPopupMenu) then
begin
LPopupMenu := TPopupMenu.Create(Self);
PopupMenu := LPopupMenu;
end;
for I := 0 to LPopupMenu.Items.Count-1 do
begin
LItem := LPopupMenu.Items[I];
if IsSameEvent(LItem.OnClick, PopupMenuItemClick) then
Exit;
end;
LItem := TMenuItem.Create(Self);
LItem.Caption := 'Copy data to clipboard';
LItem.OnClick := PopupMenuItemClick;
LPopupMenu.Items.Add(LItem);
end;
The accepted answer indeed works perfectly - unless you add keyboard shortcuts to your menu item. If you do, these won't work before the popup menu has been accessed in some other way, because the items will not have been created.
If you need shortcuts, it may therefore be preferable to move the code from DoContextPopup to Loaded. Most simply,
procedure Loaded; override;
...
procedure Loaded;
var
MI: TMenuItem;
ItemCovered: boolean;
i: integer;
begin
inherited;
if not Assigned(PopupMenu) then
PopupMenu:=TPopupMenu.Create(self);
ItemCovered:=false;
for i := 0 to PopupMenu.Items.Count-1 do
if IsSameEvent(PopupMenu.Items[I].OnClick, CopyDataToClipboardClick) then begin
ItemCovered:=true;
break;
end;
if not ItemCovered then begin
MI:=TMenuItem.Create(PopupMenu);
MI.Caption:='Copy data to clipboard';
MI.OnClick:=CopyDataToClipboardClick;
MI.ShortCut:=ShortCut(Ord('C'),[ssShift,ssCtrl]);
PopupMenu.Items.Add(MI);
end;
end;
This won't check for popup menus added on runtime, but probably serve most cases better.
I'd like to add DBLookupComboboxes to certain columns in a DBGrid. There is a nice article on About.com on how to do this here. The problem is that with a table having many columns, if you select from the DBLookupCombobox in one column and then try to scroll left, the combobox will move left too as shown in the included images. How can the About.com code can be changed to prevent this behavior? A web search showed two others complaining of the exact same problem with no solution. Note that I want to use a DBLookupCombobox to show a name but enter the id, so using a simple picklist will not do.
procedure TForm1.DBGrid1ColExit(Sender: TObject);
begin
if DBGrid1.SelectedField.FieldName = DBLookupComboBox1.DataField then
DBLookupComboBox1.Visible := False
end;
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
if (gdFocused in State) then
begin
if (Column.Field.FieldName = DBLookupComboBox1.DataField) then
with DBLookupComboBox1 do
begin
Left := Rect.Left + DBGrid1.Left + 2;
Top := Rect.Top + DBGrid1.Top + 2;
Width := Rect.Right - Rect.Left;
Width := Rect.Right - Rect.Left;
Height := Rect.Bottom - Rect.Top;
Visible := True;
end;
end
end;
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
if (key = Chr(9)) then Exit;
if (DBGrid1.SelectedField.FieldName = DBLookupComboBox1.DataField) then
begin
DBLookupComboBox1.SetFocus;
SendMessage(DBLookupComboBox1.Handle, WM_Char, word(Key), 0);
end
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
with DBLookupComboBox1 do
begin
DataSource := DataSource1; // -> AdoTable1 -> DBGrid1
ListSource := DataSource2;
DataField := 'resource_id'; // from AdoTable1 - displayed in the DBGrid
KeyField := 'id';
ListField := 'resource_name; id';
Visible := False;
end;
DataSource2.DataSet := AdoQuery1;
AdoQuery1.Connection := AdoConnection1;
AdoQuery1.SQL.Text := 'SELECT id,resource_name FROM resources';
AdoQuery1.Open;
end;
Here is one solution using a neat hack from François.
type
// Hack to redeclare your TDBGrid here without the the form designer going mad
TDBGrid = class(DBGrids.TDBGrid)
procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
end;
TForm1 = class(TForm)
[...]
procedure TDBGrid.WMHScroll(var Msg: TWMHScroll);
begin
if Form1.DBGrid1.SelectedField.FieldName = Form1.DBLookupComboBox1.DataField then begin
case Msg.ScrollCode of
SB_LEFT,SB_LINELEFT,SB_PAGELEFT: begin
Form1.DBGrid1.SelectedIndex := Form1.DBGrid1.SelectedIndex-1;
Form1.DBLookupComboBox1.Visible := False;
end;
SB_RIGHT,SB_LINERIGHT,SB_PAGERIGHT: begin
Form1.DBGrid1.SelectedIndex := Form1.DBGrid1.SelectedIndex+1;
Form1.DBLookupComboBox1.Visible := False;
end;
end;
end;
inherited; // to keep the expected behavior
end;
I have a check box which will be enabled/disabled at run time. I just want to show different tool tips if it is enabled/disabled. I was thinking about overriding OnMouseEnter event and handle it there but OnMouseEnter will be called only if the control is enabled. How can i possible achieve that behavior? Any help would be appreciated.
I tried to handle OnMouseMove of the form and do something like this
procedure Tdlg.pnlTopMouseMove(Sender: TObject;Shift: TShiftState; X, Y: Integer);
var
point: TPoint;
checkBoxCursorPos: TPoint;
begin
inherited;
point.X := X;
point.Y := Y;
checkBoxCursorPos := chkBx.ScreenToClient(point);
if (PtInRect(chkBx.ClientRect, checkBoxCursorPos)) then
begin
if(chkBx.Enabled) then
chkBx.Hint := 'Enabled'
else
chkBx.Hint := 'Disabled' ;
Application.ShowHint := True;
end;
end;
but the condition PtinRect is not satisfied. What i am doing wrong?
There is a simple solution: place an empty TLabel over the checkbox and set its Hint to the value for the disabled checkbox state. The label has to be AutoSize off and you can enforce position and size by its BoundsRect property set to that of the CheckBox.
When the CheckBox is enabled the Hint of the Checkbox is used, while the Hint of the Label is used when the CheckBox is disabled.
Update: just saw that Bummi mentions a similar idea in his comment.
The official answer: you can’t.
The workaround: you could try using the form's MouseMove-event (assuming that won’t be disabled, of course), and if the mouse cursor is over the relevant control, display the appropriate hint.
Here is a unit that can show hints for disabled controls.
I used it like this:
TATHintControl.Create(self).HintStyleController := GlobalHintStyleController;
GlobalHintStyleController is a DevExpress stylecontroller.
Then the unit
unit ATHintControl;
{
The purpose of this component is to show hints for disabled controls (VCL doesn't)
It uses timestamp comparison instead of Timers to save resources
}
interface
uses
// VCL
Classes,
Controls,
Forms,
AppEvnts,
Messages,
Windows,
// DevEx
cxHint;
type
TGetHintForControlEvent = function(AControl: TControl): string of object;
THandleControlEvent = function(AControl: TControl): boolean of object;
TATHintControl = class(TComponent)
private
fHintTimeStamp: TDateTime;
fHintHideTimeStamp: TDateTime;
fHintControl: TControl;
fHintVisible: boolean;
FHintStyleController: TcxHintStyleController;
FHintShowDelay: Integer;
FHintHideDelay: Integer;
fGetHintForControlEvent: TGetHintForControlEvent;
fHandleControlEvent: THandleControlEvent;
fApplicationEvents: TApplicationEvents;
procedure IdleHandler(Sender: TObject; var Done: Boolean);
procedure ShortcutHandler(var Msg: TWMKey; var Handled: Boolean);
procedure SetHintStyleController(const Value: TcxHintStyleController);
procedure HideHint;
function GetCursorPos(out APoint: TPoint): Boolean;
function HandleHint: boolean;
protected
function GetHintForControl(AControl: TControl): string; virtual;
function HandleControl(AControl: TControl): boolean; virtual;
public
procedure AfterConstruction; override;
published
property HintStyleController: TcxHintStyleController read FHintStyleController write SetHintStyleController;
property OnGetHintForControl: TGetHintForControlEvent read fGetHintForControlEvent write fGetHintForControlEvent;
property OnHandleControl: THandleControlEvent read fHandleControlEvent write fHandleControlEvent;
end;
implementation
uses
Types,
SysUtils,
DateUtils;
const
cHintShowDelay: Integer = 500; // msec
cHintHideDelay: Integer = 3 * 1000; // 3 sec
{ TATHintControl }
procedure TATHintControl.AfterConstruction;
begin
inherited;
fApplicationEvents := TApplicationEvents.Create(self);
fApplicationEvents.OnIdle := IdleHandler;
fApplicationEvents.OnShortCut := ShortcutHandler;
fHintShowDelay := cHintShowDelay;
fHintHideDelay := cHintHideDelay;
end;
function TATHintControl.GetCursorPos(out APoint: TPoint): Boolean;
begin
{$WARN SYMBOL_PLATFORM OFF}
result := Windows.GetCursorPos(APoint);
{$WARN SYMBOL_PLATFORM ON}
end;
function TATHintControl.GetHintForControl(AControl: TControl): string;
begin
if Assigned(OnGetHintForControl) then
result := OnGetHintForControl(AControl)
else
result := AControl.Hint;
end;
procedure TATHintControl.HideHint;
begin
HintStyleController.HideHint;
fHintTimeStamp := 0;
fHintVisible := false;
fHintHideTimeStamp := 0;
end;
procedure TATHintControl.IdleHandler(Sender: TObject; var Done: Boolean);
begin
if Assigned(HintStyleController) then
Done := HandleHint;
end;
procedure TATHintControl.SetHintStyleController(
const Value: TcxHintStyleController);
begin
FHintStyleController := Value;
end;
procedure TATHintControl.ShortcutHandler(var Msg: TWMKey; var Handled: Boolean);
begin
fHintControl := nil; // clear the HintControl so that keypress causes it to be shown again w/o having to move the mouse
end;
function TATHintControl.HandleControl(AControl: TControl): boolean;
begin
if Assigned(OnHandleControl) then
result := OnHandleControl(AControl)
else
result := not AControl.Enabled;
end;
function TATHintControl.HandleHint: boolean;
var
vNow: TDateTime;
vScreenPos: TPoint;
vClientPos: TPoint;
vControl: TControl;
vHintString: string;
vForm: TForm;
vWinControl: TWinControl;
begin
result := (fHintTimeStamp = 0);
vForm := Screen.ActiveForm;
if not Assigned(vForm) then
exit;
if not boolean(GetCursorPos(vScreenPos)) then
exit;
vNow := Now;
vControl := nil;
vWinControl := vForm as TWinControl;
while Assigned(vWinControl) do
try
vClientPos := vWinControl.ScreenToClient(vScreenPos);
vControl := vWinControl.ControlAtPos(vClientPos, true, true, true);
if not Assigned(vControl) then
begin
vControl := vWinControl;
break;
end
else
if vControl is TWinControl then
vWinControl := vControl as TWinControl
else
vWinControl := nil;
except
exit; // in some cases ControlAtPos can fail with EOleError: Could not obtain OLE control window handle.
end;
if (fHintControl <> vControl) then
begin
if fHintVisible then
HideHint;
if Assigned(vControl) and HandleControl(vControl) then
begin
fHintControl := vControl;
fHintTimeStamp := vNow; // starts timer for hint to show
end
else
begin
fHintTimeStamp := 0;
fHintControl := nil;
end;
end
else
begin
if fHintVisible and (vNow > fHintHideTimeStamp) then
begin
HideHint;
end
else // we check HandleControl again here to make sure we still want to show the hint
if not fHintVisible and Assigned(vControl) and HandleControl(vControl) and (fHintTimeStamp > 0) and (vNow > IncMillisecond(fHintTimeStamp, fHintShowDelay)) then
begin
vHintString := GetHintForControl(vControl);
if vHintString = '' then
exit;
HintStyleController.ShowHint(vScreenPos.X + 0, vScreenPos.Y + 18, '', vHintString);
fHintTimeStamp := vNow;
fHintControl := vControl;
fHintVisible := true;
// base hide delay + dynamic part based on length of the hint string, 500 msec per 30 characters
fHintHideTimeStamp := vNow + IncMillisecond(0, fHintHideDelay) + ((Length(vHintString) div 20) * EncodeTime(0,0,0,500));
end
end;
result := (fHintTimeStamp = 0);
end;
end.
We have a combo box with more than 100 items.
We want to filter out the items as we enter characters in combo box. For example if we entered 'ac' and click on the drop down option then we want it to display items starting with 'ac' only.
How can I do this?
Maybe you'd be happier using the autocompletion features built in to the OS. I gave an outline of how to do that here previously. Create an IAutoComplete object, hook it up to your combo box's list and edit control, and the OS will display a drop-down list of potential matches automatically as the user types. You won't need to adjust the combo box's list yourself.
To expand on Rob's answer about using the OnChange event, here is an example of how to do what he suggests.
procedure TForm1.FormCreate(Sender: TObject);
begin
FComboStrings := TStringList.Create;
FComboStrings.Add('Altair');
FComboStrings.Add('Alhambra');
FComboStrings.Add('Sinclair');
FComboStrings.Add('Sirius');
FComboStrings.Add('Bernard');
FComboStrings.Sorted := True;
ComboBox1.AutoComplete := False;
ComboBox1.Items.Text := FComboStrings.Text;
ComboBox1.Sorted := True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FComboStrings);
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
var
Filter: string;
i: Integer;
idx: Integer;
begin
// Dropping down the list puts the text of the first item in the edit, this restores it
Filter := ComboBox1.Text;
ComboBox1.DroppedDown := True;
ComboBox1.Text := Filter;
ComboBox1.SelStart := Length(Filter);
for i := 0 to FComboStrings.Count - 1 do
if SameText(LeftStr(FComboStrings[i], Length(ComboBox1.Text)), ComboBox1.Text) then
begin
if ComboBox1.Items.IndexOf(FComboStrings[i]) < 0 then
ComboBox1.Items.Add(FComboStrings[i]);
end
else
begin
idx := ComboBox1.Items.IndexOf(FComboStrings[i]);
if idx >= 0 then
ComboBox1.Items.Delete(idx);
end;
end;
My brief contribution working with objects in the combobox:
procedure FilterComboBox(Combo: TComboBox; DefaultItems: TStrings);
function Origin: TStrings;
begin
if Combo.Tag = 0 then
begin
Combo.Sorted := True;
Result := TStrings.Create;
Result := Combo.Items;
Combo.Tag := Integer(Result);
end
else
Result := TStrings(Combo.Tag);
end;
var
Filter: TStrings;
I: Integer;
iSelIni: Integer;
begin
if(Combo.Text <> EmptyStr) then
begin
iSelIni:= Length(Combo.Text);
Filter := TStringList.Create;
try
for I := 0 to Origin.Count - 1 do
if AnsiContainsText(Origin[I], Combo.Text) then
Filter.AddObject(Origin[I], TObject(Origin.Objects[I]));
Combo.Items.Assign(Filter);
Combo.DroppedDown:= True;
Combo.SelStart := iSelIni;
Combo.SelLength := Length(Combo.Text);
finally
Filter.Free;
end;
end
else
Combo.Items.Assign(DefaultItems);
end;
You can handle the combo box's OnChange event. Keep a master list of all items separate from the UI control, and whenever the combo box's edit control changes, adjust the combo box's list accordingly. Remove items that don't match the current text, or re-add items from the master list that you removed previously.
As Rob already answered, you could filter on the OnChange event, see the following code example. It works for multiple ComboBoxes.
{uses}
Contnrs, StrUtils;
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
ComboBox2: TComboBox;
procedure FormCreate(Sender: TObject);
procedure ComboBoxChange(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FComboLists: TList;
procedure FilterComboBox(Combo: TComboBox);
end;
implementation
{$R *.dfm}
procedure TForm1.ComboBoxChange(Sender: TObject);
begin
if Sender is TComboBox then
FilterComboBox(TComboBox(Sender));
end;
procedure TForm1.FilterComboBox(Combo: TComboBox);
function Origin: TStrings;
begin
if Combo.Tag = 0 then
begin
Combo.Sorted := True;
Result := TStringList.Create;
Result.Assign(Combo.Items);
FComboLists.Add(Result);
Combo.Tag := Integer(Result);
end
else
Result := TStrings(Combo.Tag);
end;
var
Filter: TStrings;
I: Integer;
begin
Filter := TStringList.Create;
try
for I := 0 to Origin.Count - 1 do
if AnsiStartsText(Combo.Text, Origin[I]) then
Filter.Add(Origin[I]);
Combo.Items.Assign(Filter);
Combo.SelStart := Length(Combo.Text);
finally
Filter.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FComboLists := TObjectList.Create(True);
// For Each ComboBox, set AutoComplete at design time to false:
ComboBox1.AutoComplete := False;
ComboBox2.AutoComplete := False;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FComboLists.Free;
end;