Gradle+Java, immagini non trovate

Stato
Discussione chiusa ad ulteriori risposte.

BAT

Moderatore
Staff Forum
Utente Èlite
22,946
11,581
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
Premessa:
to sviluppando una piccola utility desktop usando:
  • Intellij IDEA community Edition, in ultima versione
  • Java 11 come linguaggio di programmazione
  • JavaFX 11 come librerie grafiche
Programmando senza uno strumento di building separato (ANT, Maven, Gradle) la GUI viene esattamente come l'ho pensata: dall'interno dell'IDE si avvia e funziona tutto quanto.
Il problema è nato quando ho tentato di generare il jar finale (artifact) per la distribuzione:
per le versioni di Java successive alla 8, gli sviluppatori devono distribuire il "prodotto" finale come unità indipendente, in pratica gli IDE generano l'artifact che contiene già tutto (moduli runtime Java+JavaFX e, per Windows, un file .exe per Windows con cui avviare l'applicazione).
Affinché ciò avvenga però è necessario un generatore di build (dopo rapida ricerca ho optato per Gradle che a quanto pare sia il meglio disponibile).

Problema:
la stuttura di directory pensata per applicazioni non modulari non funziona per i progetti modulari: così ho generato un progetto Gradle (la versione integrata in intellij IDEA dovrebbe essere la 6.8, almeno così mi sembra di capire dal file che allego).
Nello specifico esiste una directory chiamata resources dove si dovrebbero inserire i file utili per l'applicazione, per esempio le immagini.
directory.png

E qui viene il problema: dentro la cartella resources ho creato una sottocartella img con una struttura di sottodirectory che contengono immagini ma qualunque path di cartelle passi come parametro, il metodo Java getClass().getResourceAsStream(percorsoFileImmagine) mi restituisce null;
ho provato tutti i percorsi ragionevoli e pure quelli irragionevoli, come
/img/bottoni/search.png (percorso assoluto che è quello che ha sempre funzionato)
img/bottoni/search.png
./img/bottoni/search.png
/img/bottoni/search.png
search.png
(ho messo direttamente il singolo file direttamente in resources, niente da fare)
La cosa strana è che se ci metto un file di database (sqlite) ed il relativo driver in formato .jar, vengono tranquillamente visti ed usati, ma per le immagini niente!
Quindi c'è qualcosa che mi sfugge:
la mia ipotesi è che il metodo Java precedentemente citato NON vada nella directory giusta, questa è la struttura generata dalla compilazione nella directory build:
dirbuild.png
La cartella con le risorse viene ricreata con tutti i file di immagine all'interno ma le classi .class evidentemente non vi accedono, nel senso che evidentemente il metodo getClass().getResourceAsStream(percorsoFileImmagine) non va affatto lì dentro ma si aspetterebbe qualcosa nella stessa directory dei file compilati .class
Qui le cose sono due:
1. o il metodo Java che sto usando per "pescare" le immagini non è quello corretto
2. oppure dovrei metter mano al file di configurazione di Gradle (lo allego) per dirgli di fare qualcosa tipo ricreare la directory img all'interno di quella coi .class (la cosa però non mi va giù, a cosa diavolo serve avere una directory da cui prelevare le risorse se poi ce le devo copiare "a mano"?
Cioè insomma, con uno strumento moderno mi aspetterei che il lavoro si semplificasse invece che complicarsi.
Questo è il file di configurazione build.gradle:
Codice:
plugins {
    id 'java'
    id 'application'
    id "org.openjfx.javafxplugin" version "0.0.10"
}

group 'org.bat'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}

javafx {
    version = "11.0.2"
    modules = ['javafx.controls', 'javafx.fxml']
}

mainClassName = 'blam.Blam'

test {
    useJUnitPlatform()
}
questo invece è gradle-wrapper.properties (da cui deduco la versione di Gradle)
Codice:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Quindi la domanda è: come fare per pescare le immagini?
Qui sotto l'immagine di come dovrebbero essere i bottoni:
help.png
 

Skills07

