I've tried my best and cannot figure out what happened here. It worked fine in Delphi 4. After upgrading to Delphi 2009, I don't know if this is the way it is supposed to work, or if it's a problem:
This is what my program's menu looks like in Design Mode under Delphi 2009:
Notice that every word in the Main Menu and the File submenu have one letter underlined. It is supposed to be like this. This underlined letter is called the Accelerator Key and is standard in Windows applications so that you can use the Alt-key and that letter to quickly select the menu item and then submenu item with the keyboard rather than with your mouse.
You get them this way by using the "&" character as part of the caption of the item, for example: Save &As...
When I run my application, and use the mouse to open the File menu, it looks like this:
The characters are underlined in the main menu, but are not underlined in the File menu.
If instead, I use the Alt-F key to open up the File submenu, then it looks correct like this:
and all the Accelerator Key letters are properly underlined.
I've played with the AutoHotKeys option but that's not the problem.
Has someone encountered this problem before? Is the example in the 2nd image correct behavior that I don't know of? Or is there some option or coding mistake that I might have missed?
Nov 2009 (one year later): mghie seems to have got to the root of this and figured out the problem. See his accepted answer below.
There is a standard Windows setting (under display properties) to normally hide those accelerators unless the Alt key is held down. That would explain why opening the menu with Alt+F10 shows them for you. Maybe that's the cause?
[EDIT]: No, it's not. I just tried, and a simple TForm with a menu item shows the accelerator, but as soon as I add a TImageList and set the ImageIndex of the single menu item, or simply set OwnerDraw to true, then the accelerator underline disappears. I guess that really is a bug in the VCL.
BTW, this is on Windows XP.
Workaround:
I have debugged this using Delphi 2009 on Windows XP 64, and the root cause for the missing accelerators seems to be that Windows sends WM_DRAWITEM messages with the ODS_NOACCEL flag set, which it shouldn't if the system is set to show accelerators at all times. So you could say that it is not a VCL bug, but a Windows problem which the VCL does not work around.
However, you can work around it in your own code, you just need to reset the flag before passing the message to the VCL. Override the window proc
protected
procedure WndProc(var Message: TMessage); override;
like so:
procedure TYourForm.WndProc(var Message: TMessage);
const
ODS_NOACCEL = $100;
var
pDIS: PDrawItemStruct;
ShowAccel: BOOL;
begin
if (Message.Msg = WM_DRAWITEM) then begin
pDIS := PDrawItemStruct(Message.LParam);
if (pDIS^.CtlType = ODT_MENU)
and SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, #ShowAccel, 0)
then begin
if ShowAccel then
pDIS^.itemState := pDIS^.itemState and not ODS_NOACCEL;
end;
end;
inherited;
end;
This is demonstration code only, you should not call SystemParametersInfo() every time a WM_DRAWITEM message is received, but once at program start, and then every time your program receives a WM_SETTINGCHANGE message.
It is a "feature" introduced with Windows 2000:
The Old New Thing: Why does Windows hide keyboard accelerators and focus rectangles by default?
It would appear that Delphi 4 didn't support this Windows feature.
To have 2000 and XP menus show accelerator keys, right-click an empty spot on the desktop, choose Properties, click the Appearance tab, and under Effects, uncheck Hide Underlined Letters for Keyboard Navigation until I Press the Alt Key. Click OK twice.
Not sure how to do it in Vista.
I don't think it is a Delphi generated bug as you have the same behavior with Notepad on Vista. Also in Delphi itself BTW...
I must confess that I did not pay attention before your question. Thanks for pointing it out.
As Jim McKeeth noted above (correctly), this is "by design" behavior. If the menus are triggered through keyboard action the accelerators should be shown, but if triggered by the mouse the accelerators are intentionally not shown.
I have my XP configured to show accelerators at all times, but a quick test with that option changed confirms that the menus should not show underlines either (Visual Studio responded as I expected, no underlines when using the mouse). However, Microsoft Office ignores this setting and always shows the underlines. So it looks like a bug in how the menus are drawn in Delphi (I don't have any experience with Delphi myself).
I found the option for Vista as well: http://www.vistax64.com/vista-general/42125-always-show-menu-underline-keyboard-accelerators.html
You can turn this on in the new Ease of Access Center (go to Control
Panel, click Ease of Access and then click Ease of Access Center). In
the Ease of Access Center, click Make the keyboard easier to use, and
at the very bottom select the Underline keyboard shortcuts and access
keys check box.
While doing further research I found this related bug on Delphi forums: http://qc.codegear.com/wc/qcmain.aspx?d=37403
It looks like in your case the child windows (the drawn menus) aren't getting or aren't handling WM_UIUPDATESTATE message from their parent window, which is what causes the redraw with accelerators.
Related
I recently got a new development computer with again 2 monitors, but unfortunate it has windows 10.
Now I also have some tools still developed in Delphi 7 and they work but with one annoying problem.
When I start an Delphi 7 application the icon on the taskbar does not move to the second monitor.
The taskbar is setup to show icons on the taskbar of the monitor they are open. This works for all applications, except for the Delphi 7 applications.
The icon always stays on the primary monitor.
I have the source available, so I wonder is there something I have to put into the source code, or setup some property, to get this working ?
Googling this bring up lots of cases where the form does not moves to the second monitor, but I did not find anything about the icon on the taskbar.
This is because the window handle associated with the taskbar button is the window handle owned by the Application object rather than the window handle of your main form.
In later versions of Delphi you would write Application.MainFormOnTaskBar := True in your .dpr file and that would change behaviour so that the taskbar button was associated with your main form instead. I believe that MainFormOnTaskBar was introduced in Delphi 2007.
Migrating to a modern version of Delphi is the ideal way to solve the problem. If you cannot do that then you'll need to hack your way around the VCL code to ensure that the main form's window handle is the one associated with the taskbar button. That's not likely to be an easy job. Fundamentally, you are paying the price for continuing to develop with tools that are long out of date.
As Remy points out there isn't much hacking required to do the bare minimum. Change the window style of the Application window to remove the WS_EX_APPWINDOW style, and have the main form override the CreateParams method to set its owner window to NULL, i.e. Params.WndParent := 0.
I suspect that the behaviour will not be quite as smooth as you'd get with a modern VCL app. For instance, the VCL has been modified to reduce the amount of window recreation it does which is more important now that the main window is associated with the taskbar button.
In Google Chrome when you click the mouse wheel button you get this cursor:
And then you are able to scroll to all possible directions, when you move around with your mouse...
IE also has this, but only moves up and down:
Is there any component for Delphi that can do this? (for a TScrollBox for example)
TMemo, for example, can do that for you, provided you set its ScrollBars property to something else than ssNone. It will even adjust according to which scroll bars are enabled. Problem with TScrollBar component is that on its own it doesn't have any focusable parts and won't receive OnMouseWheel(/Up/Down) events, but its included windowed controls might. You could write a workaround for that on main form events, though. Check solutions at http://www.delphipages.com/forum/showthread.php?t=197309
EDIT: OnMouseWheel(/Up/Down) should be OnMouse(/Up/Down), thanks to #Sertac Akyuz for pointing this out ;)
Seems like this feature is available in RAD studio 2009 (but not in D7).
You need to use Imouse (imouse.pas unit) and the control must have ControlStyle of csPannable.
quote:
Imouse (imouse.pas unit) is a standard implementation of scrolling
with middle button (called also "mouse panning"). It's also used in
RAD Studio. Imouse functionality relays on standard window scrollbars
and sends WM_HSCROLL/WM_VSCROLL to the window to make it scroll. It
works on every window, that have a scrollbar (e.g. TListView,
TTreeView, even TForm/TFrame if AutoScroll is True and at least one
scrollbar is visible).
Oh, I've forgotten one thing. Control must have csPannable in
ControlStyle, but RichView hasn't by default. So, after adding Code:
RichViewEdit1.ControlStyle := RichViewEdit1.ControlStyle +
[csPannable];
I didn't test it though.
All that is left for me is to look into the source code (When I can get my hands on copy of D2009) and maybe impliment this with D7...
We just moved our application from a MDI container to a single document interface. Our users are used to using a "Windows" menu in the MDI parent to show windows side by side. We want to train them to right click on the Windows taskbar and use the window management functions there.
With Delphi applications we noticed that the windows shell leaves room for the hidden "Application" window. So if I only have two windows open it will arrange room for three. The Application window is not really shown but there is space left for it.
This is made worse by the fact that we have two different applications. If they only have one window open in each application and want to show them side by side windows will actually try to account for 4 windows.
So instead of seeing two windows each taking 1/2 of the screen I see two windows that take up 1/4 of the desktop and the rest of the screen is open.
I found that adding a line to hide the application window as my application starts up will fix this problem.
ShowWindow(Application.Handle, SW_HIDE);
Edit in case someone does not read
down to the answer. Based on Craig's
answer below I am setting the windows
style to WS_EX_TOOLWINDOW instead of
hiding the window.
SetWindowLong(Application.Handle, GWL_EXSTYLE,
GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
My (original) questions is: Is this safe (hiding the application window)? I'm wondering if I may be breaking something else by hiding the Application window. Are there any side effect I need to be aware of? Is there a better way to solve this issue?
I'm using Delphi 2007. The issues seem to be consistent across Windows XP, Vista, and 7.
Update: Some of the answers seem to think the problem is with the Application forms icon being visible. That is not the case. I already have MainFormOnTaskbar set to true.
Also if you are testing this be aware that the Delphi IDE (only tested with 2007) makes things worse. Try this. Open the Delphi IDE and two instances of notepad. Minimize the IDE but have both notepads un-minimized. Right click on choose Show Windows Side by Side. You will see each notepad take up 1/3 of the screen. Close the IDE and choose Show Windows Side by Side again and each will take up 1/2 half of the screen.
In Delphi 2007 (and above) the Application Window does not show on TaskBar at all if
Application.MainFormOnTaskbar := True;
line is in a project file (*.dpr). For example
begin
Application.Initialize;
Application.MainFormOnTaskbar := True; // <--
Application.CreateForm(TForm7, Form7);
Application.CreateForm(TForm8, Form8);
Application.Run;
end.
That is default setting for new applications, but this line is absent if you ported an application from previous Delphi version - you should add this line manually.
As long as MainFormOnTaskBar is true, you can fix the problem by adding this to your DPR:
SetWindowLong(Application.Handle, GWL_EXSTYLE,
GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
Later versions of Delphi automatically include the WS_EX_TOOLWINDOW flag when they create the TApplication handle.
I'm still using Delphi 7, and I've been using this technique in combination with the 'WndParent:=GetDesktopWindow' trick to get a taskbar button for each window, somewhat like Microsoft Office started doing since some version (I guess XP?)
I believe at this is the way that applications minimize to the system tray.
I have been trying to find a good-looking design using Aero in Delphi 2010. One of the obvious uses one sees, is where the glass frame is extended to include the OK/Cancel buttons at the bottom of the screen. I notice though that this doesn't look quite right in Delphi 2010 - there is a white border all around each button.
This image shows the problem: the top 3 buttons are from my app, the bottom two were taken from Paint.NET's Layer Properties dialog.
I tried various combinations of DoubleBuffered and a few combinations of placing the controls on other controls first, but the problem remains. Any ideas?
If no one has a clean solution, as a workaround use TBitBtn with DoubleBuffered = false.
It appears that the only workaround is owner-draw, or a third-party button control Check out the Glass Button by Roy Klever or, as stated in the QC entry linked below, TBitBtn with DoubleBuffered=false, which was the accepted answer above to this question.
This is a bug in Windows Aero DWM or else a bug in the windows common controls, or a bug in the way the VCL class hierachy handles common control window messages and painting when painting on glass. In short, windows common controls do not paint themselves properly on glass, or rather DWM composition (Aero) is broken. Surprise surprise.
The standard VCL button component uses the Window Class BUTTON from Windows Common Controls.
Note that TSpeedButton does not use the windows common control, and does not have this problem. however, it also does not accept focus.
It appears Embarcadero knows about this issue, it is QC # 75246, which is closed because it is really a bug in the common controls library, as Won't Fix, with the suggestion to use TBitBtn. Buttons are not alone, this is part of a group of QC reports including panels, and other common controls.
However I have a commercial TcxButton (part of developer express components) which accepts keyboard focus, and does not draw this glitch. Any code that uses the Win32 common control button control appears to have this problem. It may be possible that a low level Win32 API hacker might find a workaround for this. I am looking into it. This answer will be updated if I figure it out.
One interesting detail: TcxButton has three drawing styles, cxButton.LookAndFeel.Kind = {lfOffice11,lfFlat,lfStandard}. Selecting lfOffice11 adds this glitch back in. It looks like a strange interaction between the glass feature in aero in Vista/Win7 and the common control/xptheme button drawing code.
It may be that the only workaround is to use a completely app-drawn button control and to not use Windows common controls buttons, or any button control that relies upon the XP theme engine to draw buttons, on an aero glass pane.
Edit: July 28, someone at Embarcadero has closed the above QC Entry, which was a mistake. I am urging them to reopen it, if only to clarify if this is indeed a Windows bug in the common controls dll.
If you wish to play around, make a copy of the VCL source code for the TButton and TCustomButton classes from StdCtrls, as I have done here, modify CNCtlColorBtn, so that you force one of three things to happen - PerformEraseBackground, DrawParentBackground or inherited, and see the results. Interesting stuff.
procedure TCustomGlassButton.CNCtlColorBtn(var Message: TWMCtlColorBtn);
begin
PerformEraseBackground(Self, Message.ChildDC);
Message.Result := GetStockObject(NULL_BRUSH);
(*
with ThemeServices do
if ThemesEnabled then
begin
if (Parent <> nil) and Parent.DoubleBuffered then
PerformEraseBackground(Self, Message.ChildDC)
else
DrawParentBackground(Handle, Message.ChildDC, nil, False);
{ Return an empty brush to prevent Windows from overpainting we just have created. }
Message.Result := GetStockObject(NULL_BRUSH);
end
else
inherited;
*)
end;
Some interesting reading on Vista era glass/DWM/aero APIs (C++ developers blog)
Here I'm providing some code that makes TButton look right on Glass. Unfortunately it makes the form "click-throw", so I don't think it's a good idea. But maybe you can find a way to fix the form's "click-throw".
if you are able to use win32 api, try exploiting NM_CUSTOMDRAW notification (not ownerdraw), as i do (yes, buttons DO send it, including radio and checkboxes. For these it is best to use WM_CTLCOLORSTATIC though.). This is how it is done in C++, but the idea is the same. While my idea is good, it so happens that my buttons do DISAPPEAR once per program execution from the window, when they are customdrawn and i need to hover mouse over them so they are visible again. That's why i'm still looking for comments for this. Note that it is really difficult to reproduce disappearing buttons in one-form applications. I hovewer am experiencing this behaviour in every project.
case WM_NOTIFY:
switch(((LPNMHDR)lParam)->code){
case NM_CUSTOMDRAW:
{
NMHDR *nmh=(NMHDR*)lParam;
//these 6000 through 6004 are button identifiers assigned by me
if(nmh->idFrom >= 6000 && nmh->idFrom <= 6004){
switch(((LPNMCUSTOMDRAW)nmh)->dwDrawStage){
case CDDS_PREERASE:
//BackgroundBrush is a HBRUSH used also as window background
FillRect(((LPNMCUSTOMDRAW)nmh)->hdc, &((LPNMCUSTOMDRAW)nmh)->rc, BackgroundBrush);
break;
}
}
break;
}
break;
I have a "username" TEdit on a Delphi 2006 login form. When the application starts up the user is asked to enter the username. The TEdit gets focus and the caret is placed in its horizontal center for some reason. As soon as anything is typed the caret is left aligned again and everything looks normal.
It is also strange that it wasn't always like this. This behaviour suddenly started a few years ago (I believe we still used Delphi 6 at that time). Any idea what might be causing this?
Additional info (has been asked for):
The problem is widespread: D2006 and D6 (I believe), 5 or 6 Delphi instances on as much computers, all applications using that login form. The effect is limited to the form however, it does not occur on other TEdits.
The TEdit is not filled with spaces (that would be strange to do in the first place).
More info (Nov 13):
The caret is not centered exactly, it is almost centered.
Currently it seems to occur in a DLL only. The same login dialog is used in regular executables and does not show the problem there (although I believe it did at some time).
The edit field is a password edit, the OnChange handler sets an integer field of that form only, there are no other event handlers on that edit field.
I added another plain TEdit, which is also the ActiveControl so that it has focus when the form shows (as it was with the password edit). I also removed the default text "Edit1". Now the issue is present in that TEdit in the same way.
The "centered" caret goes back to normal if either a character is entered or if I tab through the controls - when I come back to the TEdit it looks normal. This was the same with the password edit.
I had also the same problem in Delphi 2007,
with a TEdit placed in a modal form called by double-clicking in a Grid.
I made some tests launching the same Form from a TSpeedButton.
I noticed that the problem with the TEdit appears only when the grid is focused.
after more tests the problem appears to be a bug in the VCL.
in TCustomGrid.paint there is a Call of SetCaretPos, even if the grid is not on an active Form.
../..
Focused := IsActiveControl;
if Focused and (CurRow = Row) and (CurCol = Col) then
begin
SetCaretPos(Where.Left, Where.Top);
Include(DrawState, gdFocused);
end;
../..
the code above is from TCustomGrid.paint in Grids.pas
in this code, Focused is set to true if the grid is the "activeControl" of the parent form, the code don't take into account if the form is active or not.
then, if the grid need to be repaint, setCaretPos is called with grid coordinates, causing the bug mentioned in the question.
The bug is very difficult to notice because, most of the times, the caret simply disappear from the active form instead of blinking near the middle of a TEdit.
steps to reproduce the bug :
start new VCL form app.
add TStringGrid into it.
add a second form to the app with just a TEdit in it.
return in main form (unit1) and call form2.showmodal from the grid DblClick event.
that's all : you can launch the application and double click on a grid cell.
if you drag the modal form away of the main form, the grid will need to be repaint, then causing the caret to disappear from the modal form (or to appear in the middle of the TEdit if you are very lucky)
So, I think a fix is needed in Grids.pas.
in the excerpt of grid.pas above, I suggest replacing the call of the function IsActiveControl by a call of a new function called IsFocusedControl :
// new function introduced to fix a bug
// this function is a duplicate of the function IsActiveControl
// with a minor modification (see comment)
function TCustomGrid.IsFocusedControl: Boolean;
var
H: Hwnd;
ParentForm: TCustomForm;
begin
Result := False;
ParentForm := GetParentForm(Self);
if Assigned(ParentForm) then
begin
if (ParentForm.ActiveControl = Self) then
//Result := True; // removed by DamienD
Result := ParentForm.Active; // added by DamienD
end
else
begin
H := GetFocus;
while IsWindow(H) and (Result = False) do
begin
if H = WindowHandle then
Result := True
else
H := GetParent(H);
end;
end;
end;
this fix (made in Delphi2007) worked well for me, but is not garanteed.
(also, do not modify directly units of the VCL).
Just a few additional questions:
Is this problem on one pc or on more pc's?
Does it occur on one application or on all applications?
Does it happen only on your Delphi applications or on all applications?
If it is only on one pc, I think it is a strange registry setting. If its on more pc's but you only have one delphi development pc, it could still be a registry setting. But there are other possibilities.
You could try some tests:
Create an simple app on the dev pc and run it on another. Does this show the effect.
Use an app that is created by Delphi but build on another pc that does not show the effect, and run it on the dev pc, does this show the effect?
I really think this is a registry setting. According to the information you gave me, it happened since Delphi 6 and is still happening.
It also can be a locale setting but then it has to happen in more programs.
Edit:
Thanks for the extra info.
So it looks like the problem can be isolated to a single form. But it occurs on all pc's.
What you can do, is delete the edit, and re-add a new one. This saves searching for weird property values.
Are there events hooked on the TEdit that can possible explain the effects?
What property values are set? (But I prefer a look at the dfm and the code, because then I'm possible able to reproduce the effect.)
Are you sure it is a simple TEdit? It might be initialized with a few spaces instead of an empty string. The onChange handler then might just strip spaces as soon as you start typing.
A TEdit extension might have text alignment set on centered instead of left, and set text alignment only on onChange.
[edit]
Please show the event handlers of the TEdit.
I've also noticed this behaviour in richedits.
One place in our app is a double click on a grid which displays another screen containing a RichEdit. The caret always seems to appear in the Richedit in the same place as the double click on the grid occured, ie if the dblclick was on the 3rd line of the grid, the caret will appear ~3 lines down on the edit. As soon as a key is pressed, the caret resets to the correct position of top left.
Its not limited to a certain form or pc as it happens on all developers machines and also on clients machines.
The app was originally developed in Delphi 5, but the problem didn't occur (or wasn't noticed) until we moved to D2006.
Its not a particularily big problem, just... irritating.
Another workaround:
Before showing the second form, prevent the grid on the first form doing Paint action. Code snippet as below.
Gird.BeginUpdate;
try
//Show the second form here
finally
Grid.EndUpdate;
end;