PORADY

Porady > Programowanie > Delphi > Aplikacje DPI-Aware w Delphi

Aplikacje DPI-Aware w Delphi

Spis treści

  1. Wstęp
  2. Czym są aplikacje DPI-Aware w Windows?
  3. Na czym polega skalowanie w Delphi?
  4. TImage i grafiki kontrolek
  5. Listy obrazków
  6. Komponenty z rysowanymi samodzielnie elementami
  7. TFrame - komponent z własną definicją DFM tworzony dynamicznie
  8. Pozostałe komponenty tworzone dynamicznie
  9. Znane błędy
  10. THighDPIHelper - moja pomoc w szybkim uczynieniu aplikacji DPI-Aware

Wstęp

Postęp technologii monitorów obserwuje się na wielu płaszczyznach. Jeszcze kilka lat temu monitor LCD o przekątnej 14" był drogim luksusem, a element ten był najdroższym w laptopach. Dziś monitory zużywają znacznie mniej prądu, mają większe przekątne, potrafią odwzorować znacznie bogatsze palety barw, mniej smużą oraz mogą mieć dużo mniejsze plamki (pojęcie wywodzące się jeszcze z czasów monitorów CRT) czyli piksele, a zatem posiadać dużo większą rozdzielczość przy tych samych rozmiarach fizycznych. Obraz na monitorze wysokiej rozdzielczości (High-DPI) jest dużo bardziej bogaty w szczegóły, a wygładzanie krawędzi (a więc i miękkość obrazu) przestaje odgrywać tak duże znaczenie (co również poprawia wyrazistość).

Niestety, to co może być atutem przy oglądaniu filmów czy zdjęć staje się przekleństwem przy pracy z aplikacjami. Problem ten znany był już częściowo w roku 2000 i systemy operacyjne z tego okresu próbowały zaradzić dając możliwość określania parametry DPI ekranu. Ale Microsoft długo nie mógł znaleźć skutecznego rozwiązania wyświetlania aplikacji w sposób czytelny i odpowiednio powiększony na takich ekranach. Rewolucja przyszła dopiero wraz z Windowsem 10.

Owszem, zawsze można było sobie radzić zmniejszeniem rozdzielczości wyświetlanego obrazu, który przez monitor był skalowany do całego ekranu. Jednak po pierwsze, kupowanie takiego monitora, by nie skorzystać z jego możliwości, byłoby pozbawione sensu. Po drugie obraz taki wygląda znacznie gorzej, niż na monitorze o tej samej przekątnej, lecz mniejszej natywnej rozdzielczości.

Popularność wyświetlaczy High-DPI nieubłaganie rośnie i zmusza twórców do przygotowywania aplikacji w sposób, który zapewni komfortową pracę. Bez tego aplikacja będzie skalowana przez system tak, jakby używać mniejszej rozdzielczości.

Wygląd programu z wyłączoną i włączoną obsługą ekranów wysokiej rozdzielczości

Jak dostosować swoje aplikacje z Delphi do tego, aby wyglądały poprawnie? O tym będzie można dowiedzieć się z tego artykułu.

Na wstępnie zaznaczę, że poniższy poradnik jest tylko wstępnym i częściowym zarysem najistotniejszych wskazówek, które pomóc mają we zwróceniu uwagi na najczęstsze i najistotniejsze problemy oraz ukazać drogę ich rozwiązania. I z góry także uprzedzę: nie jest absolutnie wykonalne zaprojektowanie aplikacji tak, aby wyglądała identycznie przy każdym poziomie skalowania. Tak naprawdę aplikacja projektowana w danej rozdzielczości (poziomie skalowania) będzie wyglądała idealnie (czyli zgodnie z zamysłem twórcy) tylko, jeśli będzie uruchomiona w systemie z takim samym skalowaniem. W każdym innym przypadku liczyć się będzie trzeba ze zmodyfikowanym, często gorszym, wyglądem. A o tym, jak trudnym jest przygotowanie złożonej aplikacji zgodnej z ekranami wysokiej rozdzielczości niech świadczy, że samo środowisko RAD, pomimo wsparcia od wersji 10, takiej zgodności nie posiada. (Nie jest też środowiskiem 64-bitowym, co potrafi skutecznie uprzykrzać życie ze względu na pamięciożerność).

