Jakub Florczyk - Blog o programowaniu .NET

Programista praktyczny

Category: Compact Framework

SamsungMobileSDKNET

Żeby nie było, iż jestem jednostronny to dla odmiany dziś o firmie Samsung. I o jego w moim mniemaniu cudownym dziecku Samsung Windows Mobile SDK.

Na początek małe wytknięcie pięty achillesowej firmy HTC. Otóż mimo iż produkują świetne telefony, z najlepszymy nakładkami i oprogramowaniem, to ogromnym minusem jest brak jakiegokolwiek SDK dla bibliotek HTC.
Oczywiście podstawową część obsługi telefonu możemy załatwić standardowymi bibliotekami .NET / Tapi/ ExTapi / RIL ale często te najsmaczniejsze kąski siedzą w warstwach do których HTC nas nie chce dopuścić. I do dziś się zastanawiam dlaczego.
O tyle dobrze, że niektórym udało się dostać to obsługi akcelerometru i LED-ów HTC o tyle kamera np. nadal jest jedną wielką zagadką i dostęp do surowych danych a nie ubranych w jakieś okienka Microsoftu.

Z drugiej strony pojawia się firma Samsung i udostępnia pełne SDK do funkcji telefonu. Pisząc pełne, mam na myśli: akcelerometr, kamerę, lampę kamery, radio, led, haptics, czujnik światła, mysz optyczną, orientację 2D i 3D, czujnik zbliżeniowy, procesor audio, slider-a, wyjście TV, wibrację i kółko nawigacji. Jeżeli jeszcze ci szczęka nie opadła, to jest właśnie dobry moment.

Samsung całość udostępnił jako SDK, które można pobrać tutaj. Biblioteki są przygotowane pod C++ z jednym przykładem natywnym.

Jako, że nie mogłem znaleźć wrappera z pełną implementacją, postanowiłem go napisać samemu. Biblioteka dostępna jest na CodePlex SamsungMobileSDKNET. Na obecną chwilę zawiera pełną implementację wersji 2.1. Ale ponieważ nie posiadam telefonu Samsunga, żaden element nie jest przetestowany. Dlatego prośba dla programistów z telefonami Samsunga o przetestowanie poszczególnych elementów. No chyba, że wcześniej z Orange-a wezmę Samsunga Omnia II – bo ostatnio mi proponowali nawet w rozsądnej cenie i sam przetestuje poszczególne elementy.

RilNET – Radio Interface Layer (RIL) .NET wrapper – sprostowanie

A propos artykułu RilNET – Radio Interface Layer (RIL) .NET wrapper należy się drobne sprostowanie działania biblioteki a przede wszystkim obsługi lokalizacji.

Otóż po wielu testach na telefonach firm HP i HTC oraz po przeczytaniu kilku artykułów w sieci wszystkim osobom, które używają RIL-a należy się małe wyjaśnienie o którym nie zdawałem sobie sprawy w momencie pisania wpisu.

Otóż implementacja biblioteki RIL zależy od producenta telefonu. Sprowadza się to do tego, iż część funkcji może nie być w ogóle zaimplementowana!

W praktyce wygląda to tak, iż mój wysłużony HP 614C nie posiadał zaimplementowanej metody udostępniającej informację o komórce sieci  GetCellTowerInfo przez co w artykule błędnie zasugerowałem, że to pewnie wina sieci Orange.
Po zamianie i testach na HP Touch Pro2 wszystko działa poprawnie. Funkcja zwraca informacje o sieci!

Oczywiście można sobie zadać pytanie: no i co z tego? Otóż wbrew pozorom bardzo wiele. Chociażby to, że Google Maps potrafi obsługiwać RIL-a i jest w stanie określić waszą przybliżoną pozycję na podstawie wieży komórkowej.

Podsumowując. Jest to kolejny powód aby kupować telefony poważnych firm. Choć HP kiedyś bardzo się starało w sprawie PocketPC, obecnie daleko im do czołówki i standardów króla HTC.

BlipiNET

