Delphi: Webbrowser.Document -> check existence of Element by ID - delphi

I've got a TWebbrowser Component and some Buttons doing stuff. This works well so far, but during one procedure I want to click on a button on the website to display additional Information, which should be clicked on as well.
So I've got this:
WebBrowser1.OleObject.Document.GetElementByID('linkDtlC0-0').Click();
WebBrowser1.OleObject.Document.GetElementByID('linkDtlC0-1').Click();
WebBrowser1.OleObject.Document.GetElementByID('linkDtlC0-2').Click();
Then I have to click a Button on the website to display the next three elements to click on. No Problem so far, but after clicking the button the site takes a few Seconds to display the three elements.
My Problem:
When I try to click the elements immediately after clicking the Button clearly it results in an error saying that there is no element (yet) to click on with this name. Of course I could make my program wait a few seconds (more) just be be sure and then try to click, but I do not want to waste time so my question is:
Is there a way to check, whether an element (by name) exists on the Document inside the TWebbrowser?

Check, whether an element exists:
uses
MSHTML;
procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant);
var
Element: IHTMLElement;
begin
if pDisp = TWebBrowser(ASender).ControlInterface then
begin
Element := (WebBrowser1.Document as IHTMLDocument3).getElementById('linkDtlC0-3');
if Assigned(Element) then
Element.click;
end;
end;

Related

Clear all the data in TEdit after data has been saved

Does anyone of you know how to clear back all the data that I have insert in TEdit field in a form after I click submit button?
I have a button "save" and after I click on this button, some message will appear like " the data have been saved". At the same time, I want the TEdit field clear from all the data that I have insert before this.
The show message appear but the TEdit field remain the same.
I don't know how to make this happen.
The following code snippet will reset the Text value of all TEdit objects (and other descendants of TCustomEdit, such as TLabeledEdit, TMaskEdit or TMemo) found on the form. Thus, after pressing the "Save" button, the entire screen will be cleared.
Additionally you can use DB objects for this. Thus, the relevant fields will be reset automatically after the Append/Post operation. Another recommendation is that if the screen will close after the "Save" button, the free agent of the relevant form will reset all objects again. You will need to create that form while opening it, and you need to send it to the "Available forms" side from Project -> Options -> Forms so that it does not auto-create.
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
for I := 0 to Self.ComponentCount - 1 do
begin
if Self.Components[I] is TCustomEdit then
begin
(Self.Components[I] as TCustomEdit).Text := '';
end;
end;
end;

Dynamically create submenu

