Obsługa modułu z poziomu innego modułu

0

Witam,
Po dłuższej przerwie od korzystania z Lazarusa i zabawy w programowanie zdecydowałem się powrócić. Staram się wszystko robić dokładniej i rozsądniej, tak żeby mój kod był przejrzysty, bo często miałem z tym problemy. Niestety okazuje się, że moje chęci spowodowały dodatkowy problem, z którym sobie nie mogę poradzić. Całość podzieliłem na osobne moduły. Problem pojawia się jeśli jednym modułem chcę oddziaływać na drugi. W skrócie są dwa moduły. Pierwszy tworzy pusty TPanel po lewej stronie formy, drugi tworzy TButton po prawej stronie. Oba te komponenty są tworzone dynamicznie. Chciałbym osiągnąć by po naciśnięciu przycisku, Panel zmieniał kolor.

Nie potrafię znaleźć rozwiązania. Jedyne co mi przychodzi do głowy to odniesienie się do rodzica przycisku. Z tego co widzę istnieje coś takiego jak ComponentIndex. Nie wiem czy jest to to czego szukam, ale zakładam że każdy komponent musi być jakoś identyfikowane. Kwestia tylko jak się odnieść do tego konkretnego. Bo pewnie łatwiejsze będzie to gdy takie komponenty będę miał stworzone dwa, ale co jeśli będzie ich 100?

Udało mi się osiągnąć coś takiego

unit Main;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, MyPanel, MyButton;

type

  TForm2 = class(TForm)

    procedure FormCreate(Sender: TObject);
  private
  public

  end;

var
  Form2: TForm2;

implementation

{$R *.lfm}


procedure TForm2.FormCreate(Sender: TObject);
var
  Panel: TMyPanel;
  Button: TMyButton;
begin
  Panel := TMyPanel.CreateIt(Form2);
  Button := TMyButton.CreateIt(Form2);

  Panel.Show;
  Button.Show;
end;

end.

unit MyPanel;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, ExtCtrls, Forms, Windows, Graphics, Controls;

type
  TMyPanel = class(ExtCtrls.TPanel)
    public
      constructor CreateIt(AOwner: TComponent);
      procedure Paint; override;
    end;

implementation

constructor TMyPanel.CreateIt(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Hide;
  Parent := AOwner as TWinControl;
  Left := 0;
  Top := 0;
  Width := 100;
  Height := 100;
end;

procedure TMyPanel.Paint;
var
  Rec :TRect;
begin
  with Canvas do
  begin
    Brush.Color := clBlack;
    Brush.Style := bsSolid;
    Pen.Style := psClear;
    with Rec do
    begin
      Left := 0;
      Top := 0;
      Right := Self.Width + 1;
      Bottom := Self.Height + 1;
    end;
    Rectangle(Rec);
  end;
end;

end.


unit MyButton;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, ExtCtrls, Forms, Windows, Graphics, Controls, Dialogs;

type
  TMyButton = class(ExtCtrls.TPanel)
    public
      procedure ButtonClick(Sender: TObject);
      constructor CreateIt(AOwner: TComponent);
      procedure Paint; override;
    end;

implementation

procedure TMyButton.ButtonClick(Sender: TObject);
begin
  Showmessage('zmiana koloru panel');
  (Sender as TWinControl).Parent.Components[0].Destroy;
end;

constructor TMyButton.CreateIt(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Hide;
  Parent := AOwner as TWinControl;
  Left := 110;
  Top := 0;
  Width := 1000;
  Height := 100;
  onClick := ButtonClick;
end;

procedure TMyButton.Paint;
var
  Rec :TRect;
begin
  with Canvas do
  begin
    Brush.Color := clGreen;
    Brush.Style := bsSolid;
    Pen.Style := psClear;
    with Rec do
    begin
      Left := 0;
      Top := 0;
      Right := Self.Width + 1;
      Bottom := Self.Height + 1;
    end;
    Rectangle(Rec);
  end;
end;

end.


I faktycznie udaje się usunąć ten Panel który chce, natomiast nie potrafię się odnieść do jego właściwości Color. Druga kwestia to jest to co pisałem powyżej, Jeśli będzie tych komponentów chociażby 10, to już nie wystarczy wpisać 0 i wiedzieć że zadziała.

1

Przechowaj referencje tych dynamicznie tworzonych komponentów w jakichś zmiennych (np. polach klasy formy), a nie będziesz ich musiał później szukać.

1

Pytanie, czy chcesz to zrobić porządnie, czy żeby po prostu działało?

Moim zdaniem (są różne podejścia, ale ja się staram trzymać takiego w swojej "twórczości" ;) ) poszczególne moduły nie powinny za bardzo wiedzieć o swoim istnieniu. Zamiast tego staram się stworzyć jakiś centralny kontroler, czyli jakiś obiekt, który zarządza interakcjami między poszczególnymi elementami składowymi. W Twoim przypadku (aczkolwiek nie jest to do końca idealne, ale na początek może być) takim kontrolerem może być klasa TForm2, która odpowiada za utworzenie okienka, na którym Twoje działania maja się odbywać.

I teraz samo działanie się sprowadza do tego, że moduł/kod odpowiedzialny za obsługę wciśnięcia przycisku totalnie nie ma pojęcia o pozostałych modułach. Jego w ogóle nie obchodzi, co będzie się działo po wciśnięciu przycisku. Jedyne co ten kod robi, to przekazuje do kontrolera informację o tym, że przycisk został wciśnięty. Czyli tworzysz w klasie TForm2 procedurę o jakiejś nazwie i jedynie przekazujesz do modułu z przyciskiem, że w chwili wciśnięcia, ma on poinformować kontroler/główną klasę o takim zdarzeniu. A potem kod przycisku idzie spać i się niczym nie interesuje ;)

