Wizualny przycisk

0

Witam wszystkich, potrzebuję zrobić do programu przycisk wizualny, tzn. coś w rodzaju TPNGButton tylko szybszy. TPNGButton jest strasznie wolny, gdy pod nim jest jakaś grafika. W moim programie tłem formularza jest grafika typu PNG w komponencie TImage i na nim jest umieszczana cała masa różnych komponentów, dlatego zależy mi na tym, żeby był szybki.

Co potrzebuję... komponent, który jest zbudowany na tej samej zasadzie, co TPNGButton, czyli z grafik, które obsługują kanał alfa (np. ico czy png). Musi być także zdarzenie OnMouseEnter i OnMouseLeave, żeby grafika się zmieniała gdy kursor jest nad przyciskiem i gdy opuszcza jego pole. Te dwa zdarzenia muszą być zaprogramowane wewnątrz modułu komponentu (tak żebym nie musiał w programie programować ich, tylko w samym module komponentu). No i to tyle.

Co mam:

unit ImageButton;

interface

uses
  SysUtils,
  Classes,
  Controls,
  ExtCtrls,
  Graphics,
  Messages;

type
  TImageButton = class(TImage)
  private
    FImageNormal: TIcon;
    FImageHot: TIcon;

    procedure SetNormalImage(Icon: TIcon);
    procedure SetHotImage(Icon: TIcon);
  protected
    FMouseOver: TNotifyEvent;
    FMouseOut: TNotifyEvent;

    procedure CMMouseEnter(var Message:TMessage); message cm_MouseEnter;
    procedure CMMouseLeave(var Message:TMessage); message cm_MouseLeave;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  published
    property ImageNormal: TIcon read FImageNormal write SetNormalImage;
    property ImageHot: TIcon read FImageHot write SetHotImage;

    property OnMouseOver: TNotifyEvent read FMouseOver write FMouseOver;
    property OnMouseOut: TNotifyEvent read FMouseOut write FMouseOut;
  end;

procedure Register;

implementation

constructor TImageButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FImageNormal := TIcon.Create();
  FImageHot := TIcon.Create();
end;

destructor TImageButton.Destroy();
begin
  FreeAndNil(FImageNormal);
  FreeAndNil(FImageHot);

  inherited Destroy();
end;

procedure TImageButton.SetNormalImage(Icon: TIcon);
begin
  FImageNormal.Assign(Icon);
end;

procedure TImageButton.SetHotImage(Icon: TIcon);
begin
  FImageHot.Assign(Icon);
end;

procedure TImageButton.CMMouseEnter(var Message: TMessage);
begin
  if Assigned(FMouseOver) then
    OnMouseOver(Self);

  Picture.Assign(FImageHot);
  Message.Result := 1;
end;

procedure TImageButton.CMMouseLeave(var Message: TMessage);
begin
  if Assigned(FMouseOut) then
    OnMouseOut(Self);

  Picture.Assign(FImageNormal);
  Message.Result := 1;
end;

procedure Register;
begin
  RegisterComponents('Samples', [TImageButton]);
end;

end.

Gdy tworzę formularz i kładę na nim komponent (wizualnie), wszystko gra. Jednak gdy przycisk tworzę dynamicznie, podczas zamykania programu wyskakuje Access Violation.

Nie wiem dokładnie dlaczego, w każdym razie przycisk tworzę w OnCreate formularza i usuwam w OnClose:

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);

    procedure btnCloseClk(Sender: TObject);
  public
    btnClose: TImageButton;
  end;

implementation

procedure TMainForm.btnCloseClk(Sender: TObject);
begin
  Close;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  btnClose := TImageButton.Create(MainForm);

  with btnClose do
    begin
      Parent := MainForm;

      Width := 30;
      Height := 62;

      Left := MainForm.ClientWidth - 40;
      Top := 148;

      Cursor := crHandPoint;

      ParentShowHint := False;
      ShowHint := True;
      Hint := 'Zamknij';

      Picture.LoadFromFile('btnClose.ico');

      ImageNormal.LoadFromFile('btnClose.ico');
      ImageHot.LoadFromFile('btnCloseHot.ico');

      OnClick := btnCloseClk;
    end;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  btnClose.Free();
  btnClose := nil;
