Zasoby. Jak poprawnie je dodać w aplikacji

0

Cześć
Pierwszy raz tworze zasoby więc mam kilka pytań.

Jak w Delphi 10.1 Berlin takowe stworzyć.
Zrobiłem tak:
Project ->Resources and Images -> dodałem pliki .bmp

pojawił mi się plik po zapisaniu w folderze z plikami Project1Resource.rc
Ładuje plik .bmp do TImage

Image1.Picture.Bitmap.LoadFromResourceName(hInstance, '01_bitmap');

Wyświetla, z tym że podglądając plik Project1Resource.rc w notatniku jest w nim ścieżka do pliku
Bitmap_1 BITMAP "..\\..\\Pictures\\01BMP.bmp"

Podobnie jak w oknie Project Manager jest "drzewko" do pliku. Po usunięciu obrazka z folderu wyświetla komunikat iż nie ma pliku i się nie z kompiluje. Przywrócę jest ok. Więc nie o to chyba w zasobach chodzi.

Robię to z kompendium tyle że widocznie mam nowszą wersje Delphi i nie mam dokładnie tego jak to tam pisze. Szukałem.
Więc jak to poprawnie zrobić w Delphi 10.1 Berlin?

Docelowo interesują mnie typy plików .jpg, .png oraz ładowanie ich do TImage i TPanel z zasobów. więc tu też bym prosił o odpowiedź. Czy też nadal się je ładuje ręcznie jak to jest opisane w kompendium na temat zasobów. Czy może już inaczej? Pytam bo widziałem w oknie Resources and Images w filtrze z rozszerzeniami typy plików .png i .jpg. Więc może nie trzeba tego ręcznie robić? Więc jak?

0

Wszystko robisz dobrze, tyle że jeśli używasz plików z rozszerzeniem .rc to pliki, do których znajdują się w nim ścieżki, musisz również trzymać na dysku. Pliki .rc nie zawierają w sobie zasobów, a tylko informują kompilator gdzie znajdują się one na dysku. Tak więc musisz trzymać na dysku zarówno pliki .rc, jak i wszystkie obrazki, na które wskazuje jego zawartość.

Jeżeli nie chcesz trzymać gołych obrazków na dysku to musisz skompilować plik .rc do formatu .res - wtedy wszystkie pliki na które wskazuje .rc zostaną skompilowane do jednego pliku zasobów o rozszerzeniu .res. Po stworzeniu takiego pliku również możesz dołączyć go do zasobów programu (używając dyrektywy $R w kodzie modułu), a bitmapki i inne obrazki możesz już usunąć z dysku.

Dawniej pakiet Delphi posiadał program brcc32.exe, który to był kompilatorem zasobów i z poziomu konsoli można było skompilować pliki .rc do formatu .res. W najnowszej wersji nie wiem jak to wygląda, jednak skoro kompilator Delphi jest przystosowany do automatycznej kompilacji plików .rc to należy z tego mechanizmu korzystać (zaoszczędzi się dużo czasu).

0

Dzięki Wielkie za odpowiedź.
Całkiem przypadkiem dowiedziałem się o plikach .rc o których nie miałem pojęcia.
Tylko jaki jest sens trzymania kolejnego pliku? skoro musimy i tak trzymać wszystkie pliki fizycznie to już lepiej podać ścieżkę do nich bezpośrednio w kodzie na jedno wyjdzie tak mi się wydaje. Ale to pytanie z ciekawości.

Cały czas chcę jednak ukryć te pliki by nie pogubić ich przypadkiem, tylko nie wiem gdzie to jest ukryte w nowym Delphi. Na pewno nie w tym oknie Resources and Images i nie tak jak to jest opisane w kompendium. Z tego okna wyszedł mi właśnie plik .rc

I ponowie też pytanie o pliki .jpg i .png odnośnie zasobów Czy ręcznie czy już coś jest innego? Dlatego iż w powyższym oknie są dodane takie typy plików w filtrze rozszerzeń tylko że w Resource Type mamy do wyboru BITMAP, CURSOR, ICON, RCDATA i FONT tylko.
Młody to zdziwiony :)

0

Dodam tylko że mam taką aplikacje jak brcc32.exe ale uruchamia się i zamyka. Muszę to chyba gdzieś w Delphi znaleźć.

0

