Wspólne zdarzenia dla wielu komponentów

0

Wróciłem do mojego głównego projektu i teraz próbuje trochę poprawić ten kod. Mam menu główne, które składa się z 6 przycisków. Wszystkie znajdują się na jednym głównym panelu. Natomiast same przyciski składają się z 3 elementów. Mniej więcej wygląda to tak:
-PanelPrzyciskTlo
--PanelPrzyciskWnetrze
---LabelPrzyciskNapis
Oczywiście, każdy z tych przycisków jest oznaczony jeszcze słowem oznaczającym daną opcję. Każdy przycisk ma 3 zdarzenia. onClick, onMouseEnter i onMouseLeave. Mój problem dotyczy tych dwóch ostatnich zdarzeń. Cel jest taki, aby po najechaniu na przycisk wszystkie trzy komponenty zmieniały swój kolor. Udało mi się to osiągnąć. Jednak musiałem stworzyć dla każdego PanelPrzyciskTlo oddzielne zdarzenia i później wstawić je też dla tych podrzędnych komponentów. A więc kod wyglądał tak:

procedure TProjekt.PanelPrzyciskTloMouseLeave(Sender: TObject);
begin
  PanelPrzyciskTlo.Color := KolorTlaOpcjiMenuGlownego;
  PanelPrzyciskWnetrze.Color := KolorOpcjiMenuGlownego;
  LabelPrzyciskNapis.Font.Color := KolorCzcionki;
end;  

To daje łącznie 12 zdarzeń, a jeśli opcji będzie więcej, bo zamierzam dodać jeszcze 2 lub 3, to wtedy dalej to będzie rosło. Chciałbym to zrobić w sposób jaki przedstawił to Olesio w innym moim temacie.

//...
var
  Edt : TEdit;
begin
  Edt := TEdit(Sender);
  if (Edt <> nil) and (Length(Edt.Text) = 1) then
      SelectNext(Edt, True, True);
end;
 

Jednak tu mam problem, bo zdarzenie musi obejmować trzy komponenty. A jeśli wstawiłbym coś takiego

var
  Panel: TPanel;
begin
  Panel := TPanel(Sender);
  Panel.Color := KolorTlaOpcjiMenuGlownego;
end;
 

to zarówno po najechaniu na tło i na wnętrze ono zmieniało by kolor na ten sam. Czy jest jakaś mozliwość aby w taki sposób ustawić, że kolor ma zmienić np. Panel który jest bezpośrednio pod lub nad obecnym Panelem?

Mam nadzieję, że zrozumiale wytłumaczyłem o co mi chodzi.

1

A czy koniecznie muszą to być panele z etykietami? Bo jeżeli nie, to o wiele prościej było by zamienić trzy różne komponenty na jeden - TPaintBox; Żeby taki komponent pomalować, wystarczy obsłużyć zdarzenie OnPaint;

A jeśli koniecznie chcesz pozostać przy swoim rozwiązaniu, to zauważ, że klasa TPanel posiada właściwość Components; Za jej pomocą możesz uzyskać dostęp do komponentów zawartych w panelu, mając tylko i wyłącznie referencję do obiektu panelu; A jeśli komponenty są luźno porozrzucane na formie, a nie jeden w drugim, to powinieneś się poważnie zastanowić nad swoim rozwiązaniem.

0

Jutro pokombinuje coś z TPaintBox i pewnie skoro to podpowiadasz to będę w stanie zrobić to o co mi chodzi.

Jednak jako, że w dużym stopniu ten projekt jest w celu nauki, dlatego też chciałbym to rozwiązać tak jak zakładałem początkowo. Komponenty nie są porozrzucane, tylko tak jak próbowałem przedstawić, jeden mieści się w drugim. Problemy w tym momencie mam dwa. Przede wszystkim jak tej właściwości Components użyć, aby dostać się do właściwości podrzędnego komponentu.

Druga sprawa. Ten pierwszy komponent miałby być tylko tłem, a więc jest on dość wąski. Po najechaniu na niego i użyciu właściwości o której wspominałeś zapewne będzie działało wszystko prawidłowo. Natomiast po zjechaniu na ten wewnętrzny panel zostanie to zinterpretowane jako MouseLeave tego Panelu będącego tłem.

1

