TTreeView drawing error when deactivating a form - delphi

I have found what appears to be a bug related to TTreeView.
Take a form containing a TTreeView with HideSelection set to True.
Make the tree view multi-select and select multiple items in the tree view.
Show another form so that your app has two forms.
Give the tree view the focus and then click in the other form.
The result looks like this:
But in fact there should be no items highlighted. Interestingly, the last item is selected and it is no longer highlighted, as indeed should all the other items. It appears that the most recently clicked item is the one that gets the special treatment.
If instead you click in the edit box (or indeed any other control that takes focus) then all items are correctly hidden. So it's fine for the focus to transfer to another control on the form—the problem seems to be limited to deactivating the form.
I have discovered by trial and error that I can fix this by calling Invalidate on the tree view whenever the form is deactivated and activated (need to prevent mirror image of the bug). However, I'm looking for a better understanding of what the bug is and how to fix it in a less invasive manner, i.e. at the tree view level rather than the containing form level.
So, to summarise, my questions are:
What exactly is causing the problem?
How can I fix it without writing code that hooks TForm events?
Submitted the issue as QC#94908.

The solution seems to be to respond to NM_SETFOCUS and NM_KILLFOCUS notifications by invalidating selected nodes. You can modify TCustomTreeView.CNNotify directly or you can write a new TCustomTreeView descendant. Here is a quick hack only to show the missing code:
type
TTreeView = class(ComCtrls.TTreeView)
private
procedure CNNotify(var Message: TWMNotifyTV); message CN_NOTIFY;
end;
procedure TTreeView.CNNotify(var Message: TWMNotifyTV);
begin
case Message.NMHdr^.code of
NM_KILLFOCUS, NM_SETFOCUS:
InvalidateSelectionsRects;
end;
inherited;
end;
Edit: David's QC report.

Related

How do I assure that mouse pointer appears when mouse moves over form after commanding the drop down for a combobox to show?

