Can Windows manage multiple focus points on a single control? - delphi

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.

Related

Is possible for a menu item to receive an OnClick event even when it's not enabled?

I'm trying to enable an Administrator to enable/disable menu items in the main menu of my Application by Ctrl+Clicking them. To do that I've injected the TMenuItem class in my main form with a custom version and overridden the Click virtual method, like so:
uses
Forms, Menus;
type
TMenuItem = class(Menus.TMenuItem)
public
ControlActivationState: Boolean;
procedure Click; override;
end;
TMyMainForm = class(TForm)
...
procedure TMenuItem.Click;
begin
if ControlActivationState and IsKeyPressed(VK_CONTROL) then
Self.Enabled := not Self.Enabled
else
inherited;
end;
It works, but only for the top level menu.
Why the top level menu items receives OnClick events even when they are disabled and the other menu items don't?
Is there a way to make the child menu items receive those events too?
The top level OnClick event is triggered by receipt of a WM_INITMENUPOPUP message. That message is sent even when the top level item is disabled. I'm not sure why it is sent in that scenario, but it is. And the same is true for a sub-item that has children.
However, for a sub-item without children, the OnClick is triggered by a WM_COMMAND message. But the system never even sends the message if the menu item is disabled.
What you are attempting to do cannot be readily done. The only way I can see you doing it is to handle the raw mouse and keyboard events. Personally, I would not contemplate doing so.
TMenuItem is a TComponent, i.e. it's not a windowed control and it doesn't have classical events. Instead, click events which happen on a real windowed control are delegated to a TMenuItem instance. I don't know which window control is the real host for events but even if I did I think it would be hard to determine which TMenuItem corresponds to the actual click point.
My advice is to make a dedicated window for menu editing with a tree control which generically populates its items at runtime based on the actual menu layout, and then provide enable/disable for the tree nodes which reflect on the corresponding menu items. You can then save/load menuitem list, etc. This should be much cleaner and easier then diving into the murky depths of VCL and figuring out (and overriding) how events are propagated from 'real' controls to design-time representations called TComponents...
In fact, you are trying to do it the hard way...
The easy solution to your problem would be to override the OnDrawItem() method of your TMenuItem to display it like it was disabled, and handle the OnClick event alternatively.
(Do not forget to set the .OwnerDraw property of the menu to make this solution work.)
Edit:
According to the Delphi help using the OnAdvancedDrawItem event makes it more easy because it provides intormation about the menuitem itself.

Is there a workaround for something like "Form1.MousePreview:=true"?