I have a TMainMenu with a menu item called mnuWindows. I wish to create submenu items dynamically. I thought this code might do it but it doesn't work:
var
mnuitm: TMenuItem;
mnuitm:=TMenuItem.Create(nil);
mnuitm.Text:='some text';
mnuWindows.AddObject(mnuitm);
When I click on mnuWIndows, nothing happens. Where am i going wrong?
EDIT:
The submenu was not displaying on clicking because each time I did so, the program had been freshly started and I didn't realize that under these circumstances, two clicks are necessary. The first click doesn't visibly do anything and the second click drops down the submenu. So, I concede the code snippet above works.
But I still have a difficulty. I need to create several submenu items so I tried the following loop inside the mnuWindows OnClick event handler:
for I := 0 to TabSet1.Tabs.Count - 1 do
begin
mnuitm := TMenuItem.Create(mnuWindows);
mnuitm.Text := TabSet1.Tabs[I].Text;
mnuitm.OnClick:=MenuItemClick;
if not mnuWindows.ContainsObject(mnuitm) then
mnuWindows.AddObject(mnuitm);
end;
The intent of the above code is that clicking the mnuWindows item displays a list of the tabs in a tabset. This code works up to a point. On first being clicked, it correctly lists the current tabs. But when I add a tab and click on mnuWindows again, the new tab is not shown in the list. The list is exactly as before. I wondered if the menu needed updating or refreshing somehow. I came across the following method
IFMXMenuService.UpdateMenuItem(IItemsContainer, TMenuItemChanges)
but it is poorly documented and I'm not sure how to use it or even whether it is relevant.
EDIT2:
I thought the two down votes on my post were harsh. I have searched the web extensively for an example of how to dynamically create submenus in Firemonkey and there is very little. I did find a solution from 2012, but syntax changes since then mean that it does not work in Tokyo 10.2.
Try something like this. As others have commented above, you need to provide an event that will happen when the menu item is clicked. Note also that my methods here require a bunch of parameters. It would have been cleaner if I had created a class and passed the details that way, but I wrote this a long time ago and now have many places in my code that use it in this form. Also, if I wrote this now, I would use a function to return the menu item created in case I needed to interact with it in particular cases (e.g., check it, assign a hot key, etc.)
procedure PopMenuAddItem(menu: TPopupMenu; sText: string; iID: integer;
clickEvent: TNotifyEvent; bEnabled: boolean = true);
var
NewMenuItem: TmenuItem;
begin
NewMenuItem := TmenuItem.create(menu);
with NewMenuItem do
begin
Caption := sText;
tag := iID;
Enabled := bEnabled;
OnClick := clickEvent;
end;
menu.Items.Add(NewMenuItem);
end;
procedure PopMenuAddSubItem(menuItem: TmenuItem; sText: string; iID: integer;
clickEvent: TNotifyEvent; bEnabled: boolean = true);
var
NewMenuItem: TmenuItem;
begin
NewMenuItem := TmenuItem.create(menuItem);
with NewMenuItem do begin
Caption := sText;
tag := iID;
Enabled := bEnabled;
OnClick := clickEvent;
end;
menuItem.Add(NewMenuItem);
end;
I have answered my own question.
As a reminder, what I wanted to do was to dynamically create a submenu under my top level menu item "Windows" (component name "mnuWindows"). In the submenu, I wished to list the names of the tabs in a tabset.
Attempting to create the submenu dynamically in the mnuWindows.OnClick event was a failure.
My eventual solution was to rebuild the submenu with the following method and to call this method immediately after creating a new tab, removing a tab, or renaming a tab:
procedure Form1.ReBuildWindowsMenu;
var
mnuitm: TMenuItem;
I: Integer;
begin
mnuWindows.Clear; // removes submenu items
for I := 0 to TabSet1.Tabs.Count - 1 do
begin
mnuitm := TMenuItem.Create(MainMenu1);
mnuitm.Caption:= TabSet1.Tabs[I].Text; // submenu item displays same text as associated tab
mnuitm.OnClick := MenuItemClick; // makes the associated tab active
mnuWindows.AddObject(mnuitm);
end;
end;
The OnClick handler contains the single statement
TabSet1.ActiveTabIndex:=(Sender as TMenuItem).Index;
This simple solution keeps my Windows list perfectly synched with the tabs in the tabset. I'm planning to use a similar approach to put a most recently used (MRU) file list into my File menu.

Can a Component Editor be executed on multiple components?

Short Version
I am trying to implement my first ever Component Editor for a custom button I have made. With the help of some online articles I have successfully installed the editor and can see the menu item when I right click on my button in the Form Designer.
But this component editor menu is not showing when selecting more than one of my button controls.
Do Component Editors only work with single selected controls by default, or can they work with multiple selected controls and if so how?
Long Version
I was in the process of implementing a TPropertyEditor for one of my own components but have now decided that a TComponentEditor would be better served, or so I thought.
Basically I have a TCustomButton which I have ownerdrawn, this button component has several published properties for changing the appearance such as the border and fill color etc.
The Component Editor I am implementing displays in the context menu a new menu item to "Load settings from a File". When executed a simple TOpenDialog is shown to which you can select the appropriate file, for example an Ini File which I then read and set the values from the File accordingly.
Everything is working good from what I can see, but as I am still sort of new and getting to grips with the whole custom controls side of Delphi I noticed something that does not happen - I am not sure if this is the actual intended behavior or whether I can change it.
The problem is using the Component Editor menu on multiple selected instances of my button control. If just one button is selected and I right click in the Designer, my menu is shown at the top of the context menu, however multiple selected controls do not display the Component Editor menu.
Code Sample
type
TMyButtonEditor = class(TComponentEditor)
public
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;
implementation
{ TMyButtonEditor }
procedure TMyButtonEditor.ExecuteVerb(Index: Integer);
var
OpenDialog: TOpenDialog;
begin
case Index of
0:
begin
OpenDialog := TOpenDialog.Create(nil);
try
OpenDialog.Filter := 'All Files (*.*)|*.*';
if OpenDialog.Execute then
begin
// handle opened file..
end;
finally
OpenDialog.Free;
end;
end;
end;
end;
function TMyButtonEditor.GetVerb(Index: Integer): string;
begin
case Index of
0:
begin
Result := 'Load settings from File...';
end;
end;
end;
function TMyButtonEditor.GetVerbCount: Integer;
begin
Result := 1;
end;
In register procedure unit:
RegisterComponentEditor(TMyButton, TMyButtonEditor);
From what I can see only single components can use a Component Editor at any given time, or am I wrong and they can be used on multiple controls?
I was hoping to select say maybe 3 or 4 of my buttons on the Form Designer and use the Component Editor to apply imported settings on those buttons all at once.
Component editors can only operate on a single component.
This is one very good reason to prefer making properties available through the Object Inspector rather than component editors, wherever possible. Because the Object Inspector can operate on multiple components at once.

