PwT.N W9
Transkrypt
PwT.N W9
Programowanie w technologii .NET wykład 9 – Dokumenty Kontrolki Label i TextBlock nie nadają się do wyświetlania dużej ilości formatowanego tekstu (np. dokumentów czy pomocy): 1/48 • Dokumenty pozwalają prezentować dużą ilość tekstu w czytelny, wygodny sposób: obsługa kolumn, podziału na strony, dzielenia słów, ... Dokumenty w WPF dzielą się na dwie kategorie: • Fixed documents – dokumenty przeznaczone do wydruku, o ustalonej pozycji całej zawartości, stronicowaniu; są odpowiednikiem plików PDF. Ważne, gdy chcemy wydrukować coś bez zmian. • Flow documents – przeznaczone do oglądania w okienku, ich zawartość układana jest automatycznie – dostosowując się do okna i sposobu wyświetlania; odpowiadają dokumentom HTML. Dokument musi być wyświetlany w kontenerze: • Fixed documents – w kontenerze DocumentViewer • Flow documents – do dyspozycji mamy kontenery FlowDocumentReader, FlowDocumentPageViewer i FlowDocumentScrollViewer Wszystkie te kontenery są tylko do odczytu. Poza tym, mamy do dyspozycji API do tworzenia fixed documents oraz kontrolkę RichTextBox do edycji flow documents. 2/48 Flow Documents • • • W tym typie dokumentów zawartość dostosowuje się do kontenera. Przeznaczone do wyświetlania na ekranie. Jest pozbawiony wielu wad HTMLa. Domyślnie zawartość HTML wypełnia okno – co może utrudniać czytanie, jeśli szerokość okienka (i długość linii) jest duża. Rozwiązaniem jest odgórne ograniczenie szerokości, ale wiąże się to ze stratą miejsca (marginesy). Flow Document dodaje m. in. paginację (podział na strony), formaty wielokolumnowe, dzielenie słów i możliwość zmiany ustawień użytkownika. Do tworzenia dokumentu typu flow służy klasa FlowDocument. Nowy dokument możemy tworzyć jako osobny plik lub też umieścić go w istniejącym oknie, wykorzystując jeden z dostępnych kontenerów (w przykładzie użyjmy FlowDocumentScrollViewer). <Window ...> <FlowDocumentScrollViewer> <FlowDocument> ... </FlowDocument> </FlowDocumentScrollViewer> </Window> 3/48 Flow Elements • • • • • Nie możemy umieścić tekstu bezpośrednio w dokumencie. Flow document tworzony jest z flow elements. Nie dziedziczą one z klas UIElement i FrameworkElement, lecz tworzą własną, odrębną hierarchię klas ContentElement i FrameworkContentElement. Są prostsze, niż elementy poznawane dotąd, ale obsługują podobny zestaw zdarzeń i własności. Podstawowa różnica: nie odpowiadają za renderowanie samych siebie. Zajmuje się tym kontener, który je przechowuje (co pozwala na optymalizację). Domyślnie nie przyjmują też focusa (Focusable ustawione na false). Dwa podstawowe typy Flow Elements: • • Block elements (elementy blokowe) – służą do grupowania innych elementów. Np. paragraf (Paragraph) może zawierać kilka sekcji różnie sformatowanego tekstu. Inline elements (elementy liniowe) – są zagnieżdżane w elementach blokowych (lub innych elementach liniowych). Np. element Run zawiera pewien tekst, który możemy umieścić w paragrafie. Ten model zawartości pozwala na wielokrotne poziomy zagnieżdżenia (np. element Bold w elemencie Underline – model bardzo podobny do HTMLa) 4/48 Elementem najwyższego poziomu musi być element blokowy (np. Paragraph): <Window ...> <FlowDocumentScrollViewer> <FlowDocument> <Paragraph>Hello, world!</Paragraph> </FlowDocument> </FlowDocumentScrollViewer> </Window> 5/48 Nie ma ograniczenia, ilu elementów najwyższego poziomu użyjemy: <Window ...> <FlowDocumentScrollViewer> <FlowDocument> <Paragraph>Hello, world!</Paragraph> <Paragraph>A to jest drugi paragraf.</Paragraph> </FlowDocument> </FlowDocumentScrollViewer> </Window> Pasek przewijania jest dodawany automatycznie, a czcionka jest pobrana z ustawień systemowych, a nie okna zawierającego kontener. Domyślnie FlowDocumentScrollViewer.IsSelectionEnabled jest ustawione na true. 6/48 Formatowanie elementów • • • • • • • • • • Foreground, Background – pędzle wypełniające pierwszy plan i tło elementu FontFamily, FontSize, FontStretch, FontStyle, FontWeight – własności dotyczące kroju pisma ToolTip – podpowiedź wyświetlana po zatrzymaniu się na elemencie Style – styl definiujący wygląd elementu BorderBrush, BorderThickness – obramowanie elementu Margin – odległość między elementem a kontenerem lub elementem sąsiednim. Domyślnie odległość między Block elements (np. dwoma paragrafami) to 18 jednostek (jeśli chcemy to zmniejszyć, należy zmniejszać z obu stron). Padding – odstęp między krawędzią elementu a elementem zagnieżdżonym TextAlignment – wyrównanie w poziomie (Left, Right, Center lub Justify) LineHeight – odległość między liniami zagnieżdżonego tekstu. LineStackingStrategy – określa, w jaki sposób linie są oddzielone, gdy zawierają tekstu o różnych wielkościach czcionki (MaxHeight wybiera najwyższy rozmiar czcionki, BlockLineHeight używa wartości LineHeight dla wszystkich linii) Ponadto: • TextDecorations (udostępniane przez Paragraph i wszystkie elementy inline) – Strikethrough, Overline, Underline lub dowolna ich kombinacja. • Typography – szczegóły dotyczące sposobu renderowania tekstu. 7/48 Elementy blokowe Paragraph i Run Paragraf nie zawiera tekstu, ale kolekcję elementów liniowych – Paragraph.Inlines (może on zatem zawierać nie tylko tekst). Aby umieścić tekst w paragrafie, musi znaleźć się on w elemencie Run: <Paragraph> <Run>A to jest drugi paragraf.</Run> </Paragraph> Jeśli go nie podamy, zostanie utworzony automatycznie, ale należy o tym pamiętać, jeśli chcemy sięgnąć do tego tekstu programistycznie: <Paragraph Name="paragraf">Hello, world!</Paragraph> ((Run)paragraf.Inlines.FirstInline).Text = "Dzień dobry!"; Lepszym pomysłem może być umieszczanie tekstu, który chcemy modyfikować w elemencie Span (aby móc go nazwać i sięgać do niego bezpośrednio). 8/48 Klasa Paragraph zawiera własność TextIndent, która pozwala ustawić rozmiar wcięcia pierwszego wiersza. W odróżnieniu od HTML, w WPF nie ma elementów nagłówków (należy używać odpowiednio sformatowanych paragrafów). List Reprezentuje punktowaną lub numerowana listę – wybrać to można przy pomocy własności MarkerStyle: • Disc • Box • Circle • Square • Decimal (domyślnie zaczyna się od 1, ale można wybrać inny StartingIndex) • LowerLatin (a, b, c). • UpperLatin (A, B, C). • LowerRoman (i, ii, iii, iv). • UpperRoman (I, II, III, IV). • None – bez znacznika MarkerOffset pozwala ustawić odstęp między znacznikiem a elementem listy. 9/48 Wewnątrz listy zagnieżdżamy elementy ListItem, reprezentujące poszczególne podpunkty. Każdy ListItem musi zawierać element blokowy (np. Paragraph). <FlowDocument> <Paragraph>Tematy projektów</Paragraph> <List> <ListItem> <Paragraph>Sklep</Paragraph> </ListItem> <ListItem> <Paragraph>Miejski Dom Kultury</Paragraph> </ListItem> <ListItem> <Paragraph>Lista zadań</Paragraph> </ListItem> <ListItem> <Paragraph>Wizard klas</Paragraph> </ListItem> </List> </FlowDocument> 10/48 Table Przeznaczona do wyświetlania zawartości w postaci tabeli. Jest wzorowana na <table> z HTMLa. Aby stworzyć tabelę, należy: 1. Umieścić element TableRowGroup w elemencie Table. TableRowGroup zawiera grupę wierszy. Pozwala nadać jedno formatowanie kilku wierszom jednocześnie. 2. Umieścić element TableRow w elemencie TableRowGroup. TableRow reprezentuje pojedynczy wiersz. 3. W TableRow umieścić element TableCell dla każdej kolumny wiersza. 4. W TableCell umieścić element blokowy (przeważnie Paragraph), do przechowywania zawartości komórki. 11/48 <FlowDocument> <Paragraph FontSize="16pt">Oddanych zadań:</Paragraph> <Table> <TableRowGroup Paragraph.TextAlignment="Center"> <TableRow FontWeight="Bold"> <TableCell> <Paragraph>Nr</Paragraph> </TableCell> <TableCell> <Paragraph>Temat</Paragraph> </TableCell> <TableCell> <Paragraph>Oddanych</Paragraph> </TableCell> </TableRow> ... </TableRowGroup> </Table> </FlowDocument> 12/48 • • W przeciwieństwie do kontenera Grid, komórki w Table są wypełniane w kolejności (wg pozycji). Domyślnie kolumny rozmieszczane są równomiernie. Można samodzielnie określić rozmiar lub proporcje (ale nie oba naraz, nie możemy łączyć kolumn o ustalonym rozmiarze z proporcjonalnymi): <Table.Columns> <TableColumn Width="*"/> <TableColumn Width="3*"/> <TableColumn Width="*"/> </Table.Columns> • • • • Można również określić ColumnSpan lub/i RowSpan. CellSpacing określa odstęp między komórkami. Każda komórka może być formatowana osobno. Nie ma zbyt dobrego wsparcia dla obramowań: istnieją BorderThickness, BorderBrush (własności TableCell), ale rysują osobną krawędź wokół każdej komórki; BorderThickness i BorderBrush w klasie Table to krawędź wokół całej tabeli. 13/48 Section Sekcja jest analogiczna do elementu <div> z HTMLa. Służy do grupowania elementów blokowych, by móc nadać im jeden styl formatowania. <Section FontFamily="Comic Sans MS" Background="LightSteelBlue"> <Paragraph > ... </Paragraph> <Paragraph > ... </Paragraph> </Section> (Oczywiście można też używać w połączeniu ze stylami.) (działa to dla czcionek, bo są dziedziczone, a dla tła, bo domyślnie w paragrafie jest przezroczyste i widać tło sekcji) 14/48 BlockUIContainer • Pozwala na umieszczenie w dokumencie (w miejscu elementu blokowego) elementów dziedziczących z UIElement. • Pozwala na dodawanie np. przycisków, pól wyboru, a nawet całych paneli czy gridów ze złożoną zawartością. Jedyne ograniczenie: pojedyncze dziecko. • Jest to przydatne, gdy chcemy połączyć dokument z kontrolkami zapewniającymi pewną interakcję z użytkownikiem, np. przyciski w systemie pomocy lub pola tekstowe w formularzu. <Paragraph> Naciśnij, aby uzyskać więcej pomocy. </Paragraph> <BlockUIContainer> <Button HorizontalAlignment="Left" Padding="5"> Pomoc </Button> </BlockUIContainer> 15/48 Elementy liniowe • • • • • • • Run – zawiera zwykły tekst, można ustawić mu formatowanie, choć częściej stosuje się do tego Span; tworzony domyślnie np. w paragrafach Span – (analogiczne do HTMLowego <span>) opakowuje dowolną liczbę elementów inline, zazwyczaj używany do formatowania fragmentu tekstu (w tym celu opakowujemy w niego tekstu lub np. element Run); przydatne do nazwania fragmentu dokumentu (aby móc odwołać się do niego w kodzie) Bold, Italic, and Underline – umożliwiają nałożenie odpowiedniego formatowania na tekst (zazwyczaj lepiej jest jednak opakować tekst w Span i ustawić odpowiedni styl) Hyperlink – reprezentuje odnośnik (podnosi zdarzenie Click) LineBreak – dodaje złamanie linii InlineUIContainer – pozwala na umieszczanie elementów dziedziczących po UIElement w miejscu elementów inline. Floater, Figure – umożliwia wstawienie specjalnie traktowanego obszaru do wyświetlania obrazów, wyróżnionych informacji i innej zawartości 16/48 Białe znaki Na początku lub końcu elementu są ignorowane, ale przed elementem inline – nie. Dlatego to działa źle: <Paragraph>Do końca grudnia<Bold> należy </Bold>przygotować prototyp interfejsu.</Paragraph> a to dobrze: <Paragraph>Do końca grudnia <Bold>należy</Bold> przygotować prototyp interfejsu.</Paragraph> Można też używać: <Run xml:space="preserve"> a b c </Run> 17/48 Floater Element, który pozwala umieścić pewną zawartość obok głównego dokumentu: <Paragraph> Przykładowy tekst. <Floater Style="{StaticResource Cytat}"> <Paragraph>...cytat...</Paragraph> </Floater> Dalszy ciąg przykładowego tekstu.. </Paragraph> <Paragraph> Kolejny akapit. </Paragraph> 18/48 Styl użyty w przykładzie: <Style x:Key="Cytat"> <Setter Property="Paragraph.FontSize" Value="24"/> <Setter Property="Paragraph.FontStyle" Value="Italic"/> <Setter Property="Paragraph.Foreground" Value="SteelBlue"/> <Setter Property="Paragraph.Padding" Value="5"/> <Setter Property="Paragraph.Margin" Value="15,10,5,10"/> </Style> Możemy ograniczyć zajmowane przez niego miejsce i ręcznie określić położenie: <Floater Style="{StaticResource Cytat}" Width="200" HorizontalAlignment="Left"> <Paragraph>...</Paragraph> </Floater> Aby dostosować wygląd, możemy też ustawić własności Background, BorderBrush, BorderThickness, Margin i Padding (pozostałe elementy inline tego nie mają, tylko Floater i Figure). 19/48 Możemy go użyć do wyświetlenia obrazka (element Image wewnątrz BlockUIContainer lub InlineUIContainer). Niestety, Floater nie dostosowuje szerokości do zawartości. <Paragraph>...</Paragraph> <Paragraph> ... <Floater Width="100" Padding="5,0,5,0" HorizontalAlignment="Right"> <BlockUIContainer> <Image Source="Masqueofthereddeath.jpg"></Image> </BlockUIContainer> </Floater> ... </Paragraph> 20/48 Figure Podobna do Floatera, ale daje więcej kontroli nad położeniem. uwaga: wiele poniższych własności (np. HorizontalAnchor, VerticalOffset, HorizontalOffset) nie jest wspieranych przez kontener FlowDocumentScrollViewer, dlatego w przykładzie użyjemy FlowDocumentReader. • • • • • • Width – szerokość; poza stała wartością, jak w Floaterze, możemy podać wartość proporcjonalną (np. „0.25 content”, „2 Column”). Height – wysokość – Floater dostosowywał ją do zawartości, tu możemy określić ją ręcznie HorizontalAnchor – zamiast HorizontalAlignment z klasy Floatera; pozwala orientować również względem strony lub kolumny (poza np. ContentCenter mamy PageCenter i ColumnCenter). VerticalAnchor – pozwala wyrównać do bieżącej linii, kolumny, strony HorizontalOffset, VerticalOffset – pozwalają na przesunięcie figury względem miejsca zakotwiczenia WrapDirection – określa, czy tekst może opływać figurę z jednej czy obu stron 21/48 <FlowDocumentReader> <FlowDocument> <Paragraph>...</Paragraph> <Paragraph>... <Figure Width="0.5 column" Padding="5,0,5,0" HorizontalAnchor="ColumnLeft"> <BlockUIContainer> <Image Source="Masqueofthereddeath.jpg" /> </BlockUIContainer> </Figure> ...</Paragraph> </FlowDocument> </FlowDocumentReader> 22/48 Interakcja z elementami (uwaga: tekst musimy ręcznie umieszczać w elementach Run) // Pierwszy fragment zdania Run runFirst = new Run(); runFirst.Text = "Oto "; // Wytłuszczony tekst Bold bold = new Bold(); Run runBold = new Run(); runBold.Text = "dynamicznie wygenerowany"; bold.Inlines.Add(runBold); // Koniec zdania Run runLast = new Run(); runLast.Text = " dokument."; // Dodawanie fragmentów do paragrafu Paragraph paragraph = new Paragraph(); paragraph.Inlines.Add(runFirst); paragraph.Inlines.Add(bold); paragraph.Inlines.Add(runLast); // Utworzenie dokumentu i dodanie paragrafu FlowDocument document = new FlowDocument(); document.Blocks.Add(paragraph); // Wyświetlenie dokumentu docViewer.Document = document; 23/48 A czego użyć, aby przeglądać (i modyfikować) gotowy dokument? • kolekcja FlowDocument.Blocks zwraca elementy blokowe; FlowDocument.Blocks.FirstBlock zwraca pierwszy, a FlowDocument.Blocks.LastBlock – ostatni element kolekcji • w celu przeglądania elementów blokowych: Block.NextBlock (lub Block.PreviousBlock). Ponadto kolekcja Block.SiblingBlocks pozwala przeglądać elementy tego samego poziomu co aktualny • wiele elementów blokowych zawiera inne elementy (np. List zawiera elementy ListItem, Section zawiera kolekcję Blocks, Paragraph zawiera Inlines. Jeśli celem jest wymienienie fragmentu tekstu w dokumencie, najlepiej jest wydzielić ten fragment w postaci elementu Span. Możemy rozpoznawać je po nazwie, albo np. po Tagu (jeśli szukamy elementu, który występuje więcej niż raz). 24/48 Justowanie • • Domyślnie dokumenty są wyjustowane (TextAlignment – Justify). Ustawienie FlowDocument.IsOptimalParagraphEnabled na true włącza opcję optymalnego dzielenia wierszy (podziały linii są ustawiane tak, by jak najlepiej rozłożyć odstępy w dokumencie) Ustawiając FlowDocument.IsHyphenationEnabled na true włączamy dzielenie słów (do ustalenia podziału wykorzystywany jest słownik). 25/48 Kontenery Flow Document Są tylko do odczytu. Wszystkie obsługują drukowanie i zoom. • FlowDocumentScrollViewer – cały dokument z paskiem przewijania. Nie obsługuje paginacji, ani wielu kolumn. • FlowDocumentPageViewer – dzieli dokument na wiele stron. • FlowDocumentReader – łączy cechy obu powyższych: użytkownik wybiera który rodzaj widoku go interesuje. Zmiana kontenera jest prosta: <FlowDocumentPageViewer> <FlowDocument> <Paragraph>Jeden akapit tekstu.</Paragraph> </FlowDocument> </FlowDocumentPageViewer> Jak prosty kontener może też służyć TextBlock – może przechowywać elementy liniowe (inline), oferuje zawijanie (TextWrapping) i ucinanie (TextTrimming) tekstu. To ostatnie może przyjmować wartość: None, WordEllipse (niemieszczące się słowa zastępuje „...”), CharacterEllipse (niemieszczące się znaki zastępuje „...”). 26/48 Zoom • • Ustawiany przy pomocy własności Zoom kontenera. Jest to wartość w procentach. Można ustawiać ją ręcznie, używać metod IncreaseZoom() i DecreaseZoom() (zwiększają lub zmniejszają o ZoomIncrement) lub pozwolić wybrać użytkownikowi (FlowDocumentScrollViewer daje do dyspozycji suwak powiększenia): <FlowDocumentScrollViewer MinZoom="50" MaxZoom="1000" Zoom="100" ZoomIncrement="5" IsToolBarVisible="True"> ... </FlowDocumentScrollViewer> Elementy Floater lub Figure o ustalonym rozmiarze też są powiększane proporcjonalnie. 27/48 Strony i kolumny • • Kontener FlowDocumentPageViewer może dzielić długi dokument na strony – zastępuje to scrollowanie. A jeśli okno będzie dość szerokie, tekst zostanie podzielony na kolumny. Uwaga: Floater domyślnie przyjmuje szerokość całej kolumny, Można go zmniejszyć, ale nie powiększyć. Z kolei Figure może zajmować kilka kolumn. • FlowDocumentReader również obsługuje podział na strony: można wybrać widok jednej strony (jak we FlowDocumentPageViewer) lub dwóch stron obok siebie. 28/48 Podział na strony i kolumny możemy kontrolować w klasie FlowDocument lub Paragraph. FlowDocument: • ColumnWidth – preferowany rozmiar kolumny (działa jako rozmiar minimalny) • IsColumnWidthFlexible – decyduje, czy kontener może dopasować rozmiar kolumny (jeśli false, użyta jest dokładna wartość ColumnWidth, jeśli true – ColumnWidth to rozmiar minimalny). Domyślnie: true; • ColumnGap – odstęp między kolumnami • ColumnRuleWidth i ColumnRuleBrush – szerokość i wypełnienie pionowej kreski między kolumnami Paragraph: • KeepTogether – czy paragraf może być podzielony na strony (gdy true: cały paragraf znajdzie się na jednej stronie) • KeepWithNext – czy można oddzielić końcem strony ten paragraf od następnego (przeważnie: do nagłówków) • MinOrphanLines – jak paragraf jest dzielony na strony; minimalna liczba linii, jaka ma pozostać na poprzedniej stronie (gdy się nie mieszczą – przenoszony jest cały paragraf) • MinWidowLines – minimalna liczba linii, jaka ma być przeniesiona na następną stronę 29/48 Odczyt dokumentu z pliku • Do ładowania zawartości do kontenera służy klasa XamlReader (z System.Windows.Markup). W poniższym kodzie pominięto obsługę błędów: using (FileStream fs = File.Open(documentFile, FileMode.Open)) { FlowDocument document = XamlReader.Load(fs) as FlowDocument; if (document == null) { MessageBox.Show("Nie udało się odczytać dokumentu."); } else { flowContainer.Document = document; } } • Podobnie łatwo zapisać dokument z kontenera: using (FileStream fs = File.Open(documentFile, FileMode.Create)) { XamlWriter.Save(flowContainer.Document, fs); } 30/48 Drukowanie • • Wystarczy wywołać metodę Print() z kontenera. Wyświetla on okno drukowania z wyborem drukarki i ustawień. Drukowanie działa przez system poleceń, zatem wystarczy dodać przycisk (lub skorzystać z Ctrl+P): <Button Command="ApplicationCommands.Print" CommandTarget="docViewer">Print</Button> W podobny sposób obsługiwane jest np. wyszukiwanie i powiększenie. 31/48 Edycja Flow Document Kontrolka RichTextBox umożliwia edycję flow documents. Ładowanie pliku – można zadeklarować dokument od razu wewnątrz RichTextBox: <RichTextBox> <FlowDocument> <Paragraph>Dokument do edycji.</Paragraph> </FlowDocument> </RichTextBox> Można też wczytać go przy pomocy XamlReader.Load(). Jeśli chcemy czytać inne formaty, pomocna jest klasa System.Windows.Documents.TextRange. Dozwolone typy: • DataFormat.Xaml – zawartość XAML • DataFormats.Rtf – rich text (*.rtf) • DataFormats.XamlPackage – zawartość XAML z osadzonymi obrazami • DataFormats.Text – zwykły tekst 32/48 OpenFileDialog openFile = new OpenFileDialog(); openFile.Filter = "RichText Files (*.rtf)|*.rtf|All Files (*.*)| *.*"; if (openFile.ShowDialog() == true) { TextRange documentTextRange = new TextRange( richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd); using (FileStream fs = File.Open(openFile.FileName, FileMode.Open)) { // po rozszerzeniu rozpoznajemy typ pliku: if (Path.GetExtension(openFile.FileName).ToLower() == ".rtf") { documentTextRange.Load(fs, DataFormats.Rtf); } else { documentTextRange.Load(fs, DataFormats.Xaml); } } } (najpierw tworzymy TextRange obejmujący zawartość dokumentu, którą chcemy wymienić) 33/48 Zapis pliku: SaveFileDialog saveFile = new SaveFileDialog(); saveFile.Filter = "XAML Files (*.xaml)|*.xaml|RichText Files (*.rtf)|*.rtf|All Files (*.*)|*.*"; if (saveFile.ShowDialog() == true) { // cały zakres dokumentu: TextRange documentTextRange = new TextRange( richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd); // Jeśli plik istnieje, zostanie nadpisany using (FileStream fs = File.Create(saveFile.FileName)) { if (Path.GetExtension(saveFile.FileName).ToLower() == ".rtf") { documentTextRange.Save(fs, DataFormats.Rtf); } else { documentTextRange.Save(fs, DataFormats.Xaml); } } } 34/48 Elementem najwyższego poziomu będzie Section (tylko takie pliki XAML są akceptowane). Formatowanie tekstu: Kontrolka RichTextBox obsługuje polecenia związane z edycją i formatowaniem dokumentów, dlatego aby obsługiwać np. zmiany formatu, wystarczy dodać przyciski na pasku narzędzi. 35/48 Fixed Documents • • • • • Fixed documents są mnie elastyczne niż Flow documents. Służą jako dokument gotowy do wydruku, który można przenosić i drukować w niezmienny formacie (na wzór PDF). Wykorzystują pliki XPS (XML Paper Specification). Sterowniki drukarki w Viście i Windows 7 pozwalają na drukowanie dowolnych dokumentów do formatu XPS. Format XPF to archiwum zip zawierające dokument w postaci XML, wraz z obrazkami, osadzonymi czcionkami, etc. Dokument XPS możemy wyświetlić w kontenerze DocumentViewer. XpsDocument doc = new XpsDocument("filename.xps", FileAccess.Read); docViewer.Document = doc.GetFixedDocumentSequence(); doc.Close(); W dokumentach XPS układ i rozmiar strony są ustalone, dokument przeznaczony jest od razu do wydruku. 36/48 Annotations Adnotacje. Pozwalają dodać komentarz lub wyróżnić fragment tekstu w dokumencie flow lub fixed. Dwa rodzaje: • Highlighting – wybranie części tekstu i wyróżnienie go kolorem (zakreślacz). • Sticky notes – dołączenie do fragmentu tekstu pływającej ramki z informacją. 37/48 • • Wszystkie cztery rodzaje kontenerów obsługują adnotacje. Przedtem jednak musimy wykonać dwa kroki: włączyć możliwość stosowania adnotacji i dodać kontrolki, przy pomocy których użytkownik będzie je dodawał. Klasy adnotacji • AnnotationService – klasa zajmująca się usługą adnotacji (aby je udostępnić, należy stworzyć jej obiekt) • AnnotationStore – zajmuje się składowanie dodanych adnotacji. Udostępnia metody pozwalające na dodawanie i usuwanie pojedynczych adnotacji oraz zdarzenia podnoszone w momencie ich dodawania i zmiany. Jest to klasa abstrakcyjna, dziedziczy z niej klasa XmlStreamStore, która składuje adnotacje w formacie XML. • AnnotationHelper – udostępnia kilka statycznych metod do posługiwania się adnotacjami. 38/48 Włączanie obsługi // A stream for storing annotation. private MemoryStream annotationStream; // The service that manages annotations. private AnnotationService service; protected void window_Loaded(object sender, RoutedEventArgs e) { // Create the AnnotationService for your document container. service = new AnnotationService(docReader); // Create the annotation storage. annotationStream = new MemoryStream(); AnnotationStore store = new XmlStreamStore(annotationStream); // Enable annotations. service.Enable(store); } • Zamiast MemoryStream (który zniknie po zakończeniu aplikacji) możemy użyć FileStream, co spowoduje zapisanie utworzonych adnotacji do pliku. • Jeśli już były jakieś adnotacje w strumieniu, gdy wywołano AnnotationService.Enable(), wówczas od razu się one pojawią. 39/48 protected void window_Unloaded(object sender, RoutedEventArgs e) { if (service != null && service.IsEnabled) { // Flush annotations to stream. service.Store.Flush(); // Disable annotations. service.Disable(); annotationStream.Close(); } } Uwaga: Każdy kontener dokumentu może mieć jedną instancję AnnotationService. Każdy dokument powinien mieć własną instancję AnnotationStore (przy otwieraniu nowego dokumentu, należy wyłączyć usługę, zamknąć strumień, stworzyć nowy Store i ponownie uruchomić usługę). 40/48 Dodawanie adnotacji (dwa sposoby) Metody klasy AnnotationHelper: • CreateTextStickyNoteForSelection() • CreateInkStickyNoteForSelection() • DeleteTextStickyNotesForSelection() • DeleteInkStickyNotesForSelection() • CreateHighlightsForSelection() • ClearHighlightsForSelection() Wszystkie działają dla aktualnego zaznaczenia. Lub odpowiadające im polecenia klasy z AnnotationService. 41/48 <Window ... xmlns:annot= "clr-namespace:System.Windows.Annotations;assembly=PresentationFramework" ...> ... <ToolBar DockPanel.Dock="Top"> <Button Command="annot:AnnotationService.CreateTextStickyNoteCommand" >Text Note</Button> </ToolBar> • • (Naciśnięcie przycisku spowoduje dodanie okienka, w którym użytkownik może pisać tekst adnotacji.) (uwaga: gdy Button jest na toolbarze, możemy pominąć CommandTarget, gdyż polecenie samo znajdzie swój cel.) 42/48 Możemy dostarczyć dodatkowych informacji dodawanej adnotacji: np. nazwę użytkownika, który ją dodał. <Button Command="annot:AnnotationService.CreateTextStickyNoteCommand" CommandParameter="{StaticResource AuthorName}"> Text Note </Button> Z nazwą pobraną z zasobów: <Window.Resources> <sys:String x:Key="AuthorName">[Anonymous]</sys:String> </Window.Resources> Lub wczytaną z ustawień systemowych: (System.Security.Principal.WindowsIdentity) WindowsIdentity identity = WindowsIdentity.GetCurrent(); this.Resources["AuthorName"] = identity.Name; 43/48 Pozostałe: <Button Command="annot:AnnotationService.CreateInkStickyNoteCommand" CommandParameter="{StaticResource AuthorName}"> Ink Note </Button> <Button Command="annot:AnnotationService.DeleteStickyNotesCommand"> Delete Note(s) </Button> Dodawanie wyróżnienia wymaga podania koloru. Jest on nakładany na tekst, a zatem powinien być półprzeźroczysty (dwie pierwsze cyfry koloru, to alpha). <Button Background="Yellow" Width="15" Height="15" Margin="2,0" Command="annot:AnnotationService.CreateHighlightCommand"> <Button.CommandParameter> <SolidColorBrush Color="#54FFFF00"></SolidColorBrush> </Button.CommandParameter> </Button> 44/48 <Button Background="LimeGreen" Width="15" Height="15" Margin="2,0" Command="annot:AnnotationService.CreateHighlightCommand"> <Button.CommandParameter> <SolidColorBrush Color="#5432CD32"></SolidColorBrush> </Button.CommandParameter> </Button> I usunięcie: <Button Command="annot:AnnotationService.ClearHighlightsCommand"> Clear Highlights </Button> Uwaga: adnotacje są drukowane, zatem przed rozpoczęciem drukowania dobrze jest wyłączyć usługę adnotacji. 45/48 Przeglądanie adnotacji Klasa AnnotationStore pozwala również przeglądać stworzone adnotacje. IList<Annotation> annotations = service.Store.GetAnnotations(); foreach (Annotation annotation in annotations) { ... } Własności klasy Annotation • Id – globalny identyfikator • AnnotationType – rodzaj adnotacji • Anchors – wskazanie na adnotowany tekst (można też skorzystać z metody GetAnchorInfo() klasy AnnotationHelper). • Cargos – wskazanie na dane użytkownika dołączone do adnotacji (np. tekst notki) (niskopoziomowe) • Authors – kolekcja stringów identyfikujących autorów adnotacji • CreationTime – data i czas utworzenia • LastModificationTime – data i czas ostatniej modyfikacji Własności adnotacji są tylko do odczytu, nie ma łatwego sposobu programistycznej manipulacji adnotacjami. 46/48 Reakcja na zmianę adnotacji Cztery zdarzenia: • AnchorChanged (gdy adnotacja jest przenoszona) • AuthorChanged (gdy zmieniana jest informacja o autorze) • CargoChanged (gdy zmieniane są dane, np. tekst) • StoreContentChanged (gdy jest tworzona, usuwana, etc.) Dostosowanie wyglądu notatek Można ustawić styl: <Style TargetType="{x:Type StickyNoteControl}"> <Setter Property="Background" Value="LightGoldenrodYellow"/> </Style> Więcej możliwości dadzą nam później szablony kontrolek. 47/48 Zachowanie adnotacji w Fixed Document • W wypadku flow documents adnotacje muszą być składowane w osobnym pliku. • W wypadku fixed documents możemy przechowywać adnotacje w pliku XPS (a nawet kilka niezależnych od siebie zbiorów adnotacji). Uri annotationUri = PackUriHelper.CreatePartUri( new Uri("AnnotationStream", UriKind.Relative)); Package package = PackageStore.GetPackage(doc.Uri); PackagePart annotationPart = null; if (package.PartExists(annotationUri)) annotationPart = package.GetPart(annotationUri); else annotationPart = package.CreatePart(annotationUri, "Annotations/Stream"); AnnotationStore store = new XmlStreamStore(annotationPart.GetStream()); service = new AnnotationService(docViewer); service.Enable(store); 48/48