Lipiec « 2011 « Pabloware + Windows Phone

Pabloware + Windows Phone

Niezależny blog firmy Pabloware o systemie Windows® Phone 7

Omówienie interfejsu użytkownika aplikacji Dietphone

Główny ekran aplikacji podzielony jest na dwie zakładki: posiłki i produkty. Jako „zakładki” rozumiana jest standardowa kontrolka interfejsu użytkownika Windows Phone 7 pivot. Umożliwia ona przechodzenie pomiędzy poszczególnymi ekranami (zakładkami) poprzez wykonanie gestu „przewinięcia w poziomie” oraz wyświetla ich tytuły, w miarę dostępności miejsca, u góry ekranu. Kontrolka ta jest intensywnie używana w interfejsie użytkownika opisywanej aplikacji.

Zakładka posiłki

Zakładka posiłki; wyszukiwanie posiłku; skok do daty

Zakładka posiłki jest widoczna od razu po uruchomieniu aplikacji. Są na niej wyszczególnione wszystkie posiłki wprowadzone do aplikacji przez użytkownika. Posiłki pogrupowane są według dat, a daty uszeregowane zgodnie z odwróconą chronologią. W ramach daty, posiłki uszeregowane są chronologicznie.

Każdy posiłek może mieć przypisaną nazwę, która jest widoczna na liście. Obok nazwy widoczne są na liście trzy pierwsze składniki posiłku. Jeśli nazwa posiłku nie jest określona, więcej miejsca pozostaje na składniki. Pozwala to szybko identyfikować posiłek na liście w preferowany przez użytkownika sposób. Lista posiłków może być oczywiście przewijana.

U dołu ekranu z posiłkami znajdują się przyciski dodaj i szukaj. Naciśnięcie przycisku szukaj powoduje wyświetlenie u góry ekranu okna na tekst, według którego zostaną przefiltrowane posiłki. Podczas wyszukiwania, uwzględniane są wszystkie dane posiłku, tj. m.in. składniki i nazwa.

Możliwe jest także przeskoczenie, na liście posiłków, do konkretnej daty. W tym celu należy nacisnąć nagłówek z datą. Zostaną wyświetlone ostatnie daty, ale tylko tyle ile mieści się ich na ekranie. Starsze posiłki są zgrupowane pod wspólną etykietą starsze. Takie ułożenie pozwala bardzo szybko przejść do konkretnej daty w ostatnim okresie, co może być przydatne dla użytkownika w celach kontrolnych.

Zakładka produkty

Druga zakładka głównego ekranu aplikacji zawiera listę wszystkich produktów w bazie danych, pogrupowanych w kategorie. Po lewej stronie nazwy produktu znajdują się dwa kwadraty z wykresami. Pierwszy z nich oznacza liczbę WW na 100g danego produktu, w porównaniu do maksymalnej liczby WW na 100g produktu w danej kategorii. Drugi kwadrat pełni tę samą rolę, w odniesieniu do miernika WBT. Takie graficzne oznaczenie ułatwia rozeznanie się przez użytkownika w wartościach odżywczych produktów, w danej kategorii. Pozwala znaleźć alternatywne produkty, bardziej wskazane, według kryteriów stosowanej diety lub po prostu zdobyć większą wiedzę o wartościach odżywczych, co jest walorem edukacyjnym aplikacji.

Podobnie, jak ma to miejsce na zakładce posiłki, naciśnięcie przycisku szukaj, powoduje wyświetlenie okna tekstowego umożliwiającego wprowadzenie fragmentu nazwy szukanego produktu.

Ponadto przejście podczas wyszukiwania, pomiędzy zakładkami posiłki i produkty, powoduje oczywiście kontynuację wyszukiwania, według wprowadzonego tekstu.

Zakładka produkty; szukanie produktu; składniki posiłku

Strona posiłku

Naciśnięcie przycisku dodaj na zakładce posiłki spowoduje utworzenie nowego posiłku i otwarcie jego strony. Strona posiłku zawiera dwie zakładki: składniki i ogólne. Po przejściu na stronę posiłku, zawsze otwierana jest najpierw zakładka składniki. Zakładka ogólne nie musi być bowiem wcale przez użytkownika „odwiedzana”, w celu utworzenia kompletnego posiłku.

Ponadto stronę danego posiłku można oczywiście otworzyć naciskając posiłek na liście, która jest umieszczona na zakładce posiłki.

Typową czynnością, po utworzeniu posiłku, jest dodanie do niego składników. Aby dodać składnik do posiłku, należy nacisnąć przycisk dodaj na zakładce składniki strony posiłku, znajdujący się obok mierników WW i WBT. Spowoduje to wyświetlenie ekranu wyboru produktu, prezentującego je tak samo, jak zakładka produkty.

Na ekranie wyboru produktu można kliknąć nagłówek kategorii produktu, aby zobaczyć listę wszystkich kategorii, co umożliwia szybki przeskok do poszukiwanej kategorii. Jest to przydatne, kiedy nie znamy nazwy produktu, nie możemy więc skorzystać z wyszukiwania, jednak znamy lub potrafimy rozpoznać kategorię produktu.

Ponadto na ekranie wyboru produktu, podobnie jak na zakładce produkty, dostępny jest przycisk dodaj. Można więc „w locie” dodać produkt, podczas wybierania go jako składnik.

Kategorie produktów; składnik posiłku; ogólne dane posiłku

Po wybraniu produktu, wyświetlone zostanie okno wyskakujące Ilość składnika. Zgodnie z nazwą, w oknie tym użytkownik może podać ilość składnika (czyli produktu), dodawaną do posiłku. Może przy tym wybrać jednostkę (obecnie są to gramy i mililitry) oraz, w przypadku niektórych produktów, także predefiniowaną porcję. Na dole okna Ilość składnika widoczne są trzy przyciski, kolejno: ok, anuluj i usuń (ten ostatni usuwa składnik). Ponadto okno Ilość składnika można oczywiście otworzyć naciskając składnik na zakładce składniki strony posiłku. W ten sposób można poprawić lub usunąć wprowadzony wcześniej składnik.

Jako wspomniano powyżej, strona posiłku zawiera także zakładkę ogólne. Na zakładce tej użytkownik może zmienić datę oraz godzinę posiłku. Uważny czytelnik zauważy przycisk przełączany kłódka obok kontrolek daty i godziny. Kiedy kłódka jest zamknięta, podczas zapisu posiłku zostanie, jako czas posiłku, użyta bieżąca data i godzina. Natomiast kiedy kłódka jest otwarta, czas posiłku nie zostanie zmieniony podczas zapisu. Kłódka jest automatycznie zamykana dla posiłków otwartych w odległości czasowej, nie większej niż 3 minuty od daty posiłku, a więc dla wszystkich nowych oraz poprawianych, zaraz po utworzeniu, posiłków. Założono bowiem, że posiłki będą wprowadzane bezpośrednio przed ich spożyciem, więc data posiłku powinna być ustawiana automatycznie, ale należy zapewnić użytkownikowi możliwość jej zmiany.

Na zakładce ogólne można także wybrać nazwę posiłku z listy edytowalnej oraz wprowadzić do niego notatkę.

U góry strony posiłku zawsze widoczna jest jego wartość energetyczna. U dołu strony posiłku znajdują się dwa przyciski, kolejno: zapisz i anuluj. Zgodnie bowiem z zachowaniem aplikacji systemowych, dopiero po naciśnięciu przycisku zapisz, dana informacja zostanie zapisana. Naciśnięcie przycisku anuluj lub sprzętowego przycisku wstecz spowoduje opuszczenie danej informacji. Zmiany nie zostaną wtedy zachowane, tak jak ma to miejsce w aplikacjach systemowych. Ponadto przycisk zapisz jest aktywny dopiero po dokonaniu jakichkolwiek zmian przez użytkownika. Warto dodać, że przed zapisem wprowadzone dane są walidowane, więc użytkownik może zobaczyć ostrzeżenie o nieprawidłowości danych (np. brak informacji o wartościach odżywczych składnika lub zerowa ilość składnika).

Ponadto u dołu strony posiłku można pokazać menu (naciskając trzy kropki) i w ten sposób uzyskać dostęp do funkcji usuwania posiłku.

Strona produktu

Zupełnie tak, jak w przypadku posiłków, naciśnięcie przycisku dodaj na zakładce produkty, spowoduje utworzenie nowego produktu i otwarcie jego strony. Stronę produktu można oczywiście otworzyć także naciskając dany produkt na liście widocznej na zakładce produkty. Strona produktu zawiera trzy zakładki: ogólne, 100g oraz porcja.

Na zakładce ogólne użytkownik może zmienić nazwę produktu oraz wybrać z listy edytowalnej jego kategorię.

Zakładka ogólne; zakładka 100g; zakładka porcja

Na zakładce 100g użytkownik wprowadza wartość odżywczą w 100g produktu, np. przepisując te informacje z opakowania produktu. Użytkownik może zmienić wszystkie ilości poza ilością Węglowodany przyswajalne, która jest różnicą ilości Węglowodany i Błonnik, więc jest obliczana automatycznie. Również automatycznie jest obliczana Wartość energetyczna, gdy nie jest już wypełniona podczas otwarcia produktu, ale można ją zmienić. Na dole ekranu widoczne są duże wykresy mierników WW i WBT na 100g produktu, o takim samym znaczeniu, jak małe wykresy widoczne na zakładce produkty. Obok wykresów dostępna jest wartość tych mierników.

Warto zaznaczyć, że użytkownik nie musi wprowadzać wszystkich danych, a aplikacja będzie korzystać wtedy tylko z wprowadzonych danych, np. brak wprowadzenia Wartości energetycznej (kcal), nadal pozwoli uzyskać informację o WW oraz WBT i przeciwnie, wprowadzenie tylko Wartości energetycznej, pozwoli później uzyskać informacje jedynie o wartości energetycznej w posiłku.

Na kolejnej zakładce porcja można zmienić wartość odżywczą w definiowanej przez użytkownika porcji produktu. Użytkownik może podać słowny opis porcji (np. szklanka, łyżka) oraz jej rozmiar w wybranej przez siebie jednostce. Podawane tu informacje o wartościach odżywczych są takie same, jak na zakładce 100g, ale odnoszą się do porcji produktu. Jeśli rozmiar porcji podany jest w gramach, nie ma konieczności podawania jej wartości odżywczych, bowiem aplikacja może użyć danych z zakładki 100g. Wypełnienie zakładki porcja jest całkowicie opcjonalne.