Czym są aplikacje DPI-Aware w Windows?

W Windowsie możemy rozróżnić dwa typy aplikacji: zgodną lub nie z ekranami wysokiej rozdzielczości. Całe rozróżnienie polega na prostym zapisie w pliku .manifest umieszczonym wraz z plikiem wykonywalnym lub - co aktualnie wyłącznie praktykowane - dołączeniem takiego pliku do zasobów pliku wykonywalnego. Na szczęście w Delphi od wersji 10 Seattle wystarczy zaznaczenie jednego pola w oknie opcji projektu:

Opcje projektu Delphi włączające obsługę ekranów wysokich rozdzielczości

W Windows 10 aplikacje, które nie mają tego wpisu, są "oszukiwane" przez system: "sądzą", że pracują w środowisku z rozdzielczością podstawową 96 DPI. Lecz system operacyjny, zanim wyświetli je na ekranie, powiększa obraz okien takiego programu. Ot tak, jakbyśmy w programie graficznym zwiększyli rozmiar obrazu. Wynikiem będą rozmyte krawędzie wszystkich elementów.

Aplikacje DPI-Aware nie są tak traktowane. Wiedzą one, jaka jest używana rozdzielczość ekranu, a system pozostawia im samym możliwość przeskalowania się w sposób wektorowy, a więc bez utraty jakości.

Na czym polega skalowanie w Delphi?

Za skalowanie okien w Delphi odpowiadają dwie właściwości obiektów dziedziczących po TCustomForm, czyli wszystkich formatkach. Pierwsza - Scaled - określa, czy okno w ogóle ma być poddawane skalowaniu. Natomiast do obliczenia proporcji skalowania służy PixelsPerInch. Na marginesie, ustawienie jej na 0 jest równoznaczne z wyłączeniem tej pierwszej, natomiast wartością zwracaną będzie DPI ekranu (czyli wartość obiektu Screen.PixelsPerInch, a jeśli program jest uruchamiany na Windowsie 8.1 lub nowszym - ekranu, na którym umiejscowione zostaje okno). I właśnie porównanie DPI formatki z DPI ekranu pozwala na działanie. Jeśli obie wartości są sobie równe, nie ma potrzeby nic zmieniać. W przeciwnym razie zaczyna się cała procedura skalowania:

  1. Ustawienie odpowiednio większej czcionki;
  2. Ustawienie zakresu pasków przewijania;
  3. Przeliczenie minimalnych i maksymalnych wartości rozmiaru okna (Constraints);
  4. Wywołanie rekurencyjne funkcji skalowania ChangeScale dla kontrolek umieszczonych bezpośrednio na formie (one będą wywoływać dla kontrolek, dla których są rodzicami);
  5. Wyznaczenie nowej szerokości i wysokości obszaru roboczego okna (ClientWidth i ClientHeight).

Opcję skalowania posiada też każda jedna kontrolka dziedzicząca po TControl. Tutaj następujące operacje są wykonywane w ramach przesklaowania:

  1. Wyznaczenie nowego położenia oraz wymiarów kontrolki (jeśli konieczne);
  2. Wyznaczenie rozmiarów minimalnych i maksymalnych;
  3. Przeliczenie wartości marginesów (Margins);
  4. Dodatkowo, jeśli komponent dziedzi po TWinControl skalowane są także wartości odstępów wewnętrznych (Padding).

Oczywiście, kolejne komponenty mogą przeprowadzać własne dodatkowe operacje skalowania poprzez nadpisywanie procedury ChangeScale.

Co istotne, okna w momencie wyświetlania skalują się same. Ale już dodawane dynamicznie elementy i trzeba zadbać samemu o ich przeskalowanie. Więcej na ten temat będzie w dalszej części.