I have two 100% overlapping panels with different contents on a form.
The first (static display of information) should be visible by default, but the other (user interaction) should replace it if the user moves the mouse near the two - and if the mouse moves away, it should switch back.
Something like this:
if (*the mouse is near*) then
begin
Panel1.Hide;
Panel2.Show;
end
else
begin
Panel2.Hide;
Panel1.Show;
end;
My problem is: where to capture mouse movement?
Each component has its own OnMouseMove handler - of course I could forward each of them to the forms handler, but I'd rather have something a bit more elegant (and easier to maintain).
The perfect solution would be something like Form1.MousePreview := true;.
Another solution would be assigning a generic handler that translates coordinates and calls the forms handler; The assigning could be done in FormCreate.
But that's not as easy as it seems, because one TImage already has its own mouse event handlers.
I have tried OnMouseEnter and OnMouseLeave of the two panels but it didn't work; #1 disappeared, but #2 did not appear. I guess that's because if the mouse leaves Panel2, it should disappear - but it also leaves it if it enters a button on it.
That's why I'd like to use a coordinate based approach to make the check more reliable.
Maybe the two panel method is completely wrong?
Update: Yes, it is, as Remy said.
I am now using a TJVPageList because a TPageControl has visual tabs.
The OnMouseEnter handler of the PageList sets one page, the OnMouseLeave sets the other; But once the mouse moves over the PageList, both pages start flickering.
I have tried adding the event handlers to each page, too, but that made no difference.
Should I check all OnMouseEnter / Leave events to filter out the PageList, the two pages and all components sitting on the pages?
Another update: I am using a TPageControl now, but the behaviour is similar.
It doesn't flicker, but if I move the mouse ontop the TPageControl, no TTabSheet is displayed at all.
Only if I press down the left mousebutton, the UI sheet is displayed. The other sheet is displayed normally if I move away the mouse. (The TJVPageList displays the UI page if I press the left mousebutton, too.)
I have used the mouse event handlers (enter/leave) of the TPageControl and both TTabSheets.
Update 3:
Done it.
The static Panel / TabSheet / JvStandardPage (#1) must not trigger the OnMouseLeave handler.
What you describe might be better served using a single TPageControl instead of two TPanel controls. Use the TPageControl's own OnMouseEnter/Leave events (or intercept the CM_MOUSEENTER/LEAVE messages) to switch the TPageControl.ActivePage as needed.

How to avoid the "Open IME" popup in a StringGrid?

In a StringGrid, sometimes I get the unwanted menu below when I right-click. Is this a Windows popup?
How to I prevent this popup from appearing rather than my own?
I have goAlwaysShowEditor in my Options.
I have set StringGrid.PopupMenu to my popup.
I've set StringGrid.OnMouseDown to show my popup if it's a right click.
You can override the virtual CreateEditor method like this way (not a good solution though, I know :-):
type
TStringGrid = class(Grids.TStringGrid)
protected
function CreateEditor: TInplaceEdit; override;
end;
implementation
function TStringGrid.CreateEditor: TInplaceEdit;
begin
Result := inherited CreateEditor;
TMaskEdit(Result).PopupMenu := Form1.PopupMenu1;
end;
That is the popup menu found in every Windows EDIT control. Possible the world's most known menu (the only competition comes from the system menu). You want it, because your user's expect it (and need it). When you edit the text in a cell, the TStringGrid control actually creates a standard Windows EDIT control, which is great. And thus you get its popup menu.
In addition, to show your own popup menu (when you are not editing a cell), you don't need to set the OnMouseDown handler. It is enough to set the PopupMenu property. In fact, it is very bad to use the OnMouseDown handler to trigger a popup menu, because then the menu will only be shown when the user right-clicks the control (and not, for instance, when he presses the "context" button on his keyboard).
If you really want your own popup menu to show, even when the user is editing a cell, you really have to give him his usual options for undo, copy, cut, paste, Unicode stuff, etc., manually. Surely you don't want that?

TTreeView drawing error when deactivating a form

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.

How to Detect that the Mouse is unmoved and button still pressed?

In Delphi, I've added a Scrollbar component (oriented vertical) to the right side of my form.
I've added a Scrollbar OnChange event so I can change the view of the form and the position of the scrollbar thumb when the user clicks on the UpArrow or DownArrow button with his mouse, and this works fine.
But the OnChange event only seems to get triggered when the mouse button is initially pressed on the arrow.
I notice all scrollbar controls repeat the command and continue scrolling while the mouse remains pressed on the arrow, and I'd like to implement this behavior.
So how can I easily detect if the user has not moved the mouse and continues to press the mouse button while the mouse remains over the arrow?
Conclusion. Somehow something in the scrollbar in my project got corrupted. After I deleted the ScrollBar, and added it again, the problem vanished.
This is one of those tricky ones that took me a lot of time to solve. Thanks for your help. I'm closing this question.
Use the OnScroll event.
The following code adds 'xxx' to a memo as long as the mouse is held down on the scrollbar arrow button. Tested with Delphi 6.
procedure TForm1.ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
begin
Memo1.Lines.Add( 'xxx' );
end;
The usual way to handle auto-repeating is to enable a TTimer and check in the OnTimer() event handler whether the action needs to be performed again, and to deactivate the timer if not. If you need sample code, I seem to remember that the SynEdit control used a similar technique for autoscrolling in drag and drop operations.
If a component does not encapsulate the behaviour you are looking for and you can't easily simulate the behaviour with the methods available you should really subclass the closest component that does most of what you need and add the behaviours that are missing.
I know that some extra work is involved but it really is the better way to go. Now with Delphi, I seem to recall that subclassed components needed a bit of extra work as well to be able to be used from the IDE for form design, maybe this has changed since version 7.

Resources