Tylko jaki jest sens trzymania kolejnego pliku? skoro musimy i tak trzymać wszystkie pliki fizycznie to już lepiej podać ścieżkę do nich bezpośrednio w kodzie na jedno wyjdzie tak mi się wydaje. Ale to pytanie z ciekawości.

No nie - jeśli używasz plikow .rc to zewnętrzne bitmapy i inne obrazki musisz trzymać na dysku, ale tylko do kompilacji. Po przekompilowaniu projektu, wszystkie pliki na które wskazuje zawartość pliku .rc zostaną dodane do zasobów pliku wykonywalnego. Tak więc jako deweloper, pliki graficzne musisz trzymać na dysku, ale użytkownik który będzie używał pliku wykonywalnego Twojego programu już tych plików nie potrzebuje, bo będą się znajdować wewnątrz pliku .exe.

Dodam tylko że mam taką aplikacje jak brcc32.exe ale uruchamia się i zamyka. Muszę to chyba gdzieś w Delphi znaleźć.

Ten program uruchamia się np. z poziomu konsoli - otwierasz sobie program cmd.exe, w konsoli przechodzisz do katalogu z kompilatorem brcc32.exe i z linii poleceń uruchamiasz ten kompilator, podając w argumencie nazwę pliku .rc. Po chwili plik .res zostanie wygenerowany.


Ogólnie to nie polecam Ci bawić się w ręczną kompilację zasobów, bo póki co nic z tego nie rozumiesz.

0

nie jestem mocny w tym temacie, ale kiedy potrzebowałem dołączyć do projektu dodatkowe pliki,robiłem to tak ...
Utworzyłem plik tekstowy np. "myresources.rc' o przykładowej treści jak poniżej ....
pjpk115 RCDATA .\res\pjpk115.exe
i dodałem go do projektu.
W pliku mam nazwę zasobu , jego typ oraz ścieżkę do pliku źródłowego .
Po kompilacji zewnętrzny plik, w tym wypadku 'pjpk115.exe', jest dołączony do zasobów pliku wykonywalnego mojej aplikacji,
A chcąc go pobrać z zasobów robię to tak :

lRs := TResourceStream.Create(HInstance, 'pjpk115', RT_RCDATA);

Tym samym dystrybuując aplikację nie muszę się martwić o pliki z zasobami zawierającymi dodatkowe pliki, bo te już siedzą w pliku aplikacji

0

Tak tak teraz zakapowałem :) Dzięki wielkie :)
Czyli faktycznie robiłem wszystko dobrze czyli w moim przypadku plik .res da mi to że ja jako deweloper też nie będę potrzebował fizycznie plików a przy .rc tak. Lecz po komplikacji w tym i w tym przypadku mamy to w pliku .exe

To jeśli mogę za dam jeszcze jedno pytanie w tym oknie Resources and Images mogę dodać typy plików też o których pisze .jpg i .png wszystko pięknie tylko że muszę jakiś typ określić BITMAP, CURSOR, ICON, RCDATA i FONT a to żaden z powyższych. Jak coś takiego załadować np.: w TImage chociażby przez zdarzenie OnClick Buttona?

0

Czyli faktycznie robiłem wszystko dobrze czyli w moim przypadku plik .res da mi to że ja jako deweloper też nie będę potrzebował fizycznie plików a przy .rc tak. Lecz po komplikacji w tym i w tym przypadku mamy to w pliku .exe

W skrócie - będziesz ich (plików graficznych) potrzebował do kompilacji, ale nikt nie będzie ich potrzebował do uruchomienia programu.

To jeśli mogę za dam jeszcze jedno pytanie w tym oknie Resources and Images mogę dodać typy plików też o których pisze .jpg i .png wszystko pięknie tylko że muszę jakiś typ określić BITMAP, CURSOR, ICON, RCDATA i FONT a to żaden z powyższych.

Pliki innego typu (dowolnego) możesz dodać jako RCDATA. Potem przy ładowaniu pliku z zasobu musisz skorzystać z RT_RCDATA, aby dało się go załadować do pamięci.

Poniżej przykład ładowania pliku PNG o nazwie zasobu MyPNG z sekcji RCDATA:

var
  LImageRes: TResourceStream;
begin
  LImageRes := TResourceStream.Create(hInstance, 'MyPNG', RT_RCDATA);

Po załadowaniu zasobu do strumienia, można przekopiować jego zawartość do komponentu TImage:

MyImage.Picture.LoadFromStream(LImageRes);
0

Jejku DZIEKUJE BARDZO :)

0

Mogę prosić jeszcze małą podpowiedź. Kompilator wyświetla błąd podkreślając LoadFromStream

Cannot access protected symbol TPicture.LoadFromStream

0

Zadziałało mi coś takiego to jest gotowiec znaleziony, dlatego pytam

var
  Png: TPngImage;
  Begin
 try
  PNG := TPngObject.Create;
  Png.LoadFromResourceName(hInstance, 'PngImage_1');
  Image1.Picture.Graphic := Png;
finally
    Png.Free;
end;

Twój kod zdecydowanie lepszy tylko nie rozumiem błędu, tu do każdego pliku osobna zmienna, osobne zwolnienie, do jednego pliku ok ale do więcej to jest porażka. Jak temu zaradzić?

0

spróbuj tak:

lRs := TResourceStream.Create(....);
Image.Picture.Bitmap.LoadFromStream(lRs) 
0

Dzięki za chęci
Skompiluje ale wyświetli w aplikacji błąd Bitmap image is not valid ale to dlatego że mamy typ RCDATA a w nim .png. Jeśli bym zmienił na BITMAP to w ogóle nie skompiluje nie zgodność typów czemu się nie dziwie. Już wcześniej tego próbowałem. Jednym wyjściem chyba jest powyższy kod i zrobienie jakieś procedury ogólnej a we właściwym miejscu tylko ładować obrazek. Powyżej jedna zmienna odpowiada za wszystko i dlatego sobie nie radze z tym, oczywiście mogę zrobić dla każdego obrazka nową ale to jest bez sensu przy większej liczbie.

0

przenieś to do procedury albo funkcji

procedure loadPngFromResources(aImage: Timage; aPngName: string);
var
  Png: TPngImage;
Begin
  try
    Png := TPngObject.create;
    Png.LoadFromResourceName(hInstance, aPngName);
    aImage.Picture.Graphic := Png;
  finally
    Png.Free;
  end;
End;

i w jednym wierszu masz wywołanie procedury

 loadPngFromResources(self.image, 'PNG_1');
0

Dziękuje :) , nie umiałem tej nazw zasobu ogarnąć a wystarczyło tylko zmienną string podstawić.
Wiedziałem że to mam przenieść ale świeżak zawsze coś z partaczy :) Jest coś na ten temat konkretnie w kompendium, może coś ominąłem. Na razie jestem za młody więc pozostaje na razie samodoskonalenie. Albo jak to się konkretnie nazywa to przenoszenie to sam sobie w necie poszukam. Chyba że pozostaje tylko myśleć :) Dzięki Wielkie :)

0

Ostatnia podpowiedz i nie marudzę :)

Jak tu się pod to podszyć w linii zamiast ścieżki, tak by:
filename := loadPngFromResources('PNG_1'); no ale tak nie może być

procedure LoadImage(filename: string; aimage: Timage);
begin
  if not FileExists(filename) then
    filename := 'Graphic\Zasoby\01.png';   
0

ogarnięte Jeszcze raz Dzięki

0

Poznaję ten kod - pochodzi on z innego wątku. Tamten kod dodatkowo posiadał blok try except, którego nie podałeś tutaj, a on ma w tym przypadku znaczenie. No nic, sugeruję coś takiego:

procedure LoadDefaultImage(AImage: TImage);
var
  LImage: TPngImage;
begin
  LImage := TPngImage.Create();
  try
    LImage.LoadFromResourceName(hInstance, 'PNG_1');
    AImage.Picture.Graphic := LImage;
  finally
    LImage.Free();
  end;
end;

procedure LoadImage(AFileName: String; AImage: TImage);
begin
  if not FileExists(AFileName) then
    LoadDefaultImage(AImage)
  else
  try
    AImage.Picture.LoadFromFile(AFileName);
  except
    LoadDefaultImage(AImage);
  end;
end;

Dzięki temu domyślny obraz zostanie załadowany wtedy, gdy plik pod zadaną ścieżką nie istnieje lub wystąpi jakikolwiek błąd z jego załadowaniem. W przeciwnym razie zostanie prawidłowo wczytany z pliku do pamięci.