Head of Development
Staff Forum
Utente Èlite
35,439
11,502
CPU
Ryzen 7 5800x
Dissipatore
Deepcool gammax l240 v2
Scheda Madre
Aourus Gigabyte X-470 ultra gaming
HDD
Samsung Evo 850 250gb ,2tb x 2 toshiba P300, 256 Ssd silicon power, Samsung evo plus 1tb nvme
RAM
32 gb G.Skill Tridentz 3200 mhz RGB
GPU
Zotac - RTX 3070 Ti Trinity OC
Monitor
ASUS TUF QHD 165hz IPS, ITEK GCF 165hz IPS QHD, Samsung Curvo 144hz QHD
PSU
Seasonic Focus 650w 80 platinum
Case
Coolermaster h500p
Periferiche
Asus Tuf Wireless
Net
Fibra Tim 100mb
OS
Windows 10 Pro 64 bit
mi serve qualche dettaglio in piu, in primis dove ti da l'errore (la riga di codice precisa).
Poi che errore ti da la gradle perchè se non trova l'immagine sicuro in fase di compile ti darà qualche segnalazione.

prova questo metodo:
Java:
public static Image createImage(Object context, String resourceName) {
  URL _url = context.getClass().getResource(resourceName);
  return new Image(_url.toExternalForm());
}

vediamo cosa fa
l'importante è capire dove prendere la resource
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,946
11,581
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
in primis dove ti da l'errore (la riga di codice precisa).
non mi dà nessun errore perché il codice di creazione dell'immagine è in un try-catch:
Java:
try {
            img = new Image(getClass().getResourceAsStream(tb.getIconPath()));
            imgv = new ImageView(img);
            imgv.setFitWidth(buttonH); // ridimensiona secondo la base prefissata
            imgv.setPreserveRatio(true);
            imgv.setSmooth(true);
            imgv.setCache(true);
            setGraphic(imgv);
        } catch (NullPointerException ex) { // immagine non trovata
            //setText(testo);
        }
