6

Quick question I cannot find any information on.

On one of my components I am creating I have a value which is of Integer type.

I need to allow only values entered in the Object Inspector to be between 0-10, anything that falls outside of this range should display a message to say that the value inputted is not appropriate, and then return the focus back to the Delphi Object Inspector.

Example:

TMyComponent = class(TComponent)
private
  FRangeVal: Integer;
  procedure SetRangeVal(const Value: Integer);
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
published
  property RangeVal: Integer read FRangeVal write SetRangeVal default 0;
end;

...

procedure SetRangeVal(const Value: Integer);
var
  OldValue: Integer;
begin
  OldValue := Value;
  if (Value < 0) or (Value > 10) then
  begin
    MessageDlg('Please enter a value between 0-10', mtError, [mbOK], 0);
    // FRangeVal := OldValue; ?? revert to the last value that worked
    // return focus back to property in Inspector ??
  end 
  else
  begin
    if Value <> FRangeVal then
    begin
      FRangeVal := Value;
    end;
  end;
end;

Do I need to raise some kind of special built in exception that I am unaware of maybe? The above works with my message box popping up, but focus to the culprit property in the Object Inspector is lost and I have to re click back into it to change the value again. If the entered value is bad I just want to show the message and return the focus so I can quickly enter a new value.

PS, code above was written in the web browser hence the original question showed I did not use the setter SetRangeVal for the property RangeVal - this was just a typing mistake.

  • 3
    I'm not in a place to try it with a component, but what happens if you define FRangeVal as an enumerated type? e.g. LimitedNum = 0..10; – Glenn1234 Jan 01 '13 at 23:57
  • You don't need `OldValue`. The **old value** is stored in `FRangeVal` and the **new value** is stored/passed in `Value` and only if you are satisfied you store `Value` in `FRangeVal`. Thats the initial use case of a Setter – Sir Rufo Jan 02 '13 at 08:43
  • 1
    I see a procedure `SetRangeVal` but it is never used by the property. `property RangeVal: Integer read FRangeVal write SetRangeVal default 0;` and also, the default of `0` will not change the value to a default of 0, but only show this property as **bold** if it's anything other than `0` in the object inspector. – Jerry Dodge Jan 02 '13 at 08:48
  • A true default can only be initialized from this class' constructor, otherwise it will be some large random number. Just pointing out. And please `Raise Exception.Create('Error Message');` instead of `MessageDlg` – Jerry Dodge Jan 02 '13 at 08:54
  • 2
    @JerryDodge FRangeValue will initially always be 0 because it's the default value of `Integer` and `default 0` will cause also that this property is not stored in dfm if it's value is 0 – Sir Rufo Jan 02 '13 at 08:54
  • @SirRufo I beg to differ, I know it's not the point of this question, but integers do not magically default to `0`. – Jerry Dodge Jan 02 '13 at 08:56
  • @JerryDodge They will if they are class fields or global variables :o) – Sir Rufo Jan 02 '13 at 08:56
  • Ok, confirmed, but I do recall battling uninitialized integers as component properties in the past. May have been a Delphi 7 thing, I'm in XE2 now. – Jerry Dodge Jan 02 '13 at 08:59
  • @JerryDodge http://stackoverflow.com/questions/132725/are-delphi-variables-initialized-with-a-value-by-default – Sir Rufo Jan 02 '13 at 09:00
  • @Jerry Class memory is always initialized to zero. Was that way in D1 when Delphi classes were first introduced. – David Heffernan Jan 02 '13 at 09:19
  • @DavidHeffernan Thanks for clarification, actually thinking back I really don't remember details, could have been that I was using Records or something, but good to know they are initialized from within classes. – Jerry Dodge Jan 02 '13 at 09:21
  • I typed it in the web browser, write should of been SetRangeVal not FRangeVal. @JerryDodge in my constructor I set the field to 0 so it does become the default. –  Jan 02 '13 at 13:17

3 Answers3

11

First, if your property can only contain values between 0 and 10, don't define it as a vague integer property; define it as a subtype with a defined range of values:

type
  TMyComponentRangeValue = 0..10;

TMyComponent = class(TComponent)
private
  FRangeVal: TMyComponentRangeValue;
  procedure SetRangeVal(const Value: TMyComponentRangeValue);
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
published
  property RangeVal: TMyComponentRangeValue read FRangeVal 
    write SetRangeVal default 0;
end;

Now you can let the compiler and IDE handle checking for allowable values without doing anything yourself. (The IDE wlll handle the exception and reverting back to the previous value if an invalid value is entered.)

procedure TMyComponent.SetRangeValue(const Value: TMyComponentRangeValue);
begin
  if Value <> FRangeValue then
    FRangeValue := Value;
end;
Ken White
  • 123,280
  • 14
  • 225
  • 444
2

The question is answered by @KenWhite.
In cases which can not be handled be defining an own type, you could just RAISE an error instead of displaying a Messagebox.

bummi
  • 27,123
  • 14
  • 62
  • 101
  • 2
    Yes, you need to raise an exception, NOT display a popup dialog. If you want to do that then you need to write youe own custom property editor instead. – Remy Lebeau Jan 02 '13 at 01:21
2

This is your corrected code (using the Setter Method) and Integer type.

You can choose between a silent or an exception Setter:

{$DEFINE SILENT_SETTER}

TMyComponent = class(TComponent)
private
  FRangeVal: Integer;
  procedure SetRangeVal(const Value: Integer);
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
published
  property RangeVal: Integer read FRangeVal write SetRangeVal default 0;
end;

...

procedure SetRangeVal(const Value: Integer);
begin
  // Range Checking
  if (Value < 0) or (Value > 10) then
{$IFDEF SILENT_SETTER}
    Exit;
{$ELSEIF}
    raise Exception.Create('Value out of Range');
{$ENDIF}

  // Store if needed
  if Value <> FRangeVal then
    FRangeVal := Value;
end;
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
  • Exception cannot be the right class to raise. Presumably there is an already declared exception class for this purpose. – David Heffernan Jan 02 '13 at 09:21
  • I was using the setter because my messagebox was fired I just accidentally typed the wrong part in. –  Jan 02 '13 at 13:36