A way to redirect keypresses from TWebBrowser to the ParentForm

First question. Help format it, if needed, please.
Context
I have a TWebBrowser in the main form that is used to behave like it was a printer.
So I load some HTML text in it as user do some commands in the real printer...
I want the user to be able to click and select text from the WebBrowser.
Problem
When the user clicks in the WebBrowser some of the shortcuts registered from the actions don't work anymore. For example, there is an action with shortcut F7. If the user clicks in the WebBrowser and presses F7, it does not invoke my shortcut.
I know that this is by design of the WebBrowser.
So, I thought: I want to send every key combination back to the form.
The question is: How?
If it was another control, I could use a perform(WM_KeyDown,...) in the OnKeyDown event.
Alternatives or suggestions would be appreciated too. I am very tired these past 2 days, so I could be missing something.
Derivate TWebBrowser with an implementation of IDocHostUIHandler or use the famous EmbeddedWB
Implement the interface with an event OnTranslateAccelerator called in TranslateAccelerator
Set the event on your brwoser instance
Detect your key(s) like this:
function TBrowserPageIE.DoTranslateAccelerator(const lpMsg: PMSG; const pguidCmdGroup: PGUID; const nCmdID: DWORD): HRESULT;
begin
result := S_FALSE;
if lpMsg.message = WM_KEYDOWN then begin
if lpMsg.wParam = VK_F7 then begin
// do something here...
result := S_OK;
end;
end;
end;
An option that I tested and worked is to trap the key_code on HTML/javascript and then send that to the form using changing the document title. I will let it here hoping that help someone...
You will need add the javascript to trap the keys in the Header of the HTML page like this:
<script = ''javascript''>
function keypresed() {
var tecla=window.event.keyCode;
document.title = "Command"+tecla;
event.keyCode=0;
event.returnValue=false;
}
document.onkeydown=keypresed;
</script>
Then in Webbrowser you use the onTitleChangeEvent to use the key.
var
s:string;
begin
if Copy(Text,0,7) = 'Command' then
begin
//get the key...
s:= Copy(Text,8,Length(Text));
// if before the webbrowser get the focus edit1 was the focused control, you will need remove that focus first...
dummy.setfocus;
edit1.setfocus;
//perform keydown
keybd_event(StrToInt(s), 1,0,0)
end;
end;
Well, this can be used to perform any other custom command. :)

Detect TWebBrowser refresh event in Delphi 2009

I am using a TWebBrowser component which I use to load XML documents into which are linked to a XSL file.
I have a default page I display when no XML document is loaded. However, if the user deletes the XML file whilst it is open in the browser and then refreshes I get the standard resource could not be found error. What I would like to do instead is if the page cannot be loaded, check that the file exists, and if it doesn't just load the default page again.
I have tried using the OnNavigateError and OnBeforeNavigate2 events however they does not seem to trigger on a refresh.
Any idea's?
There is an onRefresh event which is exposed by the TWebBrowser replacement TEmbeddedWB. This version also exposes many other features which are otherwise hidden by the TWebBrowser component.
This is a bit of a cludge but it works in my tests, using the standard TWebBrowser component.
What I did was override the F5 key in the form's OnKeyUp event. By setting the form's KeyPreview property to True, you can call your own refresh. Seeing as the TWebBrowser.Refresh method doesn't appear to call any of the navigation events (as you said in your question), I call the TWebBrowser.Navigate event myself, which does fire the events.
You need to store the URL, which I imagine you're already doing. But if not, then in the BeforeNavigate2 event, you are given the URL as a parameter. So store this in a variable or on-screen control. Then, when F5 is pressed (or the Refresh button if you have put one on screen), simpy navigate to that URL again. The OnBeforeNavigate2, OnNavigateComplete2 and OnDocumentComplete events are all fired again, allowing you the opportunity to perform your test and put your placeholder page up instead of IEs default error page.
procedure TForm1.WebBrowser1BeforeNavigate2(Sender: TObject;
const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
Headers: OleVariant; var Cancel: WordBool);
begin
Edit1.Text := URL;
end;
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_F5) then
begin
WebBrowser1.Navigate(Edit1.Text);
end;
end;

Resources