Czy można nadać dowolny wygląd komponentom w Lazarusie?

0

Np. chciałbym zmienić wygląd TListBox, bo ten standardowy mi nie odpowiada. Wygląd przyciskom nadałem poprzez wstawienia zamiast TButton, TImage, choć nie wiem jak to się ma do poprawności.

Chodzi mi mniej więcej o coś takiego jak CSS przy tworzeniu stron.

2

Jejq, jakirz to straszny buond byu w temacie wontq, rze asz oczy bolom, ale poprawiuem. Ale spoko, wczoraj na pewnym garażu widziałem przyklejone ogłoszenie, że jakiś jełop chce go sprzedać nazywając swoją własność "gararzem", także pewnie jeśli dorzuci się mu słownik ortograficzny to by zszedł z ceny ;)

Co do pytania, to większości komponentów da się nadać własny wygląd. Modyfikując ich zdarzenia do rysowania, jeżeli takie posiadają. Albo poprzez obsługę standardowych komunikatów rysowania w ich WndProc. Chociaż akutat pod Lazarusem z łatwym subclassingiem własnej WndProc nie raz są cyrki, w porównaniu z łatwością z jaką mozna to zrobić pod choćby Delphi 7.

Natomiast co do TListBox, to posiada on zdarzenie OnDrawItem. I przy zmianie własności Style na lbOwnerDrawFixed, robimy co chcemy z wyglądem Itemów w tym zdarzeniu. Tylko nie wiem czy znowu nie skupiasz się na pierdółkach, o czym już pisałem w innym Twoim wątku. Najpierw zrób sobie ten wymarzony program - bazę danych opartą o plik typowany, ze spisem czegoś tam, żeby to w ogółe działalo jak trzeba. Dopiero późnej skupiaj się na tym by wszystko wyglądało mega-hiper-ql, jak jakiś Windows 8, który przez to funkcjalnością ma beznadziejną i bardziej wkurza niż służy wygodzie, ale gimbaza się podnieca, bo są kafeleczki jak na jakimś ajoesie czy cholera wie czym.

Wiadomo, fajnie GUI jest ok, nieraz potrzebne. Jednak osobiście zawsze staram się skupić na funkcjonalności swoich programów, wygląd to zawsze była dla mnie sprawa "dalszo rzędna". Wiadomo, ze nie ma co jak szalona gimbaza poznająca IDE jakiegoś języka z wizualnym tworzeniem GUI, w amoku ułożyć 1638 editów i innych zbędnych komponentów. Porozciągać buttony by zajmowały połowe formatki, a później prosić kolegów z piaskownicy na samosi albo pseudo haxorskich 'forach' o pomoc. Bo googlować się nie chce i samemu uczyć języka. A niestety samo ułożenie zajefajnego GUI nie sprawi, że program sam się domyśli co ma robić, tak jak sobie wymarzymy bez linijki kodu.

EDIT: ech - tyle poprawek posta, ale jak się mało śpi po długiej zmianie w pracy, a później reversuje Tyskie Klasyczne to tak bywa ;) Także for giw mi plis.

0

Ja większość kodu mam napisane, tyle że w wersji konsolowe. Teraz oczywiście muszę go przerobić. Ale już chce to dopasowywać do wyglądu. Może nie będzie to napisane tak profesjonalnie jak powinno, ale to tylko amatorski program na potrzeby kilkunastu osób w tym momencie. Może nie powinienem się skupiać tak na wyglądzie, ale sam robię to jakby pod siebie. Często kiedy jakiś program używam to właśnie podstawą jest interfejs, intuicyjność w obsłudze. Jasne, Twój program z bazą działa tak jak działać powinien, ale wygląda zwyczajnie. I tak na prawdę takiej aplikacji raczej nie wiele osób chciało by używać, aczkolwiek mogę się mylić.

