2017-05-11 14 views
2

TPanel에서 파생 된 복합 구성 요소에서 하위 구성 요소의 연결 속성을 설정하고 얻는 유일한 목적을 가진 속성을 게시하려고합니다. 폼에 복합 요소를 추가 할 때마다 액세스 위반이 발생합니다.복합 구성 요소에 하위 구성 요소의 속성을 게시하는 방법은 무엇입니까?

'MyRuntimePackage.bpl'모듈의 주소 12612D86에서 액세스가 위반됩니다. 주소 00000080. 나는 TLabel하고 PopupMenu 속성을 사용하여 간단한 예를 준비했지만 폼/프레임에 복합 구성 요소를 배치 할 때 난 여전히 같은 문제가

의 읽기.

런타임 패키지

uses 
    StdCtrls, Menus, ExtCtrls, Classes; 

type 
    TTestCompoundComponent = class(TPanel) 
    private 
    FSubCmp : TLabel; 
    function GetLabelPopupMenu() : TPopupMenu; 
    procedure SetLabelPopupMenu(AValue : TPopupMenu); 
    protected 
    procedure Notification(AComponent: TComponent; Operation: TOperation); override; 
    public 
    constructor Create(AOwner : TComponent); override; 
    destructor Destroy(); override; 
    published 
    property LabelPopupMenu : TPopupMenu read GetLabelPopupMenu write SetLabelPopupMenu; 
    end; 

... 

function TTestCompoundComponent.GetLabelPopupMenu() : TPopupMenu; 
begin 
    Result := FSubCmp.PopupMenu; 
end; 

procedure TTestCompoundComponent.SetLabelPopupMenu(AValue : TPopupMenu); 
begin 
    if(GetLabelPopupMenu() <> AValue) then 
    begin 
    if(GetLabelPopupMenu() <> nil) 
    then GetLabelPopupMenu().RemoveFreeNotification(Self); 

    FSubCmp.PopupMenu := AValue; 

    if(GetLabelPopupMenu() <> nil) 
    then GetLabelPopupMenu().FreeNotification(Self); 
    end; 
end; 

procedure TTestCompoundComponent.Notification(AComponent: TComponent; Operation: TOperation); 
begin  
    inherited; 
    if((AComponent = GetLabelPopupMenu()) AND (Operation = opRemove)) 
    then SetLabelPopupMenu(nil); 
end; 

constructor TTestCompoundComponent.Create(AOwner : TComponent); 
begin 
    inherited; 
    FSubCmp := TLabel.Create(nil); 
    FSubCmp.Parent := Self; 
end; 

destructor TTestCompoundComponent.Destroy(); 
begin 
    FSubCmp.Free; 
    inherited; 
end; 

디자인 타임 패키지 FSubCmp가 작성되기 전에 Notification() 건축 중에 opInsert 통지를 받으면

procedure Register; 
begin 
    RegisterComponents('MyTestCompoundComponent', [TTestCompoundComponent]); 
end; 
+1

알림 메서드에서 상속받은 것을 잊어 버렸습니다. – kobik

+0

@kobik : 네가 맞아요, 고마워요! 질문을 업데이트했습니다. – ExDev

답변

8

@ kobik 님의 답변은 FSubCmp이 생성되기 전에 FSubCmp.PopupMenu 속성에 액세스하는 AV의 근본 원인을 설명합니다. 그러나 전체 구성 요소 코드는 달성하고자하는 것에 지나치게 복잡합니다.

구성 요소를 TLabelOwner으로 설정해야하며 소멸자를 완전히 제거해야합니다. (혹시 나중에 오브젝트 인스펙터에서 TLabel을 노출하려는 특히 사용자가 디자인 타임 속성을 사용자 정의 할 수 있도록) 그리고 당신은 또한 당신의 생성자에서 FSubCmp.SetSubComponent(True)를 호출해야합니다

constructor TTestCompoundComponent.Create(AOwner : TComponent); 
begin 
    inherited; 
    FSubCmp := TLabel.Create(Self); 
    FSubCmp.SetSubComponent(True); 
    FSubCmp.Parent := Self; 
end; 

귀하 Notification() 메서드는 SetLabelPopupMenu(nil) 대신 opRemove에 직접 응답하여 FSubCmp.PopupMenu := nil으로 설정해야합니다.

procedure TTestCompoundComponent.Notification(AComponent: TComponent; Operation: TOperation); 
begin  
    inherited; 
    if (Operation = opRemove) and (AComponent = LabelPopupMenu) then 
    FSubCmp.PopupMenu := nil; 
end; 
: 당신은 이미 PopupMenu가 할당 알고 그것을 파괴의 진행 중임을, 그래서 여분의 코드는 PopupMenu (반복)를 검색 nil를 확인한 RemoveFreeNotification()를 호출하기 위해, opRemove 작업에 대한 모든 과잉이다

귀하의 SetLabelPopupMenu() 방법은 일반적으로 눈이 피고 GetLabelPopupMenu()에 대한 모든 중복 전화를 사용합니다.한 번만를 호출하고 필요에 따라 다음 사용할 수있는 로컬 변수에 반환 된 객체 포인터를 저장 :

procedure TTestCompoundComponent.SetLabelPopupMenu(AValue: TPopupMenu); 
var 
    PM: TPopupMenu; 
begin 
    PM := LabelPopupMenu; 

    if (PM <> AValue) then 
    begin 
    if (PM <> nil) then 
     PM.RemoveFreeNotification(Self); 

    FSubCmp.PopupMenu := AValue; 

    if (AValue <> nil) then 
     AValue.FreeNotification(Self); 
    end; 
end; 