Warto w tym miejscu wspomnieć o jeszcze drugiej procedurze, która towarzyszy skalowaniu: ScaleBy. Wykonuje ona dokładnie to samo, co ChangeScale, ale kontrolka uwzględnia także samą siebie w odróżnieniu od wywoływania wyłącznie dla kontrolek potomnych, co jest przydatne przy rekurencyjnym wywołaniu.

Samo skalowanie to prosta operacja mnożenia wartości pierwotnej przez aktualne DPI monitora i dzielenia przez DPI formatki, w jakiej została zaprojektowana (domyślnie: 96). Wynik następnie jest zaokrąglany, a wszystko odbywa się za pomocą funkcji MulDiv z WinAPI.

I właśnie w tej matematyce pogrzebana jest cała niedoskonałość skalowania. Załóżmy, że mamy dwie linie o szerokości 1 piksela odległe o siebie również o 1 piksel, czyli z wartościami Left = 0 i 2. Na ekranach ze skalowaniem 125% dalej pozostaną ona jednopikselowymi, ale odstęp zwiększy się do 2 pikseli (0 × 125% ≈ 0; 2 × 125% ≈ 3). Z kolei przy skalowaniu 150% zmienią swoją szerokość na 2, zaś odstęp zmniejszy się znów do 1 piksela (położenie drugiej: 2 × 150% = 3; przy szerokości pierwszej linii wynoszącej 2 piksele).

Wygląd programu z 4 liniami 1px oddalonymi o 1px po przeskalowaniu

To sprawia, że poziomy skalowania będące wielokrotnością całkowitą (200%, 300%) będą znacznie lepiej oddawały układ aplikacji, niż wszelkie pośrednie. Ponadto trzeba pamiętać, że obliczenia są zmiennoprzecinkowe, zatem jeśli coś zwiększa swój wymiar wraz z osiągnięciem progu 150%, 250% wcale nie oznacza, że tak samo stanie się na progu 350%. Mało tego - jedne wartości mogą przy takim poziomie skalowania ulec zmianie, a inne nie. Ot, taki urok zmiennoprzecinkowych obliczeń w dyskretnym środowisku cyfrowym. Zatem najgorszym możliwym wyborem są skalowania z wartością 50%, a najlepsze te będące wielokrotnością 100%.

Niestety na tym problem aplikacji zgodnych z ekranami wysokich rozdzielczości się nie kończy. Bardzo szybko dostrzeżemy elementy, które nie ulegają przeskalowaniu, czemu musimy zarazić sami. Przykładem jest tutaj pierwszy rysunek porównawczy, który znalazł się na początku artykułu. Widać przede wszystkim nieprzeskalowane grafiki w nagłówku oraz nachodzące na siebie teksty w prawym-dolnym obszarze programu.

TImage i grafiki kontrolek

Pierwszym z takich elementów, jakie rzucą się w oczy, są wszelkiego rodzaju grafiki rastrowe. Pół biedy, jeśli na komponencie TImage, który będzie dopasowany do zawartego w nim obrazka i nie będzie miał ustawionej właściwości AutoSize ustawimy własność Streched. Wtedy obraz rozciągnie się wraz z wymiarami i w zasadzie będzie wszystko ok... Prawie, bo takie skalowanie niesie ze sobą pogorszenie jakości.

Jak nie tracić? A no trzeba by przygotować odpowiednie obrazki pod każdą z rozdzielczości, a następnie dynamicznie wczytywać właściwą. Dzięki temu użytkownik będzie mógł cieszyć oko perfekcyjną grafiką w programie! Ale przygotowywać osobne grafiki dla poziomów 125%, 150%, 175% itd.? A co, jeśli ktoś wybierze poziom 180%? Dlatego takie projektowanie aplikacji mija się z celem.

Najprostszym z kolei sposobem jest po prostu upscaling wszystkich grafik. Jedna prosta procedura pozwoli na powiększenie grafiki - niestety, kosztem jej jakości: procedure ScaleBitmap(img: TBitmap; const ScaleM, ScaleD: Integer); var b: TBitmap; begin b := TBitmap.Create; try b.Width := MulDiv(img.Width, ScaleM, ScaleD); b.Height := MulDiv(img.Height, ScaleM, ScaleD); b.PixelFormat := pf32bit; b.Canvas.StretchDraw(Rect(0, 0, b.Width, b.Height), img); img.Assign(b); finally b.Free; end;