I have a problem where:
I have a form with just a combobox.
The combobox has focus and mouse is not hovering over the form when item 3 happens.
I trigger the combobox's drop-down list to show on a key-press event.
When the drop-down list is visible and then I move my mouse pointer over the form, the pointer is either invisible, shows that it is busy, or shows the resizing icon but does not turn back to a normal pointer when over the form.
Is there something that can be done to assure that, when the drop-down of the combobox shows, that the mouse pointer is visible when I move the pointer over the form?
I have tried:
Applicaiton.ProcessMessages after showing the drop-down.
Changing focus to the form the combobox is on after showing the drop-down.
Adding Key := #0; after calling the drop-down to show.
procedure TForm1.ComboBox1KeyPress(Sender: TObject; var Key: Char);
begin
SendMessage(ComboBox1.Handle, CB_SHOWDROPDOWN, Integer(True), 0);
Key := #0;
end;
Tried using a timer to trigger the drop-down within the key-press event.
Tried using "SetCursor" after commanding the drop-down to appear.
Tried using ".DroppedDown", but did not see any difference in result from that of "SendMessage".
I would hope to be able to show the mouse pointer after the drop-down is displayed, but it is hidden instead. Thanks for any suggestions.
(NOTE: This problem I have run into is not exlusive to Delphi. I was able to duplicate the issue using Visual C# 2017. Either way, if there is a way to correct this, it would be good to know).
As already commented to the question, the issue is not Delphi related. You can observe the same behavior in dialog boxes which contains a similar combo that the OS presents. One example is the one on the "run" dialog.
Involving a single environment, re-setting the cursor in an OnDropDown event handler fixes the problem.
procedure TForm1.ComboBox1DropDown(Sender: TObject);
begin
winapi.windows.SetCursor(Screen.Cursors[Cursor]);
end;
Originally I tested the above because no one calls SetCursor after the drop down. Though it seems that no one calls it before either. So I have no idea about the cause or why the above fix works.

Strange wm_keydown behaviour with THtmlEdit

This is a strange one: This is the situation
The application builds an edit form at run-time from an XML document. The UI/UX design is such that there are three levels of nested TTabControls and for the final (lowest) level the TTabItem is created at run-time. This has a TVertScrollBox which itself contains a column-grid control of mine and that (at last!) contains the THtmlEditor.
The problem is that when the controls are built and the content loaded, the THtmlEdit initially ignores keydown. It will respond to mouse events, the caret can be positioned. Switching to a different top-level tab, or away from the whole application and back, then cures the issue and the editor responds to keydown messages.
I have tried putting a breakpoint in procedure THtmlEditor.KeyDown() ... and then tracing back up the call-chain. There doesn't seem to be anything behaving differently. I've paid particular attention to input focus, explicitly calling Editor.SetFocus even though that is apparently already called ...
I have tried putting a conditional breakpoint in function TPlatformWin.HandleMessage: Boolean; ... and my only observation is that when in non-working mode, the call to DispatchMessage(Msg) doesn't arrive at the editor, or its parent form.
I have tried to build a MDC for this, replicating the structure outlined above but ... that always works!
What can I try next?
Has anyone seen this behaviour (and fixed it)?
EDIT:
The detail I didn't mention -- didn't think about it -- is that for the error condition to show, the control focused before the THmtlEditor is a TWebBrowser. If I set focus on a TEdit after TWebBrowser and then to THtmlEditor it seems to work.
Grrr!
This is only half an answer, but for future reference, this is what I've done:
It's a bodge :-(
I've put a TEdit on the main form and set it invisible. I've then added a handler to the MainForm OnFocusChanged which sets a boolean trap-flag to track if TWebBrowser has previously been focused.
For the instances of THtmlEditor I've added an OnClick handler which checks the trap-flag and makes the TEdit visible, calls SetFocus on it, re-hide the TEdit and return True so that the THtmlEditor can re-set focus to itself. The trap-flag is to avoid unnecessarily losing focus if the user is just clicking in the THtmlEditor.
The next step -- which I may never get to -- would be to trace through the focus code for TEdit. My suspicion is that it all relates ITextInput which TEdit supports and THtmlEditor doesn't.

TRadioButton dropped on TListBoxItem behaves erratically when scrolled

I am trying to implement a Firemonkey TListbox that has a number of TListBoxItems. On one of the TListBoxItems I have placed a number of TRadioButtons by simply dropping them on the TListBoxItem at design time. When the application is run the visual appearance of the buttons is erratic when the TListBoxItem is scrolled off the screen and back on.
Another TListBox question here at StackOverflow makes the point that a TListBox does not host any item other than a TListBoxItem. That being said, there seems to be no such restriction that I can find about a TListBoxItem. I have routinely placed TEdit, TLabel and TComboBox components in this manner without difficulty.
To duplicate the condition start a new Firemonkey desktop application; drop a TListBox on the form and set it to alClient; populate the TListBox with a number of TListBoxItems; set the heights of the TListBoxItems or Form so that when run, you are able to scroll the TListBox; drop three TRadioButtons on the topmost TListBoxItem; set the GroupName for all the TRadioButtons to the same name; run the application; select a TRadioButton so that it appears selected; scroll the TListBox so that the TListBoxItem containing the buttons scrolls off the form; scroll back; continue selecting different TRadioButtons and scrolling until anomalies are observed.
Questions: First and probably most important, am I permitted to simply drop components on a TListBoxItem at design time? Has my previous success with this technique been simply accidental?
Second and also of importance for an application I'm working on, if this is NOT a viable approach to my UI, can anyone suggest better approaches?
TIA
I entered a Report at Embarcadero's Quality Central and after a few exchanges with Tomohiro-san along with Marcus Adams suggestion that the problem is related to scrolling came up with the following:
Tracking through the various cases mentioned it appears that there are a number of other cases that are related to this one because of the use of scrolling. When an item is scrolled off the screen and then scrolled back the system apparently reapplies the styles to the items being returned to visibility. At least, in the few cases I have traced that seems to be the case. It is on this reapplication of style and rendering that the problem seems to occur. In my own examples I have added a button that displays the IsChecked property for the controls and the rendering does not reflect the property correctly in all cases, suggesting that it is the style/rendering that is causing the issues.
The related issues I was able to find are:
QC 120593
QC 117381
QC 119638
QC 117658
I'm waiting for a resolution.
I ran into the same problem. I found no solution, but as a work-around, I ended up just replacing the radio buttons with check boxes and adding code so that they behaved in a similar manner to Radio buttons. E.g., only allowing one of a group of check boxes to be selected...

Can Windows manage multiple focus points on a single control?

I'm building a custom control which is to have multiple focus points. For example, within 1 control, let's say there's 3 regions (could be defined as a rect on the canvas) which are to be able to have focus. Depending on which one has focus, I don't want the default Windows dotted line around it, but some special handling. I know nothing about how to even give 1 custom control its own focus.
The original project was a single TPanel with a few VCL controls on it, each of course being its own window, thus having its own focus. But now I'm putting it into a custom class of its own, which these 3 controls will no longer exist (they were only there in version 1 as a prototype) and I need to now somehow mimic the focus in these different regions.
I guess similar to something as simple as a TListBox, where certain items within that control get the focus instead of the entire control its self.
Here's a picture to help demonstrate what I'm making...
The one on the top is the original with the buttons. But the one on the bottom is the new one I'm building which is all custom drawn.
To elaborate, I'd like to see if Windows already has special handling for this type of scenario before I go and re-invent the wheel.
I'm not looking for the "Easiest" way to accomplish this. And I also do not want recommendations to revert back to how I had it before, because there's many reasons I don't want 1 control with multiple other controls within. I just need a yes or no, with an explanation.
More
I just realized the main concern is the use of the tab key. The control has to first get the focus, start the focus on whichever sub-item it's supposed to, then respond to tab on my command until it reaches the end, then pass the tabbing over to the next control. Then it also needs shift+tab as well. How on earth would I implement this tabbing? That's where I got stuck, but it just struck me that this is the main reason I'm asking.
About the handling of the TAB key - it should be something like this: you handle the WM_GETDLGCODE message to indicate that you want to proccess the TAB key, ie
TMyControl = ...
protected
procedure WMGetDlgCode(var Msg: TMessage); message WM_GETDLGCODE;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
...
procedure TMyControl.WMGetDlgCode(var Msg: TMessage);
begin
inherited;
Msg.Result:= Msg.Result or DLGC_WANTTAB;
end;
and the in the overriden KeyDown method you decide what to do in response of it, something like
procedure TMyControl.KeyDown(var Key: Word; Shift: TShiftState);
begin
if(Key = VK_TAB)then begin
if(ssShift in Shift)then begin
if(first subcontrol is focused) set focus to previous control on parent
else set focus to previous child area
end else begin
if(last subcontrol is focused) set focus to next control on parent
else set focus to next child area
end;
end else inherited;
end;
No you can't get windows to recognize multiple points of keyboard focus inside the same window handle, since each control with a window handle either has, or does not have, keyboard focus. The "inner focus" between multiple controls is up to you to sort out.
As you already knew, the most simple way to accomplish this is to have multiple sub-controls with their own window-handles, which is what you said you are doing:
TMyThreeEditControls = class(TControl) // parent has no window handle!!!!
protected
FEdit1:TEdit;
FEdit2:TEdit;
FEdit3:TEdit;
...
end
In the situation above, the parent control is a TControl, it creates several sub controls, in my example above, all three have their own window handles, and thus Windows can display keyboard focus when you hit tab, and handle mouse focus as part of Windows's common controls library's functionality.
In short, the "composite" approach where you include sub-objects (other controls) in your main control, which you are trying to move away from is in fact, the only way to let Windows do most of the work.
On the other hand, what you might be looking for is not a way to have a control paint itself, but some code to make something look like it is focused, in your own custom painting routines, if that is what you are looking for, you should look into the VCL source code or this link on about.com for examples on how to tell Windows to draw a focus rectangle, etc. The about.com link is an imitation and does not use real windows code to draw focus in a Windows-theme aware way.
Update: it is possible that what you are also looking for is the way to determine if mouse co-ordinates are within a specified rectangle (the rectangle represents a button in your case) and if so, to draw a "hot state" for the button. There are more sub-tasks than this to accomplish if you wish to build a control yourself, I recommend you study existing controls such as TStringGrid and TCategoryButtons in the VCL source code, to see the MouseMove, MouseDown, and MouseUp handling code you will need to do the things you are trying to do. In particular, StringGrid source code is the way to see how the "tab key" can be used within a single control with a single window handle, because in that control the tab key can be used (if the right options are turned on) to navigate among all the cells inside the string grid, as if each one was a separate control, even though it is all one control.
Another way to achieve this is to use one edit box that you re-use for each region that you want to have "focused". This is essentially how Delphi's grids work.
When the user clicks in that area (or hits the tab key into your control) you set the edit controls text to the data in that area, set the edit controls bounds to the area and make it visible. When the user exits the control (by clicking out of it or tabbing) you hide the edit control. If you make your control accept the tab key, you can make it "edit" the next area when they hit tab and exit it when they are in the last area.
Then it's just house keeping to make sure you store the entered data in the right spot in your component.

Modify a Delphi DFM resource to close upon showing?

Is it possible to edit a DFM (Delphi's form script format) in such a way that a form closes itself when shown?
I don't code in Delphi, so I'm not familiar with how these forms work, but it seems I could put code (but not standard Delphi code as it seems) in the OnShow or OnCreate events of the form. However, after trying several statements like Close, Exit, FormNameExit, Destroy, etc. won't work (a log will be created, stating the error that the value of the OnShow property was invalid, etc.)
The normal way of closing the form is through a button, but the button doesn't have a OnClick event, just a property, "ModalResult = 1".
Is there a way to make the window close upon opening, some standard function I could put on the OnCreate or OnShow events of the form? Or maybe, creating a checkbox on the form itself, that gives ModalResult = 1? (don't know if this works)
Thanks for any suggestion!
=)
(Note: maybe it's obvious, but I don't have the source.)
Not in DFM. You would have to modify the source.
The OnShow and OnCreate lines you're seeing are only used to give the name of a method that's already defined in the source code. You can't add much functionality at all by modifying the DFM file.
Perhaps the form already has a matching event handler that closes it: the OnClick handler for a close button or menu item, maybe? If so, you could try setting it as the OnShow or OnCreate handler.
You might be able to add a TButton to the form and set its ModalResult -- I don't recall whether you actually need a field in the form class for each control in the DFM -- but that would only work if the form is shown modally, and you'd still have to click it to make the form close.
EDIT: Seeing some of your comments added while I typed my text-wall clarifies things a bit.
I'm guessing that you you're using a resource editor to edit the DFM and modify the behaviour of the app without actually touching the source code?
In this case, the best you could try is to set the Visible property to False. However, this will have no benefit if the developer 'actively displays the form in code'. (He could have done this by calling Show, ShowModal or even by explicitly setting the Visible property.)
Unfortunately, if this is the case, then there's nothing you can do without modifying the actual source code. This is because the DFM is processed when the form is loaded; i.e. before the developer's code that shows the form. Even looking for a place to set ModalResult is useless, because the current ModalResult is cleared when ShowModal is called.
I don't think I understand exactly what you're trying to do, because it doesn't make sense.
It seems to me that you want the form to be automatically closed as soon as it is shown; and that doesn't make sense. :S
So, if I have understood you correctly, please explain why you would want to do this; there may be a better solution for your actual problem.
However, some general concepts...
If you want a form to close, you should link it to some action that closes the form. Either put a button on the form, or a menu item.
Standard forms have a standard Windows mechanism to close them by default. (I.e. the X on the top-right.)
There are two ways of showing forms, and the way in which it is shown does have an effect on how it will be closed. It can be shown modally (which means it is the only form of the application which will interact with the user), or it can be shown normally (which allows the user to switch between other forms of the application).
The point of showing a form modally is that it blocks the flow of your code until the user has finished doing something that was required; it often involves the user providing some form of answer or confirmation.
When shown modally, the form should rather be closed with a ModalResult.
When shown normally, the ModalResult has no effect.
Whenever a form is 'closed', there are a few ways in which this can be done.
The form can simply be hidden; it's still there, but invisible. Next time you want to show the form, you just make it visible again.
The form can be destroyed; meaning that it no longer exists in memory. If this is done, then next time you want to use the form, you have to recreate it.
The attempt to close the form can be actively prevented (Not usually advisable; but may be necessary in specific cases - if for example some information on the form is incorrect).
The form may be simply minimised (this is often done with MDI child forms).
NOTE: There are also a number of attributes on forms (FormStyle being the most important) that have an effect on how it behaves, displays, and can be closed. (E.g. MDI Child forms will by default either minimise, or do nothing when closed.)
NB:If the main form of an application is properly closed, then the application will shut down.
Now, some of the technicalities...
As mentioned earlier a form can be displayed either modally, or normally; using either MyForm.Show; or ModalResult := MyForm.ShowModal;
NOTE: If the form was shown modally, you then need to check the ModalResult to find out the user's answer and act accordingly.
If you displayed the form modally, you should set the ModalResult and the form will close itself. An easy way to do this is to assign a ModalResult to the buttons on the form; then the button will automatically set the form's ModalResult when clicked.
If you displayed the form normally, then all you need to do is call MyForm.Close at the appropriate point in time. NB: Note their are other ways to 'close' a form, but it is better to use this method because it allows you to handle the OnCloseQuery event which is a mechanism to confirm whether the form is allowed to close.
NOTE: While closing the form, there are two events that Delphi can call which you can handle in order to modify how the closing of the form behaves:
OnCloseQuery is called to confirm whether the form is allowed to close.
OnClose is called to find out how the form should close (as explained previously).
Coming back to your question (which sounds like you want the form closed automatically). Rather than closing the form automatically; just don't bother showing it. This is very easy to do. All forms have a Visible property; if set to True, Delphi will automatically show the form normally when it is created. So all you need to do is ensure the property is False.
You really can't do much without the source but move files around or change existing properties. If you have a MAP file for the program and there are existing events in place (onCreate/OnShow) you could patch the executable to invoke different code for those events, but it won't be easy and you have to insure that you don't inject more code than was there previously or make any external calls to routines which don't exist.

Resources