How to restrict input to letters only in Delphi XE5? - delphi

Ran into a couple of problems using a code that only lets the user input alphabetical characters and a backspace.
When I compile my program using RAD studio 2010, apart from Vcl problems in the Uses clause, It compiles correctly, all working fine. However when I try and compile using XE5, I get an error: E2010 Incompatible types: 'Word' and 'AnsiChar'
If anyone can point me in the right direction it would be great!
Code below:
procedure TFRMStuTakeTest.DBEDTWord01KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
(*only return value if alphabetic *)
if Key in ['A'..'Z', 'a'..'z', #8] then
else
Key := #0;
end;
Sorry if there's something about procedures having to be from clean projects (i.e. unnamed/ not descriptive)

OnKeyDown is designed to deal with virtual key codes (those that are referred to using the VK_ constants), not individual letter and number keystrokes.
Use OnKeyPress to process individual characters, not OnKeyDown.
procedure TFRMStuTakeTest.DBEDTWord01KeyPress(Sender: TObject; var Key: Char);
begin
if not CharInSet(Key, ['A'..'Z','a'..'z', #8]) then
Key := #0;
end;
Better still, use the EditMask on the underlying TField, and set a valid mask for alpha characters using something like 'LLLLL;0;_', which would require 5 letters between '['A'..'Z','a'..'z']', and will handle all of the validations, editing keystrokes, and so forth.
YourTable.FieldByName('Word1').EditMask := 'LLLLL;0;_';
See the documentation for TField.EditMask for more information, and follow the link at the bottom to TEditMask for details about the mask characters. (There's an editor for the EditMask property in the Object Inspector; they're the same as those used by TMaskEdit, so you can drop one of those on your form and click the ... button on the right side of the EditMask property to access it.)

Related

Problem when using return key to move to TDBGrid from TEdit

In a search form I have two components, a TEdit and and TDBGrid. The user enters text into the TEditand in the TEdit.OnChange event I search for matching items in a table using a query. These are displayed in a TDGrid. The event only triggers when the user has entered more than three characters in the TEdit.Textto cut down the overhead.
The problem occurs when the search has returned all possible records and the user wants to select one of the records displayed in the TDBGrid. While I told the users to use the Tab key (or the mouse) to switch from the TEdit to the TDBGrid, they use the return key to do this in another application and are insisting that they should be able to do the same thing in the application I'm altering/adding to. I can understand this, but the problem is that if I use
if (key = VK_RETURN) then key := VK_TAB;
in the TEdit.OnKeyUp event, the original value of the last key pressed in the TEdit is "remembered" as VK_RETURN and is passed to the TDBGrid's OnKeyUp event. Since I have other actions triggered in that event, I get undesired things happening as soon the the TDBGrid gains focus because they also want to select the correct row in the grid by pressing the return key again.
Therefore what I want to do is to "cancel" the key value passed to the TDBGrid from the TEdit. I tried using if (Sender = TEdit) then Key := VK_CANCEL; in the DGBrid's OnKeyUp event but I get a compiler "Incompatible types" error and I can't find anything to tell me how I should use it in this situation.
Is this possible? Or am I going about this the wrong way?
Thanks in advance!
I believe the following approach satisfies your needs and I suspect it to be the shortest such approach:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
Key := #0;
end;
procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key = VK_RETURN then
begin
PostMessage(Handle, WM_NEXTDLGCTL, 0, 0);
Key := 0;
end;
end;
This will make Enter, in Edit1, move focus to the next control. This is accomplished by posting the form a WM_NEXTDLGCTL message. The OnKeyPress handler is needed to suppress the "invalid input" message beep.
For bonus points, do
PostMessage(Handle, WM_NEXTDLGCTL, Ord(ssShift in Shift), 0);
instead to have Shift+Enter correspond to Shift+Tab.
I'm not quite sure I like this entire idea, though. I think I prefer to have Enter act on key down instead. The target control shoudn't care about this key up message.
Just to add to Andreas answer, I have used the same approach. There are 3 events you can play around with, onkeydown, onkeypress, onkeyup. At some point you will probably have to assign the key to null to avoid the "beep". The other thing to watch out for are unintended side effects, like moving 2 fields instead of one as has happened to me in the past.

Is there any way to disable selecting of text in a memo control?

Is there any way to disable selecting of text in a memo control because it's very anoying.
The memo is Read Only.
I think you should rethink. I realise that your control is used in read-only mode, but still, what if the end user wishes to copy a part of the text? Then he needs to be able to select the part in question.
Still, if you are certain that you need to disable every kind of selection, the easiest approach is to use a TRichEdit instead of the TMemo, and do simply
procedure TForm1.RichEdit1SelectionChange(Sender: TObject);
begin
RichEdit1.SelLength := 0;
end;
You could also use the onMouseUp event
procedure TForm1.Memo1MouseUp(Sender: TObject: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Memo1.SelLength > 0 then
Memo1.SelLength := 0;
end;
But, that doesn't stop selecting with the keyboard..
or you could also use the onEnter, and just change the focus to another control on your form.
procedure TForm1.Memo1Enter(Sender: TObject);
begin
Edit1.SetFocus;
end;
I played around with TRichEdit and TMemo until I was bored to tears. Yes, you can do a few tricks with event handling on the object, but it still is not the desired effect - and the cursor winds up blinking somewhere. So the best thing I could find was to use TLabel. I'm using Borland C++ Builder 6, and the \n is translated correctly with inline text strings for TLabel. So,
Label1->Caption = "this is a test of the emergency\n"
"broadcast station, this is only\n"
"a test. If this had been an\n"
"actual emergency, blah blah blah...\n";
Works just fine. I haven't tried to read in from a file, but I'm certain that if the stream were exactly as seen it would also work. Since you are going to have to enter or read in the text you want displayed anyway - this should work well instead of using a bunch of TLabels for each line. If you have a concern for word wrapping, you will have to process that portion separately. If it static, then just do it by hand like I did in the example. I sure hope this helps or at least gives an idea...
atomkey -
As i understand you would like to use memo as label actually (and sometimes it really have sense).
When i need to use TcxMemo (memo component from DeveloperExpress) as label i use such simple procedure:
procedure ShowMemoAsLabel(m: TcxMemo);
begin
m.Enabled := False;
m.Properties.ReadOnly := True;
// AH: Unfortunately it doesn't copy some important properties, maybe it will
// be fixed in future versions of DEX, but at moment we do some job ourselves.
m.StyleDisabled := m.Style;
m.StyleDisabled.BorderColor := m.Style.BorderColor;
m.StyleDisabled.BorderStyle := m.Style.BorderStyle;
m.StyleDisabled.Color := m.Style.Color;
m.StyleDisabled.Edges := m.Style.Edges;
m.StyleDisabled.Shadow := m.Style.Shadow;
m.StyleDisabled.TextColor := m.Style.TextColor;
m.StyleDisabled.TextStyle := m.Style.TextStyle;
m.StyleDisabled.TransparentBorder := m.Style.TransparentBorder;
end;

Need a ComboBox with filtering

I need some type of ComboBox which can load it's items from DB. While I type some text in to it, it should filter it's list, leaving only those items, that have my text somewhere (at the beginning, middle...). Not all my DataSet's have filtering capabilities, so it is not possible to use them. Is there any ready to use components with such abilities? I have tried to search in JVCL, but without luck.
You could try customizing the autocomplete functionality of a regular ComboBox. Loading its items from a DB is easy:
ComboBox1.Items.Clear;
while not Table1.Eof do begin
ComboBox1.Items.AddObject( Table1.FieldByName('Company').AsString,
TObject(Table1.FieldByName('CustNo').AsInteger) );
Table1.Next;
end;
As far as the auto-complete for middle-of-word matching, you might try adapting this code. The functionality that matches at the beginning of the text in the Items is enabled by setting AutoComplete to true, and needs to be turned off before you try writing your own OnChange event handler that does auto-complete. I suggest that you could more safely do the match and selection on the enter key, because attempting to do it on the fly makes things quite hairy, as the code below will show you:
Here's my basic version: Use a regular combobox with onKeyDown, and onChange events, and AutoComplete set to false, use above code to populate it, and these two events
procedure TForm2.ComboBox1Change(Sender: TObject);
var
SearchStr,FullStr: string;
i,retVal,FoundIndex: integer;
ctrl:TComboBox;
begin
if fLastKey=VK_BACK then
exit;
// copy search pattern
ctrl := (Sender as TCombobox);
SearchStr := UpperCase(ctrl.Text);
FoundIndex := -1;
if SearchStr<>'' then
for i := 0 to ctrl.Items.Count-1 do begin
if Pos(SearchStr, UpperCase(ctrl.Items[i]))>0 then
begin
FoundIndex := i;
fsearchkeys := ctrl.Text;
break;
end;
end;
if (FoundIndex>=0) then
begin
retVal := ctrl.Perform(CB_SELECTSTRING, 0, LongInt(PChar(ctrl.Items[FoundIndex]))) ;
if retVal > CB_Err then
begin
ctrl.ItemIndex := retVal;
ctrl.SelStart := Pos(SearchStr,UpperCase(ctrl.Text))+Length(SearchStr)-1;
ctrl.SelLength := (Length(ctrl.Text) - Length(SearchStr));
end; // retVal > CB_Err
end; // lastKey <> VK_BACK
end;
procedure TForm2.ComboBox1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
fLastKey := Key;
end;
Suppose the contents of the list are "David Smith", and "Mark Smithers". You type S and it matches the first letter of the last name, in David Smith. Now it shows David Smith with the "David S" part not selected, and the "mith" part selected (so that the next characters you type will replace the auto completed portion, a standard auto-complete technique). Note that the above code has had to prefix the S you typed with the "David " part you didn't type. If you are a lot more clever than me, you can find a way to remember that the user typed "s" and then, maybe an "m", followed by some more letters, and eventually having typed "Smithe", match Smithers, instead of always David smith. Also note that you can only set the SelStart and SelLength to select a continuous length of a string.
The code I have provided will only work when the list of items never contains any repeated substrings. There are good reasons why the Windows Common Control combobox "autocomplete" functionality only works with prefix matching, and not mid-string matching.
Since anything that would implement mid-string matching should probably draw the part you typed in not-selected, and since that not-selected part would be in mid-string, you would probably need to write your own control from scratch and not rely on the TComboBox base code, and its underlying MS Common Controls combobox functionality.
DevExpress' "TcxExtLookupCombobox" has this capability - and more. Might be overkill though.

"pressing" the button of TButtonedEdit using the keyboard

I was just about to replace a TEdit + TButton combination with one TButtonedEdit control but when I tried to test it, I found no way to "press" the (right) button using the keyboard.
I tried Alt+Enter, Alt+Down, Alt+Right, the same keys with Ctrl and a few more key combinations but none of them worked. The VCL sources did not shed any light on this issue either (but hey "professional programmers don't look at the VCL sources" anyway)
Am I missing something?
This is with Delphi 2010 on a Windows XP box, the TButtonedEdit component was introduced in Delphi 2009 IIRC.
Note: I have accepted Andreas Rejbrand's answer because it answers the question. But I have also added my own answer for the benefit of those who might be interested in what I actually implemented.
No, there is no such keyboard shortcut, partly (maybe) because of the ambiguity in which button (the left or right button) the keyboard shortcut should execute.
I always do it like this:
procedure TForm1.ButtonedEdit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_RETURN) and (ssCtrl in Shift) then
ButtonedEdit1RightButtonClick(Sender);
end;
The Ctrl+Enter shortcut is very natural if the button displays a modal dialog (which helps the user fill the edit box), or something similar. If it instead executes a procedure taking the edit text as argument (e.g., an address bar or a search box), Enter alone is more suitable. If the button is a clear button (that clears the edit box), then Escape might be the best shortcut, or possibly no shortcut at all (and then it is a good thing that there is no default shortcut).
The fact that the suitable shortcut depends on the situation also suggests that there should be no default shortcut, I think.
By the way, don't forget to make the TButtonedEdit DoubleBuffered, for otherwise it will flicker way too much.
I have now created an interposer class that looks like this:
interface
{...}
type
TdzButtonedEdit = class(TButtonedEdit)
protected
procedure KeyDown(var _Key: Word; _Shift: TShiftState); override;
public
procedure Loaded; override;
end;
{...}
implementation
{...}
{ TdzButtonedEdit }
procedure TdzButtonedEdit.KeyDown(var _Key: Word; _Shift: TShiftState);
begin
inherited;
if (_Key = VK_RETURN) and (ssCtrl in _Shift) then
if Assigned(OnRightButtonClick) then
OnRightButtonClick(Self);
end;
procedure TdzButtonedEdit.Loaded;
begin
inherited;
if RightButton.Visible and (RightButton.Hint = '') then begin
RightButton.Hint := _('Ctrl+Return to ''click'' right button.');
ShowHint := true;
end;
end;
which I use in the form by declaring:
TButtonedEdit = class(TdzButtonedEdit)
end;
before the form's class declaration.
If I can ever be bothered I'll make it a full blown custom component.
btw: Why did Embarcadero make TEditButton.TGlyph strict private? That's very inconvenient because
normally I would have called RightButton.Glyph.Click rather than OnRightButtonClick.
Given that there is no way to pass the input focus to these embedded buttons, and given that they display glyphs, how could there be keyboard access? How would the user discover it?
On a modal dialog you can press enter and so long as the focus control is not a button, then the default button is pressed and the form closes. That is part of the platform UI standard. Similarly for escape and cancel. Many other controls have standard keyboard access (lists, drop downs, edits etc.)
This is not a standard control and so it would be wrong to impose some default keyboard access beyond what is expected in an edit control. It's fine for the designer to add access because they know what is reasonable on their form, but the VCL designers got it right by not including a default behaviour that would apply to every instance of this control..

Delphi: Convert keys to letters, ignoring shiftsate

I'm trying to make a search feature where the user can hold in the control key and type some text to search.
I'm using the OnKeyDown/OnKeyUp to trap the control key.
Is there a easy way to check if the key parameter given to the onKeyUp/Down event is a litteral?
Or is it possible to convert the char given to OnKeyPressed while holding down the control key to the char it would have been if the control key where not pressed?
Edit:
I need a solution that can handle letters beyond the simple a..z range, like æ, ø å.
It looks like Delphi 2009 got a couple of useful methods in the TCharachter-class; e.g. function IsLetterOrDigit(C: Char): Boolean;
I'm stuck with delphi 2007, though...
The OnKeyDown & OnKeyPress Events have a number of Limitations.
Rather use a TApplicationEvents Component.
In it's OnShortCut Event hander use the following code
procedure TFormA.ApplicationEvents1ShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
if (Msg.CharCode = Ord('B')) and (HiWord(Msg.KeyData) and KF_ALTDOWN <> 0) then
begin
Handled := True;
// Do what needs to be done;
end;
end;
This will trap ALT-B
have a look at Delphi - Using the TApplicationEvents OnShortCut event to detect Alt+C key presses for more info
You can convert the Key(Char) parameter from OnKeyPress event to it's ordinal value using Ord(Key) however, in the OnKeyDown event you can do the following
if CharInSet(Char(Key), ['A'..'Z']) then
do something
you might also want to play with ShiftState to check if ALT, CTRL and/or SHIFT key is down...
Here you have all the info:
Virtual Key Codes
Understanding and Processing Keyboard events in Delphi

Resources