end;

Nie wiem dokładnie gdzie jest bląd, w każdym razie przy zamykaniu programu wyrzuca bląd.

Działa trochę wolno, może dlatego, że korzystam z grafiki w formacie .ico.

Jeżeli ktoś wie jak to zrobić lepiej, bardzo prosze o pomoc.
Dziękuję z góry, pozdrawiam.

0

wywala blad przy "zamykaniu programu" czy przy "zamykaniu programu po kliknieciu w moj przycisk" ?
dlaczego zwalnianie komponentu dales w OnClose a nie w OnDestroy ?

0
cimak napisał(a)

dlaczego zwalnianie komponentu dales w OnClose a nie w OnDestroy ?

Nie odpowiem Ci na to pytanie... Widocznie AV wyskakuje przy zwalnianiu przycisku z pamięci, bo jak przełożyłem zwalnianie do OnDestroy działa bez zarzutu.

cimak, mówisz, że wszystkie komponenty wizualne należy zwalniać w OnDestroy a nie w OnClose?

No dobrze, jeden problem z głowy. Dziękuję. Jednak na dal nie wiem, jak przyspieszyć działanie przycisku tak, by grafika była szybciej ładowana do komponentu.

Testowałem wykorzystanie PNGObject zamiast TImage, ale nic szybciej nie działa, mam nawet takie wrażenie, że jeszcze wolniej...

0

Nie musisz zwalniać komponentów sami się zwalniają po to istnieje lista komponentów przy formie. Właśnie to jest przyczyną błędu.

0

Po pierwsze używaj grafiki PNG, a nie ICO z którym są same problemy (np.: jeśli ktoś ustawi głębię kolorów na 16bit).

Po drugie, po co dwa razy zwalniasz ?

begin
  btnClose.Free();
  btnClose := nil;
end;

Jeśli już to tak, chociaż do zwalniania wystarczy samo Free:

begin
  btnClose.Picture := nil;
  btnClose.Free;
end;

I po trzecie, także bym spróbował w OnDestroy formy.
Porobiłbym na twoim miejscu parę testów + sprawdź czy nie będziesz miał wycieków pamięci.

Po czwarte, nie zabrakło tutaj inherited ?

procedure TImageButton.CMMouseEnter(var Message: TMessage);
begin
  inherited;

  if Assigned(FMouseOver) then
    OnMouseOver(Self);
 
  Picture.Assign(FImageHot);
  Message.Result := 1;
end;
 
procedure TImageButton.CMMouseLeave(var Message: TMessage);
begin
  inherited;

  if Assigned(FMouseOut) then
    OnMouseOut(Self);
 
  Picture.Assign(FImageNormal);
  Message.Result := 1;
end;
0

TPNGButton jest strasznie wolny
Co to znaczy „wolny”? Może wrzucasz ogromny obrazek do kilkupikselowego przycisku?

0

Przyczyną błędu było zwalnianie komponentów w OnClose, przerzuciłem do OnDestroy i wszystko działa bez zarzutów.

Opi napisał(a)

Po pierwsze używaj grafiki PNG, a nie ICO z którym są same problemy (np.: jeśli ktoś ustawi głębię kolorów na 16bit)

Ikony tworzę sam z głębią 32 bitową. Nie chcę korzystać z ico bo o wiele więcej zajmuje plik niż to samo w png.

Opi napisał(a)

Po drugie, po co dwa razy zwalniasz ?

Test, na wszelki wypadek bo nie wiedziałem co powoduje AV.

Opi napisał(a)

Po czwarte, nie zabrakło tutaj inherited ?

Zabrakło, ale nie zauważyłem, żeby bez tego coś się sypało.

Azarien napisał(a)

Co to znaczy „wolny”? Może wrzucasz ogromny obrazek do kilkupikselowego przycisku?

