Reading RFID tags on Android

As promised some time ago, this post will be about Android.

About half year ago I bought Samsung Galaxy Nexus as my first Android phone and I found one thing interesting – the Near Field Communication (NFC).

Near field communication (NFC) is a set of standards for smartphones and similar devices to establish radio communication with each other by touching them together or bringing them into close proximity, usually no more than a few centimeters.[1]

In short, this is a technology based on RFID[2] tags that allows smartphone to do 3 things:

  • read and write RFID tags,
  • emulate RFID tag,
  • send small amount of data between devices in small proximity.

The first one can be used to read tags that matches MIME types on the device to activate some functionality, configure the device or just to read cards that uses the RFID technology. It’s also possible to write to those tags. The second enables the device to behave as RFID tag for another devices. And the third is used for communication between two devices, mostly to setup another, more powerful connection type such as Bluetooth or WiFi.

In this post I will show how to read data from the RFID tags since I don’t have any available card to write to. Fortunately this task is quite well covered by Android SDK[3] documentation so I will just write a quick setup of the basic project. I assume that an empty project is created with basic implementation provided by Eclipse or Android tool from SDK.

Enabling NFC in the project

First of all, to enable NFC in the project the modification of the AndroidManifest.xml file is required. The NFC is available since version 10 and requires special permission – android.permission.NFC. To enable it just add following code to the manifest node:

1
2
3
4
5
6
7
8
9
10
11
12
13
<uses-sdk 
   android:minSdkVersion="10" 
   android:targetSdkVersion="15" 
   />
 
<uses-permission 
   android:name="android.permission.NFC"
   />
 
<uses-feature 
   android:name="android.hardware.nfc" 
   android:required="true" 
   />

uses-feature entry means here that the application requires NFC and won’t work without it.

Since RFID tags are read and dispatched by the Android, application must register an Intent filter and a Techlist to get them. To do this add following code to activity node in manifest file:

1
2
3
4
5
6
7
<intent-filter>
   <action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>
<meta-data
   android:name="android.nfc.action.TECH_DISCOVERED"
   android:resource="@xml/techlist"
   />

There are free types of actions that can be used with NFC – android.nfc.action.NDEF_DISCOVERED, android.nfc.action.TECH_DISCOVERED and android.nfc.action.TAG_DISCOVERED. I chose the android.nfc.action.TECH_DISCOVERED because none of my RFID chips had NDEF messages and TAG, as the documentation says, is too general. Also TECH just worked.
As you can see the code also specifies the techlist xml file. This file contains a list of NFC technologies supported by the application. @xml/techlist means that this file has name techlist.xml and lies in res/xml/ directory. Following code shows an example of my file:

1
2
3
4
5
6
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
   <tech-list>
      <tech>android.nfc.tech.MifareClassic</tech>
      <tech>android.nfc.tech.NfcA</tech> 
   </tech-list>
</resources>

This covers basic setup of NFC but requires the user to specify the application which will handle the Tag everytime it is read by Android. It is quite annoying so Android allows to register an Activity in the foreground to be the default one for handling Tag intents. To do that add following code to your Activity class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
private NfcAdapter mAdapter = null;
private PendingIntent mPendingIntent = null;
private IntentFilter[] mIntentFilters = null;
private String[][] mTechLists = null;
 
public void onCreate(Bundle savedInstanceState) {
   /* ... */
 
   mAdapter = (NfcAdapter)NfcAdapter.getDefaultAdapter(this);
 
   // Creating PendingIndent for foreground dispatching
   mPendingIntent = PendingIntent.getActivity(
         this, 
         0, 
         new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 
         0);
 
   IntentFilter tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
   try {
      ndef.addDataType("*/*");
   } catch(MalformedMimeTypeException e) {
      throw new RuntimeException("fail", e);
   }
 
   mIntentFilters = new IntentFilter[] { tech };
   mTechLists = new String[][] { getTechList(this.getResources()) };
 
   /* ... */
}
 
public void onResume() {
   /* ... */
 
   mAdapter.enableForegroundDispatch(this, mPendingIntent, mIntentFilters, mTechLists);
 
   /* ... */
}
 
public void onPause() {
   /* ... */
 
   mAdapter.disableForegroundDispatch(this);
 
   /* ... */
}
 