W nowej wersji Pocket Blip pracuję nad zmianą dostawcy wyszukiwarki oraz statystykami użytkowników. Wybór dostawcy padł na Blipi Marka Fossa, ponieważ poza wyszukiwarką Marek udostępnia też ranking i licznik obserwujących. Z tego powodu powodu popełniłem bibliotekę dostępową .NET do API. Projekt można znaleźć na CodePlex BlipiNET.

Jak w przypadku innych bibliotek zdecydowałem się na “toporne” metody HttpWebRequest ze względu na brak nowych rozwiązań a’la WCF w wersji Json w Compact Framework.

Przykłady użycia:

// Wyszukiwanie wiadomości
BlipiService bs = new BlipiService("[twój klucz API]");
Message[] messages = bs.Search("jakubflorczyk");
// Ranking (Top100)
BlipiService bs = new BlipiService("[twój klucz API]");
User userRank = bs.GetRank("jakubflorczyk");

Dostępna jest pełna implementacja aktualnego API:
- wyszukiwanie
- pobieranie rankingu użytkownika
- pobieranie licznika obserwujących użytkownika
- pobieranie statystyk Top 10 i 100

Lokalizacja aplikacji w .NET Compact Framework

W kilku krokach i słowach, postaram się wyjaśnić zasadę lokalizacji aplikacji .NET Compact Framework. Wszystko przy uwzględnieniu certyfikowania aplikacji dla Windows Marketplace for Mobile.

Na samym początku pragnę przypomnieć, że lokalizacja opiera się na tłumaczeniu aplikacji dla danego kraju a nie języka! To jest bardzo ważny element z którego nie wszyscy sobie zdają sprawę. Ale później pokaże jak w prosty sposób “oszukać” Marketplace aby nasza aplikacja na jednym języku chodziła na wiele krajów.

Drugim ważnym problemem jest pytanie jakie musi sobie zadać każdy deweloper: w którym momencie lokalizować aplikację? Odpowiedź na to pytanie nie jest prosta. Jeżeli zamawiamy grafiki, piszemy teksty do naszej aplikacji należy o tym pamiętać na samym początku. Ale samą lokalizację sugeruje robić na samym końcu – nawet po testach funkcjonalnych. Ale ta teoria sprawdza się tylko w przypadku aplikacji okienkowych, bo gry warto lokalizować na samym początku, wraz z pisanym kodem. W aplikacjach okienkowych problem sprowadza się do zmian wprowadzanych na poszczególnych formach, które następnie musimy weryfikować na innych wersjach językowych. Dlatego aby uprościć sobie przeklikiwanie się przez kolejne formy i sprawdzanie czy zmiany zostały poprawnie przeniesione, sugeruje lokalizować aplikację na samym końcu.

Lokalizacja kodu.

Tutaj sprawa wygląda dość prosto i całość problemu sprowadza się do poprawnego wyświetlania danych oraz odpowiedniego sposobu konwertowania tychże.

// konwersja
DateTime dateTime = Convert.ToDateTime(xeEntry.Attribute("DateTime").Value, CultureInfo.CurrentCulture);
// wyświetlanie
label1.Text = dateTime.ToString(CultureInfo.CurrentCulture);

Ot co, cała filozofia. Jeżeli używamy Microsoft FxCop – nie omieszka nas o tym poinformować odpowiednim komunikatem.

Lokalizacja okien (Form-ów).

Mój projekt składa się tylko z jednego forma, w którym jest bardzo proste menu (w tym momencie w języku angielskim):
Largo_Form1

Następnie przełączamy się na właściwości forma i zmieniamy Localizable na True oraz Language na Polish:

Largo_Language

Po tej operacji w nazwie okna (na górnym pasku) powinniśmy zobaczyć napis Form1.cs [Design - Polish]. W tym momencie możemy przetłumaczyć menu na język polski:

Largo_Form1_Polish

Kiedy dokonamy pierwszej zmiany w plikach aplikacji zostanie utworzony dodatkowy plik o nazwie Form1.pl.resx. Każde tłumaczenie jakiego dokonamy będzie miało odzwierciedlenie właśnie w tym pliku – poprzez utworzenie odpowiedniego rekordu lokalizacji.

Tak jak pisałem na początku w przypadku okienek warto te kroki zostawić na sam koniec, aby później nie paprać się zbytnio w zmianach na kolejnych formach kiedy zauważymy źle ustawiona kontrolkę lub tym podobny trywialny problem, który po lokalizacji może narastać lawinowo w zależności od ilości krajów.

