Automatically allowing Ctrl+A to select all in a TMemo? - delphi

In Delphi 7's TMemo control, an attempt to do the key combo Ctrl + A to select all does not do anything (doesn't select all). So I've made this procedure:
procedure TForm1.Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
C: String;
begin
if ssCtrl in Shift then begin
C:= LowerCase(Char(Key));
if C = 'a' then begin
Memo1.SelectAll;
end;
end;
end;
Is there a trick so that I don't have to do this procedure? And if not, then does this procedure look OK?

This is more elegant:
procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = ^A then
begin
(Sender as TMemo).SelectAll;
Key := #0;
end;
end;

While the accepted answer by Andreas Rejbrand is correct, it is not the expected Windows visual behavior. It leaves the cursor position unchanged. Ctrl-A (Select All) should leave the cursor at the bottom of the text and scroll the control so the cursor is in view.
If this is not done, the control exhibits odd behavior. For example, assume there is more text than fits the window, and the window is not scrolled to the bottom. You press Ctrl-A, and all text is Selected. Ctrl-C will now copy all text to the clipboard. Although you can't see it the cursor is now at the bottom of the View, which has not scrolled. If you now press Ctrl-Down the Selected Text becomes just the text in view, then the cursor moves down and window scrolls down one line. The new bottom line is not selected. This makes it look like the Select All only selected the visible text.
The fix is simply to move the caret to the end of text before SelectAll.
procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = ^A then begin
With Sender as TMemo do begin
SelStart := Length(Text);
Perform(EM_SCROLLCARET, 0, 0);
SelectAll;
end;
Key := #0; //Eat the key to suppress the beep
end;
end;
Note that 'Eat the key' only works in the OnKeyPress event, not the OnKeyDown or OnKeyUp events.

I used the previous answer and discussion to create a standalone component which handles the KeyPress event which I use in small test programs.
TSelectMemo = class(TMemo)
protected
procedure KeyPress(var Key: Char); override;
end;
...
procedure TSelectMemo.KeyPress(var Key: Char);
begin
inherited;
if Key = ^A then
SelectAll;
end;
Another way of adding "select all" behavior to all components on a form is to add an action list to your form with a standard select all action.

Related

Delphi VCL form Arrow Key management solution fires twice

I have a VCL form with many tframes (referred to as tcellFrame) containing components arranged in a grid. I am using mouse clicks and arrow keys for the user to navigate between them. The mouse clicks work fine, but I had trouble with the arrow keys until I discovered this question thread: Delphi XE and Trapping Arrow Key with OnKeyDown. The solution in Sertac Akyuz's answer does handle getting the arrow key messages to the form using
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
case Msg.CharCode of
VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
if Assigned(onKeyDown) then
onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
else
inherited
end;
end;
but acts upon the form twice for each key stroke. Instead of moving to the left one cellframe, it moves two. Tracing the thread using the debugger demonstrates that the onkeydown event is called twice.
My onKeyDown event is structured as follows:
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
i : integer;
g, r, c, str : string;
tmpFrame : tcellFrame; //frame component containing tlabels
begin
...
case key of
VK_UP:
begin
//calc new cell location values for g,r,c
str := ('Cell'+g+r+c);
picklist.Clear; // picklist is a form-wide tstringlist variable containing currently selected cellframes.
picklist.add (str);
TmpFrame := FindComponent(picklist [0]) as TCellFrame;
tmpframe.Color := pickClr;
end;
//VK_DOWN, VK_LEFT, VK_RIGHT: defined similarly to VK_UP
end;
end;
There is more code in Formkeydown, but it is all internal calculations to determine the proper tcellframe name to place in the picklist.
My questions are:
What is causing this repeat to occur?
How do I terminate the message implementation after its first instance?
In your CM_DIALOGKEY message handler, return a non-zero value if you handle the key, then it won't be dispatched further.
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
case Msg.CharCode of
VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
begin
if Assigned(onKeyDown) then
onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
Msg.Result := 1; // <-- add this
end;
else
inherited;
end;
end;
However, if you have KeyPreview=True on the Form and the arrow keys are already being dispatched normally, then there is no need to handle CM_DIALOGKEY at all, just let the Form's OnKey... events get dispatched normally. You should not be firing the Form's OnKey... events from a CM_DIALOGKEY handler.
See A Key's Odyssey for more details.

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.

CheckBox in a DBGrid

