Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Accessing OnChange in Dynamic Component 2

Status
Not open for further replies.

ColinTod

Programmer
Oct 16, 2006
19
NZ
Hi,

I have been spending the last few weeks working on a bit of software and have now run into a stumbling block that I can't seem to get past. I was wandering if you might be able to help.


I have a form that has a PageControl on it.
I dynamicly create tabsheets using this:
procedure TForm1.NewCharClick(Sender: TObject);
var ts: TTabSheet;
i : integer;
begin
ts := TTabSheet.Create(Self);
With ts Do
Begin
Caption := 'NPC';
PageControl := PageControl1;
With TForm2.Create( Self ) Do
Begin
Parent := ts;
Visible := True;
Align := alClient;
Windows.SetParent( handle, ts.handle );
End;
End;
Pagecontrol1.ActivePage := ts;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
PageControl1.OwnerDraw:=true;
end;

As you can see Form2 is attached to the tabsheet.
On Form 2 I dynamically create a TComboBox and a


TCBox = class(TCombobox)
private
procedure CBoxOnChange(Sender:TObject);
public
constructor create(AOwner:TComponent; iTop, iLeft, iWidth, iHeight:integer);
destructor Destroy;
end;


var
Form2: TForm2;
New_CB_1 : TCBox;


constructor TCBox.create(AOwner:TComponent; iTop, iLeft, iWidth, iHeight:integer);
begin
inherited create(AOwner);
parent:=TWinControl(AOwner);
with Self do
begin
top:=iTop;
left:=iLeft;
width:=iWidth;
height:=iHeight;
visible:=true;
enabled:=true;
OnChange:=CBoxOnChange;
Items.add('Line1');
Items.add('Line2');
end;
end;

destructor TCBox.destroy;
begin
end;

Procedure TForm2.CreateParams( Var Params: TCreateParams );
begin
Inherited CreateParams( Params );
Params.Style := Params.Style or WS_CHILD or WS_CLIPSIBLINGS;
end;

procedure TCBox.CBoxOnChange(Sender: TObject);
begin
Form2.lOutput.caption := screen.ActiveControl.Name;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
New_CB_1 := TCBox.create(Panel1, 30, 10, 100, 20);
end;


I tested Form2 as a stand alone program and it works fine.
But used here, when the form is dynamically created, I get errors when I try to envoke the onchange events [Access violation at address ......].


Do you have any ideas?
Any help would be appreciated.

Colin
 
Here's some code that I use to create a panel in a scroll box and the panel has a label and a text box within it. The biggest thing is that I set the .Parent property explicitly - which is different than the .Owner property (even thought they point to the same component)

Code:
var
  NewPanel: TPanel;
  NewLabel: TLabel;
  ValueLabel: TLabel;
  sl: TStringList;
  i: integer;
begin
    NewPanel := TPanel.Create(sb);
    with NewPanel do begin
        Parent := sb;
        Height := kDefaultFieldHeight;
        visible := True;
        Top := sb.Tag;
        Align := alTop;
        Name := kfldQT_Quantity+'Pan';
        Caption := '';

        with TLabel.Create(NewPanel) do begin
            Parent := NewPanel;
            Left := kFieldTitleLeft;
            Width := kFieldTitleWidth;
            Visible := True;
            Top := kFieldTitleTop;
            Caption := 'Quantity';
            Hint := Caption;
            ShowHint := True;
            Name := kfldQT_Quantity+'Lab';
        end;

        with TcxTextEdit.Create(NewPanel) do begin
            Parent := NewPanel;
            Left := kEditFieldLeft;
            Width := (NewPanel.Width - Left) - kEditFieldTitleSpacer;
            Anchors := Anchors + [akRight];
            Visible := True;
            Top := kEditFieldTop;
            Name := kfldQT_Quantity+'Field';
            Text := '';
            Properties.OnValidate := QT_Quantity_ValidateField;
        end;
        sb.Tag := sb.Tag + NewPanel.Height; 
    end;
 
But used here, when the form is dynamically created, I get errors when I try to envoke the onchange events [Access violation at address ......].

The first step would be to try and establish which line is causing the AV. It could be when trying to call OnChange, but could be something in the OnChange as well.