Lokalizacja zasobów (Resources).

Załóżmy że nasz aplikacja wyświetla MessageBox-a z tekstem “Are you sure?” – np w momencie kliknięcia w Exit (tak na marginesie certyfikacja dla Marketplace zabrania takich praktyk). Tekst przenosimy do zasobów pod nazwą MessageBox_Text. Mamy załatwioną wersję angielską.
Aby utworzyć odpowiednie tłumaczenie w wersji polskiej, musimy utworzyć kopię pliku Resources.resx zmieniając jego nazwę na Resources.pl.resx i dokonując tłumaczenia. Gotowe!

Lokalizacja instalatora.

Aby przenieść i wkompilować wszystkie wersje językowe w jednego CAB-a musimy dodać do projektu instalatora pliki lokalizacyjne. Aby to zrobić klikamy prawym klawiszem na projekcie i wybieramy Add -> Project Output… -> Localized resources.

W tym momencie uzyskujemy jeden plik CAB, który załaduje wersję językową naszej aplikacji w zależności od ustawień regionalnych telefonu użytkownika.

A co na to Marketplace?

Zanim napiszę o Marketplace wrócę do tematu, który z premedytacją pominąłem na początku a dotyczy działania lokalizacji na telefonie. Otóż sam proces jest stosunkowo prosty w działaniu. Windows Mobile odpala program, szukając jednocześnie lokalizacji zgodnej z danym ustawieniem regionalnym. Jeżeli nie dokonamy lokalizacji programu albo nie system operacyjny nie znajdzie odpowiedniej lokalizacji, zostanie załadowana lokalizacja domyślna programu. Jeżeli system odnajdzie odpowiednią lokalizację – zostanie ona załadowana.

Windows Marketplace for Mobile zakłada, że każdy certyfikowany program może zawierać nieograniczoną ilość lokalizacji o ile znajdują się one w jednym pliku CAB. Jeżeli lokalizacja znajduje się w oddzielnym pliku CAB niż “oryginał” cały proces certyfikacji musi zostać wykonany ponownie za co zapłacimy 100$. W pierwszym wypadku zapłacimy tylko 9.99$ za każdy kolejny kraj. Jednym słowem warto kompilować CAB-y z wieloma lokalizacjami.

Wracając do tematu “oszukania” Marketplace. Z mojego punku widzenia istnieje Polska i reszta świata, która mówi w języku angielskim. W Marketplace reszta świata mówiąca po angielsku sprowadza się do następujących krajów: Australia, Kanada, Indie, Irlandia, Nowa Zelandia, Singapur, Wielka Brytania, Stany Zjednoczone. Aby nie komplikować sobie sprawy z lokalizacjami sugeruję utworzyć “domyślną” lokalizację w języku angielskim a tłumaczenie w Polskim. Chodzi o to, że zamiast tworzyć osiem lokalizacji, tworzymy tylko jedną.

Poprawny .NET Compact Framework CAB w kilku krokach

Na początek przyznam się, że zawsze miałem problemy z projektami instalacyjnymi. Dlatego chciałbym w kilku krokach przybliżyć wam mój sposób tworzenia poprawnego CAB-a dla Compact Framework, do którego doszedłem po wielu testach i próbach.

Projekt do którego tworzę CAB-a nazywa się Largo.

Krok pierwszy – tworzymy “Smart Device CAB Project”:

Smart_Device_CAB_Project

Ja przyjąłem standard nazewnictwa [Nazwa projektu].Cab. Ale jako tako nazwa ta nie ma większego znaczenia.

Krok drugi – zmieniamy nazwę pliku wyjściowego (prawy klik na projekcie i wybieramy Properties):

Na początek mała uwaga, kliknięcie Properties z menu kontekstowego przywołuje inny ekran niż wybór Properties z Properties Window.

Property_Pages

Domyślna nazwa zbudowana jest według zasady “Debug\[Nazwa projektu CAB].cab” dlatego w naszym przypadku będzie to “Debug\Largo.Cab.cab”. Wystarczy usunąć przyrostek .cab aby nazwa wyglądała poprawnie (“Debug\Largo.cab”).

