Limit maximum text length of the inplace editor in TDBGrid - delphi

How can I limit the maximum text length of the inplace editor in TDBGrid? (Delphi Berlin)
The Data Type is Float.

The inplace editor in a TDBGrid will update its content by calling
procedure TInplaceEdit.UpdateContents;
begin
Text := '';
EditMask := Grid.GetEditMask(Grid.Col, Grid.Row);
Text := Grid.GetEditText(Grid.Col, Grid.Row);
MaxLength := Grid.GetEditLimit;
end;
Where GetEditMask is implemented the following way:
function TCustomDBGrid.GetEditMask(ACol, ARow: Longint): string;
begin
Result := '';
if FDatalink.Active then
with Columns[RawToDataColumn(ACol)] do
if Assigned(Field) then
Result := Field.EditMask;
end;
and GetEditLimit like this:
function TCustomDBGrid.GetEditLimit: Integer;
begin
Result := 0;
if Assigned(SelectedField) and (SelectedField.DataType in [ftString, ftWideString]) then
Result := SelectedField.Size;
end;
There you have multiple ways to get to the desired behavior I think.
Use TField EditMask property for the Field you want to restrict. This will be returned by Grid.GetEditMask call. No need to inherit from TDBGrid and override anything. Behavior can be controlled on a by-field-basis.
Create your own TDBGrid descendant where you override GetEditLimit
to return a MaxLength for the inplace editor depending on SelectedField
Code for approach 1 could look like this:
// Opening of dataset
...
DataSet.FieldByName('FloatField').EditMask := '00.00';
This will mask will require two digits before and after the decimal seperator. See TEditMask for more on masks.
For approach 2:
uses
Data.DB,
Vcl.DBGrids;
type
TMyDBGrid = class(TDBGrid)
protected
function GetEditLimit: Integer; override;
end;
implementation
{ TMyDBGrid }
function TMyDBGrid.GetEditLimit: Integer;
begin
Result := inherited GetEditLimit;
if (Result = 0) and Assigned(SelectedField) and (SelectedField.DataType = ftFloat) then
Result := 5; // Whatever you decide
end;
Like kobik suggests, you can then use this class as interposer class. To do this, add TDBGrid = class(TMyDBGrid); in the unit you want to use that grid. If you declared TMyDBGrid in the same unit you want to use it, make the type reference clear TMyDBGrid = class(Vcl.DBGrids.TDBGrid).

Related

Get TextSettings.Font.Style property with GetObjectProp using Delphi Tokyo 10.2

I'm using Delphi's GetObjectProp function to get the properties of the form components, I get all the properties of several components, but I can not get the TextSettings.Font.Style (Bold, Italic, ...) property of components like TLabel for example. I need to know if the component text is bold or italic. The procedure I am working on trying to get these properties follows below:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
TextSettings: TTextSettings;
Fonte: TFont;
Estilo: TFontStyle;
Componente_cc: TControl;
begin
Componente_cc := TControl(Label1);
if IsPublishedProp(Componente_cc, 'TextSettings') then
begin
TextSettings := GetObjectProp(Componente_cc, 'TextSettings') as TTextSettings;
if Assigned(TextSettings) then
Fonte := GetObjectProp(TextSettings, 'Font') as TFont;
if Assigned(Fonte) then
Estilo := GetObjectProp(Fonte, 'Style') as TFontStyle; // <-- error in this line
if Assigned(Estilo) then
Edit1.text := GetPropValue(Estilo, 'fsBold', true);
end
end;
The error displayed on the line where I marked above is.
[dcc64 Error] uPrincipal.pas(1350): E2015 Operator not applicable to this operand type
What am I doing wrong?
GetObjectProp(Fonte, 'Style') will not work since Style is not an object-based property to begin with, it is a Set-based property. And GetPropValue(Estilo, 'fsBold', true) is just plain wrong (not that you would get far enough to call it anyway), because fsBold is not a property, it is a member of the TFontStyle enum. To retreive the Style property value, you would have to use GetOrdProp(Fonte, 'Style'), GetSetProp(Fonte, 'Style'), or GetPropValue(Fonte, 'Style') instead (as an integer, string, or variant, respectively).
That being said, once you have retrieved the TextSettings object, you don't need to use RTTI at all to access its Font.Style property, just access the property directly.
Try this instead:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
Componente_cc: TControl;
TextSettings: TTextSettings;
begin
Componente_cc := ...;
if IsPublishedProp(Componente_cc, 'TextSettings') then
begin
TextSettings := GetObjectProp(Componente_cc, 'TextSettings') as TTextSettings;
Edit1.Text := BoolToStr(TFontStyle.fsBold in TextSettings.Font.Style, true);
end;
end;
A better (and preferred) solution is to not use RTTI at all. FMX classes that have a TextSettings property also implement the ITextSettings interface for exactly this situation, eg:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
Componente_cc: TControl;
Settings: ITextSettings;
begin
Componente_cc := ...;
if Supports(Componente_cc, ITextSettings, Settings) then
begin
Edit1.Text := BoolToStr(TFontStyle.fsBold in Settings.TextSettings.Font.Style, true);
end;
end;
Read Embarcadero's documentation for more details:
Setting Text Parameters in FireMonkey

