Getting TcxTreeList data from external application written in Delphi - delphi

I need to get all data from a TcxTreeList (a custom Devexpress control) from an external application. I know how to get data from standard controls in external applicatioins (i.e. Tree Views, List boxes, Memos, List Views and so on) sending the right windows messages, but I don't know how to do that for this kind of custom controls.
Reading the documentation from Devexpress I can see the following class hierarchy: TcxTreeList -> TcxCustomTreeList -> TcxExtEditingControl -> TcxEditingControl -> TcxControl -> TCustomControl, but unfortunately I don't know what kind of windows messages to send in order to get data.
For example: in order to get data for a standard Tree View I send messages with parameters like TVGN_CHILD, TVGN_NEXT, TVM_GETITEM and so on in the right sequence.
Can someone suggest me how to get data for the TcxTreeList control?
Of course I'm asking this because it's absolutely impossible to modify the source code of the external application (I don't have it) or to rewrite it.
Thank you in advance

I think you may be in for a bit of an uphill struggle with this. Try the following:
Download & Install the Devex VCL Trial
Create a new Delphi project, add a TcxTreelist to its main form, add two columns to the
tree and, using the tree's Items editor, add two top-level item node to it and a sub-item
to each of these.
Compile & run the project, then inspect its behavoiur using WinSpy++ of similar.
Note that until you click any of the tree nodes, the cxTreelist has no internal windows (I mean
windows from an OS pov).
While you edit one of the data nodes in the tree, a window of class TcxCustomInnerTextEdit is created.
TcxCustomInnerTextEdit is declared in the Devex VCL source in the unit cxTextEdit.Pas and is a descendant
of TCustomEdit. It has message handlers as follows (in addition to TCustomEdit's)
{ private }
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
procedure EMReplaceSel(var Message: TMessage); message EM_REPLACESEL;
procedure EMSetSel(var Message: TMessage); message EM_SETSEL;
procedure WMChar(var Message: TWMChar); message WM_CHAR;
procedure WMClear(var Message: TMessage); message WM_CLEAR;
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMIMEChar(var Message: TMessage); message WM_IME_CHAR;
procedure WMIMEComposition(var Message: TMessage); message WM_IME_COMPOSITION;
procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
procedure WMSetFont(var Message: TWMSetFont); message WM_SETFONT;
procedure WMSize(var Message: TWMSize); message WM_SIZE;
procedure WMUndo(var Message: TWMSize); message WM_UNDO;
protected
procedure MouseEnter(AControl: TControl); dynamic;
procedure MouseLeave(AControl: TControl); dynamic;
As you say how to get data from standard Windows controls, maybe these handler declarations are sufficient for you to see whether you could do what you want.
If you can select the node you want by sending a message to the app, depending
on what you wanting to do, you may be able to do it using the messages for those handlers.
Whether you could do similar using UI-automation, I'm not sure. Generate an import unit
from UIAutomationCore.dll and experiment with what you can do with that, following the example
in How get current url address on mains browsers using UIAutomation?.
You'll notice that one of the enumeration constants in the import unit is
TreeScope_Children but I'd be v. surprised if you could use that to get at
the individual data nodes of the cxTreeList, but you never know.
Btw, TcxCustomInnerEdit implements a couple of interfaces
IcxCustomInnerEdit = interface(IcxContainerInnerControl)
['{468D21B5-48AA-4077-8ED5-4C6112D460B1}']
function CallDefWndProc(AMsg: UINT; WParam: WPARAM; LParam: LPARAM): LRESULT;
function CanProcessClipboardMessages: Boolean;
function GetEditValue: TcxEditValue;
function GetOnChange: TNotifyEvent;
function GetReadOnly: Boolean;
procedure LockBounds(ALock: Boolean);
procedure SafelySetFocus;
procedure SetEditValue(const Value: TcxEditValue);
procedure SetParent(Value: TWinControl);
procedure SetOnChange(Value: TNotifyEvent);
procedure SetReadOnly(Value: Boolean);
property EditValue: TcxEditValue read GetEditValue write SetEditValue;
property Parent: TWinControl write SetParent;
property ReadOnly: Boolean read GetReadOnly write SetReadOnly;
property OnChange: TNotifyEvent read GetOnChange write SetOnChange;
end;
{ IcxInnerEditHelper }
IcxInnerEditHelper = interface
['{35667555-6DC8-40D5-B705-B08D5697C621}']
function GetHelper: IcxCustomInnerEdit;
end;
and IcxContainerInnerControl is declared as
IcxContainerInnerControl = interface
['{1B111318-D9C9-4C35-9EFF-5D95793C0106}']
function GetControl: TWinControl;
function GetControlContainer: TcxContainer;
property Control: TWinControl read GetControl;
property ControlContainer: TcxContainer read GetControlContainer;
end;
but I have no idea whether you could invoke any of these interfaces from a separate Delphi app, even if they are of any use for what you're at.
I haven't studied the code of TcxTreeList as a whole, but I imagine that the image
of each data node is painted "virtually" by the treelist as a whole, except for the currently-focused node if any. So, I wouldn't expect to find
co-existing sub-controls for each of the data nodes and other innards of the treelist.
Depnding or your budget, purchasing the Devex VCL library, which comes with full source, may be a good investment.
Good luck!

Related

Properly overriding WndProc

One day ago I had started to rewrite one of my old components and I decided to improve its readability.
My component is a typical TWinControl that has overridden WndProc to handle a lot of messages of my own. There are so many code for each message and it became a problem for me to read code.
So, looking for a solution to improve code inside WndProc, I have organized these large pieces of code in procedures that called each time when appropriate message has delivered in WndProc. That's how it looks now:
procedure TMyControl.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_WINDOWPOSCHANGED:
WMWINDOWPOSCHANGED(Message);
WM_DESTROY:
WMDESTROY(Message);
WM_STYLECHANGED:
WMSTYLECHANGED(Message);
// lots of the same procedures for Windows messages
// ...
MM_FOLDER_CHANGED:
MMFOLDERCHANGED(Message);
MM_DIRECTORY_CHANGED:
MMDIRECTORYCHANGED(Message);
// lots of the same procedures for my own messages
// ...
else
Inherited WndProc(Message);
end;
end;
Unfortunately Inherited word in these procedures doesn't work anymore!
Important note: in some of WM_XXX messages I didn't call Inherited to perform my own handling of such message, so code shown below will break down my efforts to implement some features.
procedure TMyControl.WndProc(var Message: TMessage);
begin
Inherited WndProc(Message);
case Message.Msg of
WM_WINDOWPOSCHANGED:
WMWINDOWPOSCHANGED(Message);
// further messages
// ...
end;
end;
I also want to avoid inserting Inherited after each message-ID as shown below, because it looks awful and I think there is exists more elegant way to override WndProc.
procedure TMyControl.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_WINDOWPOSCHANGED:
begin
Inherited WndProc(Message);
WMWINDOWPOSCHANGED(Message);
end;
// further messages
// ...
end;
end;
So my question is:
how to properly override WndProc to have an ability to use code grouped in procedures and to be able to call for original window procedure only for some messages?
As RM's answer stated, your message handling methods can call inherited WndProc(Message) instead of just inherited, and that will work fine.
However, by introducing methods with the same names as the messages they are processing, you are exposing knowledge of the specific messages you are processing. So you may find it easier to just use Message Methods instead of overriding WndProc, eg:
type
TMyControl = class(...)
private
procedure WMWindowPosChanged(var Message: TMessage); message WM_WINDOWPOSCHANGED;
procedure WMDestroy(var Message: TMessage); message WM_DESTROY;
procedure WMStyleChanged(var Message: TMessage); message WM_STYLECHANGED;
// and so on ...
end;
Your message methods can then call inherited (or not) as needed, eg:
procedure TMyControl.WMWindowPosChanged(var Message: TMessage);
begin
inherited;
//...
end;
Calling inherited WndProc from WMWINDOWPOSCHANGED will call the inherited one. So you can do it like this:
procedure WMWINDOWPOSCHANGED(var Message: TMessage)
begin
// call inherited WndProc if you need to
inherited WndProc(Message);
.. do you own processing
end;

Is possible to use messages in a class procedure?

I want use messagesin my program and i've a question: Can I use messages in a class procedure or Can I use messages in a procedure without class?
Here is my code:
const
WM_CUSTOM_TCP_CLIENT = WM_USER + 10;
type
TFeedbackEvent = class
public
class procedure feedback(var msg: TMessage); message WM_CUSTOM_TCP_CLIENT;
end;
The Delphi returns the following message:
[Error] unit.pas(33): Invalid message parameter list
Thank you very much.
There is a very nice article on the topic: Handling Messages in Delphi 6. This is a must read.
Handling or processing a message means that your application responds
in some manner to a Windows message. In a standard Windows
application, message handling is performed in each window procedure.
By internalizing the window procedure, however, Delphi makes it much
easier to handle individual messages; instead of having one procedure
that handles all messages, each message has its own procedure. Three
requirements must be met for a procedure to be a message-handling
procedure:
The procedure must be a method of an object.
The procedure must take one var parameter of a TMessage or other message-specific record type.
The procedure must use the message directive followed by the constant value of the message you want to process.
As you can read in the article, the procedure must be a method of an object, not a class. So you cannot just use message handlers in a class procedure.
A possible workaround to handle messages in a class instance (also in object instance or window-less applications), is to manually create window handle via AllocateHWND, and process messages yourself via a WndProc procedure.
There is a good example on this in delphi.about.com: Sending messages to non-windowed applications (Page 2):
The following sample is a version of the above example, modified to work with class method. (If using class method is not really required, use original example from the link above instead):
First, you need to declare a window handle field and a WndProc procedure:
TFeedbackEvent = class
private
FHandle: HWND;
protected
class procedure ClassWndProc(var msg: TMessage);
end;
procedure WndProc(var msg: TMessage);
Then, process the messages manually:
procedure WndProc(var msg: TMessage);
begin
TFeedbackEvent.ClassWndProc(msg);
end;
procedure TFeedbackEvent.ClassWndProc(var msg: TMessage);
begin
if msg.Msg = WM_CUSTOM_TCP_CLIENT then
// TODO: Handle your message
else
// Let default handler process other messages
msg.Result := DefWindowProc(FHandle, msg.Msg, msg.wParam, msg.lParam);
end;
Finally, at the end of the file, declare initialization and finalization section to create/destroy the handle:
initialization
FHandle := AllocateHWND(WndProc);
finalization
DeallocateHWnd(FHandle);
Of course, this is not the recommended way to do this (especially watch for problems with initialization/finalization), it was just an example to show that it is possible.
Unless you have some very strange requirement to use class method, its better to use regular class method and object constructor and destructor instead initialization and finalization sections (as shown in Sending messages to non-windowed applications (Page 2)).

Detect help button click in MessageBox?

In Delphi XE7, I need to use the Help button in a MessageBox. MSDN states:
MB_HELP 0x00004000L Adds a Help button to the message box. When the
user clicks the Help button or presses F1, the system sends a WM_HELP
message to the owner.
However, when I click the Help button in the MessageBox, no WM_HELP message seems to be sent to the application:
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
if Msg.message = WM_HELP then
CodeSite.Send('ApplicationEvents1Message WM_HELP');
end;
procedure TForm1.btnShowMessageBoxClick(Sender: TObject);
begin
MessageBox(Self.Handle, 'Let''s test the Help button.', 'Test', MB_ICONINFORMATION or MB_OK or MB_HELP);
end;
So how can I get the MessageBox Help button click and how can I detect which MessageBox did it come from?
The documentation says, with my emphasis:
the system sends a WM_HELP message to the owner.
That is MSDN code for the fact that the message is delivered synchronously directly to the window procedure. In other words, it has been sent using SendMessage, or an equivalent API.
You've attempted to handle it in TApplicationEvents.OnMessage which is used to intercept asynchronous messages. That is messages that are placed on the message queue. These messages are (typically) placed on the queue with PostMessage.
So the reason for you never seeing the message in TApplicationEvents.OnMessage is that the message is never placed in the queue. Instead you need to handle the message in the owner window's window procedure. In Delphi the simplest way to do that is as follows:
type
TForm1 = class(TForm)
....
protected
procedure WMHelp(var Message: TWMHelp); message WM_HELP;
end;
....
procedure TForm1.WMHelp(var Message: TWMHelp);
begin
// your code goes here
end;
As for how to detect which message box was responsible for the message being sent, there's no simple way when using MessageBox. Perhaps the best would be to switch to MessageBoxIndirect. That allows you to specify an ID in the dwContextHelpId field of MSGBOXPARAMS. That ID is passed to the recipient of the WM_HELP message, as described in the documentation.
If you are going to display a topic an a help file, in response to the user pressing the help button, then you might consider the VCL function MessageDlg. That allows you to pass a help context ID, and the framework will show the application help file, passing on that help context ID.
Minimal working sample:
type
TForm20 = class(TForm)
procedure FormCreate(Sender: TObject);
protected
procedure WMHelp(var Message: TWMHelp); message WM_HELP;
end;
procedure TForm20.FormCreate(Sender: TObject);
begin
MessageBox(Handle, 'Help test', nil, MB_OK or MB_HELP);
end;
procedure TForm20.WMHelp(var Message: TWMHelp);
begin
Caption := 'Help button works';
end;

Delphi: How to Receive a Custom Windows Message from PostMessage?

I have two applications that I need to communicate via PostMessage (SendMessage is ruled out as I need to have the second Application able to output to Excel whilst being called.
So far Application 1 makes a call to Application 2, making sure that it is open, and when it is, App 1 is set to disabled.
When the user is finished with Application 2 I need to send a message back to Applciation 1 to allow it to unlock.
I have registered the same Windows Message in each application using:
const
MyMessage = 'My-Message';
var
MyMessageID: cardinal;
procedure TMF.FormCreate(Sender: TObject);
begin
MyMessageID := RegisterWindowMessage(MyMessage);
end;
And in Application 2 I can Post the message to Application 1 using:
targetHandle := FindWindow(Pchar('TMF'), Pchar('Send Test'));
...
if PostMessage(targetHandle, (MyMessageID), 0, 0) then
...
What I don't understand is how I declare the Handler for the Message in Application 1.
If I was using a standard Windows Message, such as WM_COPYDATA I'd declare a procedure
procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
But that falls down because I can't declare MyMessageID early enough.
Perhaps it's because it's Friday afternoon, but what am I missing?
You cannot use the message keyword because the message constant is not known at compile time. Instead you have to override WndProc:
procedure WndProc(var Message: TMessage); override;
....
procedure TMF.WndProc(var Message: TMessage);
begin
inherited;
if Message.Msg = MyMessageID then begin
....
end;
end;

TTouchKeyboard: send keystroke to same program?

I saw your tip about : TTouchKeyboard: send keystroke to other program
How can I send the keys to the other form in the same Delphi application?
And how can I call the form with the TTouchKeyboard? (Show, showModal, parameters?)
Thanks!
ShowModal is a bad idea... you focus the caller...
You can still use the same tip with the form which contains the keyboard, in order to stay disable...
Then, you can add a property with the handle of the form which should get the keystroke.
And finally, you hack the TTouchKeyboard to set the focus to the form with the handle you previously set...
For instance, your TTouchKeyboard hack could be like this:
type
TMyKeyboard = class(TTouchKeyboard)
protected
procedure WndProc(var Message: TMessage); override;
end;
type
TForm1 = class(TForm)
...
private
fHandleOfTheTargetForm: HWND;
public
property HandleOfTheTargetForm: HWND read fHandleOfTheTargetForm write fHandleOfTheTargetForm;
...
procedure TMyKeyboard.WndProc(var Message: TMessage);
begin
if (Assigned(Form1)) then
begin
if Form1.HandleOfTheTargetForm <> 0 then
begin
SetForegroundWindow(HandleOfTheTargetForm);
end;
end;
inherited;
end;
You can find a quick demo project here.

Resources