Jeszcze w tym temacie takie moje spostrzeżenie na koniec. Dzięki tym moim pytaniom o pewnie błache sprawy zacząłem lepiej rozumieć tak na prawdę to wszystko. Przy pisaniu tego programu miałem kilka problemów, bo kod w niektórych miejscach musi być inny niż w wersji konsolowej. Tak mi się przynajmniej wydaje. A więc, miałem kilka problemów, ale wszystkie do tej pory udało mi się rozwiązać. Wiedziałem, czego szukać, gdzie a później jak się zastosować do tego co znajdę. W tym momencie moje ostatnie pytania dotyczyły tylko możliwości i tego czy stosuję poprawne rozwiązania, a nie konkretny kod.
Tak więc, dziękuję bardzo :)

0

Proszę bardzo. A co do wyglądu mojej prostej bazy. Miało to w moim założeniu zostać napisane "dla wprawki". I pokazać, że się da oraz jak to zrobić. Chodziło o funkcjonalność. Interface jest również prosty. Bo po co miałby być wymyslny. Można zmienić buttony i dodać do nich oczywśćie jakąś grafikę w postaci glyphów. U mnie w pracy na przykład używają skomplikowanego chyba dość oprogramowania do rozliczenia różnych rzeczy oraz ważenia. I z tego co wiem, nie jest to też wymyslny interfac'e. Po prostu ma się komunikować po necie z centralą i wysyłać im jakieś raporty dla statystyk i takie tam. A i tak wiele pomocniczych rzeczy robi się w Excelu, bo sobie Panie mogą pokolorować ładnie komóreczki wedle ustaleń i ładnie się to oraz czytelnie wszystko prezentuje. A jest to firma działające w większej sieci i pewnie takich stanowisk w kraju jest dużo. Może też kilkanaście, a może więcej.

Wedlug mnie zawsze najważniejsza będzie funkcjonalnośc. Nie mogę teraz tego znaleźć, ale kiedyś Bartek chyba na www.secnews.pl opisywał jak powinno się przygotowywać swoją profesjonalną aplikację również pod kątem jej wyglądu. A którą później chcemy sprzedawać. Ale wiadomo, że fajnie jest zrobić grafikę pod buttonami na toolbarze, ikonki dla pozycji w menu i jakieś tam grafiki po bokach przy kolejnych krokach, jeżeli mamy u siebie jakiś kreator. Ale bez większej przesady. Na prawdę nie masz się co na tym za bardzo skupiać. Bo przesadzisz w drugą stronę i będziesz musiał ogarnąć DirectX. I zrobić jakieś bajery oraz animacje jak pod menu w jakiejś grze. Program okienkowy, to nadal powinien być program okienkowy, a nie gra z wodotryskami na jakimś licencjonownym silniku. Szkoda przesadnie sobie zawracać tym głowę, uwierz mi. Skup się na ogarnięciu języka i funkcjonalności.

1

@lucasp17 - to może inaczej; Wszystkim istniejącym komponentom standardowym, jak i firm trzecich da się nadać własny wygląd;

Teraz sprostowanie - to co ludzie nazywają "własnym wyglądem" to udostępnione zdarzenia do rysowania wybranych elementów komponentu; To nie jest własny wygląd, tylko własne rysowanie pewnych elementów komponentu;

Weźmy pod lupę komponent klasy TListBox - to co można samemu rysować, to jedynie itemy, w zdarzeniu OnDrawItem; A co z resztą? Właśnie - nico; Nie można rysować własnej ramki komponentu, nie można wpływać na wysokość itemów (dynamicznie je zwężać i rozszerzać), nie można dodać czegokolwiek swojego, bo budowa komponentu na to nie pozwala; Jedyne co można kombinować, to dorzucać swoje elementy do obiektów listy itemów i je jakoś rysować;

Podobnie sprawa wygląda z innymi standardowymi komponentami - np. TButton; Co można zrobić? ustawić mu szerokość i wysokość, no i nadać Caption w wybranym stylu fonta; Nie można narysować przycisku samemu (bo nie ma zdarzenia), nie można narysować sobie np. ikonki, nie można dodać np. subprzycisku do rozwijania menu kontekstowego, nie można formatować tekstu etykiety;