semplicemente la prima istruzione ritorna null, per cui il catch non fa nulla, i pulsanti vengono creati e funzionano ugualmente (tutta l'applicazione funziona);
Quel metodo che mi segnali lo avevo letto ieri sera su stackoverflow (https://stackoverflow.com/questions/59029879/javafx-image-from-resources-folder), ma cosa devo passare nel primo paramentro?
Cioè quell'Object context dove lo prendo/con cosa lo ricavo? il secondo parametro è chiaro, è il percorso dell'immagine sottoforma di stringa
 

Ibernato

Utente Èlite
4,330
2,047
OS
Windows 10 Pro / Ubuntu 22.04
@pabloski scommetto che hai la soluzione...? o magari @Andretti60 ? @Ibernato ?
Al volo, tramite ricerca su internet :)

Lifepaths.class.getClass().getResourceAsStream(...) loads resources using system class loader, it obviously fails because it does not see your JARs

//prova ad usare questo
Lifepaths.class.getResourceAsStream(...) loads resources using the same class loader that loaded Lifepaths class and it should have access to resources in your JARs
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,946
11,581
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
Al volo, tramite ricerca su internet :)
intanto grazie...
leggendo al volo vedo che il problema che mi linki è relativo all'accesso dentro file che siano già all'interno di un .jar, qui invece siamo al livello precedente, non esite ancora il .jar (siamo dentro l'IDE con un progetto da compilare va Gradle).
A naso la proposta di @Skills07 è la più papabile, cerco di provarle un po' tutte ma sono sicuro che i guai non finiscono qua...
[Edit]
vedo comunque altri articoli raggiungibili dall spunto che ni hai dato, me li leggo...
 
Ultima modifica:

Ibernato

Utente Èlite
4,330
2,047
OS
Windows 10 Pro / Ubuntu 22.04
intanto grazie...
leggendo al volo vedo che il problema che mi linki è relativo all'accesso dentro file che siano già all'interno di un .jar, qui invece siamo al livello precedente, non esite ancora il .jar (siamo dentro l'IDE con un progetto da compilare va Gradle).
A naso la proposta di @Skills07 è la più papabile, cerco di provarle un po' tutte ma sono sicuro che i guai non finiscono qua...
Si, però tu il jar lo hai pubblicato su maven, poi hai aggiunto la dipendenza nel gradle dove se lo scaricherà. Non dovrebbe essere la stessa cosa? Fai un tentantivo per curiosità.

Aspetta un attimo, ma dove hai messo la path per scaricareil jar da maven?
 
Ultima modifica:

BAT

Moderatore
Staff Forum
Utente Èlite
22,946
11,581
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
Si, però tu il jar lo hai pubblicato su maven
no no, niente Maven, nessun builder di jar, come spiegato nel primo post: non è stato possibile creare un .jar "totale" in quanto il progetto non era modulare, avevo usato un progetto standard java di Intell'j IDEA con configurazione in file .iml
vista l'impossibilità di creare un "eseguibile" ho dovuto creare un nuovo progetto modulare (con annesso file module-info.java che prima non avevo) dove contavo di fare un semplice copia-incolla; di fatto funzionerebbe pure, a parte il poco piacevole intoppo che ho con le immagini.
 

Ibernato

Utente Èlite
4,330
2,047
OS
Windows 10 Pro / Ubuntu 22.04
no no, niente Maven, nessun builder di jar, come spiegato nel primo post: non è stato possibile creare un .jar "totale" in quanto il progetto non era modulare, avevo usato un progetto standard java di Intell'j IDEA con configurazione in file .iml
vista l'impossibilità di creare un "eseguibile" ho dovuto creare un nuovo progetto modulare (con annesso file module-info.java che prima non avevo) dove contavo di fare un semplice copia-incolla; di fatto funzionerebbe pure, a parte il poco piacevole intoppo che ho con le immagini.
Però il jar viene visto, giusto?
Prova a fare come ti ho detto o come ti hanno suggerito nel primo post
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,946
11,581
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
Però il jar viene visto, giusto?
non esiste nessun jar, i file sono tutti sfusi (vedi immagini nel primo post)
devo trovare il modo di accedere alla cartella immagini, ma non con il metodo getResourceAsStrema()
probabilmente mi toccherà revisionare l'inetro codice, un null prima o poi fa casino, il try-catch che ho implementato in quel modo non va affatto bene.
 

Ibernato

Utente Èlite
4,330
2,047
OS
Windows 10 Pro / Ubuntu 22.04
non esiste nessun jar, i file sono tutti sfusi (vedi immagini nel primo post)
devo trovare il modo di accedere alla cartella immagini, ma non con il metodo getResourceAsStrema()
probabilmente mi toccherà revisionare l'inetro codice, un null prima o poi fa casino, il try-catch che ho implementato in quel modo non va affatto bene.
Ok ora è chiaro. Avevo letto velocemente ed ero da smartphone.
Allora prova a fare come suggerito al 1 post. Con il contesto dovrebbe andare.
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,946
11,581
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG

Skills07

Head of Development
Staff Forum
Utente Èlite
35,439
11,502
CPU
Ryzen 7 5800x
Dissipatore
Deepcool gammax l240 v2
Scheda Madre
Aourus Gigabyte X-470 ultra gaming
HDD
Samsung Evo 850 250gb ,2tb x 2 toshiba P300, 256 Ssd silicon power, Samsung evo plus 1tb nvme
RAM
32 gb G.Skill Tridentz 3200 mhz RGB
GPU
Zotac - RTX 3070 Ti Trinity OC
Monitor
ASUS TUF QHD 165hz IPS, ITEK GCF 165hz IPS QHD, Samsung Curvo 144hz QHD
PSU
Seasonic Focus 650w 80 platinum
Case
Coolermaster h500p
Periferiche
Asus Tuf Wireless
Net
Fibra Tim 100mb
OS
Windows 10 Pro 64 bit
dovrei vedere leggermente piu codice, @BAT postami cosa fa quella classe o metodo totale per recepire le immagini cosi vediamo come modificarlo
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,946
11,581
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
Le cose stanno in questo modo:
avevo bisogno di una classe che estendesse la classe standard dei pulsanti in modo che, oltre al normale pulsante:
  • venisse associato automaticamente il testo del pulsante stesso (per esempio "Search")
  • venisse associato automaticamente un tooltip (la "nuvoletta" che descrive la funzione di un bottone quando il mouse lo sorvola)
  • un'immagine;
Allo scopo ho definito una classe chiamata BottoneGUI;
dentro tale classe c'è una enumerazione che descrive il tipo di bottone. Il codice dell'enumerazione è il seguente:
Java:
// tipo di bottone
    enum TipoBottone {
        RICERCA("Search","Search (cerca)","search.png"),
        AGGIUNGI("Add","Add component (aggiungi componente)","add.png"),
        DESELEZIONA("Deselect","Deselect row (deseleziona riga)","remove-selection.png");

        private final String tipo; // Search, add ecc.
        private final String trad; // tooltip
        private final String icon; // icona PNG

        // directory delle immagini usate nella GUI
        private static final String imgDir = "/img/bottoni/";

        /**
         * Costruttore di TipoBottone.
         *
         * @param t il tipo
         * @param u la traduzione in italiano di <tt>t</tt>
         * @param i il nome dell'icona
         */
        private TipoBottone(String t, String u, String i){
            tipo = t;
            trad = u;
            icon = i;
        }

        /**
         * @return il tipo (RICERCA ecc.)
         */
        public String getTipo(){
            return tipo;
        }

        /**
         * @return un tooltip da associare ad un bottone
         */
        public String getToolTip(){ return trad; }

        /** Ricava il percorso di directory dell'icona del bottone.
         *
         * @return una stringa con il percorso di directory dell'icona di questo bottone
         */
        public String getIconPath(){
            StringBuilder sb = new StringBuilder(imgDir);
            sb.append(icon); // icona
            return sb.toString();
        } // FINE metodo getIconPath

    } // FINE enum TipoBottone

Come vedete dentro l'enumerazione c'è una stringa imgDir dove è memorizzato il percorso assoluto dell'icona;
riferendomi a questa parte: RICERCA("Search","Search (cerca)","search.png")
in parole povere, si crea questo nuovo tipo di bottone con
Java:
BottoneGUI bg = new BottoneGUI(BottoneGUI.TipoBottone.RICERCA);
viene
  • creato un bottone "Search" (è la prima stringa della costante che vedete nell'enumerazione) --> funziona
  • associato il tooltip Search (cerca)" (è la seconda stringa nella definizione dell'enumerazione) --> funziona
  • idealmente messa l'immagine search.png (la terza stringa nell'enumerazione), che dovrebbe essere prelevata dal percorso /img/bottoni/search.png --> è questo che non funziona, e la causa ora capisco che è il metodo che ho usato
Ragionando a posteriori mi rendo conto che ho fatto una pessima gestione delle immagini mettendo i percorsi in stringhe, quindi dovrò riscrivere parecchio codice, ma per controllarlo dovrò prima capire come recuperare le immagini. Il codice completo della classe BottoneGUI è il seguente:
Java:
package blam;

import javafx.scene.control.Button;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.util.Duration;

public class BottoneGUI extends Button {
    // tipo di bottone
    enum TipoBottone {
        RICERCA("Search","Search (cerca)","search.png"),
        AGGIUNGI("Add","Add component (aggiungi componente)","add.png"),
        DESELEZIONA("Deselect","Deselect row (deseleziona riga)","remove-selection.png");

        private final String tipo; // Cerca, aggiungi ecc
        private final String trad; // tooltip
        private final String icon; // icona PNG

        // directory delle immagini usate nella GUI
        private static final String imgDir = "/img/bottoni/";

        /**
         * Costruttore di TipoBottone.
         *
         * @param t il tipo
         * @param u la traduzione in italiano di <tt>t</tt>
         * @param i il nome dell'icona
         */
        private TipoBottone(String t, String u, String i){
            tipo = t;
            trad = u;
            icon = i;
        }

        /**
         * @return il tipo (RICERCA ecc.)
         */
        public String getTipo(){
            return tipo;
        }

        /**
         * @return un tooltip da associare ad un bottone
         */
        public String getToolTip(){ return trad; }

        /** Ricava il percorso di directory dell'icona del bottone.
         *
         * @return una stringa con il percorso di directory dell'icona di questo bottone
         */
        public String getIconPath(){
            StringBuilder sb = new StringBuilder(imgDir);
            sb.append(icon); // icona
            return sb.toString();
        } // FINE metodo getIconPath

    } // FINE enum TipoBottone

    private static final double buttonH = 24.0;
    // caratteristiche pulsante
    private static final String family = "Verdana";
    private static final double size = 12.0;
    private static final FontWeight fw = FontWeight.SEMI_BOLD;
    private static final FontPosture fp = FontPosture.REGULAR;
    private static final Font f = Font.font(family, fw, fp, size);
    // font per tooltip
    private static final Font fDesc = Font.font("Verdana", FontWeight.MEDIUM, 12);
    private Image img;
    private ImageView imgv;

    public BottoneGUI(TipoBottone tb){
        super();
        setFont(f);
        try {
            img = new Image(getClass().getResourceAsStream(tb.getIconPath()));
            imgv = new ImageView(img);
            imgv.setFitWidth(buttonH); // ridimensiona secondo la base prefissata
            imgv.setPreserveRatio(true);
            imgv.setSmooth(true);
            imgv.setCache(true);
            setGraphic(imgv);
        } catch (NullPointerException ex) { // immagine non trovata
            //setText(testo);
        }
        setText(tb.getTipo());
        Tooltip ttip = new Tooltip(tb.getToolTip());
        ttip.setFont(fDesc);
        ttip.setShowDelay(Duration.seconds(0.2)); // dopo quanto tempo in secondi appare il tooltip
        setTooltip(ttip);
    } // FINE costruttore
} // FINE BottoneGUI
 
Stato
Discussione chiusa ad ulteriori risposte.

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!