U dołu strony produktu znajdują się dwa przyciski, kolejno: zapisz i anuluj. Ich zastosowanie jest analogiczne, jak na stronie posiłku. Również dostępna w menu funkcja usuwania jest taka sama, jak na stronie posiłku.

Poprawność prowadzonych przez aplikację obliczeń, zależy od poprawności wprowadzonych danych. Dlatego też przed zapisem produktu, jest on walidowany w zakresie:

  • podania wartości odżywczych na 100g;
  • spójności wprowadzonej wartości energetycznej z podaną ilością składników odżywczych (tolerancja błędu wynosi 5 kcal) w ramach 100g i porcji;
  • relacji błonnika do węglowodanów;
  • kompletności informacji o porcji, jeżeli zaczęto ją podawać;
  • proporcjonalności wartości odżywczej na 100g i na porcję (tolerancja błędu to 1g).

Jeśli produkt nie przejdzie którejś z tych walidacji, użytkownik uzyska komunikat wskazujący miejsce możliwego wystąpienia błędu, ale może ten błąd zignorować, są to bowiem jedynie wskazówki, które z powodu rygorystyczności walidacji, mogą być „fałszywymi alarmami”.

Serializacja binarna wewnątrz aplikacji Dietphone

Za wyborem ręcznej serializacji binarnej przemawia fakt, że projektowana aplikacja należy do tego rodzaju narzędzi, które uruchamia się na chwilę, ale wielokrotnie w ciągu dnia. W przypadku takich aplikacji bardzo ważne jest szybkie uruchamianie się aplikacji. Należy dodać, że w środowisku mobilnym nie mamy do dyspozycji takiej wydajności sprzętu, jak na komputerze biurkowym. Wybrany model serializacji pozwala oszczędzić czas.

Wadą tego rodzaju serializacji, z punktu widzenia programisty, jest konieczność pisania metod serializacyjnych dla każdej klasy. W przypadku projektowanej aplikacji nie był to jednak problem, ponieważ jej model nie zawiera wielu klas. W przeciwnym przypadku ręczna serializacja byłaby bardzo kłopotliwa i wykorzystalibyśmy, za pewną cenę wydajności, mechanizm refleksji .NET, likwidując powyższą kłopotliwość.

Jest natomiast prawdopodobne, że model będzie rozbudowywany. Tutaj wybór ręcznej serializacji również jest korzystny. Można bowiem dowolnie zdecydować co zrobić, kiedy plik danych pochodzi z wcześniejszej wersji aplikacji.

Automatyczne narzędzia serializacji, które potrafią radzić sobie w takich sytuacjach, czynią to za cenę przechowywania identyfikatorów każdej właściwości obiektu, przez co obniża się ich efektywność. Jeszcze gorszą efektywność, niż gotowe narzędzia do serializacji, oferowały bardziej klasyczne zewnętrzne narzędzia bazodanowe, dostępne na obraną platformę. Przez co nie zostały zastosowane. Należy przypomnieć, że obecnie nie jest dostępna na Windows Phone 7 wbudowana baza danych SQL.


Z wbudowanych w bibliotekę Silverlight mechanizmów serializacji, najszybsza okazała się serializacja przy użyciu mechanizmu DataContract do binarnej reprezentacji XML platformy .NET (nie jest to stricte serializacja binarna, jedynie binarne sformatowanie kodu XML). Brak jest natomiast w bibliotece Silverlight binarnego serializatora BinaryFormatter, znanego z pełnej wersji platformy .NET, więc serializację binarną trzeba zapewnić samemu lub korzystać z gotowych narzędzi zewnętrznych. Zastosowanie w aplikacji własnego mechanizmu serializacji binarnej, pozwoliło wczytać na telefonie komórkowym dane 843 produktów w czasie 320 milisekund. Wobec trwania tej operacji przez 4500 milisekund, podczas użycia wspomnianego mechanizmu DataContract. Oszczędność wyniosła zatem 93% czasu.


Mechanizm serializacji jest odizolowany od reszty aplikacji. Założono możliwość wykorzystania go w innych aplikacjach na Windows Phone 7, więc rdzeń kodu serializacji jest całkowicie niezależny od warstwy Model aplikacji. Konieczne było jedynie dodanie bardzo prostych klas zapewniających metody serializacji konkretnych encji warstwy Model. Te klasy są już zależne od warstwy Model. Oczywiście sama warstwa Model jest całkowicie niezależna od mechanizmu przechowywania danych, w tym od serializacji binarnej. Poniżej znajduje się opis wszystkich elementów odpowiedzialnych za serializację w opisywanej aplikacji.

BinaryFile

Jest to abstrakcyjna klasa generyczna, która implementuje interfejs BinarySerializer. Odpowiada plikowi o określonej nazwie i wersji, w którym przechowywane są obiekty danego typu. Umożliwia zapis i odczyt z pliku listy wszystkich obiektów. Jej podklasy konkretne muszą zapewnić zapis i odczyt pojedynczych obiektów ze strumienia binarnego, są to bowiem metody abstrakcyjne tej klasy.

BinarySerializer i BinarySerializerExtensions

Generyczny interfejs BinarySerializer odpowiada za serializację pojedynczego obiektu.

Natomiast klasa BinarySerializerExtensions wprowadza rozszerzenia do klas BinaryWriter oraz BinaryReader (klasy platformy .NET), o możliwość zapisywania i odczytywania obiektów z tych klas, przy użyciu interfejsu BinarySerializer oraz ułatwia zapis i odczyt niektórych innych typów.

Konstrukty BinarySerializer i BinarySerializerExtensions bazują na bardzo ciekawej pracy Kevina Marshalla, w której rolą obiektów, implementujących interfejs IBinarySerializable, jest serializacja samych siebie. Stosując SRP i programowanie generyczne zmieniono nieznacznie to założenie, wprowadzając zasadę wydzielania serializacji obiektów do zewnętrznych klas, implementujących interfejs BinarySerializer.

BinaryStorage i BinaryStorageCreator

Klasa BinaryStorage pełni rolę adaptera, będąc podklasą BinaryFile i implementując przy jej użyciu interfejs Storage, który należy do warstwy Model aplikacji.

Natomiast klasa BinaryStorageCreator implementuje, należący do warstwy Model aplikacji interfejs StorageCreator. Korzysta przy tym z klasy StorageBuilder również wchodzącej w skład warstwy Model aplikacji. Jest to ciekawy przypadek, który zostanie szerzej omówiony.

Zacznijmy od porównania naiwnej wersji kodu z poprawioną wersją kodu. Obie wersje zostały uproszczone (uwzględniono tylko kategorie i produkty oraz pominięto przypisanie BinaryStreamProvider do BinaryFile), aby czytelnik mógł skupić się na istocie problemu.

Pierwsza wersja:

Naiwna wersja klasy BinaryStorageCreator (kod C#)

Druga wersja:

Poprawiona wersja klasy BinaryStorageCreator korzystająca z klasy StorageBuilder (kod C#)

Zamierzonym efektem było utworzenie instancji odpowiedniej podklasy klasy abstrakcyjnej BinaryStorage<T> na podstawie typu generycznego T. Problem częściowo wziął się stąd, że w danym kontekście programista chciał, żeby typ BinaryStorage<T> był dla kompilatora rodzicem typu CategoryBinaryStorage, który dziedziczy z BinaryStorage<Category>, podczas gdy dla kompilatora typ generyczny T może w tym samym kontekście równać się klasie Product, która poza wspólnym typem bazowym nie ma nic wspólnego z Category, a więc mielibyśmy odmienny typ BinaryStorage<Product> zamiast BinaryStorage<Category>. Jak widać klasa o innym typie generycznym jest po prostu inną klasą i kompilator nie może pozwolić na bezpieczne rzutowanie typów w takiej sytuacji. To na programiście spoczywa obowiązek upewnienia się w czasie wykonania programu, że tworzony jest obiekt właściwego typu generycznego. Choć jak pokażemy, można również w tym przypadku skorzystać z kontroli typów języka.

Obrany efekt utworzenia instancji odpowiedniej podklasy BinaryStorage<T>, na podstawie typu generycznego T, można uzyskać w naiwny sposób sprawdzając typ generyczny i tworząc na tej podstawie odpowiedni typ konkretny (jak w pierwszej wersji). Takie rozwiązanie oznacza jednak rezygnację z silnej kontroli typów języka C# i nie jest bezpieczne, ani przejrzyste. Po refactoringu udało się wypracować bezpieczne rozwiązanie, widoczne w drugiej wersji, które można zredukować do samego użycia operatora as. Użyto jednak dodatkowo operatora typeof w warunku, aby nie tworzyć nadmiarowych obiektów dla operatora as, a całość zamknięto w klasę, która poprzez swą konstrukcję, uniemożliwia utworzenie obiektu niewłaściwego typu. W najgorszym przypadku, kiedy programista rozbudowując program zapomni dodać wywołanie metody ProposeStorageForEntity, zobaczy wyjątek „Nie zaimplementowano” i żaden obiekt nie zostanie utworzony.

Jeszcze bardziej eleganckie i krótsze rozwiązanie polegałoby na przeładowaniu metody CreateStorage<T>, za pomocą wymuszenia minimalnej klasy bazowej typu generycznego T, przy użyciu operatora where. Niestety jednak przeładowanie metod różniących się tylko operatorem where nie działa w języku C#, choć można sądzić, że byłoby przydatne w wielu sytuacjach.

BinaryStreamProvider

Obiekt implementujący interfejs BinaryStreamProvider jest używany przez obiekt BinaryFile, do uzyskania wejściowego i wyjściowego strumienia pliku. Wydzielenie tego interfejsu było motywowane zróżnicowanym na platformach Windows i Windows Phone sposobem uzyskiwania strumienia pliku. Poza tym umożliwiło proste przejście od domyślnych plików danych instalowanych wraz z aplikacją, do własnych danych użytkownika aplikacji, bez konieczności kopiowania plików danych.

CategoryBinaryStorage, MealBinaryStorage, MealNameBinaryStorage i ProductBinaryStorage

Są to klasy dziedziczące po abstrakcyjnej, generycznej klasie BinaryStorage i pośrednio po BinaryFile. Ich rolą jest obsługa zapisu i odczytu pojedynczej encji modelu, odpowiednio: kategorii, posiłku (razem ze składnikami posiłku), nazwy posiłku i produktu spożywczego. Dostarczają także nazwy pliku i wersji, dla obsługiwanego przez siebie typu obiektu.

Zawartość warstwy Model w implementacji aplikacji Dietphone

Aplikacja została skonstruowana zgodnie z opisanym w poprzednich wpisach wzorcem architektonicznym MVVM, ale ograniczymy się tutaj do opisu jedynie warstwy Model aplikacji. Ponadto w następnym wpisie zostaną opisane autonomiczne fragmenty kodu, które mogą mieć zastosowanie w innych aplikacjach na system Windows Phone 7.

Calculator

Klasa ta służy do obliczania wartości odżywczych na podstawie zawartości składników odżywczych. Motywacją do jej utworzenia było wydzielenie kodu, używanego w wielu miejscach modelu, do jednej klasy. Obliczanie wartości odżywczych, samo w sobie, jest oczywiście bardzo proste i wygląda następująco (jednostką wejściową są gramy):

Obliczanie wartości odżywczych

Category i MealName

Klasa Category to kategoria, do której obecnie mogą być przypisane tylko produkty. Zaś klasa MealName to nazwa posiłku (np. „śniadanie”). Obie klasy są encjami.

DefaultEntities

Jest to interfejs dostarczający domyślnych encji. Obecnie obsługiwane są encje: nazwa produktu i produkt. Domyślne encje używane są, gdy danej encji nie można znaleźć lub po prostu jej nie określono. Wynika to z zasady projektowej unikania, w takich sytuacjach, wartości null.

Entity i EntityWithId

Entity, to podstawowa klasa encji, która pamięta swojego właściciela we właściwości Owner. Dla bezpieczeństwa, jest to właściwość jednorazowego zapisu i chronionego odczytu.

Klasa EntityWithId, to zgodnie z nazwą, klasa Entity, wzbogacona o identyfikator (typu GUID). Większość encji z niej dziedziczy. Klasa ta umożliwiła uogólnienie wyszukiwania encji po ID.

Factories

Jest to najważniejsza część modelu. Ten właściciel wszystkich encji jest kontenerem, zawierającym fabryki poszczególnych typów encji i korzystającym z nich w celach: udostępnienia wszystkich encji, tworzenia nowych encji i zapisu wszystkich encji. Udostępnia, poprzez swoje właściwości, także implementacje interfejsów: Finder oraz DefaultEntities.

Celem utworzenia tego interfejsu była chęć zapewnienia jednego miejsca, z którego można uzyskać dostęp do całego modelu. Ponieważ każda encja zna obiekt implementujący ten interfejs (swojego właściciela), może ona poruszać się po całym modelu i jest samowystarczalna. To duża zaleta.

Implementacja nie jest klasą statyczną, ani nie wymusza wzorca singletonu, choć obecnie jest tworzona w aplikacji tylko raz. Jest przekazywana do warstwy ViewModel podczas tworzenia tejże, umożliwiając jej komunikację z warstwą Model. Taka strategia tworzenia ułatwia testowanie aplikacji, izolując komponenty wszędzie, gdzie jest to możliwe.

Factory i FactoryCreator

Factory jest to generyczna klasa fabryki encji. Umożliwia odczyt i zapis encji danego typu oraz utworzenie nowej encji. Do właściwego odczytu i zapisu korzysta z obiektu implementującego interfejs Storage. Ponadto przypisuje właściciela do każdej odczytanej lub utworzonej encji.

Natomiast klasa FactoryCreator tworzy obiekty klasy Factory. Korzysta z klasy StorageCreator, skąd uzyskuje obiekt Storage, który przekazuje do tworzonego obiektu Factory. Klasy FactoryCreator używa implementacja Factories.

Finder i FinderExtensions

Interfejs Finder pozwala przeszukiwać model według różnych kryteriów, w tym według ID. Zaś klasa FinderExtensions zapewnia generyczne rozszerzenie (cecha języka C#) każdej listy encji zawierających ID o metodę szukania encji po ID. Korzysta z niej implementacja Finder.

Meal

Jest to encja reprezentująca posiłek. Zawiera listę składników posiłku (czyli obiektów MealItem), które udostępnia na zewnątrz jako ReadOnlyCollection (typ .NET). Umożliwia ich dodawanie, usuwanie, a także jednorazową inicjalizację całej listy oraz kopiowanie jej z innego posiłku. Potrafi również obliczyć swoją wartość odżywczą i udostępnia walidację.

MealItem

MealItem jest encją reprezentującą składnik posiłku. Jest to końcowa klasa, której funkcjonalność budowana jest w następujących, kolejno po sobie dziedziczących klasach:

  1. MealItemBase – pozwala określić: produkt, ilość i jednostkę, a więc w pełni określić składnik posiłku. Referencja do produktu jest pamiętana po jego odnalezieniu.
  2. MealItemWithNutrientsPerUnit – oblicza ilość składników odżywczych i wartość odżywczą w określonym produkcie, przypadających na pojedynczą określoną jednostkę (lub porcję).
  3. MealItemWithNutrients – oblicza ilość składników odżywczych i wartość odżywczą, przypadających na cały składnik posiłku.
  4. MealItemWithValidation – udostępnia walidację.

Pomocnicza klasa UnitUsability sprawdza dostępność informacji o składnikach odżywczych na jednostkę produktu, dla klas: MealItemWithNutrientsPerUnit i MealItemWithValidation.

Product i UnitAbbreviations

Encja Product reprezentuje produkt spożywczy. Potrafi obliczyć niektóre swoje składniki odżywcze i wartość odżywczą oraz je wszystkie przechowuje. Udostępnia również walidację.

Natomiast klasa UnitAbbreviations konwertuje jednostki ilości produktu na tekst i odwrotnie.

Storage, StorageCreator i StorageBuilder

Storage, to generyczny interfejs odczytywania i zapisywania list encji, który jest implementowany poza modelem. Natomiast StorageCreator, to generyczny interfejs tworzenia obiektów Storage (generyczna fabryka abstrakcyjna). Również jest implementowany poza modelem. Jego implementacja jest jedynym obiektem przekazywanym do implementacji Factories podczas jej tworzenia.

Interesująca jest klasa StorageBuilder, stosowana przez obiekty StorageCreator i służąca do automatycznego wybierania właściwej, dla danego typu encji, implementacji Storage. Zostanie ona omówiona, przy okazji jej wykorzystania, w następnym wpisie.

Założenia funkcjonalne Dietphone, przykładowej aplikacji użytkowej dla Windows Phone 7

Aplikacja ma obliczać mierniki wartości odżywczej takie jak wymienniki węglowodanowe (WW) oraz wymienniki białkowo-tłuszczowe (WBT). Założono, że aplikacja oblicza również wartość energetyczną (kcal), pozwalającą wykorzystać aplikację przez osoby stosujące dietę o ustalonej wartości energetycznej.


Samo w sobie obliczanie wartości odżywczej posiłku nie jest skomplikowane, ale wymaga użycia danych o zawartości poszczególnych składników odżywczych (np. białko, tłuszcz), w produktach wchodzących w skład posiłku. Zwykle w posiłku jest przynajmniej kilka produktów, co wprowadza już pewną komplikację, trzeba bowiem odnaleźć dane każdego produktu. Założono więc, że aplikacja będzie posiadać gotową, możliwie pełną, edytowalną bazę produktów, z zapewnieniem ich szybkiego wyszukiwania tekstowego i przeglądania według kategorii, aby można było z niej sprawnie dodawać produkty do posiłku.

Ponadto przydatne podczas przeglądania produktów może być prezentowanie wizualnego znacznika wartości odżywczej produktu, w porównaniu do innych produktów w swojej kategorii. Pozwoli to użytkownikom odnaleźć alternatywne produkty o bardziej korzystnym rozkładzie wartości odżywczych.

Składniki odżywcze w produktach powinny móc być definiowane w odniesieniu do przynajmniej dwóch jednostek ilości produktu: gramów, jako jednostki podstawowej i drugiej jednostki opcjonalnej. Ponadto jednostki powinny móc być łatwo dodawalne do aplikacji przez programistę lub użytkownika, a domyślnie dostępną dodatkową jednostką powinny być mililitry.

Dla wygody użytkownika zasadne jest zapewnienie możliwości zapisania predefiniowanej porcji konkretnego produktu, takiej jak np. szklanka mleka. Co więcej, kiedy użytkownik zdefiniuje dane produktu w odniesieniu np. do szklanki i poda, że szklanka to np. 200ml, aplikacja powinna umożliwić korzystanie z tych danych dla dowolnej określonej w mililitrach ilości produktu. Natomiast ilość w gramach powinna móc być podawana w odniesieniu do 100g, bo w takim ujęciu informacje te są umieszczane na opakowaniach produktów spożywczych.

Po zakończeniu edycji produktu, jego dane powinny być walidowane pod względem spójności wprowadzonych składników odżywczych (np. w odniesieniu do wartości energetycznej, którą można wyliczyć z pozostałych danych), jak również ich kompletności. Podobnie, na koniec edycji posiłku, powinna nastąpić walidacja. W celu wychwycenia popełnionych przez użytkownika podstawowych błędów, podczas wprowadzania posiłku.

Jak wspomniano wcześniej, dodawanie produktów jako składników posiłków, powinno odbywać się jak najsprawniej. Należy to działanie połączyć z podawaniem ilości produktu oraz uwzględnić przy tym predefiniowane porcje produktu, a domyślną jednostką powinny być gramy. Użytkownik podczas dodawania składników posiłku powinien mieć dostępną informację o bieżącej sumie wartości odżywczych (WW, WBT i kcal) w posiłku, przy czym powinna to być informacja dobrze widoczna. Podczas edycji posiłku użytkownik powinien mieć też możliwość usuwania składników oraz zmiany ich ilości.


Ponadto należy zapewnić możliwość wglądu przynajmniej w sumaryczne informacje o każdym posiłku, a najlepiej w listy wszystkich składników każdego posiłku. Dlatego też założono, że każdy posiłek będzie dodawany do dziennika posiłków, z możliwością jego późniejszej edycji i oczywiście wglądu. W celu szybszego odnajdywania, założono uszeregowanie posiłków według chronologii (wskazana jest funkcja szybkiego przejścia do konkretnej daty) oraz możliwość ich filtrowania według nazwy użytego produktu. Przyjęto, że każdy posiłek będzie mógł mieć przypisaną wybieraną z dostosowywalnej listy, opcjonalną nazwę (np. „śniadanie”, „obiad”, „kolacja”) oraz będzie zapamiętana data i godzina ukończenia dodawania w aplikacji tego posiłku, jak również będzie można dodać do niego notatkę.


Zakłada się, że interfejs użytkownika aplikacji mobilnej powinien zostać zaprojektowany w zgodzie ze standardem aplikacji na system Windows Phone 7, a więc powinien być przeznaczony do użycia „w biegu”, bez konieczności wykonywania niebędących niezbędnymi lub zbyt precyzyjnych czynności oraz być zgodny z wytycznymi projektowymi tego systemu i mieć spójny z nim wygląd. Bardzo ważna jest również szybkość działania aplikacji, ze szczególnym uwzględnieniem szybkości uruchamiania, aby aplikacja ta mogła być uruchamiana wielokrotnie w ciągu dnia i użytkownik mógł ją obsłużyć jak najszybciej.

Autor założył również, że przydatne będzie zapewnienie możliwości dostępu do danych zgromadzonych w aplikacji mobilnej także w aplikacji webowej, która w przyszłości mogłaby również zostać przygotowana. Ma to już na tym etapie pewne znaczenie dla implementacji, w której należy uwzględnić gotowość modelu biznesowego na synchronizację danych z serwerem, przy czym założono, że aplikacja będzie zawsze pracować asynchronicznie na lokalnej kopii danych, oczywiście z powodu szybkości i niezawodności takiego rozwiązania.

Wzorzec architektoniczny Model View ViewModel (2/2)

Warstwa View

Ostatnia do omówienia we wzorcu architektonicznym MVVM jest warstwa View. Jest to warstwa odpowiedzialna za wygląd interfejsu użytkownika aplikacji. Jako pojedynczy element warstwy View (czyli jeden widok), w przypadku aplikacji dla Windows Phone 7 rozumie się stronę aplikacji, choć jest to granica umowna, bo można też pod tą nazwą rozumieć rozbudowaną kontrolkę niestandardową, okno wyskakujące, itp.

W technologiach Silverlight i WPF warstwa View składa się z dwóch części. Podstawową częścią jest plik XAML, zawierający deklaratywny opis interfejsu użytkownika. Opcjonalną częścią jest plik code-behind, w którym może zostać umieszczony kod programu, związany z tym interfejsem użytkownika.

Część warstwy View, zawarta w pliku XAML, jest zawsze niezależna od warstwy ViewModel. Wskazuje się w niej wprawdzie konkretne miejsca warstwy ViewModel, ale nie jest ona kompilowana, nie może więc być z naszego punktu widzenia zależna. Co więcej, można przygotować dla niej testowe dane statyczne, używane podczas graficznego projektowania widoku.

Sprawia to, że możliwe jest całkowite oddzielnie roli projektanta interfejsu użytkownika od roli programisty, bo projektant nie musi mieć dostępu do stworzonego przez programistę kodu źródłowego programu, żeby stworzyć widok interfejsu użytkownika, od razu wiążąc go z przygotowaną wcześniej przez programistę warstwą ViewModel. Jest to często podawana zaleta wzorca architektonicznego MVVM i formatu XAML, choć oczywiście jest prawdziwie przydatna jedynie w odpowiednio dużych i złożonych pod względem graficznym projektach. W małych lub prostych graficznie projektach, w których programista często nie korzysta nawet z graficznego trybu projektowania, ręcznie pisząc kod XAML, jest ona, zdaniem autora, pomijalna.

Aby uzyskać wiązania danych z kontrolką interfejsu użytkownika, deklaruje się je w pliku XAML, przy czym dane te pochodzą z warstwy ViewModel, nazywanej źródłem wiązania i są wiązane z tzw. celem wiązania. Pod względem implementacji, warstwa ViewModel, jedynie musi udostępnić te dane w taki sposób, aby warstwa View była informowana o ich zmianie, co czyni się implementując bardzo prosty interfejs INotifyPropertyChanged. Z kolei kontrolki interfejsu użytkownika muszą dziedziczyć po klasie DependencyObject i używać klasy DependencyProperty, aby ich właściwości mogły być wiązane.

Oprócz wiązania danych, w pliku XAML możliwe jest również „wiązanie” zachowań aplikacji (np. reakcji na naciśnięcie przycisku), poprzez wykorzystanie mechanizmu komend (ang. command). Jednak jedyną korzyścią z takiej strategii jest możliwość „wiązania”, przez projektanta interfejsu użytkownika (a nie tylko przez programistę), zachowań aplikacji z poszczególnymi elementami interakcji z interfejsem użytkownika. Poważną wadą mechanizmu komend jest ilość kodu, jaka musi zostać, na etapie programowania, dodana do projektu, aby udostępnić pojedynczą komendę, co powoduje niepotrzebne komplikowanie projektu. Użycie komend powoduje także niepotrzebne spowolnienie aplikacji pod systemem Windows Phone 7.

W związku z powyższym, autor rezygnuje na ten moment z użycia komend, gdy nie niosą one żadnych korzyści, a mają poważne wady. Jedynym sposobem wiązania interfejsu użytkownika z zachowaniami udostępnianymi przez warstwę ViewModel, pozostaje wtedy plik code-behind.

Plik ten, jak już wcześniej napisano, jest opcjonalny i w „ekstremalnej” wersji wzorca architektonicznego MVVM jest prawie lub całkowicie pusty. Jednak umieszczenie w tym pliku bezargumentowych, pojedynczych wywołań, odpowiednio przygotowanych metod klas warstwy ViewModel, w metodach obsługi zdarzeń interfejsu użytkownika, nie wprowadza żadnej dodatkowej komplikacji. Jest dokładnie tym samym, pod względem np. możliwości testowania kodu, co „wiązanie” komend, ale pozbawionym wymienionych wad komend.

Innym przykładem kodu, który według wielu osób można umieszczać w pliku code-behind, jest kod operujący tylko i wyłącznie na interfejsie użytkownika, bez bezpośredniego związku z logiką aplikacji. Do tej kategorii należą np.: pokazywanie okien dialogowych, uruchamianie dekoracyjnych animacji lub ustawianie ogniska na kontrolce.

Można wyobrazić sobie nawet rezygnację z mechanizmu automatycznych wiązań danych i w konsekwencji ręczne przepisywanie, w pliku code-behind, danych z obiektu ViewModel do kontrolek interfejsu użytkownika. W środowisku Windows Phone 7 niosłoby to za sobą pewną korzyść wydajnościową, ale powodowałoby rozbudowanie i utrudnienie w zarządzaniu plikiem code-behind. Wprawdzie pod względem testowania kodu nie różniłoby się niczym od stosowania wiązań danych w pliku XAML, ale tylko przy założeniu, że dane te byłyby statyczne. Obsługiwanie zmiany danych wprowadza bowiem trudną do zaakceptowania, w nietestowanej warstwie View, komplikację kodu.

Podsumowanie

Wzorzec architektoniczny MVVM niesie za sobą możliwość podziału aplikacji na luźno ze sobą połączone warstwy. Powoduje to, że aplikacje stają się bardziej rozszerzalne i łatwiejsze w zarządzaniu. Umożliwia także pełniejsze ich testowanie. Sprzyja ich modularyzacji, mającej na celu lepszy podział pracy nad aplikacją.

Elastyczność tego wzorca projektowego zachęca do stosowania go w wyważony sposób, w zgodzie z możliwościami środowiska, takiego jak np. telefon komórkowy. Chociaż dostępnych jest wiele, bardzo się od siebie różniących, gotowych bibliotek ułatwiających implementację tego wzorca, dostarczających do tego celu gotowego szkieletu, autor na ten moment nie decyduje się na skorzystanie z żadnej z tych bibliotek, ponieważ w ten sposób można wybrać z tego wzorca najlepsze elementy, nie obciążając budowanych aplikacji dodatkową biblioteką.

Za zastosowaniem tego wzorca architektonicznego w aplikacjach, przemawia głównie możliwość wykonywania testów jednostkowych na praktycznie całym kodzie aplikacji. Albowiem można w ten sposób podnieść niezawodność aplikacji. Również istotna jest duża rozszerzalność stosującego ten wzorzec kodu, gdyż umożliwia to jego łatwe rozbudowywanie. Ponadto trzeba podkreślić, że wzorzec ten może sprzyjać stosowaniu dobrych zasad projektowych, choć niesie w tym zakresie też zagrożenia.

Ponadto można spotkać się z opiniami, że zrozumienie podstawowych zasad wzorca MVVM, nawet gdy nie zamierzamy stosować go w praktyce, pozwala lepiej zrozumieć filozofię bibliotek WPF i Silverlight. Zrozumienie biblioteki Silverlight jest niezbędne do tworzenia aplikacji użytkowych, przeznaczonych dla systemu Windows Phone 7.

Wzorzec architektoniczny Model View ViewModel (1/2)

Zastosowanie na platformie Windows Phone 7 technologii Silverlight, pociąga za sobą popularność na niej wzorca architektonicznego MVVM. Mianowicie czytelnik dowie się, że wzorzec architektoniczny MVVM jest zdecydowanie wygodniejszy w stosowaniu, kiedy możliwe jest deklaratywne wiązanie danych (ang. binding) z kontrolkami interfejsu użytkownika (co nie znaczy, że nie można go stosować bez tego mechanizmu). Mechanizm takiego wiązania jest częścią bibliotek Silverlight oraz WPF, więc nic dziwnego, że z myślą o nich, ten wzorzec architektoniczny został opracowany i jest razem z nimi stosowany.

Można zaryzykować twierdzenie, że drugą przyczyną popularności wzorca architektonicznego MVVM na platformie Windows Phone 7 jest podział interfejsu użytkownika aplikacji na tej platformie na odrębne jednostki zwane stronami, co ułatwia zastosowanie tego wzorca, upraszczając identyfikację jego elementów.

Kod aplikacji o architekturze zgodnej ze wzorcem MVVM podzielony jest zgodnie z nazwą wzorca na trzy warstwy, które zostaną omówione poniżej oraz w następnym wpisie, uszeregowane według ich postępującej zależności.

Nakładanie się warstw wzorca architektonicznego MVVM

Warstwa Model

Zgodnie z nazwą, znajdują się tutaj klasy reprezentujące model biznesowy aplikacji, podobnie jak ma to miejsce, w tak samo nazwanej warstwie wzorca architektonicznego MVC (Model View Controller). Stosując zasadę pojedynczej odpowiedzialności (wzorzec projektowy SRP) i elementarne zasady projektowania obiektowego, dochodzimy do potwierdzenia, że w warstwie Model powinien znaleźć się wyłącznie kod modelu danych, na których działa aplikacja oraz kod zachowań tego modelu (logiki biznesowej).

Najbardziej oczywistą częścią klas należących do warstwy Model omawianego wzorca będą właściwości, które odpowiadają konkretnym danym, na których operuje aplikacja. Pojawia się tutaj ryzyko traktowania tych klas, jako prostych rekordów, służących do przenoszenia danych. Jak często w przypadku tego wzorca architektonicznego bywa, jest to kwestia nie do końca ustalona, ale można zasugerować czytelnikowi, traktowanie klas modelu, jako pełnowartościowych klas, a więc udostępniających, tam gdzie to możliwe, dane w postaci znaczącej dla reszty aplikacji, a nie jako dane surowe. Zgodnie, bowiem z dobrymi zasadami projektowania, gdybyśmy potraktowali klasy modelu, jako rekordy danych, nie moglibyśmy uzupełniać ich zachowaniami logiki biznesowej, bo doprowadziłoby to do mieszania obiektów z rekordami. Jeśli to konieczne, można w celu zachowania obiektowości modelu, umieścić dane w rekordach, które będą dołączone do obiektów modelu. Trzeba mieć jednak świadomość, że wprowadzi się tym kolejną warstwę do architektury aplikacji, nieprzewidzianą w powszechnie rozumianym wzorcu architektonicznym MVVM. Wydaje się, że jest to obszar wzorca do pilnego doprecyzowania, jeśli chcielibyśmy aby wzorzec był ścisły.

Innym, często spotykanym przykładem kodu, który umieszcza się w klasach warstwy Model, jest walidacja danych, bowiem można ją zaliczyć do logiki biznesowej. Autor umieszcza w tej warstwie również np. kod dokonujący obliczeń, co nie budzi wątpliwości, pod względem zgodności ze wzorcem, jeśli są to obliczenia należące do logiki biznesowej aplikacji. W klasach należących do warstwy Model można umieszczać również odnajdywanie referencji do powiązanych elementów modelu, zapewniając tym samym spójność odnajdywania modelu na poziomie samego modelu, przy czym właściwa praca odnajdywania może znaleźć się w klasie pomocniczej.

Podsumowując, można pokusić się o założenie, że w warstwie Model powinien znaleźć się taki kod logiki biznesowej aplikacji, który będzie miał jak najbardziej bezpośrednie zastosowanie w innych warstwach aplikacji, jako sedno jej funkcjonalności, ale nie, jako kod pomocniczy, np. związany z konkretnym środowiskiem uruchomieniowym aplikacji. W związku z tym kod modelu może być użyty bez żadnych zmian np. w wersjach biurkowej i webowej tej samej aplikacji. Wynika też z tego, że warstwa Model jest całkowicie niezależna od dwóch pozostałych warstw omawianego wzorca architektonicznego.

Warstwa ViewModel

Odpowiedzialnością klas znajdujących się w warstwie ViewModel jest dostarczenie z i do interfejsu użytkownika danych i zachowań, udostępnianych przez warstwę Model, w postaci, która przed zastosowaniem w interfejsie użytkownika, nie wymaga już żadnej modyfikacji. Oczywista jest związana z tym zależność pośredniczącej ze swej natury warstwy ViewModel od warstwy Model. Jednak zależność warstwy ViewModel od interfejsu użytkownika nie jest już tak oczywista. Spróbujmy poznać tę zależność.

Z pewnością warstwa ViewModel nie powinna bezpośrednio korzystać z interfejsu użytkownika, aby możliwa była jej kompilacja i testowanie bez posiadania tegoż (sposób odseparowania warstwy ViewModel od interfejsu użytkownika zostanie opisany przy okazji omawiania warstwy View). Jest bowiem, jedną z największych zalet stosowania wzorca architektonicznego MVVM, możliwość stosowania testów jednostkowych, na praktycznie całej programowej części aplikacji (warstwach Model i ViewModel). Gdyby warstwa ViewModel była bezpośrednio zależna od interfejsu użytkownika, stosowanie wobec niej testów jednostkowych byłoby z samej ich natury mocno utrudnione. Na takie trudności niestety natrafiamy nie stosując wzorca MVVM, bądź też innych metod izolacji interfejsu użytkownika od reszty aplikacji.

Z drugiej strony warstwa ViewModel jest funkcjonalnie całkowicie podporządkowana interfejsowi użytkownika. W związku z tym, przyjmuje się ogólną zasadę, że jednemu widokowi w interfejsie użytkownika (takiemu, jak np. okno z listą danych), powinna odpowiadać jedna klasa należąca do warstwy ViewModel.

Jednak najbardziej podstawowym przypadkiem klasy należącej do warstwy ViewModel, jest klasa opakowująca poszczególne właściwości odpowiadającej jej klasy, należącej do warstwy Model. Często właściwości te nie są przekazywane do interfejsu użytkownika w identycznej, jak obecna w warstwie Model postaci, co związane jest z koniecznością np. konwersji ich na inny typ danych lub też innej formy doprowadzenia ich do postaci, nadającej się do wyświetlenia w interfejsie użytkownika, np. poprzez zmianę ustrukturyzowania danych.

Dla porządku warto przypomnieć, że chodzi tu jedynie o przygotowanie danych do wyświetlenia, a nie np. o obliczenia na danych. Bowiem te, według przyjętego w tym wpisie rozumienia wzorca architektonicznego MVVM, winny odbyć się jeszcze w warstwie Model.

Innym mechanizmem, służącym do konwersji danych w celu ich wyświetlenia, są konwertery, wspomniane w poprzednim wpisie. Z powodów tam przedstawionych nie są one chętnie wykorzystywane przez autora, a ich zadanie jest oczywiście zlecane warstwie ViewModel. Co więcej wydaje się, że stosowanie konwerterów razem z warstwą ViewModel jest całkowicie zbędne, skoro duplikują one odpowiedzialność warstwy ViewModel. Ich użyciu może sprzyjać przesunięcie odpowiedzialności z warstwy Model na warstwę ViewModel lub bezpośrednie udostępnienie klas należących do warstwy Model w warstwie ViewModel, lecz oba nie są chyba zjawiskami właściwymi, z punktu widzenia zgodności ze wzorcem.

Z powyższego opisu dobrych klas należących do warstwy ViewModel, łatwo wyodrębnić dwa typy tych klas:

  • klasy odpowiadające podstawowej jednostce interfejsu użytkownika, np. stronie (ViewViewModel);
  • klasy odpowiadające typom warstwy Model (ModelViewModel).

Trzeba zaznaczyć, że nie jest to podział powszechnie przyjęty we wzorcu MVVM (brak jest nawet nazw tych klas, stąd w nawiasach proponuje się nazwy robocze), choć jego użycie przez autora nie jest odosobnione.

Jak łatwo się domyślić, klasy typu ViewViewModel udostępniają obiekty klas typu ModelViewModel, przy czym w zależności od rodzaju obsługiwanego interfejsu użytkownika, mogą udostępniać pojedynczy obiekt klasy typu ModelViewModel lub całą ich listę. Bowiem pełnią one często rolę pomocniczą do klas typu ModelViewModel, udostępniając operacje takie jak dodawanie i usuwanie elementów, aby klasy typu ModelViewModel mogły ograniczać się do „tłumaczenia” klas modelu na interfejs użytkownika.

Jedyną wadą stosowania tego podziału jest konieczność posiadania dwóch klas warstwy ViewModel, gdzie czasem wystarczyłaby jedna, co prowadzi niekiedy do powstania dużej liczby klas. Wydaje się jednak, że jest to niska cena, wobec uzyskania zgodności z zasadą pojedynczej odpowiedzialności.

Jak zapewne czytelnik pamięta, warstwa Model jest całkowicie przenośna. Ciekawym wobec tego, nieomówionym jeszcze tematem, jest przenośność warstwy ViewModel. Spróbujmy odpowiedzieć sobie na pytanie: czy warstwa ViewModel może być przenoszona między platformami?

Można wyobrazić sobie, że na każdą platformę, na którą dostarczana będzie aplikacja, przeznaczona będzie osobna implementacja warstwy ViewModel. Taka zasada jest logiczna, gdy różnice w interfejsie użytkownika pomiędzy platformami są znaczące (np. strona internetowa w porównaniu do aplikacji na telefon komórkowy). Z drugiej strony, gdy różnice są relatywnie niewielkie (np. aplikacja na system Windows w porównaniu do aplikacji na system Mac OS), można wyobrazić sobie zastosowanie wspólnej implementacji warstwy ViewModel na obu platformach, co znakomicie przyczyniłoby się do zwiększenia łatwości budowy aplikacji, przeznaczonych na wiele platform.

Szczególne aspekty budowania aplikacji pod Windows Phone 7

Zdecydowana większość dodatkowych czynności, jakie należy podjąć budując aplikacje pod system Windows Phone 7 (podobnie jak pod inne systemy mobilne) związana jest z wydajnością tych aplikacji. Spowodowane jest to oczywiście ograniczoną mocą procesora telefonu komórkowego. Ważne jest również jak najmniejsze zużywanie baterii, na co ma wpływ m.in. stopień obciążenia procesora. We wpisie tym czytelnik pozna podstawowe zasady, jakimi należy kierować się, aby wydajność tworzonych aplikacji była jak najlepsza, a zużycie baterii było jak najmniejsze.

Omówione zostaną także szczególne aspekty, które są związane z interfejsem użytkownika.

Poprawa szybkości ładowania kodu i zasobów aplikacji

W przypadku średniej wielkości i dużych aplikacji, poprawienie ich szybkości ładowania do pamięci może być uzyskane poprzez podział aplikacji na kilka plików DLL. Możliwe są tu dwie strategie. Pierwszą z nich jest podział dużych aplikacji na pliki DLL odpowiadające poszczególnym częściom aplikacji i ładowane ich w miarę potrzeby. Drugą, adekwatną w przypadku średniej wielkości aplikacji możliwością, jest umieszczenie rzadko używanych elementów (np. stron opcje i o programie) w osobnym pliku DLL, a całej reszty aplikacji w innym pliku DLL. Bardzo małe aplikacje oczywiście znikomo zyskają na takim podziale.

Trzeba zaznaczyć, że powyższa porada dotyczy ładowania kodu samej aplikacji, a nie danych aplikacji. Wszelkie dystrybuowane z aplikacją pliki danych i np. zasoby multimedialne powinny być oznaczone w pliku projektu jako Content, a nie jako Resource. Tak oznaczone pliki nie będą weryfikowane pod kątem podpisu cyfrowego generowanego przez usługę Marketplace (nie będą nawet umieszczone w pliku XAP aplikacji [sic!]), co spowoduje szybsze ładowanie aplikacji, szczególnie za jej pierwszym uruchomieniem.

Gdy aplikacja ze względu na ładowanie zasobów potrzebuje więcej niż sekundę, aby pokazać pierwszą stronę, firma Microsoft zaleca pokazanie ekranu splash screen (domyślnie zresztą zawartego w każdym nowym projekcie), choć z drugiej strony zalecane jest też utrzymanie ciągłości przejścia między systemem i aplikacjami w wymiarze interfejsu użytkownika. W praktyce więc, korzystne jest załadowanie takiej liczby zasobów, żeby możliwe było pokazanie początkowego stanu pierwszej strony aplikacji zawierającego np. animację ładowania pozostałych zasobów. Tym samym ekrany splash screen okazują się rzeczywiście przydatne tylko, kiedy takie częściowe ładowanie nie jest z różnych względów możliwe.

Skrupulatne podejście do wydajności podczas programowania

Modny obecnie paradygmat odwrócenia sterowania jest przykładem techniki, która choć jest bardzo pożyteczna, podczas tworzenia dużych aplikacji (np. webowych), to intensywnie stosowana w aplikacjach na telefon komórkowy może doprowadzić do zbyt małej ich wydajności.

Innym przykładem ułatwienia w programowaniu, które na telefonie komórkowym w mniejszym lub większym stopniu nie jest korzystne, z powodu wydajności, jest korzystanie z mechanizmów wiązania danych (ang. binding) z kontrolkami interfejsu użytkownika w technologii Silverlight, co jest oczywiście wolniejsze od ręcznego przepisywania danych do interfejsu użytkownika, w kodzie aplikacji.

Dodatkowym spowolnieniem podczas wiązania danych z kontrolkami interfejsu użytkownika jest korzystanie ze zautomatyzowanych konwerterów (np. popularny konwerter Silverlight służy do zmiany wartości logicznej na typ wyliczeniowy, używany przez kontrolki do określenia ich widoczności). Konwertery takie są z powodzeniem stosowane, w celu uproszczenia kodu, w aplikacjach Silverlight lub WPF przeznaczonych na komputery biurkowe, ale na telefonach komórkowych, powodowane przez nie spowolnienie, jest już odczuwalne przez użytkownika, jeśli np. są stosowane w listach. Rozwiązaniem jest oczywiście ręczna konwersja wartości w kodzie, przed dowiązaniem lub przepisaniem ich do kontrolek.

Kolejnym, związanym z technologią Silverlight, wrażliwym punktem wydajności jest korzystanie z rozbudowanych szablonów kontrolek. W najgorszym przypadku elementy tych szablonów mogą posiadać zapętlające się współzależności renderowania (np. dotyczące rozmiaru), które będą dodatkowo spowalniać renderowanie strony. Najlepiej jest stosować jak najprostsze szablony kontrolek, o stałych rozmiarach, określając ten rozmiar na sztywno. Gdy zaś potrzeba zastosować bardziej skomplikowane szablony, można je zamknąć jako osobne kontrolki, w najlepszym przypadku nie korzystające z wiązania danych. Stosowanie kontrolek zamiast wiązania danych komplikuje implementację, ale powoduje wzrost wydajności.

Wymienione w tym punkcie optymalizacje sprowadzają się do rezygnacji z najbardziej kosztownych wydajnościowo ułatwień w budowaniu aplikacji i zarządzaniu kodem, na rzecz własnych rozwiązań. Jest to często konieczna czynność, aby uzyskać zadowalającą szybkość aplikacji na telefonie komórkowym, choć oczywiście nie znaczy to, że jest to czynność wystarczająca.

Opóźnione ładowanie danych

Zalecane przez firmę Microsoft jest nie ładowanie żadnych danych, podczas konstruowania stron Silverlight z kodu XAML, ponieważ konstruowanie to samo w sobie jest kosztowne czasowo. Poprawnym w związku z tym sposobem postępowania ma być odczekanie do wystąpienia odpowiedniego zdarzenia, oznajmiającego o skonstruowaniu wszystkich elementów wizualnych strony (LayoutUpdated) i dopiero potem dokonanie czasochłonnego ładowania danych. Spowoduje to, że użytkownik relatywnie szybko zobaczy stronę, na którą zostaną następnie załadowane dane, zamiast długo oczekiwać na pokazanie się strony.

Być może czytelnik zgodzi się, że możliwe wydaje się rozwinięcie powyższej idei i w przypadku statystycznych stron o niezmiennym podczas konstruowania z kodu XAML układzie wizualnym, udostępnienie jakiejś formy ich prekompilacji (w zasadzie hibernacji), aby skrócić czas konstruowania. Przypominałoby to per analogiam możliwość oznaczania klas, z których nie można dziedziczyć, jako sealed w języku C#, co powoduje, że nie są już dla tych klas szukane nadpisania metod wirtualnych i w związku z tym poprawiona jest szybkość wywołań tych metod. Prekompilowane strony mogłyby być modyfikowane wyłącznie po całkowitym załadowaniu, ale taki schemat postępowania i tak jest obecnie zalecany, więc z punktu widzenia autora aplikacji korzystającego z gotowych, odpowiednio przygotowanych kontrolek XAML, koszt takiego rozwiązania byłby niewidoczny, a strony byłyby konstruowane szybciej.

Oczywiście wyrażoną na początku tego punktu zasadę opóźnionego ładowania danych na stronę można uogólnić i zastosować na wszystkie składniki strony z osobna, a nie tylko na stronę jako całość. W ten sposób można ładować poszczególne dane dopiero, kiedy użytkownik przejdzie do takiego miejsca na stronie, w którym dane te są potrzebne. Przykładowym miejscem zastosowania są tu strony przewijane w pionie lub poziomie, jak również strony pokazujące okna wyskakujące, a może nawet listy rozwijane.

Minimalizacja użycia wątku interfejsu użytkownika

Wątek interfejsu użytkownika nie powinien być nigdy zablokowany długotrwałą pracą, bo jego najważniejszym zadaniem jest odpowiadanie na interakcje użytkownika z aplikacją. Kiedy np. użytkownik naciśnie przycisk powodujący jakąś długotrwałą czynność, interfejs użytkownika nie powinien zostać zablokowany. Jednym ze sposobów uzyskania takiego efektu, gdy do wykonania jest długotrwała praca po stronie aplikacji, jest zlecenie pracy do wątku działającego w tle. Nie jest to niczym nietypowym również w przypadku programowania na biurkowej wersji Windows, ale ponieważ procesor telefonu komórkowego ma słabsze możliwości, do blokowania wątku interfejsu użytkownika dochodzi na nim znacznie łatwiej, stąd konieczność zlecania pracy do wątku tła występuje częściej.

Taki sposób postępowania będzie przypuszczalnie przynosić korzyści także w Windows Phone 7.1, w którym obsługa dotyku zostanie przeniesiona, z wątku interfejsu użytkownika, do wątku Kompozytora (co ma poprawić czas reakcji kontrolek na dotyk), bowiem wątek interfejsu użytkownika, jako główny wątek aplikacji, przypuszczalnie nadal będzie jedynym wątkiem, zdolnym ostatecznie odpowiedzieć na interakcję użytkownika z aplikacją, np. inicjując jakąś logikę biznesową. Poza tym do wątku interfejsu użytkownika należy obecnie też m.in. rysowanie elementów wizualnych po raz pierwszy, a można sobie wyobrazić, że może nastąpić to podczas wykonywania intensywnej pracy przez aplikację, w wyniku przewinięcia w tym czasie strony przez użytkownika.

Postrzegany czas odpowiedzi

Ważną cechą interfejsu użytkownika w środowisku o ograniczonej wydajności, jest zdolność do natychmiastowej odpowiedzi na bodźce użytkownika. W systemie Windows Phone 7 mocno akcentowane są animacje, więc jest to najbardziej oczywista forma odpowiedzi. Można np. wyświetlić animację ładowania strony lub okna wyskakującego, jeśli czynność ta będzie czasochłonna, aby zmniejszyć postrzegany czas odpowiedzi, czyli czas, po którym użytkownik może najpóźniej otrzymać odpowiedź, przy czym może to być także odpowiedź informująca jedynie, że należy czekać, a czas ten nie powinien przekraczać jednej sekundy.

Innym sposobem odpowiedzi, jest uruchomienie wibracji na 100 ms, czyli na najkrótszy możliwy czas, jako odpowiedzi na naciśnięcie przycisku, przed pokazaniem okna wyskakującego. Oczywiście ten sposób odpowiedzi nie powinien być nadużywany, a czas wibracji powinien być krótki, w przeciwnym wypadku użytkownik może potraktować go jako sygnał błędu.

Odpowiednia wielkość aktywnych elementów interfejsu użytkownika

Elementy interfejsu użytkownika, które można nacisnąć palcem, powinny być na tyle duże, aby nie zdarzało się często przypadkowe naciśnięcie niewłaściwych elementów. Jest to oczywiście implikacja użycia pojemnościowego ekranu dotykowego.

Przyjmuje się, że elementy te nie powinny być mniejsze, niż kwadrat o bokach 9 mm (ok. 34 pikseli) lub wyjątkowo 7 mm (ok. 26 pikseli) i nie powinny znajdować się bliżej siebie, niż w odległości 2 mm (ok. 8 pikseli).

Wybór właściwej klawiatury ekranowej

Jak wspomniano już w tym wpisie, znacząca większość obecnych na rynku telefonów z systemem Windows Phone 7 nie posiada klawiatury sprzętowej, więc jedynym na nich sposobem wprowadzania znaków jest klawiatura ekranowa (SIP).

System Windows Phone 7 pozwala ustalić (poprzez właściwość InputScope), jakiego rodzaju klawiatura będzie wyświetlana użytkownikowi, podczas wprowadzania znaków do konkretnego pola tekstowego. Dostępnych jest 10 podstawowych rodzajów SIP i różnią się one od siebie nie tylko dostępnością i układem klawiszy, ale także funkcjami takimi jak słownik i automatyczna wielka litera na początku. Wybór właściwej SIP z pewnością przyspiesza wprowadzanie danych, więc warto ustalić SIP dla każdego pola tekstowego.

Architektura systemu operacyjnego Windows Phone 7 z perspektywy aplikacji

Architekturę systemu operacyjnego Windows Phone 7 można z punktu widzenia budowania aplikacji pod ten system podzielić na kilka podstawowych obszarów, które zostaną omówione w tym wpisie.

Architektura systemu operacyjnego z perspektywy aplikacji

Jądro

Jądro Windows Phone 7 bazuje na jądrze Windows CE 6.0, w stosunku do którego zostało wzbogacone m.in. o stronicowanie pamięci oraz o warstwy bezpieczeństwa, sieci i przechowywania danych. W jego skład wchodzą także sterowniki do wymienionych w poprzednim wpisie czujników telefonu oraz do multimediów i Wi-Fi.

Model aplikacji

Model aplikacji Windows Phone 7 cechuje:

  • znany z technologii Silverlight i XNA format pliku XAP, zawierający całą aplikację (są to pliki DLL oraz manifest i zasoby, w tym ikona aplikacji, skompresowane w formacie ZIP);
  • licencjonowanie z wykorzystaniem usługi Marketplace (użytkownik końcowy nie może zainstalować aplikacji inaczej, niż poprzez usługę Marketplace, aczkolwiek w wersji 7.1 za jej pośrednictwem można będzie dystrybuować aplikacje także do zamkniętego kręgu odbiorców), ponadto kod każdej aplikacji (oczywiście skompilowany do pośredniego języka Common Intermediate Language, CIL platformy .NET) jest przed jej umieszczeniem w usłudze Marketplace sprawdzany przez firmę Microsoft, a następnie podpisywany cyfrowo;
  • aktualizacje aplikacji zawsze zatwierdzane przez użytkownika w usłudze Marketplace, chociaż poza tym będące całkowicie zautomatyzowane;
  • izolacja każdej aplikacji w piaskownicy, przy czym autorzy systemu używają terminu „komora”, ang. chamber – są, bowiem cztery typy komór, a aplikacje (pochodzące spoza firmy Microsoft) znajdują się w komorach o najmniejszych przywilejach (LPC, ang. Least Privileged Chamber) co oznacza, że każda aplikacja nie ma dostępu do plików każdej innej aplikacji (oczywiście poza swoimi), zarówno jeśli chodzi o pliki uruchomieniowe, jak i pliki danych (ani używanej przez nią pamięci operacyjnej), nie ma także dostępu do plików systemu operacyjnego, co ma zapewnić bezpieczeństwo;
  • w wersji 7.1 zapowiadana jest możliwość współdzielenia danych między aplikacjami za potwierdzeniem użytkownika, ale w praktyce na razie ma to być jedynie odczyt danych z dwóch aplikacji systemowych: kontaktów i kalendarza, więc jedynym miejscem integracji danych pozostaje chmura.

Model interfejsu użytkownika

Model interfejsu użytkownika Windows Phone 7 dziedziczy po sieci WWW sposób nawigacji w historii pomiędzy stronami aplikacji (za co odpowiedzialna jest warstwa powłoki systemu), z zastrzeżeniem, że nie jest możliwe przechodzenie wprzód w historii, można się tylko cofać. Związany z tym sposób przechodzenia między aplikacjami ma zapewnić wrażenie spójności aplikacji i systemu operacyjnego. Ponadto podzielenie funkcjonalności aplikacji na niewielkie obszary (strony) umożliwia, bardziej adekwatne do możliwości, dysponowanie ograniczonymi zasobami telefonu komórkowego.

Pomocny w takim sposobie nawigacji jest wątek Kompozytora (ang. Composer). Jest to wątek o wysokim priorytecie, odpowiedzialny za rendering (choć w wersji 7.1 odpowiadać będzie także za obsługę dotyku, co ma przyspieszyć czas reakcji telefonu na dotyk). Ten wspólny wątek renderingu umożliwia tworzenie płynnych animacji przejść między aplikacjami i ponieważ jest niezależny od głównego wątku aplikacji (zwanego wątkiem interfejsu użytkownika), zapewnia płynność raz uruchomionych animacji w aplikacjach.

Wątek Kompozytora (podobnie jak cały system) do renderingu wykorzystuje procesor karty graficznej, bo jak wspomniano w poprzednim wpisie, do minimalnych wymagań sprzętowych systemu należy GPU zgodny z DirectX 9. Zdaniem autora, niesie to oczywiście tę samą korzyść, jak na komputerach PC, na których wykorzystanie GPU pozwala odciążyć CPU. Jest to szczególnie istotne, jeśli weźmiemy pod uwagę, że na telefonach komórkowych czas CPU jest jeszcze cenniejszy.

Integracja z chmurą

System Windows Phone 7 w odróżnieniu od Windows Mobile 6 nie umożliwia obecnie synchronizacji danych z komputerem PC poprzez USB ani bluetooth, w związku z tym możliwa jest jedynie synchronizacja poprzez usługi w chmurze. Jest to przykład, jak ważna jest dla tego systemu chmura. Świadczy o tym również udostępnienie aplikacjom integracji z usługami takimi jak:

  • notyfikacje typu „pchnij” (ang. push notifications), które pozwalają zmniejszyć zużycie baterii, w sytuacji, gdy aplikacja musiałaby periodycznie sprawdzać, czy na serwerze dostępne są nowe dane (usługa wykorzystuje do tego celu serwer pośredniczący firmy Microsoft, do którego powinien zostać, jako zapytanie, wysłany sygnał o dostępności nowych danych, który to sygnał jest następnie przekazywany do aplikacji);
  • lokalizacja, która wykorzystując Wi-Fi pozwala uzyskać informację o lokalizacji szybciej, niż z systemu AGPS (wynika z tego, że łącznie system korzysta z trzech faktycznych źródeł informacji o lokalizacji: GPS, sieć komórkowa i Wi-Fi);
  • wyszukiwarka i mapy bing, dla których dostępne są kontrolki mapy oraz API wyszukiwarki;
  • Windows Live ID, będąca usługą logowania znaną również pod nazwą .NET Passport;
  • Xbox LIVE, użyteczna w zakresie np. wymiany danych o wynikach gry na koncie użytkownika.

Środowisko uruchomieniowe aplikacji

W systemie Windows Phone 7 środowisko uruchomieniowe aplikacji bazuje na znanej z .NET Framework bibliotece uruchomieniowej CLR (ang. Common Runtime Library). Jednakże nie są oczywiście dostępne wszystkie języki platformy .NET. Jedynym językiem, w którym obecnie można pisać aplikacje jest język C#. Natomiast w wersji 7.1 dodatkowo dostępny będzie język VB.NET.

W skład środowiska uruchomieniowego wchodzą również dwie biblioteki odpowiedzialne za renderowanie interfejsu użytkownika aplikacji: Silverlight i XNA (opisane już w poprzednim wpisie). Ponadto trzecim elementem, na którym można budować interfejs użytkownika jest tandem HTML i JavaScript, renderowany przez wbudowaną w system przeglądarkę Internet Explorer 8, dostępną dla aplikacji jako komponent, podobnie jak ma to miejsce w biurkowej wersji Windows (w Windows Phone 7.1 będzie to wersja 9 przeglądarki, wyposażona w obsługę sprzętowego renderingu HTML 5).

Kolejnym składnikiem środowiska uruchomieniowego aplikacji są podstawowe API systemu operacyjnego, dające dostęp do obszarów takich, jak np.: wyposażenie telefonu, akcja tworzenia nowej wiadomości e-mail w systemowym programie pocztowym, i in. (w wersji 7.1 API mają być jeszcze znacząco rozbudowane, np. o obsługę wielozadaniowości). Oczywiście jest dostępna również najbardziej podstawowa biblioteka Base Class Library (BCL), wspólna z innymi platformami .NET, co zapewnia przenośność skompilowanego kodu między tymi platformami, pod warunkiem, że kod ten ogranicza się do obszarów niezwiązanych z konkretnym systemem operacyjnym (np. klasy danych aplikacji).

Środowisko uruchomieniowe aplikacji pozwala w wersji 7.0 systemu przechowywać dane w formie dowolnych plików, umieszczonych w wydzielonym dla każdej aplikacji obszarze, poprzez API Isolated Storage, które w podobnej formie znane jest z technologii Silverlight. Natomiast w wersji 7.1, system dodatkowo udostępnia obsługę danych strukturalnych, poprzez wbudowany silnik bazodanowy SQL Server CE (z dołączonym ORM, umożliwiającym obsługę zapytań LINQ to SQL, ale bez obsługi bardziej rozwiniętej technologii Entity Framework).

Koncepcja Windows Phone 7 w porównaniu do Windows Mobile 6 i innych systemów mobilnych

Biblioteka Silverlight

Windows Phone 7 już od pierwszego wrażenia znacznie różni się od swego poprzednika, co wynika z obranego profilu użytkownika końcowego i koncepcji jego pracy z systemem, a ma swoje głębsze konsekwencje technologiczne. Spróbujmy zrozumieć, z czego dokładnie te konsekwencje wynikają.

Zacznijmy od tego, że poprzednia wersja została od początku zaprojektowana dla urządzeń z ekranem opornościowym (reagującym na dotyk pojedynczego rysika), podczas gdy nowa wersja przeznaczona jest wyłącznie dla urządzeń z ekranem pojemnościowym (potrafiącym rozpoznać dotyk wielu palców).

Oczywista różnica z tego wynikająca, to konieczność zapewnienia obsługi nowych zdarzeń interakcji z interfejsem użytkownika, bowiem ekran pojemnościowy pozwala na wykonanie gestów, takich jak np. powiększanie dwoma palcami. Ponadto elementy interaktywne na ekranie i odległość między nimi muszą być odpowiednio większe, aby obsługa palcem była równie precyzyjna, jak obsługa rysikiem, znanym ze starszej wersji systemu mobilnego. Także sposób odpowiedzi interfejsu użytkownika na bodźce powinien być bardziej dynamiczny, bo bardziej dynamiczna jest swobodna praca z ekranem pojemnościowym, niż praca z rysikiem.

Chociaż widać już pierwsze różnice wprowadzane przez nowy system, to zasadne mogłoby się wydawać utrzymanie pełnej zgodności z narzędziami dla programistów przeznaczonymi dla Windows Mobile 6. Konieczne byłoby dodanie jedynie kilku nowych kontrolek i zdarzeń do istniejących kontrolek, aby zapewnić obsługę wielodotyku. Uczynili tak np. autorzy Delphi, wprowadzając do wersji 2010 obsługę wielodotyku w znanej bibliotece wizualnej VCL.

Wydaje się jednak, że dla twórców Windows Phone 7 kluczowa była chęć zmiany profilu użytkownika końcowego systemu mobilnego z biznesowego na indywidualno-biznesowego, a co za tym idzie uczynienia interfejsu użytkownika bardziej atrakcyjnym dla tych osób. Sposób, w jaki firma Microsoft rozumie tę atrakcyjność, nie jest oczywiście przedmiotem tego wpisu, można jedynie zasygnalizować, że wraz z systemem Windows Phone 7 zaproponowano nowy język wizualny komunikacji z użytkownikiem – nazwany Metro UI (nazwa ta inspirowana jest językiem wizualnym komunikacji miejskiej). Najkrócej rzecz ujmując, sprowadza się on do „minimalistycznej” w formie grafiki, nieprzypominającej niczym interfejsu biurkowego Windows (ani Windows Mobile 6, który z biurkowego Windows się wywodzi) – połączonej z relatywnie dużą ilością animacji, która „wynagradza” minimalizm grafiki.

Z tego punktu widzenia słuszna wydaje się zmiana głównej technologii interfejsu użytkownika na omawianą za chwilę bibliotekę Silverlight, która z sukcesem rozwijana jest od 2007 roku i stanowi obecnie najciekawszą propozycję tej firmy w zakresie programowania interfejsów użytkownika, daleko wykraczającą poza dotychczas proponowane przez Microsoft rozwiązania. Choć trzeba zaznaczyć, że prym ten wiedzie razem z podobną do niej, lecz przeznaczoną na biurkowy system Windows i z tego powodu będącą jej (w pewnym uproszczeniu) nadzbiorem, biblioteką WPF (Windows Presentation Foundation), która ze względu na nowatorskość, obecnie jest jeszcze relatywnie mało wykorzystywana w praktyce.

Biblioteka Silverlight, jaką znaliśmy przed premierą Windows Phone 7, jest dostępną na komputery z systemami Windows i Mac OS wtyczką do przeglądarki internetowej, pozwalającą uruchamiać bogate w animacje aplikacje, podobnie jak Adobe Flash, jednak z różnicami pod względem możliwości i intencji użycia. Jej pierwotne miejsce przeznaczenia (wtyczka przeglądarki) może wydawać się skrajnie różne od aplikacji telefonu komórkowego, kiedy weźmiemy jednak pod uwagę fakt, że biblioteka Silverlight jest kompletnym środowiskiem uruchomieniowym aplikacji, zamkniętym w niewymagającej wiele miejsca postaci, nakierowanym od początku na multimedialność oraz nieobciążonym charakterystyką interfejsu użytkownika biurkowego systemu Windows, to zacznie nam się wydawać adekwatna do zamierzonego kształtu systemu.

Oczywiście nie jest to dokładnie ta sama biblioteka Silverlight, co we wtyczce do przeglądarki, ale z punktu widzenia jej użytkownika – programisty – niewiele się od niej różniąca. Jest to mianowicie biblioteka Silverlight w wersji 3 (lub 4 w Windows Phone 7.1) wzbogacona głównie o API obsługi telefonu i optymalizacje wydajnościowe, spowodowane ograniczoną mocą obliczeniową telefonu komórkowego, w porównaniu do komputera biurkowego. Jedyna w API różnica o powszechnym znaczeniu, wynika z tych optymalizacji i dotyczy wielowątkowości animacji, więc zmiany zostały ograniczone do minimum.


Należy zaznaczyć, że powyższej genezy przejścia na technologię Silverlight nigdy firma Microsoft (o ile autorowi wiadomo) nie przedstawiła. W związku z tym geneza ta została autorsko wyprowadzona z cząstkowych informacji udostępnianych przez producenta i jako subiektywna obarczona jest ryzykiem.

Z punktu widzenia innowacyjności interfejsu użytkownika, nowy system z jednej strony korzysta z wypracowanych przez firmę Apple (pioniera użycia ekranu pojemnościowego) szczegółowych rozwiązań – np. zastosowanie gestu przewijania w kontrolkach wyboru daty. Jednak z drugiej strony firma Microsoft w nowym systemie mobilnym odchodzi od swojego systemu biurkowego dalej niż zrobiła to firma Apple, wprowadzając niestosowane przez konkurenta podejście. W związku z tym jest to innowacja, ale trzeba przyznać, że bazująca na fundamentach wypracowanych przez firmę Apple.

Biblioteka XNA

Omówienie technologii interfejsu użytkownika nowego systemu mobilnego nie byłoby kompletne, gdybyśmy nie wspomnieli o znanej z konsoli do gier Xbox biblioteki XNA. Jest ona dostępna na Windows Phone 7 równolegle z biblioteką Silverlight, jednak służy do pisania gier i aplikacji stricte multimedialnych.

W obecnej wersji systemu nie można łączyć na ekranie elementów interfejsu pochodzących z obu bibliotek, ale zmieni to się w Windows Phone 7.1. Można natomiast korzystać z API pomocniczych obu bibliotek w jednej aplikacji, co jest korzystne, bo oba API uzupełniają się.

Wymagania sprzętowe

Dużą zmianą, w stosunku do systemu Windows Mobile 6 jest określenie przez firmę Microsoft bardzo szczegółowych wymagań sprzętowych, które muszą zostać spełnione przez producentów telefonów, aby ich produkty mogły być wyposażone w nowy system. Jest to ułatwienie dla programistów, bo umożliwia skupienie się na spójnej konfiguracji sprzętowej, na której będzie działać aplikacja – zapewniając jej z nią kompatybilność.

Warto zauważyć, że takie stanowisko dotyczące sprzętu jest pośrednim między skrajnymi stanowiskami dwóch, jak się wydaje najważniejszych na tym rynku, konkurentów firmy Microsoft. Z jednej strony firma Apple jest wyłącznym producentem jedynego w danej generacji modelu telefonu komórkowego, dedykowanego swojemu systemowi mobilnemu, więc umożliwia programistom zoptymalizowanie aplikacji, w pełni dla tego modelu. Z drugiej strony firma Google nie narzuca w praktyce żadnych wymagań sprzętowych (są one bowiem bardzo niskie, więc traktowane symbolicznie) – co przypomina sytuację systemu Windows Mobile 6 i znacznie utrudnia zapewnienie przez programistów kompatybilności aplikacji z wszystkimi urządzeniami, na których można zainstalować dany system operacyjny.

Wydaje się, że stanowisko dotyczące wymagań sprzętowych Windows Phone 7 koresponduje z opisaną, na początku tego wpisu, chęcią zmiany profilu użytkownika końcowego systemu, bowiem użytkownicy indywidualni mają z reguły mniejszą, niż użytkownicy biznesowi, możliwość uzyskania dedykowanych dla swych urządzeń aplikacji, jak również uzyskania szybkiej pomocy technicznej, stąd konieczność zapewnienia spójnej i bardziej niezawodnej platformy sprzętowej.

Znaczące, z programistycznego punktu widzenia, obligatoryjne wymagania sprzętowe Windows Phone 7 to:

  • System-on-a-chip Snapdragon z procesorem Scorpion o zestawie instrukcji ARMv7 i zegarze 1 Ghz oraz z procesorem karty graficznej zdolnym do akceleracji DirectX 9 (w przypadku Windows Phone 7.1 możliwa jest również nowsza wersja tego SoC z zegarem 800 Mhz, lecz równie wydajna, bądź też wersja szybsza z zegarem 1 Ghz – w obu przypadkach są to SoC ze zaktualizowanym GPU) – do dyspozycji aktywnej aplikacji jest 90% czasu procesora;
  • minimum 256 MB pamięci RAM, z czego aktywna aplikacja otrzymuje 90 MB
    (w wersji 7.1, w zależności od ilości wolnej pamięci, będzie mogło to być więcej), przy czym wszystkie aktualnie obecne na rynku telefony z Windows Phone 7 dysponują 512 MB pamięci RAM;
  • przynajmniej 8 GB pamięci flash (każda aplikacja ma do dyspozycji maksymalnie 2 GB, co wynika z architektury systemu);
  • trzy przyciski sprzętowe służące do sterowania systemem: wstecz, start i szukaj (dla aplikacji obecnie dostępny jest tylko przycisk wstecz, natomiast przycisk szukaj zostanie udostępniony w wersji 7.1) oraz trzy przyciski sprzętowe typowe dla wszystkich telefonów komórkowych: głośność, aparat i włącz/odblokuj;
  • wysoka, jak na telefon komórkowy rozdzielczość ekranu 480×800 (do pewnego momentu dopuszczana była niższa rozdzielczość 320×480, ale ostatecznie
    nie pojawiły się urządzenia z tą rozdzielczością i obecnie Microsoft rekomenduje programistom koncentrowanie się wyłącznie na wyższej rozdzielczości);
  • ekran pojemnościowy musi potrafić jednocześnie wykrywać minimum cztery punkty dotyku, w związku z tym możliwe jest operowanie czterema palcami jednocześnie, co stwarza możliwość kreacji nowych gestów sterowania aplikacjami, jak również dostępna jest gotowa biblioteka, umożliwiająca wykrywanie standardowych gestów;
  • obecne muszą być następujące czujniki: AGPS, przyspieszeniomierz, kompas (którego API zostanie wprowadzone dopiero w wersji 7.1 systemu) oraz czujnik zbliżeniowy i czujnik światła (oba nie są obecnie dostępne poprzez API systemu);
  • aparat fotograficzny z lampą błyskową i kamera o matrycy minimum 5 megapikseli (w wersji 7.0 aplikacje mają do dyspozycji tylko API robienia zdjęć, ale w wersji 7.1 dodany ma być dostęp do bieżącego strumienia kamery, co umożliwi konstruowanie aplikacji z dziedziny rzeczywistości rozszerzonej);
  • wibracja i radio FM.

Opcjonalne zalecenia sprzętowe o znaczeniu dla programistów są następujące:

  • podczas projektowania systemu, firma Microsoft oczekiwała wyboru przez producentów telefonów wyświetlaczy klasy OLED, jako słabiej zużywających baterie, niż wyświetlacze LCD, gdy znaczna część ekranu jest czarna, a jest to domyślny kolor tła ekranów systemowych Windows Phone 7 – ostatecznie jednak na rynku obecne są urządzania obu rodzajów, więc zdaniem autora nie zawsze zastosowanie czarnego koloru tła przyniesie korzyść użytkownikom, aczkolwiek w aplikacjach codziennego użytku warto stosować domyślne kolory i wybór pozostawić użytkownikowi, który może wybrać biały kolor tła w ustawieniach systemu;
  • telefony mogą posiadać klawiaturę sprzętową, przy czym dostępna jest oczywiście klawiatura ekranowa (SIP, ang. Software Input Panel), która dostosowuje swój układ w zależności od rodzaju wprowadzanych informacji – np. adres internetowy (zdecydowana większość aktualnie obecnych na rynku telefonów klawiatury sprzętowej nie posiada);
  • od wersji 7.1 producenci będą mogli opcjonalnie wyposażać swoje urządzenia w żyroskop, który będzie od tej wersji systemu dostępny dla aplikacji.