0

@lucasp17: a tak w ogóle to co Ty wyprawiasz? Kombinujesz jak koń pod górę, zamiast zrobić to porządnie.

Designera użyj i ustaw komponenty w odpowiedni sposób, zamiast cudować z dynamicznym tworzeniem komponentów. Natomiast swoje kontrolki wrzuć do swojego pakietu, a ten doinstaluj w IDE, tak aby dało się je wykorzystać w designerze.

0
furious programming napisał(a):

@lucasp17: a tak w ogóle to co Ty wyprawiasz? Kombinujesz jak koń pod górę, zamiast zrobić to porządnie.

Designera użyj i ustaw komponenty w odpowiedni sposób, zamiast cudować z dynamicznym tworzeniem komponentów. Natomiast swoje kontrolki wrzuć do swojego pakietu, a ten doinstaluj w IDE, tak aby dało się je wykorzystać w designerze.

Ja wiem, że wiele rzeczy cały czas robię nie do końca tak jak należy. Ale w tym przypadku jest to tylko skrócony kod, po to żeby było najłatwiej rozwiązać ten problem, o który mi chodzi i żebym zrozumiał co jest co. To dla mnie najlepszy sposób nauki na małych przykładach gdzie widzą jasno co za co odpowiada. Nie ukrywam, że nie jestem programistą, a to moje pisania to tylko luźna przygoda, która zaspokaja moje potrzeby.

cerrato napisał(a):

Pytanie, czy chcesz to zrobić porządnie, czy żeby po prostu działało?

Moim zdaniem (są różne podejścia, ale ja się staram trzymać takiego w swojej "twórczości" ;) ) poszczególne moduły nie powinny za bardzo wiedzieć o swoim istnieniu. Zamiast tego staram się stworzyć jakiś centralny kontroler, czyli jakiś obiekt, który zarządza interakcjami między poszczególnymi elementami składowymi. W Twoim przypadku (aczkolwiek nie jest to do końca idealne, ale na początek może być) takim kontrolerem może być klasa TForm2, która odpowiada za utworzenie okienka, na którym Twoje działania maja się odbywać.

I teraz samo działanie się sprowadza do tego, że moduł/kod odpowiedzialny za obsługę wciśnięcia przycisku totalnie nie ma pojęcia o pozostałych modułach. Jego w ogóle nie obchodzi, co będzie się działo po wciśnięciu przycisku. Jedyne co ten kod robi, to przekazuje do kontrolera informację o tym, że przycisk został wciśnięty. Czyli tworzysz w klasie TForm2 procedurę o jakiejś nazwie i jedynie przekazujesz do modułu z przyciskiem, że w chwili wciśnięcia, ma on poinformować kontroler/główną klasę o takim zdarzeniu. A potem kod przycisku idzie spać i się niczym nie interesuje ;)

To jest chyba dokładnie to co sam miałem na myśli. Przykład powyżej jest tylko skrótem tego wszystkiego. W obu moich modułach dzieje się dużo więcej. Ale właśnie chciałbym aby interakcja pomiędzy nimi odbywała się zupełnie gdzieś indziej.

Tylko nie wiem właśnie jak tą informacje przekazać. Mógłbyś zaproponować jakieś rozwiązanie na tym konkretnym przykładzie?

