Wykrywanie zmian w agregowanych właściwościach własnego komponentu

0

Dawno już nie tworzyłem własnych komponentów (od podstaw, rozszerzanie istniejących klas pomijam), a że będę potrzebował ich dość sporo do swojego nowego projektu, dla testów wykonałem sobie prosty komponent dziedziczący z klasy TGraphicControl i się bawię; Wszystko szło gładko, do wczoraj;

Stworzyłem grupę właściwości, która jest klasą dedykowaną, dziedziczącą z TPersistent; Instancja tej agregowanej klasy tworzona jest w konstruktorze klasy komponentu oraz zwalniana w jej destruktorze; Poniżej przykład, aby zobrazować jak wygląda użycie takiego cuda:

type
  TAppearanceGroup = class(TPersistent)
  private
    FLineWidth: Integer;
    FLineColor: TColor;
  public
    constructor Create();
  published
    property LineWidth: Integer read FLineWidth write FLineWidth default 1;
    property LineColor: TColor read FLineColor write FLineColor default clMenuHighlight;
  end;

type
  TGraphicControl1 = class(TGraphicControl)
  private
    FAppearance: TAppearanceGroup;
  private
    procedure SetAppearance(AAppearance: TAppearanceGroup);
  protected
    procedure Paint(); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  published
    property Appearance: TAppearanceGroup read FAppearance write SetAppearance;
  end;

procedure Register;

implementation

constructor TAppearanceGroup.Create();
begin
  inherited Create();

  FLineWidth := 1;
  FLineColor := clMenuHighlight;
end;

constructor TGraphicControl1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAppearance := TAppearanceGroup.Create();
end;

destructor TGraphicControl1.Destroy();
begin
  FAppearance.Free();
  inherited Destroy();
end;

procedure TGraphicControl1.SetAppearance(AAppearance: TAppearanceGroup);
begin
  FAppearance.Assign(AAppearance);
  Invalidate();
end;

procedure TGraphicControl1.Paint();
begin
  with Canvas do
  begin
    Pen.Color   := FAppearance.FLineColor;
    Pen.Width   := FAppearance.FLineWidth;
    Brush.Style := bsClear;

    FillRect(Rect(0, 0, Width, Height));
    Rectangle(Rect(5, 5, Width - 5, Height - 5));
  end;
end;

procedure Register;
begin
  RegisterComponents('Tests', [TGraphicControl1]);
end;

Jak widać w powyższym kodzie, klasa komponentu posiada właściwość Appearance, której dane odczytywane są z pola FAppearance, a modyfikowane za pomocą metody SetAppearance; Wszystko gra, nowy węzeł właściwości pokazuje się w OI, a sam komponent wygląda tak, jak mu kazałem wyglądać (według tego, co jest w metodzie Paint):

object-inspector-and-form.png

Wszystko pięknie ładnie, ale jest jeden problem; Podczas zmiany wartości którejkolwiek właściwości z grupy Appearance (LineWidth lub LineColor), natychmiastowo komponent musi zostać odmalowany; To oczywiste - zmieniam kolor ramki, więc chcę widzieć jak wygląda z innym kolorem (czy z inną szerokością linii);

Z moich badań wynika, że po zmianie wartości właściwości z grupy Appearance, metoda SetAppearance nie jest w ogóle wywoływana; W tej metodzie musi być wywołana jakaś metoda, która pozwoli przemalować komponent (np. Invalidate którego użyłem w powyższym przykładzie, lub chociaż Repaint); W tym przykładowym kodzie chodzi tylko o przemalowanie, ale w tym nad którym pracuję, po zmianie wartości którejś z agregowanych właściwości, ma być wykonane więcej czynności; Jednak jeśli uda się przemalować komponent, to i nie będzie problemu z wykonaniem innych czynności;

Czy ktoś wie jak i gdzie w takim razie móc zareagować na zmianę wartości właściwości? W jaki sposób móc zareagować od razu, nie czekając na przemalowanie komponentu, wywołane np. przesunięciem komponentu, jego rozciągnięciem czy odsłonięciem przez inne okno?

