OnMouseEnter i OnMouseLeave

0

Witam

Próbuję zbudować do własnego użytku wstążkę (RibbonUI) pozbawioną przełączania do innych zakładek (wstążkę bez zakładek). Problem mam z zachowaniem animacji, gdy kursor wejdzie w obszar sekcji "OnMouseEnter" i wyjdzie z tego obszaru "OnMouseLeave".

Na poniższym obrazie, zielona kropka to kursor. Sekcje to TImage, położone na TPanel.

user image

  1. Kursor jest poza obszarem sekcji
  2. Kursor jest w obszarze sekcji (w tym momencie sekcja zostaje odmalowana o czerwony pasek)
  3. Kursor nadal jest w obszarze sekcji, ale wszedł w przycisk leżący na tej sekcji i nastąpiło przywrócenie domyślnego koloru sekcji, a powinno być nadal jak w pkt. 2

Jak to rozwiązać ?

Teoretycznie istnieje możliwość obsłużenia OnMouseEnter i OnMouserLeave przycisku i odmalowywania sekcji, ale jeśli takich przycisków będzie kilkanaście to nie widzi mi się takie rozwiązanie do każdego przycisku podpinać odmalowywanie to raz. Dwa to zbyt dużo wywołanych odmalowań.

0

Zgaduję, że znalazłeś rozwiązanie? Kiedyś zacząłem robić tego typu komponent i wykorzystałem regiony, ich kombinacje i sprawdzanie czy mysz znajduje się w regionie. Takie rozwiązanie pozwalało na określenie obszaru "podświetlenia" niezależnie od tego co jest w środku. Chociaż bardziej się to nadaje do wykorzystania jako rozwiązanie wbudowane w komponent niż obsługę zbioru luźnych komponentów.

0

Rozwiązałem to tak jak napisałem na początku.
Kiedy kursor wchodzi w przycisk leżący na sekcji, następuje odmalowanie o czerwony pasek.

Problem w tym, że jest to złe rozwiązanie, ponieważ malowanie jest niepotrzebnie zwielokrotnione. W momencie wejścia kursora na przycisk schodzi się z sekcji i tu są dwa odmalowywania, a nie powinno być wcale.

0

nie wiem czy dobrze madmike'a zrozumialem ale ja bym wlasnie tak zrobil:
w onMouseLeave sekcji sprawdzal czy kursor wciaz znajduje sie w rejonie sekcji - jesli nie to usun czerwony pasek. jesli sekcja jest prostokatna to banal.

0

Jeśli jestem kursorem na przycisku, a pod nim jest tylko nie wielki prześwit sekcji (widać to na obrazku dla lewej i górnej strony przycisku - mały prześwit rzędu 3px), to schodząc z przycisku kursorem w lewo (możliwie szybko), odmalowanie nie zostanie wykonane. Ale jeśli w OnMouseLeave jest zastosowane dla przycisku, to odmaluje poprawnie - problem w tym, że zbędne jest stosowane OnMouseEnter i OnMouseLeave dla przycisku. Zachowanie to powinno odnosić się tylko do sekcji.
Po prostu w momencie wjechania kursora w przycisk (OnMouseEnter), traci się kontrolę nad OnMouseLeave sekcji.

0

Moim zdaniem powinieneś przeprogramować ten komponent. W MouseMove prawdopodobnie jest sprawdzanie, czy kursor jest na przycisku, czy nie. Ewentualnie w MouseEnter przycisku jest wysłanie jakiegoś komunikatu do komponentu głównego. Popatrz na to, stwórz komponent pochodny i przeładuj odpowiednie procedury, lub pozmieniaj je w klasie bazowej(chociaż tego nie polecam).

1

zauwazylem jedna rzecz, nie wiedzialem o tym wczesniej...:

komponent TPanel a na nim (w nim) rozne komponenty...
w momencie gdy kursor znajduje sie na panelu i przesune go nad jakis komponent to zosaje wywolane (dla penelu) onLeave i naychmiast OnEnter.
poniewaz dzieje sie to szybko to TPanel np nie zdazy zmienic koloru. efekt jest taki ze czy najade na inny komponent czy nie to panel zawsze jest podswietlony.

ale jesli ktorykolwiek z komponentow na TPanel rowniez wykorzystuje Enter/Leave to sytuacja sie zmienia.
bo teraz bo najechaniu na jakis komponent, panel otrzymuje tylko OnLeave.

troche kodu:
na formie TPanel i TMemo. na Panelu dodatkowo TButton i jeszcze jakis inny komponent, np drugie TMemo:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;