Do procedury poza grafiką do przeskalowania przekazywane są jeszcze dwa parametry, które określają stopień skali, a są w rzeczywistości wartościami z odpowiednio Screen.PixelsPerInch i PixelsPerInch z aktualnej formatki. Dzięki tym wartością możemy utworzyć na początku nową czystą bitmapę odpowiednio większą. Następnie ustalane są właściwości PixelFormat dla bitmapy źródłowej i docelowej na pf32bit. Dlaczego? Po pierwsze pozwala to na zachowanie wszystkich kolorów wraz z przeźroczystością, jeśli jest definiowana kanałem alfa. Po drugie pozwoli to zastosować najlepszy algorytm skalowania.

Teraz można już po prostu wykonać funkcję rysowania ze skalowaniem StretchDraw na nowym obrazku wypełniając do grafiką ze starego. Na koniec wystarczy nowy obrazek wpisać w miejsce starego (Assign przepisuje wszystkie parametry jednej bitmapy do drugiej robiąc w ten sposób kopię).

Taka procedura może być zastosowana do wszystkich obrazków na komponentach TImage a każde TBitBtn, TSpeedButton i innych, które posiadają właściwość Glyph lub jakąkolwiek inną będącą typu TBitmap;

Listy obrazków

Jednak to nie koniec walki z obrazkami. Osobnym problemem są listy obrazków TImageList. Zaskakującą w rezultacie może dla niektórych być próba po prostu zmiany własności szerokości i wysokości dla tego elementu. Niestety, nie spowoduje to przeskalowania grafik a usunięcie ich!

Co zatem można zrobić, aby przeskalować obrazki z listy? Na początek warto dokonać kopii wszystkich najlepiej przy użyciu tymczasowej TImageList-y. Wtedy będzie można śmiało zmienić właściwości tej pierwotnej, by osadzić w niej już przeskalowane obrazki pobierane kolejno z listy tymczasowej. Nadmienić trzeba, że opisywany komponent nie tylko przechowuje same obrazki, ale też ich maski przeźroczystości. Są one tworzone podczas dodawania, dzięki czemu nie tracimy narożnego punktu obrazka, który w wielu przypadkach określa kolor przeźroczysty - maskę. Cały kod prezentuje się następująco (użyta w nim procedura skalowania jest tą, która zastosowana była w poprzednim punkcie): procedure ScaleImageList(imgList: TImageList; const ScaleM, ScaleD: Integer); var ii: integer; orgW, orgH: Integer; mb, ib, sib, smb: TBitmap; tmpImgList: TImageList; begin orgW := imgList.Width; orgH := imgList.Height; // tworzymy listę tymczasową tmpImgList := TImageList.Create(nil); try tmpImgList.Height := imgList.Height; tmpImgList.Width := imgList.Width; tmpImgList.ColorDepth := imgList.ColorDepth; // kopiujemy obrazki z obecnej for ii := 0 to imgList.Count - 1 do tmpImgList.AddImage(imgList, ii); // ustalamy rozmiar po przeskalowaniu imgList.SetSize(MulDiv(orgW, ScaleM, ScaleD), MulDiv(orgH, ScaleM, ScaleD)); for ii := 0 to tmpImgList.Count - 1 do begin ib := TBitmap.Create; // obrazek mb := TBitmap.Create; // maska try ib.Width := orgW; ib.Height := orgH; ib.Canvas.FillRect(ib.Canvas.ClipRect); mb.Width := orgW; mb.Height := orgH; mb.Canvas.FillRect(mb.Canvas.ClipRect); // pobieramy obrazek oraz maskę ImageList_DrawEx(tmpImgList.Handle, ii, ib.Canvas.Handle, 0, 0, ib.Width, ib.Height, CLR_NONE, CLR_NONE, ILD_NORMAL); ImageList_DrawEx(tmpImgList.Handle, ii, mb.Canvas.Handle, 0, 0, mb.Width, mb.Height, CLR_NONE, CLR_NONE, ILD_MASK); // skalujemy ScaleBitmap(im, ScaleM, ScaleD); ScaleBitmap(mb, ScaleM, ScaleD); // dodajemy do listy imgList.Add(sib, smb); finally ib.Free; mb.Free; end; end; finally tmpImgList.Free; end; end;