Wolno zmienia się grafika na nim. Jak widać z kodu w pierwszym poście, rozmiar komponentu wynosi 30x62 pixele, grafika w pliku ma dokładnie taki sam rozmiar. Właściwośc Stretch jest na False, więc dodatkowo nie sprawdza, czy rozciągać grafikę czy nie.

W przykładzie z pierwszego postu używam grafiki formatu ico, żeby sprawdzić efekt. Po napisaniu pierwszego postu przerobiłem kod na obsługę png i nie zauważyłem różnicy, tak że ikony są równie szybko ładowane po najechaniu myszką jak png.

Wyszła akurat taka sytuacja, że muszę wszystkie komponenty tworzyć dynamicznie, czego wcześniej nie robiłem i dlatego mam takie zamieszanie.

_13th_Dragon napisał(a)

Nie musisz zwalniać komponentów sami się zwalniają po to istnieje lista komponentów przy formie.

Wiem wiem, racja, automatycznie się zwalniają, ale ja dzięki temu wiem, jakie stworzyłem. Nie powoduje to błędu, jeśli sam je zwolnię. Wrzuciłem zwalnianie do OnDestroy i wszystko działa bezbłędnie.

Przerobię po raz kolejny kod komponentu i postaram się napisać, czy spełnia moje oczekiwania czy nie.
Dziękuję za pomoc.

0

A może zamiast TPNGButton i innych przycisków, postaw na formie jeszcze jeden TImage i obsłuż zdarzenie OnClick.

0

Ikony tworzę sam z głębią 32 bitową. Nie chcę korzystać z ico bo o wiele więcej zajmuje plik niż to samo w png.

Z ICO nie, z PNG nie, to w końcu z czego korzystasz ?
PNGComponents są zoptymalizowane pod kątem szybkości i mają bardzo dużo zalet. Wspomniana głębia kolorów 16-bit - sprawdzałeś jak zachowują się Twoje ikony np.: na Windows 98 ?

Poza tym, sam korzystam z PNG i nigdy więcej nie zamierzam powracać do tej parodii jaką jest ICO.

0
TomRiddle napisał(a)

A może zamiast TPNGButton i innych przycisków, postaw na formie jeszcze jeden TImage i obsłuż zdarzenie OnClick.

Robiłem tak, postawiłem na formie Label i zwykły TImage. W OnMouseEnter labelka pokazuję TImage, w OnMouseLeave obrazka ukrywam TImage. Działa z taką samą szybkością co mój komponent TImageButton. Podejrzewam, że działa to tak wolno dlatego, że tłem jest TImage rozciągnięty na całą formę, która jest zmaksymalizowana na cały ekran. Bo inaczej, robię zwykłą formę o niedużych rozmiarach (np. 500x350px) i kładę PNGButton, wrzucam do niego te grafiki co wcześniej do TImageButton i działa bardzo szybko. Jak tylko wrzucę jako tło TImage na alClient, zaczyna się mulić... Kompa nie mam znowu archiwalnego: IBM R31, Intel Celeron 1,13GHz, 512 RAM, ATI Mobility 48Mb. Kiedyś robiłem programik z wykorzystaniem TPNGButton, działa świetnie, ale nie miałem grafiki stanowiącej tło formularza. Myślę, że tu tkwi problem.

Jak macie ochotę to sprawdźcie jak zachowuje się TPNGButton na tych zasadach:

  • WindowState na wsMaximized
  • BorderStyle na bsNone
  • tło formularza to obraz png w TImage
  • na niego postawić PNGButton, gdzie grafika jest półprzezroczysta

Jeśli będzie u was działało bez zarzutu - będę szukać dalej rozwiązania.

Opi napisał(a)

Z ICO nie, z PNG nie, to w końcu z czego korzystasz ?

Napisałem, że korzystam z PNG, w kodzie w pierwszym poście użyłem tylko ICO a także, że ta sama grafika w ICO zajmuje 68Kb, a w PNG tylko 2,4Kb.

