TreeView as vertical menu - how to get name of associated MenuItem - treeview

I am working on changing TMainMenu to TTreeView i.e.vertical menu.
I am able to create the TTreeView and by calling AddChildObject() I have Node.Data.
I was able to call the OnClick event of an associated MenuItem using TMenuItem(TreeView1.Selected.Data).Click.
How to get the name of the associated MenuItem?
I tried
TMenuItem(TreeView.Selected.Data).Name, but the Name shows as an empty string.
procedure TForm1.TreeViewClick(Sender: TObject);
begin
if Assigned ((Sender as TTreeView).Selected) then
begin
TMenuItem ((Sender as TTreeView).Selected.Data).Click;
// this works fine i.e. on node click specific menuitem click is called
TMenuItem ((Sender as TTreeView).Selected.Data).Name;
// Name is empty inspite of menuitem name being not empty
end;
end;
Code for populating the TreeView:
procedure CopyMenuToTreeView( aMenu: TMenu; aTreeview: TTreeView );
procedure AddItems( anItem: TMenuItem; aParentNode: TTreeNode );
var
node: TTreeNode;
i: Integer;
begin
for i := 0 To anItem.Count -1 do begin
node := aTreeView.Items.AddChildObject(
aParentNode,
anItem.Items[i].Caption );
AddItems( anItem.Items[i], node );
end;
end;
begin
Assert( Assigned( aTreeView ), 'No treeview' );
aTreeView.Items.BeginUpdate;
try
aTreeView.Items.Clear;
if Assigned( aMenu ) then
AddItems( aMenu.Items, nil );
aTreeView.FullExpand;
finally
aTreeView.Items.EndUpdate;
end; { Finally }
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
CopyMenuToTreeview( MainMenu1, TreeView1 );
end;
Also, here is the existing TMenuItem.OnClick:
procedure TForm1.MI_Click(Sender: TObject);
begin
//Common code for many menuitem
//Code for creating form based on name of menuitem
CreateMIForm('Frm'+((Sender as TMenuItem). Name));
end;

I have fixed your code - AddChildObject() needs a 3rd argument. In TreeView1Click I used an intermediate variable.
Tested with Delphi 10.4.1.
unit TreeViewMenuDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Menus, Vcl.ComCtrls;
type
TForm1 = class(TForm)
TreeView1: TTreeView;
MainMenu1: TMainMenu;
File1: TMenuItem;
Close1: TMenuItem;
Exit1: TMenuItem;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure Close1Click(Sender: TObject);
procedure Exit1Click(Sender: TObject);
procedure TreeView1Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure CopyMenuToTreeView( aMenu: TMenu; aTreeView: TTreeView );
procedure AddItems( anItem: TMenuItem; aParentNode: TTreeNode );
var
node: TTreeNode;
i: Integer;
begin
for i := 0 To anItem.Count -1 do begin
node := aTreeView.Items.AddChildObject(
aParentNode,
anItem.Items[i].Caption,
anItem.Items[i]);
AddItems( anItem.Items[i], node );
end;
end;
begin
Assert( Assigned( aTreeView ), 'No treeview' );
aTreeView.Items.BeginUpdate;
try
aTreeView.Items.Clear;
if Assigned( aMenu ) then
AddItems( aMenu.Items, nil );
aTreeView.FullExpand;
finally
aTreeView.Items.EndUpdate;
end; { Finally }
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
CopyMenuToTreeView(MainMenu1, TreeView1);
end;
procedure TForm1.Close1Click(Sender: TObject);
begin
ShowMessage('File/Close');
end;
procedure TForm1.Exit1Click(Sender: TObject);
begin
ShowMessage('File/Exit');
end;
procedure TForm1.TreeView1Click(Sender: TObject);
var
MenuItem : TMenuItem;
begin
if Assigned ((Sender as TTreeView).Selected) then begin
MenuItem := TMenuItem((Sender as TTreeView).Selected.Data);
MenuItem.Click;
// this works fine i.e. on node click specific menuitem click is called
ShowMessage(MenuItem.Name);
end;
end;
end.
DFM file:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 299
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
Menu = MainMenu1
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object TreeView1: TTreeView
Left = 16
Top = 24
Width = 205
Height = 257
Indent = 19
TabOrder = 0
OnClick = TreeView1Click
end
object Button1: TButton
Left = 280
Top = 36
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 1
OnClick = Button1Click
end
object MainMenu1: TMainMenu
Left = 364
Top = 124
object File1: TMenuItem
Caption = '&File'
object Close1: TMenuItem
Caption = 'Close'
OnClick = Close1Click
end
object Exit1: TMenuItem
Caption = 'Exit'
OnClick = Exit1Click
end
end
end
end