Komponenty z rysowanymi samodzielnie elementami

Wiele ze standardowych komponentów udostępnia zdarzenia pozwalające na przejęcie kontroli nad rysowaną pozycją. Dzieje się to albo poprzez samo przypisanie zdarzenia, albo dodatkowo ustawiając właściwość, np. w przypadku TComboBox: Style ustawiane na csOwnerDrawFixed.

Aby aplikacja była zgodna z innymi rozdzielczościami, niż standardowa, należy pamiętać, aby wszystkie wartości dotyczące położenia i wielkości elementów były odpowiednio przeskalowane. Dlatego nie wystarczy do narysowania kwadratu użyć Sender.Canvas.Rectangle(1, 1, 15, 15), ale należy pamiętać o przeliczeniach, np. dla powyższego przypadku: xy1 := MulDiv(1, Screen.PixelsPerInch, Form1.PixelsPerInch); xy15 := MulDiv(15, Screen.PixelsPerInch, Form1.PixelsPerInch); Sender.Canvas.Rectangle(xy1, xy1, xy15, xy15);

Pamiętać też trzeba o takich właściwościach, jak ItemHeight występujący w komponentach TCustomComboBox i potomnych, czy zdarzeniach, które mają za zadanie zwracać wysokość elementu. Wszędzie trzeba stosować przeliczenie względem aktualnej rozdzielczości ekranu. Takich przypadków, gdzie trzeba pamiętać o konieczności ręcznego dostosowania kodu lub właściwości jest kilka, a wszystko zależy od użycia. Niestety, nic nie stanie się automatycznie - programista musi pamiętać o tym sam i podczas tworzenia formy zmienić odpowiednio parametry komponentów.

TFrame - komponent z własną definicją DFM tworzony dynamicznie

Obiekty TFrame są bardzo wygodnym rozwiązaniem tworzenia "skupiska" komponentów na pewnym panelu. O ile umieszczone są one statycznie w programie (co zmniejsza ilość kodu w przypadku wielokrotnego użycia oraz zawsze podnosi poziom abstrakcji, co pozytywnie wpływa na czytelność kodu) to nie ma problemu - podczas skalowania formy także sama ramka jak i wszystkie niezbędne komponenty na niej zostaną przeskalowane. Jednak często TFrame wykorzystuje się do tworzenia dynamicznych niestandardowych list lub zbiorów. A w tym momencie trzeba pamiętać o tym, że samo utworzenie nie zmieni już wartości zapisanych w zasobie DFM skojarzonym z daną klasą - trzeba zrobić to samemu! myFrame := TMyFrame.Create(Self); myFrame.ScaleBy(Screen.PixelsPerInch, Self.PixelsPerInch);

Oczywiście taką operację warto wykonać tylko warunkowo, jeśli parametry będą różne od siebie, aby nie obciążać niepotrzebnie procesora niepotrzebnym zadaniem. W tym miejscu dobrze widać, że używanie skalowania będzie miało zawsze negatywny wpływ na wydajność programu podczas projektowania interfejsu.

Pozostałe komponenty tworzone dynamicznie