A jednak źle Ci podałem wcześniej - należy skorzystać z właściwości Controls, a nie Components; Ta druga jest pusta, nawet jeśli komponenty osadzimy w sobie; Pewnie trzeba by było nadać Parenta komponentom osadzonym, ale nie sprawdzałem; W każdym razie trzeba użyć Controls;

Jeżeli na głównym panelu jest kolejny panel, a w tym kolejnym jest etykieta, to wystarczy tyle:

var
  lblRead: TLabel;
begin
  lblRead := ((Sender as TPanel).Controls[0] as TPanel).Controls[0] as TLabel;

Dzięki temu zmienna lblRead będzie posiadać wskazanie na obiekt etykiety; Ale tak jak napisałem Ci wcześniej - zastąp trzy komponenty jednym; A że po TPaintBox maluje się banalnie, to będzie to dobre rozwiązanie dla Ciebie.

1
furious programming napisał(a):

... Ale tak jak napisałem Ci wcześniej - zastąp trzy komponenty jednym; A że po TPaintBox maluje się banalnie, to będzie to dobre rozwiązanie dla Ciebie.

lub wykorzystaj gotowy komponent: http://wiki.lazarus.freepascal.org/BGRAControls

0

Na razie zdołałem zrobić to tą metodą, którą chciałem od początku, a więc be zmiany ilości i typu komponentów, co bardziej mi odpowiada, bo nie muszę zmieniać dużej części kodu. Działa wszystko tak jak bym chciał. To samo zdarzenie ustawiam dla wszystkich komponentów przycisku. Jednak chciałbym zapytać czy taki kod jest poprawny.

procedure TProjekt.PrzyciskMouseEnter(Sender: TObject);  
begin
  if (TObject(Sender).ClassType = TPanel) and (TPanel(Sender).Parent <> PanelMenuGlowne) then
    Sender := TPanel(Sender).Parent;
  if TObject(Sender).ClassType = TLabel then
    Sender := TPanel(Sender).Parent.Parent;
  TPanel(Sender).Color := KolorTlaOpcjiMenuGlownegoMEnter;
  TPanel(Sender).Controls[0].Color := KolorOpcjiMenuGlownegoMEnter;
end;
 
1

Niepotrzebnie rzutujesz Sender na TObject - przecież on jest już co najmniej TObject; Sprawdź, czy przy szybkim ruchu kursora, wszystko jest obsługiwane poprawnie; A z tym z reguły są problemy, bo czasem komunikat nie zostanie wysłany (zbyt szybko ruszono kursorem) i interfejs zaczyna wariować - źle się przemalowuje;

Rozwiązaniem może być wprowadzenie time-outu, ale to trochę bardziej skomplikowane.

0

Faktycznie kiedyś miałem z tym problem, jak zaczynałem od programu działającego w oknie. Wtedy po wyjeździe za okno kursorem bardzo szybko nie wszystko działało prawidłowo. Tutaj program działa na pełnym ekranie i wszystko działa tak jak bym chciał. Ewentualnie jeżeli przejadę za szybko, to po prostu zdarzenie nie zadziała i nie zmieni koloru, ale nie uważam by to było błędem.

Na razie postanowiłem zrobić tak jak ktoś na forum mi doradzał. Najpierw skupie się na funkcjonalności, a dopiero później z czasem będę dodawał jakieś "bajery" i starał się dodać ciekawy interfejs. Więc w tym momencie to rozwiązanie by mnie zadowalało, jeśli nie byłoby błędów w kodzie.

furious programming napisał(a):

Niepotrzebnie rzutujesz Sender na TObject - przecież on jest już co najmniej TObject

Tak na prawdę, nigdy wcześniej nie robiłem takich rzeczy. Nie używałem właściwości Controls czy Parents. Dopiero kilka dni temu zrozumiałem, że jeśli takie samo zdarzenie jest dla kilku komponentów, to nie trzeba zmieniać ich bezpośrednio, tylko można to zrobić za pomocą Sender. Nie wiem być może dało się to zrobić inaczej. Jak pewnie z kodu się możecie domyślić chodziło o rozpoznanie na który komponent najechano myszką. W obecnej chwili to był mój jedyny pomysł jak to mogę rozwiązać. Chociaż jeszcze 24 godziny temu nie przypuszczałem, że jestem w stanie coś takiego sam wymyślić, bo o ile uważam, że mam do tego głowę, to jeszcze dosyć małą wiedzę. Mimo wszystko sam jestem zadowolony z rezultatu. Tylko pytanie czy może tak ten kod zostać?

