Samouczek Silverlight – część 4
Powiązanie danych i przechowywanie ustawień
W poprzedniej, 3 części, zajęliśmy się pobraniem danych z usługi internetowej oraz wyświetleniem ich w kontrolce UI. DataGrid, którego użyliśmy, nie spełnia naszych docelowych oczekiwań. Użyjemy kontrolki ItemsControl oraz DataTemplate. Wprowadzi nas to w tematykę powiązań (binding) w XAML.
Przemeblowanie UI – kasujemy DataGrid
Cóż, pomimo całej wykonanej pracy, kasujemy kontrolkę DataGrid. Co za tym idzie, nie będziemy potrzebować referencji do biblioteki xlmns:sdk, usuwamy ją także.
Zastępujemy kontrolkę DataGrid, kontrolką ItemsControl (identyfikator kontrolki pozostaje niezmieniony):
<ItemsControl x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1"/>
Tutaj autor promuje znów narzędzie Expression Blend. I sugeruje użycie go do modyfikacji szablonu ItemTemplate dla kontrolki ItemsControl. Kontrolka ItemsControl wyświetla dane w zadanym przez nas układzie. W momencie gdy zmienimy tylko DataGrid na ItemsControl, bez podania szablonu wyświetlania, otrzymamy:
Kontrolka ItemsControl nie ma pojęcia jak wyświetlić wyniki wyszukiwania, dlatego potrzebujemy szablonu. Chcemy aby dany wyświetlały się we wzorcu:
Gdzie koperta to miejsce na obrazek awatara autora wpisu. Tutaj autor przechodzi do Blend i tworzy wizualnie szablon. Osobiście preferuje czysty XAML. W pliku Search.xaml dodajemy sekcje z szablonem SearchTweetTemplate:
<navigation:Page.Resources> <DataTemplate x:Key="SearchResultsTemplate"> <Grid Margin="4,0,4,8" d:DesignWidth="446" d:DesignHeight="68"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Border VerticalAlignment="Top" Margin="8" Padding="2" Background="White"> <Image Width="40" Height="40" /> </Border> <StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="0,4,0,0"> <TextBlock x:Name="AuthorName" FontWeight="Bold" /> <Grid Margin="0,6,0,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="2" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock x:Name="TweetMessage" TextWrapping="Wrap" /> <TextBlock x:Name="PublishDateLabel" Grid.Row="2" /> </Grid> </StackPanel> </Grid> </DataTemplate> </navigation:Page.Resources>
Zdefiniowaliśmy tabele (Grid) z dwoma kolumnami. W pierwszej wyświetlamy obrazek o rozmiarach 40×40. W drugiej grupujemy kilka elementów jeden pod drugim (StackPanel). Najpierw tekst z nazwą autora wpisu (TeXtBlock AuthorName), a pod nim tabelka z 3 wierszami. Pierwszy to wpis z Tweet-ra, Drugi to odstęp, a trzeci to data publikacji wpisu.
Z uwagi na brak paska przewijania w kontrolce ItemsControl, dodajemy go używając kontrolki Scrollviewer:
<ScrollViewer Grid.Row="2" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" BorderThickness="1"> <ItemsControl x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1"/> </ScrollViewer>
Stworzyliśmy szablon. Teraz musimy wskazać kontrolce ItemsControl aby go używała:
<ItemsControl x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1" ItemTemplate="{StaticResource SearchResultsTemplate}"/>
Następnie wskażemy szablonowi co robić z danym, które otrzymujemy.
Powiązania (binding) w XAML
Kontrolka ItemsControl otrzymuje dane do wyświetlania (kontrolka ma identyfikator jak stary DataGrid, nie zmieniliśmy nic w kodzie C#, więc SerachResults.ItemsSource wciąż wskazuje na naszą kolekcje danych PagedCollectionView. Aby połączyć dane z szablonem, musimy użyć powiązań (binding). Podstawowa składnia w XAML wygląda tak:
{Binding Path=<ściezka do danych>. Mode=<tryb połączenia>}
Są dostępne dodatkowe opcje, ale my zaczniemy z podstawowymi. Aby, powiązać element obrazka z awatarem w kolekcji danych, potrzebny jest kod:
<Image Width="40" Height="40" Source="{Binding Path=Avatar, Mode=OneWay}" />
Do powiązania autora z elementem AuthorName:
<TextBlock x:Name="AuthorName" FontWeight="Bold" Text="{Binding Path=Author, Mode=OneWay}"/>
Do powiązania tekstu wpisu z elementem TweetMessage:
<TextBlock x:Name="TweetMessage" Text="{Binding Path=Tweet, Mode=OneWay}" TextWrapping="Wrap" />
We wszystkich przypadkach używamy trybu OneWay (jednokierunkowego), ponieważ nie wymieniamy danych z usługą internetową, ruch danych jest tylko w jednym kierunku. Dla daty publikacji, chcemy dodatkowo użyć zdefiniowany format daty. Do tego celu posłużymy się Value Converters (formatowanie danych).
Tworzenie formatowania danych (Value Converter)
Value converters (formatki danych) to klasy używające IValueConverter, dostarczającego metod Convert i COnvertBack. W przypadku daty wpisu pozwolimy na zdefiniowanie formatu objektu DateTime. Stworzymy klase DateTimeConverter.cs i w katalogu Converters naszego projektu (pliki i katalogi dodajemy zgodnie ze wskazówkami w poprzednich częściach). Kod:
using System; using System.Threading; using System.Windows.Data; namespace WyszukiwarkaTwitter.Converters { /* * Use this converter for formatting dates in XAML databinding * Example: * Text="{Binding Path=PublishDate, Converter={StaticResource DateTimeFormatter}, ConverterParameter=MMM yy}" /> * * */ public class DateTimeConverter : IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { DateTime? bindingDate = value as DateTime?; if (culture == null) { culture = Thread.CurrentThread.CurrentUICulture; } if (bindingDate != null) { string dateTimeFormat = parameter as string; return bindingDate.Value.ToString(dateTimeFormat, culture); } return string.Empty; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } }
Formatowania użyjemy w kodzie XAML (plik Search.xaml). Dodamy referencje do klasy.
xmlns:converters="clr-namespace:WyszukiwarkaTwitter.Converters"
Następnie w sekcje XAML Resources (gdzie zdefiniowaliśmy szablon) dodamy element formatki:
<navigation:Page.Resources> <converters:DateTimeConverter x:Key="DateTimeFormatter" /> <DataTemplate x:Key="SearchResultsTemplate"> ...
Teraz możemy powiązać datę publikacji oraz użyć formatowania:
<TextBlock x:Name="PublishDateLabel" Text="{Binding Path=PublishDate, Converter={StaticResource DateTimeFormatter}, ConverterParameter=dd-MMM-yyyy hh:mm tt}" Grid.Row="2" />
Poinformowaliśmy XAML aby użył IValueConverter do wyświetlenia daty, co powoduje pokazanie daty w zdefiniowanym formacie. Wynikiem powyższych zmian jest pożądany przez nas sposób wyświetlania wpisów:
Gotowe. Nie było to takie trudne, prawda? Składania powiązań (binding) jest kluczowym elementem w tworzeniu aplikacji.
Przechowywanie ustawień
Użytecznym aspektem aplikacji byłoby przechowywanie identyfikatora ostatniego wpisu, przy kolejnym uruchomieniu aplikacji, moglibyśmy zacząć wyszukiwanie od nieznanych nam jeszcze wpisów. Dodatkowo fajnie byłoby stworzyć historię szukanych wyrazów, którą moglibyśmy przeglądać na stronie historia.
W tym celu użyjemy w Silverlight mechanizmu IsolatedStorage. Pozwala on na niezabezpieczone przechowywanie małych ilości danych we wskazanej przez użytkownika lokalizacji. Więcej informacji w języku angielskim:
Dodamy w katalogu Model pomocniczą klasę Helper. Obsłuży ona zapis/odczyt ustawień. Wymogiem IsolatedStorage jest utworzenie pliku do zapisu/odczytu danych. W naszym przypadku użyjemy IsolatedStorageSettings do zapisu pary nazwa/wartość (szukany wyraz/ostatni identyfikator). Zawartość pliku Helper.cs:
using System.IO.IsolatedStorage; namespace WyszukiwarkaTwitter.Model { public class Helper { internal static string GetLatestTweetId(string searchTerm) { if (IsolatedStorageSettings.ApplicationSettings.Contains(searchTerm)) { return IsolatedStorageSettings.ApplicationSettings[searchTerm].ToString(); } else { return "0"; } } internal static void SaveLatestTweetId(string searchTerm, string latestId) { if (IsolatedStorageSettings.ApplicationSettings.Contains(searchTerm)) { IsolatedStorageSettings.ApplicationSettings[searchTerm] = latestId; } else { IsolatedStorageSettings.ApplicationSettings.Add(searchTerm, latestId); } } } }
Teraz użyjemy jej w pliku Search.xaml.cs. W funkcji SzukajTweetaEx tuż po uruchomieniu paska postepu dodamy :
private void SzukajTweetaEx() { if (!string.IsNullOrEmpty(SearchTerm.Text)) //sprawdź czy wprowadzono fraze { _timer.Stop(); //zatrzymaj stoper gdy wyszukujemy dłużej niż zdefiniowany interwał BusyIndicator.IsBusy = true; //włącz pasek postępu _lastId = Helper.GetLatestTweetId(SearchTerm.Text); // pobierz zapisany identyfikator ostatniego wpisu Helper.SaveLatestTweetId(SearchTerm.Text, _lastId); // zapisz historie, nawet gdy brak nowych wpisów
...
oraz w funkcji OnReadCompleted po zamknięciu czytnika XML dodamy (w przypadku udanego wyszukiwania) zapis szukanej frazy i identyfikator ostatniego wpisu:
...
rdr.Close(); //zamknij czytnik Helper.SaveLatestTweetId(SearchTerm.Text, _lastId); //zapisz identyfikator ostatniego wpisu
...
Podsumowanie
Stworzyliśmy szablon wyświetlania danych, powiązaliśmy dane używając składni XAML, dodaliśmy formatkę danych oraz przechowywanie ustawień. Nasza aplikacja jest w sumie gotowa. W części 5 popracujemy nad upiększeniem interfejsu użytkownika.