Z TFrame poszło w miarę gładko. Ale co z innymi komponentami, które nie mają swoich definicji, a tworzymy je sami podczas działania programu? Oczywiście, można pozostawić kod takim, jakim jest, a na koniec dodać tylko linijkę z poleceniem ScaleBy. Ale w pewnych sytuacjach nie możemy sobie na to pozwolić i właściwości muszą być określone od razu. Przykład tworzenia listy 10 etykiet, które będą umieszczone jedna pod drugą z odstępem wynoszącym... 2 piksele dla standardowej rozdzielczości: var i, y: Integer; lbl: TLabel; begin y := 0; for i := 0 to 9 do begin lbl := TLabel.Create(Self); lbl.Parent := Panel1; lbl.Top := y; lbl.Left := MulDiv(4, Screen.PixelsPerInch, Self.PixelsPerInch); Inc(y, lbl.Height + MulDiv(2, Screen.PixelsPerInch, Self.PixelsPerInch)); end; end;

Istotne są tutaj dwie kwestie. Pierwsza, to oczywiście ustawienia położenia - od lewej musimy wartość 4 pikseli dla standardowej rozdzielczości odpowiednio przeskalować. Druga - trudniejsza - to położenie od góry. Dla aplikacji niezgodnej z ekranami wysokiej rozdzielczości programista pewnie pójdzie na łatwiznę i zrobi to tak: lbl.Top := i * 18 {16 z czcionki i 2 odstępu}; Tak, działało. Może ktoś bardziej przezorny użyje: lbl.Top := i * (lbl.Height + 2); Ale wciąż nie jest tutaj przeliczony odstęp. Jeśli i to uwzględnimy, jest już dobrze: lbl.Top := i * (lbl.Height + MulDiv(2, Screen.PixelsPerInch, Self.PixelsPerInch)); Dlaczego w kodzie zatem użyto pomocniczej zmiennej y? Czy coś to zmienia? W tym przypadku nie, ale po prostu operacja dodawania jest szybsza, niż mnożenia. Poza tym, jeśli np. dla każdej z etykiety będziemy chcieli użyć innej czcionki, będzie prościej.

Jednak trzeba zaznaczyć tu istotną kwestię. Gdzie kończy się nasza lista? W przypadku braku skalowania będzie to 178 piksel (16 × 10 + 9 × 2). Jeśli taka będzie np. wysokość panelu, to po przeskalowaniu o 125% dostanie on wysokość 223 piksele. A gdzie w rzeczywistości będzie koniec? Zwiększy się wysokość czcionki do 20 pikseli, a odstęp wyniesie 3 piksele (po zaokrągleniu: 2 × 125%). 20 × 10 + 9 × 3 daje 227! Aż o 4 piksele więcej, niż wynika to z proporcji. I w tym momencie okazuje się, że panel musi mieć indywidualnie zmienioną szerokość. Na nic zda się automatyzacja – trzeba wysokość zmienić ręcznie.

Rozmieszczając więc dynamicznie elementy koniecznie trzeba pamiętać, że w wyniku zaokrągleń interfejs może się zupełnie rozjechać! Czy da się temu zaradzić? Tak! Odstępy nie mogłyby być stałe, lecz musiały być raz 2, a raz 3 piksele (gdyż rzeczywisty odstęp po założonym przeskalowaniu powinien wynieść 2,5 piksela). Czy będzie to wyglądało ładnie? Tu wszystko zależy od projektu interfejsu.

To, na co raz jeszcze zwracam uwagę, to absolutna minimalizacja używania stałych, a maksymalnie używanie już wyliczonych własności komponentów, gdzie to tylko możliwe. Nie posługujmy się więc wartością np. 16 jako wysokość czcionki, lecz pobierzmy tą wysokość z komponentu czyli Label.Height. Jeśli gdzieś już wprowadzamy stałą - trzeba pamiętać o jej przeskalowaniu. I na koniec odpowiedzieć na pytanie - czy zależy nam bardziej na jednorodności interfejsu czy jego wierniejszym przeskalowaniu.

To wciąż nie jedyne rzeczy, na które trzeba zwrócić uwagę. Załóżmy, że nasza aplikacja wyświetla ikonę samej siebie (lub innego programu) pobranej z zasobów. Parametrami funkcji LoadImage są m.in. rozmiary. Także tu trzeba pamiętać o skalowaniu. Pozytywnym jest tutaj fakt, że jeśli w zasobach będzie ikona o danym rozmiarze - zostanie pobrana ona. Jeśli nie - przeskalowana ta o rozmiarze mniejszym. Dzięki temu wykorzystany zostanie atut wysokiej rozdzielczości.

