2017-05-11 17 views
2

TPanelから派生したコンパウンドコンポーネントでは、サブコンポーネントのリンケージプロパティを設定し取得する唯一の目的を持つプロパティを公開しようとしています。コンパウンドコンポーネントをフォームに追加するたびに、アクセス違反が発生します。複合コンポーネントにサブコンポーネントのプロパティを公開するにはどうすればよいですか?

モジュール 'MyRuntimePackage.bpl'のアドレス12612D86でアクセス違反が発生します。アドレス00000080.

の読む私はTLabelとそのPopupMenuプロパティを使用して簡単な例を用意しましたが、フォーム/フレーム上の化合物成分を配置するとき、私はまだ同じ問題を抱えています。

Runtimeパッケージ:

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通知を受信したときにGetLabelPopupMenu()

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()に余分なコードのすべてが冗長であり、除去されるべきである:

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

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

これも@kobikによって提案された修正が冗長であり、1同様に除去することができる手段

:ユーザーがTLabelを直接解放することを決めた場合(実際には誰も実際にそれを行うことはできませんが、技術的に可能です)、あなたはthaを処理するNotification()トンの状況(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:あなたが質問された質問に対する実際の回答を提供しました(なぜ、AVが起こっているのですか?なぜなら、 'FSubCmp'は作成前にアクセスされているからです)。私はコンポーネントのデザインを改善する方法についていくつかの詳細を提供しているだけです(これにより、AVが起きるのを避けることもできます)。 –

+0

偉大な答え!私はそれを完全に間違ってやっていた、私は本当に説明を感謝しました! – ExDev

5

FSubCmpnilです。 FSubCmpnilの場合、そのPopupMenuプロパティを参照するとAVが発生します。だから、あなたは例えば、GetLabelPopupMenu()にそれをチェックする必要があります。そうしないと

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

、代わりにこれにNotification()andロジックの順序を変更:条件(Operation = opRemove)がfalseの場合は

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

を、右側の状態は評価されません(短絡)。

+0

"短絡の評価"は '{$ BOOLEVAL}'が 'OFF'(デフォルトでは)に設定されていることに依存しています。それが 'ON'の場合、' Get'の中で 'GetLabelPopupMenu()'が常に呼び出されるので、 'nil'をチェックするように修正するのが唯一の解決策です。 –

+0

ありがとうございます。これは彼がデフォルトの動作であるので、私はそれを言及するとは思わなかった。いずれにせよ、私は正しい人の誰がそれをONに設定するのかなぜ疑問に思う。 – kobik

関連する問題