Wczoraj i dziś dość sporo poczytałem, jednak nie mogę dojść do tego, dlaczego metoda SetAppearance nie zostaje wywołana - zapomniałem już jak się to robi; Jeśli byłby ktoś w stanie odpowiedzieć w jaki sposób w zareagować na zmianę właściwości umieszczonej w podklasie, czy nawet w jaki sposób w ogóle korzystać z takich klas i grupowania właściwości, to byłbym wdzięczny;

Pamiętam jednak, że właśnie w ten sposób aktualizuje się klasę z zagnieżdżonymi właściwościami (przez osobną metodę i wykonanie Assign - to znalazłem choćby na stackoverflow), ale jak widać coś jest nie tak, skoro właściwości zostają zapamiętane, ale metoda ustawiająca nie zostaje w ogóle wywołana;

Proszę więc o wskazówki, bo problem jest na pewno banalny, jednak coś źle robię i nie udało mi się znaleźć tego "złego kodu";

PS: Kod testuję pod Delphi7, jakby kto pytał.

1

Nie instaluje na ogół większości komponentów, tylko staram się używać ich dynamicznie. Jednak ze źródel VCL Delphi 7 Enterprise, szukając w plikach *.pas ciągu znaków = class(TGraphicControl) trafiamy na przykład na TGauge i plik ze źródłem gauges.pas. Mamy tam na przykład zmianę koloru tła, która wygląda w kodzie tak:

procedure TGauge.SetBackColor(Value: TColor);
begin
  if Value <> FBackColor then
  begin
    FBackColor := Value;
    Refresh;
  end;
end;

Także chyba kluczowa tutaj jest metoda Refresh, zdaje się będąca odmalowaniem Canvasu. Ale pewności nie mam. Może tak pokombinuj. Ja wiem, że jest jeszcze jest ważne ComponentState i niektórzy autorzy komponentów, jeśłi mamy csDesigned znajde się, to każą komponentowi nie reagować. Na przykład tworząć komponent który ma migać kolorami albo scrollować tresć. Wiadomo, że po co nam rozpraszać nas podczas tworzenia w IDE.

Jeśli moje rady nie pomogą, to pewnie ktoś bardziej kumaty w tworzeniu komponentów Tobie pomoże. Bo kiedy ja sobie robiłem własny moduł z kodem dla tray ikonki obsługującej zdarzenie nowego odpalenia Explorera Windows itp. Robiłem to pod kątem WinAPI, zapożyczając pewne elementy kodu ze strarego komponentu TTrayIcon jeszcze pod chyba Delphi 3. A niektóre elementy funkcji z VCL przenosiłem z ich źródeł, jakimi dysponuje z wersji Enterprise. Dlatego pod VCL słabo wiele rzeczy ogarniam.

0

Nie posiadam licencji na wersję Enterprise, więc nie posiadam źródeł RTL/VCL;

Zwróć jednak uwagę, że w Twoim przykładzie, mutator modyfikuje typ prosty (TColor), a u mnie parametr metody jest klasą TAppearanceGroup; Mój komponent posiada także właściwości bezpośrednio w ciele klasy, a nie podklasie węzła właściwości i z tym nie ma problemu - wszystko się aktualizuje; Jedynym problemem jest zmiana wartości właściwości zawartej wewnątrz podklasy, czyli tak jak w moim tutejszym przykładzie, modyfikacja właściwości LineWidth lub LineColor, zawartej w klasie FAppearance; Wtedy zmiany są uwzględniane, ale metoda SetAppearance nie jest w ogóle wywoływana, więc nie ma gdzie na taką zmianę zareagować; Jeśli metoda SetAppearance zostałaby wywołana, to i Invalidate także, więc i komponent zostałby przemalowany; A tu kicha, bo ta metoda jest pomijana i nie wiem jak się do tego dobrać;

Zbiór ComponentState także obsługuję - część funkcji musi działać jedynie podczas ustawiania komponentu i wtedy sobie elegancko korzystam z enuma csDesigned; Ale to w sumie nie ma związku z problemem, bo póki co nie mogę znaleźć miejsca, w którym mógłbym zareagować na zmianę właściwości, zawartej w podklasie - tylko tu jest problem;