Related

FMX, TGrid, OnCellClick

On my fmx-form I have TGrid with column of type TCheckColumn. To capture click events on cells of grid I connect the OnCellClick handler to it.
My Question is: how to reliably trigger the OnCellClick event when clicking the left mouse button (LMB) for an arbitrary line, if:
LMB clicks occur on the same cell in the TCheckColumn;
LMB clicks are not accompanied by mouse cursor displacement.
For a better explanation of the essence of the issue, I will give a picture (see below). LMB is pressed on it in the same cell without cursor offsets. Events arising in this situation are logged in the CodeSite window (on the right). As you can see from the picture: the cell in the TCheckColumn column changes its state (the checkmark appears or disappears), but the OnCellClick event occurs only 1 time.
The picture itself
TGrid with OnCellClick event handler:
unit uMain;
interface
uses
System.SysUtils,
System.Types,
System.UITypes,
System.Classes,
System.Variants,
System.Rtti,
//
FMX.Types,
FMX.Controls,
FMX.Forms,
FMX.Graphics,
FMX.Dialogs,
FMX.Grid.Style,
FMX.Grid,
FMX.Controls.Presentation,
FMX.ScrollBox,
FMX.StdCtrls,
FMX.ImgList;
//
type
//for grid row
TRow = record
ID:integer;
Checked:boolean;
end;
//Test form
TForm1 = class(TForm)
grd: TGrid;
CheckColumn2: TCheckColumn;
Label2: TLabel;
IntegerColumn1: TIntegerColumn;
procedure FormCreate(Sender: TObject);
procedure grdGetValue(Sender: TObject; const ACol, ARow: Integer; var Value: TValue);
procedure grdSetValue(Sender: TObject; const ACol, ARow: Integer; const Value: TValue);
procedure grdCellClick(const Column: TColumn; const Row: Integer);
private
FRowsA: array of TRow;
FSelectedRow: integer;
//
procedure PopulateGrid;
//
end;
//for cols of grid
TMyCols = (mcID, mcChecked);
var
Form1: TForm1;
implementation
{$R *.fmx}
//FormCreate
procedure TForm1.FormCreate(Sender: TObject);
begin
PopulateGrid;
end;
{$REGION 'TGrid'}
//PopulateGrid
procedure TForm1.PopulateGrid;
const
rows = 10;//rows count
begin
Grd.RowCount := rows ;
SetLength(FRowsA, rows);
for var r := 0 to rows-1 do
begin
//
FRowsA[r].ID := r; //id
FRowsA[r].Checked := false ; //check
end;
end;
//grdCellClick
procedure TForm1.grdCellClick(const Column: TColumn; const Row: Integer);
var
selRow, cnt: integer;
begin
var ci := Column.Index ;
FSelectedRow := Row;
cnt := grd.RowCount ;
//checked col
if ci = Ord(TMyCols.mcChecked) then
begin
//
grd.RowCount := 0;
grd.RowCount := cnt;
grd.SelectRow(FSelectedRow);
//
FRowsA[Row].Checked := not FRowsA[Row].Checked;
//
Log.d( Format('CellClick raised: row=%d; col=%d', [Row, ci]) );
end;
end;
//grdSetValue
procedure TForm1.grdSetValue(Sender: TObject; const ACol, ARow: Integer; const Value: TValue);
var
oldVal, newVal: boolean;
begin
var g := Sender as TGrid;
if not Assigned(g) then Exit;
if (ARow < 0) or (ARow >= g.RowCount) then Exit;
//
//col num
case ACol of
//id col
Ord(TMyCols.mcID):
begin
FRowsA[ARow].Checked := Value.AsBoolean;//id
end;
//checked col
Ord(TMyCols.mcChecked):
begin
oldVal := FRowsA[ARow].Checked;
Value.TryAsType<boolean>(newVal);
FRowsA[ARow].Checked := newVal;//checked
Log.d( Format('OnSetValue raised: row=%d; col=%d; oldValue=%s; newValue=%s', [ARow, ACol, oldVal.ToString(), newVal.ToString()]) );
end;
//
end;
//
end;
//grdGetValue
procedure TForm1.grdGetValue(Sender: TObject; const ACol, ARow: Integer; var Value: TValue);
var
val: boolean;
begin
var g := Sender as TGrid;
if not Assigned(g) then Exit;
if (ARow < 0) or (ARow >= g.RowCount) then Exit;
case ACol of
//id
Ord(TMyCols.mcID) :
begin
Value := FRowsA[ARow].ID;
end;
//checked
Ord(TMyCols.mcChecked):
begin
Value := FRowsA[ARow].Checked;
end;
end;
Log.d( Format('OnGetValue raised: row=%d; col=%d', [ARow, ACol]) );
//
end;
{$ENDREGION}
//
end.