Większość rzeczy jest zablokowana (a raczej nie udostępniona), więc jedyne co można w takim przypadku zrobić, to napisać wrapper na rysowanie komponentu; W taki sposób działają np. komponenty ze znanych i szanowanych paczek, jak np. Alpha Controls; Tworzy się mapę grafiki, która używana jest jako skórka interfejsu; Efektem jest kompletnie własny wygląd komponentów (z zachowaniem jego funkcjonalności), ale efektem ubocznym jest o wiele wolniejsze przerysowywanie komponentów i spuchnięty exek;

Oczywiście taka sama sytuacja jest z formularzami, którym wygląd narzuca system, lub zainstalowany w nim dodatek, który przerabia interfejs systemu i jego okien, menu itd.; Nie można łatwo stworzyć kompletnie własnego interfejsu programu, bo większość możliwości narzuca system i systemowe (standardowe) komponenty; Microsoft niemalże perfekcyjnie narzucił programistom korzystanie z natywnego wyglądu, chyba tylko po to, aby większość programów wyglądała tak, jak system; Spisek nie spisek, w każdym razie kłoda pod nogi;

Oczywiście jest na to sposób - zbudowanie komponentu od podstaw, dziedzicząc po klaasach TControl/TWinControl dla fokusowych komponentów, lub z TGraphicControl dla niefokusowych (etykiet, obrazków itd.); Roboty więcej, ale dzięki temu można stworzyć dowolny komponent, posiadający dowolne zdarzenia, dowolne subkomponenty, dowolne rysowanie, dowolne właściwości i metody; Aby stworzyć własny komponent, wystarczy znajomość OOP w średnim stopniu i chęci główkowania, bo roboty nie jest wiele; Plusem ogromym jest na pewno kompletnie własny wygląd i możliwości, a drugim ogromnym plusem jest to, że aplikacja z takimi komponentami będzie wyglądać i zachowywać się tak samo na każdej wersji systemu Windows (nie wspominam już o Linuxach i jabłku);

W najbliższych miesiącach (pewnie do zimy) będę pracował nad projektem, który posiadać będzie kompletnie własny wygląd, bazując na komponentach napisanych od zera; Część ciekawszych komponentów (do uniwersalnych zastosowań) postaram się tutaj udostępnić (pewnie nie za darmo); Zobaczymy co z tego wyjdzie, ale już można śmiało stwierdzić, że liczba możliwości jest nieskończona;


A co poruszonej przez @olesio kwestii wyglądu aplikacji - programy są różne, jedne wyglądają odjazdowo, są funkcjonalne i intuicyjne; Inne z kolei są funkcjonalne, ale interfejs woła o pomstę do nieba; Ostanio miałem do czynienia z programem do obsługi sklepu (nazwy nie podam, choć to nie mój program) - sprzedaż, rozliczenia, faktury, magazyny itd.; Interfejs aplikacji jest w nim swoistą hybrydą - połączenia Windowsów 3.1, 98, 7, 8; Wygląda słabo, nawet jak na tak ważne narzędzie, niemalże każdy formularz zawierał po ok. 100 komponentów, część upakowana tak gęsto, że czasem cięzko kursorem trafiź, do tego sporo wpakowanych do zakładek; Funkcjonalność natomiast na pierwszy rzut oka bogata; W praktyce jednak sporo niedociągnięć, ułomności; głupi filtr do wyszukiwania towaru po nazwie działa na zasadzie zwykłego pascalowego Pos - jak podasz słowa na odwrót to nulla znajdziesz; Albo inna rzecz - wpisując nowy towar do magazynu, nie można podać mu od razu jego ilości; Przy wprowadzaniu ponad tysiąca produktów, trzeba je najpierw wprowadzić po nazwie, następnie od nuwa wyszukiwać, aby podać ich ilość; Zamiast tysiąca iteracji, mamy dwa tysiące, co przełożyło się na 10 godzin podawania sanych ilości produktów; Za 5 godzin obsługi programu naliczyłem co najmniej 20 różnych ułomności, a program kosztował coś koło 800zł, w wersji nieco ponad podstawowej...

