Upload
doancong
View
223
Download
0
Embed Size (px)
Citation preview
Programowanie w technologii .NETwykł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 znakiNa 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 zdaniaRun runFirst = new Run();runFirst.Text = "Oto ";// Wytłuszczony tekstBold bold = new Bold();Run runBold = new Run();runBold.Text = "dynamicznie wygenerowany";bold.Inlines.Add(runBold);// Koniec zdaniaRun runLast = new Run();runLast.Text = " dokument.";// Dodawanie fragmentów do paragrafuParagraph paragraph = new Paragraph();paragraph.Inlines.Add(runFirst);paragraph.Inlines.Add(bold);paragraph.Inlines.Add(runLast);// Utworzenie dokumentu i dodanie paragrafuFlowDocument document = new FlowDocument();document.Blocks.Add(paragraph);// Wyświetlenie dokumentudocViewer.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 DocumentSą 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