BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Moja druga aplikacja mobilna

22-06-2004 21:38 | luke_ash6910
Program pocztowy na Smartphone’a

1. Wstęp

Prężny rozwój technologii mobilnych szybko zaowocował możliwością uruchamiania prostych programów na własnej komórce. Spośród dostępnych sposobów pisania mobilnych aplikacji najpierw zaciekawiła mnie J2ME (Java 2 Micro Edition). Jednak niedługo po tym postanowiłem spróbować sił w tajemniczym (jeszcze wtedy) języku C#. I tak, po napisaniu „Hello World” przyszła pora by zrealizować coś bardziej ambitnego. Za urządzenie docelowe wybrałem Smartphone’a, czyli krzyżówkę komórki z Pocket PC.

2. Ogólny opis

Prezentowana przeze mnie aplikacja jest programem pocztowym umożliwiającym odbieranie i wysyłanie emaili wraz z załącznikami. Szczególny nacisk położyłem na interfejs i łatwość użytkowania. Nie bez znaczenia były także mała ilość dostępnej pamięci Smartphone’a oraz minimalizacja danych przesłanych przez połączenie sieciowe związana z dużymi kosztami, które poniesie potencjalny użytkownik tego oprogramowania. Aplikacja jest, co prawda w pełni działająca, jednak nie należy jej traktować jako produktu rynkowego.

3. Jak zacząć?

By napisać program na Smartphone’a trzeba doinstalować do Visual Studio .NET dwa składniki:

  1. Smartphone 2003 SDK:
    http://www.microsoft.com/downloads/details.aspx?FamilyId=A6C4F799-EC5C-427C-807C-4C0F96765A81&displaylang=en

  2. ActiveSync 3.1.7:
    http://www.microsoft.com/downloads/details.aspx?FamilyID=2eb5bd80-d52c-4560-ae11-da92f2b229fa&displaylang=en

Jeżeli linki do powyższych programów nie są aktualne, można sprawdzić również:

http://www.microsoft.com/downloads/search.aspx?displaylang=en&categoryid=8.

Aby stworzyć nowy projekt na Smartphone’a należy wcisnąć CTRL+SHIFT+N i w oknie wybrać typ projektu Visual C# Projects oraz wzorzec Smart Device Application i po wpisaniu nazwy wybrać OK. W następnym oknie pozostawiamy domyślne ustawienia i klikamy na OK. Teraz powinien ukazać się mały formularz świeżo stworzonego projektu. Aby przejść do kodu naciskamy F7 i już możemy pisać własne procedury. Ktoś, kto pisał wcześniej aplikacje mobilne w JAVIE od razu powinien zauważyć standardową strukturę programu. Pod tym względem MIDlet (javowa aplikacja mobilna) przypomina bardziej aplet, ponieważ wymagane jest stworzenie metod wykonywanych gdy użytkownik uruchamia aplikację, zatrzymuje ją, lub z niej wychodzi. W C# natomiast struktura programu na komórkę nie różni się od zwykłej windowsowej aplikacji.

Aby uruchomić moją aplikację wystarczy tylko otworzyć plik projektu „eMailApp.csdproj”, a następnie wcisnąć F5 (uruchamianie z debuggerem) lub CTRL+F5 (uruchamianie bez śledzenia). Następnie wciskając ENTER należy potwierdzić wybór domyślnego emulatora. Przy wystąpieniu błędu, nie należy się przejmować, a jedynie odczekać aż emulator się w pełni zainicjalizuje i dopiero potem uruchomić program lub wcześniej zainicjować emulator za pomocą przycisku .

Przed przystąpieniem do dalszego czytania zalecam wcześniejsze zapoznanie się z aplikacją; wypełnienie ustawień i odebranie/wysłanie wiadomości. Krótki opis obsługi zamieściłem razem z kodem programu. Nie polecam jednak wysyłania/odbierania wiadomości powyżej 50KB ze względu na czasochłonność tego procesu.

4. Tworzenie interfejsu