private static String[] getTechList(Resources resources) {
   ArrayList<String> list = new ArrayList<String>();
   XmlResourceParser xml = resources.getXml(R.xml.techlist);
 
   // Assumed simple parsing because of simple file structure
   for(;;) {
      int eventType = XmlPullParser.END_DOCUMENT;
      try {
         eventType = xml.next();
      } catch (XmlPullParserException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
 
      if(eventType == XmlPullParser.TEXT) {
         list.add(xml.getText());
      } else if(eventType == XmlPullParser.END_DOCUMENT) {
         break;
      }
   }
 
   return list.toArray(new String[list.size()]);
}

In short, this code creates an instance of PendingIntent, IntentFilter for TECH_DISCOVERED action and all MIME types (see */*), and loads list of supported technologies from file specified earlier. Those objects are then used in onResume and onPause methods to enable and disable foreground dispatching so the application could handle the Intent before others.

Handling NFC intent

Reading NFC tags is easy. Android provides classes to read tags from some technologies such as those specified earlier in techlist.xml. In this post I will also show an example that utilizes the MifareClassic class. MIFARE Classic[4] is a standard that defines tags with blocks of data that can be written to. The one that is commonly used is a MIFARE Classic 1K which can hold 1 KB in 16 sectors with 4 blocks each. Each sector is secured by pair of 48-bit keys – first for read access and second for write access. There are 3 commonly used keys that by default protect all sectors and allow the access to them if hadn’t been changed – KEY_DEFAULT, KEY_MIFARE_APPLICATION_DIRECTORY and KEY_NFC_FORUM.

Ok, lets try now to handle an Intent with RFID tag. First of all, there are two moments when Activity can handle an Intent. First is when Activity is registered as a handler for this type of intents and second is when Activity is already on the screen. To handle both situations add following code to your Activity class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
 
   /* ... */
 
   // Passing intent
   Intent intent = getIntent();
   resolveIntent(intent);                 // This handles first situation
}
 
@Override
public void onNewIntent(Intent intent) {
   resolveIntent(intent);                 // This handles second situation
}

When Activity is created because of the NFC Intent it can be retrived in onCreate, but when Activity is already on the screen and handles this type of Intent you must override the onNewIntent method to handle it.

Ok, but how to exactly handle the tag Intent? Following function does that for MIFARE Classic tag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private void resolveIntent(Intent intent) {
 
   if(NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
 
      Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
      MifareClassic mfc = MifareClassic.get(tag);
 
      try {
         mfc.connect();
 
         mLogger.pushStatus("");
         mLogger.pushStatus("== MifareClassic Info == ");
         mLogger.pushStatus("Size: " + mfc.getSize());
         mLogger.pushStatus("Timeout: " + mfc.getTimeout());
         mLogger.pushStatus("Type: " + mfc.getType());
         mLogger.pushStatus("BlockCount: " + mfc.getBlockCount());
         mLogger.pushStatus("MaxTransceiveLength: " + mfc.getMaxTransceiveLength());
         mLogger.pushStatus("SectorCount: " + mfc.getSectorCount());
 
         mStatus.setStatus("Reading sectors...");
 
         for(int i = 0; i < mfc.getSectorCount(); ++i) {
 
            if(mfc.authenticateSectorWithKeyA(i, MifareClassic.KEY_MIFARE_APPLICATION_DIRECTORY)){
               mLogger.pushStatus("Authorization granted to sector " + i + " with MAD key");
            } else if(mfc.authenticateSectorWithKeyA(i, MifareClassic.KEY_DEFAULT)) {
               mLogger.pushStatus("Authorization granted to sector " + i + " with DEFAULT key");
            } else if(mfc.authenticateSectorWithKeyA(i, MifareClassic.KEY_NFC_FORUM)) {
               mLogger.pushStatus("Authorization granted to sector " + i + " with NFC_FORUM key");
            } else {
               mLogger.pushStatus("Authorization denied to sector " + i);
               continue;
            }            
 
            for(int k = 0; k < mfc.getBlockCountInSector(i); ++k)
            {
               int block = mfc.sectorToBlock(i) + k;
               byte[] data = null;
 
               try {
 
                  data = mfc.readBlock(block);
               } catch (IOException e) {
                  mLogger.pushStatus("Block " + block + " data: " + e.getMessage());
                  continue;
               }
 
               String blockData = Common.getHexString(data);
               mLogger.pushStatus("Block " + block + " data: " + blockData);
            }
         }
         mfc.close();
 
      } catch (IOException e) {
         mLogger.pushStatus(e.getMessage());
      } finally {
         try {
            mfc.close();
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }
      }
   }
}

At first the tag is retrived from the Intent by method getParcelableExtra with argument of NfcAdapter.EXTRA_TAG. Then using static function from MifareClassic the object of this class is created from tag. The next step is to use connect method to establish the connection. At the time of connection the card must be close to the reader till the end of this method.
To actually read the data of any block the sector that this block belongs to must be authenticated with matching key. This is done with method authenticateSectorWithKeyA which expects an index of the sector and 6-byte authentication key. After authentication it is possible to read all 4 blocks from this sector which is done by method readBlock that expects an absolute index of the block (not relative to sector). Finally after doing all operations the close method should be invoked, even if an Exception occurs because it doesn’t close the connection by default and may cause a problem when trying to read the same tag with other technology.

Summary

Reading an RFID tag is pretty easy on Android devices as the SDK provides some high level API to do that. As I read the Android Beam is also supported with nice easy to use class. Unfortunately there is a problem with emulation of RFID tags because there’s no official API to for this kinds of operations as some people say[5]. On the other hand the device supports that and it is possible to build an Android with some support of this[6]. I shall try it when I would move to CyanogenMod.

The actual code of what I am doing with my phone and NFC can be found on GitHub here.

References

  1. http://en.wikipedia.org/wiki/Near_field_communication] []
  2. http://en.wikipedia.org/wiki/Radio-frequency_identification []
  3. http://developer.android.com/guide/topics/connectivity/nfc/nfc.html []
  4. http://en.wikipedia.org/wiki/MIFARE []
  5. http://forum.xda-developers.com/showthread.php?t=1368907 []
  6. http://forum.xda-developers.com/showthread.php?t=1281946 []

Komunikacja za pomocą JSON z serwerem PHP w Javie

Jakiś czas temu pisałem pewną aplikację w Javie, która wykorzystywała format JSON do komunikacji z serwerem i wywoływania na nim pewnych procedur. Można rzec, że była to bardzo uboga wersja JSON-RPC czyli oficjalnego protokołu wywoływania procedur na zdalnej maszynie.

JSON, podobnie jak w XML, jest formatem przesyłu danych w postaci tekstu (czyli postaci odpowiedniej dla człowieka), ale w przeciwieństwie do niego jest lżejszy, co widać chociażby po tym, że sam jest opisywany za pomocą tylko kilku znaków. Pomimo tego, że format ten pochodzi z języka Java Script, istnieje masa bibliotek, które umożliwiają jego wykorzystanie w wielu językach, w tym właśnie w języku Java.

W przypadku aplikacji, którą pisałem, jednym z jej zadań było łączenie się z serwerem napisanym PHP i wywoływaniem odpowiednich procedur, które udostępniały jakieś dane. Format procedur był następujący:

“sessid”: “id”
”action”: “akcja”
”data”: “dane”

Przy czym dane również były zapisane w formacie JSON, ale były zorganizowane w odpowiednie struktury, które rozpoznawał serwer. Biblioteką, którą użyłem do wysyłania i odbierania danych była org.json, czyli pierwsza z dostępnych dla języka JAVA. W przypadku mojego kodu jej użycie ograniczyło się do użyciu tylko dwóch klas – JSONObject oraz JSONArray, z których za pomocą metody toString(), można wyciągnąć całą zapisaną hierarchię i następnie wysłać ją za pomocą odpowiedniego strumienia połączonego z serwerem.

Teraz przedstawię trochę kodu pokazującego w jaki sposób połączyć się z serwerem za pomocą HttpURLConnection oraz jak wykorzystać to połączenie do przesłania danych w formacie JSON. Na początek kod wysyłający zapytanie i odbierający odpowiedź serwera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.*;		// BufferedReader, BufferedWriter, IOException, InputStreamReader, OutputStreamWriter
import.java.net.*;		// HttpURLConnection, URL
 
/*
...
*/
 
URL url = new URL(“http://mysweet.phpserver.com/”);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
 
conn.setDoOutput(true);			// Bo chcemy wysyłać
conn.setRequestMethod(“POST”);		// np. jako zapytanie POST
 
// Request
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
out.write(“Some post data/query”);
out.close();
 
// Response
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
 
String inputLine, answer = “”;
while((inputLine = in.readLine()) != null)
{
    answer += inputLine;
}
 
in.close();
conn.disconnect();

Jak widać nie ma tutaj zbyt dużo roboty, więcej zapewne dzieje się wewnątrz tych klas, ale przecież po to one są żeby się tym z grubsza nie przejmować. Kolejny kod przedstawia sposób tworzenia zapytań za pomocą wspomnianej biblioteki org.json:

1
2
3
4
5
6
7
8
9
10
11
12
import org.json.*;	//JSONArray, JSONException, JSONObject
 
/*
...
*/
 
JSONObject root = new JSONObject()
    .put(“sesid”, sesid)
    .put(“action”, action)
    .put(“data”, data);			 // data może być liczbą, stringiem lub obiektem JSONObject
 
string query = “json=+ root.toString();

Ostatecznie obiekt query zostanie przekazany jako zapytanie do wcześniej wspomnianego obiektu klasy BufferedWriter, dzięki któremu poleci do serwera. Odbieranie odpowiedzi jest równie proste:

1
2
3
4
5
6
7
8
9
10
11
string answer;
 
/* Odbieranie odpowiedzi
...
*/
 
JSONObject object = new JSONObject(answer);
int status = object.getInt(“status”);
string text = object.getString(“some_text”);
JSONObject node = object.getJSONObject(“some_struct”);
JSONArray array = object.getJSONArray(“some_array”);

I generalnie tak to wygląda, nie trzeba nic samemu parsować, ponieważ wszystko zapewnia kilka gotowych już klas – największa zaleta obiektowych języków programowania.

Visual Editor dla Eclipse

visual_editorSwego czasu do pisania aplikacji okienkowych w Javie (głównie z musu) korzystałem z NetBeans i jego edytora graficznego, jednak w przypadku ostatniego projektu na laboratoria postanowiłem przetestować pewną alternatywę, czyli Visual Editor dla Eclipse.

Visual Editor jest (jak większość dodatków do Eclipse) wtyczką, która ma na celu automatyczne generowanie kodu wykorzystującego bibliotekę okienek Swing. Niestety okazuje się, że mimo wersji 1.5 dla Eclipse Helios jest to raczej prototyp działający trochę na siłę niż poprawna wersja Release.

Jego największą wadą jest dość wysoka niestabilność, co potrafi owocować widokiem okienek jak na obrazku obok niemal co chwila (tak, korzystałem z wersji x64). Drugą rzeczą, która boli jest to, że zagnieżdżone ve_errorokienko zostawia swoja kotwiczkę jako osobny proces, który siedzi sobie jako zupełnie osobna aplikacja w pasku zadań i skutecznie blokuje możliwość użycia skrótu alt+tab, a w przypadku zmiany edytowanych okienek, powoduje wymienione crashe. Kolejną wadą, trochę drobniejszą, jest generowany kod, który, dla większej ilości komponentów i ich właściwości, niestety wygląda jak makaron  (chociaż organizacja na zasadzie statycznych wartości, tudzież singletonów jest dość fajnym rozwiązaniem.

Z zalet Visual Editora mogę wymienić chyba głównie to że jest, a co do samego wyglądu i integracji z Eclipse, to oprócz osobnego procesu dla okienka i crashów, komponuje się to w miarę wygodnie.

Tak przy okazji narzekania na tworzenie okienek w Javie – dlaczego nikt nie wymyślił obliczania minimalnego rozmiaru okna na podstawie minimalnych rozmiarów zawartych w nim komponentów, to naprawdę ułatwiłoby życie.

Java 2 Micro Edition

Tym razem będzie trochę o programowaniu aplikacji na telefon komórkowy korzystając z platformy J2ME, co jak się okazuje wcale nie jest takie trudne. Aby w ogóle zacząć pisać aplikację na tę platformę należy zaopatrzyć się w odpowiednie narzędzia. Praktycznie każdy szanujący się edytor do tworzenia aplikacji Javowych, umożliwia tworzenie projektów na urządzenia mobilne. Ja jednak postanowiłem pójść po linii najmniejszego oporu i ściągnąłem pakiet J2ME Platform SDK. Pakiet ten zawiera w sobie wszystko co jest potrzebne do pisania tego typu aplikacji (w tym niewielki edytor oparty na Netbeans). Można go znaleźć pod tym linkiem.

Aplikacje J2ME nazywa się Midletami, ponieważ punktem startowym aplikacji jest klasa dziedzicząca po interfejsie o tej nazwie. Interfejs ten wymusza implementacje 3 podstawowych funkcji:

  • void startApp()
  • void pauseApp()
  • void destroyApp(boolean unconditional)

Pierwsza funkcja wywoływana jest gdy aplikacja rozpoczyna lub wznawia działanie, w celu umożliwienia załadowania, zainicjalizowania odpowiednich danych. Druga funkcja umożliwia aplikacji zwolnienie części zasobów w momencie gdy aplikacja przechodzi w stan wstrzymania. Ostatnia funkcja jest wywoływana w momencie zamykania midletu. Funkcja ta posiada argument typu boolean, który określa, czy aplikacja ma zostać zamknięta bezwarunkowo (w przeciwnym wypadku powinien zostać rzucony odpowiedni wyjątek).

Oprócz tego działanie aplikacji opiera się na kilku innych klasach: Display, Command oraz dziedziczących po klasie abstrakcyjnej DisplayableScreen lub Canvas. Klasa Display odpowiada za to co jest wyświetlane na ekranie, a wyświetlane są obiekty klas pochodnych od klasy Displayable. Dodatkowo te obiekty mogą wysyłać różne zdarzenia, czyli obiekty klasy Command. W celu rejestracji tych zdarzeń potrzebny jest jeszcze obiekt klasy implementującej interfejs CommandListener, który należy przekazać do funkcji Displayable::setCommandListener().

Oto przykładowy kod Hello World:

package hello;
 
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
 
public class HelloMIDlet extends MIDlet implements CommandListener
{
    private Command exitCommand; // The exit command
    private Display display;     // The display for this MIDlet
 
    public HelloMIDlet()
    {
        display = Display.getDisplay(this);
        exitCommand = new Command("Exit", Command.EXIT, 0);
    }
 
    public void startApp()
    {
        TextBox t = new TextBox("Hello", "Hello, World!", 256, 0);
 
        t.addCommand(exitCommand);
        t.setCommandListener(this);
 
        display.setCurrent(t);
    }
 
    public void pauseApp()
    {
    }
 
    public void destroyApp(boolean unconditional)
    {
    }
 
    public void commandAction(Command c, Displayable s)
    {
        if (c == exitCommand)
        {
            destroyApp(false);
            notifyDestroyed();
        }
    }
}

Jak widać te kilka klas wystarcza, aby utworzyć prostą aplikację na telefon komórkowy. Bardziej złożone korzystają dodatkowo z RecordStore do zapisywania informacji między różnymi uruchomieniami aplikacji oraz kilku innych.

Swing czyli taniec z Javą

2009-10-26(3)

Tym razem postawiłem napisać o czymś, czym się aktualnie zajmuję na uczelni czyli programowaniem w Javie, a konkretniej programowanie interfejsu graficznego w Swingu. Java Swing jest w pełni obiektową (jak sam język) biblioteką graficzną służącą do majstrowania okienkowego GUI (podobnie jak QT, czy też Windows Forms). Korzystanie z tej biblioteki jest bardzo proste, niestety ma ona jedną podstawową wadę (oprócz bycia w Javie) – domyślnie okienko posiada własny style, więc nie wykorzystuje systemowego, dlatego może się wyróżniać.

Tworzenie okienek w Swingu jest bardzo proste, przykładowo wyświetlenie pustego okienka wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
 
public class Okienko
{
   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            JFrame f = new JFrame();
            f.setTitle("Hello World!");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setSize(290, 250);
            f.setVisible(true);
         }
      });
   }
}