Krok trzeci – zmiana nazwy produktu i firmy (wybieramy Properties z Properties Window):

Properties_Window

Manufacturer – zmieniamy na nazwę naszej firmy / imię nazwisko lub nick. Ta nazwa pojawi się tylko i wyłącznie podczas instalacji w momencie kiedy Windows Mobile pyta nas czy chcemy zainstalować program “[Manufacturer] [ProductName]“. Według mnie nazwa ta jest odrobinę zbędna ale np. Windows Marketplace for Mobile wymaga aby nazwa ta zgadzała się z nazwą firmy / dewelopera który jest zgłoszony do sklepu.

ProductName  – zmieniamy na nazwę naszego programu. Domyślnie jest to nazwa projektu CAB. W tym punkcie warto pamiętać, że pod taką nazwą pojawi się katalog z naszą aplikacją w “Program Files”.

Krok czwarty – usuwamy z “File System” folder o nazwie “Program Files Folder” (prawy klik na katalogu i wybieramy Delete):

Program_Files_Folder_remove

W większości przypadków ten katalog jest zbędny dlatego spokojnie można go usunąć.

Krok piąty – dodajemy “Primary Output” (prawy klik na projekcie Add -> Project Output… ):

Primary_Output

To jest zestaw podstawowych plików naszej aplikacji, który zawiera plik exe oraz załączone biblioteki.

Krok szósty – utworzenie katalogu “Programs Folder” (prawy klik na lewym panelu Add Special Folder – > Programs Folder):

Programs_Folder

Krok siódmy – utworzenie skrótu do programu (rozwijamy w lewym panelu wcześniej utworzony “Programs Folder” i w prawym panelu, prawy klik “Create New Shortcut”):

Create_New_Shortcut

Następnie pojawi się okno wyboru elementu do którego tworzymy skrót:

Select_Item_In_Project

Wybieramy “Application Folder” a następnie “Primary output from [Nazwa projektu] (Active)”:

Primary_Output_From_Largo

Nazwa która się pojawi od razu zmieniamy na “Largo” (albo jakakolwiek inna). Ta nazwa pojawi się w menu Start.

I to zasadniczo wszystko. W siedmiu krokach udało nam się utworzyć podstawowy projekt CAB. Ze swojej strony chciałbym tylko dodać kilka porad co do późniejszej pracy z takim projektem:

  • Jeżeli dodajemy do oryginalnego projektu jakieś biblioteki warto po kompilacji kliknąć na katalogu “Detected Dependencies” i wybrać “Refresh Dependencies”. Czasami Visual Studio zapomina je odświeżyć i dlatego warto zrobić to ręcznie.
  • Jeżeli używamy plików z ustawionymi atrybutami “Build Action” na “Content” pamiętajmy o tym aby je dołączyć do CAB. Wystarczy prawy klik na projekcie Add -> Project Output -> Content Files i pliki znajdą się w instalatorze.
  • Podobnie jak powyżej dodajemy pliki lokalizacyjne Add -> Project Output -> Localized resources.

HMACSHA1 (HMAC-SHA1) w Compact Framework

Ostatnio w wolnych chwilach pracuje nad protokołem OAuth dla Pocket Blip-a wraz z Filipem Tepperem z Blip.pl, który był tak miły i udostępnił mi jedno konto testowe. Problemy pojawiły się już na początku bo w bibliotekach Compact Framework brakuje implementacji HMAC-SHA1. Chciałem sprawę rozwiązać przez użycie OPENNETCF, które mają ten algorytm zaimplementowany, ale niestety pojawiały się błędy których nijak nie mogłem rozwiązać, bo sypały je biblioteki CF-a.

Dlatego jak w poprzednim artykule o PointF rozwiązałem problem używając Reflector-a i dzięki drobnym poprawkom klasa działa lepiej i się nie sypie jak wersja z OPENNETCF:

namespace System.Security.Cryptography
{
    public abstract class KeyedHashAlgorithm : HashAlgorithm
    {
        // Fields
        protected byte[] KeyValue;

        // Methods
        protected KeyedHashAlgorithm()
        {
        }