Teraz zajmiemy się procesem tworzenia interfejsu. Ponieważ zanim przystąpi się do odbierania/wysyłania wiadomości należy najpierw wprowadzić ustawienia, to jako pierwszy stworzymy ten właśnie formularz. Za cel postawiłem sobie zmieszczenie całej konfiguracji w jednym oknie przy jednoczesnym zachowaniu czytelności. Wszyscy, którzy próbowali skonfigurować ActiveSync’a na emulatorze zapewne zrozumieją moje intencje. W każdym bądź razie wypełnianie ustawień przynajmniej nie powinno odstraszać użytkownika. Poniżej zamieściłem efekt końcowy zarówno w designerze jak i po uruchomieniu na emulatorze.

Rys. 1. Ekran ustawień w designerze (po lewej) i emulatorze (po prawej)

Żeby stworzyć nowy formularz wybieramy menu Project->Add Windows Form wpisujemy nazwę i klikamy na Open. Zakładam, że technika rozmieszczania elementów poprzez ich przeciąganie na formularz nie jest czytelnikowi obca. Przejdźmy zatem od razu do właściwości. Pojawiają się one w prawym dolnym rogu ekranu po wybraniu elementu i umożliwiają zmianę jego parametrów od nazwy (Properties->(Name)) po wyświetlany tekst (Properties->Text). I tak, aby stworzyć pole tekstowe na hasło wystarczy ustawić w nim właściwość  PasswordChar, co pokazano na rysunku poniżej.

Rys. 2. Ustawianie właściwości PasswordChar

Oprócz wyżej wspomnianych bardzo przydatna jest też właściwość Multiline, która, gdy ustawiona na true, umożliwia po wciśnięciu przycisku wyboru (ten niebieski po środku telefonu) na edycję pola w trybie pełnego okna. Do tego możemy dołączyć suwaki (pionowy i/lub poziomy) dzięki ScrollBars. Zachęcam do wypróbowania pozostałych, jednak teraz proponuję przejść do tworzenia pola rozmiaru. Zawierać ma ono wartość liczbową określającą górny próg rozmiaru wiadomości, po przekroczeniu którego użytkownik będzie pytany o zgodę na jej pobranie. Ma to na celu zarówno ograniczenie czasu połączenia, jak i zapobieżenie ściąganiu dużych wiadomości do pamięci telefonu. Ale co w tym właściwie takiego interesującego? Otóż pole to przyjmuje tylko cyfry (wpisywane z klawiatury telefonu). W przeciwieństwie do J2ME na próżno możemy szukać właściwości lub funkcji ustawiającej tryb przyjmowania danych w kontrolce. W sytuacjach takich jak ta, gdy .NET Compact Framework nie zawiera metod umożliwiających nam zrealizowanie określonego zadania, należy wywołać wybrane funkcje ze standardowej .NET Framework przy wykorzystaniu Platform Invoke. P/Invoke jest usługą umożliwiającą umieszczanie w kodzie niezarządzalnych funkcji rezydujących w dll-ach. Zawiera ona także komponent odpowiedzialny za formatowanie danych przekazywanych do i z określonej biblioteki, tzw. wprowadzacz, albo formator (Marshal). Aby z niej skorzystać należy załączyć poniższą przestrzeń nazw.

using System.Runtime.InteropServices;

Następnie musimy zadeklarować funkcję, którą importujemy za pomocą instrukcji DllImport, np.:

[DllImport("msvcrt.dll")]
public int puts(string s);

Oprócz biblioteki dll, w DllImport możemy podać jeszcze szereg atrybutów określających sposób importowania. W .NET Compact Framework dostępne są następujące atrybuty:

Wracając do pola Rozmiar, należy zaimportować z biblioteki core.dll funkcję SendMessage(). Należy zauważyć, że funkcja importowana musi być określona jako static extern, ponieważ nie zawiera się ona w instancji obiektu naszej klasy i nie definiujemy jej ciała.

[DllImport("coredll.dll")] //wszystkie atrybuty domyslne, czyli:
/*[DllImport("coredll.dll"),
CallingConvention=CallingConvention.Winapi,
CharSet=CharSet.Auto,
EntryPoint="SendMessage",
SetLastError=false]*/
private static extern int SendMessage(int hWnd, uint wMsg, uint wParam, uint lParam);

Dzięki niej możemy wysłać do kontrolki wiadomość o zmianę trybu na numeryczny. Najpierw jednak musimy zdobyć jej uchwyt, dlatego zaimportujemy jeszcze jedną funkcję.

[DllImport("coredll.dll")]
private static extern int GetFocus();

Teraz możemy już napisać funkcję ustawiającą tryb numeryczny w polu rozmiaru.