Napiszę tak - interfejs aplikacji to bardzo ważdny element, dlatego że to jedyna część programu, z którą ma do czynienia użytkownik; Musi od wyglądać dobrze - jeden styl, urozmaicone komponenty, pogrupowane tematycznie, nie za dużo, nie za mało, część schowane pod rozwijanymi menu, część widoczne; Do tego sensowne hinty lub opisy, jak największa intuicyjność; GUI aplikacji musi być z jednej strony przyjazne dla oka, z drugiej storny intuicyjne, aby użytkownik od razu wiedział co i jak się wykonuje;

Kolejna rzecz to funkcjonalność aplikacji - program musi w pełni realizować przyjęte funkcje; Muszą one działać perfekcyjnie, bo program ładnie wyglądający ale wadliwy lub opośledzony jest jak choinka - co z tego że ładna, świecąca, wręcz animowana, skoro nieporęczna, zajmuje miejsce i blokuje światło;

Wygląd, intuicyjność, funkcjonalność i niezawodność aplikacji to trzy cztery najważniejsze elementy każdego programu, a określanie im priorytetów to ocena subiektywna; Natomiast przyłożenie się do ich wszystkich i ich opracowanie na jak najwyższym poziome, uczyni każdą aplikację arcydziełem; Szkoda, że w dzisiejszych czasach pieniądz jest ważniejszy od przyjemności, jaką użytkownik może czerpać z obsługi software'u.

0

A w takim razie, czy jest możliwość nadania jako tła TListBox jakiegoś zdjęcia? Tak czysto teoretycznie? Bo kolor można zmienić w ustawieniach, ale np. gdybym chciał, żeby w tle było jakieś logo? Np. czy da się na TListBox nałożyć TImage, tak żeby wszystkie Itemy pojawiały się na obrazku? Teraz o tym pomyślałem, a sprawdzę jak będę w domu.

1

Tak, da się. Tylko że należy się zastanowić nad czytelnością. Ponieważ dodawane nowe elementy zasłonią tekstem i swoim tłem tę grafikę. Poniżej masz kod prostego komponentu dziedziczącego po TScrollBox. Jest on wzbogacony o Canvas, do którego normalnie w tym komponencie nie mamy dostępu. Podejrzewam, że z TListBox trzeba by pokombinować podobnie. A najwyżej coś tutaj ktoś jeszcze Tobie doradzi, ale masz już podstawe by wiedzieć jak można pokombinować.

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: a @kAzek mnie ubiegł i pokazał prostszy sposób bez tworzenia własnej kontrolki od zera.

EDIT 2: tylko zaraz po chwili usunął post :/

1

Można dość łatwo wepchnąć bitmapę pod tekst itemów, ale nie dosłownie;

Stwórz sobie bitmapę o takich rozmiarach, jak wnętrze komponentu (obszar wchodzący w skład wszystkich itemów, widoczny jednorazowo w komponencie); Teraz w zdarzeniu OnDrawItem sprawdzaj indeks itema na ekranie, za pomocą właściwości TopIndex; Oblicz jego numer, który jest wyświetlany w komponencie, odejmując TopIndex od indeksu podanego w zdarzeniu; Tak otrzymany indeks posłuży do skopiowania fragmentu bitmapy tła; Teraz podczas rysowania tła itema skopiuj fragment bitmapy i namaluj go najpierw, a po nim dopiero narysuj np. tekst itema i inne rzeczy, które chcesz umieścić w danych pozycjach;

Każde przerysowanie itemów pozwoli na utrzymywanie nieprzewijalnego tła (czyli bitmapy, która będzie w jednym miejscu); Powinno to działać przy scrollowaniu; Sprawdź sobie, mało przy tym roboty, więcej arytmetyki niż kodzenia;