Delphi Canvas Textout with RightToLeft BidiMode

I want to print Right-to-left Unicode strings on a Canvas. I can't find a BidiMode property or something like that to get it done.
currently the symbols which are located at the end of strings, appear before the first character of the text which is printed on the Canvas.
FMX
FireMonkey does not have any BiDi capabilities at this time.
VCL
The Vcl.TControl class has public DrawTextBiDiModeFlags() and DrawTextBiDiModeFlagsReadingOnly() methods, which help the control decide the appropriate BiDi flags to specify when calling the Win32 API DrawText() function.
In Vcl.Graphics.TCanvas, its TextOut() and TextRect() methods do not use the Win32 API DrawText() function, they use the Win32 API ExtTextOut() function instead, where the value of the TCanvas.TextFlags property is passed to the fuOptions parameter of ExtTextOut(). The TextFlags property also influences the value of the TCanvas.CanvasOrientation property, which TextOut() and TextRect() use internally to adjust the X coordinate of the drawing.
For right-to-left drawing with TCanvas, include the ETO_RTLREADING flag in the TextFlags property.
Had no success to display RTL text with "TextOut" when form bidimode is set to "bdLeftToRight", so I usually used
XXX.Canvas.TextRect(Rect,Text,[tfRtlReading,tfRight]);
Worked very well for me..
I needed to detect Hebrew, so I did it like this:
function CheckHebrew(s: string): boolean;
var
i: Integer;
begin
Result := false;
for i := 1 to Length(s) do
if (ord(s[i])>=1424) and (ord(s[i])<1535) then
begin
Result := true;
exit;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
tf : TTextFormat;
r : TRect;
s : string;
begin
r.Left := 0;
r.Top := 0;
r.Width := Image1.Width;
r.Height := Image1.Height;
s := Edit1.Text;
if CheckHebrew(s) then
tf := [tfRtlReading,tfRight,tfWordBreak]
else
tf := [tfWordBreak];
Image1.Canvas.FillRect(r);
Image1.Canvas.TextRect(r,s,tf)
end;

Storing the value of variable using TRadioGroup

