Drag&Drop using Winapi for Third-Party Control - delphi

I need to integrate drag&drop of our existing application with a new component.
Our application uses Winapi.ActiveX do implement this. The new component comes with its own drag&drop events which aren't Winapi-compatible, though.
The main problem for now is dragging things into the new control. Dragging out of it should be achievable, if all else fails by caching the object under the cursor on mouse down.
As I understand it you can't override drag&drop registration, that is if I call RegisterDragDrop on the component before they do it somewhere in their code, I'll be the one holding the registration.
The observed behavior, however, is that calling RegisterDragDrop reports success, but dragging something over the control doesn't lead to a DragEnter event on the IDropTarget. Their drag&drop event fires just fine, even after RegisterDragDrop, but as I said, the returned data object only holds valid data for a select few types of draggable objects.
I also tried registering the panel that contains this component for drag&drop, but that didn't work either.
What would you suggest?
Update:
I realized, I was trying to register the wrong handle. When I try to register the right handle, I actually get a drag&drop already registered error.
I guess I didn't formulate it too clearly, but the question is, how do you implement drag&drop for a closed-source component that already has it's own implementation, which doesn't provide enough flexibility for your taste?
What I can think of:
Bug the developer of the component until they change the component.
Do some API hooking to go in between the component's implementation and the OS.
Hopefully something else that is simple.
Update:
Thanks so far, now I can drag things into the control the way I want it.
The next step is dragging things out. Here the problem is that other drop targets in the application don't understand the control's IDataObject. For a different control we already have code that uses IDataObject::SetData to append data in a format that all the application's drop targets can understand.
The difference with the new control, however is that when it initiates a drag&drop operation, it doesn't use a IDataObject that has IDataObject::SetData implemented (returns E_NOTIMPL); Is there a way to replace the IDataObject with a different one, while the d&d operation is ongoing?

Try to reset already registered IDropTarget of your ActiveX control after control creation with the following code:
const
sOleDropTargetInterface = 'OleDropTargetInterface';
procedure ResetDropTarget(AWnd: HWND);
var
Unknown: IUnknown;
begin
Unknown := IUnknown(GetProp(AWnd, PChar(GlobalFindAtom(sOleDropTargetInterface))));
if Assigned(Unknown) then
try
if not SetProp(AWnd, PChar(GlobalFindAtom(sOleDropTargetInterface)), 0) then
RaiseLastOSError;
Unknown._Release;
finally
Unknown := nil;
end;
end;
Update
kobik points the right solution: using of RevokeDragDrop function.

Related

Can ShortCut-Key, that has been processed by Delphi TAction, be allowed to propagate further to other components?

Core problem: I don't like the way how TcxGrid delete (that is initiated by the default Ctrl+Del shortcut key for the TcxGrid or by the Delete button of the cx-navigator) is working: it is simple and direct call to the delete of the underlying dataset, there is no TcxGrid(/DataController).OnDelete(Sender: TObject, var AHandled: Boolean) event for the cxGrid or cxGridDataView. And from https://www.devexpress.com/Support/Center/Question/Details/Q308755/tcxgrid-deleting-record I understand that I can only configure the confirmation message, but I have no control over the Delete itself, i.e., I can not implement custom Delete and therefore I don't even try to ask for the straight solution of this problem. My question is about workaround.
That is why I am refusing to use cx-navigator and I opt to introduce TAction that handles Ctrl+Del shortcut key for the entire form. I have multiple grids on the form each with its own problematic Delete procedure that should be handled in non-standard way by the custom procedure.
That is why my DeleteAction determines the ActiveControl (TcxGridSite, TcxGrid) initially and then calls relevant Delete procedure afterwards. All that is fine. But some other components (e. g. TcxDBTextEdit) have default Ctrl+Del handling as well and that handling is very, very good. But if shortcut key Ctrl+Del is handled by action (and it is marked as handled even in the case when no active grid can not be found and therefor even in the case when ActionExecute has not done anything useful) then further propagation is stopped. That can be observed empirically and that can be seen theoretically from http://edn.embarcadero.com/article/38447 .
But maybe still there is some workaround how the shortcut key that has been processed by the ActionExecute can still be propagated further to the default components if they are Active controls.
I know that TAction is for (form-wide global) menu functionality but TcxGrid is not extensible enough and that is why should try to stretch the Delphi TAction design as well.
Probably there would be various ways to achieve the processing you require, there are quite a few places you can intervene during key processing, as you've already read from Peter Below's article linked in your question.
A place to intervene that I can think of which would contain all the pieces together is the form's IsShortCut method. One probable disadvantage of such a method could be that you'd have to implement it on all the forms that you want their key handling behavior changed.
Normally key processing continues only if it's not handled by shortcuts. Below solution reports the shortcut to be not processed for that reason, but then has to cause the execution of the action manually. I tested the code with a regular edit as can be seen, but I guess it shouldn't make any difference.
function TForm1.IsShortCut(var Message: TWMKey): Boolean;
begin
if (ActiveControl is TEdit) and (Message.CharCode = VK_DELETE) and
(KeyDataToShiftState(Message.KeyData) = [ssCtrl]) then begin
Result := False; // if you don't require the action to be executed, just exist here
ActionList1.IsShortCut(Message); // executes the action, ignore result
// returning false will result in main form's shortcut handler to be called
// below is only required if this is the main form
if Application.MainForm = Self then
Message.CharCode := 0;
end else
Result := inherited IsShortCut(Message);
end;