My question is how to set a column in dbgrid in Delphi 7 which will be with a checkbox items.
Thanks in advance.
The easiest and most complete method as tested by me is as follows:
In the private section of your unit, declare a global for retaining grid options. It will be used for restoring after temporary disabling text editing while entering the checkbox column - as this is maybe one of the little errors mentioned by Jordan Borisovin regarding the delphi.about.com article
private
GridOriginalOptions : TDBGridOptions;
In the OnCellClick event, if field is boolean, toggle and post change to database
procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
if (Column.Field.DataType=ftBoolean) then
begin
Column.Grid.DataSource.DataSet.Edit;
Column.Field.Value:= not Column.Field.AsBoolean;
Column.Grid.DataSource.DataSet.Post;
end;
end;
Drawing the checkbox for boolean fields of the grid
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
const
CtrlState: array[Boolean] of integer = (DFCS_BUTTONCHECK, DFCS_BUTTONCHECK or DFCS_CHECKED) ;
begin
if (Column.Field.DataType=ftBoolean) then
begin
DBGrid1.Canvas.FillRect(Rect) ;
if (VarIsNull(Column.Field.Value)) then
DrawFrameControl(DBGrid1.Canvas.Handle,Rect, DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_INACTIVE)
else
DrawFrameControl(DBGrid1.Canvas.Handle,Rect, DFC_BUTTON, CtrlState[Column.Field.AsBoolean]);
end;
end;
Now the new part, disable cell editing while in the boolean column. On the OnColEnter and OnColExit events:
procedure TForm1.DBGrid1ColEnter(Sender: TObject);
begin
if Self.DBGrid1.SelectedField.DataType = ftBoolean then
begin
Self.GridOriginalOptions := Self.DBGrid1.Options;
Self.DBGrid1.Options := Self.DBGrid1.Options - [dgEditing];
end;
end;
procedure TForm1.DBGrid1ColExit(Sender: TObject);
begin
if Self.DBGrid1.SelectedField.DataType = ftBoolean then
Self.DBGrid1.Options := Self.GridOriginalOptions;
end;
Even more, handle space key for toggling the checkbox
procedure TForm1.DBGrid1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if ((Self.DBGrid1.SelectedField.DataType = ftBoolean) and (key = VK_SPACE)) then
begin
Self.DBGrid1.DataSource.DataSet.Edit;
Self.DBGrid1.SelectedField.Value:= not Self.DBGrid1.SelectedField.AsBoolean;
Self.DBGrid1.DataSource.DataSet.Post;
end;
end;
That's it!
If you're using the TClientDataset+TDatasetProvider+TDataset, you can manipulate the data array variant before it gets to the clientdataset and include an not updatable boolean field.
Once is done, all you need is to draw on the grid using the OnDrawColumnCell event. Here I doesn't used a CheckBox but just a bitmap (when user click it changes to selected/unselected).
Please excuse me for posting this as answer, I don't have the 50 reputation to add comments yet.
Mihai MATEI's answer is very close to the rare (as in really working) solution except for an use case where it bugs.
Whenever the users' first action on the grid is to click on the checkbox, the first click will work but the second will reveal the underlying DBGrid editor.
This happens because the "GridOriginalOptionsmechan" mechanism needs to be initialized.
To do so, just add the following code in the grid's OnEnter event:
procedure TForm1.DBGrid1Enter(Sender: TObject);
begin
DBGrid1ColEnter(Sender);
end;
That's it!
OK
I used this article for my problem. OK But the problem is that it wasn't working how it should. So I change my logic in the code. And implementing it by saving the selected rows from the dbgrid in a list.

How to hide caret in Delphi TEdit?

I want to remove the caret from a TEdit control in Delphi. I have made the component Enabled := False but the caret still appears.
My question is how to remove the caret from a disabled TEdit control?
I assume that you mean TEdit control.
The solution is HideCaret function, the only problem is where to call it. The 2 event handlers below worked fine for me:
procedure TForm18.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
HideCaret(Edit1.Handle);
end;
procedure TForm18.Edit1MouseEnter(Sender: TObject);
begin
HideCaret(Edit1.Handle);
end;
Place a TApplicationEventscontrol on the form and in the OnIdle event, hide the caret, as follows. Set the event to nil so it only fires once.
procedure TFormMain.AppEventsIdle(Sender: TObject; var Done: Boolean);
begin
AppEvents.OnIdle := nil;
HideCaret(Memo1.Handle);
end;