Why do modal Delphi forms not receive WM_SYSCOMMAND when the user clicks the task bar button?

In a Delphi (2007) program, running on Windows 8.1, I would like to get notified when the user clicks on the task bar button belonging to my program. So I am trapping the WM_SYSCOMMAND which usually gets send in that case.
This works fine for program's main window.
If a modal window is active (opened with Form2.ShowModal), the same code cannot trap the WM_SYSCOMMAND, neither in the main for nor in the modal form. What is different? And is there any way to change this?
This is the code I have added to both forms:
unit unit1;
interface
type
TForm1 = class(TForm)
// [...]
procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
end;
// [...]
implementation
// [...]
procedure Tf_dzProgressTest.WMSysCommand(var Msg: TWMSysCommand);
begin
inherited; // place breakpoint here
end;
// [...]
end.
I also tried to use Application.OnMessage or a TApplicationEvents component and even overriding the form's WndProc method. Neither could trap WM_SYSCOMMAND while a modal form was active.
When you click on the task bar button, the system attempts to execute the minimize action for the window associated with the task bar button. Typically that is the window for the main form. That is where the WM_SYSCOMMAND originates.
Now, when a modal form is showing, the main form is disabled. It was disabled with a call to the Win32 EnableWindow function. That is an integral part of modality. The modal window is the only enabled top level window because you are not supposed to interact with any other top level window.
When a window is disabled, its system menu is also disabled. That is why the system is unable to perform the minimize action, and why you do not receive WM_SYSCOMMAND.
There's not a whole lot that you can do about this. Once you show a modal form, the main window has to be disabled. And at that point it is not going to receive WM_SYSCOMMAND and is not going to find out that the user clicked the task bar button.
David explained the problem very well, so I'm not going to repeat what he said.
What I'm going to give you is a work around using non blocking code.`
You'll need to declare an event that will tell us when the form has closed.
TModalResultEvent = procedure(aSender: TObject; var aModal: TModalResult) of object;
This allows us to Listen in on Messages passing through the Application
const
WM_SYSCOMMAND1 = WM_USER + 1;
type
TApplicationHelper = class(TWinControl)
private
FListener: TWinControl;
public
constructor Create(AOwner: TComponent); override;
procedure WMSysCommand1(var Msg: TWMSysCommand); message WM_SYSCOMMAND1;
procedure FirstChance(var Msg: TMsg; var Handled: Boolean); virtual;
property Listener: TWinControl read FListener write FListener;
end;
constructor TApplicationHelper.Create(AOwner: TComponent);
begin
inherited;
Application.OnMessage := FirstChance;
if aOwner is TWinControl then
FListener := TWinControl(aOwner)
else
FListener := Self;
end;
procedure TApplicationHelper.FirstChance(var Msg: TMsg;
var Handled: Boolean);
begin
{get in and out...this gets called alot...I would recommend only using
PostMessage since it is non blocking}
if Assigned(FListener) then
begin
if Msg.Message = WM_SYSCOMMAND then
begin
PostMessage(FListener.Handle, WM_SYSCOMMAND1, Msg.wParam, Msg.lParam);
end;
end;
end;
procedure TApplicationHelper.WMSysCommand1(var Msg: TWMSysCommand);
begin
ShowMessage('WMSYSCOMMAND1 AppHelper');
end;
end.
Example of how to call the Non Blocking form.
unit IForms;
interface
uses
Forms, Controls;
type
TModalResultEvent = procedure(aSender: TObject; var aModal: TModalResult) of object;
IForm = interface
function getEnableForm: boolean;
procedure setEnableForm(const Value: boolean);
Property EnableForm: boolean read getEnableForm write setEnableForm;
end;
implementation
end.
TForm1 = class(TForm, IForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure CheckBox1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FEnable: boolean;
FAppHelper: TApplicationHelper;
procedure FormModal(aSender: TObject; var aModal: TModalResult);
function getEnableForm: boolean;
procedure setEnableForm(const Value: boolean);
//don't need it
//procedure EnableChildren(aParent: TWinControl; aEnable: boolean);
procedure WMSysCommand1(var Msg: TWMSysCommand); message WM_SYSCOMMAND1;
public
{ Public declarations }
Property EnableForm: boolean read getEnableForm write setEnableForm;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
Unit2, Unit3;
procedure TForm1.Button1Click(Sender: TObject);
var
a_Form: TForm2;
begin
//Normal blocking code
a_Form := TForm2.Create(nil);
try
a_Form.ShowModal;
finally
a_Form.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
a_Form: TForm3;
begin
//Non blocking code
a_Form := TForm3.Create(nil);
a_Form.ShowModal(Self, FormModal);
end;
{
mrNone = 0;
mrOk = idOk;
mrCancel = idCancel;
mrAbort = idAbort;
mrRetry = idRetry;
mrIgnore = idIgnore;
mrYes = idYes;
mrNo = idNo;
mrAll = mrNo + 1;
mrNoToAll = mrAll + 1;
mrYesToAll = mrNoToAll + 1;
}
procedure TForm1.FormModal(aSender: TObject; var aModal: TModalResult);
var
a_Message: string;
begin
if aSender is TForm then
a_Message := 'Form: ' + TForm(aSender).Name;
Case aModal of
mrNone: a_Message := a_Message + ' None';
mrOk: a_Message := a_Message + ' Ok';
mrCancel: a_Message := a_Message + ' Cancel';
mrAbort: a_Message := a_Message + ' Abort';
mrRetry: a_Message := a_Message + ' Retry';
mrYes: a_Message := a_Message + ' Yes';
mrNo: a_Message := a_Message + ' No';
mrAll: a_Message := a_Message + ' All';
mrNoToAll: a_Message := a_Message + ' No To All';
mrYesToAll: a_Message := a_Message + ' Yes To All';
else
a_Message := a_Message + ' Unknown';
end;
ShowMessage(a_Message);
end;
{
procedure TForm1.EnableChildren(aParent: TWinControl; aEnable: boolean);
var
a_Index: integer;
begin
for a_Index := 0 to aParent.ControlCount - 1 do
begin
if aParent.Controls[a_Index] is TWinControl then
EnableChildren(TWinControl(aParent.Controls[a_Index]), aEnable);
aParent.Controls[a_Index].Enabled := aEnable;
end;
end;}
function TForm1.GetEnableForm: boolean;
begin
//Result := FEnable;
Result := Enabled;
end;
procedure TForm1.SetEnableForm(const Value: boolean);
begin
//FEnable := Value;
Enabled := Value;
//EnableChildren(Self, FEnable);
end.
procedure TForm1.FormCreate(Sender: TObject);
begin
FAppHelper:= TApplicationHelper.Create(Self);
FAppHelper.Parent := Self;
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
if CheckBox1.Checked then
FAppHelper.Listener := Self
else
FAppHelper.Listener := FAppHelper;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FAppHelper.Free;
end;
procedure TForm1.WMSysCommand1(var Msg: TWMSysCommand);
begin
ShowMessage('WMSYSCOMMAND1 Form1');
end;
{
object Form1: TForm1
Left = 84
Top = 126
Width = 514
Height = 259
Caption = 'Form1'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 56
Top = 56
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object Button2: TButton
Left = 256
Top = 56
Width = 75
Height = 25
Caption = 'Button2'
TabOrder = 1
OnClick = Button2Click
end
object CheckBox1: TCheckBox
Left = 256
Top = 112
Width = 97
Height = 17
Caption = 'Send to Form'
Checked = True
State = cbChecked
TabOrder = 2
OnClick = CheckBox1Click
end
end
}
This is the Non Blocking Form
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Unit1, StdCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
FForm: IForm;
FModalResultEvent: TModalResultEvent;
protected
procedure DoClose; virtual;
public
{ Public declarations }
procedure ShowModal(aForm: IForm; aModalResultEvent: TModalResultEvent) overload;
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
{
object Button1: TButton
Left = 32
Top = 128
Width = 73
Height = 25
Caption = 'Yes'
ModalResult = 6
TabOrder = 0
OnClick = Button1Click
end
object Button2: TButton
Left = 128
Top = 128
Width = 57
Height = 25
Caption = 'No'
ModalResult = 7
TabOrder = 1
OnClick = Button1Click
end
object Button3: TButton
Left = 216
Top = 128
Width = 57
Height = 25
Caption = 'Cancel'
ModalResult = 2
TabOrder = 2
OnClick = Button1Click
end
}
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction);
begin
try
DoClose;
finally
Action := caFree;
end;
end;
procedure TForm3.ShowModal(aForm: TForm; aModalResultEvent: TModalResultEvent);
begin
FForm := aForm;
FModalResultEvent := aModalResultEvent;
if Assigned(FForm) then
FForm.EnableForm:= False;
Self.Show;
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
if Sender is TButton then
begin
Self.ModalResult := TButton(Sender).ModalResult;
Close;
end;
end;
procedure TForm3.DoClose;
var
a_MR: TModalResult;
begin
a_MR := Self.ModalResult;
if Assigned(FForm) then
FForm.EnableForm := True;
if Assigned(FModalResultEvent) then
FModalResultEvent(Self, a_MR);
end;

