How to Disable Backspace in WebBrowser - delphi

When I press Backspace, Web-browser will go to previous page. How can I disable it without preventing the clearing text ability?

TWebBrowser doesn't seem to have an OnKeydown event, and it also has many other problems. I usually forget it and go to TEmbeddedWB (embedded web browser component pack), and it has better features.
I wrote some code to try to detect if we are in an edit control, and only conditionally block backspace keys, since as Paktas says, simply blocking the OnKey event would break editing in
web page forms, using TEmbeddedWB, the code looks like this:
procedure TForm2.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
element: IHTMLElement;
elementTagName: String;
begin
// form contains field: EmbeddedWB1: TEmbeddedWB which is an improved version of TWebBrowser.
element := EmbeddedWB1.GetActiveElement;
if Assigned( element ) then
begin
elementTagName := element.tagName;
end;
if ( not SameText( elementTagName, 'INPUT' ) )
and ( not SameText( elementTagName, 'TEXTAREA' ) ) then
if ((Key= VK_LEFT) and (Shift = [ssAlt]))
or ((Key= VK_RIGHT) and (Shift = [ssAlt]))
or (Key= VK_BROWSER_BACK)
or (Key= VK_BROWSER_FORWARD)
or(Key = VK_BACK) then
begin
Key := 0; { block backspace, but not in INPUT or TEXTAREA. }
Exit;
end;
end;

The only way for doing this is to check for each key press and if a backspace is detected ignore it. However you would also need to monitor whether any of your inputs are focused, so that users could stil preserve backwards deleting.

Related

Search and Replace in a Trichedit