        public static KeyedHashAlgorithm Create()
        {
            return Create("System.Security.Cryptography.KeyedHashAlgorithm");
        }

        public static KeyedHashAlgorithm Create(string algName)
        {
            return (KeyedHashAlgorithm)CryptoConfig.CreateFromName(algName);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.KeyValue != null)
                {
                    Array.Clear(this.KeyValue, 0, this.KeyValue.Length);
                }
                this.KeyValue = null;
            }
            base.Dispose(disposing);
        }

        // Properties
        public virtual byte[] Key
        {
            get
            {
                return (byte[])this.KeyValue.Clone();
            }
            set
            {
                if (base.State != 0)
                {
                    throw new CryptographicException("Cryptography_HashKeySet");
                }
                this.KeyValue = (byte[])value.Clone();
            }
        }
    }

    public abstract class HMAC : KeyedHashAlgorithm
    {
        // Fields
        private int blockSizeValue = 0x40;
        internal HashAlgorithm m_hash1;
        internal HashAlgorithm m_hash2;
        private bool m_hashing;
        internal string m_hashName;
        private byte[] m_inner;
        private byte[] m_outer;

        // Methods
        protected HMAC()
        {
        }

        public static HMAC Create()
        {
            return Create("System.Security.Cryptography.HMAC");
        }

        public static HMAC Create(string algorithmName)
        {
            return (HMAC)CryptoConfig.CreateFromName(algorithmName);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.m_hash1 != null)
                {
                    this.m_hash1.Clear();
                }
                if (this.m_hash2 != null)
                {
                    this.m_hash2.Clear();
                }
                if (this.m_inner != null)
                {
                    Array.Clear(this.m_inner, 0, this.m_inner.Length);
                }
                if (this.m_outer != null)
                {
                    Array.Clear(this.m_outer, 0, this.m_outer.Length);
                }
            }
            base.Dispose(disposing);
        }

        protected override void HashCore(byte[] rgb, int ib, int cb)
        {
            if (!this.m_hashing)
            {
                this.m_hash1.TransformBlock(this.m_inner, 0, this.m_inner.Length, this.m_inner, 0);
                this.m_hashing = true;
            }
            this.m_hash1.TransformBlock(rgb, ib, cb, rgb, ib);
        }

        protected override byte[] HashFinal()
        {
            if (!this.m_hashing)
            {
                this.m_hash1.TransformBlock(this.m_inner, 0, this.m_inner.Length, this.m_inner, 0);
                this.m_hashing = true;
            }
            this.m_hash1.TransformFinalBlock(new byte[0], 0, 0);
            byte[] hashValue = this.m_hash1.Hash;
            this.m_hash2.TransformBlock(this.m_outer, 0, this.m_outer.Length, this.m_outer, 0);
            this.m_hash2.TransformBlock(hashValue, 0, hashValue.Length, hashValue, 0);
            this.m_hashing = false;
            this.m_hash2.TransformFinalBlock(new byte[0], 0, 0);
            return this.m_hash2.Hash;
        }

        public override void Initialize()
        {
            this.m_hash1.Initialize();
            this.m_hash2.Initialize();
            this.m_hashing = false;
        }

        internal void InitializeKey(byte[] key)
        {
            this.m_inner = null;
            this.m_outer = null;
            if (key.Length > this.BlockSizeValue)
            {
                base.KeyValue = this.m_hash1.ComputeHash(key);
            }
            else
            {
                base.KeyValue = (byte[])key.Clone();
            }
            this.UpdateIOPadBuffers();
        }

        private void UpdateIOPadBuffers()
        {
            int num;
            if (this.m_inner == null)
            {
                this.m_inner = new byte[this.BlockSizeValue];
            }
            if (this.m_outer == null)
            {
                this.m_outer = new byte[this.BlockSizeValue];
            }
            for (num = 0; num < this.BlockSizeValue; num++)
            {
                this.m_inner[num] = 0x36;
                this.m_outer[num] = 0x5c;
            }
            for (num = 0; num < base.KeyValue.Length; num++)
            {
                this.m_inner[num] = (byte)(this.m_inner[num] ^ base.KeyValue[num]);
                this.m_outer[num] = (byte)(this.m_outer[num] ^ base.KeyValue[num]);
            }
        }

        // Properties
        protected int BlockSizeValue
        {
            get
            {
                return this.blockSizeValue;
            }
            set
            {
                this.blockSizeValue = value;
            }
        }

        public string HashName
        {
            get
            {
                return this.m_hashName;
            }
            set
            {
                if (this.m_hashing)
                {
                    throw new CryptographicException("Cryptography_HashNameSet");
                }
                this.m_hashName = value;
                this.m_hash1 = HashAlgorithm.Create(this.m_hashName);
                this.m_hash2 = HashAlgorithm.Create(this.m_hashName);
            }
        }

        public override byte[] Key
        {
            get
            {
                return (byte[])base.KeyValue.Clone();
            }
            set
            {
                if (this.m_hashing)
                {
                    throw new CryptographicException("Cryptography_HashKeySet");
                }
                this.InitializeKey(value);
            }
        }
    }

    public class HMACSHA1 : HMAC
    {
        public HMACSHA1(byte[] key)
            : this(key, false)
        {
        }

        public HMACSHA1(byte[] key, bool useManagedSha1)
        {
            base.m_hashName = "SHA1";
            if (useManagedSha1)
            {
                base.m_hash1 = new SHA1Managed();
                base.m_hash2 = new SHA1Managed();
            }
            else
            {
                base.m_hash1 = new SHA1CryptoServiceProvider();
                base.m_hash2 = new SHA1CryptoServiceProvider();
            }
            base.HashSizeValue = 160;
            base.InitializeKey(key);
        }
    }

}