private const uint EM_SETINPUTMODE = 0x00DE;//wiadomosc: ustaw input mode
private const uint EIM_NUMBERS = 2;//tryb numeryczny
public static int tylkoNumery(Control ctrl)
{
     ctrl.Focus();          //najpierw musimy ustawic Focus()
     int hWnd = GetFocus();  //zeby teraz uzyskac uchwyt do kontrolki
     return SendMessage(hWnd, EM_SETINPUTMODE, 0, EIM_NUMBERS);
}

Oprócz trybu numerycznego możemy ustawić jeszcze tryb multi-tap oraz słownika T9:

public const uint EIM_SPELL = 0;//tryb multi-tap (jak w tel. komorkowych)
public const uint EIM_AMBIG = 1;//tryb slownika T9

Teraz przejdźmy do tworzenia menu. Zrobimy to na przykładzie głównego ekranu zawierającego spis wszystkich odebranych wiadomości. Efekt końcowy widoczny jest poniżej.

Rys. 3. Ekran wiadomości w designerze (po lewej) i emulatorze (po prawej)

Aby stworzyć menu należy przeciągnąć na formularz kontrolkę MainMenu (chyba, że już znajduje się na ekranie), a następnie w miejsca z napisem Type Here wpisywać nazwy kolejnych elementów menu (MenuItem-ów). Teraz trzeba „podpiąć” pod każdy element odpowiednią akcję, co robi się poprzez dwukliknięcie lewym przyciskiem myszy. Stworzona automatycznie funkcja, zawierająca w nazwie nazwę naszej zmiennej MenuItem, odpowiadać będzie za obsługę wybranej opcji. Gdy, na przykład, po kliknięciu na Ustawienia chcemy pokazać użytkownikowi formularz ustawień, wpiszemy następujące instrukcje:

//cUstawienia, to nazwa zmiennej MenuItem, ktora zawiera tekst "Ustawienia"
private void cUstawienia_Click(object sender, System.EventArgs e)
{
     ustawienia.Show();//instrukcja pokazujaca nowy formularz
     this.Hide();     //instrukcja ukrywajaca obecny formularz
}

W niektórych przypadkach konieczne jest ukrywanie jednej lub wielu opcji z menu. Do tego wykorzystujemy logiczną właściwość Enabled, którą posiada każdy MenuItem.

Smartphone 2003 oprócz menu do sterowania programem wykorzystuje jeszcze niebieski przycisk wyboru i otaczające go strzałki, umożliwiającymi poruszanie się po menu, ale nie tylko. Żeby to pokazać należy najpierw stworzyć listę elementów zawierających nadawcę i temat wiadomości (którą widzimy na rysunku 3). Zapełnijmy więc cały ekran kontrolką ListView pozostawiając jedynie miejsce na menu. Teraz w jej właściwościach ,a konkretnie w View, należy wybrać jeden z czterech sposobów wyświetlania (Details, LargeIcons, List, SmallIcons). Ja wybrałem widok List. Przy Details, konieczne jest także dodanie kolumny, co możemy zrobić wybierając przycisk po kliknięciu na właściwość Columns a następnie wybierając Add. W podobny sposób możemy dodać kilka elementów do listy, dzięki właściwości Items. Jest to przydatne podczas testowania wyglądu wszystkich typów widoku, ponieważ dodawanie i usuwanie realizujemy zazwyczaj w programie (patrz kod; klasa Ekran). Teraz, kiedy w liście znajduje się kilka elementów możemy strzałkami poruszać się między nimi, jednak aby po naciśnięciu przycisku wyboru wykonała się akcja na odpowiednim elemencie musimy wykonać szereg czynności. Po pierwsze należy we właściwościach ListView przejść do Events poprzez kliknięcie na piorunek . Teraz zlokalizować trzeba akcję użytkownika, którą chcemy obsłużyć – w naszym przypadku ItemActivate. Po podwójnym kliknięciu, stworzona zostanie pusta funkcja obsługująca akcję. Gdy nie chcemy robić nowej, możemy wybrać jedną z już stworzonych z ComboBox’a po prawej. Dlaczego jednak nie kliknęliśmy na ListView, podobnie jak w przypadku menu? Ponieważ domyślną dla ListView akcją jest SelectedItemChange, czyli przejście z jednego elementu do kolejnego, a nie jego wybór. Załóżmy teraz, że chcemy dany element usunąć. W tym celu należy wypełnić funkcję obsługującą akcję wyboru tak, jak pokazano poniżej.