Published interface properties bug and workarounds

I wrote a set of components that link to each other via published interface properties. They are registered and installed in a design package.
Using published interface properties is not that common in Delphi, and thus, unsurprisingly, doesn't seem to work that well.
It works fine when components reside on the same form, however interface property links between components on different forms cause issues.
Unlike object links to components on another form, interface links don't seem to be recognized by IDE. What I mean is best described by an example, when you have 2 forms open in IDE, and have links between components on them, then trying to switch to form view as text (Alt+F12) would cause IDE to correctly complain that:
Module 'UnitXXX.pas' has open descendents or linked modules. Cannot close.
But if the property is an interface then this does not happen, what happens instead is that the link is severed (and that's the best case scenario when you use Notification mechanism to clear references, otherwise you're left with an invalid pointer)
Another problem, likely as a consequence of the same bug is that when you open a project in IDE, the order in which forms will be reopened is undefined, so IDE can try to open a form that contains components that have interface links to components on another form, but that other form is not recreated yet. So this effectively results in either AV or severed links.
Back in 90s while I used Datasets and Datasources I remember similar issues with links between forms disappearing, so this is somewhat similar.
As a temp workaround I added duplicate published properties, for each Interface property I added another that is declared as TComponent. This makes Delphi aware there is a link between forms, but is an ugly workaround to say the least.
So I wonder if there is something I can do to fix this issue ? It's an IDE bug and likely not fixable directly, but perhaps I can override something or otherwise hook in to streaming mechanism to more effectively workaround this bug.
I haven't ever gone so deep into streaming mechanism, but I suspect the Fixup mechanism is supposed to deal with this. There is a csFixups TComponentState so I hope a workaround is possible.
Edit: Using D2007.
Update:
New updated reproducible example uploaded to http://www.filedropper.com/fixupbugproject2
Added property ComponentReference: TComponent so that it's easy to compare and trace interface vs component streaming.
I narrowed the problem down to assembler level which is a bit out of my depth.
In procedure GlobalFixupReferences in classes unit it calls:
(GetOrdProp(FInstance, FPropInfo) <> 0)
which eventually executes:
function TInterfacedComponent.GetInterfaceReference: IInterface;
begin
// uncomment the code bellow to avoid exception
{ if (csLoading in ComponentState) and (FInterfaceReference = nil) then
// leave result unassigned to avoid exception
else
}
result := FInterfaceReference; // <----- Exception happens here
end;
As you can see from the comment, the only way I found to avoid the exception is to leave the result unassigned, but that breaks the functionality since comparison above in GlobalFixupReferences fails due to GetOrdProp <> 0, which severes the link.
tracing deeper the more exact location of exception is in
procedure _IntfCopy(var Dest: IInterface; const Source: IInterface); in system unit
This line in particular raises an read of address 0x80000000
{ Now we're into the less common cases. }
##NilSource:
MOV ECX, [EAX] // get current value
So, why MOV fails and what's wrong with ECX or EAX I have no idea.
To summarize, the problem happens only with published interface properties that have a getter method, and the property points to component on another form/module (and that form/module is not recreated yet). In such case restoring form DFM causes an AV.
I'm pretty sure the bug is in the ASM code in GetOrdProp, but it's beyond my ability to fix, so the
easiest workaround is to use a Field instead of a getter method and read it directly in the property. This is, fortunately good enough for my case currently.
Alternatively, you can declare the property as TComponent instead of interface, then write a TComponentProperty descendant, override ComponentMayBeSetTo to filter component that don't support the required interface. And of course register it using RegisterPropertyEditor

Make sure nested child control is visible

I have a utility routine that I call when validating user input in a dialog fails. It sets focus to the offending control, beeps and displays an appropriate message to the user. This works well as long as the offending control is not hidden. Now I have to adapt this to a situation where the relevant controls are children of some kind of collapsible group boxes (possibly even nested), and I have to make sure that the "ancestor" boxes are expanded before calling SetFocus.
Now I have a few possibilities:
Build knowledge about the collapsible component into the error reporting routine. I'd like to avoid that as the routine should rather stay generic.
Pass an callback that can be called prior to (or instead of) SetFocus. This is error prone because one has to remember to pass the callback at all the relevant places.
My favourite solution would probably be an event (or overrideable method) (probably in TWinControl) that tells a container control "please make sure you and you child controls are visible" but I don't know of such a thing.
Any ideas how I can handle this situation?
Define an interface with a method called something like: EnsureVisible.
Implement it for all your components (you may need to derive your own versions of some of these components). This allows different controls to have quite different behaviour.
When a control needs to make sure it is visible it walks its parents and calls EnsureVisible if the interface is implemented.
If you don't like interfaces then do it with a custom Windows message, but you get the basic idea.
In my opinion the best solution would be a separate routine that builds knowledge about all container controls, allowing the dialog validation routine to stay generic and at the same time being focused enough to be easily tested and maintained. Something along the lines of:
procedure ForceControlVisible(C: TControl);
begin
// Recursive code
if Assigned(C.Parent) then ForceControlVisible(C.Parent);
// Code specific to each container control class
if C is TTabSheet then
begin
// Code that makes sure "C" is the active page in the PageControl
// goes here. We already know the PageControl itself is visible because
// of the recursive call.
end
else if C is TYourCollapsibleBox then
begin
// Code that handles your specific collapsible boxes goes here
end
end;
OOP-style methods that rely on virtual methods or implementing interfaces would be way more elegant, but require access to the source code of all the controls you want to use: even if you do have access to all required sources, it's preferable not to introduce any changes because it makes upgrading those controls difficult (you'd have to re-introduce your changes after getting the new files from the supplier).
Each component knows its Parent. You can walk up the list to make each parent visible.