Żeby to było bardziej skomplikowane.. jeśli 2 moduł by tworzył dwa przyciski, jeden miałby zmieniać kolor np na niebieski, a drugi na żółty. Ale chciałbym by pod oba była podpięta ta sama procedura, która najpierw musi sprawdzić który przycisk został wciśnięty, a później wiedzieć do którego komponentu musi się odnieść.

4

dwa przyciski, jeden miałby zmieniać kolor np na niebieski, a drugi na żółty. Ale chciałbym by pod oba była podpięta ta sama procedura, która najpierw musi sprawdzić który przycisk został wciśnięty, a później wiedzieć do którego komponentu musi się odnieść

No to robisz coś w stylu

procedure TForm2.ChangeColor(NewColorL TColor);

a następnie dla przycisku pierwszego dajesz w obsłudze wciśnięcia coś w stylu

procedure Button1Click(Sender: TObject);
begin
   Form2.ChangeColor(clBlue);
end;  

a dla drugiego

procedure Button2Click(Sender: TObject);
begin
   Form2.ChangeColor(clYellow);
end;  

Zauważ, żę tak naprawdę "kontrolera" nie obchodzi, który przycisk został wciśnięty. On jedynie dostaje informację, że ma wykonać określoną czynność. Tak samo jak przycisku - wcale nie interesuje, co konkretnie ma się wydarzyć. On jedynie przekazuje dalej informację, że coś ma zostać wykonane (w tym przypadku - że jakiś kolor ma być zmieniony na żółty), ale nie ma pojęcia ani co za element ma być pokolorowany, ani tym bardziej w jaki sposób ma to się stać.

0

Zauważ, żę tak naprawdę "kontrolera" nie obchodzi, który przycisk został wciśnięty. On jedynie dostaje informację, że ma wykonać określoną czynność. Tak samo jak przycisku - wcale nie interesuje, co konkretnie ma się wydarzyć. On jedynie przekazuje dalej informację, że coś ma zostać wykonane (w tym przypadku - że jakiś kolor ma być zmieniony na żółty), ale nie ma pojęcia ani co za element ma być pokolorowany, ani tym bardziej w jaki sposób ma to się stać.

Przy czym to już nie do końca jest to co bym chciał, bo właśnie chciałbym jedną procedurę, która by obsługiwała wszystkie przyciski. Więc ona musiałaby rozróżniać, który przycisk został wciśnięty rzeczywiście

A druga kwestia Form2.ChangeColor(clYellow); tego nie mogę wstawić do innego modułu, bo on nie widzi Form2. Chyba, że do się to jakoś banalnie obejść, ewentualnie coś źle rozumiem.

3

Może być jedna procedura do obsług OnClick wszystkich przycisków a o, który przycisk chodzi wskaże parametr Sender czyli np. tak:
Można nadać przyciskom odpowiednie wartości właściwości Tag (w tym wypadku 1 i 2). Preferuję rozpoznawanie elementu po Tag niż np. po Name ale można by i po Name tylko kod byłby inny.
i to samo zrobić na kilka sposobów (oczywiście nie podaję wszystkich możliwych):
1)

procedure TForm1.Button1Click(Sender: TObject);
const
  COLORS: array [1..2] of TColor = (clBlue, clYellow);
var
  Button: TButton absolute Sender;
begin
  Unit2.ChangeColor(COLORS[Button.Tag]);
end;
procedure TForm1.Button1Click(Sender: TObject);
const
  COLORS: array [1..2] of TColor = (clBlue, clYellow);
begin
  Unit2.ChangeColor(COLORS[(Sender as TButton).Tag]);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if (Sender as TButton).Tag = 1 then //przy większej liczbie buttonów zamiast drabinki if elseif lepiej zastosować case
    Unit2.ChangeColor(clBlue)
  else
    Unit2.ChangeColor(clYellow)
end;
0

Czyli dochodzimy do tego, że i tak wszystkim komponentom muszę nadać atrybut identyfikujący, prawda?

1

Może by się dało jakoś wykorzystać TAction.OnUpdate ?

0
lucasp17 napisał(a):

Czyli dochodzimy do tego, że i tak wszystkim komponentom muszę nadać atrybut identyfikujący, prawda?

Niekoniecznie. Jeśli dane zdarzenie podepniesz pod wszystkie przyciski to referencję danego (np. klikniętego) przycisku otrzymasz w parametrze Sender, o którym wspomniał już @kAzek. Nie upraszczaj problemu, a po prostu napisz co docelowo chcesz zaimplementować – dobierze się odpowiednie rozwiązanie.