private void listView1_ItemActivate(object sender, System.EventArgs e)
{//usuwamy zaznaczony element z listy
     int ktory;
     if (listView1.SelectedIndices.Count!=0)//jezeli cos zaznaczono
     {
          ktory=listView1.SelectedIndices[0];//to pobiezemy jego indeks
          listView1.Items.RemoveAt(ktory);//i usuniemy z listy
     }
}

Aby w pełni wykorzystać funkcjonalność ListView do każdego elementu dołączymy obrazek. W tym celu przeciągamy na formularz element typu ImageList i we właściwości Images, po kliknięciu na , dodajemy za pomocą Add obrazki. Teraz wracamy do właściwości ListView i w SmallImageList lub LargeImageList (w zależności od widoku) wybieramy naszą nową listę obrazków. Po wykonaniu powyższych czynności możemy zmieniać obrazek ustawiany przy danym elemencie zmieniając wartość jego atrybutu ImageIndex, co pokazano poniżej.

//zmiana obrazka w n-tym elemencie na obrazek o indexie 1 w liscie obrazkow
listView1.Items[n].ImageIndex=1;

Powielając przedstawione powyżej techniki utworzyłem inne formularze. Przejdę teraz do opisu realizacji komunikacji Smartphone’a z serwerami pocztowymi.

5. Przesyłanie wiadomości

Na początku kilka słów o serwerach pocztowych. Przy wysyłaniu wiadomości łączymy się ze zwykłym serwerem SMTP lub rozszeżonym (ESMTP), a przy pobieraniu – z serwerem POP3. Komunikacja z nimi odbywa się na zasadzie przesyłania poleceń i odbierania odpowiedzi. Nie będę tu opisywał ich znaczenia, ponieważ zrobiłem to już w komentarzach w kodzie aplikacji (patrz klasy: KomunikatorSMTP i KomunikatorPOP). Bardziej szczegółowy opis można znaleźć w poniższym opracowaniu:

http://nss.et.put.poznan.pl/study/projekty/sieci_komputerowe/protokoly_pocztowe/html/index.html.
Zarówno SMTP, ESMTP jak i POP3 to protokoły tekstowe i umożliwiają przesyłanie jedynie znaków tekstowych. Jest to bardzo ważne, ponieważ przy wysyłaniu pliku należy go zamienić na postać tekstową przeprowadzając kodowanie Base64. Analogicznie po odebraniu wiadomości z załącznikiem należy go rozkodować. Warto jeszcze dodać, że serwer ESMTP w przeciwieństwie do zwykłego SMTP przed wysłaniem wiadomości przeprowadza autoryzację użytkownika, dlatego konieczne jest podanie loginu i hasła zakodowanych w Base64 (patrz kod; KomunikatorSMTP.auth()). Przed przystąpieniem do pisania programu polecam nawiązanie kilku testowych połączeń poprzez telnet, podając dodatkowo w wierszu poleceń nazwę serwera i port (25 dla SMTP i ESMTP lub 110 dla POP3), np.:

telnet pop3.tlen.pl 110

Teraz przejdźmy do opisu kodu. Aby umożliwić komórce realizowanie połączeń sieciowych musimy najpierw załączyć odpowiednią przestrzeń nazw.

using System.Net.Sockets;

Do komunikacji sieciowej używamy obiektu klasy TcpClient, który po otwarciu połączenia tworzy wykorzystywany do przesyłania danych strumień sieciowy (NetworkStream) zawierający strumień odbierający i wysyłający dane. Do otwarcia połączenia potrzebna jest nazwa serwera (np. smtp.wp.pl) oraz numer portu. Otwórzmy zatem połączenie z przykładowym serwerem SMTP.

try
{
     this.client= new TcpClient("smtp.wp.pl", 25);
     this.netStream= client.GetStream();
}
catch
{
     throw new Exception("Serwer "+server+" nie odpowiada.\nProsze sprawdzic poprawnosc nazwy.");
}