It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Code:
procedure TCBox.CBoxOnChange(Sender: TObject);
begin
  Form2.lOutput.caption := screen.ActiveControl.Name;
end;

As Glenn mentioned above, it could be the call to the OnChange, or IN the OnChange - in this case, what is Form2? I don't think the TCBox knows of any such object as Form2 unless you are creating Form2 from within the TCBox. In this case, you need to create an identical event procedure within Form2 which is linked to the TCBox, but don't call Form2 from within TCBox, because it won't always be there...

Code:
type
  TForm2 = class(TForm)
  procedure FormCreate(Sender: TObject);
  public
    CBox1: TCBox;
    procedure CBox1OnChange(Sender: TObject);
  end;

implementation

procedure TForm2.FormCreate(Sender: TObject);
begin
  CBox1.OnChange:= CBox1OnChange;
end;

procedure TForm2.CBox1OnChange(Sender: TObject);
begin
  Form2.lOutput.caption := screen.ActiveControl.Name;
end;


JD Solutions
 
One huge thing I would recommend to avoid this type of common mistake is to move all of your code for the TCBox into its own unit apart from TForm1. If I were making it, I prefix all my units of my components with 'JD', so it would be 'JDCBox.pas'. Then you use that unit in Form2, and whala. This will keep you from accidentally adding cross-references which should not exist, such as this one.

JD Solutions
 
Here, I split it up for you...

Unit JDCBox.pas
(TCBox)
Code:
unit JDCBox;

interface

uses
  SysUtils, Classes, Windows, Controls, StdCtrls;

type
  TCBox = class(TComboBox)
  public
    constructor Create(AOwner: TComponent; iTop, iLeft, iWidth, iHeight: Integer);
    destructor Destroy; override;
  end;

implementation

constructor TCBox.Create(AOwner: TComponent; iTop, iLeft, iWidth, iHeight: Integer);
begin
  inherited create(AOwner);
  Parent:= TWinControl(AOwner);
  Name:= 'MyCombo';  //This is a test to see the result on lOutput on Form2
  Text:= '';  //Clear, since assigning the Name put a value here
  //with Self do begin  //Not needed since we're already in the same context
    top:= iTop;
    left:= iLeft;
    width:= iWidth;
    height:= iHeight;
    //visible:= true; //Not needed, already by default
    //enabled:= true; //Not needed, already by default
    Items.Add('Test Line 1');
    Items.Add('Test Line 2');
  //end;
end;

destructor TCBox.Destroy;
begin
  //Nothing needed here since nothing is created - method can be removed completely
  //If you need to keep it, you need to override this method and inherit the destroy
  inherited Destroy;
end;

end.

Unit uCBoxTest.pas
(Form2)
Code:
unit uCBoxTest;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, JDCBox, StdCtrls;

type
  TForm2 = class(TForm)
    lOutput: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    CBox1: TCBox;
    procedure CreateParams(Var Params: TCreateParams); override;
    procedure CBox1OnChange(Sender: TObject);
  public

  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.FormCreate(Sender: TObject);
begin
  CBox1:= TCBox.Create(Self, 10, 10, 100, 24);
  CBox1.OnChange:= CBox1OnChange;
end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
  if assigned(CBox1) then CBox1.Free;
end;
    
procedure TForm2.CBox1OnChange(Sender: TObject);
begin
  Form2.lOutput.caption := screen.ActiveControl.Name;
end;