Dla zainteresowanych klasę zmieniłem z miejscach gdzie pobierana jest wartość HashAlgorithm-u (this.m_hash1.Hash) oraz wszędzie tam gdzie znajdują się opisy błedów (za leniwy jestem aby je wyciągać z Resources :) .

PointF w Compact Framework

Jedną z największych bolączek w programowaniu w Compact Framework jest brak implementacji PointF dla platformy Windows Mobile. Mimo braku obsługi w rysowaniu obiektów przy użyciu float Point-a, sama struktura przydaje się w przekształceniach aby uniknąć w nich strat.

Poniżej prezentuje wersję PointF wyciągniętą wprost z “pełnej” wersji przy użyciu Reflector-a:

using System.Globalization;
using System.Runtime.InteropServices;

namespace System.Drawing
{
    [Serializable, StructLayout(LayoutKind.Sequential)]
    public struct PointF
    {
        public static readonly PointF Empty;
        private float x;
        private float y;

        public PointF(float x, float y)
        {
            this.x = x;
            this.y = y;
        }

        public bool IsEmpty
        {
            get
            {
                return ((this.x == 0f) && (this.y == 0f));
            }
        }
        public float X
        {
            get
            {
                return this.x;
            }
            set
            {
                this.x = value;
            }
        }
        public float Y
        {
            get
            {
                return this.y;
            }
            set
            {
                this.y = value;
            }
        }
        public static PointF operator +(PointF pt, Size sz)
        {
            return Add(pt, sz);
        }

        public static PointF operator -(PointF pt, Size sz)
        {
            return Subtract(pt, sz);
        }

        public static PointF operator +(PointF pt, SizeF sz)
        {
            return Add(pt, sz);
        }

        public static PointF operator -(PointF pt, SizeF sz)
        {
            return Subtract(pt, sz);
        }

        public static bool operator ==(PointF left, PointF right)
        {
            return ((left.X == right.X) && (left.Y == right.Y));
        }

        public static bool operator !=(PointF left, PointF right)
        {
            return !(left == right);
        }

        public static PointF Add(PointF pt, Size sz)
        {
            return new PointF(pt.X + sz.Width, pt.Y + sz.Height);
        }

        public static PointF Subtract(PointF pt, Size sz)
        {
            return new PointF(pt.X - sz.Width, pt.Y - sz.Height);
        }

        public static PointF Add(PointF pt, SizeF sz)
        {
            return new PointF(pt.X + sz.Width, pt.Y + sz.Height);
        }