I want to change the value of T according to a particular selection but it's not changing. Please have a look. The variable T has been declared along with Form1:TForm1 before 'implementation'. Basically, T should get assigned a linear or non linear equation depending upon the the selection of the respected radio buttons. I put a TEdit in the form so as to get an idea whether it is working or not. The last part is just a way to check by taking an example of Integer values.
Also, if I am not able to give a clear idea then just suggest me how to store a value of the concerned value using the Radiobuttons of the RadioGroup.
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
if RadioGroup1.Items[RadioGroup1.ItemIndex] = 'Linear Tension' then
T:= 5;
if RadioGroup1.Items[RadioGroup1.ItemIndex] = 'Non-Linear tension' then
T:= 10;
end;
procedure TForm1.Edit1Change(Sender: TObject);
var
code: Integer;
value: Real;
begin
Val(Edit1.Text,value,code);
Edit1.Text := formatfloat('#.0', T);
end;
end.
It's really not a good idea to use a textual comparison for RadioGroup items. It's much better to simply use the ItemIndex directly:
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
case RadioGroup1.ItemIndex of
0: T := 5;
1: T := 10;
else
raise Exception.Create('No item selected - should not get here');
end;
ShowMessage(FloatToStr(T));
end;
Do not compare the captions because you will have magic values in your code.
Declare a ValueObject containing the Value and the Name
type
TTensionValue = record
private
FValue : Integer;
FName : string;
public
constructor Create( AValue : Integer; const AName : string );
class function EMPTY : TTensionValue;
property Value : Integer read FValue;
property Name : string;
end;
TTensionValues = TList<TTensionValue>;
class function TTensionValue.EMPTY : TTensionValue;
begin
Result.FValue := 0;
Result.FName := '';
end;
constructor TTensionValue.Create( AValue : Integer; const AName : string );
begin
// Validation of AValue and AName
if AName = '' then
raise Exception.Create( 'AName' );
if AValue < 0 then
raise Exception.Create( 'AValue' );
FValue := AValue;
FName := AName;
end;
Prepare a List with valid entries
type
TForm1 = class( TForm )
...
procedure RadioGroup1Click( Sender: TObject );
private
FTensions : TTensionValues;
procedure PopulateTensions( AStrings : TStrings );
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
end;
procedure TForm1.AfterConstruction;
begin
inherited;
FTensions := TTensionValues.Create;
FTensions.Add( TTensionValue.Create( 5, 'Linear Tension' ) );
FTensions.Add( TTensionValue.Create( 10, 'Non-Linear tension' ) );
end;
procedure TForm1.BeforeDestruction;
begin
FTenstions.Free;
inherited;
end;
Populate that list to the RadioGroup
procedure TForm1.PopulateTensions( AStrings : TStrings );
var
LValue : TTensionValue;
begin
AStrings.BeginUpdate;
try
AStrings.Clear;
for LValue in FTensions.Count - 1 do
AStrings.Add( LValue.Name );
finally
AStrings.EndUpdate;
end;
end;
procedure TForm1.FormShow( Sender.TObject );
begin
PopulateTensions( RadioGroup1.Items );
end;
Now you only ask the TensionList for the value
procedure TForm1.RadioGroup1Click( Sender: TObject );
begin
T := FTensions[RadioGroup1.ItemIndex].Value;
end;
The selected value now only rely on the chosen ItemIndex and not on the caption text.
From what I can tell, you're simply trying to change the value displayed on Edit1 when RadioGroup1 is clicked. To achieve this, all you'll need to do is move
Edit1.Text := formatfloat('#.0', T);
to the end of your RadioGroup1Click procedure.
I'm assuming Edit1Change is the onChange procedure of Edit1. If so, according to the documentation this procedure only gets called when the Text property already might have changed. So not only will this procedure not get called (how would delphi know you intend to use the value of T to change the text of Edit1?), when it does get called, it might result in a stack overflow, since changing the text value indirectly calls the onChange event. (though setting it to the same value it already had might not call it).
That being said, checking if a value is being changed properly, does not require a TEdit, a TLabel would be a better fit there. Though in your case, i would opt for simply placing a breakpoint and stepping through the code to see if the value get's changed correctly.
There are also some a lot of additional problems with your code, such as inconsistent formatting, magic values, bad naming conventions and lines of code that serve no purpose, I would suggest you read up on those before you get into bad habits.

How can I turn a list of properties into a stringlist?

I have a component with a lot of properties, many of which are types.
For example:
BackgroundStyle = [bsSolid, bsGradient, bsNone]
BorderStyle = [bsNone, bsSingle, bsWide]
I am building a form to allow the user to configure these properties at runtime and I would like to populate some dropdown lists dynamically, rather than having to type all of them in by hand.
Is this possible? Thanks!
Use RTTI for that. Specifically, look at the GetPropInfo() and GetEnumName() functions in the TypInfo unit.
Remy is on the ball with this one. Lately I just happen to do something similar and a bit of refactoring (within a text editor, so you mileage may vary with the complier):
class function TEnumerationRoutines.TitleCaseDescriptionFromOptions<T>: TStrings;
var
LRttiContext : TRttiContext;
LRttiEnumerationType: TRttiEnumerationType;
LTypeInfo : Pointer;
LPTypeInfo : PTypeInfo;
lp: Integer;
begin
LTypeInfo := TypeInfo(T);
LPTypeInfo := PTypeInfo(LTypeInfo);
if LPTypeInfo^.Kind <> tkEnumeration then
raise Exception.Create('Type is not an enum');
Result := TStringList.Create;
LRttiEnumerationType := LRttiContext.GetType(LTypeInfo) as TRttiEnumerationType;
for lp := LRttiEnumerationType.MinValue to LRttiEnumerationType.MaxValue do
Result.Add(GetEnumName(LTypeInfo, Ord(lp)));
end;
and call it with:
MyStrings := TEnumerationRoutines.TitleCaseDescriptionFromOptions<BackgroundStyle>;
or
MyStrings := TEnumerationRoutines.TitleCaseDescriptionFromOptions<BorderStyle>;

How to use "Sender" parameter with "As" operator for more then one class at a time?