0

W skrócie to jest właśnie to co napisałem. Załóżmy że mam kilka przycisków w module A. Każdy z nich ma wykonywać inne operacje, ale załóżmy że ma zmieniać Panel na inny kolor.

Być może moje podejście jest niewłaściwe i dla każdego przycisku powinno się tworzyć osobną procedurę OnClick, natomiast przyciski tworzę dynamicznie w tablicy i stworzenie jednej procedury daje mi gwarancję, że tylko ją będę musiał modyfikować.

Po kliknięciu przycisku jest Sender, to fakt. Ale tak czy siak procedura musi rozróżnić, czy Sender to jest przycisk 1 czy przycisk 2. Porównać je może poprzez Tag. Czyli tak czy siak jakoś muszą być identyfikowane. Nie korzystałem z Tag, nie czytałem jeszcze o tym, zrobię to, ale zakładać że jest to właściwość, którą trzeba nadać przyciskom, by później je rozróżnić.

1

Właśnie dlatego chcemy abyś przedstawił konkretny problem to na tej podstawie dobierze się rozwiązanie. Twoje założenie ze zmianą koloru to banalny przykład a nie problem ustaw każdemu przyciskowi właściwość Tag na konkretny kolor (chyba wygodniej to w kodzie) i po kliknięciu odczytujesz z tej właściwości kolor żadne cuda...

Button1.Tag:= clRed;
Button1.OnClick:= Button1Click; //jeżeli Lazarus to @ przed nazwą procedury
//lub
Button1.Tag:= RGB(255, 0, 0);
Button1.OnClick:= Button1Click; //jeżeli Lazarus to @ przed nazwą procedury
procedure TForm1.Button1Click(Sender: TObject);
begin
   Unit2.ChangeColor(TButton(Sender).Tag);
end;

No właśnie to zacznij korzystać z Tag czasem się przydaje.

1
lucasp17 napisał(a):

W skrócie to jest właśnie to co napisałem. Załóżmy że mam kilka przycisków w module A. Każdy z nich ma wykonywać inne operacje, ale załóżmy że ma zmieniać Panel na inny kolor.

Niby różne operacje, ale to to samo – zmiana koloru tego samego komponentu.

Być może moje podejście jest niewłaściwe i dla każdego przycisku powinno się tworzyć osobną procedurę OnClick, natomiast przyciski tworzę dynamicznie w tablicy i stworzenie jednej procedury daje mi gwarancję, że tylko ją będę musiał modyfikować.

Zdarzenia tworzy się po coś i jeśli się wie po co się je tworzy, to się wie czy ma być jedno, czy wiele. Jeśli zdarzenia mają wykonywać takie same czynności, to tworzy się jedno i podpina pod zadane komponenty. Wiele dedykowanych zdarzeń tworzy się wtedy, gdy wykonywane w nich operacje są zupełnie różne i nie da się łatwo napisać kodu uniwersalnego.

Ale tak czy siak procedura musi rozróżnić, czy Sender to jest przycisk 1 czy przycisk 2.

To zależy od przeznaczenia. Sender to nic innego jak referencja komponentu wywołującego zdarzenie (zdegradowana do bazowego typu obiektów, więc trzeba skorzystać z rzutowania lub absolute, aby dobrać się do konkretnych składowych). Natomiast właściwość Tag umożliwia dodanie własnych informacji do komponentu, np. jakiejś liczby (lub wskaźnika/referencji), tak aby można je było odczytać z Sendera – to bardzo przydatne.

Nie korzystałem z Tag, nie czytałem jeszcze o tym, zrobię to, ale zakładać że jest to właściwość, którą trzeba nadać przyciskom, by później je rozróżnić.

To zwykła właściwość liczbowa, więc używa się jej tak samo jak innych właściwości liczbowych – nic szczególnego. No może tylko tyle, że nie jest ona komponentowi potrzebna do życia – nie zmienia ustawień kontrolki, a jej modyfikacja nie odpala żadnych zdarzeń. Służy jedynie programiście, do zastosowań dodatkowych.

@kAzek pokazał w jaki sposób wrzucić kolory do tagów kontrolek i jak z tych tagów później skorzystać. Przy czym przypisanie metody do właściwości zdarzeniowej w domyślnym trybie OBJFPC musi być poprzedzone operatorem @. Zresztą jeśli nie wiesz jaka jest różnica pomiędzy tymi trybami i po co się je zmienia to ich nie zmieniaj – korzystaj z domyślnego.