2009-10-26(4)

Ten krótki kod tworzy prostą formatkę JFrame, a następnie ustawia jej tytuł, rozmiar, akcje zamnięcia i na końcu pokazuje okienko. Wszystko to jest wykonywane w funkcji run() anonimowej klasy wewnętrznej po interfejsie Runnable (swoją drogą najpiękniejszy mechanizm w Javie :)), która działa w osobnym wątku biblioteki Swing. Jak widać jest to bardzo proste.

Co jednak gdy chcemy stworzyć coś bardziej wyszukanego? Wtedy najlepiej rozszerzyć (odziedziczyć) klasę JFrame. Mając własną klasę o wiele łatwiej wszystkim zarządzać wszystkimi komponentami, które znajdują się na formatce. Poniższy kod tworzy formatkę i wyświetla na niej przycisk i pole tekstowe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.awt.FlowLayout;
import javax.swing.*;
 
public class Okienko extends JFrame
{
   private JButton bPisz = new JButton("Pisz");
   private JTextArea textArea = new JTextArea(10, 20);
 
   public Okienko()
   {
      setTitle("Hello World!");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setSize(290, 250);
 
      setLayout(new FlowLayout());
      add(new JScrollPane(textArea));
      add(bPisz);
   }
 
   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            new Okienko().setVisible(true);
         }
      });
   }
}

