PwT.N W14
Transkrypt
PwT.N W14
Aplikacja oparta na stronach Model tradycyjnej aplikacji okienkowej: okno lub okna (okna dokumentów, okna dialogowe), menu, paski zadań. Praca wykonywana jest przeważnie w obrębie pojedynczego okna, dodatkowe okna służą wykonaniu pobocznych zadań – i albo po ich wykonaniu następuje powrót do okna macierzystego, albo wykonują swe zadanie niezależnie i równolegle. Model aplikacji opartej na stronach. Pojedynczym elementem aplikacji jest strona (przeważnie jedna). Wykonywanie zadań opiera się na przemieszczaniu się z jednej strony do innej – liniowo. Kolejne kroki lub kolejne czynności przenoszą nas do następnych stron. Interfejs oparty na stronach Zmienia się tylko element główny – zamiast klasy Window klasa Page. Jest jednak istotna różnica – strona musi znajdować się w kontenerze. Może nim być: ● NavigationWindow ● element Frame wewnątrz innego okna ● element Frame wewnątrz innej strony ● Internet Explorer lub Firefox 1/45 Dodawanie stron do projektu: menu Project -> Add Page... Przykładowa zawartość strony: <Page x:Class="Wpf6.Page1" ... WindowTitle="To jest moja strona" Title="Moja strona" FontSize="16"> <StackPanel Margin="3"> <TextBlock Margin="3"> To jest prosta strona. </TextBlock> <Button Margin="2" Padding="2">OK</Button> <Button Margin="2" Padding="2">Zamknij</Button> </StackPanel> </Page> WindowTitle – jako nagłówek okienka Title – nazwa strony do zapisania w historii przeglądania (uwaga: nie ustawia się tu żadnego “rozmiaru strony” czy “rozmiaru okna”) 2/45 plik App.xaml: <Application x:Class="Wpf6.App" ... StartupUri="Page1.xaml" > <Application.Resources> </Application.Resources> </Application> (WPF sam rozpozna, że wskazujemy na stronę a nie na okno i utworzy kontener – NavigationWindow – w którym wyświetli stronę) 3/45 4/45 Ręczne stworzenie strony i kontenera: NavigationWindow win = new NavigationWindow() win.Content = new Page1(); win.Show(); Uzyskanie dostępu do okna kontenera z poziomu strony (najwcześniej możemy to zrobić w zdarzeniu ładowania strony): private void Page_Loaded(object sender, RoutedEventArgs e) { NavigationWindow win = (NavigationWindow)Window.GetWindow(this); } 5/45 Page Własności: Background, Foreground, FontFamily, FontSize – podobnie jak w wypadku okna Content – zawartość strony WindowWidth, WindowHeight, WindowTitle – ustawienie parametrów okna, ma znaczenie tylko dla stron wyświetlanych w oknie (a nie np. ramce) NavigationService – referencja na obiekt pozwalający na nawigację (przenoszenie się do innych stron) KeepAlive – informuje, że obiekt strony powinien zachowywać swą zawartość gdy użytkownik przejdzie do innej strony ShowsNavigationUI – czy kontener zawierający stronę ma wyświetlać przyciski nawigacji Title – nazwa strony wyświetlana w historii nawigacji (strona nie ma metod typu Show czy Hide – jest wyświetlana dopóki jej nie opuścimy przy pomocy nawigacji) 6/45 Hyperlinks <TextBlock Margin="3"> To jest prosta strona. Kliknij <Hyperlink NavigateUri="Page2.xaml"> tutaj </Hyperlink> aby przejść dalej. </TextBlock> 7/45 Kliknięcie (zdarzenie Click) możemy przetwarzać ręcznie (i np. wykonać jakieś zadanie lub przenieść użytkownika do innej strony): NavigationService nav; nav = NavigationService.GetNavigationService(this); nav.Navigate(new System.Uri("Page1.xaml", UriKind.RelativeOrAbsolute)); // lub: Page1 nextPage = new Page1(); nav.Navigate(nextPage); Możemy również podać tylko stronę docelową i nawigacją zajmie się WPF: <Hyperlink NavigateUri="Page2.xaml">...</Hyperlink> Uwaga: ta automatyczna nawigacja działa tylko jeśli link znajduje się na stronie, a nie np. zwykłym oknie. 8/45 9/45 Możliwa jest również nawigacja do lokacji zewnętrznych: <Hyperlink NavigateUri="http://wi.pb.edu.pl/"> Strona wydziału </Hyperlink> Wskazanie do którego elementu przewinąć docelową stronę: <Hyperlink NavigateUri="Page2.xaml#myButton"> tutaj </Hyperlink> Zawartość docelowej strony: <Page x:Class="Wpf6.Page2" ...> <StackPanel> <Button Name="myButton">Zamknij</Button> </StackPanel> </Page> 10/45 Obsługiwanie błędów nawigacji: najlepiej w klasie aplikacji <Application x:Class="Wpf6.App" ... StartupUri="Page1.xaml" NavigationFailed="Application_NavigationFailed" > <Application.Resources> </Application.Resources> </Application> public partial class App : Application { private void Application_NavigationFailed(...) { if (e.Exception is System.Net.WebException) { MessageBox.Show("Nie udało się wejść na " + e.Uri.ToString()); e.Handled = true; } } } 11/45 Strona w zwykłym oknie: element Frame <Window x:Class="Wpf6.Window1" ... Title="Zwykłe okno"> <Grid Margin="3"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <StackPanel> <TextBlock Margin="3" TextWrapping="Wrap"> Zwykłe okno </TextBlock> </StackPanel> <Frame Grid.Column="1" Source="Page1.xaml" BorderBrush="Blue" BorderThickness="1" /> </Grid> </Window> 12/45 Można samodzielnie ustawić widoczność przycisków nawigacji: <Frame NavigationUIVisibility="Hidden" ... /> 13/45 Strony zagnieżdżone: <Page x:Class="Wpf6.Page2" ... Title="Page2" > <StackPanel Margin="3"> <TextBlock Margin="3"> Witamy na drugiej stronie. </TextBlock> <Frame Source="Page3.xaml" BorderBrush="Blue" BorderThickness="1" /> </StackPanel> </Page> 14/45 15/45 16/45 alternatywne rozwiązanie: <Frame JournalOwnership="OwnsJournal" .../> 17/45 Historia nawigacji 18/45 19/45 W historii został zapisany cały poprzedni stan odwiedzanej strony. Uwaga: ponowne nawigowanie do tej samej strony (np. przez link) tworzy zupełnie nową instancję strony i stan nie zostanie odtworzony Zachowywane są tylko te właściwości zależnościowe elementów strony, które mają ustawioną (podczas rejestracji właściwości) flagę Journal. Jeśli chcemy aby historia zachowywała jakieś nasze własne dane powinniśmy stworzyć właściwość zależnościową (w klasie strony): private static DependencyProperty MojeDaneProperty; static Page1() { FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(); metadata.Journal = true; MojeDaneProperty = DependencyProperty.Register( "MojeDaneProperty", typeof(string), typeof(Page1), metadata, null); } private string MojeDane { set { SetValue(MojeDaneProperty, value); } get { return (string)GetValue(MojeDaneProperty); } } Możemy również wymóc zatrzymanie całego obiektu w pamięci ustawiając właściwość strony KeepAlive. 20/45 Zdarzenia nawigacji: Navigating – zaraz rozpocznie się nawigacja, można to zatrzymać Navigated – rozpoczęto nawigację (ale strona jeszcze nie została uzyskana) NavigationProgress – podnoszone co 1KB danych informuje o postępie nawigacji (NavigationProgressEventArgs.BytesRead i NavigationProgressEventArgs.MaxBytes) LoadCompleted – strona została pobrana i przetworzona (ale jeszcze nie zainicjowana i wyświetlona) FragmentNavigation – przed przescrollowaniem do żądanego elementu NavigationStopped – nawigacja zatrzymana metodą StopLoading() NavigationFailed – niepowodzenie (być może nie udało się zlokalizować strony docelowej) 21/45 Journal Model aplikacji – wizard: 22/45 a co z historią? NavigationService nav = NavigationService.GetNavigationService(this); string pageName = ""; while (pageName != "Page1.xaml") { JournalEntry entry = nav.RemoveBackEntry(); pageName = System.IO.Path.GetFileName(entry.Source.ToString()); } 23/45 efekt: 24/45 Dodawanie własnych elementów do historii <DockPanel Margin="3"> <TextBlock DockPanel.Dock="Top">Składniki</TextBlock> <Button DockPanel.Dock="Bottom" Click="Add_Click"> Dodaj </Button> <ListBox Name="listBox"></ListBox> </DockPanel> 25/45 public partial class Page1 : Page { ... // dodawanie elementów do list static char i = 'A'; private void Add_Click(...) { string item = i.ToString(); i = (char)((int)i+1); listBox.Items.Add(item); } ... } A jak zapamiętywać w Dzienniku historię operacji? 26/45 // klasa do zapamiętania stanu w historii: [Serializable()] public class ListaJournalEntry : CustomContentState { // tu zapamiętamy dane strony private List<string> items; public List<string> Items { get { return items; } } // nazwa pod jaką element pojawi się w historii: private string _journalName; public override string JournalEntryName { get { return _journalName; } } // (tylko gettery, bo wartości ustawimy w konstruktorze) 27/45 // pozwólmy aby strona sama odtworzyła swą zawartość // (metoda callback) public delegate void ReplayListChange(ListaJournalEntry o); private ReplayListChange replayListChange; public override void Replay( NavigationService navigationService, NavigationMode mode) { this.replayListChange(this); } // konstruktor public ListaJournalEntry( List<string> items, string journalName, ReplayListChange replayListChange) { this.items = items; this._journalName = journalName; this.replayListChange = replayListChange; } } // end of ListaJournalEntry 28/45 // dodanie poniższego interfejsu jest ważne! public partial class Page1 : Page, IprovideCustomContentState { ... // dodawanie elementów do list static char i = 'A'; private void Add_Click(...) { string item = i.ToString(); string journalName = "Dodano " + item; NavigationService nav = NavigationService.GetNavigationService(this); nav.AddBackEntry(GetJournalEntry(journalName)); i = (char)((int)i+1); listBox.Items.Add(item); } 29/45 // składowanie zawartości private ListaJournalEntry GetJournalEntry(string name) { // kopiowanie zawartości listBoxa List<string> items = new List<string>(); foreach(object o in listBox.Items) items.Add((string)o); // obiekt stanu return new ListaJournalEntry(items, name, Replay); } // odtworzenie zawartości private void Replay(ListaJournalEntry state) { listBox.Items.Clear(); // kopiowanie z historii do listBoxa foreach (string item in state.Items) listBox.Items.Add(item); // przy odtworzeniu stanu zapamiętamy jego nazwę z historii restoredName = state.JournalEntryName; } private string restoredName; 30/45 // dodawanie historii, gdy nastąpiło wyjście ze strony public CustomContentState GetContentState() { return GetJournalEntry(restoredName); } ... } // end of Page1 31/45 Page Functions odpowiednik okien dialogowych – strony, które mogą zwracać wartość menu Add → New Item → Page Function (WPF) template <PageFunction ... xmlns:local="clr-namespace:MojaAplikacja" x:Class="NavigationApplication.PageFunction1" x:TypeArguments="local:Dodatek" Title="Wybór dodatku" > ... </PageFunction> public partial class PageFunction1 : PageFunction<Dodatek> { /*...*/ } Określamy typ zwracanej wartości. 32/45 // otwieranie PageFunction: <Page ...> <StackPanel> <TextBlock>Wybierz dodatki</TextBlock> <ListBox .../> <TextBlock HorizontalAlignment="Right"> <Hyperlink Click="Nowy_Click"> Dodaj nowy </Hyperlink> </TextBlock> </StackPanel> </Page> // zawsze ręczne tworzenie: private void Nowy_Click(object sender, RoutedEventArgs e) { PageFunction1 pageFunction = new PageFunction1(); // callback wołany po powrocie z PageFunction pageFunction.Return += Returned; this.NavigationService.Navigate(pageFunction); } private void Returned(object sender, ReturnEventArgs<Dodatek> e) 33/45 // odczyt po powrocie: private void Returned(object sender, ReturnEventArgs<Dodatek> e) { Dodatek dodatek = (Dodatek)e.Result; /* przetwarzamy uzyskane dane... */ } (warto też oczyścić historię, aby nie pozwolić na powrót do page function) // jak strona zwraca wynik: private void lnkOK_Click(object sender, RoutedEventArgs e) { // zwrócenie wartości... OnReturn(new ReturnEventArgs<Dodatek>(/*...*/)); } private void lnkCancel_Click(object sender, ...) { // lub nie... OnReturn(null); } 34/45 <PageFunction ...> <StackPanel> <TextBox .../> <TextBlock HorizontalAlignment="Right"> <Hyperlink Click="lnkOK_Click">Ok</Hyperlink> <Hyperlink Click="lnkCancel_Click">Anuluj</Hyperlink> </TextBlock> </StackPanel> </PageFunction> 35/45 XAML Browser Applications – XBAP Aplikacje oparte na stronach uruchamiane w przeglądarce internetowej: • uruchamiane w oknie przeglądarki • nie wymagają instalacji • mają ograniczone uprawnienia • komputer, na którym aplikacja ma być uruchomiona musi mieć zainstalowany .NET Framework przynajmniej w wersji 3.0 • obsługiwane przeglądarki: Internet Explorer 6+, FireFox 2+ • jest to osobny rodzaj projektu w VS Kluczowe pliki wynikowe: • ApplicationName.exe – skompilowany kod • ApplicationName.exe.manifest – wymagania aplikacji • ApplicationName.xbap – startowy punkt aplikacji; to ten plik będzie otwierany w przeglądarce (zdalnie bądź lokalnie) 36/45 • uwaga: wraz z projektem powstają pliki kluczy ważne do aktualizowania aplikacji: ApplicationName_TemporaryKey.pfx – można też podać własny klucz: Properties w oknie Solution Explorer i zakładka Signing • aby aktualizować aplikację musimy pilnować numeru wersji – Solution Explorer → Properties → Publish → Publish Version Albo łatwiejsza metoda – automatyczne zwiększanie numeru wersji – menu Build → Publish i opcja Automatically Increment Revision with Each Publish • 37/45 Isolated Storage // czy możemy zapisać dane w fizycznym pliku? string filePath = System.IO.Path.Combine(appPath,"dane.txt"); FileIOPermission permission = new FileIOPermission( FileIOPermissionAccess.Write, filePath); // sprawdzanie uprawnień if (CheckPermission(permission)) { // zapis lokalny try { using (FileStream fs = File.Create(filePath)) { /*...zapis danych...*/ } } catch { /*...*/ } } Sprawdzamy możliwość zapisu, by móc uruchamiać aplikację lokalnie (w innych uprawnieniach). 38/45 else { // zapis do isolated storage try { IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); using (IsolatedStorageFileStream fs = new IsolatedStorageFileStream( "dane.txt", FileMode.Create, store)) { /*...zapis danych...*/ } } catch { /*...*/ } } Jest to odpowiednik ciasteczek (niewielka porcja informacji zapisywana u klienta). 39/45 // metoda użyta w powyższym przykładzie: private bool CheckPermission( CodeAccessPermission requestedPermission) { try { // próbujemy uzyskać uprawnienie requestedPermission.Demand(); return true; } catch { return false; } } 40/45 Okna popup – odpowiednik okien dialogowych (Popup dodajemy jako element strony). <Page ...><StackPanel> ... <TextBlock><Hyperlink Click="cmdStart_Click"> Pokaż </Hyperlink></TextBlock> <Label Name="lblName"/> ... <Popup StaysOpen="True" Placement="Center" MaxWidth="200" Name="dialogPopUp"> <Border ...> <StackPanel Margin="5" ...> <TextBlock Margin="10" TextWrapping="Wrap"> Podaj nazwę użytkownika. </TextBlock> <TextBox Name="txtName" Margin="10"/> <StackPanel Orientation="Horizontal" Margin="10"> <Button Click="dialog_cmdOK_Click" Padding="3" Margin="0,0,5,0">OK</Button> <Button Click="dialog_cmdCancel_Click" Padding="3">Anuluj</Button> </StackPanel> </StackPanel> </Border> </Popup> </StackPanel></Page> 41/45 42/45 private void cmdStart_Click(object sender, RoutedEventArgs e) { ShowPopup(true); } private void dialog_cmdOK_Click(object sender, ...) { // pobranie nazwy z popupu do okna głównego lblName.Content = "Podałeś: " + txtName.Text; ShowPopup(false); } private void dialog_cmdCancel_Click(object sender, ...) { ShowPopup(false); } private void ShowPopup(bool show) { this.IsEnabled = !show; this.Background = show ? Brushes.LightGray : null; dialogPopUp.IsOpen = show; } 43/45 44/45 Zagnieżdżanie aplikacji XBAP wewnątrz html • • Aplikacje XBAP mogą być uruchamiane poprzez odwołanie do pliku .xbap w pasku adresu przeglądarki Mogą być również umieszczone jako część strony, np.: <html> <head> <title>Strona zawierająca XBAP</title> </head> <body> <h1>Zwykła zawartość strony HTML.</h1> <iframe src="BrowserApplication.xbap"></iframe> <p>Więcej zwykłej zawartości.</p> </body> </html> 45/45