Skoro już przy grafice jesteśmy, można wspomnieć też o wyświetlaniu lub tworzeniu grafiki dynamicznie. Wyobraźmy sobie np. program rysujący fraktale. Oczywiście, jeśli tworzymy aplikację DPI-Aware to absolutnie nie skalujemy wyliczonych wartości, ale zawsze używajmy kontekstu rozmiaru obszaru, po którym rysujemy. Dzięki temu aplikacja wyświetli piękną grafikę w wysokiej rozdzielczości. Trzeba zatem uważać, aby nie popaść w hurtowe skalowanie absolutnie wszystkich wyliczeń.

Znane błędy

Niestety, poza usilnymi działaniami programisty, środowisko i komponenty dostarczane wraz z nim dopiero raczkuje w tej technologii. Sporo błędów związanych z poprawną obsługą ekranów wysokiej rozdzielczości zostało naprawionych dopiero w wersji 10.2.2, 10.2.3 i 10.3 (na moment pisania tego tekstu jeszcze niedostępna dla wszystkich). Najbardziej dokuczliwym w moim odczuciu było (w wersji 10) kładzenie paneli na zakładkach TPageControl, grupowany styl wyświetlania listy TListView czy umieszczanie położenia skrótów klawiaturowych na pozycjach menu. Na chwilę obecną rozwiązanych we wspomnianych wersjach jest przeszło 100 różnych błędów, a drugie tyle oczekuje na rozpatrzenie lub naprawę. W większości dotyczy to platformy VCL, ale niemało także związanych jest z FireMonky, które od początku było przecież tworzone z myślą o skalowalności interfejsu.

A co z innymi popularnymi zestawami? JVCL praktycznie w ogóle nie nadaje się do aplikacji DPI-aware, jeśli tylko korzystamy z jakichś opcji, które wymagają od komponentu rysowania „po swojemu” (np. TJvMenu i TJvPopupMenu). Swoją drogą, wszelkie menu też bardzo często są raportowane jako niedziałające poprawnie w określonych warunkach.

THighDPIHelper - moja pomoc w szybkim uczynieniu aplikacji DPI-Aware

Wszystkim, którzy pragną jak najmniejszym kosztem przygotować aplikację zgodną z ekranami wysokiej rozdzielczości polecam skorzystanie z mojego komponentu THighDPIHelper . W najprostszym scenariuszu wystarczy wyłącznie położenie tego niewizualnego komponentu na każdej z formatek i skompilowanie projektu. Więcej informacji można znaleźć na stronie komponentu, gdzie znaleźć można opis możliwości i dokładny opis sposobu użycia w najczęstszych przypadkach.

Informacje o poradzie
Poradę czytano:827 razy
Ocena porady:
5,50 (2 oceniających)
(Kliknij właściwą gwiazdkę, by oddać głos)

Wróć

Komentarze (0)


Ładowanie komentarzy... Trwa ładowanie komentarzy...

Uwaga! Wszystkie porady na tej stronie są mojego autorstwa i objęte są prawami autorskimi! Kopiowanie, publikowanie lub cytowanie całego tekstu bez wiedzy autora - ZABRONIONE! Dowiedz się więcej o prawach autorskich

Strona istnieje od 25.01.2001
Ta strona używa plików Cookie.
Korzystając z niej wyrażasz zgodę na przetwarzanie danych a zakresie podanym w Polityce Prywatności.
 
archive To tylko kopia strony wykonana przez robota internetowego! Aby wyświetlić aktualną zawartość przejdź do strony.

Optymalizowane dla przeglądarki Firefox
© Copyright 2001-2024 Dawid Najgiebauer. Wszelkie prawa zastrzeżone.
Ostatnia aktualizacja podstrony: 11.07.2023 21:16
Wszystkie czasy dla strefy czasowej: Europe/Warsaw