Ewentualnym rozwiązaniem było by napisanie metod zmieniających do każdej właściwości zagregowanej; Poniżej kod takiego dziwoląga:

type
  TAppearanceGroup = class(TPersistent)
  private
    FOwner: TGraphicControl;
    FLineWidth: Integer;
    FLineColor: TColor;
  private
    procedure SetLineWidth(ALineWidth: Integer);
    procedure SetLineColor(ALineColor: TColor);
  public
    constructor Create(AOwner: TGraphicControl);
  published
    property LineWidth: Integer read FLineWidth write SetLineWidth;
    property LineColor: TColor read FLineColor write SetLineColor;
  end;

type
  TGraphicControl1 = class(TGraphicControl)
  private
    FAppearance: TAppearanceGroup;
  private
    procedure SetAppearance(AAppearance: TAppearanceGroup);
  protected
    procedure Paint(); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  published
    property Appearance: TAppearanceGroup read FAppearance write SetAppearance;
  end;

procedure Register;

implementation

constructor TAppearanceGroup.Create(AOwner: TGraphicControl);
begin
  inherited Create();

  FOwner := AOwner;
  FLineWidth := 1;
  FLineColor := clMenuHighlight;
end;

procedure TAppearanceGroup.SetLineWidth(ALineWidth: Integer);
begin
  if FLineWidth <> ALineWidth then
  begin
    FLineWidth := ALineWidth;
    FOwner.Invalidate();
  end;
end;

procedure TAppearanceGroup.SetLineColor(ALineColor: TColor);
begin
  if FLineColor <> ALineColor then
  begin
    FLineColor := ALineColor;
    FOwner.Invalidate();
  end;
end;

constructor TGraphicControl1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAppearance := TAppearanceGroup.Create(Self);
end;

destructor TGraphicControl1.Destroy();
begin
  FAppearance.Free();
  inherited Destroy();
end;

procedure TGraphicControl1.SetAppearance(AAppearance: TAppearanceGroup);
begin
  FAppearance.Assign(AAppearance);
  Invalidate();
end;

procedure TGraphicControl1.Paint();
begin
  with Canvas do
  begin
    Pen.Color   := FAppearance.FLineColor;
    Pen.Width   := FAppearance.FLineWidth;
    Brush.Style := bsClear;

    FillRect(Rect(0, 0, Width, Height));
    Rectangle(Rect(5, 5, Width - 5, Height - 5));
  end;
end;

procedure Register;
begin
  RegisterComponents('Tests', [TGraphicControl1]);
end;

end.

Po modyfikacji jakiejkolwiek właściwości w grupie Appearance (czyli w zawartości pola FAppearance), zostaje wywołana metoda Invalidate na obiekcie z pola FOwner, który jest przekazywany w konstruktorze i zapamiętany; Po zmianie wartości właściwości komponent aktualizuje się od razu, czyli wszystko działa prawidłowo;

Ale to raczej nie wchodzi w grę, dlatego że przy np. 30 właściwościach w kilku podklasach (czyli typowym drzewku właściwości), dla każdej pojedynczej właściwości trzeba by pisać odrębny mutator, czyli musiałbym napisać ich 30 i do każdej podklasy przekazywać Ownera, tak jak widać to w powyższym przykładzie; To raczej nie jest dobre rozwiązanie i obstawiam, że jest jakaś metoda na wykrycie zmian w całej podklasie, a nie tylko pojedynczej jej właściwości.

2

W skrócie - namieszałeś i to raczej poważnie ;-)

furious programming napisał(a):

Z moich badań wynika, że po zmianie wartości właściwości z grupy Appearance, metoda SetAppearance nie jest w ogóle wywoływana; W tej metodzie musi być wywołana jakaś metoda, która pozwoli przemalować komponent (np. Invalidate którego użyłem w powyższym przykładzie, lub chociaż Repaint); W tym przykładowym kodzie chodzi tylko o przemalowanie, ale w tym nad którym pracuję, po zmianie wartości którejś z agregowanych właściwości, ma być wykonane więcej czynności; Jednak jeśli uda się przemalować komponent, to i nie będzie problemu z wykonaniem innych czynności;