1

Moduły i klasy powinny jak najmniej "wiedzieć" o innych modułach i klasach , to podstawa dobrego i rozprzężonego kodu. Dobrym rozwiązaniem są interfejsy

1
lucasp17 napisał(a):

W skrócie to jest właśnie to co napisałem. Załóżmy że mam kilka przycisków w module A. Każdy z nich ma wykonywać inne operacje, ale załóżmy że ma zmieniać Panel na inny kolor.

to każdy ma wykonywać inną rzecz czy wykonywać to samo na innym panelu?

0

Z dotychczasowych opisów wygląda na to, że przycisków jest wiele, a panel jeden. Przy czym prawdziwy problem nie jest znany, bo OP posłużył się uproszczeniem. Dlatego by pasowało poznać prawdziwy problem, bo może się okazać, że trzeba będzie zupełnie inaczej podejść do jego rozwiązania.

0

Przycisków wiele, Panel jeden. Kliknięcie przycisku ma powodować kilka tych samym działań, plus kilka kolejnych w zależności od wciśniętego przycisku. Dlatego chciałem to zrobić w jednej procedurze, by w razie zmian edytować tylko to jedną, ale może właściwsze jest wykonania dodatkowej procedury, która ma wykonywać te same działania po kliknięciu każdego przycisku i wtedy tak czy siak w razie poprawy będę poprawiał tylko w jednym miejscu.

Nie będę nigdy programistą i mój kod wygląda na pewno przeciętnie, wiele rzeczy da się zrobić lepiej. Mi przychodzi do głowy jakiś pomysł i od razi staram się go wprowadzić. Mi to wystarcza, jeśli coś działa tak jakbym chciał.

1

Możesz skorzystać z jednego zdarzenia podłączonego pod wszystkie przyciski. Każdemu z nich nadaj inny Tag, na podstawie którego będziesz te przyciski identyfikował – czyli liczby od 0 po kolei aż do n-1. Jeśli każdy przycisk ma przede wszystkim ustalać nowy kolor dla panelu, to zadeklaruj sobie stałą macierz kolorów i tagu przycisku (pobranego ze zrzutowanego Sendera) użyj jako indeksu. Pozostałe operacje wykonaj w następnej kolejności.

Czyli zakładając że masz pięć przycisków z nadanymi właściwościami Tag od 0 do 4 i każdy z nich posiada to samo przypisane zdarzenie, zawartość zdarzenia OnClick może wyglądać tak:

procedure TForm1.Button1Click(Sender: TObject);
const
  PANEL_COLORS: array [0 .. 4] of TColor = (clRed, clGreen, clBlue, clWhite, clBlack);
var
  Button: TButton absolute Sender;
begin
  // nadanie koloru panelowi
  Panel1.Color := PANEL_COLORS[Button.Tag];

  // dodatkowe operacje w zależności od klikniętego przycisku
  case Button.Tag of
    2: DoSomething();
    4: DoSomethingElse();
  end;
end;

Oczywiście to tylko uproszczenie, ale idea powinna być widoczna.

0

Tak, jak najbardziej. Wszystko wydaje mi się być już jasne.

Teraz tylko pytanie nieco inne, odnośnie tag. Dwa przyciski mogą mieć przypisaną taką samą wartość tego atrybutu? Chodzi mi o dwa zupełnie inne przyciski, bo tak czy siak zdarzenie onclick jest podpięte pod konkretne przyciski. Więc jeśli Jakiś inny będzie miał przypisany taki sam tag, to nic się nie wydarzy, bo i tak nie ma tego zdarzenia podpiętego pod siebie. Zupełnie dwie różne tablice przycisków mogą być identyfikowane tymi samymi numerami? Nie musi to być jakiś wyróżniający numer jeden jedyny w całym programie?

1

TAG to dowolna wartość typu INT albo WORD, typu wartości nie jestem pewien a nie mam jak sprawdzić, zresztą to mało ważne.
Same wartości mogą być zupełni dowolne i nie muszą być unikatami

2

Dokładnie to Longint w starszych Delphi a w nowych NativeInt
http://docwiki.embarcadero.com/Libraries/Rio/en/System.Classes.TComponent.Tag
ale można tam przechowywać cokolwiek może to być np. wskaźnik na obiekt lub rekord.

2

Tag to zwykły Integer PtrInt, a więc LongInt dla 32-bitowych procesorów lub Int64 dla procesorów 64-bitowych. Przypominam że wątek dotyczy Lazarusa, nie Delphi.

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