Pääsivulle | TIETOTEKNIIKAN PÄÄSIVULLE
Java-ohjelmointikieli
Java SWT
Vuosina 1998-2002 rakensin Herbaariosovellustani Javalla Linuxissa
ja Windows 98:ssa. Projektikansioni ovat edelleen tallella ja
helmikuussa 2020 kokeilin vieläkö sovellus toimii. Hieman yllätyin,
että kyllähän se toimi sekä Macissä että Windows 10:ssä. Erikoinen
kasvien tietokantaratkaisu, kasvikuvien katselu ja tietojen
tallennus, kaikki toimi. Ainoa kauneusvirhe oli, että yhdessä
näytössä elementit eivät olleet täysin oikeassa kohdassa.
Niinpä päätin perehtyä uudestaan Java-ohjelmointiin. Googlaamalla
löytyi lukuisia versioita ja koodaustyökaluja. Lopuksi päädyin
siihen, että Eclipse on sopivin ohjelmointityökalu ja alan tekemään
koodia käyttäen versiota Java SWT ja tarvittaessa muitakin
kirjastoja. Eclipsen kanssa otan käyttöön Windowbuilderin.
Asensin Eclipsen kolmeen käyttöjärjestelmään: macOS Catalina,
Windows 8.1 ja Windows 10. Nopeasti tekemäni testiohjelmat
osoittivat, että lähdekoodi on siirrettävissä käyttöjärjestelmästä
toiseen. Pian opin, että olennaisen tärkeää oli käyttää kaikissa
tekstitiedostossa samaa koodausta (encoding), ja valitsin tekstille
UTF-8 ja rivinvaihdoille UNIX. Nämä saattoi laittaa Eclipsen
asetuksissa oletusarvoiksi.
Ohjelmointikielenä Java on suuresti samankaltainen kuin C# myös
syntaksiltaan, mutta ei yhtä monipuolinen. Samalla tavalla
rakennetaan luokkia ja niiden funktioita ja järjestetään niiden
yhteydet. Yksityiskohdissa on toki eroja.
Seuraavassa on esitetty joitain oppimiani perusasioita.
Eclipsen peruskonffaus
Kun lataat Eclipse-, java- ym. pakkauksia, niin käytä Windowsissa
niiden purkamiseen 7zip-softaa.
Päävaiheet:
1. Lataa java-kirjasto "OpenJDK" ja asenna paikoilleen -
http://jdk.java.net/13/
2. Lataa Eclipse ja asenna paikoilleen, mutta ennen käynnistystä
konffaa eclipse.ini
3. Lataa sqlite-jdbc ja asenna paikoilleen
4. Käynnistä Eclipse
5. Tutki asetukset, mm. ruksaa että src ja bin eri kansioissa,
valitse encoding ja valitse koodinkirjoitustyylisi. Kannatta
tallentaa oma profiilinsa, jos joutuu asentamaan Eclipsen uudestaan
6. WindowBuilderin asentaminen (Help / Eclipse Marketplace)
7. Sovellusprojektien käynnistäminen
Windows-koneissa kopioin java-kansion levyn juureen C:\jdk-13.0.2\ jotta
sen täyspolkuun ei tule yhtään välilyöntejä. Macciä varten tuli
kansio jdk-13.0.2.jdk ja sen vein paikkaan:
Library /
Java / JavaVirtualMachines
Kohdassa 2 on tärkeää ilmoittaa Eclipselle mistä java-kirjasto
löytyy. Se tehdään tekemällä tiedostoon eclipse.ini kaksi riviä
juuri rivin "-vmargs" eteen:
Windowsissa:
-vm
C:\jdk-13.0.2\bin\javaw.exe
Macissä:
-vm
/Library/Java/JavaVirtualMachines/jdk-13.0.2.jdk/Contents/Home/bin
Ennen Eclipsen käynnistymistä päätä minne sijoitat Java-projektisi
ja sitä varten kansio. Kyseisen kansion nimessä eikä absoluuttisessa
polussa ei saa olla välilyöntejä eikä skandeja. Tämä tulee olemaan
Eclipsen "workspace", jota heti kysytään.
Kohdassa 3 on mainittu SQLite:n asentaminen. Paketti löytyy GitHubin
kautta ja sieltä latautuu tiedosto
sqlite-jdbc-3.30.1.jar
Tätä samaa käytetään sekä Macille että Windowsille. Macissä asensin
sen kansioon:
Library /
Java / Extensions
mutta Windowsissa vein sen suoraan projektieni projektikansioihin.
Tämän jälkeen jar-paketti on Eclipsessä merkittävä ryhmään
Modulepath.
Eclipse IDE:ä ja WindowBuilderiä päivitetään jatkuvasti. Macissa
Eclipse ilmoitti, että päivitys on tarjolla, asennetaanko?
Klikkasin, että kyllä, ja kaikki meni hyvin. Projektit toimivat
edelleen jne. Windows 8.1:ssä ei mennyt yhtä hyvin ja lopuksi piti
perustaa uusi workspace, asentaa Eclipse IDE lataamalla uusi versio,
ja aloittaa kaikki projektit alusta. Mutta Windows 10:ssä ei kysytty
edes lupaa uuden version asennukselle, se asentui itse sovellusta
käynnistettäessä. Mutta onneksi päivitys onnistui hyvin ilman
yhtäkään pulmaa.
Uuden projektin
käynnistys
Tässä perustetaan SWT-projekti, jossa hyödynnetään WindowBuilderiä:
- menusta valitse File -> New -> Other
- valitaan SWT/JFace Java Project
- seuraavassa ikkunassa annetaan projektille nimi, ja klikataan Next
(eikä Finish)
- poistetaan ruksi paikasta "Creat module-info.java file" eli ei
rakenneta paketteja mikä mutkistaa monia asioita perusprojekteissa
Seuraavaksi perustetaan sovelluksen aloitusikkuna:
- projektin nimen päällä context menusta valitaan New -> Other
- ja sieltä WindowBuilder -> SWT Designer -> SWT ->
Application Window
- klikataan Next ja päästään antamaan aloitusluokan nimi (usein sama
kuin projektin nimi, mutta saa olla muutakin)
Vastaavasti perustetaan muita luokkia, tyyppiä esimerkiksi SWT
Dialog tai Java Class.
WindowBuilder auttaa elementtien sijoittamisessa ikkunaan, näyttää
millainen ikkunasta on tulossa ja erityisen havainnollinen se on
tapahtumien määrittelyssä: kontrolli valitaan ja context menussa Add
event handler ja sieltä esimerkiksi select -> selection ->
widgetselected. Mutta composite- ja layout-määrittelyissä se välillä
epäonnistuu ja lopputulosta pitää käsin viimeistellä koodissa.
Lisäksi ikkunan osien sijoittelussa lopputulos ei ole aina
samanlainen Windowsissa ja Macissä, eikä WindowBuilder aina näytä
muutosta mitä merkitsen numeroilla elementin sijainnille. Koodiin
pitää välillä kirjoittaa tyyliin että jos ollaan Macissä, arvo on 75
ja Windowsissa 62.
Projektin testaaminen
Eclipse toimii erinomaisesti sekä Windowsissa että Macissä. Jos
olet koodannut oikein, määritellyt aputiedostojen sijainnin oikein
jne., niin sovelluksen ajaminen onnistuu hyvin. Testi-ilmoitukset
saat luontevasti esiin. Ainoa poikkeus on Macissä, jossa menut
eivät toimi suoraan. Jotta ne saisi toimimaan, pitää klikata
jotain muuta ikkunaa näytöllä ja sitten takaisin oman sovelluksen
ikkunaa. Nyt menutkin toimivat.
Kokonaan toinen asia on oman softan testaaminen ja toimimaan
saaminen Eclipsen ulkopuolella. Jos olet kirjoittanut 5000 riviä
ohjelmakoodia, niin itse koodit kyllä toimii eri ympäristöissä
(minulla nyt kokemusta Windowsista ja Macistä). Mutta konffaaminen
on tehtävä erikseen ja eri tavoin eri ympäristöihin, seuraavassa
lyhyesti mistä on kyse.
Sovelluksessa tarvitaan usein vakiotiedostoja, kuten kuvia tai
tekstitiedostoja. Nämä voidaan sijoittaa omaan kansioonsa ja
kansio sijoittaa Eclipsessä projektin juureen, ja määritellä
projektin resurssiksi (buildpathiin). Kaikki toimii hienosti
Eclipsessä, mutta ei Eclipsen ulkopuolella, ei vaikka käyttäisi
suositeltuja funktioita getResource() ja getResourceAsStream().
Nämä ovat toimineet joskus, mutta eivät enää macOS Catalinassa
eikä Windows 10:ssä. Joten päädyin ratkaisuun, että en määrittele
vakiotiedostoja Eclipsessä resurssiksi, vaan etsin niiden
sijainnin ohjelmakoodissa (ks. jäljempänä).
Eclipsessä voi sovelluksensa tallentaa jar-paketiksi jota
kutsutaan jopa nimellä "Runnable JAR File". Tämä toimii mainiosti
Windowsissa, ks alempana, mutta ei toimi ollenkaan Macissä.
Macissä voi Eclipsessä muodostaa myös app-paketin "Mac OS X
application bundle", mutta jo termikin viittaa vanhoihin
Macceihin, eikä se toimi macOS Catalinassa. Kun sen käynnistää
Macissä (Catalina), tulee ilmoitus "Vanha ajonaikainen Java SE 6
pitää asentaa, jotta Herba.app voidaan avata". Ilmoituksessa oleva
linkki vie sivulle, jossa kerrotaan että Java SE 6 toimii
korkeintaan macOS High Sierrassa.
Java-sovelluksen avaaminen Windowsissa
Ensimmäiseksi tulee konffata käyttämäsi java-kirjaston polku
ympäristömuuttujiin. Ja sitten voi jatkaa edellä saadun
jar-tiedoston kanssa, nimi on esimerkiksi Herbawin.jar. Sijoita se
koneellasi sopivaan paikkaan, ja rinnalle aputiedostokansiosi. Tee
sitten tekstitiedosto nimeltä Herba.bat ja sinne rivi
java -jar
Herbawin.jar
Nyt kaksoiklikkaamalla tiedostoa Herba.bat sovelluksesi
käynnistyy.
Java-sovelluksen avaaminen Macissä
Tämä toimii uudessa käyttöjärjestelmässä parhaiten käyttäen vanhaa
periaateohjetta:
https://www.eclipse.org/swt/macosx/
Sieltä löytyi demosovellus HelloSWT, joka oli pakattu Macin app
bundleksi, ja toimi jopa macOS Catalinassa. Sitä voi käyttää omaan
sovellukseensa. Oheisessa kuvassa on kyseisen app bundlen sisältö,
jonne olen sijoittanut oman yksinkertaisen testisovellukseni:

Tässä tietoja:
- kansioon Resources sijoitin omia aputiedostoja
- info.plist en koskenut paitsi myöhemmin muutin sinne
skriptitiedoston nimen
- kansiossa MacOS on Eclipsen projektikansiosta kerätyt java- ja
class-tiedostot
- swthello on skriptitiedosto (ks alempana), jolla sovellus
käynnistetään; myöhemmin muutin nimeksi omatesti
- kansiossa swt on netistä haettu uusin versio swt.jar, löytyi
nimellä swt-4.15-cocoa-macosx-x86_64.zip
- kansion swt muihin tiedostoihin en koskenut, mutta lisäsin sinne
myöhemmin muita jar-kirjastoja
Skriptin swthello sisältö on:
#!/bin/sh
BASEDIR=`dirname $0`
exec java \
-XstartOnFirstThread \
-classpath $BASEDIR/swt/swt.jar:$BASEDIR \
Aloitus
Kun kansioon swt lisätään sovelluksen tarvitsemia muita kirjastoja,
kuten minun tapauksessani sqlite-jdbc-3.30.1.jar, on tieto siitä
kirjoitettava skriptitiedostoon.
Minulla ongelmaksi muodostui menujen avautuminen ja
sqlite-tietokannan avaaminen. Tässä testattuja versioita:
1) Kaksoisklikkaamalla app, tai käynnistys context-menusta:
tietokantaa ei saa auki mutta menut toimivat suoraan
2) Kaksoisklikkaamalla skriptitiedostoa, tai sen käynnistys
context-menusta: tietokannan saa auki, mutta menut saa toimimaan
vasta kun ensin klikkaa muualle näytöllä
3) Vein skriptitiedoston ulos app-paketista, samoin aputiedostojeni
kansion ja MacOS-kansion, jotka yhdistän: toimii samoin kuin edellä
kohta 2
Joten itse käytän toistaiseksi tapaa 3 itse ja antaessani
sovelluksia lähipiirini käyttöön. Välilyönnit ja skandit ovat
kiellettyjä skriptitiedoston sisältämän kansion nimessä ja sen koko
polussa. Ja jos sovellus asennetaan muuhun kuin kehityskoneeseen, on
java-kehityspaketti asennettava edellä mainittuun Eclipsen
käyttämään kansioon, minulla nyt
/Library/Java/JavaVirtualMachines/
Aputiedostojen kansion paikan tunnistaminen
Koska minulla aputiedostojen kansio sijaitsee eri paikassa riippuen
ympäristöstä, niin kansion polku on tunnistettava koodissa. Minulla
on neljä eri tilannetta Herba-sovellukseni osalta:
- Eclipse IDE:ssä kansio on nimeltään Herbatied ja sijaitsee
projektikansion juuressa
- Windowsissa kansio on nimeltään Herbatied ja sijaitsee
jar-tiedoston rinnalla samassa kansiossa
- Macissä käytössä app-bundle, ja olen sijoittanut aputiedostot
bundlen kansioon Resources
- Macissä app-bundle ei ole käytössä, ja aputiedostojen kansio on
nimeltään Herbatied, jossa on lisäksi java-tiedostot alikansiossa
koodit
String
userdirz = System.getProperty("user.dir");
// Selvitetään aluksi aputiedostojen kansio. Se on riippuvainen
ympäristöstä
// minne sovellus on asennettu.
tyodirz = userdirz + "/Herbatied/"; // jos olemme Eclipse
IDE:ssä
if (!((new File(tyodirz)).exists()))
{
// ajammeko Windowsin jar-tiedostoa
File currDir = new File("");
tyodirz = currDir.getAbsolutePath() +
"Herbatied/";
if (!((new File(tyodirz)).exists()))
{
// ajammeko Macin
app-pakettia (app-doubleclick tai context avaa):
String pathz =
this.getClass().getClassLoader().getResource("").getPath();
if (pathz.length() < 10)
pathz =
"eiloydy";
tyodirz = pathz.substring(0,
pathz.length() - 6) + "Resources/";
if (!((new
File(tyodirz)).exists()))
{
//
ajammeko Macissä skripti-doubleclick tai context avaa, eikä
//
skripti ole app-bundlen sisällä (apukansiossa java on kansiossa
'koodit'):
tyodirz =
pathz.substring(0, pathz.length() - 7);
if
(!((new File(tyodirz)).exists()))
{
mz = "Ei löydy työtiedostojen
kansiota\n\n(Aloitus A01-01)";
System.out.println("Ei löydy työtiedostojen
kansiota\n\n(Aloitus A01-01)");
return false;
}
}
}
}
System.out.println("tyodirz = " + tyodirz);
Ohjelmoinnin yksityiskohtia
SQLite-tietokannan käsittely
Tässä on toimiva funktio tietokannan avaamiseksi. Funktion
inputtina on tietokantatiedoston koko polku:
import
java.sql.*
//
-------------------------------------------
// 21.3.2020 A09
// avataan tietokanta
// Output on virheteksti, ja jos "" niin kaikki ok
protected String avaaTietokanta(String dbpolz)
{
String z = "";
try
{
Class.forName("org.sqlite.JDBC");
String dbURL = "jdbc:sqlite:" +
dbpolz;
conlite =
DriverManager.getConnection(dbURL); // jos tiedostoa ei
ole, se luodaan
if (conlite != null)
System.out.println("Connected to the database");
}
catch (ClassNotFoundException ex)
{
ex.printStackTrace(); //
TODO: mitä tämä tekee?
z = "ei löytynyt
sqlite-kirjastoa";
}
catch (SQLException ex)
{
z = ex.getMessage();
if (z.toLowerCase().indexOf("out
of memory") != -1)
z = "Ei
löytynyt tai saatu aikaan tietokantaa " + dbpolz;
}
if (conlite == null && z.isEmpty())
z = "Tietokantaa ei saatu
avattua";
if (!z.isEmpty())
System.out.println(z);
return z;
}
Tässä on normaali luuppi tietojen lukemiseksi tietokannasta:
Statement
kome = null;
ResultSet rs = null;
String sqz = "SELECT * from Laadut";
try
{
kome = conlite.createStatement();
rs = kome.executeQuery(sqz);
while (rs.next())
{
String z =
rs.getString("LaatuNimi"); // SQL NULL antaa null
int itu = rs.getInt("idLaatu");
// SQL NULL antaa 0
int ju = rs.getInt(2); // first
column is 1
// tee arvoilla z, itu, ju jotain
}
}
catch (SQLException e)
{
String vz = e.getLocalizedMessage();
}
finally // tällä tavalla mikään ei jää auki
{
if (rs != null) { try { rs.close(); } catch
(SQLException e) { /*ignored*/ }}
if (kome != null) { try { kome.close(); } catch
(SQLException e) { /*ignored*/ }}
}
Näin käsitellään kun kentässä on SQL NULL eikä sinulle 0 /
null kelpaa
int iVal =
0;
Statement kome;
ResultSet rs = kome.executeQuery(sqz);
if (rs.next())
{
iVal = rs.getInt("IDjoku");
if (rs.wasNull())
{
// käsittele SQL NULL
}
}
Funktio 'preparedStatement' on kätevä ja tehokas toistuvissa
INSERT- ja UPDATE-komennoissa:
PreparedStatement
pskome = null;
String sqz = "INSERT INTO apuc24 VALUES (?)";
try
{
pskome = conlite.prepareStatement(sqz);
for (Integer ipai : pait)
{
pskome.setInt(1, ipai);
pskome.execute();
}
}
catch (SQLException e)
{
vz = e.getLocalizedMessage();
System.out.println(vz);
}
finally // tällä tavalla mikään ei jää auki
{
if (pskome != null) { try { pskome.close(); } catch
(SQLException e) { /*ignored*/ }}
}
Ilmoitus- ja kyselyikkunat
Javasta löytyy luokkia ja funktioita yksinkertaisten
ilmoitusikkunoiden ja kyselyikkunoiden esittämiseksi.
Ilmoitusikkuna on sellainen, jossa on esimerkiksi ilmoitus
"Antamasi päivämäärä on muodoltaan väärin" ja OK-painike sen
kuittaamiseksi. Kyselyikkunassa sovellus kysyy jotain asiaa ja
voit painikkesta valita esimerkiksi OK tai Peruuta. Mutta
valitettavasti ne eivät toimi kunnolla, joten niitä ei kannata
käyttää (macOS Catalina, Windows 10):
- JOptionPane-luokan eri ikkunat eivät toimi lainkaan
Macissä (jos composite=null) tai eivät sovellu enää
SWT-ympäristöön, koska tarvitaan JFrame. Null-arvolla toimivat
hyvin Windowsissa
- MessageBox (SWT-widget) ei osaa näyttää oikeanlaista ilmoituksen
ikonia Macissä, mutta Windows on ok
- MessageDialog (JFace-widget) antaa osan ilmoitusikoneista väärin
Macissä, mutta Windows on ok
Joten ensimmäinen sopiva Java-harjoitustyö itsekullekin on tehdä
itse oma ilmoitus- ja kyselyikkuna. Minä tein oman luokan
"Mesboksi" käyttäen pohjana SWT Dialog:ia WindowBuilderissä.
Merkkijonojen vertailu
Javassa ei tule vertailla merkkijonoja käyttäen operaattoreita == ja
!= kuten on soveliasta Swiftissä ja C#:ssa. Javassa ne kertovat
ovatko molemmat merkkijonot samassa muistikohdassa.
Normaalivertailut ovat:
if
(nimiz.isEmpty()) // sama kuin nimiz.length() ==
0
if (nimiz == null || nimiz.isEmpty()) // näin jos
String voi olla null
if (nimiz.contentEquals("Erkki")) // merkkijonon
vertailu toiseen
Merkkijonon vertailu aakkostusta varten:
"a".compareTo("b");
// tuloksena negatiivinen luku -1
"a".compareTo("a"); // tuloksena 0
"b".compareTo("a"); // tuloksena positiivinen luku 1
Taulukot kuten Array
Tavallinen taulukko (array of int) määritellään (declaration)
allakuvatulla tavalla ja samalla voi myös luoda objektin (toinen
rivi):
int[] ai;
int[] ai = new int[7];
ArrayList on hyödyllinen taulukkorakenne, jonka kokoa voidaan
kasvattaa alkio kerrallaan:
ArrayList<String>
nimet = new ArrayList<String>();
nimet.add("Timo");
nimet.remove("Matti");
int kohta = nimet.indexOf("Timo"); // -1
ei löydy
Listaa pitkin voidaan mennä luupissa näin:
for(String
str : nimet)
System.out.println(str);
Listaan voi laittaa myös omatekoisia luokkia. Jos tällaisen listan
haluaa laittaa järjestykseen jollain itse määritellyllä tavalla, se
tapahtuu seuraavasti, kun meillä on ArrayList<Kuvainfo>
kuvasto. Luokka Kuvainfo on oma luokka. Sinne on laitettava
nimirivin loppuun aakkostusta varten:
implements
Comparable<Object>
ja luokan sisään on laitettava funktio:
@Override
public int compareTo(Object kix) // järjestetään
kentän Sunimiz perusteella
{
String suz = ((Kuvainfo)kix).getSunimi();
return this.Sunimiz.compareTo(suz);
}
Itse aakkostus tapahtuu näin:
Collections.sort(kuvasto);
HashMap on toimiva rakenne, joka vastaa monista muista kielistä
tuttua Dictionaryä. On Javassa omakin Dictionary, mutta se on outo
ja buginen, ei kannata yrittää käyttää. Seuraavassa HashMapin
peruskäyttötapoja:
HashMap<String,
String> nimet = new HashMap<String, String>();
nimet.put("nimi1", "john"); // lisätään avain ja
tietoja
nimet.put("nimi2", "marry");
nimet.get("nimi2"); // saadaan avaimella tieto ulos
String tuz = nimet.getOrDefault("nimi9", "ei oo"); // hyvä
käyttää, jos ei ole varma löytyykö avainta
nimet.remove("nimi1"); // poistetaan avain tietoineen
nimet.isEmpty() // onko sisältöä lainkaan
int montako = nimet.size();
nimet.clear(); // hashmapin tyhjennys
for (String z : nimet.keySet()) // avainten käyttäminen
System.out.println(z);
for (String z : nimet.values()) // tietojen käyttäminen
System.out.println(z);
for (String z : nimet.keySet()) // avainten ja tietojen
käyttäminen
System.out.println("key: " + z + " value: " +
nimet.get(z));
// Tässä Hashmap purettuna nurinpäin:
for (String key: nimet.keySet())
{
String valz = nimet.get(key);
if (valz.contentEquals("john"))
{
System.out.println(key);
}
}
Myös kelpaa esimerkiksi HashMap<String, Integer> mutta ei saa
käyttää int, vaan Integer, Boolean, Character, Double jne.
Polut ja tiedostot
Windowsissa ei tarvita kenoja vaan
esimerkiksi tämä kelpaa: "E:/KUVAT/KoeKuvat/IMG_1154_orient_1.JPG"
Tekstitiedoston lukeminen sisään muuttujaan ja luetun tiedon
käsittelyä rivi kerrallaan:
String
failiz; // tekstitiedoston nimi
polkuineen
String tuloz = ""; // luetut tiedot tänne
StringBuilder sb = new StringBuilder(); // tänne luetaan
tekstiä
try
{
BufferedReader bufr = new
BufferedReader(new FileReader(failiz));
try
{
String riviz;
while ((riviz =
reader.readLine()) != null)
{
//
käsitellään riviä riviz
sb.append(riviz).append("\n");
}
}
finally
{
bufr.close();
}
}
catch(IOException e)
{
System.out.print("Lukeminen epäonnistui\n"+
e.getLocalizedMessage());
sb.setLength(0);
}
tuloz = sb.toString();
Tehdään tilapäistiedosto:
File tilap =
File.createTempFile("myTempFile", ".txt", null);
Onko tiedosto olemassa
File tef = new
File("c:/temp/temp.txt"); // tämä ei tee failia
boolean exists = tef.exists();
Missä käyttöjärjestelmässä ollaan
Tiettyjen kontrollien koot voivat olla erikokoisia Mac- ja
Windows-näytöillä samalla lähdekoodilla. Tämän takia koodissa
voidaan tarvita käyttöjärjestelmästä riippuvia mittoja.
Käyttöjärjestelmän selvittäminen ohjelmakoodissa tapahtuu näin:
String osz =
System.getProperty("os.name").toLowerCase();
if (osz.indexOf("mac") != -1)
return "MAC";
if (osz.indexOf("windows 10") != -1)
return "WIN10";
return "WIN8";
Joissain tapauksissa voi olla tarpeen selvittää käyttäjän
kotikansio:
System.getProperty("user.home");
// antaa esimerkiksi "C:\Users\erkki"
Label, jonka voi kopioida leikepöydälle
Joskus voi olla hyödyllistä saada kopioida näytölle kirjoitettava
teksti leikepöydälle ja niin, että teksti näyttää tavalliselta
Labeliltä. Se käy määrittelemällä kontrolli Text samannäköisesti
kuin Label:
Text textInfo = new
Text(sc, SWT.WRAP);
textInfo.setSize(500,240);
textInfo.setEditable(false);
textInfo.setBackground(null);
textInfo.setText("Oma tilanneilmoitus:\n" + tilannez);
Labelin fontti
Fontteja pääsee javassa määrittelemään, mutta joskus voi lopputulos
olla ongelmallinen: valittu sopiva fontti ei ehkä olekaan
käytettävissä toisessa järjestelmässä (Mac / Win / Linux). Lisäksi
Eclipsessä voi huomata, että mukaan tulee pitkiä kansiopolkuja,
mistä ei sinänsä haittaa ole.
Itselleni vakiofontin lisäksi tarvetta on ollut kyseisen fontin
lihavoidulle versiolle. Näin sen saa yksinkertaisesti käyttöön SWT -
WindowBuilder - Dialog -luokassa:
//
määritellään luokkamuuttuja:
private Font boldFont;
// open()-funktion kohdassa missä poistutaan sovelluksesta:
boldFont.dispose();
createContents()-funktion alussa missä ekan kerran otetaan label
(tässä lblX) käyttöön:
// lihavoidun fontin määrittely vakiofontille:
FontDescriptor boldDes =
FontDescriptor.createFrom(lblX.getFont()).setStyle(SWT.BOLD);
boldFont = boldDes.createFont(lblX.getDisplay());
// ja sitten fontin käyttäminen
lblLati.setFont(boldFont);
Selainnäyttö
Selaimen katselu sovelluksen ikkunassa olikin hyvin yksinkertaista,
verrattuna Swift- ja C#-sovelluksiin: tehdään uusi luokka (ikkuna)
projektin, luokan tyyppi SWT Dialog. WindowBuilderin avulla
sijoitetaan ikkunaan kontrolli Browser (sen nimeksi laitetaan alla
browser), ja viedään sille sisältö:
String htmz =
"https://www.tukes.fi"; // htm-teksti muuttujan sisällä
browser.setText(htmz);
// tiedosto, jonka sisällä on htm-teksti:
String htmfilez = "c:/tilap/testifile.htm";
browser.setUrl(htmfilez);
// aina lopuksi:
browser.dispose();
Minä käytän tätä kontrollia kartan näyttämiseen (OpenStreetMap tai
Maanmittauslaitoksen maastokartta). Apukansion (ks. edellä) sisällä
minulla on kansioita, joissa on javascriptiä. Katso koodausesimerkit
sivuillani, jossa Swift- ja C# -koodausohjeita. Java-version olen
toteuttanut samalla tavalla.
Joissain sovelluksissa voi olla tarpeen ennen selaimen käyttämistä
tarkistaa, onko netti käytettävissä. Allaoleva funktio on nopea myös
jos nettiä ei löydy. Omassa sovelluksessa kannattaa yrittää
tavoitella palvelinta mitä aikoo käyttää, tässä on esimerkkinä
google.
import
java.net.HttpURLConnection;
// -------------------------------------------------
// Tarkistetaan onko pääsy nettiin, tai tarkemmin Googlen
palvelimelle
// Tämä tarkistus on nopea
//
https://stackoverflow.com/questions/1402005/how-to-check-if-internet-connection-is-present-in-java
public static boolean onkoNetti()
{
HttpURLConnection connection = null;
try
{
URL url = new
URL("https://www.google.com");
connection =
(HttpURLConnection) url.openConnection();
connection.getContent(); // jos ei nettiä, niin tämä
vie -> exception
}
catch (IOException e)
{
return false; //
Failed to get a connection
}
finally
{
if (connection !=
null) // cleanup
connection.disconnect();
}
return true;
}
Seuraava funktio kertoo ovatko inputin koordinaatit Suomessa vai ei.
Funktio käyttää ns. käänteiskyselyä. Funktiota käyttävän on joko
annettava ostettu tunnus tai oma käytössä oleva
sähköpostiosoitteensa.
//
---------------------------------------------------------
// ovatko koordinaatit Suomessa, esim. latiz = "61.234", longiz =
"22.345"
// output = "Suomi", "Muu" , ""
public static String annaMaa(String latiz, String longiz)
{
String maaz = "";
// Etsi maa OpenStreet-palvelusta
// Tässä annettava kysyjän identifikaatio
String zzz =
"https://nominatim.openstreetmap.org/reverse?email=ETUNIMI.SUKUNIMI@zoner.fi&format=xml&lat="
+ latiz + "&lon=" +
longiz + "&zoom=18&addressdetails=1";
try
{
HttpClient client =
HttpClient.newHttpClient();
HttpRequest request =
HttpRequest.newBuilder().uri(URI.create(zzz)).build();
HttpResponse<String>
response = client.send(request,
HttpResponse.BodyHandlers.ofString());
String respaz =
response.body();
if
(omaTrim(respaz).isEmpty()) // ei ollut nettiä
tai palvelua
{
System.out.println("M27: ei ollut nettiä tai
OpenStreetMap-palvelua");
return
"";
}
if
(respaz.indexOf("<country_code>") == -1) // oli
palvelu mutta ei saatu tietoa maasta
{
System.out.println("M27 ei maatietoa, respaz =" +
respaz);
return
"";
}
maaz =
(respaz.indexOf("<country_code>fi</country_code>")
> -1) ? "Suomi" : "Muu";
}
catch (Exception e)
{
System.out.println("M27: " +
e.getLocalizedMessage());
}
return maaz;
}
Värialueet
Ikkunoihin on usein mukava saada jotain väriä. Värejä en ole
noukkinut WindowBuilderin tarjoamista vaihtoehdoista, koska ne
mutkistavat koodia ja lähdekansioita. Tässä ovat kaikinpuolin
yksinkertaiset koodirivit:
import
org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
private Color vaalsin;
Device device = Display.getCurrent();
// määritellään värejä
vaalsin = new Color(device, 221, 238, 255);
Composite composite = new Composite(shellPaikatso, SWT.NONE);
composite.setBackground(vaalsin);
vaalsin.dispose(); // tämä erillisenä tarpeen, kun
poistutaan ikkunasta
Ikkunallisen luokan käsittely
Kutsuttaessa dialogi-ikkunaa (SWT Dialog Window) käytetään yleensä
tällaisia lausekkeita:
1) Dialogi-ikkuna, modaalinen, ja otsassa normaalit
painikkeet:
Mesboksi msb
= new Mesboksi(shell, SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM);
2) Dialogi-ikkuna, modaalinen, avautuu Macissä sheet-ikkunana,
Windowsissa tavallisena:
Paihaku pha =
new Paihaku(shell, SWT.APPLICATION_MODAL | SWT.SHEET);
Luokassa on vakiofunktiot open(), jossa on aloitus ja lopetus, ja
createContents(), jossa luodaan näytön kontrollit ja osat.
Kontrollien rakentaminen ikkunaan:
- näytön koko merkitään koodissa halutuksi
- WindowBuilderissa koko näytölle formlayout
- näytölle lisätään compositeja, eli alueita
joissa on eri tehtäviä tai vaikkapa vain muista poikkeava väri
- tämä sujui Windowsissa paljon paremmin kuin
vastaava homma Macissä, eli vähemmän korjailtavaa lähdekoodissa
- usein joutuu koodissa paikkaamaan
composite-määrittelyjä
- WindowBuilder voi kaatuakin, joten mitä tahansa
lisäätkään WindowBuilderissä, tallenna heti sen jälkeen
Composite:n asettelussa tulee käyttää luokkaa FormData, mikä ei ole
kovin havainnollinen, Tässä esimerkkejä:
FormData fd =
new FormData();
// Fix the left edge of the control to 25% of the parent's width +
10px offset:
fd.left = new FormAttachment(25, 10);
// Fix the top edge of the control to the parent's top edge:
fd.top = new FormAttachment(0);
// Fix the top edge of the control 5 px below the bottom edge of
another:
fd.top = new FormAttachment(comp0, 5, SWT.BOTTOM);
// Fix the top edge of the control on same level as top of
another:
fd.top = new FormAttachment(comp0, 0, SWT.TOP);
// Fix the lower edge of the control to 75% of the parent's height
+ 0px offset:
fd.bottom = new FormAttachment(75);
// Fix bottom edge at exactly the same height as the one of
otherControl
fd.bottom = new FormAttachment(otherControl, 0, SWT.BOTTOM);
// Fix bottom edge 40 px below the bottom edge of another
fd.bottom = new FormAttachment(otherControl, 40, SWT.BOTTOM);
// Fix the right edge of the control to the parent's left edege +
620px offset:
fd.right = new FormAttachment(0, 620);
// Fix the right edge of the control to the parent's right edge:
fd.right = new FormAttachment(100, 0);
// Fix left edge 10px to the right of the right edge of
otherControl
fd.left = new FormAttachment(otherControl, 10, SWT.RIGHT);
// finally:
composite.setLayoutData(fd);
Välilehti, jossa on taulukko: kun ensimmäinen välilehti on
paikoillaan, niin vie seuraavaksi taulukko, se tekee itse uuden
välilehden taulukkoineen. Lopuksi koodissa poista ensin tehty
välilehti (ei aina onnistu WindowBuilderissä). Jos haluat
välilehdelle erilaisia kontrolleja, esimerkiksi Label, Button ja
Table, niin ne on ensin koottava Compositelle joka määritellään koko
TabFolderille. Compositeen kootaan eri kontrollit. Ja lopuksi
composite asetetaan halutulle välilehdelle:
tabitem.setControl(munComposite);
Ikkunasta poistumisen aloite mistä tahansa luokan funktiosta:
if
(!shell.isDisposed())
shell.dispose();
tai
shell.close();
Näistä logiikka siirtyy open()-funktion loppuriveille, jossa
valmistellaan outputtina lähtevä tieto.
Dialog-ikkunasta saa ulos melkein minkämuotoista dataa tahansa.
Output on muotoa Object, joten käypiä ovat esimerkiksi String tai
omatekoinen luokka.
Kutsuva luokka käsittelee open()-funktion antaman tuloksen.
Combobox
Tässä tärkeimpiä comboboxin käsittelyfunktioita:
private
CCombo combo;
combo.removeAll(); // tyhjennetään combobox
combo.add("Alavus"); // lisätään alkio comboboxiin
// mikä on valittuna
int sele = combo.getSelectionIndex(); // -1 jos
ei valintaa
aluez = combo.getItem(aluesele);
// alue valituksi comboboxissa
int ai = combo.indexOf(paikkaz);
combo.select(ai);
combo.select(0); // valitsee ekan
int kpl = combo.getItemCount(); // montako
vaihtoehtoa valikossa on
Menut
Menun tekeminen WindowBuilderissä:
- MenuBar ikkunan yläpuolelle
- CascadeMenu on 'alasvetomenu' joita laitetaan
vierekkäin menubaariin
- Menuitem on niitä joita laitetaan päällekkäin
CascadeMenuun
- jos haluaa "sivumenun", niin CascadeMenuun
laitetaan yhdeksi alkioksi CascadeMenu
- tarvittaessa voi laittaa ruksilliset menut,
termi CheckMenuitem
Tarvittaessa voidaan menun käyttö estää:
menu.setEnabled(false);
Menut eivät toimi Macissä (Catalina) odotetusti. Menut ovat
käytettävissä vasta kun sovelluksen käynnistyksen jälkeen on käynyt
klikkaamassa näytöllä jotain muuta. Lisäksi Macissä näkyy
menubaarissa ylimääräinen "SWT", jota ei saa pois:

Taulut (tables)
Riveille voidaan laittaa tietoja koko rivi kerrallaan, tai sitten
yhden sarakkeen kohdalle kerrallaan:
for (int i=0; i <
24; i++)
{
TableItem item = new TableItem(taulu,
SWT.NONE);
// kaikki sarakkeet:
item.setText( new String[] {
String.valueOf(1000+i), "paikka "+i,
String.valueOf(1000-i), "alue "+i, "Seli seli
"+i } );
item.setText(1, "paikkax " + i); // vain
toiseen sarakkeeseen
}
Valitulta riviltä tiedon hakeminen:
TableItem[]
sss = taulu.getSelection(); // valitut rivit
if (sss != null && sss.length > 0) .....
TableItem ss2 = sss[0];
String stz = ss2.getText(0); // valitun rivin ekan sarakkeen
sisältö
String st2z = ss2.getText(1); // toka sarake
Taulukoiden aakkostuksen joutuu itse koodaamaan, mikä on usein
joustavin ratkaisu (tableViewer sisältää mahdollisuuden, että
aakkostus otsaketta klikkaamalla)
Tein sen seuraavasti:
Lisäsin taulukon sarakkeelle tapahtumankäsittelijän, esim:
columnPaikKpl.addSelectionListener(new
SelectionAdapter()
{
@Override
public void widgetSelected(SelectionEvent e)
{
// oma apufunktio joka
kirjoittaa koko taulukon sisällön uudestaan:
lajitteluPaikkasarake("kpl");
}
});
taulu.setSelection(index);
// merkitään rivi valituksi
taulu.update(); // jos tietoja muutettu tai kaikki
poistettu, tällä päivitetään näkymä
taulu.removeAll() // tyhjentää koko taulukon
Radiobuttonit
Radiobuttonit sijoitetaan kehykseen (Group), jos niiden halutaan
toimivan automaattisesti oikein (vain yksi valittuna). Tämä kehys
tulee Windowsissa väärään kohtaan verrattuna radiobuttoniin, kun
taas Macissä kaikki on ok. Jos Windowsissa tämän tekee niin, että
radiobuttonit ovat kehyksen päällä (eli kehys ei näy), niin näky
on erilainen Macissä. Millään väriasetuksilla kehystä ei saa
piiloon. Yksi tapa kiertää ongelma on sijoittaa radiobuttonit
paikoilleen ilman kehystä ja tehdä valinnan hallinnointiin omaa
koodia (addSelectionListener) joka huolehtii, että vain yksi
valittu. WindowBuilder suostuu sijoittamaan radiobuttonit vain
kehykseen joten asiaa pitää koodata käsin.
Kuvan skaalaus ja Orientation
Kuvan metatietojen Orientation kertoo onko kuva oikeassa asennossa
vai ylösalaisin, kyljellään tai peilikuva tai näiden
yhdistelmä.Vääränlaisia kuvia syntyy tyypillisesti nykyaikaisissa
älypuhelimissa riippuen siitä missä asennossa puhelinta on pidetty
ja onko otettu kuva etukameralla vai takakameralla.
SWT-kirjastoissa ei ole funktioita, joilla voisi tutkia kuvien
orientaatiota, tai jotka oikaisisivat kuvan automaattisesti. Nyt
käytän siihen metadata-extratctor-kirjastoa, jonka on tehnyt
drewnoakes-porukka ja julkaisuut sen GitHubissa. Täältä löytyy
tekemäni sovellus, joka näyttää kuvan tarvittaessa oikeinpäin
käännettynä Sen orientation-, flip- ja rotate-osat löytyvät luokasta
Meka.
Kuvamatriisi
Joihin tarkoituksiin on kätevä esittää joukko pikkukuvia ja valita
mille kuvalle tehdään toimenpiteitä. Täällä on sellaiselle periaatekoodaus.
Matriisi on toteutettu RowLayout-rakenteella, kuva ja kuvateksti
ovat Group-elementissä. Tällä tavalla kaikki kuvat teksteineen
saatiin siistiin järjestykseen. Kuvaa voi kaksoisklikata
tehtävää toimenpidettä varten tai valita toimenpide kuvakohtaisesta
context menusta. Toimenpiteitä varsinaisessa sovelluksessani ovat
mm. kuvan tallennus työpöydälle, kuvan näyttäminen oikean kokoisena,
kasvin yksityiskohtaisten tietojen esittäminen. Oheinen
periaatekoodaus näyttää kuvat kuva0.jpg, kuva1.jpg jne ja
testaamista varten korjaa koodista kuvien polut ja sijoita
tämännimiset kuvat koneellasi vastaavaan paikkaan.
Käytännössä lopputulos on melko hidas. Vastaavan näytön esilletuonti
Mac Swift-sovelluksessa (samassa Macissä) vei aikaa vajaan
kolmasosan verrattuna Java-sovellukseen. Lisäksi Swift-sovelluksessa
on mukana kunkin kuvan Orientation-tarkistus mitä ei ole (vielä)
oheisessa Java-versioss.
--------------------------------
(sivua muokattu 15.6.2020)