1

@dani17: jeżeli dla Ciebie efekt jest ok, to może być. Tylko można ciut uprościć. Na pewno bez sensu jest rzutować TObject. Chyba bazowałeś na moim przykładzie z Editem, ale go do końca nie zrozumiałeś. Po to tam było TEdit(Sender) żeby móc się odwoływac do własności tego typu. Przykładowo możemy mieć taki kod w OnChange dla jakiegoś/jakichś komponentu/komponentów TEdit:

procedure TForm1.Edit1Change(Sender : TObject);
begin
  Caption := TEdit(Sender).Text;
end;

Bez tego TEdit kompilator nie pozwalał by nam odwołać się do własności Text. A klasa TObject jest jakby bazowa. I ona sama w sobie nie posiada takiej własności jak Text. W zasadzie większośc komponentów jednak po niej dziedziczy bo są obiektami. Tutaj musiał byś poczytać już więcej o podstawach programowania obiektowego, właśnie dziedziczenia, a takżźe rzutowaniu itd.

1

Ewentualnie jeżeli przejadę za szybko, to po prostu zdarzenie nie zadziała i nie zmieni koloru, ale nie uważam by to było błędem.

To nie wszystko - czasem zdaża się, że komponent zarejestruje komunikat CM_MOUSEENTER, ale przy szubkim zabraniu kursora znad komponentu, komunikat CM_MOUSELEAVE już nie dotrze; Efektem tego będzie brak przemalowania komponentu, więc ustawiony kolor dla hovera pozostanie aż do momentu, w którym znów najedziesz kursorem na niego;

Tylko pytanie czy może tak ten kod zostać?

No nie wiem - wytłumacz mi, w jaki sposób ma działać poniższy kod, i w ogóle do czego ma służyć:

if TObject(Sender).ClassType = TLabel then
  Sender := TPanel(Sender).Parent.Parent;

Dlaczego jeśli Sender jest etykietą, rzutujesz go na klasę panelu? To nie ma prawa działać, ale Ty pewnie nie podpiąłeś tego zdarzenia pod etykiety, więc nie wyłapałeś tego błędu.

0

Zdarzenie to jest ustawione i dla obu Paneli i dla Labela. Chodzi o to żeby nawet po najechaniu na Label program działał tak jakby było najechane na Panel1. W ten sam sposób jeśli najadę na Panel2 to też zostaje to zmienione na Panel1. Później w łatwy sposób mogę zmienić kolor Panel1 i Panel2, bo jako ten główny obiekt zawsze jest Panel1.

1

Ten kod nie powinien w ogóle działać, tzn. spodziewałbym się twardego wyjątku; Głównie dlatego, że jeśli Sender jest klasy TLabel lub z niej dziedziczy, to w żaden sposób nie można go wykorzystać jako TPanel, bo te klasy kompletnie nic nie łączy; Ani na wysokim poziomie, ani na niższym, bo TLabel dziedziczy z klasy TGraphicControl, a TPanel z TWinControl;

Jeśli już chcesz używać tego kodu dla różnych komponentów, to rzutuj na właściwe klasy:

procedure TProjekt.PrzyciskMouseEnter(Sender: TObject);  
begin
  if (Sender is TPanel) and (TPanel(Sender).Parent <> PanelMenuGlowne) then
    Sender := TPanel(Sender).Parent
  else
    if (Sender is TLabel) then
      Sender := TLabel(Sender).Parent.Parent;

  // od tego momentu Sender zawsze musi posiadać wskazanie na panel
  // w przeciwnym razie nie można go bezpiecznie rzutować na TPanel
  TPanel(Sender).Color := KolorTlaOpcjiMenuGlownegoMEnter;
  TPanel(Sender).Controls[0].Color := KolorOpcjiMenuGlownegoMEnter;
end;
0
      Sender := TLabel(Sender).Parent.Parent;

Chodzi o ten fragment, prawda? Faktycznie miałem w tym błąd, aczkolwiek wiedziałem o co chodzi. Pewnie wynikało to ze zbyt szybkiego pisania. A że mimo wszystko działało prawidłowo to nie zauważyłem tego.
Dziękuję za pomoc

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