Przy okazji - nie mam Delphi, ale czy nie dałoby się krócej zapisać ładowania obrazu z zasobów? Mam na myśli wykluczenie używania dodatkowej zmiennej lokalnej i wpisanie do właściwości Graphic referencji prosto z konstruktora:

procedure LoadDefaultImage(AImage: TImage);
begin
  AImage.Picture.Graphic := TPngImage.Create();
  AImage.Picture.Graphics.LoadFromResourceName(hInstance, 'PNG_1');
end;

IMO też powinno śmigać, a kodu 2x mniej.

0

[furious programming] To co podał [grzegorz_so ] to to jest to tylko już myślałem że pójdzie bez tej zmiennej ale pomyliłem się Jednak muszę to wywołanie procedury

loadPngFromResources(self.image, 'PNG_1');

Zrobić coś takiego

filename := loadPngFromResources('PNG_1'); no ale tak się nie da

procedure LoadImage(filename: string; aimage: Timage);
begin
  if not FileExists(filename) then
    filename := 'Graphic\Zasoby\01.png'; 
0

Jak chcesz konkretną odpowiedź to zadaj konkretne pytanie, bo póki co nawet nie wiadomo czy nasze odpowiedzi cokolwiek zmieniły i czy problem jest już rozwiązany, czy nadal czegoś nie rozumiesz.

0

Myślałem że udał mi się rozwiązać ale po testach poległem, W programie mam taki kod, oczywiście można go też sensowniej napisać tylko by jego założenia pozostały.

procedure LoadImage(filename: string; image: Timage);
begin
  if not FileExists(filename) then
    filename := 'Graphic\Zasoby\Brak photo.png'  
   try
    Image.picture.loadfromfile(filename);
    except
    on e: exception do
    raise Exception.Create('Error' + ' ' + e.Message)
  end;
end;

W linii:

filename := 'Graphic\Zasoby\Brak photo.png'

Zamiast ścieżki relatywnej do pliku chciałbym umieścić ten plik z zasobów,
Najlepiej gdyby tu szło umieścić wywołanie procedury które podał [grzegor_sz] czyli:

loadPngFromResources(image1, 'PNG_1');

tylko bez image1 bo gdzie to osadzić akurat w tym wypadku określa moja zmienna filename
w dalszej części kodu.
A tu cała procedura którą podał [Grzegorz_sz]

procedure loadPngFromResources(aImage: Timage; aPngName: string);
var
  Png: TPngImage;
begin
  try
    Png := TPngObject.create;
    Png.LoadFromResourceName(hInstance, aPngName);
    aImage.Picture.Graphic := Png;
  finally
    Png.Free;
  end;
end;


0

Zamiast ścieżki relatywnej do pliku chciałbym umieścić ten plik z zasobów, najlepiej gdyby tu szło umieścić wywołanie procedury które podał [grzegor_sz] czyli:

loadPngFromResources(image1, 'PNG_1');

No przecież w poprzednim poście swoim poście podałem Ci gotowca... czytasz to co się do Ciebie pisze?

tylko bez image1 bo gdzie to osadzić akurat w tym wypadku określa moja zmienna filename w dalszej części kodu.

NIE - zmienna filename ma określać ścieżkę pliku graficznego, znajdującego się na dysku. Jeśli obrazka nie będzie na dysku lub załadowanie go do pamięci z jakiegoś powodu nie będzie możliwe, to ta zmienna stanie się bezużyteczna. W takim przypadku masz załadować obraz z zasobów, a nie z pliku i dokładnie to realizuje kod zaproponowany przeze mnie.

Przy okazji - jak potrzebujesz zareagować na wyjątek (czyli coś wykonać) i puścić go dalej to do tego celu używa się samego raise:

try
  // kod mogący rzucić wyjątek
except
  // kod wykonywany po zaistnieniu wyjątku
  raise; // puszczenie wyjątku do metody nadrzędnej
end;

Dzięki temu wyjątek poleci sobie dalej, ale ani jego klasa, ani też jego treść nie zostaną zmienione.

0

Działa dodałem drugi kod też pozmieniałem jak radziłeś chodzi miodzio : DZIĘKUJĘ) Co pozmieniać by zamiast .png dostać się do .txt

procedure LoadDefaultImage(AImage: TImage; aPngName: string);
var
  LImage: TPngImage;
begin
  LImage := TPngImage.Create();
  try
    LImage.LoadFromResourceName(hInstance, aPngName);
    AImage.Picture.Graphic := LImage;
  finally
    LImage.Free();
  end;
