Recent

Author Topic: Issues with subclass creation  (Read 36485 times)

pusuni

  • Jr. Member
  • **
  • Posts: 72
Issues with subclass creation
« on: January 03, 2014, 09:29:45 pm »
Hi!! this is my first post in the forum and sorry, if this subforum is not the right place for this message.

I'm trying to make a new  control (class) derivated from TEdit class. A easy and simplified code  version of this class (the code is for example only)  is:


Code: [Select]
unit NumberEdit;

{$mode objfpc}{$H+}

interface

uses
   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
   StdCtrls, ExtCtrls;

type
   tNumero = ( real, entero );

   TNumberEdit = class(TEdit)
   private
      { Private declarations }
      Fmode                                       : tNumero;
      TextoPrevio                                 : string;
      TeclaTMP                                    : char;
      FFontFcsName, FuenteTMP                     : TFont;
      FErrSnd, FErrClr,FFontFcs                   : BOOLEAN;
      PrimeraVezColor, PrimeraVezFuente           : BOOLEAN;
      FColorErr, ColorTMP                         : TColor;
      FValorEntero                                : Int64;
      FValorReal                                  : extended;
      Timer1                                      : TTimer;
      FErrClrTime                                 : cardinal;

   protected
      { Protected declarations }



   public
      { Public declarations }


   published
      { Published declarations }
      property ColorErr      : TColor    read FColorErr    write FColorErr;
      property Mode          : tNumero   read FMode        write FMode        default real;
      property ErrClr        : BOOLEAN   read FErrClr      write FErrClr;
      property ErrClrTime    : cardinal  read FErrClrTime  write FErrClrTime;
      property ErrSnd        : BOOLEAN   read FErrSnd      write FErrSnd;
      property FontFcs       : BOOLEAN   read FFontFcs     write FFontFcs;
      property FontFcsFont   : TFont     read FFontFcsName write FFontFcsName;
      property ValorEntero   : Int64     read FValorEntero write FValorEntero;
      property ValorReal     : extended  read FValorReal   write FValorReal;
  end;


procedure Register;

implementation

procedure Register;
begin
   {$I numberedit_icon.lrs}
   RegisterComponents('Standard',[TNumberEdit]);
end;




end.       
That code compile OK and I can create a new package with this new class: TNumberEdit. Now I can create a new form with this new control, but, I get a problem.


The FontFcsFont is the TFont type. When in the Object inspector I select this one property  for setting the initials values,  I get the message:

"Cannot assign as Nil to a Tfont"

I think, I should get a font dialogue for setting those values.  I was searching information about that problem in different places:  they comment that I should create a Property Editors for TFont, But is it necessary?


The ColorErr is  another type created by me (TColor type).  If I select it in object inspector, then I get the correct dialogue for setting those properties without any extra code

Why yes here, and there, no? What's wrong in  my code?

Thank very much in advanced

typo

  • Hero Member
  • *****
  • Posts: 3051
Re: Issues with subclass creation
« Reply #1 on: January 03, 2014, 09:44:50 pm »
Try to initialize the property on the constructor as the same font as the control itself.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Issues with subclass creation
« Reply #2 on: January 03, 2014, 09:51:44 pm »
TFont is a class, while TColor is a simple range. You need to create the font when you create your edit, and destroy it when you destroy your edit. Don't forget to provide some FontChange procedure. I learned about that by checking TControl source code:

Code: Text  [Select][+][-]
  1. constructor TControl.Create(TheOwner: TComponent);
  2. ...
  3.     FFont := TFont.Create;
  4.     FFont.OnChange := @FontChanged;
  5. ...
  6.  

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Issues with subclass creation
« Reply #3 on: January 03, 2014, 10:03:54 pm »
TEdit descendants already have a published Font property. What is the purpose in adding another one?

pusuni

  • Jr. Member
  • **
  • Posts: 72
Re: Issues with subclass creation
« Reply #4 on: January 05, 2014, 12:14:19 pm »
Hi!

TEdit descendants already have a published Font property. What is the purpose in adding another one?

Yes, I know. The new version of "my" Tedit, it  changes/toggles the font (name, properties, color...) when TEdit has the focus:
if you are inside , one Font. If you are outside, another different font.

The initial version (here is the code):
Code: [Select]
unit NumberEdit;

{$mode objfpc}{$H+}

interface

uses
   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
   StdCtrls, ExtCtrls;