Powyższy kod jest już troszkę dłuższy, a jego zadanie polega na wyświetleniu na formatce przycisku oraz pola tekstowego. Pojawiły się tutaj dodatkowe komponenty takie jak: JButton, JTextArea, JScrollPane oraz funkcja setLayout(). Dwa pierwsze są oczywiste, bo jest to wspomniany przycisk i pole tekstowe, natomiast JScrollPane jest dodatkowym komponentem dodającym do JTextArea paski przesuwania (to wszystko przez proste opakowanie go). Funkcja setLayout() ustawia sposób rozmieszczenia kontrolek. Domyślnym układem jest BorderLayout, który kontrolki układa jedna na drugiej, natomiast FlowLayout ustawia je w taki sposób aby wszystkie były widoczne (po szczegóły zapraszam do dokumentacji :)).

2009-10-26(5)

No cóż mamy przycisk, ale teraz co zrobić, aby po jego naciśnięciu coś się stało. Obsługa akcji na kontrolkach opiera się na zdarzeniach, czyli w przypadku naciśnięcia przycisku wywoływana jest odpowiednia funkcja, która to zdarzenie potrafi obsłużyć. W takim razie jak obsłużyć akcję wciśnięcia przycisku? W tym celu należy dodać do przycisku odbiorcę zdarzenia za pomocą funkcji addActionListener(). Funkcja ta przyjmuje referencję do interfejsu ActionListener, z którego należy zaimplementować funkcję actionPerformed(). Właśnie ta funkcja zostanie wywołana w momencie wystąpienia zdarzenia.