Czy ktoś wie jak i gdzie w takim razie móc zareagować na zmianę wartości właściwości? W jaki sposób móc zareagować od razu, nie czekając na przemalowanie komponentu, wywołane np. przesunięciem komponentu, jego rozciągnięciem czy odsłonięciem przez inne okno?

Ale zastanów się - skąd TGraphicControl1 ma wiedzieć że zmieniłeś wartość np. TAppearanceGroup.LineColor skoro go o tym nie powiadamiasz?

Wczoraj i dziś dość sporo poczytałem, jednak nie mogę dojść do tego, dlaczego metoda SetAppearance nie zostaje wywołana - zapomniałem już jak się to robi;

Zastanów się #2 - SetApperance jest Setterm dla właściwości TGraphicControl1.Appearance, czyli w/w metoda będzie wołana automatycznie przy przypisaniu wartości do właściwości TGraphicControl1.Appearance.
czyli jak zrobisz tak:

GraphicControl1.Appearance := TAppearance.Create;

Ty nigdzie tego nie robisz, to i Ci się ta metoda nie woła - normalka.

Jeśli byłby ktoś w stanie odpowiedzieć w jaki sposób w zareagować na zmianę właściwości umieszczonej w podklasie, czy nawet w jaki sposób w ogóle korzystać z takich klas i grupowania właściwości, to byłbym wdzięczny;

Tak, a jak bardzo będziesz wdzięczny? ;-)
Ale to jest proste - musisz powiadomić nadrzędną klasę, że zmieniły się właściwości w podrzędnej. A jak - to już tylko Twoja wiedza i wyobraźnia Cię ogranicza.

Pamiętam jednak, że właśnie w ten sposób aktualizuje się klasę z zagnieżdżonymi właściwościami (przez osobną metodę i wykonanie Assign

No to musisz pokopać głębiej - bo nie do tego to służy.
Assigin służy do kopiowania obiektów (a dokładnie - do kopiowania wartości pól obiektów, czyli obiektu razem ze stanem), a nadpisując ją w swoich klasach możesz sterować tym mechanizmem.
VCL ten mechanizm wykorzystuje do odczytywania stanu kontrolek z DFMa i trzeba go poprawnie napsać, bo inaczej to co wyklikasz w OI nie zostanie zapisane do DFMa.
Ale spokojnie - to tez napisałem.

  • to znalazłem choćby na stackoverflow), ale jak widać coś jest nie tak, skoro właściwości zostają zapamiętane, ale metoda ustawiająca nie zostaje w ogóle wywołana;

Proszę więc o wskazówki, bo problem jest na pewno banalny, jednak coś źle robię i nie udało mi się znaleźć tego "złego kodu";

PS: Kod testuję pod Delphi7, jakby kto pytał.

Można to zrobić np. tak:

type
  INotify = interface
  ['{14EA3A1E-5720-4A5F-B036-6D1D08D82C2D}']
    procedure Notify;
  end;

  TAppearanceGroup = class(TPersistent)
  private
    type
      EAppearanceGroup = class(Exception);
  private
    FLineWidth: Integer;
    FLineColor: TColor;
    FNotify : INotify;
    procedure SetLineColor(const aValue: TColor);
    procedure SetLineWidth(const aValue: Integer);
  protected
    procedure DoNotifyParent; virtual;
  public
    constructor Create(AOwner: TObject);
    procedure Assign(Source: TPersistent); override;
  published
    property LineWidth: Integer read FLineWidth write SetLineWidth default 1;
    property LineColor: TColor read FLineColor write SetLineColor default clMenuHighlight;
  end;

type
  TGraphicControl1 = class(TGraphicControl, INotify)
  strict private
    procedure SetAppearance(const aValue: TAppearanceGroup);
  private
    FAppearance: TAppearanceGroup;
  protected
    procedure Paint(); override;
    procedure Notify;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    property Appearance: TAppearanceGroup read FAppearance write SetAppearance;
  end;

procedure Register;

implementation

procedure TAppearanceGroup.Assign(Source: TPersistent);
var
  lSource: TAppearanceGroup;
