Innholdsoversikt for programutvikling

Hvordan få et javaprogram til å starte andre programmer som ligger på "skrivebordet"

Innledning

Når vi bruker profesjonelle programmer, så er vi vant med at de på en måte er knyttet sammen. Dersom det for eksempel i en e-post står en nettadresse og vi klikker på den, så er vi vant med at den nettleseren vi pleier å bruke starter opp, om den ikke allerede er i gang, og at den viser den nettsida som det var adresse til i e-posten. Dersom det omvendt på en nettside er angitt en e-postadresse og vi klikker på den, så er vi vant med at e-postprogrammet vårt starter opp, om det ikke allerede er i gang, og at det gjør seg klart til å skrive en e-post nettopp til den adressen som var angitt. Et annet eksempel på slik samvirkning er at dersom vi i filutforskeren vår (for eksempel Windows Explorer) dobbeltklikker på et filnavn, så vil det programmet som er satt opp til å behandle vedkommende filtype bli aktivert til å behandle nettopp den fil vi har dobbeltklikket på. Dersom for eksempel TextPad er assosiert med javafiler, så vil TextPad da vise vedkommende javafil.

Fra og med Javaversjon 6 er det mulig også for et javaprogram å knytte seg til andre programmer vi har liggende på "skrivebordet" på tilsvarende måte som det er nevnt eksempler på ovenfor. Et javaprogram kan dermed utføre følgende operasjoner:

Programeksempel

Vi skal se nærmere på hvordan det er mulig å legge den nevnte funksjonaliteten inn i et javaprogram. Det skal vi gjøre ved å ta for oss programmet DesktopDemo.java fra The Java Tutorials. Når programmet har startet opp, så blir følgende vindu vist:

Ikonet som blir brukt i øverste venstre hjørne av programvinduet har bildefil desk32.gif. Det blir lagt inn som følge av at metoden loadFrameIcon blir kalt opp i vinduets konstruktør. Som vi ser av metodedefinisjonen som er gjengitt under, må bildefila ligge i en katalog images under katalogen som inneholder vinduets class-fil.

305   /**
306    * Load the "desktop" icon into our frame window.
307    */
308   private void loadFrameIcon()
309   {
310     URL imgUrl = null;
311     ImageIcon imgIcon = null;
312
313     imgUrl = DesktopDemo.class.getResource("images/desk32.gif");
314     imgIcon = new ImageIcon(imgUrl);
315     Image img = imgIcon.getImage();
316     this.setIconImage(img);
317   }

Funksjonalitet for de operasjonene som er omtalt i innledningen er lagt inn i klassen Desktop. Men en forutsetning for at et javaprogram skal kunne ha funksjonaliteten, er at den plattform som programmet blir kjørt på tilbyr støtte for den for javaprogrammer. Det kan vi få sjekket ved kall på metoden isDesktopSupported. På Linux, for eksempel, er dette avhengig av Gnome-biblioteker. Dersom disse ikke er tilgjengelige, så vil isDesktopSupported returnere false. Dersom metoden isteden returnerer true, så er alt i orden, da kan vi få tak i det relevante Desktop-objektet som vi trenger ved å gjøre kall på metoden getDesktop. I programmet vårt blir et slikt objekt lagret på klassenivå så sant støtte foreligger. Når støtte foreligger, så kan vi også gå videre og sjekke i detalj hvilken type støtte som foreligger. Klassen Desktop.Action inneholder en opplisting av konstanter som kan brukes til å sjekke om de forskjellige operasjonene blir støttet. I programmet DesktopDemo blir dette sjekket i konstruktøren for programvinduet. Den starter med følgende instruksjon:

 74     initComponents();

Metoden initComponents oppretter skjermkomponenter og knytter lytteobjekter til dem. Vi skal se nærmere på lytteobjektene etter hvert som vi tar for oss de enkelte operasjonene. Neste instruksjon er

 77     disableActions();

Metoden disableActions deaktiverer alle operasjoner som har å gjøre med den funksjonaliteten som er omtalt i innledningen. Operasjonene vil bli aktivert igjen når det er blitt sjekket om det foreligger støtte for dem. Komponentene som blir deaktivert er de forskjellige tekstfeltene og knappene i vinduet. Metoden ser ut som følger:

478   /**
479    * Disable all graphical components until we know whether their
480    * functionality is supported.
481    */
482   private void disableActions()
483   {
484     txtBrowserURI.setEnabled(false);
485     btnLaunchBrowser.setEnabled(false);
486
487     txtMailTo.setEnabled(false);
488     btnLaunchEmail.setEnabled(false);
489
490     rbEdit.setEnabled(false);
491     rbOpen.setEnabled(false);
492     rbPrint.setEnabled(false);
493
494     txtFile.setEnabled(false);
495     btnLaunchApplication.setEnabled(false);
496     btnFile.setEnabled(false);
497   }

Neste trinn er å sjekke om det foreligger støtte for den funksjonaliteten vi er interessert i og i så fall aktivere igjen de komponentene som det foreligger støtte for. Dette gjøres ved å utføre følgende instruksjoner:

 80     if (Desktop.isDesktopSupported())
 81     {
 82       desktop = Desktop.getDesktop();
 83       // now enable buttons for actions that are supported.
 84       enableSupportedActions();
 85     }

Metoden enableSupportedActions har følgende innhold:

441   /**
442    * Enable actions that are supported on this host. The actions are: 
443    * open browser, open email client, and open, edit, and print files
444    * using their associated application
445    */
446   private void enableSupportedActions()
447   {
448     if (desktop.isSupported(Desktop.Action.BROWSE))
449     {
450       txtBrowserURI.setEnabled(true);
451       btnLaunchBrowser.setEnabled(true);
452     }
453     if (desktop.isSupported(Desktop.Action.MAIL))
454     {
455       txtMailTo.setEnabled(true);
456       btnLaunchEmail.setEnabled(true);
457     }
458     if (desktop.isSupported(Desktop.Action.OPEN))
459     {
460       rbOpen.setEnabled(true);
461     }
462     if (desktop.isSupported(Desktop.Action.EDIT))
463     {
464       rbEdit.setEnabled(true);
465     }
466     if (desktop.isSupported(Desktop.Action.PRINT))
467     {
468       rbPrint.setEnabled(true);
469     }
470     if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled())
471     {
472       txtFile.setEnabled(true);
473       btnLaunchApplication.setEnabled(true);
474       btnFile.setEnabled(true);
475     }
476   }

Vi skal etter tur ta for oss hvordan de tre typene av funksjonalitet som er nevnt i innledningen er programmert i dette programmet.

Aktivere standard nettleser

Som det går fram av programvinduet, er det tekstfeltet bak URI: og knappen Launch Browser som har med nettleser å gjøre. Tekstfeltet txtBrowserURI og knappen btnLaunchBrowser er tilknyttet lytteobjekt på følgende måte:

129     txtBrowserURI.addActionListener(new ActionListener()
130     {
131       public void actionPerformed(ActionEvent e)
132       {
133         onLaunchBrowser(null);
134       }
135     });
136
137     btnLaunchBrowser.addActionListener(new ActionListener()
138     {
139       public void actionPerformed(ActionEvent evt)
140       {
141         onLaunchBrowser(evt);
142       }
143     });

Vi ser at både tekstfeltet og knappen gjør kall på metoden onLaunchBrowser når de blir aktivert. Metoden er definert på følgende måte:

405   /**
406    * Launch the default browser with the text provided by the user.
407    *
408    */
409   private void onLaunchBrowser(ActionEvent evt)
410   {
411     URI uri = null;
412     try
413     {
414       uri = new URI(txtBrowserURI.getText());
415       desktop.browse(uri);
416     }
417     catch (IOException ioe)
418     {
419       System.out.println("The system cannot find the " + uri +
420               " file specified");
421     }
422     catch (URISyntaxException use)
423     {
424       System.out.println("Illegal character in path");
425     }
426   }

Dersom vi i tekstfeltet for URI lar være å skrive noe, men bare trykker på Returtasten mens markøren befinner seg i feltet, eller klikker på knappen bak feltet, så vil det være systemets filutforsker som blir aktivert og vist på skjermen, det vil si Windows Explorer på Windows-systemer. Når programmet blir kjørt ved hjelp av NetBeans, så viser filutforskeren innholdet av katalogen for vedkommende NetBeans-prosjekt. Skriver vi i tekstfeltet en nettadresse og enten trykker på Returtast eller klikker på knappen bak tekstfeltet, så vil nettleseren vise vedkommende nettside så sant den er tilgjengelig.

Aktivere standard e-postprogram

Programvinduet indikerer at det er tekstfeltet bak E-mail: og knappen Launch Mail som har med e-post å gjøre. Tekstfeltet txtMailTo og knappen btnLaunchEmail er tilknyttet lytteobjekt på følgende måte:

146     txtMailTo.addActionListener(new ActionListener()
147     {
148       public void actionPerformed(ActionEvent e)
149       {
150         onLaunchMail(null);
151       }
152     });
153
154     btnLaunchEmail.setText("Launch Mail");
155     btnLaunchEmail.addActionListener(new ActionListener()
156     {
157       public void actionPerformed(ActionEvent evt)
158       {
159         onLaunchMail(evt);
160       }
161     });

Vi ser at både tekstfeltet og knappen gjør kall på metoden onLaunchMail når de blir aktivert. Den er definert på følgende måte:

378   /**
379    * Launch the default email client using the "mailto" protocol and
380    * the text supplied by the user.
381    *
382    */
383   private void onLaunchMail(ActionEvent evt)
384   {
385     String mailTo = txtMailTo.getText();
386     URI uriMailTo = null;
387     try
388     {
389       if (mailTo.length() > 0)
390       {
391         uriMailTo = new URI("mailto", mailTo, null);
392         desktop.mail(uriMailTo);
393       }
394       else
395       {
396         desktop.mail();
397       }
398     }
399     catch (IOException | URISyntaxException ioe)
400     {
401       ioe.printStackTrace();
402     }
403   }

Dersom vi i tekstfeltet for E-mail lar være å skrive noe, men bare trykker på Returtasten mens markøren befinner seg i feltet, eller klikker på knappen bak feltet, så vil det være systemets standard e-postprogram som blir aktivert og gjør seg klar til sending av e-post, uten at det er lagt inn noen adresse for mottaker. Gjør vi det samme etter at vi i tekstfeltet har skrevet noe, vil det samme skje, i tilleg til at det vi har skrevet vil stå som mottaker for e-posten. Det blir ikke foretatt noe sjekk på at det er en adresse med gyldig format.

Aktivere program for filbehandling

Dette er den mest komplekse operasjonen å programmere, siden det er så mange alternativer som skal håndteres. Bildet av programvinduet er lagt inn tidligere, men for at det skal være lettere å referere til det, legger jeg inn bildet på nytt.

De tre radioknappene rbEdit, rbOpen og rbPrint, med tilhørende tekst Open, Edit og Print, skal brukes til å bestemme hva som skal gjøres med den fil som blir valgt. Som nevnt tidligere, så inneholder klassen Desktop.Action en opplisting av konstanter som kan brukes til å identifisere de forskjellige typene aksjoner. Det som er aktuelt i dette tilfelle, er å åpne fil, editere fil, eller printe ut fil. For å registre det valget som brukeren foretar, er det på klassenivå lagt inn et datafelt av type Desktop.Action:

 66   private Desktop.Action action = Desktop.Action.OPEN;

Vi ser at startverdien er alternativet for å åpne fil, noe som gjenspeiler seg i programvinduet ved at det er radioknappen for åpning som er valgt i utgangspunktet. Under programkjøring blir datafeltet tilordnet ny verdi ved at hver radioknapp er tilordnet lytteobjekt på følgende måte:

172     rbOpen.addActionListener(new ActionListener()
173     {
174       public void actionPerformed(ActionEvent evt)
175       {
176         onOpenAction(evt);
177       }
178     });
179
180
181     rbEdit.addActionListener(new ActionListener()
182     {
183       public void actionPerformed(ActionEvent evt)
184       {
185         onEditAction(evt);
186       }
187     });
188
189
190     rbPrint.addActionListener(new ActionListener()
191     {
192       public void actionPerformed(ActionEvent evt)
193       {
194         onPrintAction(evt);
195       }
196     });

De tre metodene som blir kalt opp av de tre lytteobjektene er programmert på følgende måte:

319   /*
320    * Set the Desktop.Action to PRINT before invoking the default 
321    * application.
322    */
323   private void onPrintAction(ActionEvent evt)
324   {
325     action = Desktop.Action.PRINT;
326   }
327
328   /**
329    * Set the Desktop.Action to EDIT before invoking the default
330    * application.
331    */
332   private void onEditAction(ActionEvent evt)
333   {
334     action = Desktop.Action.EDIT;
335   }
336
337   /**
338    * Set the Desktop.Action to OPEN before invoking the default
339    * application.
340    */
341   private void onOpenAction(ActionEvent evt)
342   {
343     action = Desktop.Action.OPEN;
344   }