procedure TForm2.CreateParams(Var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  //Params.Style:= Params.Style or WS_CHILD or WS_CLIPSIBLINGS;
end;

end.

JD Solutions
 
Thanks guys.
This is great responce.
I will try these suggestions this evening and see how they go.
 
Maybe I should have tested that a little better before I posted it.

I forgot to add a line in the component's constructor to assign the appropriate internal procedure to the inherited OnChange event. This prevented the new OnChanged event to never trigger, because the OnChange of the TComboBox wasn't assigned... My bad.

Also, I added a better sample of the output, so you can see the last index and the new index.

Unit JDCBox.pas
(TCBox)
Code:
unit JDCBox;

interface

uses
  SysUtils, Classes, Windows, Controls, StdCtrls;

type
  TCBChange = procedure(Sender: TObject; OldIndex, NewIndex: Integer) of object;

  TCBox = class(TCustomComboBox)
  private
    fCBChange: TCBChange;
    fLastIndex: Integer;
    procedure Changed(Sender: TObject);
  public
    constructor Create(AOwner: TComponent; iTop, iLeft, iWidth, iHeight: Integer);
    destructor Destroy; override;
  published
    property OnChanged: TCBChange read fCBChange write fCBChange;

    property AutoComplete default True;
    property AutoDropDown default False;
    property AutoCloseUp default False;
    property BevelEdges;
    property BevelInner;
    property BevelKind default bkNone;
    property BevelOuter;
    property Style; {Must be published before Items}
    property Anchors;
    property BiDiMode;
    property CharCase;
    property Color;
    property Constraints;
    property Ctl3D;
    property DragCursor;
    property DragKind;
    property DragMode;
    property DropDownCount;
    property Enabled;
    property Font;
    property ImeMode;
    property ImeName;
    property ItemHeight;
    property ItemIndex default -1;
    property MaxLength;
    property ParentBiDiMode;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property Sorted;
    property TabOrder;
    property TabStop;
    property Text;
    property Visible;
    //property OnChange;  //Disable due to override and replacement above
    property OnClick;
    property OnCloseUp;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnDrawItem;
    property OnDropDown;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMeasureItem;
    property OnSelect;
    property OnStartDock;
    property OnStartDrag;
    property Items; { Must be published after OnMeasureItem }
  end;

implementation

constructor TCBox.Create(AOwner: TComponent; iTop, iLeft, iWidth, iHeight: Integer);
begin
  inherited create(AOwner);
  Parent:= TWinControl(AOwner);
  top:= iTop;
  left:= iLeft;
  width:= iWidth;
  height:= iHeight;
  fLastIndex:= ItemIndex;
  Self.OnChange:= Self.Changed;
end;

destructor TCBox.Destroy;
begin

  inherited Destroy;
end;

procedure TCBox.Changed(Sender: TObject);
begin
  if assigned(fCBChange) then
    fCBChange(Self, fLastIndex, ItemIndex);
  fLastIndex:= ItemIndex;
end;

end.


Unit uCBoxTest.pas
(Form2)
Code:
unit uCBoxTest;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, JDCBox, StdCtrls;

type
  TForm2 = class(TForm)
    lOutput: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    CBox1: TCBox;
    procedure CBox1OnChange(Sender: TObject; OldIndex, NewIndex: Integer);
  public

  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.FormCreate(Sender: TObject);
begin
  CBox1:= TCBox.Create(Self, 30, 30, 200, 26);
  CBox1.OnChanged:= CBox1OnChange;
  CBox1.Items.Add('Test Line 1');
  CBox1.Items.Add('Test Line 2');
  CBox1.Items.Add('Test Line 3');
end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
  if assigned(CBox1) then CBox1.Free;
end;
    
procedure TForm2.CBox1OnChange(Sender: TObject; OldIndex, NewIndex: Integer);
begin
  lOutput.Caption:= '('+IntToStr(OldIndex)+')  -->  ('+IntToStr(NewIndex)+')';
end;

end.


JD Solutions
 
Wow, duhh, I forgot I didn't post that new code in the first place :p well there it is anyway, enjoy :p

It's first of all inherited from a TCustomComboBox instead of a TComboBox for customization. In this manner, you can publish only certain inherited properties and events. Here, I created a new event replacing the OnChange event which includes the OldIndex and NewIndex of the control. In this case, you can analyze what was selected before the change and what the value is now, without reading from the control its self.



JD Solutions
 
Hi,
Thanks all for that.
This worked perfectly and was a great lesson.
This is a fantastic group with helpfull people.
I have put up 2-3 posts before and always received helpfull advice.
Colin
 
Glenn9999, Whosrdaddy, and DjangMan are the biggest contributors here, and I try to be also since they've helped me quite a bit here.


JD Solutions
 
Speaking of which, in one of my projects, I put in the help/about dialog a "Credits" section, where I refer a link back to this website, where I got most of the help for that project (along with stackoverflow.com).

JD Solutions
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top