type
  TPanel = class(ExtCtrls.TPanel)
     procedure MouseEnter(var Msg: TMessage); message CM_MouseEnter;
     procedure MouseLeave(var Msg: TMessage); message CM_MouseLeave;
end;

type
  TButton = class(StdCtrls.TButton)
     procedure MouseEnter(var Msg: TMessage); message CM_MouseEnter;
     procedure MouseLeave(var Msg: TMessage); message CM_MouseLeave;
end;




type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Memo1: TMemo;
    Memo2: TMemo;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TPanel }

procedure TPanel.MouseEnter(var Msg: TMessage);
begin
  self.Color:=clHighlight;
  form1.memo1.lines.add('enter');
end;

procedure TPanel.MouseLeave(var Msg: TMessage);
begin
  self.Color:=clBtnFace;
  form1.memo1.lines.add('leave');
end;



{ TButton }
procedure TButton.MouseEnter(var Msg: TMessage);
begin
  self.caption:='enter'
end;

procedure TButton.MouseLeave(var Msg: TMessage);
begin
  self.caption:='leave'
end;

end.
0

Cimak, dzięki - twoje rozwiązanie działa :)

0

Jednak ten przykład działa, jeśli zastosuje to na TPanel (to co podałeś MouseEnter i MouseLeave tylko dla TPanel).

Teraz sprawdziłem na docelowym przykładzie i niestety nadal to samo...
U mnie jest to rozwiązane tak (kropka zielona to komponent):

  1. TPanel odmalowywany o gradient (tu nic nie dzieje się w momencie wejścia/wyjścia kursora)
  2. Na TPanel położony jest TImage. Gdybym odmalowywał to na TPanelu, to wiadomo jak tu jest z Canvasem (np.: po zminimalizowaniu okna odmalowanie utraci się), dlatego zastosowany TImage i to na nim maluję gradient i dochodzi do wywołania OnMouseLeave, OnMouseEnter
  3. To przycisk, zakładamy, że to TSpeedButton

user image

Gdy kursor znajdzie się nad TImage (2), następuje odmalowanie o czerwony pasek. Gdy kursor wjedzie na przycisk (3), TImage traci odmalowanie, bo wywoływane jest OnMouseLeave.

Zastosowałem to co podałeś z przeróbką, czyli:

type
 TImage = class(ExtCtrls.TImage)
     procedure MouseEnter(var Msg: TMessage); message CM_MouseEnter;
     procedure MouseLeave(var Msg: TMessage); message CM_MouseLeave;
end;

procedure TImage.MouseEnter;
begin
 OdmalujGrafikeWstazki(Self);
end;

procedure TImage.MouseLeave;
begin
 OdmalujGrafikeWstazki(Self, True); //parametr oznacza usunięcie czerwonego paska
end;

Jak chcesz, mogę Ci wysłać DEMO.

0

ustawilem komponenty tak jak u Ciebie (ez speedbuttonem jakos przyciskiem) i faktycznie nie dziala...
ale jesli podepniesz te procedury do TPanel zamiast TImage to zacznie dzialac:

type
 TPanel = class(ExtCtrls.TPanel)
     procedure MouseEnter(var Msg: TMessage); message CM_MouseEnter;
     procedure MouseLeave(var Msg: TMessage); message CM_MouseLeave;
end;
0
Opi napisał(a)
  1. Na TPanel położony jest TImage. Gdybym odmalowywał to na TPanelu, to wiadomo jak tu jest z Canvasem (np.: po zminimalizowaniu okna odmalowanie utraci się), dlatego zastosowany TImage i to na nim maluję gradient i dochodzi do wywołania OnMouseLeave, OnMouseEnter

Nie wiem Opi czy jakoś to Tobie pomoże, bo chyba zdarzenia OnMouseLeave da się dopisać zawsze
do komponentu, ale jeżeli nie chcesz tracić tego co jest narysowane na Canvasie panelu i nie kłaść
na niego TImage, o ile to by w czymś Tobie pomogło. To może kiedy trzeba zapamiętuj w zmiennej
typu TBitmap co ma być na malowane, przechwytuj komunikat WM_PAINT i maluj to na nowo, jak
w kodzie poniżej. Mi takie rozwiązanie wystarczyło w TScrollBox, który wzbogaciłem o Canvas, ale
powiem szczerze, że nie wiem jak rozwiązać Twój głowny problem, lecz może i to, Ci coś pomoże.

unit canvasedscrollbox;

interface

uses
  Windows, Messages, Classes, Controls, Forms, Graphics;