Odbiegasz od pierwotnego tematu - możliwości dowolnego malowania komponentów; Nie ma sensu tworzyć jednego wątku, aby wyjaśniać pewnie sporo pytań na temat rysowania elementów komponentu; Lepiej założyć jeden wątek na jeden problem, niż jeden do wszystkiego.

1

@lucasp17 - pobawiłem się nieco tym ListBoxem, bo byłem ciekaw czy faktycznie łatwo zrobić to, o czym napisałem wcześniej; No i muszę stwierdzić, że bardzo łatwo :]

lbbackimg.png

Co potrzeba:

  1. przygotować sobie plik tła, np. bitmapę o ustalonych rozmiarach,
  2. dodać na formularz ListBoxa, ustawić mu styl na lbOwnerDrawFixed, ustalić rozmiar komponentu i wysokość itemów itd.,
  3. załdować bitmapę w konstruktorze, zwolnić w destruktorze formularza,
  4. w zdarzeniu OnDrawItem komponentu:
  5. obliczyć prostokąt do wycięcia z obrazu tła, o wysokości takiej, jak item,
  6. ustawić kolory tła i ramki itema, według stanu z argumentu State,
  7. wypełnić tło,
  8. sprawdzić, czy item ma być zaznaczony:
  9. jeśli tak - rysujemy tylko tekst itema,
  10. jeśli nie:
  11. kopiujemy fragment obrazu tła i rysujemy go na tle itema,
  12. ustawiamy styl wypełnienia na bsClear,
  13. rysujemy tekst itema,
  14. usuwamy wiejską ramkę fokusa,
  15. amen;
    Kod samego zdarzenia OnDrawItem komponentu może wyglądać następująco:
procedure TMainForm.lbPreviewDrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
  List: TListBox;
  rctSource, rctDest: TRect;
begin
  List := Control as TListBox;

  rctSource        := FBackImg.Canvas.ClipRect;
  rctSource.Top    := (Index - List.TopIndex) * 24 - 16;
  rctSource.Bottom := rctSource.Top + 24;

  rctDest := Rect;
  Inc(rctDest.Left, 22);
  Dec(rctDest.Right, 22);

  with List.Canvas do
  begin
    if odSelected in State then
    begin
      Pen.Color   := $008E4D24;
      Brush.Color := $00C56A31;
      Font.Color  := $00FFFFFF;
      Font.Style  := [fsBold];
    end
    else
    begin
      Pen.Color   := $00FFFFFF;
      Brush.Color := $00FFFFFF;
      Font.Color  := $00353535;
    end;

    Rectangle(Rect);

    if odSelected in State then
      TextOut(Rect.Left + 6, Rect.Top + 6, List.Items[Index])
    else
    begin
      CopyRect(rctDest, FBackImg.Canvas, rctSource);
      Brush.Style := bsClear;
      TextOut(Rect.Left + 6, Rect.Top + 6, List.Items[Index]);
    end;

    if odFocused in State then
    begin
      Pen.Color := Pen.Color xor $FFFFFF;
      DrawFocusRect(Rect);
    end;
  end;
end;

Jak widać niewiele roboty, a efekt bardzo fajny; Źródła programu widniejącego na zrzucie w załączniku lbbackimg.zip (bez pliku wykonywalnego), gotowe do kompilacji np. pod Delphi7; Jak widać zdarzenie nie jest uniwersalne pod pliki tła różnych wielkości (wyliczenia są statyczne), ale nie problem te obliczenia zmienić;

Ale powoli - nie ma się co od razu podniecać, tyle nie wystarczy... W przykładzie stworzyłem ListBoxa, który nie posiada paska przesuwu, a łączna wysokość wszystkich itemów jest równa wysokości komponentu (ListBox nie ma ramki); Niestety tak jak się obawiałem, podczas scrollowania nie są odświeżane wszystkie widoczne itemy, dlatego aby w takim przypadku wszystko działało, trzeba się podessać pod obsługę komunikatów scrolla i jakimś sposobem przemalować pozostałe widoczne itemy, co ciekawskim lub potrzebującym pozostawiam na zadanie dowome.

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