We are working with XE7. We are trying to build a search and replace feature into a TRichEdit.
We would like it to work just like it works in the Delphi compiler. We display our own search and replace dialog, we are not using the Delphi replace dialog.
We display the dialog, and when the user clicks the Replace button, the dialog closes, and for each instance found we want to popup a message dialog asking the user whether they want to replace that occurrence.
When we do this, the user cannot see what is selected, and if they click Yes on the message dialog, nothing is being replaced.
Our code is below:
procedure Tviewform.select_text(fndpos:integer);
begin
setfocus;
asciieditor.selstart:=fndpos;
asciieditor.sellength:=length(vwvars[curviewwin]^.finddata);
end;
procedure Tviewform.search_and_replace;
var position,endpos,fndans,cont:integer; foptions:tsearchtypes; msgques,stopall:word;
begin
with asciieditor do
begin
endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
foptions:=[];
stopall:=0;
cont:=0;
if findinfo.onbut.checked then foptions:=foptions+[stMatchCase];
lines.beginupdate;
fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
while (fndans<>-1) and (stopall=0) do
begin
select_text(fndans);
msgques:=mainform.silang1.messagedlg('Replace with '+vwvars[curviewwin]^.replace+'?.',mtinformation,[mbyes,mbno,mbcancel],0);
if msgques=mryes then
begin
asciieditor.clearselection;
seltext:=vwvars[curviewwin]^.replace;
end else
begin
if msgques=mrcancel then
begin
cont:=0;
stopall:=1;
end else cont:=1;
end;
asciieditor.Repaint;
if cont=1 then
begin
inc(vwvars[curviewwin]^.sstartpos,length(vwvars[curviewwin]^.finddata));
endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
end;
end;
lines.endupdate;
end;
end;
I see a number of issues with your code:
You are moving input focus away from the TRichEdit when you call select_text() and MessageDlg(), so make sure that TRichEdit.HideSelection is set to False (it is True by default) in order to actually see the selection.
You are calling BeginUpdate() on the TRichEdit, which disables screen redraws for it, including Repaint().
You don't need to call TRichEdit.ClearSelection() before setting the TRichEdit.SelText.
When updating vwvars[curviewwin]^.sstartpos after each replacement, you need to reset it to fndAns (the current selection's position) plus the length of vwvars[curviewwin]^.replace (the new text), not the length of vwvars[curviewwin]^.finddata (the old text). You are not moving it beyond the new text, so you are searching older text over and over.
If the user chooses No in the MessageDlg(), you are not updating vwvars[curviewwin]^.sstartpos to move past the text that the user didn't want to replace.
When setting endpos, length(asciieditor.text) can be asciieditor.gettextlen() instead for better efficiency. But more importantly, you should not be subtracting vwvars[curviewwin]^.sstartpos from endpos at all. By doing that, you are skipping more and more text at the end of the TRichEdit from the search with each replacement made. In fact, you should be using just the asciieditor's current text length as the end position of each search, so you don't actually need endpos at all.
Depending on the size of your TRichEdit and its content, if it has scrollbars enabled, you should send the TRichEdit a EM_SCROLLCARET message each time you find a matching text and before you prompt the user with the MessageDlg(), in case the matching text is off-screen.
With that said, try something more like this:
procedure Tviewform.search_and_replace;
var
fndAns: Integer;
fOptions: TSearchTypes;
msgQues: Word;
procedure select_text;
begin
{AsciiEditor.}SetFocus;
AsciiEditor.SelStart := fndAns;
AsciiEditor.SelLength := Length(vwvars[curviewwin]^.finddata);
AsciiEditor.Perform(EM_SCROLLCARET, 0, 0);
AsciiEditor.Update;
end;
begin
fOptions := [];
if FindInfo.OnBut.Checked then Include(fOptions, stMatchCase);
repeat
fndAns := AsciiEditor.FindText(vwvars[curviewwin]^.finddata, vwvars[curviewwin]^.sstartpos, AsciiEditor.GetTextLen, fOptions);
if fndAns = -1 then Break;
select_text;
msgQues := MainForm.SiLang1.MessageDlg('Replace with ' + vwvars[curviewwin]^.replace + '?', mtinformation, [mbYes,mbNo,mbCancel], 0);
if msgQues = mrYes then
begin
AsciiEditor.SelText := vwvars[curviewwin]^.replace;
AsciiEditor.Update;
Inc(fndAns, Length(vwvars[curviewwin]^.replace));
end
else if msgQues = mrNo then
begin
Inc(fndAns, Length(vwvars[curviewwin]^.finddata));
end else
Break;
vwvars[curviewwin]^.sstartpos := fndAns;
until False;
end;

When iterating through controls on a form, how can I identify particular buttons?

I need to make some changes to a TaskDialog before it is shown to the user. It's fairly simple to use Windows API calls to work with each of the controls on the dialog box. I need to be more sure which button I have found. I would have expected to find a place where I could read the result the button would give if pressed.
in other words, if I pressed a button that would cause a return value (in Delphi, it's called a modal result) of 100, I would have expected there to be an API call I could call to find out what the button's "return value" would be. I haven't yet found any such call.
I don't want to rely on the button text..
Here's what I have so far.
function EnumWindowsProcToFindDlgControls(hWindow: HWND; _param:LPARAM): BOOL; stdcall;
var
sClassName:string;
hBMP:THandle;
i:integer;
begin
SetLength(sClassName, MAX_PATH);
GetClassName(hWindow, PChar(sClassName), MAX_PATH);
SetLength(sClassName, StrLen(PChar(sClassName)));
if sClassName='Button' then
begin
// always 0...
i:=GetDlgCtrlID(hWindow);
if (i=100) or (i=102) then
begin
hBmp := LoadImage(HInstance, 'DISA', IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE or LR_LOADTRANSPARENT );
SendMessage(hWindow, BM_SETIMAGE, WPARAM(IMAGE_BITMAP), LPARAM(hBmp));
end;
end;
// keep looking
Result:=true;
end;
procedure TForm2.TaskDialog1DialogConstructed(Sender: TObject);
begin
EnumChildWindows(TaskDialog1.Handle, #EnumWindowsProcToFindDlgControls, 0);
end;
I suspect it's not entirely "respectable" to do things like this with a dialog.
This is a Delphi 10 Win32 application using Delphi's VCL TTaskDialog component which is a wrapper around Windows task dialog feature. before it's shown, the OnConstructed event fires, executing this code.
Thank you for your help!
Win32 buttons do not have "return values", which is why there is no API to retrieve such a value from them. What you are thinking of is strictly a VCL feature.
In Win32 API terms, a button can have a control ID, and in the case of MessageBox(), for example, standard ID values like IDOK, IDCANCEL, etc are assigned to the dialog buttons. When a button is clicked and the dialog is closed, the button's control ID is used as the function return value.
But task dialogs do not use control IDs, which is why you do not see any assigned to the dialog buttons.
To identify a particular task dialog button, I can think of two ways:
during child enumeration, retrieve each button's caption text (GetWindowText()), and compare that to captions you are interested in. Just know that the standard buttons (from the TTaskDialog.CommonButtons property) use localized text, which does not make this a well-suited option for locating standard buttons unless you have control over the app's locale settings.
send the dialog a TDM_ENABLE_BUTTON message to temporarily disable the desired button that has a given ID, then enumerate the dialog's controls until you find a disabled child window (using IsWindowEnabled()), and then re-enable the control. You can then manipulate the found window as needed.
For Task Dialog messages and Task Dialog Notifications that operate on buttons (like TDN_BUTTON_CLICKED, which triggers the TTaskDialog.OnButtonClicked event), the standard buttons use IDs like IDOK, IDCANCEL, etc while custom buttons (from the TTaskDialog.Buttons property) use their ModalResult property as their ID.
You can send TDM_ENABLE_BUTTON directly via SendMessage() for standard buttons, or via the TTaskDialogBaseButtonItem.Enabled property for custom buttons.
For #2, this works when I try it:
uses
Winapi.CommCtrl;
function FindDisabledDlgControl(hWindow: HWND; _param: LPARAM): BOOL; stdcall;
type
PHWND = ^HWND;
begin
if not IsWindowEnabled(hWindow) then
begin
PHWND(_param)^ := hWindow;
Result := False;
end else
Result := True;
end;
procedure TForm2.TaskDialog1DialogConstructed(Sender: TObject);
var
hButton: HWND;
begin
// common tcbOk button
SendMessage(TaskDialog1.Handle, TDM_ENABLE_BUTTON, IDOK, 0);
hButton := 0;
EnumChildWindows(TaskDialog1.Handle, #FindDisabledDlgControl, LPARAM(#hButton));
SendMessage(TaskDialog1.Handle, TDM_ENABLE_BUTTON, IDOK, 1);
if hButton <> 0 then
begin
// use hButton as needed...
end;
// custom button
TaskDialog1.Buttons[0].Enabled := False;
hButton := 0;
EnumChildWindows(TaskDialog1.Handle, #FindDisabledDlgControl, LPARAM(#hButton));
TaskDialog1.Buttons[0].Enabled := True;
if hButton <> 0 then
begin
// use hButton as needed...
end;
end;

How do I add the key binding Shift+Ctrl+H X to the Delphi IDE using the ToolsApi?

Adding a new ShortCut to the Delphi IDE is not too difficult because the Open Tools API provides a service for this. I am trying something apparently more complex: Add a Wordstar like additional ShortCut:
I want something to happen when the user presses
Shift+Ctrl+H followed by the single key X
where X should work regardless of the state of the Shift key.
This is my code:
procedure TGxKeyboardBinding.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
const
DefaultKeyBindingsFlag = kfImplicitShift + kfImplicitModifier + kfImplicitKeypad;
var
GExpertsShortcut: Byte;
ShiftState: TShiftState;
FirstShortCut: TShortCut;
SecondShortCut: TShortCut;
begin
GExpertsShortcut := Ord('H');
ShiftState := [ssShift, ssCtrl];
FirstShortCut := ShortCut(GExpertsShortcut, ShiftState);
SecondShortCut := ShortCut(Ord('X'), []);
BindingServices.AddKeyBinding([FirstShortCut, SecondShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
end;
So, if I set ShiftState := [ssCtrl] pressing
Ctrl+H X
calls my TwoKeyBindingHandler method.
But with ShiftState := [ssShift, ssCtrl] pressing
Shift+Ctrl+H X
does nothing.
Oddly enough, when specifying ShiftState := [ssShift, ssCtrl] (which should only affect the first key) pressing
Shift+Ctrl+H Shift+X
calls my TwoKeyBindingHandler method, even though the second ShortCut is added without a modifier key.
Any idea? Is this maybe a known limitation/bug of the Delphi IDE/Open Tools API? Is there a known workaround?
I tried it in Delphi 2007 and Delphi 10 Seattle, no difference.
You should be able to do it using the GetKeyState function.
The program has two operations - Think of it as opening a drop down menu item. When ctr-shift-h is pressed your programme will need to flag that the 'Menu' is now open and that subsequent keypresses will either activate an option or close the 'menu' if an invalid key is presses.
function IsKeyDown(const VK: integer): boolean;
begin
IsKeyDown := GetKeyState(VK) and $8000 <> 0;
end;
procedure Form1.OnkeyDown(...)
begin
if Not H_MenuOpen then
if IsKeyDown(vk_Control) and IskeyDown(vk_Shift) and IsKeyDown(vk_H) then
begin
//Some Boolean in the form
H_MenuOpen:=True;
//Will probably need to invalidate some parameters here so that
//no control tries to process the key
exit;
end;
if H_MenuOpen then
begin
if key=vk_X then
begin
//x has been pressed
*Your code here*
//possibly invalidate some of the params again
exit;
end;
//Nothing valid
H_MenuOpen:=False;
end;
end;
OK, since apparently nobody has found an answer, here is what I ended up doing:
I had already planned to show a hint window listing all possible characters for the second key (actually that code was already working fine, using the approach suggested by Helen Fairgrieve in her answer to this question). Instead, I now register only a one-key shortcut:
BindingServices.AddKeyBinding([FirstShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
And in the TwoKeyBindingHandler method, I show a popup menu which contains those characters as the shortcuts. The IDE/VCL/Windows then handles the rest for me.
This is what it looks like:
It's not an answer to the actual question but it solves my problem. Sorry if you got here expecting something more.

detect enter on a memo in Delphi

hi i am doing a command shell using a memo in Delphi , the problem is to detect the last line written and read the command I need to know how to detect the enter key on a memo.
as I can detect the enter key on a memo ?
In the OnKeypress event you can check for certain keys and handle them as you wish yourself. The enter key is one of these keys.
procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
const
ENTER = #13;
begin
case Key of
ENTER : begin
// Do something
end;
end;
end;
By default a TMemo has the WantReturns property set to TRUE. This means that as well as any response to the key press that you might implement in your code, the TMemo will still also receive the key event and add a new line to the content of the memo.
If you do not want this then you can either:
Set WantReturns = FALSE. you will still get the KeyPress
event for the enter key, but the memo will not add new lines in
response (they can still be added if the user presses Ctrl +
Enter)
OR
Keep WantReturns = TRUE but set Key to the value #0 for any key
events which you want to suppress (i.e. prevent from reaching the
memo control).
An example of this latter approach might look something like this:
const
NO_KEY = #0;
ENTER = #13;
begin
case Key of
ENTER : begin
// Do something
if NOT AddNewLine then
Key := NO_KEY;
end;
end;
end;
NOTE: The OnKeyPress event only allows you to respond to a subset of key events, specifically those that correspond to CHAR type values (although this does include some non-printing characters such as Tab and Backspace, for example).
If you want or need to detect the state of a wider range of non-character keys or to reliably handle key combinations such as Ctrl+Key or Shift+Key then you will need to query the state of those modifier keys. However, by the time you are responding to the key event, the state of the modifier keys may have changed and a better approach in that case is to use an alternate event which provides a greater range of key events, including the state of the Shift keys (and Control keys) at the time of the key event itself, such as OnKeyDown.
You can use OnKeyDown event, for example:
procedure TForm.Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_Return then
begin
// Your code here ...
// set Key to 0 if you do not want the key
// to be default-processed by the control...
Key := 0 ;
end;
end;
Detecting the enter key in a TMemo control is easy. Just add an OnKeyPress event:
procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
// Do something
end;
end;

How to avoid the ding sound when Escape is pressed while a TEdit is focused?

In code I have developed some years ago I have been using this a lot to close the current form on pressing the Escape key at any moment:
procedure TSomeForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
if key = #27 then close;
end;
This behaviour is defined for the TForm. The form's KeyPreview property is be set to True to let the form react to key presses before any other components. It all works perfectly well for the best part of the program, however, when the Escape key is pressed while a TEdit component is focused a sound (a ding sound used by Windows to signify invalid operation) is issued. It still works fine but I have never quite managed to get rid of the sound.
What's the problem with this?
Steps to recreate:
new VCL Forms application, set the form's KeyPreview to true
on the Events tab double-click the onKeyPress event and enter dummy code:
if key=#27 then ;
add a TListBox, TCheckBox, TEdit to the form and run the application
in the application try pressing Esc and NOTHING happens, as specified by the dummy code
focus the TEdit and press Esc. Nothing happens but the sound is played.
You get the ding because you left the ESC in the input. See how Key is a var? Set it to #0 and you eliminate the ding. That removes it from further processing.
procedure TSomeForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
if key = #27 then
begin
key := #0;
close;
end;
end;
KeyPreview is just that, a preview of what will be passed to the controls unless you stop it.
Starting from Jim's answer (thanks Jim) I had to make it work for me. What I needed was to make a dropped down combobox close keeping the selected item and move to the next/previous control when TAB/shift+TAB was pressed. Everytime I did press TAB the annoying sound filled the room. My work arroud was using onKeyDown event to catch the shiftstate, declaring var aShift: boolean; in form's interface and use the following code:
procedure TForm2.StComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if ssShift in Shift then aShift := true else aShift := false;
end;
procedure TForm2.StComboKeyPress(Sender: TObject; var Key: Char);
begin
if Key=char(VK_TAB) then
begin
Key := #0;
StCombo.DroppedDown := false;
if aShift
then previousControl.SetFocus
else nextControl.SetFocus;
end;
end;
Using the menu items and setting them to invisible, and using the shortcut, is a quick workaround that I've just stumbled across, but won't work if you need a shortcut that uses a character that is used in the first letter of an existing shortcut: For example for Alt+ENTER, you need to add something like this to the form create procedure:
MainMenu1.Items[0].ShortCut:=TextToShortCut('Alt+e');
However it's probably easier to use TActionList instead, and even though something like Alt+E is not listed you can add it.
It's an old thread... but anyway, here's a far better one: catching Alt-C!
Unlike ESC, Alt-C isn't serviced by KeyPress, so setting Key to #0 in KeyPress doesn't work, and the horrendous "ding!" is issued every time.
After hours of trying, here's the workaround I found:
- create a main menu option to service the request
- set its ShortCut to Alt+C - yes indeed, that is NOT one of the available ShortCut choices(!!)... but it does work anyway!
- do the processing in that menu option's OnClick
- you may even make in "in the background": you may set the menu option's Visible to false - as long as its Enabled stays true, it will be activated by Alt-C even though it will not be visible in the menu.
Hope that may help! And if you have something more elegant, please advise.

Resources