type
  TCanvasedScrollBox = class(TScrollBox)
  private
    FBmp : TBitmap;
    FCanvas : TCanvas;
    procedure SetBitmap(Value : TBitmap);
  protected
    procedure WMPaint(var Message : TMessage); message WM_PAINT;
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
    property Bitmap : TBitmap read FBmp write SetBitmap;
    property Canvas : TCanvas read FCanvas;
  end;

implementation

constructor TCanvasedScrollBox.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := [csAcceptsControls, csCaptureMouse, csClickEvents,
    csSetCaption, csDoubleClicks];
  Width := 185;
  Height := 41;
  BorderStyle := bsSingle;
  FBmp := TBitmap.Create;
  FCanvas := TControlCanvas.Create;
  TControlCanvas(FCanvas).Control := Self;
end;

destructor TCanvasedScrollBox.Destroy;
begin
  FBmp.Free;
  FCanvas.Free;
  inherited Destroy;
end;

procedure TCanvasedScrollBox.SetBitmap(Value : TBitmap);
begin
  FBmp.Assign(Value);
  SendMessage(Handle, WM_PAINT, 0, 0);
end;

procedure TCanvasedScrollBox.WMPaint(var Message : TMessage);
begin
  if Assigned(FBmp) then
    Canvas.Draw(0, 0, FBmp);
  inherited;
end;

end.

EDIT: no a właśnie cimak napisal, że do panelu mozna dopisać zdarzenia obslugi "wchodzenia" i
"opuszczania" myszki na obszarze Panelu, a skoro z Panelem wszystko działa dobrze, a jedynym
minusem było to, że Canvas jest tracony po wywołaniu WM_PAINT to może skorzystaj z kodu w
stylu powyższego i uzyskasz wtedy idealne dla siebie rozwiązanie, które zadziala tak, jak chcesz.
Czyli, o ile to możliwe, rysujesz po Canvasie Bitmapy dodanej jako własnośc dla TPanel i później
przy wywolaniu WM_PAINT ta Bitmapa pozwala pokazać zawartośc na komponencie. Nic innego,
nie przychodzi mi do głowy, a może jak połaczysz wskazówki cimaka i moją to całość będzie ok.

0

Tak jak Olesio napisał, mam rozwiązane dla TPanel (obraz nr 1), gdzie stanowi on podstawę pod sekcje.

type
  TPanel = class(ExtCtrls.TPanel)
   protected
     FOnPaint : TNotifyEvent;
     procedure Paint; override;
    public
     property Canvas;
     property OnPaint : TNotifyEvent read FOnPaint write FOnPaint;
  end;


  public
    procedure P_RibbonLitePaint(Sender: TObject);


procedure TPanel.Paint;
begin
 inherited;

 if Assigned(FOnPaint) then FOnPaint(Self);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 P_RibbonLite.DoubleBuffered := True;
 P_RibbonLite.OnPaint        := P_RibbonLitePaint;
 Form1.DoubleBuffered        := True;
end;

procedure TForm1.P_RibbonLitePaint(Sender: TObject);
begin
 //tu następuje namalowanie gradientu dla TPanel (obraz nr 1)
 //OdmalujGrafikeWstazki(Sender, True);
end;

Z tymże tak jak napisałem, lepiej jest stosować TImage i na jego Canvasie malować, ale skoro przykład z OnMouseLeave nie działa dla TImage to chyba jest jedyne wyjście usunąć z sekcji TImage i malować po TPanel :/

Tylko dlaczego to nie działa na TImage ?

EDIT:
Jest jeszcze inna możliwość. Wywoływać OnMouseLeave dla TImage tylko wtedy, gdy kursor wyjdzie poza obszar tego komponentu.
Rozwiązanie wygląda tak:

procedure TImage.MouseLeave;
var
 P: TPoint;
begin
 P := Self.CalcCursorPos;
 if (P.X < Self.ClientRect.Right) and (P.X > Self.ClientRect.Left) and (P.Y < Self.ClientRect.Bottom) and (P.Y > Self.ClientRect.Top) then Exit;

 Form1.OdmalujGrafikeWstazki(Self, True);
end;

Niby ok, ale gdy kliknie się na przycisk i przytrzyma LPM, następnie zjedzie kursorem poza sekcję - to odmalowanie zostaje.

0

ale Opi... niech sobie image lezy na panelu, to w niczym nie przeszkadza... zrob tak jak napisalem ^^.
jak bedziesz mial te eventy zrobione dla panelu a bedziesz ruszal kursorem nad timage to eventy panelu i tak beda dzialac.

0