Aby wysłać jakieś dane strumieniem sieciowym należy najpierw zamienić je na tablicę bajtów. Odwrotną konwersję trzeba zrealizować przy odbieraniu. W tym celu warto napisać funkcję readLine(), która odbiera bajty ze strumienia sieciowego do napotkania znaku końca linii, po czym zwraca cały string (patrz kod; KomunikatorSMTP.readLine()). Napiszmy teraz obsługę polecenia MAIL FROM dla serwera SMTP.

private bool mailFrom(string nadawca)
{//podanie serwerowi nadawcy wiadomosci, poprawna odpowiedz to "250"
     byte[] command= Encoding.ASCII.GetBytes("MAIL FROM: "+nadawca+"\r\n");
     netStream.Write(command,0,command.Length);//wyslanie komendy
     return (readLine().Substring(0,3).Equals("250"));
}

Teraz należy w podobny sposób zaimplementować pozostałe komendy.

A co z załącznikami? Jak wcześniej wspomniałem muszą one być zakodowane w Base64. Do tego można wykorzystać gotowe funkcje klasy Convert:

Convert.ToBase64String(...);
Convert.FromBase64String(...);

Jednak samo zakodowanie to nie wszystko. Plik musi być dołączony do wiadomości zgodnie ze standardem MIME. W tym celu oprócz treści pliku (w Base64) email powinien zawierać szereg innych informacji. Zarówno dla całej wiadomości jak i dla każdego załącznika (treść jest także traktowana jako załącznik) muszą być stworzone nagłówki opisujące zawartość. Poniżej zamieściłem krótki opis atrybutów MIME wykorzystywanych w programie:

  • From: nadawca@domena.com – określa nadawcę wiadomości,

  • To: odbiorca@domena.com – oznacza odbiorcę wiadomości,

  • Subject: temat wiadomosci – określa temat wiadomości,

  • MIME-Version: 1.0 – zaznaczenie używanej wersji MIME,

  • Content-type: typ – typ wiadomości lub załącznika, może zawierać dodatkowo:

    • boundary=”ciag_znakow” – rozdziela poszczególne załączniki,

    • charset=” ISO-8859-2” – stosowany zestaw znaków (tu ISO-8859-2),

    • name=”plik.bin” – określa nazwę załącznika (najczęściej nazwa pliku),

  • Content-Transfer-Encoding: kodowanie – kodowanie zawartości (np. base64),

  • Content-Disposition: dyspozycja – określenie dyspozycji (najczęściej attachment);

    • filename=”plik.bin” – określenie nazwy pliku załącznika

Żeby sprawdzić strukturę MIME wystarczy otworzyć w notatniku jakiś email z załącznikiem. Gdy będziemy już dysponować treścią pobranej wiadomości należy poprzez żmudne przeszukiwanie każdej linii nagłówka pobrać powyższe charakterystyki, co wiąże się z wielokrotnym wykonywaniem metod StartsWith() i Substring(), np.:

if (linia.StartsWith("From:"))
     nadawca=linia.Substring(6,linia.Length-8);//wyciecie nadawcy z linii

Ponieważ odbieranie lub wysyłanie wiadomości jest niekiedy czasochłonne warto zastanowić się nad współbieżnością tych procesów.

6. Tworzenie wątków

Wielowątkowość nie była głównym celem tego programu, dlatego ograniczyłem się tylko do stworzenia wątków, bez możliwości ich zatrzymania, czy anulowania (np. wyjście z aplikacji podczas pobierania wiadomości skutecznie zawiesza telefon). Współbieżność miała na celu jedynie umożliwienie przeglądania wiadomości podczas pobierania lub wysyłki.

Aby utworzyć wątki należy najpierw dołączyć przestrzeń nazw System.Threading. Teraz potrzebna jest nam jeszcze funkcja, która ma być wykonywana. Jej nazwę należy podać w konstruktorze obiektu ThreadStart. Jednak w ten sposób nie możemy przekazać do funkcji żadnych parametrów. Można to jednak obejść, co pokazuje przykład poniżej.

internal class OdbieraczWiadomosci
{//klasa do pobierania wiadomosci z serwera w oddzielnym watku
     private Ekran e;          //tu jest lista wiadomosci
     private Skrzynka parent;//tu jest funkcja pobierajaca wiadomosci
     public OdbieraczWiadomosci(Ekran e, Skrzynka parent)
     {
          this.e=e;
          this.parent=parent;
     }
     public void start()
     {//tworzymu i uruchamiamy nowy watek
          Thread t= new Thread(new ThreadStart(this.run));
          t.Start();
     }
     public void run()
     {//pobieramy wiadomosci i aktualizujemy podana jako parametr liste
          parent._pobierzWiadomosci(e);
     }
}

