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

Podobne dokumenty