Delphi ignores CS_OWNDC flag when creating TFrame component, wglMakeCurrent subsequently fails

I'm trying to put together some components for using OpenGL in RAD Studio 2009. I want to have multiple Rendering Contexts.
My idea is to have something like a "main OpenGL component", called GLMaster. It's a TFrame descendant so it should provide me with a DC. Also there's a component for a GLMonitor, acting as camera.
I create an OpenGL RC in the overriden GLMaster.Loaded inside an "if not (csDesigning in ComponentState) then"-clause. The DC for this I do not keep, it's stated in the RAD Studio help that you shouldn't:
"TWinControl.Handle
Do not refer to the Handle property during component creation or streaming. The underlying window does not exist until the first time the Handle property is referenced. When this occurs, the HandleNeeded method is called automatically."
I handle this by function pointers in components using GLMaster pointing to GLMaster.GetCurrentDC (returns a HDC).
During destruction GLMonitor wants to clean up some render-to textures and other OpenGL resources. When retrieving the DC for a call to wglMakeActive the function pointer is followed and execution jumps to GLMaster.GetCurrentDC. Pointer(Self) tells me that it's the same GLMaster that we created the "master RC" in during the streaming of the components. The property [GLMaster.]Handle is now invalid! If I however free the GLMonitor in the app form's OnClose (using simply GLMonitor_1.Free;) the Handle inside GLMaster.GetCurrentDC is valid end everything works.
I managed to see that the handle used in GLMaster.Loaded is not the same (!?!?) as after the initialization is done and the app form is showed to the user. When I found this I googled my problem and added an overriden CreateParams to add CS_OWNDC to the GLMaster component. A breakpoint there tells me that it's being executed.
Why does the VCL/Delphi silently provides the newly created components with other Handles, and thereby indirectly other DCs, even if I provide the OWNDC flag? Is there someway to get an "ALL components are now loaded and their properties are read from the .dfm file and"-message so I can run GLMaster.InitEverything inside the component?
Right now I'm thinking the fastest way onward is to put this InitEverything in the main form's OnShow handler. And then a call to GLMatser.CleanAll in the main form's OnClose. But I don't want any code in the form, that's the reason I created the components in the first place!
P.S.
Not that I think it matters for this particular problem but I use OpenGL 3.2 Core Profile.
I'm answering the "Why does the VCL/Delphi silently provides the newly created components with other Handles".
Why an control's Window Handle might change at run-time
Your TFrame (an TWinControl descendant) is placed on an other TWinControl, let's say a TForm. The parent container provides property wrappers around many settings, allowing us to easily make changes; Some of those changes can't be implemented without re-creating the windhow handle. You can get a long list of properties that cause this to happen by doing a search for RecreateWnd in Forms.pas, Controls.pas etc.
Examples of properties that call RecreateWnd to implement a change at run-time:
TScollBox.BorderStyle
TForm.BorderIcons (for MDI childs)
When your TFrame's parent needs to change Window Handle, your TFrame is going to have to change Window Handle as well. Your Delphi Control, no matter the descendense, needs to be handle this, and other controls have it worst: Controls that are implemented as wrappers around Windows controls need to save state and reload state (TEdit, TComboBox, ...)
What I think is going on
While streaming (loading your form) some control does something that needs a Window Handle before loading finishes. It's very likely your own code! After everything finishes loading the Form might need to re-create it's Handle, and this in turn causes your Frame's handle to be changed.
What you might want to override
Given the way the VCL works, you need to be prepared for your Window Handle to change. You should do a search in Controls.pas for the words Handle, HDC. If your control is so intimately linked to it's Window Handle and HDC, it's in your best interest to read up on this stuff.
Take a look at those routintes. Maybe you can sopot a better place to hook:
CreateHandle
CreateWnd
DestroyHandle
DestroyWnd
GetDeviceContext
If the title is the question, then no, Delphi does not ignore CS_OWNDC when creating a frame. Just tested with the below procedure with a frame on a form. When the flag is not specified, the line is drawn on itself again and again, resulting with a line from (0,0) to (10,10). When the DC is owned, the line extends from (0,0) to (50,50), no need to tell but the retrieved DC is same at all times.
procedure TFrame2.WmPaint(var Msg: TWMPaint);
var
i: Integer;
DC: HDC;
Pt: TPoint;
begin
inherited;
for i := 1 to 5 do begin
DC := GetDC(Handle);
GetCurrentPositionEx(DC, #Pt);
LineTo(DC, Pt.X + 10, Pt.Y + 10);
ReleaseDC(Handle, DC);
end;
end;
if your question is this:
ALL components are now loaded and their properties are read from the .dfm file and
The answer is:
Yes, just override the Loaded method of your component. It is called when all the references are valid (after dfm reading is finished).

Retrieving Delphi Window Handles

I am trying to get the window handles to a Delphi application from an external application. I can see that there are a few windows created (TApplication, TFrmMain and a few others), and I know that TApplication is the "controller", but never visible. However, can I read what the value for the real window is? I know that it is TFrmMain (for this specific application), but is it possible to actually figure this out somehow? Is the information stored in the window properties,or somewhere else? Thanks!
No, there is no documented way to discover which of the windows represents Application.MainForm from outside the application. In newer versions of Delphi, the main form's window handle isn't necessarily Application.MainForm.Handle anyway; applications can handle the OnGetMainFormHandle event to return whatever they want — that's used for choosing the parent window for modal dialogs.
You can guess by looking for windows with "main" in their class names, but even if you find one, there's no guarantee that there's only one instance of it. Applications can have multiple top-level windows, in which case it doesn't make much sense to designate any one of them as the "main" one.
The class name of any Delphi form is also the registered window classname of the underlying "Windows window". So you should be able to use the FindWindow() Windows API call to get the window handle of TFrmMain a little something like:
hWnd := FindWindow('TFrmMain', NIL);
If there are (potentially) multiple instances of a given form class name then you may be able to distinguish between them by using the 2nd parameter (Window Name, i.e. "caption" or title). If that still isn't enough then you may need to get a little bit more sophisticated and look at using the EnumWindows() function and checking the properties of the windows to find the one of interest.
To test the classname of an arbirary window handle (e.g. in your callback function that you use with EnumWindows()), use GetClassName(), e.g:
function GetWindowClassName(const aHWND: HWND): String;
var
buf: array[0..255] of Char; // Tip: Use a more appropriately sized array
begin
GetClassName(SomeHWND, #buf, Length(buf));
result := buf;
end;
...
if SameText(GetWindowClassName(hwnd), 'TFrmMain') then
...
etc
Without specific details of your particular implementation challenge it's difficult to say which is most likely to work best for you, but hopefully that should be enough pointers to get you heading down the right track.
I usually use WinDowse to help me get started, but then you have to use the API functions as described by Deltics.

Resources