begin
  inherited;
  if Source is TAppearanceGroup then
  begin
    lSource := Source as TAppearanceGroup;

    FLineWidth := lSource.LineWidth;
    FLineColor := lSource.LineColor;
    DoNotifyParent;
  end;
end;

constructor TAppearanceGroup.Create(AOwner: TObject);
begin
  inherited Create;

  if not Supports(AOwner, INotify, FNotify) then
    raise EAppearanceGroup.Create('Próba utworzenia TAppearanceGroup z właścicielem, który nie implmentuje INotify');

  FLineWidth := 1;
  FLineColor := clMenuHighlight;
end;

procedure TAppearanceGroup.DoNotifyParent;
begin
  FNotify.Notify;
end;

procedure TAppearanceGroup.SetLineColor(const aValue: TColor);
begin
  if FLineColor <> aValue then
  begin
    FLineColor := aValue;
    DoNotifyParent;
  end;
end;

procedure TAppearanceGroup.SetLineWidth(const aValue: Integer);
begin
  if FLineWidth <> aValue then
  begin
    FLineWidth := aValue;
    DoNotifyParent;
  end;
end;

procedure TGraphicControl1.Assign(Source: TPersistent);
begin
  inherited;
  if Source is TGraphicControl1 then
    Appearance.Assign(TGraphicControl1(Source).Appearance);
end;

constructor TGraphicControl1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAppearance := TAppearanceGroup.Create(Self);
end;

destructor TGraphicControl1.Destroy;
begin
  FAppearance.Free;
  inherited;
end;

procedure TGraphicControl1.Notify;
begin
  Invalidate;
end;

procedure TGraphicControl1.Paint();
begin
  with Canvas do
  begin
    Pen.Color   := FAppearance.FLineColor;
    Pen.Width   := FAppearance.FLineWidth;
    Brush.Style := bsClear;

    FillRect(Rect(0, 0, Width, Height));
    Rectangle(Rect(5, 5, Width - 5, Height - 5));
  end;
end;

procedure TGraphicControl1.SetAppearance(const aValue: TAppearanceGroup);
begin
  FAppearance.Assign(aValue);
end;

procedure Register;
begin
  RegisterComponents('Tests', [TGraphicControl1]);
end;
1

Ale zastanów się - skąd TGraphicControl1 ma wiedzieć że zmieniłeś wartość np. TAppearanceGroup.LineColor skoro go o tym nie powiadamiasz?

No właśnie szukam miejsca i sposobu, aby go powiadomić :]

Zastanów się #2 - SetApperance jest Setterm dla właściwości TGraphicControl1.Appearance [...]

No niby jest, tyle że ten setter nie jest w ogóle wywoływany po jakiejkolwiek zmianie - teraz już wiem, że on nie służy do wprowadzania zmian zawartości instancji podklasy w OI;

Poza tym, imho, TGraphicControl1.Appearance powinno być ReadOnly - przecież nie będziesz przypisywał nowej instancji do tej właściwości, prawda?

No tak, czyli ten setter w ogóle jest niepotrzebny i racja, dlatego nie jest wywoływany podczas zmiany wartości właściwości; Czyli tak czy siak trzeba porobić settery do każdej właściwości z podklas komponentu;

Tak, a jak bardzo będziesz wdzięczny?

No nie wiem - nie licząc plusika i akceptacji posta, pewnie nie dam Ci bana do końca września;

Ale to jest proste - musisz powiadomić nadrzędną klasę, że zmieniły się właściwości w podrzędnej. A jak - to już tylko Twoja wiedza i wyobraźnia Cię ogranicza.

No właśnie w tym rzecz że wiem, że muszę ją jakoś powiadomić; Tylko nie wiedziałem czy da się zrobić bez przekazywania instancji klasy komponentu to podklas, czy się po prostu nie da; No i wygląda na to, że tak to nie zadziała i oczywiście ma prawo niedziałać, bo pomyliłem przeznaczenie takiego settera;

W poprzednim swoim poście pokazałem jak robić nie chciałem - dorobiłem settery do klasy TAppearanceGroup i w konstruktorze tej klasy podawałem instancję klasy komponentu, żeby ona mogła wywołać np. Invalidate; To działa super, ale nie wiedziałem czy to dobra droga;