end;
0

A co mają wspólnego pliki .png z plikami .txt, oczywiście oprócz tego, że i jego i drugie to pliki? Wymiękam, serio.

0

Nic nie mają wspólnego, to nie TpngImage chciałem go po prostu też ukryć i zamiast trzymać na dysku w odpowiednim miejscu wczytać z zasobów wiem że nie w tej procedurze ale w podobnej a potem ją wywołać i podłożyć np. zamiast

LoadFromFile('C:\01.txt)

0

Prawie zrobione działać działa nie umiem tylko w poniższym zrobić takiego odwołania

LoadResurceTxt(List, 'ID_zasobu');     

Podświetla mi List że nie rozpoznaje

a o to cały kod:

Procedure TForm1.LoadResurceTxt (List:Tstringlist; TxtIdName:string);
var
Stream: TResourceStream;
begin
  Stream := TResourceStream.Create(HInstance, 'IDfile.txt', RT_RCDATA);
  try
    List := TStringList.Create;
    try
      List.LoadFromStream(Stream);
    finally
      List.Free;
    end;
  finally
    Stream.Free;
  end;
end;
0

Podświetla mi List że nie rozpoznaje

Bo go nigdzie nie zadeklarowałeś.

0

Zadeklarowałem List w procedurze w której chce ją wywołać, nie ma błędów w kompilacji ale jest w programie
Access violation at adres......

Procedure TForm1.LoadResurcetxt(List:Tstringlist; TxtIdName:string);
var
Stream: TResourceStream;
begin
   List := TStringList.Create;
  try
   Stream := TResourceStream.Create(HInstance, TxtIdName, RT_RCDATA);
   List.LoadFromStream(Stream);
   finally
    Stream.Free;
    List.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
List: TStringList;
begin
i := Random(i);
try
LoadResurcetxt(List,'Resource_1');
i := Random(Pred(List.Count));
Memo1.Lines.Add('');
Memo1.Lines[5]:=List.Strings[i];
finally
List.Free;
end;
end;

Jak wszystko dam pod Button to chodzi dobrze jeszcze mam coś z wywołaniem. Dziękuje nie tylko za pomoc ale i za cierpliwość.

0

Zadeklarowałem List w procedurze w której chce ją wywołać, nie ma błędów w kompilacji ale jest w programie Access violation at adres......

Oczywiście podanie w której linijce występuje wyjątek jest zbyt trudne.

procedure TForm1.LoadListFromResource(AList: TStringList; AResName: String);
var
  LStream: TResourceStream;
begin
  LStream := TResourceStream.Create(hInstance, AResName, RT_RCDATA);
  try
    AList.LoadFromStream(LStream);
  finally
    LStream.Free();
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LList: TStringList;
begin
  LList := TStringList.Create();
  try
    LoadListFromResource(LList, 'Resource_1');
    
    Memo1.Lines.Add('');
    Memo1.Lines[5] := LList[Random(LList.Count)];
  finally
    LList.Free();
  end;
end;

Tylko nie zapomnij gdzieś na początku programu wywołać Randomize, najlepiej w głównym pliku projektu (plik .dpr) wpisz wywołanie tej procedury zaraz pod begin otwierającym główny blok kodu. A jeśli ta rocedura jest wywoływana gdzieś indziej to usuń te wywołania i zostaw jedynie to w głównym pliku projektu.

Lepszym rozwiązaniem było by załadowanie listy z zasobów na początku programu (np. w konstruktorze klasy formularza) i zwolnienie jej przy zamykaniu (np. w destruktorze klasy okna). Dzięki temu można by jedynie przepisać linijkę z listy do Memo po kliknięciu w przycisk, zamiast w kółko ładować dane z zasobów i je zwalniać.

Poza tym, jeśli w Memo nie będzie co najmniej sześciu linii tekstu to program wygeneruje wyjątek w linii, w której odwołujesz się do pozycji o indeksie 5, czyli w tej linijce kodu:

Memo1.Lines[5] := LList[Random(LList.Count)];
0

Dziękuję jak powiedziałem nie tylko za pomoc ale za dużo dużo i jeszcze więcej cierpliwości.
Poza tym dużo też dodajesz od siebie jak chociażby z tym Randomize wielki szacun i duży browar dla tego Pana.
DZIĘKUJĘ

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