| File: MS\Internal\Documents\IFlowDocumentViewer.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Windows; // Rect, Point using System.Windows.Documents; // ITextSelection using System.Windows.Controls; // FlowDocumentScrollViewer using System.Windows.Threading; // Dispatcher using MS.Internal.KnownBoxes; // BooleanBoxes namespace MS.Internal.Documents { /// <summary> /// An IFlowDocumentViewer is used for FlowDocumentReader in order to simplify its /// view switching logic. /// </summary> internal interface IFlowDocumentViewer { /// <summary> /// Navigate to the previous page. /// </summary> void PreviousPage(); /// <summary> /// Navigate to the next page. /// </summary> void NextPage(); /// <summary> /// Navigate to the first page. /// </summary> void FirstPage(); /// <summary> /// Navigate to the last page. /// </summary> void LastPage(); /// <summary> /// Print the document. /// </summary> void Print(); /// <summary> /// Cancel printing of the document. /// </summary> void CancelPrint(); /// <summary> /// Show find result. /// </summary> void ShowFindResult(ITextRange findResult); /// <summary> /// Whether can navigate to specific page number. /// </summary> /// <param name="pageNumber">Requested page number.</param> /// <returns>Whether navigation is possible or not.</returns> bool CanGoToPage(int pageNumber); /// <summary> /// Navigate to requested page number. /// </summary> /// <param name="pageNumber">Requested page number.</param> void GoToPage(int pageNumber); /// <summary> /// Associate the viewer with new document. /// </summary> /// <param name="document">New document.</param> void SetDocument(FlowDocument document); /// <summary> /// The content position of the document showed in the control. /// </summary> ContentPosition ContentPosition { get; set; } /// <summary> /// The Selection of TextEditor /// </summary> ITextSelection TextSelection { get; set; } /// <summary> /// Whether can navigate to the previous page. /// </summary> bool CanGoToPreviousPage { get; } /// <summary> /// Whether can navigate to the next page. /// </summary> bool CanGoToNextPage { get; } /// <summary> /// The one-based page number of the page being displayed. /// If there is no content, this value will be 0. /// </summary> int PageNumber { get; } /// <summary> /// The number of pages currently available for viewing. /// </summary> int PageCount { get; } /// <summary> /// Fired when the current page number has been changed. /// </summary> event EventHandler PageNumberChanged; /// <summary> /// Fired when the page count has been changed. /// </summary> event EventHandler PageCountChanged; /// <summary> /// Fired when the print has been started. /// </summary> event EventHandler PrintStarted; /// <summary> /// Fired when the print has been completed. /// </summary> event EventHandler PrintCompleted; } /// <summary> /// This class is an internal subclass of the FlowDocumentScrollViewer that provides /// IFlowDocumentViewer interface used by FlowDocumentReader. /// </summary> internal class ReaderScrollViewer : FlowDocumentScrollViewer, IFlowDocumentViewer { //------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// <summary> /// Called when print has been completed. /// </summary> protected override void OnPrintCompleted() { base.OnPrintCompleted(); if (_printCompleted != null) { _printCompleted(this, EventArgs.Empty); } } /// <summary> /// Handler for the Print command. /// </summary> protected override void OnPrintCommand() { base.OnPrintCommand(); if (_printStarted != null && IsPrinting) { _printStarted(this, EventArgs.Empty); } } /// <summary> /// Notification that a specified property has been changed. /// </summary> protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); // PageCount and PageNumber are dependent on Document property value. // Hence need to raise changed events when Document property is changing. if (e.Property == DocumentProperty) { if (_pageNumberChanged != null) { _pageNumberChanged(this, EventArgs.Empty); } if (_pageCountChanged != null) { _pageCountChanged(this, EventArgs.Empty); } } } #endregion Protected Methods //------------------------------------------------------------------- // // Private Methods // //------------------------------------------------------------------- #region Private Methods /// <summary> /// Returns whether TextSelection is valid for document - As this is flow document, we require the text containers to be equal /// to allow it as a valid selection. /// </summary> private bool IsValidTextSelectionForDocument(ITextSelection textSelection, FlowDocument flowDocument) { if (textSelection.Start != null && textSelection.Start.TextContainer == flowDocument.StructuralCache.TextContainer) { return true; } return false; } /// <summary> /// Set TextSelection. /// </summary> private object SetTextSelection(object arg) { ITextSelection newTextSelection = arg as ITextSelection; if (newTextSelection != null && Document != null && IsValidTextSelectionForDocument(newTextSelection, Document)) { ITextSelection textSelection = Document.StructuralCache.TextContainer.TextSelection; if (textSelection != null) { textSelection.SetCaretToPosition(newTextSelection.AnchorPosition, newTextSelection.MovingPosition.LogicalDirection, true, true); textSelection.ExtendToPosition(newTextSelection.MovingPosition); } } return null; } #endregion Private Methods //------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------- #region Private Fields private EventHandler _pageNumberChanged; private EventHandler _pageCountChanged; private EventHandler _printCompleted; private EventHandler _printStarted; #endregion Private Fields //------------------------------------------------------------------- // // IFlowDocumentViewer Members // //------------------------------------------------------------------- #region IFlowDocumentViewer Members /// <summary> /// <see cref="IFlowDocumentViewer.PreviousPage"/> /// </summary> void IFlowDocumentViewer.PreviousPage() { ScrollViewer?.PageUp(); } /// <summary> /// <see cref="IFlowDocumentViewer.NextPage"/> /// </summary> void IFlowDocumentViewer.NextPage() { ScrollViewer?.PageDown(); } /// <summary> /// <see cref="IFlowDocumentViewer.FirstPage"/> /// </summary> void IFlowDocumentViewer.FirstPage() { ScrollViewer?.ScrollToHome(); } /// <summary> /// <see cref="IFlowDocumentViewer.LastPage"/> /// </summary> void IFlowDocumentViewer.LastPage() { ScrollViewer?.ScrollToEnd(); } /// <summary> /// <see cref="IFlowDocumentViewer.Print"/> /// </summary> void IFlowDocumentViewer.Print() { OnPrintCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.CancelPrint"/> /// </summary> void IFlowDocumentViewer.CancelPrint() { OnCancelPrintCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.ShowFindResult"/> /// </summary> void IFlowDocumentViewer.ShowFindResult(ITextRange findResult) { // If we found something, TextEditor will bring the selection into view. // It is possible, because RenderScope is inside ScrollViewer. } /// <summary> /// <see cref="IFlowDocumentViewer.CanGoToPage"/> /// </summary> bool IFlowDocumentViewer.CanGoToPage(int pageNumber) { // FlowDocumentScrollViewer is always a 1 page document, so its page number will always be 1 return (pageNumber == 1); } /// <summary> /// <see cref="IFlowDocumentViewer.GoToPage"/> /// </summary> void IFlowDocumentViewer.GoToPage(int pageNumber) { if (pageNumber == 1 && ScrollViewer != null) { ScrollViewer.ScrollToHome(); } } /// <summary> /// <see cref="IFlowDocumentViewer.SetDocument"/> /// </summary> void IFlowDocumentViewer.SetDocument(FlowDocument document) { Document = document; } /// <summary> /// <see cref="IFlowDocumentViewer.ContentPosition"/> /// </summary> ContentPosition IFlowDocumentViewer.ContentPosition { get { return this.ContentPosition; } set { if (value != null && Document != null) { // This need be called because the UI may not be ready when Contentposition is set. Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(BringContentPositionIntoView), value); } } } /// <summary> /// <see cref="IFlowDocumentViewer.TextSelection"/> /// </summary> ITextSelection IFlowDocumentViewer.TextSelection { get { return this.Selection; } set { if (value != null && Document != null) { // This need be called because the UI may not be ready when Contentposition is set. Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(SetTextSelection), value); } } } /// <summary> /// <see cref="IFlowDocumentViewer.CanGoToPreviousPage"/> /// </summary> bool IFlowDocumentViewer.CanGoToPreviousPage { get { return false; } } /// <summary> /// <see cref="IFlowDocumentViewer.CanGoToNextPage"/> /// </summary> bool IFlowDocumentViewer.CanGoToNextPage { get { return false; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageNumber"/> /// </summary> int IFlowDocumentViewer.PageNumber { get { return (Document != null) ? 1 : 0; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageCount"/> /// </summary> int IFlowDocumentViewer.PageCount { get { return (Document != null) ? 1 : 0; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageNumberChanged"/> /// </summary> event EventHandler IFlowDocumentViewer.PageNumberChanged { add { _pageNumberChanged += value; } remove { _pageNumberChanged -= value; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageCountChanged"/> /// </summary> event EventHandler IFlowDocumentViewer.PageCountChanged { add { _pageCountChanged += value; } remove { _pageCountChanged -= value; } } /// <summary> /// <see cref="IFlowDocumentViewer.PrintStarted"/> /// </summary> event EventHandler IFlowDocumentViewer.PrintStarted { add { _printStarted += value; } remove { _printStarted -= value; } } /// <summary> /// <see cref="IFlowDocumentViewer.PrintCompleted"/> /// </summary> event EventHandler IFlowDocumentViewer.PrintCompleted { add { _printCompleted += value; } remove { _printCompleted -= value; } } #endregion IFlowDocumentViewer Members } /// <summary> /// This class is an internal subclass of the FlowDocumentPageViewer that provides /// IFlowDocumentViewer interface used by FlowDocumentReader. /// </summary> internal class ReaderPageViewer : FlowDocumentPageViewer, IFlowDocumentViewer { //------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// <summary> /// Called when print has been completed. /// </summary> protected override void OnPrintCompleted() { base.OnPrintCompleted(); if (_printCompleted != null) { _printCompleted(this, EventArgs.Empty); } } /// <summary> /// Handler for the Print command. /// </summary> protected override void OnPrintCommand() { base.OnPrintCommand(); if (_printStarted != null && IsPrinting) { _printStarted(this, EventArgs.Empty); } } /// <summary> /// Notification that a specified property has been changed. /// </summary> protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); // Reader control depends on the actual value of PageCount, PageNumber // CanGoToPreviousPage and CanGoToNextPage. So when values of those // properties are changing, schedule async update of the viewer. if (e.Property == PageCountProperty || e.Property == MasterPageNumberProperty || e.Property == CanGoToPreviousPageProperty || e.Property == CanGoToNextPageProperty) { // If there is already pending update request, do nothng. Otherwise schedule a dispatcher task. if (!_raisePageNumberChanged && !_raisePageCountChanged) { Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(RaisePropertyChangedAsync), null); } // Treat CanGoToPage properties as PageNumber. Becasuse PageNumber change affects // those properties, the reader control will update them. if (e.Property == PageCountProperty) { _raisePageCountChanged = true; CoerceValue(CanGoToNextPageProperty); } else if (e.Property == MasterPageNumberProperty) { _raisePageNumberChanged = true; CoerceValue(CanGoToNextPageProperty); } else { _raisePageNumberChanged = true; } } } #endregion Protected Methods //------------------------------------------------------------------- // // Private Methods // //------------------------------------------------------------------- #region Private Methods /// <summary> /// Set TextSelection. /// </summary> private object SetTextSelection(object arg) { ITextSelection newTextSelection = arg as ITextSelection; FlowDocument flowDocument = Document as FlowDocument; if (newTextSelection != null && flowDocument != null && newTextSelection.AnchorPosition != null && newTextSelection.AnchorPosition.TextContainer == flowDocument.StructuralCache.TextContainer) { ITextSelection textSelection = flowDocument.StructuralCache.TextContainer.TextSelection; if (textSelection != null) { textSelection.SetCaretToPosition(newTextSelection.AnchorPosition, newTextSelection.MovingPosition.LogicalDirection, true, true); textSelection.ExtendToPosition(newTextSelection.MovingPosition); } } return null; } /// <summary> /// Asynchronously notify about property changes. /// </summary> private object RaisePropertyChangedAsync(object arg) { // PageCount and PageNumber are dependent on associated DP values. // Hence need to raise changed events when those DPs are changing. if (_raisePageCountChanged) { if (_pageCountChanged != null) { _pageCountChanged(this, EventArgs.Empty); } _raisePageCountChanged = false; } if (_raisePageNumberChanged) { if (_pageNumberChanged != null) { _pageNumberChanged(this, EventArgs.Empty); } _raisePageNumberChanged = false; } return null; } #endregion Private Methods //------------------------------------------------------------------- // // Private Fields // //------------------------------------------------------------------- #region Private Fields private EventHandler _pageNumberChanged; private EventHandler _pageCountChanged; private EventHandler _printCompleted; private EventHandler _printStarted; private bool _raisePageNumberChanged; private bool _raisePageCountChanged; #endregion Private Fields //------------------------------------------------------------------- // // IFlowDocumentViewer Members // //------------------------------------------------------------------- #region IFlowDocumentViewer Members /// <summary> /// <see cref="IFlowDocumentViewer.PreviousPage"/> /// </summary> void IFlowDocumentViewer.PreviousPage() { OnPreviousPageCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.NextPage"/> /// </summary> void IFlowDocumentViewer.NextPage() { OnNextPageCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.FirstPage"/> /// </summary> void IFlowDocumentViewer.FirstPage() { OnFirstPageCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.LastPage"/> /// </summary> void IFlowDocumentViewer.LastPage() { OnLastPageCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.Print"/> /// </summary> void IFlowDocumentViewer.Print() { OnPrintCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.CancelPrint"/> /// </summary> void IFlowDocumentViewer.CancelPrint() { OnCancelPrintCommand(); } /// <summary> /// <see cref="IFlowDocumentViewer.ShowFindResult"/> /// </summary> void IFlowDocumentViewer.ShowFindResult(ITextRange findResult) { if (findResult.Start is ContentPosition) { BringContentPositionIntoView((ContentPosition)findResult.Start); } } /// <summary> /// <see cref="IFlowDocumentViewer.CanGoToPage"/> /// </summary> bool IFlowDocumentViewer.CanGoToPage(int pageNumber) { return CanGoToPage(pageNumber); } /// <summary> /// <see cref="IFlowDocumentViewer.GoToPage"/> /// </summary> void IFlowDocumentViewer.GoToPage(int pageNumber) { OnGoToPageCommand(pageNumber); } /// <summary> /// <see cref="IFlowDocumentViewer.SetDocument"/> /// </summary> void IFlowDocumentViewer.SetDocument(FlowDocument document) { Document = document; } /// <summary> /// <see cref="IFlowDocumentViewer.ContentPosition"/> /// </summary> ContentPosition IFlowDocumentViewer.ContentPosition { get { return ContentPosition; } set { if (value != null && Document != null) { // This need be called because the UI may not be ready when Contentposition is set. Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(BringContentPositionIntoView), value); } } } /// <summary> /// <see cref="IFlowDocumentViewer.TextSelection"/> /// </summary> ITextSelection IFlowDocumentViewer.TextSelection { get { return this.Selection; } set { if (value != null && Document != null) { // This need be called because the UI may not be ready when Contentposition is set. Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(SetTextSelection), value); } } } /// <summary> /// <see cref="IFlowDocumentViewer.CanGoToPreviousPage"/> /// </summary> bool IFlowDocumentViewer.CanGoToPreviousPage { get { return CanGoToPreviousPage; } } /// <summary> /// <see cref="IFlowDocumentViewer.CanGoToNextPage"/> /// </summary> bool IFlowDocumentViewer.CanGoToNextPage { get { return CanGoToNextPage; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageNumber"/> /// </summary> int IFlowDocumentViewer.PageNumber { get { return MasterPageNumber; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageCount"/> /// </summary> int IFlowDocumentViewer.PageCount { get { return PageCount; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageNumberChanged"/> /// </summary> event EventHandler IFlowDocumentViewer.PageNumberChanged { add { _pageNumberChanged += value; } remove { _pageNumberChanged -= value; } } /// <summary> /// <see cref="IFlowDocumentViewer.PageCountChanged"/> /// </summary> event EventHandler IFlowDocumentViewer.PageCountChanged { add { _pageCountChanged += value; } remove { _pageCountChanged -= value; } } /// <summary> /// <see cref="IFlowDocumentViewer.PrintStarted"/> /// </summary> event EventHandler IFlowDocumentViewer.PrintStarted { add { _printStarted += value; } remove { _printStarted -= value; } } /// <summary> /// <see cref="IFlowDocumentViewer.PrintCompleted"/> /// </summary> event EventHandler IFlowDocumentViewer.PrintCompleted { add { _printCompleted += value; } remove { _printCompleted -= value; } } #endregion IFlowDocumentViewer Members } /// <summary> /// This class is an internal subclass of the FlowDocumentPageViewer that provides /// IFlowDocumentViewer interface used by FlowDocumentReader. /// In addition to that it also provides support for TwoPage view. /// </summary> internal class ReaderTwoPageViewer : ReaderPageViewer { //------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// <summary> /// Handler for the PreviousPage command. /// </summary> protected override void OnPreviousPageCommand() { GoToPage(Math.Max(1, MasterPageNumber - 2)); } /// <summary> /// Handler for the NextPage command. /// </summary> protected override void OnNextPageCommand() { GoToPage(Math.Min(PageCount, MasterPageNumber + 2)); } /// <summary> /// Handler for the LastPage command. /// </summary> protected override void OnLastPageCommand() { // Always navigate to odd PageNumber GoToPage(PageCount); } /// <summary> /// Handler for the GoToPage command. /// </summary> /// <param name="pageNumber"></param> protected override void OnGoToPageCommand(int pageNumber) { // Go to only odd page numbers. base.OnGoToPageCommand((((pageNumber - 1) / 2) * 2) + 1); } /// <summary> /// Notification that a specified property has been changed. /// </summary> protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.Property == MasterPageNumberProperty) { // Only odd page numbers are allowed. int pageNumber = (int)e.NewValue; pageNumber = (((pageNumber - 1) / 2) * 2) + 1; if (pageNumber != (int)e.NewValue) { GoToPage(pageNumber); } } } #endregion Protected Methods //------------------------------------------------------------------- // // Private Methods // //------------------------------------------------------------------- #region Private Methods static ReaderTwoPageViewer() { CanGoToNextPagePropertyKey.OverrideMetadata(typeof(ReaderTwoPageViewer), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, null, new CoerceValueCallback(CoerceCanGoToNextPage))); } /// <summary> /// Coerce the value for CanGoToNextPage property. /// </summary> private static object CoerceCanGoToNextPage(DependencyObject d, object value) { Invariant.Assert(d != null && d is ReaderTwoPageViewer); ReaderTwoPageViewer viewer = (ReaderTwoPageViewer)d; return (viewer.MasterPageNumber < viewer.PageCount - 1); } #endregion Private Methods } }