In Delphi, sometimes we need to do this...
function TForm1.EDIT_Click(Sender: TObject);
begin
(Sender As TEdit).Text := '';
end;
...but sometimes we need to repeat the function with other object class like...
function TForm1.COMBOBOX_Click(Sender: TObject);
begin
(Sender As TComboBox).Text := '';
end;
...because the operator As does not accept flexibility. It must know the class in order to allow the .Text that come after the ().
Sometimes the code gets full of similar functions and procedures because we need to do the same thing with similar visual controls that we can't specify.
This is only an case of use example. Generally I use these codes on more complex codes to achieve a standard objective on many controls and other kind of objects.
Is there an alternative or trick to make these tasks more flexible?
Use RTTI to perform common tasks on similarly-named properties of unrelated classes, eg:
Uses
..., TypInfo;
// Assigned to both TEdit and TComboBox
function TForm1.ControlClick(Sender: TObject);
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(Sender, 'Text', []);
if Assigned(PropInfo) then
SetStrProp(Sender, PropInfo, '');
end;
In some cases, some controls use Text and some use Caption instead, eg;
function TForm1.ControlClick(Sender: TObject);
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(Sender, 'Text', []);
if not Assigned(PropInfo) then
PropInfo := GetPropInfo(Sender, 'Caption', []);
if Assigned(PropInfo) then
SetStrProp(Sender, PropInfo, '');
end;
you can use the is operator, try this sample
if Sender is TEdit then
TEdit(Sender).Text:=''
else
if Sender is TComboBox then
TComboBox(Sender).Text:='';
You can eliminate the messy type-casting by using the absolute keyword which allows you to declare variables of different types occupying the same memory location, in this case the same location as the event parameter.
You still need to perform the type checking using "is" but in other respects this approach is a bit cleaner but just as safe.
procedure TMyForm.ControlClick(Sender: TObject);
var
edit: TEdit absolute Sender;
combo: TComboBox absolute Sender;
:
begin
if Sender is TEdit then
edit.Text := ''
else if Sender is TComboBox then
combobox.Text := ''
else
:
end;
I wrote in more detail about using this language feature in my blog almost 3 years ago.
I'm posting my comment as an answer because I don't see any answer here that mentions this. SetTextBuf is a public method of TControl. This method is utilized to populate the internal text data member via the SetText windows message. This is how the a TControl descendant updates both the Caption and Text properties. So all TControl descendants, such as TButton, TEdit, TComboBox will work using the following type of code. And you don't have to use RTTI.
function TForm1.EDIT_Click(Sender: TObject);
begin
(Sender as TControl).SetTextBuf('Text or Caption'); // will work for both the Caption and text property
end;
I don't know if you are using the tag property for anything but it can be useful for these situations. Setting the tag of all Tedits to say 1 and the tag of all Tcomboboxes to 2 etc could let you do:
if Sender is TControl then
Case TControl(Sender).tag of
1: TEdit(sender).text := '';
2: Tcombobox(sender).text := '';
3....etc
end;
Just a thought and it looks neater and easier to read/debug:)
Thanks to you people, specially #RemyLebeau, I could make this universal function that applies do any kind of Win Control or Data Base Control. It turns the control in Red (or whatever color you want) if it's Required but empty, if it has repeated information on the Data Base, or whatever other condition we want to check. It return numbers instead of true or false, so we can send only one message at the end of many checks and tell the user how many error did he/she made.
function CheckInput(Control: TWinControl; Condition: Boolean; EmptyState: Integer; Msg: String): Integer;
var
PropInfo: PPropInfo;
begin
{ os controles que precisam passar por condições para que seu conteúdo seja aceito }
Result := 0;
if EmptyState = ciNotEmpty then
begin
PropInfo := GetPropInfo(Control, 'Text', []);
if Assigned(PropInfo) then
begin
if GetStrProp(Control, PropInfo) = '' then
begin
Condition := False;
Msg := ciEmptyMsg;
end;
end;
end;
if not Condition then
begin
Result := 1;
PropInfo := GetPropInfo(Control, 'Color', []);
if Assigned(PropInfo) then SetPropValue(Control, PropInfo, ciErrorColor);
if Msg <> '' then ShowMessage(Msg);
end
else
begin
PropInfo := GetPropInfo(Control, 'Color', []);
if Assigned(PropInfo) then SetPropValue(Control, PropInfo, ciNormalColor);
end;
end;
If you go all the way down, you'll notice that both TEdit and TCombobox descend from TControl. If you look which method they use to set their text then you'll see it's the method implemented by TControl. That's why you can do something ugly like:
if (sender is TEdit) or (sender is TComboBox) then
TEdit(sender).Text:='test';
You have to make sure that all objects you put in here use the same method internally or your application will break in mysterious ways.

Resources