Vi ser at virkningen er å sette ny verdi for datafeltet action.

Tekstfeltet txtFile bak File: har naturligvis som oppgave å inneholde navn eller filsti for den fil som skal behandles. Det er tilordnet lytteobjekt på følgende måte:

164     txtFile.addActionListener(new ActionListener()
165     {
166       public void actionPerformed(ActionEvent e)
167       {
168         onLaunchDefaultApplication(null);
169       }
170     });

Vi vet at når vi skal velge fil, er det svært praktisk å kunne bruke en filvelger. Knappen btnFile merket ... bak tekstfeltet er ment til dette formål. Den er tilknyttet lytteobjekt på følgende måte:

206     btnFile.addActionListener(new ActionListener()
207     {
208       public void actionPerformed(ActionEvent evt)
209       {
210         onChooseFile(evt);
211       }
212     });

Metoden onChooseFile som lytteobjektet kaller opp har følgende innhold:

428   private void onChooseFile(ActionEvent evt)
429   {
430     if (evt.getSource() == btnFile)
431     {
432       int returnVal = fc.showOpenDialog(DesktopDemo.this);
433       if (returnVal == JFileChooser.APPROVE_OPTION)
434       {
435         file = fc.getSelectedFile();
436         txtFile.setText(file.getAbsolutePath());
437       }
438     }
439   }

Metoden gjør bruk av JFileChooser-objektet fc opprettet på klassenivå og den tilordner verdi til datafeltet file som også er deklarert på klassenivå:

 63   JFileChooser fc = new JFileChooser();
 64   File file;

I tillegg legger metoden filstien for den valgte fil inn som tekst i tekstfeltet for filvalg.

Når vi har valgt fil, enten ved å skrive filnavn eller filsti i tekstfeltet for dette, eller ved å bruke filvelger, så er vi klar til å behandle den. Én mulighet for dette er å trykke Returtast mens markøren står i tekstfeltet for filnavn, en annen mulighet er å klikke på knappen btnLaunchApplication med tekst Launch Application. Den er tilordnet lytteobjekt på følgende måte:

198     btnLaunchApplication.addActionListener(new ActionListener()
199     {
200       public void actionPerformed(ActionEvent evt)
201       {
202         onLaunchDefaultApplication(evt);
203       }
204     });

Vi ser at lytteobjektet gjør kall på metoden onLaunchDefaultApplication. Det er den samme metoden som lytteobjektet for tekstfeltet gjør kall på. Den er definert slik:

346   /**
347    * Launch the default application associated with a specific
348    * filename using the preset Desktop.Action.
349    *
350    */
351   private void onLaunchDefaultApplication(ActionEvent evt)
352   {
353     String fileName = txtFile.getText();
354     File file = new File(fileName);
355
356     try
357     {
358       switch (action)
359       {
360         case OPEN:
361           desktop.open(file);
362           break;
363         case EDIT:
364           desktop.edit(file);
365           break;
366         case PRINT:
367           desktop.print(file);
368           break;
369       }
370     }
371     catch (IOException | IllegalArgumentException ioe)
372     {
373       System.out.println("Cannot perform the given operation to the "
374               + file + " file");
375     }
376   }

Vi ser at avhengig av hvilken radioknapp som er valgt for øyeblikket, så vil vedkommende fil, så sant det er mulig, bli åpnet for visning, for editering, eller den vil bli sendt til printeren for utskrift.

Merknader

Det viste seg at programmet som ble hentet inn fra The Java Tutorials hadde en liten mangel. Dersom operasjon for filbehandling ble aktivert uten at det var skrevet noe i tekstfeltet for filnavn, ble det kastet ut en IllegalArgumentException. Jeg har lagt inn i programmet oppfanging av en slik exception, slik at programmet nå virker greit også i dette tilfelle. Men det viser seg også at det ikke blir akseptert filnavn som inneholder mellomrom (blanke tegn). For sammenhengende filnavn virker det som det skal. For øvrig så ser vi av programkoden at i tilfelle en operasjon ikke lar seg utføre, slik at det isteden blir skrevet ut en melding til brukeren om dette, så vil meldingen bli skrevet i det såkalte konsollvinduet, eller i Output-feltet når programmet blir kjørt ved hjelp av NetBeans. For et profesjonelt program burde selvsagt denne meldingen kommet i en meldingsboks.

Innholdsoversikt for programutvikling

Copyright © Kjetil Grønning og Eva Hadler Vihovde, revidert 2014