Delphi: ListView (vsReport) single column header caption with custom font color?

In a ListView with vsReport ViewStyle, how can I customize the font color of just any single column header caption? For example (the second column header caption has a red font color):
I would handle the NM_CUSTOMDRAW header notification code and respond to this notification message with the CDRF_NEWFONT return code at the CDDS_ITEMPREPAINT rendering stage. The following code shows how to extend list view controls to have the event for specifying header item font color:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, CommCtrl, StdCtrls;
type
TGetHeaderItemFontColorEvent = procedure(Sender: TCustomListView;
ItemIndex: Integer; var FontColor: TColor) of object;
TListView = class(ComCtrls.TListView)
private
FHeaderHandle: HWND;
FOnGetHeaderItemFontColor: TGetHeaderItemFontColorEvent;
procedure WMNotify(var AMessage: TWMNotify); message WM_NOTIFY;
protected
procedure CreateWnd; override;
published
property OnGetHeaderItemFontColor: TGetHeaderItemFontColorEvent read
FOnGetHeaderItemFontColor write FOnGetHeaderItemFontColor;
end;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
private
procedure GetHeaderItemFontColor(Sender: TCustomListView;
ItemIndex: Integer; var FontColor: TColor);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TListView }
procedure TListView.CreateWnd;
begin
inherited;
FHeaderHandle := ListView_GetHeader(Handle);
end;
procedure TListView.WMNotify(var AMessage: TWMNotify);
var
FontColor: TColor;
NMCustomDraw: TNMCustomDraw;
begin
if (AMessage.NMHdr.hwndFrom = FHeaderHandle) and
(AMessage.NMHdr.code = NM_CUSTOMDRAW) then
begin
NMCustomDraw := PNMCustomDraw(TMessage(AMessage).LParam)^;
case NMCustomDraw.dwDrawStage of
CDDS_PREPAINT:
AMessage.Result := CDRF_NOTIFYITEMDRAW;
CDDS_ITEMPREPAINT:
begin
FontColor := Font.Color;
if Assigned(FOnGetHeaderItemFontColor) then
FOnGetHeaderItemFontColor(Self, NMCustomDraw.dwItemSpec, FontColor);
SetTextColor(NMCustomDraw.hdc, ColorToRGB(FontColor));
AMessage.Result := CDRF_NEWFONT;
end;
else
AMessage.Result := CDRF_DODEFAULT;
end;
end
else
inherited;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
ListView1.OnGetHeaderItemFontColor := GetHeaderItemFontColor;
end;
procedure TForm1.GetHeaderItemFontColor(Sender: TCustomListView;
ItemIndex: Integer; var FontColor: TColor);
begin
case ItemIndex of
0: FontColor := clRed;
1: FontColor := clGreen;
2: FontColor := clBlue;
end;
end;
end.
The whole project you can download from here. Here's the result of the above example:
You can get the native header control from the listview and then mark the specific item of your column as owner drawn. You only need to change the text color (if you don't remove the string flag) when the header item requests to be drawn. The drawing message will be sent to the header's parent - the listview, hence you need to handle the message there. See here for owner drawn header controls.
Example code:
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
...
private
FLVHeader: HWND;
FSaveLVWndProc: TWndMethod;
procedure LVWndProc(var Msg: TMessage);
procedure SetHeaderItemStyle(Index: Integer);
end;
..
uses commctrl;
..
procedure TForm1.FormCreate(Sender: TObject);
begin
FLVHeader := ListView_GetHeader(ListView1.Handle);
SetHeaderItemStyle(1);
FSaveLVWndProc := ListView1.WindowProc;
ListView1.WindowProc := LVWndProc;
end;
procedure TForm1.SetHeaderItemStyle(Index: Integer);
var
HeaderItem: THDItem;
begin
HeaderItem.Mask := HDI_FORMAT or HDI_TEXT or HDI_LPARAM;
Header_GetItem(FLVHeader, 1, HeaderItem);
HeaderItem.Mask := HDI_FORMAT;
HeaderItem.fmt := HeaderItem.fmt or HDF_OWNERDRAW;
Header_SetItem(FLVHeader, 1, HeaderItem);
end;
procedure TForm1.LVWndProc(var Msg: TMessage);
begin
FSaveLVWndProc(Msg); // thanks to #Kobik (cause SO if called later then WM_NOTIFY case on some (all other then mine?) machines)
case Msg.Msg of
WM_DRAWITEM:
if (TWmDrawItem(Msg).DrawItemStruct.CtlType = ODT_HEADER) and
(TWmDrawItem(Msg).DrawItemStruct.hwndItem = FLVHeader) and
(TWmDrawItem(Msg).DrawItemStruct.itemID = 1) then
SetTextColor(TWmDrawItem(Msg).DrawItemStruct.hDC, ColorToRGB(clRed));
WM_NOTIFY:
if (TWMNotify(Msg).NMHdr.hwndFrom = FLVHeader) and
(TWMNotify(Msg).NMHdr.code = HDN_ITEMCHANGED) then
// also try 'HDN_ENDTRACK' if it doesn't work as expected
SetHeaderItemStyle(1);
WM_DESTROY: ListView1.WindowProc := FSaveLVWndProc;
end;
end;

Create Floating Image Popup Window in Delphi XE (Windows 7)

I'm running Delphi XE, under Windows 7 64 bit.
I have these third party components loaded:
Virtual Trees Version 4.8.7
TZip Version 1.5
JVCL 3.45
Graphics32 1.9 Final
GExperts 1.33
DWS
DCP Crypt Version 2.0
TeeChart Pro v2011
I want to create a popup "preview" image of a PDF when the mouse is hoovering over a TListBox Item. I'd figure I would create a TForm within my my window's FormCreate, and hide it, until (ListBox.ItemIndex > -1) on my TfrmMain.ListBoxMouseMove routine.
For now, I'm just trying to master using a JPEG image, instead of a PDF.
I noticed that using the TImage and OnMouseOver is rather SLOW. Is there a faster way of doing this? Maybe using a JEDI component?
unit MainUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, PicUnit, jpeg, GraphUtil;
type
TfrmMain = class(TForm)
lst: TListBox;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure lstClick(Sender: TObject);
procedure lstMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
procedure lstMouseLeave(Sender: TObject);
public
popPic: TfrmPic;
ImagePaths: TStringList;
LastHoover: Integer;
procedure LoadImages(Item: Integer);
end;
var
frmMain: TfrmMain;
Implementation
{$R *.dfm}
procedure TfrmMain.FormCreate(Sender: TObject);
begin
popPic := TfrmPic.Create(nil);
ImagePaths := TStringList.Create;
LastHoover := -1;
end;
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
popPic.Free;
ImagePaths.Free;
end;
procedure TfrmMain.lstClick(Sender: TObject);
begin
if (lst.ItemIndex > -1) then
begin
popPic.Show;
end { ItemIndex > -1 }
else
popPic.Hide;
end;
procedure TfrmMain.lstMouseLeave(Sender: TObject);
begin
frmPic.Hide;
end;
procedure TfrmMain.lstMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
var
HooverItem : Integer;
begin
{ Returns -1 if the mouse is NOT over a item on the list }
HooverItem := lst.ItemAtPos (Point (X, Y), True);
if (HooverItem > -1) and (HooverItem <> LastHoover) then
begin
{ Match the image onto the screen }
frmPic.Left := frmMain.ClientToScreen(Point(X, Y)).X;
frmPic.Top := frmMain.ClientToScreen(Point(X, Y)).Y;
LoadImages(HooverItem);
LastHoover := HooverItem;
if (ImagePaths.Count > 0) then
begin
{ TImage Method }
frmPic.imgStd.Stretch := True;
frmPic.imgStd.Picture.LoadFromFile (ImagePaths [0]);
frmPic.Show;
frmMain.SetFocus;
end
else
frmPic.Hide;
end
else
if (HooverItem = -1) then
frmPic.Hide;
end;
procedure TfrmMain.LoadImages(Item: Integer);
begin
{ Clear off the existing list }
ImagePaths.Clear;
if (Item = 0) then
begin
ImagePaths.Add ('C:\Floating Image Demo\0.jpeg');
ImagePaths.Add ('C:\Floating Image Demo\1.jpeg');
end
else
if (Item = 1) then
begin
ImagePaths.Add ('C:\Floating Image Demo\1.jpeg');
ImagePaths.Add ('C:\Floating Image Demo\0.jpeg');
end;
end;
end.
Well...I found that the Adobe's Acrobat Control component to be just what I needed. It's a little slow, but it's MUCH faster than the TImage method.
Here's my revised solution:
Unit MainUnit;
Interface
Uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
StdCtrls,
OleCtrls,
AcroPDFLib_TLB;
Type
TfrmMain = Class (TForm)
lst : TListBox;
Procedure FormCreate ( Sender: TObject );
Procedure FormClose ( Sender: TObject;
Var Action: TCloseAction);
Procedure lstClick ( Sender: TObject );
Procedure lstMouseMove ( Sender: TObject;
Shift: TShiftState;
X,
Y: Integer );
Procedure lstMouseLeave ( Sender: TObject );
Public
frmPic : TForm;
Pdf : TAcroPDF;
ImagePaths : TStringList;
LastHoover : Integer;
Procedure LoadImages ( Item: Integer );
End;
Var
frmMain: TfrmMain;
Implementation
{$R *.dfm}
Procedure TfrmMain.FormCreate ( Sender: TObject );
Begin
frmPic := TForm.Create (Nil);
ImagePaths := TStringList.Create;
LastHoover := -1;
{ Create the "popup" form, and the PDF viewer object }
frmPic.Height := 160;
frmPic.Width := 200;
frmPic.BorderStyle := bsNone;
Pdf := TAcroPDF.Create (frmPic);
Pdf.Parent := frmPic;
Pdf.Name := 'AcroPDF';
Pdf.Align := alClient;
Pdf.setShowToolbar (False);
End;
Procedure TfrmMain.FormClose ( Sender: TObject;
Var Action: TCloseAction );
Begin
{ Free the objects }
Try
FreeAndNil (Pdf);
frmPic.Free;
ImagePaths.Free;
Finally
{ Stop ALL threads-If this is removed some fonts within the drawings cause Adobe a screw up and keep running, thus causing an AV }
Application.Terminate;
End;
End;
Procedure TfrmMain.lstClick ( Sender: TObject );
Var
CurrentPos : TPoint;
Begin
If (lst.ItemIndex > -1) Then Begin
{ Match the image onto the screen }
Windows.GetCursorPos (CurrentPos);
frmPic.Left := CurrentPos.X + 20;
frmPic.Top := CurrentPos.Y + 20;
{ Load the PDFs }
LoadImages (lst.ItemIndex);
If (ImagePaths.Count > 0) Then Begin
{ Adobe Acrobat Control ActiveX Component }
PDF.LoadFile (WideString (ImagePaths [0]));
PDF.setShowToolbar (False);
PDF.gotoFirstPage;
frmPic.Show;
frmPic.SetFocus;
End;
End { ItemIndex > -1 }
Else
frmPic.Hide;
End;
Procedure TfrmMain.lstMouseLeave ( Sender: TObject );
Begin
frmPic.Hide;
End;
Procedure TfrmMain.lstMouseMove (Sender: TObject; Shift: TShiftState; X, Y: Integer);
Var
HooverItem : Integer;
Begin
{ Returns -1 if the mouse is NOT over a item on the list }
HooverItem := lst.ItemAtPos (Point (X, Y), True);
If (HooverItem > -1) Then Begin
{ Match the image onto the screen }
frmPic.Left := frmMain.ClientToScreen (Point (X, Y)).X + 20;
frmPic.Top := frmMain.ClientToScreen (Point (X, Y)).Y + 20;
End;
End;
{ ---------------------------------------------------------------------------- }
{ ---------------------------------------------------------------------------- }
{ --------------------------- PRIVATE METHODS -------------------------------- }
{ ---------------------------------------------------------------------------- }
Procedure TfrmMain.LoadImages ( Item: Integer );
Begin
{ Clear off the existing list }
ImagePaths.Clear;
If (Item = 0) Then Begin
ImagePaths.Add ('C:\Project_Files\SVN\Local\EXAMPLES, TEMPLATES, MISC\Floating Image Demo\0.pdf');
ImagePaths.Add ('C:\Project_Files\SVN\Local\EXAMPLES, TEMPLATES, MISC\Floating Image Demo\1.pdf');
End
Else if (Item = 1) Then Begin
ImagePaths.Add ('C:\Project_Files\SVN\Local\EXAMPLES, TEMPLATES, MISC\Floating Image Demo\1.pdf');
ImagePaths.Add ('C:\Project_Files\SVN\Local\EXAMPLES, TEMPLATES, MISC\Floating Image Demo\0.pdf');
End;
End; { LoadImages Procedure }
{ ---------------------------------------------------------------------------- }
End.

How can you change the text orientation in cells in the fixed rows in a Delphi TStringGrid

I have a standard TStringGrid on a form.
I have one Fixed Row in the grid that contains a number of columns, which are all TGridColumns objects. I have set the column titles using the object inspector and the default orientation is horizontal. Is there any way you can make the orientation vertical (like you can in cells in Excel)?
Here's how to render the first row's text vertically in Lazarus:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Grids,
StdCtrls;
type
TStringGrid = class(Grids.TStringGrid)
protected
procedure DrawCellText(ACol, ARow: Integer; ARect: TRect;
AState: TGridDrawState; AText: String); override;
end;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
procedure Button1Click(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
procedure TStringGrid.DrawCellText(ACol, ARow: Integer; ARect: TRect;
AState: TGridDrawState; AText: String);
var
TextPosition: TPoint;
begin
if ARow = 0 then
begin
Canvas.Font.Orientation := 900;
TextPosition.X := ARect.Left +
((ARect.Right - ARect.Left - Canvas.TextHeight(AText)) div 2);
TextPosition.Y := ARect.Bottom -
((ARect.Bottom - ARect.Top - Canvas.TextWidth(AText)) div 2);
Canvas.TextOut(TextPosition.X, TextPosition.Y, AText);
end
else
inherited DrawCellText(ACol, ARow, ARect, AState, AText);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
GridColumn: TGridColumn;
begin
for I := 0 to 4 do
begin
GridColumn := StringGrid1.Columns.Add;
GridColumn.Width := 24;
GridColumn.Title.Font.Orientation := 900;
GridColumn.Title.Layout := tlBottom;
GridColumn.Title.Caption := 'Column no. ' + IntToStr(I);
end;
StringGrid1.RowHeights[0] := 80;
end;
end.
Here's how to render the first row's text of the TStringGrid vertically in Delphi:
I would prefer to use the overriden DrawCell procedure because it seems to me as the easiest way to go because if you want to render the text simply in the OnDrawCell event then you should consider:
if you'll have the DefaultDrawing set to True then the text will already be rendered when the OnDrawCell event is fired, so here I would recommend e.g. to store the cell captions in a separate variable, not into Cells property so then no text will be rendered and you can draw your own stored captions vertically
if you'll have the DefaultDrawing set to False then you'll have to draw the whole cell by your own, including the 3D border, what is IMHO not so cool, and I would personally prefer to let the control draw the background for us
Here is the Delphi code which uses the overriden DrawCell procedure. The text is being centered inside of the cell rectangle; please note that I haven't used the DrawTextEx for text size measurement because this function doesn't take the changed font orientation into account.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids;
type
TStringGrid = class(Grids.TStringGrid)
protected
procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState); override;
end;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TStringGrid.DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState);
var
LogFont: TLogFont;
TextPosition: TPoint;
NewFontHandle: HFONT;
OldFontHandle: HFONT;
begin
if ARow = 0 then
begin
GetObject(Canvas.Font.Handle, SizeOf(LogFont), #LogFont);
LogFont.lfEscapement := 900;
LogFont.lfOrientation := LogFont.lfEscapement;
NewFontHandle := CreateFontIndirect(LogFont);
OldFontHandle := SelectObject(Canvas.Handle, NewFontHandle);
TextPosition.X := ARect.Left +
((ARect.Right - ARect.Left - Canvas.TextHeight(Cells[ACol, ARow])) div 2);
TextPosition.Y := ARect.Bottom -
((ARect.Bottom - ARect.Top - Canvas.TextWidth(Cells[ACol, ARow])) div 2);
Canvas.TextRect(ARect, TextPosition.X, TextPosition.Y, Cells[ACol, ARow]);
NewFontHandle := SelectObject(Canvas.Handle, OldFontHandle);
DeleteObject(NewFontHandle);
end
else
inherited DrawCell(ACol, ARow, ARect, AState);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
for I := 0 to StringGrid1.ColCount - 1 do
begin
StringGrid1.ColWidths[I] := 24;
StringGrid1.Cells[I, 0] := 'Column no. ' + IntToStr(I);
end;
StringGrid1.RowHeights[0] := 80;
end;
end.
And here's how it looks like:

Resources