Is mouse over Form?

This code is not working for me on Delphi XE:
http://delphi.about.com/cs/adptips2000/a/bltip0800_5.htm
procedure TForm1.FormDeactivate(Sender: TObject) ;
begin
ReleaseCapture;
end;
procedure TForm1.FormMouseMove
(Sender: TObject; Shift: TShiftState; X,Y: Integer) ;
begin
If GetCapture = 0 then
SetCapture(Form1.Handle) ;
if PtInRect(Rect(Form1.Left,
Form1.Top,
Form1.Left + Form1.Width,
Form1.Top + Form1.Height),
ClientToScreen(Point(x, y))) then
Form1.Caption := 'Mouse is over form' else
Form1.Caption := 'Mouse is outside of form';
end;
No errors - it just has no effect.
Please help.
EDIT 1
It turned out the problem is not with the code, even mouse FormMouseEnter and FormMouseLeave are not working because I'm passing the form to a Unit I created with a function like this:
procedure Slide(Form: TForm; Show: Boolean);
I'm calling the Show method from inside this procedure. How can I overcome this problem?
Thanks.
EDIT 2
I don't want to use the function I posted now. I want to use what the people suggested below (FormMouseEnter and FormMouseLeave) but it's not working in my case as well.
You could use OnMouseEnter and OnMouseLeave events to keep track of whether the mouse is over the form or not without capturing the mouse cursor.
This is just a matter of entering the necessary code in the OnMouseEnter and OnMouseLeave events of the corresponding form. In my case, all I did was :
Create a new project in Delphi
Select the form you are working with
Go to the Object Inspector
Switch to the Events tab in the object inspector if necessary
Scroll down to the OnMouseEnter event, double click in the white space next to it which will generate an EventHandler. Make sure you end up with the following code in the event handler :
procedure TForm1.FormMouseEnter(Sender: TObject);
begin
Self.Caption := 'Mouse in form';
end;
Go to the Object Inspector again
Find the OnMouseLeave event and double click in the white area to the right of it to generate a new event handler, and add the following code to it
procedure TForm1.FormMouseLeave(Sender: TObject);
begin
Self.Caption := 'Mouse outside form';
end;
Run the app ... move your mouse over the form and the caption will change to 'Mouse inside form', move it outside the form and the caption will say 'Mouse outside form'
Works as a charm (Tested in Delphi 2010)
As far as I understand, using SetCapture for this is a bad idea. If it functioned like you wish, you would be robbing everyone else of the mouse messages just because you don't know a better way to track mouse.
But MSDN says (http://msdn.microsoft.com/en-us/library/ms646262(VS.85).aspx) that even with SetCapture, mouse messages from outside would not be redirected to your window unless mouse button is down (probably a measure to prevent exactly what are you trying to achieve: stealing mouse without a valid reason).
It doesn't matter where are you calling Show() from, so your problem is not in that.
I needed a form (frmTasks) with a heavily modified Caption. So I created a form with a hidden Caption. I simulate the Caption with a TImage (imgRedLogo) on which I draw the stuff I need.
This code lets the user to click the fake caption (the image) and move the form around. Works like a charm WITHOUT capturing the mouse. It works even with the right mouse button (you have to test the 'Button' parameter in imgRedLogoMouseDown if you want to disable this 'feature').
FULL WORKING CODE:
VAR
Dragged : Boolean= FALSE;
IsOverImg: Boolean= FALSE; { True if mouse if over the image }
OldPos : TPoint;
procedure TfrmTasks.imgRedLogoMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Dragged:= True;
GetCursorPos(OldPos);
end;
procedure TfrmTasks.imgRedLogoMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
VAR NewPos: TPoint;
begin
if Dragged AND IsOverImg then
begin
GetCursorPos(NewPos);
frmTasks.Left:= frmTasks.Left- OldPos.X + NewPos.X;
frmTasks.Top := frmTasks.Top - OldPos.Y + NewPos.Y;
OldPos:= NewPos;
end;
end;
procedure TfrmTasks.imgRedLogoMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Dragged
then Dragged:= False;
end;
procedure TfrmTasks.imgRedLogoMouseEnter(Sender: TObject);
begin
IsOverImg:= TRUE;
end;
procedure TfrmTasks.imgRedLogoMouseLeave(Sender: TObject);
begin
IsOverImg:= FALSE;
end;
Enjoy.

Resources