Dorzuciłem do kodu komponentu te inherited, ale coś się podziało, bo kompilacja komponentu się powiodła, a jak wrzucam na formularz to mnóstwo AV wyskakuje (np. gdy kursor jest nad komponentem) i kompilator się zawiesza na amen, więc usunąłem go na razie z palety komponentów. Nie wiem dlaczego, ale wczoraj jeszcze działało, dziś się wszystko sypie...

Jak możecie - przetestujcie u siebie ten komponent. Tutaj podaje cały jego kod:

unit ImageButton;

interface

uses
  SysUtils,
  Classes,
  Controls,
  ExtCtrls,
  Messages,
  pngimage;

type
  TImageButton = class(TImage)
  private
    FImageNormal: TPNGObject;
    FImageHot: TPNGObject;

    procedure SetNormalImage(Image: TPNGObject);
    procedure SetHotImage(Image: TPNGObject);
  protected
    FMouseOver: TNotifyEvent;
    FMouseOut: TNotifyEvent;

    procedure CMMouseEnter(var Message:TMessage); message cm_MouseEnter;
    procedure CMMouseLeave(var Message:TMessage); message cm_MouseLeave;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
  published
    property ImageNormal: TPNGObject read FImageNormal write SetNormalImage;
    property ImageHot: TPNGObject read FImageHot write SetHotImage;

    property OnMouseOver: TNotifyEvent read FMouseOver write FMouseOver;
    property OnMouseOut: TNotifyEvent read FMouseOut write FMouseOut;
  end;

procedure Register;

implementation

constructor TImageButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FImageNormal := TPNGObject.Create();
  FImageHot := TPNGObject.Create();
end;

destructor TImageButton.Destroy();
begin
  FreeAndNil(FImageNormal);
  FreeAndNil(FImageHot);

  inherited Destroy();
end;

procedure TImageButton.SetNormalImage(Image: TPNGObject);
begin
  FImageNormal.Assign(Image);
end;

procedure TImageButton.SetHotImage(Image: TPNGObject);
begin
  FImageHot.Assign(Image);
end;

procedure TImageButton.CMMouseEnter(var Message: TMessage);
begin
  if Assigned(FMouseOver) then
    OnMouseOver(Self);

  Picture.Assign(FImageHot);
  Message.Result := 1;
end;

procedure TImageButton.CMMouseLeave(var Message: TMessage);
begin
  if Assigned(FMouseOut) then
    OnMouseOut(Self);

  Picture.Assign(FImageNormal);
  Message.Result := 1;
end;

procedure Register;
begin
  RegisterComponents('Samples', [TImageButton]);
end;

end.
Opi napisał(a)

sprawdzałeś jak zachowują się Twoje ikony np.: na Windows 98 ?

Niestety nie mam takiej możliwości, nie znam kogokolwiek, kto ma jeszcze ten system.

Co to masz za paczkę PNGComponents? Ja korzystam z takiej, w której jest kilkanaście plików, samych modułów jest 4:

  • zlibpas.pas
  • pngimage.pas
  • pngextra.pas
  • pnglang.pas

Opi, masz jakiś inny?

0

No i jestem. Sprawdziłem wszystko i niedowierzam... :)

Sprawa ma się tak, gdy mam tylko tło, jakim jest TImage z grafiką PNG, wszystko działa wolno, nie tylko TPNGButton. Dlaczego? Bo grafika z tłem pomimo tego, że jest w formacie .png, zajmuje bagatela 860kb! Przerobiłem ten obraz na JPEG i sprawdziłem - działa świetnie. Teraz obraz tła zajmuje 87Kb :)

Sprawdzałem na różnych komponentach (głównie TButton, TPNGButton) i zawsze działało wolno, jeśli tłem był PNG (samo narysowanie go trwało długo przez tak dużą wagę). Po zmianie tła na JPEG wszystko jest świetnie.

W takim razie nie trzeba kombinować z nowymi komponentami, wystarczy TPNGButton. Dziękuję za pomoc. Pozdrawiam.

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