type

   TNumberEdit = class(TEdit)
   private
      { Private declarations }

      FFontFcsName, FuenteTMP                     : string;    //
      FFontFcs                                    : BOOLEAN;
      PrimeraVezFuente                            : BOOLEAN;


   protected
      { Protected declarations }
      procedure DoEnter;                          Override;
      procedure DoExit ;                          Override;
      procedure MouseEnter;                       Override;
      procedure MouseLeave;                       Override;


   public
      { Public declarations }
      Constructor Create(AOwner : TComponent) ;   Override;


   published
      { Published declarations }

      property FontFcs       : BOOLEAN   read FFontFcs     write FFontFcs;
      property FontFcsFont   : string     read FFontFcsName write FFontFcsName;  //

  end;


procedure Register;

implementation

procedure Register;
begin
   {$I numberedit_icon.lrs}
   RegisterComponents('Standard',[TNumberEdit]);
end;

Constructor TNumberEdit.Create(AOwner : TComponent);
begin
   inherited Create (AOwner);
   PrimeraVezFuente   := TRUE;
end;


procedure TNumberEdit.DoEnter;
begin    (*Al entrar en la zona de edicion, cambia la letra I - ON*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font.Name; //
               PrimeraVezFuente := FALSE;
            end;
         Font.Name := FFontFcsName//;
      end;
end;

procedure TNumberEdit.DoExit;
begin    (*Al entrar en la zona de edicion, cambia la letra  I - OFF*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font.Name;  //
               PrimeraVezFuente := FALSE;
            end;
         Font.Name := FuenteTMP; //
      end;
end;

procedure TNumberEdit.MouseEnter;
begin  (*Al entrar en la zona de edicion, cambia la letra II - ON*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font.Name;     //
               PrimeraVezFuente := FALSE;
            end;
         Font.Name := FFontFcsName;
      end;
end;

procedure TNumberEdit.MouseLeave;
begin  (*Al entrar en la zona de edicion, cambia la letra  II - OFF*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font.Name; //
               PrimeraVezFuente := FALSE;
            end;
         Font.Name := FuenteTMP; //
      end;
end;


end.                     


ONLY change the font name in the new  TNUMBEREDIT class. You can compile the code -  making a new package  -  and set the values:

FontFcs = TRUE  (TRUE = enable the effect)
FontFcsFont = Terminal (for example)

It works properly (you can see in the two pictures): http://www.casimages.es/i/140105120121552113.png.html  http://www.casimages.es/i/140105120229807945.png.html


As I said earlier, I thought about making a more sophisticated control : I created new property, FontFcsFont (FFontFcsName ) of TFONT type for toggling  between two different fonts: FONT <-> FONTFCSFONT


I've been rewriting the source code and different  testing -  sorry for the delay  - following   the advices of typo and engkin (TFont is a class, while TColor is a simple range.): thanks a lot!!, both )



Quote

unit NumberEdit;

{$mode objfpc}{$H+}

interface

uses
   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
   StdCtrls, ExtCtrls;


type

   TNumberEdit = class(TEdit)
   private
      { Private declarations }

      FFontFcsName, FuenteTMP                     : TFont;    //
      FFontFcs                                    : BOOLEAN;
      PrimeraVezFuente                            : BOOLEAN;


   protected
      { Protected declarations }
      procedure DoEnter;                          Override;
      procedure DoExit ;                          Override;
      procedure MouseEnter;                       Override;
      procedure MouseLeave;                       Override;
      procedure FontChanged(Sender: TObject)      Override; //


   public
      { Public declarations }
      Constructor Create(AOwner : TComponent) ;   Override;


   published
      { Published declarations }

      property FontFcs       : BOOLEAN   read FFontFcs     write FFontFcs;
      property FontFcsFont   : TFont     read FFontFcsName write FFontFcsName;  //

  end;


procedure Register;

implementation

procedure Register;
begin
   {$I numberedit_icon.lrs}
   RegisterComponents('Standard',[TNumberEdit]);
end;

Constructor TNumberEdit.Create(AOwner : TComponent);
begin
   inherited Create (AOwner);
   PrimeraVezFuente   := TRUE;
   FFontFcsName       := TFont.Create;    //
   FFontFcsName.OnChange := @FontChanged;   //
   FuenteTMP := TFont.Create;                 //

end;



procedure TNumberEdit.FontChanged(Sender: TObject);
begin

  end;


procedure TNumberEdit.DoEnter;
begin    (*Al entrar en la zona de edicion, cambia la letra I - ON*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font; //
               PrimeraVezFuente := FALSE;
            end;
         Font := FFontFcsName//;
      end;
end;

procedure TNumberEdit.DoExit;
begin    (*Al entrar en la zona de edicion, cambia la letra  I - OFF*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font;  //
               PrimeraVezFuente := FALSE;
            end;
         Font := FuenteTMP; //
      end;
end;

procedure TNumberEdit.MouseEnter;
begin  (*Al entrar en la zona de edicion, cambia la letra II - ON*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font;     //
               PrimeraVezFuente := FALSE;
            end;
         Font := FFontFcsName;   //
      end;
end;

procedure TNumberEdit.MouseLeave;
begin  (*Al entrar en la zona de edicion, cambia la letra  II - OFF*)
   inherited;
   if FFontFcs then
      begin
         if PrimeraVezFuente then
            begin
               FuenteTMP := Font; //
               PrimeraVezFuente := FALSE;
            end;
         Font := FuenteTMP; //
      end;
end;



end.                           

( NOTE: //and bold letters  -> modified lines)

Now  I get the TFont control panel when  I select  this new property  for setting the initials values in the Object inspector, but I I can not  set the FontFcsFont (FFontFcsName )  FONT value  and  I sometimes get Access Violation message

I guess the code is very buggy ( I don't know  what I should put into procedure TNumberEdit.FontChanged(Sender: TObject); ) and the Destroy??

Any ideas?


Thanks in advanced

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Issues with subclass creation
« Reply #5 on: January 05, 2014, 06:29:09 pm »
Quote
I don't know  what I should put into procedure TNumberEdit.FontChanged(Sender: TObject); ) and the Destroy??
Checking the source code in $(LazarusDir)\lcl\include\control.inc to see what TControl does:
Code: Text  [Select][+][-]
  1. destructor TControl.Destroy;  
  2. ...
  3.   FreeThenNil(FFont);
  4. ...
  5.   inherited Destroy;
  6. ...
  7. end;      
  8.  

As for TFont.OnChange and FontChanged, you should not override the original FontChanged if you do not need to. But you can borrow it for your font as well. Maybe you can keep this:
Code: [Select]
  FFontFcsName.OnChange := @FontChanged;

And remove these:
Code: [Select]
...
      procedure FontChanged(Sender: TObject)      Override; //
...
procedure TNumberEdit.FontChanged(Sender: TObject);
begin

  end;

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Issues with subclass creation
« Reply #6 on: January 05, 2014, 09:42:42 pm »
The new version of "my" Tedit, it  changes/toggles the font (name, properties, color...) when TEdit has the focus:
if you are inside , one Font. If you are outside, another different font.

Ah, we have a better idea of what you want to achieve.
You don't need two properties: FontFcs (boolean) and FontFcsFont (TFont). This introduces unneeded redundancy. Setting FontFcsFont to the values Font has effectively turns FontFcs off, and giving it different properties from Font effectively turns it on.
Also, you don't want to override FontChanged, (or use OnFontChange) as Engkin pointed out, since you want the built-in behaviour for changes in Font, not anything new. You need to call FontChanged (or send a font change message), not change what it does for you.
I realise you want your component to do other things than auto-change the font when focused, but this cut-down version concentrates just on the font-change behaviour you are after, and shows the minimum methods you need to implement.

Code: [Select]
unit EditFontChange;

{$mode objfpc}{$H+}

interface

uses
  Classes, StdCtrls, Graphics, LMessages;

const
  DefaultFocusFontSize=16;

type

  { TEditAutoFontChange }

  TEditAutoFontChange = class(TCustomEdit)
  private
    FFocusFont: TFont;
    FTempFont: TFont;
    procedure SetFocusFont(AValue: TFont);
  protected
    procedure DoEnter; override;
    procedure DoExit; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property AutoSelected;
  published
    // new property
    property FocusFont: TFont read FFocusFont write SetFocusFont;
    // existing properties
    property Action;
    property Align;
    property Alignment;
    property Anchors;
    property AutoSize;
    property AutoSelect;
    property BidiMode;
    property BorderSpacing;
    property BorderStyle;
    property CharCase;
    property Color;
    property Constraints;
    property DragCursor;
    property DragKind;
    property DragMode;
    property EchoMode;
    property Enabled;
    property Font;
    property HideSelection;
    property MaxLength;
    property ParentBidiMode;
    property OnChange;
    property OnChangeBounds;
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEditingDone;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnResize;
    property OnStartDrag;
    property OnUTF8KeyPress;
    property ParentColor;
    property ParentFont;
    property ParentShowHint;
    property PasswordChar;
    property PopupMenu;
    property ReadOnly;
    property ShowHint;
    property TabStop;
    property TabOrder;
    property Text;
    property Visible;
  end;

implementation

{ TEditAutoFontChange }

procedure TEditAutoFontChange.SetFocusFont(AValue: TFont);
begin
  if FFocusFont=AValue then Exit;
  FFocusFont.Assign(AValue);
  Perform(CM_FONTCHANGED, 0, 0);
end;

procedure TEditAutoFontChange.DoEnter;
begin
  inherited DoEnter;
  if Assigned(Font) and Assigned(FFocusFont) and Assigned(FTempFont) then
  if (FFocusFont<>Font) then begin
    FTempFont.Assign(Font);
    Font.Assign(FFocusFont);
    Perform(CM_FONTCHANGED, 0, 0);
  end;
end;

procedure TEditAutoFontChange.DoExit;
begin
  if (FFocusFont<>Font) then begin
    Font.Assign(FTempFont);
    Perform(CM_FONTCHANGED, 0, 0);
  end;
  inherited DoExit;
end;

constructor TEditAutoFontChange.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FFocusFont:=TFont.Create;
  FFocusFont.Assign(Self.Font);
  FFocusFont.Style:=[fsBold];
  FFocusFont.Size:=DefaultFocusFontSize;
  FTempFont:=TFont.Create;
end;

destructor TEditAutoFontChange.Destroy;
begin
  FFocusFont.Free;
  FTempFont.Free;
  inherited Destroy;
end;

end.

« Last Edit: January 06, 2014, 08:56:14 am by howardpc »

pusuni

  • Jr. Member
  • **
  • Posts: 72
Re: Issues with subclass creation
« Reply #7 on: January 07, 2014, 11:51:27 am »
Thanks  very much (a both)

howardpc,  Wow!! I have NOT words for your code. I'm studing (There are things  I understand, but other no!! ) 

I don't understand why  "FTempFont.Assign(Font);" is different "FTempFont := Font;" the first statement works properly, the second  no.  I imagine ".Assign" procedure should do something more, no only assignement).

Thanks again

pusuni

  • Jr. Member
  • **
  • Posts: 72
Re: Issues with subclass creation
« Reply #8 on: January 12, 2014, 04:21:27 pm »
Hi, again!

I finished the new class, but I would like who some properties set a default values. For example, the ColorErr property is equal to  clRed, and so

I think, if in this code
Code: [Select]
unit NumberEdit;

{$mode objfpc}{$H+}

interface

uses
   Classes, SysUtils, LResources, Graphics, StdCtrls, ExtCtrls, LMessages;

type
   tNumero = ( real, entero );

   TNumberEdit = class(TEdit)
   private
      { Private declarations }
      Fmode                                    : tNumero;
      TextoPrevio                              : string;
      TeclaTMP                                 : char;
      ActivadoEfecto                           : BOOLEAN;
      FFocusFont                               : TFont;
      FTempFont                                : TFont;
      FFocusFontEnable                         : BOOLEAN;
      FErrSnd                                  : BOOLEAN;
      FColorErr                                : TColor;
      ColorTMP                                 : TColor;
      FValorEntero                             : Int64;
      FValorReal                               : extended;
      Timer1                                   : TTimer;
      FColorErrorTime                          : cardinal;

   protected

   public

   published
      { Published declarations }
      property Mode            : tNumero  read FMode            write FMode            default real;
      property ColorErr        : TColor   read FColorErr        write FColorErr;
      property ColorErrorTime  : cardinal read FColorErrorTime  write FColorErrorTime;
      property ErrSnd          : BOOLEAN  read FErrSnd          write FErrSnd;
      property ValorEntero     : Int64    read FValorEntero     write FValorEntero;
      property ValorReal       : extended read FValorReal       write FValorReal;
   end;


   procedure Register;

implementation

procedure Register;
begin
   {$I numberedit_icon.lrs}
   RegisterComponents('Standard',[TNumberEdit]);
end;


end.

 made the following changes

Code: [Select]
unit NumberEdit;

{$mode objfpc}{$H+}

interface

uses
   Classes, SysUtils, LResources, Graphics, StdCtrls, ExtCtrls, LMessages;

type
   tNumero = ( real, entero );

   TNumberEdit = class(TEdit)
   private
      { Private declarations }
      Fmode                                    : tNumero;
      TextoPrevio                              : string;
      TeclaTMP                                 : char;
      ActivadoEfecto                           : BOOLEAN;
      FFocusFont                               : TFont;
      FTempFont                                : TFont;
      FFocusFontEnable                         : BOOLEAN;
      FErrSnd                                  : BOOLEAN;
      FColorErr                                : TColor;
      ColorTMP                                 : TColor;
      FValorEntero                             : Int64;
      FValorReal                               : extended;
      Timer1                                   : TTimer;
      FColorErrorTime                          : cardinal;

   protected

   public

   published
      { Published declarations }
      property Mode            : tNumero  read FMode            write FMode            default real;
      property ColorErr        : TColor   read FColorErr        write FColorErr    default clRed;
      property ColorErrorTime  : cardinal read FColorErrorTime  write FColorErrorTime  default 500;
      property ErrSnd          : BOOLEAN  read FErrSnd          write FErrSnd          default TRUE;
      property ValorEntero     : Int64    read FValorEntero     write FValorEntero;
      property ValorReal       : extended read FValorReal       write FValorReal;
   end;


   procedure Register;

implementation

procedure Register;
begin
   {$I numberedit_icon.lrs}
   RegisterComponents('Standard',[TNumberEdit]);
end;


end.

it should works properly, but no. I've compiled  and in the object inspector, the properties take arbitrary values  (usually 0)

what's wrong  in my code?


Thanks in advanced

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Issues with subclass creation
« Reply #9 on: January 12, 2014, 04:46:05 pm »
the default property specifier is for internal use. It instructs the streaming mechanism to avoid saving the property if the value it has it is the default. To set default values to your properties then override the create constructor and set the fields value there eg
Code: [Select]
constructor TNumberEdit.Create(aOWner:TComponent);
begin
  inherited create(aOwner);
  FColorErr := clRed;
  ...... // the rest of the initialization code.
 
end;
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

pusuni

  • Jr. Member
  • **
  • Posts: 72
Re: Issues with subclass creation
« Reply #10 on: January 12, 2014, 08:46:06 pm »
Thanks a lot, taazz

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Issues with subclass creation
« Reply #11 on: January 12, 2014, 08:58:33 pm »
I don't understand why  "FTempFont.Assign(Font);" is different "FTempFont := Font;" the first statement works properly, the second  no.  I imagine ".Assign" procedure should do something more, no only assignement).

FTempFont and Font are reference variables (pointers, though the syntax hides that fact).
Code: [Select]
FTempFont := Font;
merely points the FTempFont variable to reference what Font is pointing to. The two variables now reference the same (single) object in memory.

What you need is two different instances, whose property values differ. So you need to create a new valid instance (FTempFont) of the same type as Font but distinct from it, and then copy all Font's property and field values into FTempFont. This is achieved by the Assign() method of FTempFont, which takes as its only parameter a reference to the object whose property values should be assigned (copied over) to FTempFont.

Writing
Code: [Select]
FTempFont := Font;
is not wrong in this context only because it does not produce the desired effect, it is wrong because it causes a memory leak. Whatever font instance FTempFont previously referenced is now orphaned, and inaccessible, because you have just lost the only reference you had to it by pointing FTempFont to reference a different instance. When your program closes that originally allocated memory will not be deallocated, since nothing in your program now knows about it, or knows where to find it.
Although such memory is usually called 'lost' or 'leaked', a more telling analogy might be to call it constipated memory, because it represents garbage which has not been removed, though it should have been.
« Last Edit: January 12, 2014, 09:18:49 pm by howardpc »

pusuni

  • Jr. Member
  • **
  • Posts: 72
Re: Issues with subclass creation
« Reply #12 on: January 13, 2014, 01:32:11 pm »
Hi howardpc. Thank you for your  explanation.  I know about pointers, but I  didn't know  that TFONT is a pointer.

Thanks, again

typo

  • Hero Member
  • *****
  • Posts: 3051
Re: Issues with subclass creation
« Reply #13 on: January 13, 2014, 04:58:12 pm »
TFont is not a pointer. What howardpc says is that it "may be" a pointer if you don't use Assign method.

pusuni

  • Jr. Member
  • **
  • Posts: 72
Re: Issues with subclass creation
« Reply #14 on: January 16, 2014, 12:33:26 pm »
Thanks for the clarification


I've finished the new control (new Class) TNumberEdit: Thank everyone for your help!!


Now I'm trying to do  a new class with TNumberEdit and Tlabel/TStaticText classes (I would like  to add a label who looks in the left side of TNumberEdit control).


 

TinyPortal © 2005-2018