No to musisz pokopać głębiej - bo nie do tego to służy.
Assigin służy do kopiowania obiektów (a dokładnie - do kopiowania wartości pól obiektów, czyli obiektu razem ze stanem), a nadpisując ją w swoich klasach możesz sterować tym mechanizmem.

Rozumiem, dlatego też wykonywałem Assign na polu FAppearance, aby przepisać zawartość klasy z parametru do pola; Inaczej utraciłbym referencję do instancji utworzonej w konstruktorze komponentu, czyli tym samym spowodował wyciek pamięci; Tyle że ten setter w ogóle nie był używany, więc ani nie była wywoływana metoda Assign, ani następująca po niej Invalidate; Stworzyłem ten setter, dlatego że sądziłem, iż on jest wywoływany podczas przestawiania właściwości w OI, ale oczywiście pomyliłem i słusznie nie miało prawo działać;

VCL ten mechanizm wykorzystuje do odczytywania stanu kontrolek z DFMa i trzeba go poprawnie napsać, bo inaczej to co wyklikasz w OI nie zostanie zapisane do DFMa.

Dlatego też ten mutator musi istnieć, ale nie do tego o czym myślałem; Teraz już jest dobrze - komponent się aktualizuje tak jak chciałem, a DFM zawiera to co trzeba;

Problem rozwiązałem podobnie do Twojego przykładu, jednak nie korzystałem z interfejsów, tylko po prostu przekazałem instancję klasy komponentu w konstruktorze podklasy i z niej wywołuję Invalidate, więc wszystko śmiga aż miło; Poniżej przykład sprawnego komponentu:

uses
  SysUtils, Classes, Controls, Graphics;

type
  TAppearanceGroup = class(TPersistent)
  private
    FComponent: TGraphicControl;
    FLineWidth: Integer;
    FLineColor: TColor;
  private
    procedure SetLineWidth(ALineWidth: Integer);
    procedure SetLineColor(ALineColor: TColor);
  public
    constructor Create(AComponent: TGraphicControl);
  published
    property LineWidth: Integer read FLineWidth write SetLineWidth default 1;
    property LineColor: TColor read FLineColor write SetLineColor default clMenuHighlight;
  end;

type
  TGraphicControl1 = class(TGraphicControl)
  private
    FAppearance: TAppearanceGroup;
  private
    procedure SetAppearance(AAppearance: TAppearanceGroup);
  protected
    procedure Paint(); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  published
    property Appearance: TAppearanceGroup read FAppearance write SetAppearance;
  end;

procedure Register;

implementation

constructor TAppearanceGroup.Create(AComponent: TGraphicControl);
begin
  inherited Create();

  FComponent := AComponent;
  FLineWidth := 1;
  FLineColor := clMenuHighlight;
end;

procedure TAppearanceGroup.SetLineWidth(ALineWidth: Integer);
begin
  if FLineWidth <> ALineWidth then
  begin
    FLineWidth := ALineWidth;
    FComponent.Invalidate();
  end;
end;

procedure TAppearanceGroup.SetLineColor(ALineColor: TColor);
begin
  if FLineColor <> ALineColor then
  begin
    FLineColor := ALineColor;
    FComponent.Invalidate();
  end;
end;

constructor TGraphicControl1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FAppearance := TAppearanceGroup.Create(Self);
end;

destructor TGraphicControl1.Destroy();
begin
  FAppearance.Free();
  inherited Destroy();
end;

procedure TGraphicControl1.SetAppearance(AAppearance: TAppearanceGroup);
begin
  FAppearance.Assign(AAppearance);
end;

procedure TGraphicControl1.Paint();
begin
  with Canvas do
  begin
    Pen.Color   := FAppearance.FLineColor;
    Pen.Width   := FAppearance.FLineWidth;
    Brush.Style := bsClear;

    FillRect(Rect(0, 0, Width, Height));
    Rectangle(Rect(5, 5, Width - 5, Height - 5));
  end;
end;

procedure Register;
begin
  RegisterComponents('Tests', [TGraphicControl1]);
end;

end.

