I am implementing context sensitive help in my Delphi 2009 application. It works fine except in one case. I cannot identify that I am in the main menu, and which menu item has been opened.
What I want to do is if the user has opened the File menu and while its open presses F1, then I'll bring up my help on the File menu. If they open the Edit menu and press F1, then I'll bring up my help on the Edit menu, etc.
I am using ApplicationEventsHelp to process the user's pressing of F1 as follows:
function MainForm.ApplicationEvents1Help(Command: Word; Data: Integer;
var CallHelp: Boolean): Boolean;
begin
if Command = HELP_COMMAND then begin
Application.HelpSystem.ShowTopicHelp(PChar(Data), Application.CurrentHelpFile);
CallHelp := false;
end;
Result := true;
end;
As I mentioned, this works for everything except the main menu. I've tried using
FindVCLWindow(Mouse.CursorPos)
and other such methods that identify the active control to see if they would identify the menu, but they don't seem to.
Is there a way to tell which menu item (if any) is open when the F1 key is pressed?
Thank you everyone for your help and good ideas.
Just to document my final solution, I found that the system is not particularly good at figuring out which control it is in and sometimes gets it wrong and passes incorrect data to ApplicationEventsHelp which brings up an inappropriate help page.
After experimenting and using the solution for handling the menus in the accepted answer, I found it was best to identify which control I was in to bring up the correct help item. I ended up not even using the HelpKeyword property, but hardcoding it. The code is clear and it works. I also have my the help for my RVEdit window bringing up different help pages depending on the section of the document you are in (my CurCursorID tells me that).
For anyone who wants to do this like I did, here is how:
function TLogoAppForm.ApplicationEvents1Help(Command: Word; Data: Integer;
var CallHelp: Boolean): Boolean;
var
HelpKeyword: string;
SType: string;
begin
if Command = HELP_COMMAND then begin
if PtInRect(RVEdit.ClientRect, RVEdit.ScreenToClient(Mouse.CursorPos)) then begin
if CurCursorID = 'H' then HelpKeyword := 'RefTopReport'
else if CurCursorID = 'T' then HelpKeyword := 'RefTableContents'
else if CurCursorID = '~HNAME' then HelpKeyword := 'RefIndexNames'
else if copy(CurCursorID, 1, 2) = 'N+' then HelpKeyword := 'RefIndexNames'
else if CurCursorID = 'B' then HelpKeyword := 'RefBottomReport'
else if CurCursorID <> '' then HelpKeyword := 'RefInformationArea'
else HelpKeyword := 'RefEverythingReport';
Application.HelpSystem.ShowTopicHelp(HelpKeyword, Application.CurrentHelpFile);
end
else if PtInRect(ElTree.ClientRect, ElTree.ScreenToClient(Mouse.CursorPos)) then
Application.HelpSystem.ShowTopicHelp('RefTreeView', Application.CurrentHelpFile)
else if PtInRect(TopToolbar.ClientRect, TopToolbar.ScreenToClient(Mouse.CursorPos)) then
Application.HelpSystem.ShowTopicHelp('RefTopToolbar', Application.CurrentHelpFile)
else if PtInRect(BottomToolbar.ClientRect, BottomToolbar.ScreenToClient(Mouse.CursorPos)) then
Application.HelpSystem.ShowTopicHelp('RefBottomToolbar', Application.CurrentHelpFile)
else
Application.HelpSystem.ShowTopicHelp('RefMainWindow', Application.CurrentHelpFile);
CallHelp := false;
end
else if Command = HELP_CONTEXTPOPUP then begin
case Data of
0: HelpKeyword := 'RefMenuBar';
11: HelpKeyword := 'RefFileMenu';
12: HelpKeyword := 'RefEditMenu';
13: HelpKeyword := 'RefSearchMenu';
14: HelpKeyword := 'RefNavigateMenu';
15: HelpKeyword := 'RefViewMenu';
16: HelpKeyword := 'RefOrganizeMenu';
17: HelpKeyword := 'RefHelpMenu';
else HelpKeyword := '';
end;
if HelpKeyword <> '' then begin
Application.HelpSystem.ShowTopicHelp(HelpKeyword, Application.CurrentHelpFile);
CallHelp := false;
end;
end;
Result := true;
end;
I did have to put 11 through 17 into the HelpContext property of the MenuItems in my 7 main menus so that the correct help would come up depending on which menu you were in. The detection of the menu item is the help the answer to this question provided me.
The nice thing is that this code is easy to follow (using HelpKeywords instead of HelpContext numbers) and will probably still work even after conversion to Delphi XE and FireMonkey.
Looking at Command = HELP_COMMAND and the cast of Data to PChar, it seems you work with a help system based on keywords rather then on context identifiers (HelpType = htKeyword).
(Here in Delphi 7) Menu items do not have the HelpType and HelpKeyword properties, so you are bound to use the HelpContext property:
function TForm1.ApplicationEvents1Help(Command: Word; Data: Integer;
var CallHelp: Boolean): Boolean;
begin
if Command = HELP_COMMAND then
begin
//Application.HelpSystem.ShowTopicHelp(PChar(Data), Application.CurrentHelpFile);
//Doesn't this do the same?
Application.HelpKeyword(PChar(Data));
CallHelp := False;
end
else if Command = HELP_CONTEXT then
begin
// Convert the context identifier to your keyword, or:
Application.HelpContext(Data);
CallHelp := False;
end;
Result := True;
end;
By trapping windows message 'WM_MENUSELECT' it is possible to keep track of the selected menu item.
See menuitemhints for more information.
Example :
type
TForm1 = class(TForm)
...
private
fMyCurrentSelectedMenuItem : TMenuItem;
procedure WMMenuSelect(var Msg: TWMMenuSelect) ; message WM_MENUSELECT;
end
procedure TForm1.WMMenuSelect(var Msg: TWMMenuSelect) ;
var
menuItem : TMenuItem;
hSubMenu : HMENU;
begin
inherited; // from TCustomForm (so that Application.Hint is assigned)
menuItem := nil;
if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then
begin
if Msg.MenuFlag and MF_POPUP = MF_POPUP then
begin
hSubMenu := GetSubMenu(Msg.Menu, Msg.IDItem) ;
menuItem := Self.Menu.FindItem(hSubMenu, fkHandle) ;
end
else
begin
menuItem := Self.Menu.FindItem(Msg.IDItem, fkCommand) ;
end;
end;
//miHint.DoActivateHint(menuItem) ;
fMyCurrentSelectedMenuItem := menuItem;
end; (*WMMenuSelect*)
So when the F1 button is pressed you can use the fMyCurrentSelectedMenuItem to activate the correct help.
You can use GetMenuItemRect function:
1. Go through all items in your main menu and call GetMenuItemRect to get item position. Function will work only if item is displayed.
2. Use GetCursorPos and PtInRect to check if mouse is over menu item and call appropriate help topic.
Related
I need to selectively enable certain menu items based upon the status of the user. I've managed to get code to enable the actual items I want but I can't see how to enable all the parent menu items above each one that I enable in a multi-level menu. Without enabling them as well the menu item still can't be used as the user cannot reach it.
eg if I have
EditTop
EditSub1
Editsub2
EditSubSub1
EditSub3
I can enable EditSubSub1 but I also therefore need to enable Editsub2 and EditTop as well or it can't be reached by the user. That's what I would appreciate help with.
The code I have at the moment is the following (Assume that other code has given me a TstringList containing the menu names I want enabled)
First some code to disable everything.
procedure DisableMenu(AMenu: TMenuItem);
//recurses through all the menu and disables eveything
var
i: integer;
begin
for i := 0 to AMenu.Count - 1 do
begin
AMenu[i].enabled := false;
DisableMenu(AMenu[i]);
end;
end;
Then code that searches for and returns a TmenuItem based upon its name
(This came from
http://www.delphipages.com/forum/showthread.php?t=45723)
function FindMnuItem(Menu: TMenu; MenuName: string): TMenuItem;
procedure FindSubItems(mnuItem: TMenuItem);
var i: integer;
begin
for i:=0 to mnuItem.Count- 1 do
if mnuItem.Items[i].Name= MenuName then
begin
Result:= mnuItem.Items[i];
break;
end
else
FindSubItems(mnuItem.Items[i]);
end;
var i: integer;
begin
Result:= nil;
for i:= 0 to Menu.Items.Count -1 do
begin
if Menu.Items[i].name = MenuName then
begin
Result:= Menu.Items[i];
break;
end
else
if Result<> nil then
break
else
FindSubItems(Menu.Items[i]);
end;
end;
Finally the code I would like some help with. This selectively enables each menu item based upon the names in the Stringlist AllowedMenus but only those ones, not the ones above each one in the tree. How do I do that?
//first disable all menu items
DisableMenu(MainMenu1.Items);
//now enable the ones we want enabled
for i := 0 to AllowedMenus.count-1 do
begin
MenuName := AllowedMenus[i];
FindMnuItem(MainMenu1, MenuName).Enabled := true; //enable an item
end
All you need to do is walk up the menu tree using the TMenuItem.Parent property.
var vMenuItem : TMenuItem;
[...]
//first disable all menu items
DisableMenu(MainMenu1.Items);
//now enable the ones we want enabled
for i := 0 to AllowedMenus.count-1 do
begin
MenuName := AllowedMenus[i];
vMenuItem := FindMnuItem(MainMenu1, MenuName);
while Assigned(vMenuItem) do
begin
vMenuItem.Enabled := true; //enable an item
vMenuItem := vMenuItem.Parent;
end;
end
i think that u can use this function;
(D21 is your Actual Items):
procedure UpdateMenuParent(MyItemMenu: TMenuItem);
begin
TMenuItem(MyItemMenu).Enabled := true;
if TMenuItem(MyItemMenu).Parent <> nil then
UpdateMenuParent(TMenuItem(MyItemMenu).Parent);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
UpdateMenuParent(D21);
end;
As the title states, I am trying to detect when another application's caption changes and perform a specific action only if the caption is different than it was when last checked. I am not sure if it helps or not but this is the code I am using to grab the caption of the other application.
function GetWinCaption: string;
var
Handle: THandle;
Len: LongInt;
Title: string;
begin
Result := '';
Handle := FindWindow('Classname_Goes_Here', nil);
if Handle <> 0 then
begin
Len := GetWindowTextLength(Handle) + 1;
SetLength(Title, Len);
GetWindowText(Handle, PChar(Title), Len);
GetWinCaption := TrimRight(Title);
end;
end;
The above code works fine to grab the caption, but I am having a difficult time wrapping my head around the code I would need to write to actually perform the check to see if the caption has changed or not.
This is the code I have been playing around with to do the check but I am not sure this is the best way to go about doing this and could use the advise of someone with more experience in this area.
var
CurrCaption, PrevCaption : string;
...
...
if (CurrCaption = '') or (CurrCaption = PrevCaption) then
begin
CurrCaption := GetWinCaption;
end
else
begin
PrevCaption := CurrCaption ;
end;
// and then later on I do the comparison like this.
if CurrCaption = PrevCaption then
begin
ShowMessage('not changed');
end
else
begin
ShowMessage('changed');
end;
Hi i am having a problem with incremental search in delphi.
I Have looked at this http://delphi.about.com/od/vclusing/a/lb_incremental.htm
But this doesn't work in firemonkey so i came up with this :
for I := 0 to lstbxMapList.Items.Count-1 do
begin
if lstbxMapList.Items[i] = edtSearch.Text then
begin
lstbxMapList.ItemByIndex(i).Visible := True;
end;
if lstbxMapList.Items[I] <> edtSearch.Text then
begin
lstbxMapList.ItemByIndex(i).Visible := False;
end;
end;
When i use this the listbox is just blank.
You're hiding every item that doesn't exactly match edtSearch.Text. Try this instead (tested in XE3):
// Add StrUtils to your uses clause for `StartsText`
uses
StrUtils;
procedure TForm1.edtSearchChange(Sender: TObject);
var
i: Integer;
NewIndex: Integer;
begin
NewIndex := -1;
for i := 0 to lstBxMapList.Items.Count - 1 do
if StartsText(Edit1.Text, lstBxMapList.Items[i]) then
begin
NewIndex := i;
Break;
end;
// Set to matching index if found, or -1 if not
lstBxMapList.ItemIndex := NewIndex;
end;
Following from Kens answer, if you want to hide items as per your question, just set the Visible property but note that since the expression of an if statement returns a boolean and Visible is a boolean property it's possible to greatly simplify things. Note also that I've also used ContainsText which will match the string anywhere in the item text:
procedure TForm1.edtSearchChange(Sender: TObject);
var
Item: TListBoxItem;
begin
for Item in lstbxMapList.ListItems do
Item.Visible := ContainsText(Item.Text.ToLower, Edit1.Text.ToLower);
end;
I have two TEdit boxes that I am using to specify file paths, one is for UNC paths, the other is for a local path. However, I would like it so if the user can only enter text in one box. If they enter text in one box, it should clear the other one. How should I go about doing this? Also, not sure if I should use an OnEnter, OnChange, or some other method.
You can do it pretty simply. Create one OnChange handler, and assign it to both TEdits using the Object Inspector's Events tab. Then you can use something like the following:
procedure TForm1.EditChanged(Sender: TObject); //Sender is the edit being changed
begin
if Sender = UNCEdit then // If it's is the UNCEdit being changed
begin
LocalPathEdit.OnChange := nil; // Prevent recursive calling!
LocalPathEdit.Text := ''; // Clear the text
LocalPathEdit.OnChange := EditChanged; // Restore the event handler
end;
else
begin
UNCEdit.OnChange := nil;
UNCEdit.Text := '';
UNCEdit.OnChange := EditChanged;
end;
end;
This can be streamlined slightly, but it's not quite as readable to others. It can also be protected with a try..finally, although for simply clearing an edit's text content it's not really needed.
procedure TForm1.EditChanged(Sender: TObject);
var
TmpEdit: TEdit;
begin
if Sender = UNCEdit then
TmpEdit := LocalPathEdit
else
TmpEdit := UNCEdit;
TmpEdit.OnChange := nil;
try
TmpEdit.Text := '';
finally
TmpEdit.OnChange := EditChanged;
end;
end;
If you want to keep the two edit boxes, this is how I would do it.
procedure TForm1.Edit1Exit(Sender: TObject);
begin
if (Edit1.text <> '') then
Edit2.text:= '';
end;
procedure TForm1.Edit2Exit(Sender: TObject);
begin
if (Edit2.text <> '') then
Edit1.text:= '';
end;
You want the value check so that you don't accidentally wipe the value when your users tab through the fields.
You could hook both edit boxes to the following KeyPress event
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
If Sender = Edit1 then
Edit2.clear
else
if Sender = Edit2 then
Edit1.clear;
end;
Is there a way to Minimize an external application that I don't have control over from with-in my Delphi application?
for example notepad.exe, except the application I want to minimize will only ever have one instance.
You can use FindWindow to find the application handle and ShowWindow to minimize it.
var
Indicador :Integer;
begin
// Find the window by Classname
Indicador := FindWindow(PChar('notepad'), nil);
// if finded
if (Indicador <> 0) then begin
// Minimize
ShowWindow(Indicador,SW_MINIMIZE);
end;
end;
I'm not a Delphi expert, but if you can invoke win32 apis, you can use FindWindow and ShowWindow to minimize a window, even if it does not belong to your app.
Thanks for this, in the end i used a modifyed version of Neftali's code, I have included it below in case any one else has the same issues in the future.
FindWindow(PChar('notepad'), nil);
was always returning 0, so while looking for a reason why I found this function that would find the hwnd, and that worked a treat.
function FindWindowByTitle(WindowTitle: string): Hwnd;
var
NextHandle: Hwnd;
NextTitle: array[0..260] of char;
begin
// Get the first window
NextHandle := GetWindow(Application.Handle, GW_HWNDFIRST);
while NextHandle > 0 do
begin
// retrieve its text
GetWindowText(NextHandle, NextTitle, 255);
if Pos(WindowTitle, StrPas(NextTitle)) <> 0 then
begin
Result := NextHandle;
Exit;
end
else
// Get the next window
NextHandle := GetWindow(NextHandle, GW_HWNDNEXT);
end;
Result := 0;
end;
procedure hideExWindow()
var Indicador:Hwnd;
begin
// Find the window by Classname
Indicador := FindWindowByTitle('MyApp');
// if finded
if (Indicador <> 0) then
begin
// Minimize
ShowWindow(Indicador,SW_HIDE); //SW_MINIMIZE
end;
end;
I guess FindWindow(PChar('notepad'), nil) should be FindWindow(nil, PChar('notepad')) to find the window by title.