No tak, teraz w dobrym kierunku :) Ale do rzeczy...

Jak wiadomo, jest jeden główny TPanel i na nim leżą sekcje TPanel. Na każdej z sekcji TPanel leży TImage.

type
  TPanel = class(ExtCtrls.TPanel)
   protected
     FOnPaint : TNotifyEvent;
     procedure Paint; override;
    public
     property Canvas;
     property OnPaint : TNotifyEvent read FOnPaint write FOnPaint;
     procedure MouseEnter(var Msg: TMessage); message CM_MouseEnter;
     procedure MouseLeave(var Msg: TMessage); message CM_MouseLeave;
  end;

MouseEnter i MouseLeave odpowiada za reakcje dla TPanel'i sekcji, a pozostałe za odmalowanie gradientu dla głównego TPanel.
Aby odróżnić reakcję na kursor dla głównego TPanel ustaliłem Tag = 99 dla sekcji. W ten sposób:

procedure TPanel.MouseEnter;
begin
 if Self.Tag = 99 then Form1.OdmalujGrafikeWstazki(Self.Controls[0]);
end;

procedure TPanel.MouseLeave;
begin
 if Self.Tag = 99 then Form1.OdmalujGrafikeWstazki(Self.Controls[0], True);
end;

Odmalowanie odbywa się tylko dla TPanel'i sekcji.
OK, do tego momentu jest dobrze. Teraz muszę się odwołać do TImage, które leży na sekcji. Jak widać zastosowałem odwołanie do pierwszej kontrolki znajdującej się na sekcji TPanel

Self.Controls[0]

Ale jest to trochę niebezpieczne rozwiązanie. Teoretycznie można jeszcze sprawdzić, czy kontrolka należy do TImage, ale może Cimak/Olesio macie jakiś pomysł, jak odwołać się do tego TImage.

Dodam, że w obecnej formie, już działa jak należy. A więc:

  • pod przyciski nie trzeba podpinać odmalowywania dla OnMouseEnter i OnMouseLeave,
  • gdy kliknę w przycisk, przytrzymam LPM i zjadę z sekcji, odmalowanie działa prawidłowo, a więc MouseLeave dla TPanel'u sekcji odbywa się,
  • nie wykorzystuję TBitmap dla TPanel, więc całość odbywa się na TImage,
  • nawet szybkie przechodzenie kursorem między sekcjami (lub uciekanie kursorem z sekcji) odmalowuje prawidłowo.
0

w zasadzie to moje z TPanel to byl tkai ogolny przyklad. w takich przypadkach powinienes uzyc innej, wlasnej klasy, zamiast nadpisywac isniejace...
powinienes zrobic cos w stylu:

type
 TSekcja = class(ExtCtrls.TPanel)
     procedure MouseEnter(var Msg: TMessage); message CM_MouseEnter;
     procedure MouseLeave(var Msg: TMessage); message CM_MouseLeave;
end;

i np kolejna, TRibbon, czyli ten gowny panel na ktorym leza sekcje.

tylko wtedy musisz je tworzyc dynamicznie ale mam nazieje ze docelowo tak chcales zrobic? ;>
albo po prostu napisz w oparciu o to zestaw komponentow.

0

To jest rozwiązanie jeśli chodzi o odróżnienie TPanel'i ze wstążki od innych TPanel'i. Ale tu rozwiązałem sposób z Tag.
Natomiast pozostaje odwołanie do TImage. Czy poprzez Self.Controls[0] to jedyne wyjście ?

Dynamicznie to wszystko tworzyć to bym się chyba pogubił :P

0

sposob z tag jest taki "troche" prowizoryczny...
a jesli chodzi o timage.. hmm... tworzac dynamicznie tez by sie dalo to ladniej rozwiazac, bo panel mialby przypisany swoj wlasny timage i nie byloby problemu:
w onEnter/Leave pisalbys tylko: self.image...
jesli Ci wystarcza prowioryczne rozwiazania to mozesz przy uzyciu controls[0] tylko najpierw sprawdzaj czy controls[0] to na pewno TImage a nie np button.

a czy bys sie pogubil? mysle ze nie. moze troche trudniej, bo nie widac tego w czasie tworzenia formy i musisz napisac wiecej rzeczy z palca.
ale tworzac dynamicznie to sie staje bardzo bardzo elastyczne i wprowadzenie jakiejs zmiany, dodanie czegos, zajmuje Ci moment.
a tak musisz kombinowac i krecic. uwierz mi, ze jak raz zrobisz cos takiego dynamicznie to Ci sie spodoba :D

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