Empty a TMemo with Ctrl+Enter - delphi

What I'm trying to accomplish:
User enters text into a TMemo box
If they press Enter it creates a new line
If they press Ctrl+Enter it moves the text to another box and empties the TMemo
I'm using this code [KeyPreview is True]:
procedure TFMsg.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Shift = [ssCtrl]) and (Key = $0D) then
begin
Key := 0;
btnSendClick(Sender); //this moves the text and empties the TMemo box
end;
end;
What's actually happening:
Ctrl+Enter sends the text to the other box
The TMemo empties but seems to accept the Enter key as the cursor sits flashing on the second line
Any help gratefully received. Thank you!

The best way to handle this is as follows:
Create an action list, or action manager, or re-use an existing one.
Add an action that clears the memo and moves to the next one. You'll need to check that the active control really is a memo.
Give the action the shortcut that you desire, CTRL+ENTER.
Note that you don't need to attach the action to anything. It's mere presence is enough to ensure that the shortcut will be handled.
For compound keyboard actions using modifier keys it's always simplest to use an action shortcut and so keep at arm's length from the lower level keyboard handling code.
Your action handler might look like this:
if ActiveControl is TMemo then
begin
Memo := TMemo(ActiveControl);
Text := Memo.Text;
Memo.Clear;
SelectNext(Memo, True, True);
if ActiveControl is TMemo then
begin
Memo := TMemo(ActiveControl);
Memo.Text := Text;
end;
end;
In this code I'm assuming that there are multiple memos and the text is moved from one memo to the next one in the tab order. But your needs may well differ. In which case I'm sure it will be obvious what you need to do for your scenario.