Miałem tylko nadzieję, że do każdej właściwości z podklas nie będę musiał dopisywać pojedynczych setterów, ale skoro to jedyna sensowna i mająca prawo działać opcja, to może być - trochę więcej kodu się klepnie i już;

Problem rozwiązany, dziękuję Wam za pomoc.

1
furious programming napisał(a):

Ale zastanów się - skąd TGraphicControl1 ma wiedzieć że zmieniłeś wartość np. TAppearanceGroup.LineColor skoro go o tym nie powiadamiasz?

No właśnie szukam miejsca i sposobu, aby go powiadomić :]

Zastanów się #2 - SetApperance jest Setterm dla właściwości TGraphicControl1.Appearance [...]

No niby jest, tyle że ten setter nie jest w ogóle wywoływany po jakiejkolwiek zmianie - teraz już wiem, że on nie służy do wprowadzania zmian zawartości instancji podklasy;

Poza tym, imho, TGraphicControl1.Appearance powinno być ReadOnly - przecież nie będziesz przypisywał nowej instancji do tej właściwości, prawda?

No tak, czyli ten setter w ogóle jest niepotrzebny i racja, dlatego nie jest wywoływany podczas zmiany wartości właściwości; Czyli tak czy siak trzeba porobić settery do każdej właściwości z podklas komponentu;

Tak, a jak bardzo będziesz wdzięczny?

No nie wiem - nie licząc plusika i akceptacji posta, pewnie nie dam Ci bana do końca września;

Taaaa... to się jeszcze okaże, tylko zaczekaj aż ktoś wyjedzie z jakąś mega-fajną-zajawką ;-)

Ale to jest proste - musisz powiadomić nadrzędną klasę, że zmieniły się właściwości w podrzędnej. A jak - to już tylko Twoja wiedza i wyobraźnia Cię ogranicza.

No właśnie w tym rzecz że wiem, że muszę ją jakoś powiadomić; Tylko nie wiedziałem czy da się zrobić bez przekazywania instancji klasy komponentu to podklas, czy się po prostu nie da; No i wygląda na to, że tak to nie zadziała i oczywiście ma prawo niedziałać, bo pomyliłem przeznaczenie takiego settera;

Ano; ale to nie jedyna droga, można to zrobić inaczej.
Tyle, że to dłuuuuga i nudna rozprawka jest, a nie ma sensu tego przeciągać.
Powiem tyle, że mam taką jedną potężną kontrolkę własnego pomysłu, która składa się kilkudziesięciu klas - jedne są agregowane inne nie, ale wszystkie muszą powiadamiać siebie (czasem kaskadowo) o zmianie stanu.
I tam zrealizowane jest na zasadzie generycznych notyfikacji object-to-object - kiedyś na grupie pl.comp.lang.delphi o tym pisałem, można poszukać.

W poprzednim swoim poście pokazałem jak robić nie chciałem - dorobiłem settery do klasy TAppearanceGroup i w konstruktorze tej klasy podawałem instancję klasy komponentu, żeby ona mogła wywołać np. Invalidate; To działa super, ale nie wiedziałem czy to dobra droga;

No to musisz pokopać głębiej - bo nie do tego to służy.
Assigin służy do kopiowania obiektów (a dokładnie - do kopiowania wartości pól obiektów, czyli obiektu razem ze stanem), a nadpisując ją w swoich klasach możesz sterować tym mechanizmem.

Rozumiem, dlatego też wykonywałem Assign na polu FAppearance, aby przepisać zawartość klasy z parametru do pola; Inaczej utraciłbym referencję do instancji utworzonej w konstruktorze komponentu, czyli tym samym spowodował wyciek pamięci; Tyle że ten setter w ogóle nie był używany

A nieprawda - był używany tylko w jednym przypadku, kiedy IDE lub aplikacja ładowała okno z DFMa ;-)

, więc ani nie była wywoływana metoda Assign, ani następująca po niej Invalidate; Stworzyłem ten setter, dlatego że sądziłem, iż on jest wywoływany podczas przestawiania właściwości w OI, ale oczywiście pomyliłem i słusznie nie miało prawo działać;