Wynikowy kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.awt.FlowLayout;
import javax.swing.*;
import java.awt.event.*;
 
public class Okienko extends JFrame
{
   private JButton bPisz = new JButton("Pisz");
   private JTextArea textArea = new JTextArea(10, 20);
 
   public Okienko()
   {
      setTitle("Hello World!");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setSize(290, 250); 
 
      bPisz.addActionListener(new ActionListener()
      {
         public void actionPerformed(ActionEvent e)
         {
            textArea.append("Hello World ");
         }
      });
 
      setLayout(new FlowLayout());
      add(new JScrollPane(textArea));
      add(bPisz);
   }
 
   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            new Okienko().setVisible(true);
         }
      });
   }
}

Podstawy programowania

W czwartek 2 października odbyły się pierwsze zajęcia laboratoryjne z Podstaw Programowania na Politechnice Wrocławskiej. Językiem programowania na zajęciach jest Java. Nie ma w niej wskaźników, dlatego dla wielu osób jest dużo łatwiejsza niż C++. Ja myślę, że może być to ciekawe doświadczenie i przydatna umiejętność, bo jak wiadomo, najlepiej znać wiele języków.

Największym moim zaskoczeniem na zajęciach okazało się “IDE”, ponieważ ciężko mi nazwać BlueJ środowiskiem programistycznym. Jest to raczej zabawka dla początkujących, która pokazuje w jaki sposób działają funkcje, lecz niestety z poważnym programowaniem ma niewiele wspólnego.  Na stanowisku znalazłem również środowisko Eclipse, więc pomimo, że jeszcze go nie znam, jest nadzieja na coś lepszego.