Use the Memo's OnKeyPress event for that:
procedure TFMsg.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
if (key=#10) and (GetKeyState(VK_CONTROL)<0) then
begin
key:=#0;
btnSendClick(Sender);
end;
end;
Please note that you have to check for Line Feed (#10), not for Carriage Return (#13).

property WantReturns: Boolean;

Related

Make a DBGrid go to records based on a letter pressed

Overall idea: mimic for example, pressing the 'c' key on the keyboard while on windows desktop until you get to the 'Chrome' shortcut. Or when using a listbox, pressing the letter of the value you want instead of scrolling through the whole list. I want to be able to do this for a DBGrid, so I could press a letter to jump to the record that the first character of a string of a "name column" matches the key pressed.
This is called incremental search and in the Embarcadero CodeCentral site exist a sample of how create a DBGrid - Incremental search / filter
re your comment to RRUZ answer:
I tried to get it to work without a TEdit and gave up after several hours. I don't want to discourage you, though. I would be interested in such a solution as well. Depending on the exact use case, it might be enough to just catch the entries to the dbgrid and pass them on to an invisible TEdit which then triggers the lookup. (In my case this did not suffice.) You do this by adding a DBGrid1KeyPress method with the following code:
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
var
msg: TWMKey;
begin
msg.CharCode := Ord(KEY);
PostMessage(Edit1.Handle, WM_CHAR, Ord(key), 0);
end;
You will also have to remove doEditing from the grid's options and possibly switch it to ReadOnly.
A way to do it without having TEdit appearing on screen, is to add the following event snippets. The following code will work on top of the Embarcadero CodeCentral's code that was referred previously by RRUZ:
procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key=VK_BACK then
begin
Edit1.Text := copy(Edit1.Text,1,length(Edit1.Text)-1);
end;
end;
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
if Key in ['a'..'z'] + ['A'..'Z'] then
begin
Edit1.Text := Edit1.Text + Key;
end;
end;
You can then set the Edit box invisible. All you need is to focus on the list and press keys.

OnKeyDown not working on Dialog invoked from main form (which also uses OnKeyDown)

Delphi 2010
I am using a OnFormKeyDown event on my main form, and I basically use the same event on a Dialog
//main form
procedure TfrmMain.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DOWN: btnLast.OnClick(Self);
VK_Up: btnFirst.OnClick(Self);
VK_Left: btnPrev.OnClick(Self);
VK_Right: btnNext.OnClick(Self);
end;
end;
procedure TfrmMain.mniShowOwnedClick(Sender: TObject);
var
I: Integer;
begin
frmMain.KeyPreview:= False;
frmOwned.KeyPreview:= True;
frmOwned.Owned2.Clear;
for I := 0 to Tags.Count - 1 do
if Owned.IndexOf(Tags.Names[I]) <> -1 then
frmOwned.Owned2.Add(Tags[I]);
if frmOwned.ShowModal = mrOK then
begin
frmMain.KeyPreview:= True;
frmOwned.KeyPreview:= False;
end;
end;
//Dialog
procedure TfrmOwned.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DOWN: btnLast.OnClick(Self);
VK_Up: btnFirst.OnClick(Self);
VK_Left: btnPrev.OnClick(Self);
VK_Right: btnNext.OnClick(Self);
end;
end;
The Form's OnKeyDown works fine, but I can't seem to get the dialogs to work
The problem is that these keys are used as dialog navigation keys. And as such, they never make their way to the OnKeyDown event.
To be honest I had a hard time understanding why they are firing for your main form's OnKeyDown event. I could not make that happen in my test environment. That's because I had added a button to the form. That's enough to mean that the arrow keys are treated as navigation keys. Try creating a an app with a single form and adding a few buttons. Then run the app and use the arrow keys to move the focus between the buttons. That's what I mean when I say that the arrow keys are treated as navigation keys.
I expect that the difference between your two forms is that the main form has nothing that can be navigated around by arrow keys, but the modal form does.
Now, you could stop the arrow keys being treated as navigation keys. Like this:
type
TMyForm = class(TForm)
....
protected
procedure CMDialogKey(var Message: TCMDialogKey); message CM_DIALOGKEY;
....
end;
....
procedure TMyForm.CMDialogKey(var Message: TCMDialogKey);
begin
case Message.CharCode of
VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN:
Message.Result := 0;
else
inherited;
end;
end;
However, a better solution, in my view, is to stop trying to implement shortcuts using OnKeyDown events. That seems like the wrong solution. The right solution is to use actions. Create an action list. Add actions for first, last, previous and next actions. Give them the appropriate ShortCut properties. Assign those actions to your buttons. And the job is done.
One of the benefits of this is that you can stop trying to fake button click events. For what it is worth, calling OnClick is the wrong way to do that. Call the button's Click method if ever your really need to do that. However, use an action and it's all taken care of.
Another benefit is that you'll no longer need to pfaff around with KeyPreview. Simply put, if you want to implement short cuts, use TAction.ShortCut.

Catch a mouse click on any VCL component, and determine its .Tag value

I'm using a home-grown translation tool. (Next time I'll use one of the libraries, as described here: delphi translation tool.)
My translators complain that translating a long list of strings is difficult because they're not seeing them in context (on the screen in which they appear.)
One translator made a great suggestion that he should be able to click on a component to change its text. I can implement this if I can find an way to hook program wide, an event so that when a user clicks on a component while holding down the CTRL key, an event handler is called. The event handler would determine if the component has a .Caption property, and if so, get the value of the Tag component (and then allow some user input.)
(Each translatable component has unique integer in its Tag properly, which I use to look up the component's .Caption.)
Any suggestions on how to go about this? It's over my head. I need something like a form's KeyPreview, but for mouse-clicks that would figure out what VCL component was clicked, and determine it's .Tag value.
Tom
EDIT:
Using David H.'s suggestion, the only events I get are when the app gets focus or loses it. What have I done wrong?
function TForm1.AppHookFunc(var Message : TMessage) : Boolean;
begin
Result := FALSE;
inc(i); outputdebugstring(Pchar(inttostr(i) + ': ' + IntTostr(Message.msg)));
if Message.Msg = WM_MBUTTONDOWN then
begin Beep;
//...DoSomething...
//Result := True;
end;
end;
procedure TForm1.FormCreate( Sender: TObject);
begin
Application.HookMainWindow(AppHookFunc);
end;
procedure TForm1.FormDestroy(
Sender: TObject);
begin
Application.UnHookMainWindow(AppHookFunc);
end;
EDIT 2
I'm almost there! But FindDragTarget seldom returns anything but nil. If I make an enormous button covering most of the control, I can sometimes get it to work. The X,Y coordinates in the tagMSG received are relative to the control. I would have though they'd relative to the form. Am I still using a different event hook than I should? Any suggestions:
procedure TForm1.ApplicationEvents1Message( var Msg: tagMSG;
var Handled: Boolean);
var
Target: TControl;
Point: TPoint;
begin
Handled := FALSE;
if (Msg.Message = WM_LBUTTONDOWN) And isAltDown then
begin
Point.X := LongRec(Msg.lParam).Lo;
Point.Y := LongRec(Msg.lParam).Hi;
Target := FindDragTarget( Point, {AllowDisabled=}TRUE);
if Assigned(Target) then
begin
if Target Is TButton then
outputdebugString(Pchar(TButton(Target).Caption));
end
else
outputdebugstring(Pchar(IntToStr(Point.X) + ', ' + IntToStr(Point.Y)));
end;
end;
FINAL EDIT:
I changed the code above to use GetCursorPos rather than Msg.lParam. It's working now. Very cool! SO Rocks!
THANK YOU BOTH FOR YOUR HELP!
I'm assuming this is a VCL app. For FireMonkey this would not work.
Add an Application.OnMessage event handler.
In the event handler look for WM_LBUTTONDOWN or perhaps WM_LBUTTONUP and check that the modifier key state is as you desire, e.g. CTRL is down.
Call FindDragTarget passing the position associated with the mouse event. This will give you the control under the mouse, if indeed there is one (i.e. check for nil).
Do whatever it is you want to that control.

Delphi: How to Assign an Up Arrow Keyboard Shortcut to Action/MenuItem, and Keep It Actual for Navigating the List Control (ListBox/VTV)?

Please assist me: How to assign an up arrow keyboard shortcut to action or menu item, and keep it actual for navigating the list control (e.g. ListBox/Virtual Treeview/other) at the same time?
Thanks!
You comment:
And how about the Winamp player? It has Volume Up/Volume Down features assigned to the up arrow key and down arrow key correspondingly.. Okay, if that impossible in Delphi, then ...
but it certainly is possible, it just isn't a good idea to do it, and against the Windows User Experience Interaction Guidelines.
But if you're set on implementing it, here's how. Override the following method in your form class that contains the action components:
function IsShortCut(var Message: TWMKey): Boolean; override;
and in it you can prevent the Up and Down key from triggering the actions they are shortcuts for:
function TWeirdForm.IsShortCut(var Message: TWMKey): Boolean;
begin
if (Message.CharCode in [VK_UP, VK_DOWN])
// insert test whether message needs to go to the focused control instead
and (...)
then begin
// insert calls to code that should be executed instead
Result := False;
exit;
end;
inherited;
end;
Note that you should test for the correct shift state too, and check that your code doesn't break any other window behaviour users expect, like moving of the window with the arrow keys.
On the form properties set KeyPreview := true
then on KeyUp event of the form write event to check if you Up key is pressed and make it call the menu item (on this case menu item called Action1):
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) and (ActiveControl = ListBox1)then
Action11.Click;
end;
procedure TForm1.Action11Click(Sender: TObject);
begin
if ListBox1.ItemIndex >=0 then
ShowMessage(ListBox1.Items[ListBox1.ItemIndex]);
end;
If you need the Action1 to be executed even if they Current Control isn't the listbox, remove the and part of the IF statement

Delphi 2009: Pass component name onclick event then set property

I have a custom component of type TSpeedButton that has two extra properties defined:
CommentHeading: string;
CommentText: string;
I set CommentHeading at design time.
When the speed button is pressed a memo is shown with a button beneath it for saving its contents. The procedure that handles this:
procedure CustomSpeedButton1Click(Sender: TObject);
begin
Receiver := CustomSpeedButton1.Name; // possibly used to save the memo text back to this speedbuttons property after comments are submitted
ViewComments(CustomSpeedButton1.CommentTitle,CustomSpeedButton1.CommentText);
end;
And the ViewComments procedure itself:
procedure ViewComments(comment_caption:string; comment_text:string);
begin
label15.Hide; // label showing editing in progress, hidden until user begins typing
Button1.Enabled := false; // the button for saving the memo text, hidden until user begins typing
CommentsBox.Visible := true; // pop up the comment box at the bottom of the form
CommentsBox.Caption := 'Comments: ' + comment_caption;
CommentsMemo.Text := comment_text; // if there are existing comments assign them to memo
end;
The contents of the memo need to be assigned to the CommentText property of the custom SpeedButton.
What I was initially thinking was that I could pass the component name to a variable when the custom SpeedButton gets pressed and then retrieve that name when the save button on the memo is pressed and use it to assign the memo text to the speedbuttons CommentText property. But then I realized that to do this I'd have to use some kind of case..of statement that checked for each possible speedbutton name and then assign the memo value to its properties and this just seems ridiculously tedious.
Is there an easier way to assign the memo text to the speedbutton that opened the memo to begin with?
Ultimately, you're asking how to tell the ViewComments function which button's properties it's working with.
Do you understand what the Sender parameter is doing in the OnClick event? It's telling the event handler which object's event is being handled. It's serving precisely the role that you're looking to bring to the ViewComments function.
That's what Mason was getting at in his answer. Rather than pass all the property values, pass the object itself:
procedure ViewComments(CommentButton: TCustomSpeedButton);
Then call it from all your buttons' event handlers:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
begin
ViewComments(CustomSpeedButton1);
end;
procedure TForm1.CustomSpeedButton2Click(Sender: TObject);
begin
ViewComments(CustomSpeedButton2);
end;
No strings, no case statements, no lookups.
That should answer your question, but you can do it even better. Remember what I said before about the Sender parameter? When someone clicks the first button, the Sender parameter of that OnClick handler will be the button, so we can rewrite the first event handler like this:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
And you can rewrite the second event handler like this:
procedure TForm1.CustomSpeedButton2Click(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
Hmm. They're the same. Having two identical functions is wasteful, so get rid of one and rename the other so it doesn't sound button-specific:
procedure TForm1.CommentButtonClick(Sender: TObject);
begin
ViewComments(Sender as TCustomSpeedButton);
end;
Then set the OnClick properties of both buttons to refer to that one event handler. You can't do that just by double-clicking the property in the Object Inspector. You'll need to either type the name yourself, choose it from the drop-down list, or assign the event property at run time:
CustomSpeedButton1.OnClick := CommentButtonClick;
CustomSpeedButton2.OnClick := CommentButtonClick;
I'd also like to encourage you to use more meaningful names for your controls. That Label15 is particularly egregious. How can you remember that the fifteenth label is the one that indicates that editing is in progress? Call it EditInProgressLabel, for instance.
Since you're already passing extra variables around, why not just pass the SpeedButton itself? Then you won't need to look up the reference.
A small change to your code should do the trick:
procedure TForm1.CustomSpeedButton1Click(Sender: TObject);
var
btn: TCustomSpeedButton;
begin
btn := Sender as TCustomSpeedButton;
Receiver := btn.Name;
ViewComments(btn.CommentTitle, btn.CommentText);
end;
and after editing the comment:
procedure TForm1.StoreComments(comment: string);
var
btn: TCustomSpeedButton;
begin
btn := FindComponent(Receiver) as TCustomSpeedButton;
btn.CommentText := comment;
end;
You can also memorize the button itself instead of just it's name.

Resources