Analogicznie utworzona została klasa WysylaczWiadomosci. Przejdę teraz omówienia przechowywania wiadomości w telefonie.

7. Przechowywanie wiadomości

Smartphone posiada system plików, w przeciwieństwie do komórek z J2ME. Dzieki temu przechowywanie w nim wiadomości nie różni się od używania plików w zwykłej aplikacji. Najpierw należy dodać przestrzeń nazw System.IO. Następnie za pomocą obiektu klasy FileStream możemy zapisywać i odczytywać dane z plików. Jednak aby nie robić tego bajt po bajcie, do zapisu w formacie binarnym warto użyć jeszcze BinaryWriter’a, a do odczytu BinaryReader’a, co pokazano poniżej.

FileStream fs= new FileStream(@"\nowy_plik.bin",FileMode.Create, FileAccess.Write);
BinaryWriter bw= new BinaryWriter(fs);
bw.Write("string");//zapisujemy string
bw.Write(10);//zapisujemy liczbe
bw.Write(true);//zapisujemy wartosc logiczna
bw.Close();
fs.Close();

Znak ‘@’ przed ścieżką do pliku oznacza, że znaki w ciapkach będą traktowane tak, jak je widzimy, a nie jako znak nowej linii (‘\n’) i napis „owy_plik.bin”. Warto w tym przykładzie zwrócić uwagę na to, że bez względu na typ zmienne zapisujemy tą samą instrukcją. Niestety nie obowiązuje to już przy ich odczytywaniu, co widać poniżej:

InitializeComponent();
FileStream fs= new FileStream(@"\nowy_plik.bin",FileMode.Open, FileAccess.Read);
BinaryReader br= new BinaryReader(fs);
br.ReadString();//odczytanie stringu
br.ReadInt32();//odczytanie liczby
br.ReadBoolean();//odczytanie wartosci logicznej
br.Close();
fs.Close();

W przypadku plików tekstowych możemy wykorzystać klasy StreamWriter oraz StreamReader.

8. Podsumowanie

Mam nadzieję, że zachęciłem Cię drogi czytelniku tym opisem swoich doświadczeń do tworzenia aplikacji mobilnych w C#, lub chociaż wyjaśniłem trochę podstaw na dobry początek. Jest jeszcze wiele interesujących rzeczy do poznania w tej dziedzinie, jednak nie sposób tego wszystkiego opisać w jednym artykule. Sam, jako początkujący C#-owiec, mam jeszcze wiele do odkrycia. Kilka ciekawych rzeczy, których tu nie poruszyłem można znaleźć w kodzie aplikacji (np. FileChooser). Postaram się przybliżyć je w kolejnym artykule.

Bibliografia

1. Wstęp do Smartphone 2003:

http://www.oreillynet.com/pub/a/wireless/2004/01/07/smartphone.html

2. Wprowadzenie do P/Invoke w .NET CF:

http://msdn.microsoft.com/mobility/understanding/articles/default.aspx?pull=/library/en-us/dnnetcomp/html/netcfintrointerp.asp?frame=true

3. Zaawansowane P/Invoke w .NET CF:

http://msdn.microsoft.com/mobility/understanding/articles/default.aspx?pull=/library/en-us/dnnetcomp/html/netcfadvinterop.asp
4. Charakterystyka protokołów POP3 i SMTP:

http://nss.et.put.poznan.pl/study/projekty/sieci_komputerowe/protokoly_pocztowe/html/index.html

5. Podstawy MIME – RFC 1521, oraz inne (RFC 2045 - 2049):

http://www.rfc-archive.org/getrfc.php?rfc=1521

6. „Bardzo prosty klient poczty na Smartphone’a...” - artykuł Artura Gniadzika:

http://www.codeguru.pl/articleDetails.aspx?id=35

Łukasz Boruszko

Wydział Informatyki, Politechnika Białostocka

 

Załączniki:

Podobne artykuły

Komentarze 1

User 108709
User 108709
1 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
OK, miły tekst - ale opis InteropServices.... ajj
pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.