그러나, 당신의 Notification() 방법은 실제로 완전히 중복하고 모두 제거해야합니다. TLabel은 이미 자체 PopupMenu 속성에 FreeNotification()을 호출하고 TPopupMenu 개체가 해제되면 PopupMenu 속성을 nil으로 설정하는 자체 Notification() 구현을 가지고 있습니다. 수동으로 처리 할 필요가 없습니다. 그리고, SetLabelPopupMenu()에 추가 모든 코드는 중복 제거해야합니다

procedure TTestCompoundComponent.SetLabelPopupMenu(AValue: TPopupMenu); 
begin 
    FSubCmp.PopupMenu := AValue; 
end; 

이것은 또한 @kobik에 의해 제안 된 수정이 중복뿐만 아니라 1 제거 할 수 있다는 것을 의미합니다 :

function TTestCompoundComponent.GetLabelPopupMenu: TPopupMenu; 
begin 
    Result := FSubCmp.PopupMenu; 
end; 

: 사용자가 TLabel을 직접 해방하기로 결정한 경우가 아니라면 (실제로 어리석은 사람이 아니며 실제로 아무도 실제로 그렇게하지 않지만 기술적으로 가능함) 그쪽을 다루는 Notification() t 상황합니다 (TLabelOwner 당신을 위해 FreeNotificatio()를 호출로 구성 요소를 할당) :

uses 
    StdCtrls, Menus, ExtCtrls, Classes; 

type 
    TTestCompoundComponent = class(TPanel) 
    private 
    FSubCmp: TLabel; 
    function GetLabelPopupMenu: TPopupMenu; 
    procedure SetLabelPopupMenu(AValue: TPopupMenu); 
    public 
    constructor Create(AOwner: TComponent); override; 
    published 
    property LabelPopupMenu: TPopupMenu read GetLabelPopupMenu write SetLabelPopupMenu; 
    end; 

... 

constructor TTestCompoundComponent.Create(AOwner : TComponent); 
begin 
    inherited; 
    FSubCmp := TLabel.Create(Self); 
    FSubCmp.SetSubComponent(True); 
    FSubCmp.Parent := Self; 
end; 

function TTestCompoundComponent.GetLabelPopupMenu: TPopupMenu; 
begin 
    Result := FSubCmp.PopupMenu; 
end; 

procedure TTestCompoundComponent.SetLabelPopupMenu(AValue: TPopupMenu); 
begin 
    FSubCmp.PopupMenu := AValue; 
end; 

심지어 그냥이 :

말했다되고 그건
function TTestCompoundComponent.Notification(AComponent: TComponent; Opration: TOperation); 
begin 
    inherited; 
    if (Operation = opRemove) and (AComponent = FSubCmp) then 
    FSubCmp := nil; 
end; 

function TTestCompoundComponent.GetLabelPopupMenu: TPopupMenu; 
begin 
    if FSubCmp <> nil then 
    Result := FSubCmp.PopupMenu 
    else 
    Result := nil; 
end; 

가, 여기에 코드의 단순화 된 버전입니다 :

uses 
    StdCtrls, Menus, ExtCtrls, Classes; 

type 
    TTestCompoundComponent = class(TPanel) 
    private 
    FSubCmp: TLabel; 
    public 
    constructor Create(AOwner: TComponent); override; 
    published 
    property SubLabel: TLabel read FSubCmp; 
    end; 

... 

constructor TTestCompoundComponent.Create(AOwner : TComponent); 
begin 
    inherited; 
    FSubCmp := TLabel.Create(Self); 
    FSubCmp.SetSubComponent(True); 
    FSubCmp.Parent := Self; 
end; 
+0

이것은 실제로 허용 된 대답이어야합니다. +1 나는이 사건에서 아무 통보도 필요 없다는 사실을 완전히 간과했다. – kobik

+0

@kobik : 요청한 질문에 대한 실제 답변을 제공했습니다 (AVbox가 생성되기 전에 'FSubCmp'에 액세스하고 있기 때문에 왜 그런가요?). 구성 요소의 디자인을 개선하는 방법에 대한 세부 정보를 제공하고 있습니다. AV의 발생을 피할 수 있습니다. –

+0

좋은 답변입니다! 나는 그것을 완전히 잘못하고 있었다, 나는 정말로 설명을 바르게 평가했다! – ExDev

5

GetLabelPopupMenu()에서 FSubCmpnil이다. FSubCmpnil 인 경우 PopupMenu 속성을 참조하면 AV가 발생합니다.

if FSubCmp = nil then 
    Result := nil 
else 
    Result := FSubCmp.PopupMenu; 

그렇지 않으면,이 대신에 Notification()and 논리의 순서를 변경 : 그래서, 당신은 예를 들어, GetLabelPopupMenu()에 그 확인에 필요한 조건 (Operation = opRemove)은 거짓

if (Operation = opRemove) and (AComponent = GetLabelPopupMenu()) 

경우 오른쪽 조건은 평가되지 않습니다 (단락).

+0

"단락 회로 평가"는'{$ BOOLEVAL} '이 (기본적으로)'OFF '로 설정되어 있음에 따라 달라집니다. 그것이'ON'이면'Get'에서'GetLabelPopupMenu()'가 항상 호출 될 것이므로'nil'을 검사하기 위해 그것을 고치면 궁극적으로 유일한 해결책이 필요합니다. –

+0

감사. 이것이 그가 기본 행동이기 때문에 나는 그것을 언급하는 것을 생각하지 않았다. 어쨌든 나는 왜 옳은 마음에있는 사람이라면 언제까지 ON으로 설정할 수 있을지 궁금해. – kobik

관련 문제