        public static PointF Subtract(PointF pt, SizeF sz)
        {
            return new PointF(pt.X - sz.Width, pt.Y - sz.Height);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is PointF))
            {
                return false;
            }
            PointF tf = (PointF)obj;
            return (((tf.X == this.X) && (tf.Y == this.Y)) && tf.GetType().Equals(base.GetType()));
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return string.Format(CultureInfo.CurrentCulture, "{{X={0}, Y={1}}}", new object[] { this.x, this.y });
        }

        static PointF()
        {
            Empty = new PointF();
        }
    }
}

Pocket GPW

Po wielu przeciwnościach losu w końcu udało mi się ukończyć pierwszą stabilną wersję Pocket GPW. Mobilną aplikacje Giełdy Papierów Wartościowych, która była pomysłem Vonski-ego w konkursie ogłoszonym na portalu PDA.pl.

    Na początek chciałbym podziękować wszystkim firmom i osobom zaangażowanym w projekt:

  • Dawid Gatti z PDA.pl za to że uwierzył że w samą idee konkursu i go zorganizował na łamach swojego portalu
  • TomTom za ufundowanie naprawdę świetnej nagrody TomTom XL z IQ Route 2.0
  • Piotr Szostak z Streemo.pl za patronat medialny
  • HTC za udostępnienie HTC Touch Pro2 do testów aplikacji
  • adek za cierpliwość przy finalnym testowaniu aplikacji

Aplikacja dostępna jest na portalu Codeplex. Poza plikiem CAB instalatora dostępne jest także pełne źródło aplikacji, dla osób które są zainteresowane jej modyfikacją lub po prostu przejrzeniem.

Program w aktualnej wersji pobiera notowania, pozwala na edycję notatki przy notowaniu oraz zdefiniowanie alarmów dla określonych warunków. Dane są przechowywane w bazie SQL Server CE (plik znajduje się w instalatorze).

    Wymagania:

  • .NET Compact Framework 3.5; do pobrania tutaj
  • Microsoft SQL Server Compact 3.5 dla Windows Mobile; do pobrania tutaj (tutorial jak zainstalować znajduję się tutaj)

Ekrany:

Pocket_GPW_Screen1 Pocket_GPW_Screen2 Pocket_GPW_Screen3
Pocket_GPW_Screen4 Pocket_GPW_Screen5 Pocket_GPW_Screen6
Pocket_GPW_Screen7

Czy to już koniec? Na pewno nie, nadal czekam na sugestie i pomysły dotyczące aplikacji; na pewno każdą przeczytam, choć może nie koniecznie je zaimplementuje ;-)

.NET Compact Framework PictureBox w rozdzielczości VGA

W jednym z poprzednich wpisów opisywałem identyfikację rozdzielczości VGA na urządzeniach Windows Mobile. Pisałem, że kontrolki .NET CE same dopasowują się do aktualnej wartości CurrentAutoScaleDimensions. Tak też działa PictureBox, jednak zawartość kontrolki nie skaluje się automatycznie. A więc jak rozwiązać ten problem?

Zacznijmy od początku. PictureBox w rozdzielczości QVGA:

PictureBox_QVGA

Wszystko wygląda dobrze, ale zmieńmy skin kontrolki na rozdzielczość VGA:

PictureBox_VGA

Jak widać przy standardowym ustawieniu kontrolki grafika pozostała bez zmian. Najprostszym rozwiązaniem tego problemu jest zmiana SizeMode PictureBox na StretchImage, aby grafika wypełniła dostępne miejsce:

PictureBox_VGA-Stretch

Teraz wszystko wygląda poprawnie. W przypadku używania tego rozwiązania pamiętaj o tym, żeby wielkość kontrolki dopasować do wielkości osadzanej grafiki, aby uniknąć niepoprawnego skalowania.

Managed wrapper dla Windows Mobile 6.5 Gesture API

Dwójka programistów Microsoft Ron Buckton i Alex Yakhnin przygotowała wrappera dla Windows Mobile 6.5 Gesture API dla .NET Compact Framework, który do tej pory dostępny był tylko dla programistów C++.

Bibliotekę można pobrać tutaj. Alex i Ron przygotowali także dwa webcast-y opisujące sposób użycia wrapper-a, które można obejrzeć tu i tu. Polecam bo Gesture samo w sobie jest świetną biblioteką a efekty są bardzo ciekawe przy małym nakładzie pracy.