niedziela, 4 listopada 2012

Ree7 Tile Toolkit i obrazy na kafelkach

Jeśli kiedykolwiek poczujecie chęć stworzenia własnych "kafelek" do swojej aplikacji to zapewne odnajdziecie w czeluściach Internetu bibliotekę "Ree7 Tile Toolkit for Windows Phone 7" umożliwiającą tworzenie całkowicie dowolnych kafelek. I żeby była jasność - poprzez "całkowitą dowolność" mam na myśli tworzenie zawartości Tile w ten sam sposób co zwykłego User Control (oczywiście Tile musi być statyczny, więc animacji w nim nie odtworzymy) - czyli tak za pomocą XAML jak i bezpośrednio przy użyciu kodu w VB czy C#.

Niestety przy tworzeniu własnych kafelek napotkać możemy jeden podstawowy problem wynikający ze sposobu obsługi obrazów w Silverlight. Obrazy generalnie nie są ładowane do pamięci aż do momentu, w którym są wyświetlane, tak więc tworzone kafelki po prostu renderowane są bez elementów pochodzących z plików graficznych. Dodatkowo w przykładowej aplikacji biblioteki ten problem jest nie dostrzegalny, gdyż tam kafelki są uprzednio wyświetlane w drzewie aplikacji:
SimpleTileContainer.Children.Add(myCustomTile1);
a tworzone są dopiero po kliknięciu w odpowiedni przycisk.
ShellTile.Create(new Uri("/MainPage.xaml?src=SampleTile", UriKind.Relative), myCustomTile1.GetShellTileData());

Z kolei kafelek dynamicznie generowany nie używa żadnych grafik przy jego tworzeniu:
CustomTile dynamicTile = CustomTileFactory.GetTemplatedTile("SampleTile3", new NativeCountTileTemplate()
{
     Background = null,
     Icon = null,
     Count = ":D",
     AppName = "Tile w/o visual" 
}, TileSize.Standard);
Tak więc od razu nie widać żadnego problemu, ale wystarczy, że do dynamicznie tworzonego kafelka dodamy grafikę i nagle okazuje się, że utworzony Tile dalej wygląda tak samo:
CustomTile dynamicTile = CustomTileFactory.GetTemplatedTile("SampleTile3", new NativeCountTileTemplate()
{
     Background = null,
     Icon = new BitmapImage()
     {
          CreateOptions = BitmapCreateOptions.None,
          UriSource = new Uri("SampleTileIcon2.png", UriKind.Relative)
     },
     Count = ":D",
     AppName = "Tile without Icon" 
}, TileSize.Standard);
Jak wcześniej napisałem problem wynika ze sposobu obsługi obrazów przez Silverlight (i ogólnie .NET). W przypadku WPF problem ten można rozwiązać ustawiając "((BitmapImage)bi).CacheOption = BitmapCacheOption.OnLoad;" dla źródła kontrolki Image, jednak w przypadku WindowsPhone ta opcja nie jest dostępna i musimy poradzić sobie w inny sposób.

Jednym z łatwiejszych rozwiązań jest załadowanie obrazu do strumienia danych (Stream) i dopiero użycie tego strumienia jako źródła dla obrazu:
StreamResourceInfo sr = Application.GetResourceStream(new Uri("SampleTileIcon2.png", UriKind.Relative));
            BitmapImage bmp = new BitmapImage();
            bmp.SetSource(sr.Stream);

CustomTile dynamicTile = CustomTileFactory.GetTemplatedTile("SampleTile3", new NativeCountTileTemplate()
{
     Background = null,
     Icon = bmp,
     Count = ":D",
     AppName = "Tile with Icon" 
}, TileSize.Standard);
Rozwiązanie to jest proste i ze bardzo proste w implementacji - niestety wymaga ustawienia wszystkich obrazów bezpośrednio w kodzie, gdyż te umieszczone w XAML pozostaną niezaładowane.

Ale skoro ponownie wywołujemy do tablicy tego XAMLa, to wróćmy do tego o czym pisałem na początku - czyli w całości własnym kafelku. Z grubsza rzecz ujmując szablonem wysyłanym do fabryki (CustomTileFactory) może być dowolny obiekt dziedziczący po UserControl, tak więc mogą to być również całe strony aplikacji (Page) jednak z czysto praktycznych względów warto ograniczyć się do tego minimum. Warto również tworząc szablonową kontrolkę ustawić jej wysokość i szerokość na 173 piksele (d:DesignHeight="173" d:DesignWidth="173") - czyli standardową wielkość kafelki pod Windows Phone 7. Krok ten można pominąć, gdyż biblioteka sama przeskaluje tło kafelki do tych wymiarów, jednak takie rozwiązanie po prostu ułatwi nam jej projektowanie.
A gdy już wszystko ustawimy w swojej kontrolce tak jak chcemy możemy przetworzyć ją już znaną nam fabrykom:
CustomTile customTile = CustomTileFactory.GetTemplatedTile("Tile",
                new TileFromUserControl(), TileSize.Standard);
Niestety domyślnie biblioteka nie umożliwia tworzenia "podwójnych" kafelek - tych składających się z przedniej i tylnej warstwy, które na telefonie obracają się między sobą. Jednak drobna modyfikacja biblioteki pozwala na uzupełnienie tej funkcjonalności. Jak zwykle diabeł tkwi w szczegółach i jest tak i tym razem, a konkretniej w metodzie ((CustomTile)customTile).GetShellTileData(), która to zwraca dane naszej utworzonej kafelki. Domyślnie ustawione jest tylko tło, które zawiera wyrenderowaną zawartość szablonu, jednak nic nie stoi na przeszkodzi by do wyniku tej metody dodać tło utworzone za pomocą innego CustomTile:
CustomTile customTile1, customTil2;
(...)
StandardTileData std = customTile1.GetShellTileData();
std.BackBackgroundImage = customTile2.GetShellTileData().BackgroundImage;
I tak utworzony zbiór danych kafelka możemy już bez trudu umieścić na stronie startowej telefonu:

.ShellTile.Create(new Uri("/MainPage.xaml", UriKind.Relative), std);.

Keywords: Kafelek, Tile, Custom Tile, Tile from XAML, Windows Phone, Image from stream, Dynamic Tile creation

Brak komentarzy:

Publikowanie komentarza