VCL ten mechanizm wykorzystuje do odczytywania stanu kontrolek z DFMa i trzeba go poprawnie napisać, bo inaczej to co wyklikasz w OI nie zostanie zapisane do DFMa.

Dlatego też ten mutator musi istnieć, ale nie do tego o czym myślałem; Teraz już jest dobrze - komponent się aktualizuje tak jak chciałem, a DFM zawiera to co trzeba;

Problem rozwiązałem podobnie do Twojego przykładu, jednak nie korzystałem z interfejsów, tylko po prostu przekazałem instancję klasy komponentu w konstruktorze podklasy i z niej wywołuję Invalidate, więc wszystko śmiga aż miło; Poniżej przykład sprawnego komponentu:

A nie podoba mi się takie rozwiązanie - intf jest po to, aby odseparować jedno od drugiego. Czyli konkretną klasę od notyfikacji, a sama notyfikacja może (i powinna) być mechanizmem ogólnym, który da się wykorzystać wszędzie.
Poza tym łatwiej taki kod utrzymywać i rozwijać; nie mam zamiaru Cię przekonywać, ale jak zaczniesz pisać dużo a przede wszystkim - utrzymywać to co napisałeś, to sam na to wpadniesz.

Miałem tylko nadzieję, że do każdej właściwości z podklas nie będę musiał dopisywać pojedynczych setterów, ale skoro to jedyna sensowna i mająca prawo działać opcja, to może być - trochę więcej kodu się klepnie i już;

Mam wrażenie że się nie rozumiemy; te settery dla SubProperty są potrzebne tylko dla IDE, bo inaczej nie będzie ci działać zapisywanie właściwości SubPropeorty do DFMa.
Tak działa IDE - po prostu.
Gdybyś nie potrzebował owych komponentów w IDE, settery możesz (imho - powinieneś) olać.

Problem rozwiązany, dziękuję Wam za pomoc.

Proszę.

0

Taaaa... to się jeszcze okaże, tylko zaczekaj aż ktoś wyjedzie z jakąś mega-fajną-zajawką ;)

Z tym banem to oczywiście żart, ale nie niszcz użytkowników za ich niewiedzę/głupotę :P

Powiem tyle, że mam taką jedną potężną kontrolkę własnego pomysłu, która składa się kilkudziesięciu klas - jedne są agregowane inne nie, ale wszystkie muszą powiadamiać siebie (czasem kaskadowo) o zmianie stanu.
I tam zrealizowane jest na zasadzie generycznych notyfikacji object-to-object - kiedyś na grupie pl.comp.lang.delphi o tym pisałem, można poszukać.

Nie no, aż tak rozwiniętych komponentów raczej nie będę robił, ale dobrze wiedzieć, że jest inna droga; Na wypadek jakbym zechciał zrobić taki kombajn, to na pewno przyda się to, o czym napisałeś; Na dzień dzisiejszy nie mam doświadczenia w tworzeniu aż tak skomplikowanych komponentów, więc siłą rzeczy nie znam takich rozwiązań; Na jakiś pomysł pewnie bym wpadł, ale czy było by to wygodne to już zupełnie inna sprawa;

A nieprawda - był używany tylko w jednym przypadku, kiedy IDE lub aplikacja ładowała okno z DFMa ;)

Mam wrażenie że się nie rozumiemy; te settery dla SubProperty są potrzebne tylko dla IDE, bo inaczej nie będzie ci działać zapisywanie właściwości SubPropeorty do DFMa.
Tak działa IDE - po prostu.
Gdybyś nie potrzebował owych komponentów w IDE, settery możesz (imho - powinieneś) olać.

No tak, to zauważyłem jak mi settera brakowało i w DFMie była pustka, przez co po ponownym wczytaniu projektu, wszystkie dodatkowe niedomyśle wartości właściwości poszły do nila; Tylko że ja widziałem rozszerzone zastosowanie tego settera, oczywiście nieistniejące; Dlatego też nie dziwię się, że nic na ten temat nie znalazłem w sieci (szukałem czegoś, co zwyczajnie nie istnieje);

No nic, jeszcze raz dziękuję za informacje.

1 użytkowników online, w tym zalogowanych: 0, gości: 1