|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description: Control for displaying paginated content.
//
using MS.Internal; // For Invariant.Assert
using MS.Internal.Commands;
using MS.Internal.Documents;
using MS.Internal.Telemetry.PresentationFramework;
using MS.Utility;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows.Automation.Peers;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Markup;
namespace System.Windows.Controls
{
/// <summary>
/// DocumentViewer is a control that allows developers to create a custom reading
/// experience in their applications for digital documents.
/// </summary>
/// <seealso cref="IDocumentPaginatorSource" />
/// <speclink>http://d2/DRX/default.aspx</speclink>
[TemplatePart(Name = "PART_FindToolBarHost", Type = typeof(ContentControl))]
[TemplatePart(Name = "PART_ContentHost", Type = typeof(ScrollViewer))]
public class DocumentViewer : DocumentViewerBase
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// Initializes class-wide settings.
/// </summary>
static DocumentViewer()
{
// Create our CommandBindings
CreateCommandBindings();
// Register property metadata
RegisterMetadata();
ControlsTraceLogger.AddControl(TelemetryControls.DocumentViewer);
}
/// <summary>
/// Instantiates a new instance of a DocumentViewer
/// </summary>
public DocumentViewer() : base()
{
//Perf Tracing - DocumentViewer Construction
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXInstantiated);
SetUp();
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
#region Command convenience methods
/// <summary>
/// Tells DocumentViewer to display a "thumbnail view" of pages. This is analogous
/// to the ViewThumbnailsCommand.
/// </summary>
public void ViewThumbnails()
{
OnViewThumbnailsCommand();
}
/// <summary>
/// Tells DocumentViewer to fit a single page to the width of the current viewport.
/// This is analogous to the FitToWidthCommand.
/// </summary>
public void FitToWidth()
{
OnFitToWidthCommand();
}
/// <summary>
/// Tells DocumentViewer to fit a single page to the height of the current viewport.
/// This is analogous to the FitToHeightCommand.
/// </summary>
public void FitToHeight()
{
OnFitToHeightCommand();
}
/// <summary>
/// Tells DocumentViewer to fit the current MaxPagesAcross count to the current
/// viewport. This is analogous to the FitToMaxPagesAcrossCommand.
/// </summary>
public void FitToMaxPagesAcross()
{
OnFitToMaxPagesAcrossCommand();
}
/// <summary>
/// Tells DocumentViewer to fit the specified number of pages across to the current viewport
/// and sets MaxPagesAcross to the passed in value. This is analogous to the
/// FitMaxPagesAcrossCommand.
/// </summary>
/// <param name="pagesAcross"></param>
public void FitToMaxPagesAcross(int pagesAcross)
{
if (ValidateMaxPagesAcross(pagesAcross))
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.FitColumns(pagesAcross);
}
}
else
{
throw new ArgumentOutOfRangeException("pagesAcross");
}
}
/// <summary>
/// Tells DocumentViewer to invoke the Find Dialog. This is analogous to the
/// FindCommand.
/// </summary>
public void Find()
{
OnFindCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll up by one viewport. This is analogous to the
/// ScrollPageUpCommand.
/// </summary>
public void ScrollPageUp()
{
OnScrollPageUpCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll down by one viewport. This is analogous to the
/// ScrollPageDownCommand.
/// </summary>
public void ScrollPageDown()
{
OnScrollPageDownCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll left by one viewport. This is analogous to the
/// ScrollPageLeftCommand.
/// </summary>
public void ScrollPageLeft()
{
OnScrollPageLeftCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll right by one viewport. This is analogous to the
/// ScrollPageRightCommand.
/// </summary>
public void ScrollPageRight()
{
OnScrollPageRightCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll up by one line (16px). This is analogous to the
/// MoveUpCommand.
/// </summary>
public void MoveUp()
{
OnMoveUpCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll down by one line (16px). This is analogous to the
/// MoveDownCommand.
/// </summary>
public void MoveDown()
{
OnMoveDownCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll left by one line (16px). This is analogous to the
/// MoveLeftCommand.
/// </summary>
public void MoveLeft()
{
OnMoveLeftCommand();
}
/// <summary>
/// Tells DocumentViewer to scroll right by one line (16px). This is analogous to the
/// MoveRightCommand.
/// </summary>
public void MoveRight()
{
OnMoveRightCommand();
}
/// <summary>
/// Tells DocumentViewer to increase Zoom by a predefined, nonlinear value. This is
/// analogous to the IncreaseZoomCommand.
/// </summary>
public void IncreaseZoom()
{
OnIncreaseZoomCommand();
}
/// <summary>
/// Tells DocumentViewer to decrease Zoom by a predefined, nonlinear value. This is
/// analogous to the DecreaseZoomCommand.
/// </summary>
public void DecreaseZoom()
{
OnDecreaseZoomCommand();
}
#endregion Command convenience methods
/// <summary>
/// Called when the Template's tree has been generated
/// </summary>
/// <remarks>
/// This method is commonly used to check the status of the visual
/// tree prior to rendering, so that elements of the tree can be
/// customized before they are shown.
/// If a style is changed that affects the visual tree, the
/// ApplyTemplate method will expand the new visual tree and return
/// true. Otherwise, it will return false.
/// When overriding this method, be sure to call the ApplyTemplate
/// method of the base class.
/// </remarks>
/// <returns>
/// True if the Visual Tree has been created
/// </returns>
/// <exception cref="NotSupportedException">There must be a ScrollViewer in
/// DocumentViewer's visual tree which has Name PART_ContentHost.</exception>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Walk the visual tree, locating those elements marked with the special
// properties such as "I'm the Content area!"
FindContentHost();
// Create the Find toolbar and add it to our Visual Tree when appropriate.
InstantiateFindToolBar();
//Perf Tracing - DocumentViewer Visuals Created
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXStyleCreated);
// If unset, we will set the ContextMenu property to null -- this will prevent the TextEditor
// from overriding our ContextMenu with its own. The TextEditor checks the UIScope (DocumentViewer)
// not the RenderScope (DocumentGrid) for previously-set Context Menus, and if it finds one (or if
// it finds that ContextMenu has explicitly been set to null, it will not override it.
// Since DocumentViewer doesn't have a ContextMenu -- the ContextMenu is set on DocumentGrid which the
// TextEditor won't find, we set ContextMenu to null here.
// Yes, this seems, on the surface, to be a bit redundant -- but there is a difference between an
// "unset null" and an "explicitly set" null to the Property engine that the TextEditor looks for.
// We check that the ContextMenu is null because we don't want to override any user-specified ContextMenus.
if (this.ContextMenu == null)
{
this.ContextMenu = null;
}
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
#region Commands
/// <summary>
/// This command will invoke ViewThumbnails method, causing as many pages
/// as feasible to be displayed in the current viewport.
/// </summary>
public static RoutedUICommand ViewThumbnailsCommand
{
get
{
return _viewThumbnailsCommand;
}
}
/// <summary>
/// This command will invoke the FitToWidth method,
/// setting the layout to 1 page across, and zooming so that one page is displayed
/// at the width of the current viewport.
/// </summary>
public static RoutedUICommand FitToWidthCommand
{
get
{
return _fitToWidthCommand;
}
}
/// <summary>
/// This command will invoke the FitToWidth method,
/// setting the layout to 1 page across, and zooming so that one page is displayed
/// at the height of the current viewport.
/// </summary>
public static RoutedUICommand FitToHeightCommand
{
get
{
return _fitToHeightCommand;
}
}
/// <summary>
/// This command will invoke the FitToMaxPagesAcross method,
/// effectively setting MaxPagesAcross
/// to the value of the parameter and fitting those pages into view.
/// </summary>
public static RoutedUICommand FitToMaxPagesAcrossCommand
{
get
{
return _fitToMaxPagesAcrossCommand;
}
}
#endregion Commands
#region Dependency Properties
#region HorizontalOffset
/// <summary>
/// Reflects the current Horizontal position in the document in pixel units given
/// the current page layout.
/// </summary>
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.Register(
"HorizontalOffset",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_horizontalOffsetDefault, //default value
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, //MetaData flags
new PropertyChangedCallback(OnHorizontalOffsetChanged)), //changed callback
new ValidateValueCallback(ValidateOffset)); //validate callback
/// <summary>
/// Reflects the current Horizontal position in the document in pixel units given
/// the current page layout.
/// </summary>
public double HorizontalOffset
{
get { return (double) GetValue(HorizontalOffsetProperty); }
set
{
SetValue(HorizontalOffsetProperty, value);
}
}
#endregion HorizontalOffset
#region VerticalOffset
/// <summary>
/// Reflects the current Vertical position in the document in pixel units given
/// the current page layout.
/// </summary>
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.Register(
"VerticalOffset",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_verticalOffsetDefault, //default value
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, //MetaData flags
new PropertyChangedCallback(OnVerticalOffsetChanged)), //changed callback
new ValidateValueCallback(ValidateOffset)); //validate callback
/// <summary>
/// Reflects the current Vertical position in the document in pixel units given
/// the current page layout.
/// </summary>
public double VerticalOffset
{
get { return (double) GetValue(VerticalOffsetProperty); }
set
{
SetValue(VerticalOffsetProperty, value);
}
}
#endregion VerticalOffset
#region ExtentWidth
/// <summary>
/// Reflects the current width of the document layout.
/// </summary>
private static readonly DependencyPropertyKey ExtentWidthPropertyKey =
DependencyProperty.RegisterReadOnly(
"ExtentWidth",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_extentWidthDefault,
new PropertyChangedCallback(OnExtentWidthChanged)));
/// <summary>
/// Reflects the current width of the document layout.
/// </summary>
public static readonly DependencyProperty ExtentWidthProperty =
ExtentWidthPropertyKey.DependencyProperty;
/// <summary>
/// Reflects the current width of the document layout.
/// </summary>
public double ExtentWidth
{
get { return (double) GetValue(ExtentWidthProperty); }
}
#endregion ExtentWidth
#region ExtentHeight
/// <summary>
/// Reflects the current height of the document layout.
/// </summary>
private static readonly DependencyPropertyKey ExtentHeightPropertyKey =
DependencyProperty.RegisterReadOnly(
"ExtentHeight",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_extentHeightDefault,
new PropertyChangedCallback(OnExtentHeightChanged)));
/// <summary>
/// Reflects the current height of the document layout.
/// </summary>
public static readonly DependencyProperty ExtentHeightProperty =
ExtentHeightPropertyKey.DependencyProperty;
/// <summary>
/// Reflects the current height of the document layout.
/// </summary>
public double ExtentHeight
{
get { return (double) GetValue(ExtentHeightProperty); }
}
#endregion ExtentHeight
#region ViewportWidth
/// <summary>
/// Reflects the current width of the DocumentViewer's content area.
/// </summary>
private static readonly DependencyPropertyKey ViewportWidthPropertyKey =
DependencyProperty.RegisterReadOnly(
"ViewportWidth",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_viewportWidthDefault,
new PropertyChangedCallback(OnViewportWidthChanged)));
/// <summary>
/// Reflects the current width of the DocumentViewer's content area.
/// </summary>
public static readonly DependencyProperty ViewportWidthProperty =
ViewportWidthPropertyKey.DependencyProperty;
/// <summary>
/// Reflects the current width of the DocumentViewer's content area.
/// </summary>
public double ViewportWidth
{
get { return (double) GetValue(ViewportWidthProperty); }
}
#endregion ViewportWidth
#region ViewportHeight
/// <summary>
/// Reflects the current height of the DocumentViewer's content area.
/// </summary>
private static readonly DependencyPropertyKey ViewportHeightPropertyKey =
DependencyProperty.RegisterReadOnly(
"ViewportHeight",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_viewportHeightDefault,
new PropertyChangedCallback(OnViewportHeightChanged)));
/// <summary>
/// Reflects the current height of the DocumentViewer's content area.
/// </summary>
public static readonly DependencyProperty ViewportHeightProperty =
ViewportHeightPropertyKey.DependencyProperty;
/// <summary>
/// Reflects the current height of the DocumentViewer's content area.
/// </summary>
public double ViewportHeight
{
get { return (double) GetValue(ViewportHeightProperty); }
}
#endregion ViewportHeight
#region ShowPageBorders
/// <summary>
/// Reflects whether a "Drop Shadow" border should be shown around the pages being displayed.
/// </summary>
public static readonly DependencyProperty ShowPageBordersProperty =
DependencyProperty.Register(
"ShowPageBorders",
typeof(bool),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_showPageBordersDefault, //default value
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, //MetaData flags
new PropertyChangedCallback(OnShowPageBordersChanged))); //changed callback
/// <summary>
/// Reflects whether a "Drop Shadow" border should be shown around the pages being displayed.
/// </summary>
public bool ShowPageBorders
{
get { return (bool) GetValue(ShowPageBordersProperty); }
set { SetValue(ShowPageBordersProperty, value); }
}
#endregion ShowPageBorders
#region Zoom
/// <summary>
/// Reflects the effective Zoom percentage based on the last layout related
/// property set or command issued.
/// If this was the last property set, or the Zoom command was last issued,
/// then this will just be the last Zoom value set.
/// When another layout related property (ie MaxPagesAcross, etc) was set
/// or a command (ie FitToMaxPagesAcross, FitToHeight, etc) was issued, then
/// this value will be the resulting Zoom value from that layout adjustment.
///
/// Must be greater than 5.0 and less than 5000.0. Invalid values will throw.
/// </summary>
public static readonly DependencyProperty ZoomProperty =
DependencyProperty.Register(
"Zoom",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_zoomPercentageDefault, //default value
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, //MetaData flags
new PropertyChangedCallback(OnZoomChanged), //changed callback
new CoerceValueCallback(CoerceZoom))); // coercion callback
/// <summary>
/// Reflects the effective Zoom percentage based on the last layout related
/// property set or command issued.
/// If this was the last property set, or the Zoom command was last issued,
/// then this will just be the last Zoom value set.
/// When another layout related property (ie MaxPagesAcross, etc) was set
/// or a command (ie FitToMaxPagesAcross, FitToHeight, etc) was issued, then
/// this value will be the resulting Zoom value from that layout adjustment.
///
/// Must be greater than 5.0 and less than 5000.0.
/// </summary>
public double Zoom
{
get { return (double) GetValue(ZoomProperty); }
set { SetValue(ZoomProperty, value); }
}
#endregion Zoom
#region MaxPagesAcross
/// <summary>
/// Reflects the number of Columns of pages displayed, based on the last
/// value set through this property, or a layout related command
/// (ie FitToMaxPagesAcross, ViewThumbnails, etc..)
/// </summary>
public static readonly DependencyProperty MaxPagesAcrossProperty =
DependencyProperty.Register(
"MaxPagesAcross",
typeof(int),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_maxPagesAcrossDefault, //default value
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, //MetaData flags
new PropertyChangedCallback(OnMaxPagesAcrossChanged)), //changed callback
new ValidateValueCallback(ValidateMaxPagesAcross)); //validation callback
/// <summary>
/// Reflects the number of Columns of pages displayed, based on the last
/// value set through this property, or a layout related command
/// (ie FitToMaxPagesAcross, ViewThumbnails, etc..)
/// Valid values are from 1 to 32, inclusive.
/// </summary>
public int MaxPagesAcross
{
get { return (int) GetValue(MaxPagesAcrossProperty); }
set { SetValue(MaxPagesAcrossProperty, value); }
}
#endregion MaxPagesAcross
#region VerticalPageSpacing
/// <summary>
/// Reflects the vertical gap between Pages when laid out, in pixel units.
/// </summary>
public static readonly DependencyProperty VerticalPageSpacingProperty =
DependencyProperty.Register(
"VerticalPageSpacing",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_verticalPageSpacingDefault, //default value
new PropertyChangedCallback(OnVerticalPageSpacingChanged)), //changed callback
new ValidateValueCallback(ValidatePageSpacing)); //validation callback
/// <summary>
/// Reflects the vertical gap between Pages when laid out, in pixel units.
/// </summary>
public double VerticalPageSpacing
{
get { return (double) GetValue(VerticalPageSpacingProperty); }
set { SetValue(VerticalPageSpacingProperty, value); }
}
#endregion VerticalPageSpacing
#region HorizontalPageSpacing
/// <summary>
/// Reflects the horizontal gap between Pages when laid out, in pixel units.
/// </summary>
public static readonly DependencyProperty HorizontalPageSpacingProperty =
DependencyProperty.Register(
"HorizontalPageSpacing",
typeof(double),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(
_horizontalPageSpacingDefault, //default value
new PropertyChangedCallback(OnHorizontalPageSpacingChanged)), //changed callback
new ValidateValueCallback(ValidatePageSpacing)); //validation callback
/// <summary>
/// Reflects the horizontal gap between Pages when laid out, in pixel units.
/// </summary>
public double HorizontalPageSpacing
{
get { return (double) GetValue(HorizontalPageSpacingProperty); }
set { SetValue(HorizontalPageSpacingProperty, value); }
}
#endregion HorizontalPageSpacing
#region CanMoveUp
/// <summary>
/// Reflects whether the DocumentViewer is at the top of the current document.
/// </summary>
private static readonly DependencyPropertyKey CanMoveUpPropertyKey =
DependencyProperty.RegisterReadOnly(
"CanMoveUp",
typeof(bool),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(_canMoveUpDefault));
/// <summary>
/// Reflects whether the DocumentViewer is at the top of the current document.
/// </summary>
public static readonly DependencyProperty CanMoveUpProperty =
CanMoveUpPropertyKey.DependencyProperty;
/// <summary>
/// Reflects whether the DocumentViewer is at the top of the current document.
/// </summary>
public bool CanMoveUp
{
get { return (bool) GetValue(CanMoveUpProperty); }
}
#endregion CanMoveUp
#region CanMoveDown
/// <summary>
/// Reflects whether the DocumentViewer is at the bottom of the current document.
/// </summary>
private static readonly DependencyPropertyKey CanMoveDownPropertyKey =
DependencyProperty.RegisterReadOnly(
"CanMoveDown",
typeof(bool),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(_canMoveDownDefault));
/// <summary>
/// Reflects whether the DocumentViewer is at the bottom of the current document.
/// </summary>
public static readonly DependencyProperty CanMoveDownProperty =
CanMoveDownPropertyKey.DependencyProperty;
/// <summary>
/// Reflects whether the DocumentViewer is at the bottom of the current document.
/// </summary>
public bool CanMoveDown
{
get { return (bool) GetValue(CanMoveDownProperty); }
}
#endregion CanMoveDown
#region CanMoveLeft
/// <summary>
/// Reflects whether the DocumentViewer is at the leftmost extent of the current document.
/// </summary>
private static readonly DependencyPropertyKey CanMoveLeftPropertyKey =
DependencyProperty.RegisterReadOnly(
"CanMoveLeft",
typeof(bool),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(_canMoveLeftDefault));
/// <summary>
/// Reflects whether the DocumentViewer is at the leftmost extent of the current document.
/// </summary>
public static readonly DependencyProperty CanMoveLeftProperty =
CanMoveLeftPropertyKey.DependencyProperty;
/// <summary>
/// Reflects whether the DocumentViewer is at the leftmost extent of the current document.
/// </summary>
public bool CanMoveLeft
{
get { return (bool) GetValue(CanMoveLeftProperty); }
}
#endregion CanMoveLeft
#region CanMoveRight
/// <summary>
/// Reflects whether the DocumentViewer is at the rightmost extent of the current document.
/// </summary>
private static readonly DependencyPropertyKey CanMoveRightPropertyKey =
DependencyProperty.RegisterReadOnly(
"CanMoveRight",
typeof(bool),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(_canMoveRightDefault));
/// <summary>
/// Reflects whether the DocumentViewer is at the rightmost extent of the current document.
/// </summary>
public static readonly DependencyProperty CanMoveRightProperty =
CanMoveRightPropertyKey.DependencyProperty;
/// <summary>
/// Reflects whether the DocumentViewer is at the rightmost extent of the current document.
/// </summary>
public bool CanMoveRight
{
get { return (bool) GetValue(CanMoveRightProperty); }
}
#endregion CanMoveRight
#region CanIncreaseZoom
/// <summary>
/// Reflects whether the DocumentViewer can zoom in any further
/// (ie not at the highest "zoom level") of the current document.
/// </summary>
private static readonly DependencyPropertyKey CanIncreaseZoomPropertyKey =
DependencyProperty.RegisterReadOnly(
"CanIncreaseZoom",
typeof(bool),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(_canIncreaseZoomDefault));
/// <summary>
/// Reflects whether the DocumentViewer can zoom in any further
/// (ie not at the highest "zoom level") of the current document.
/// </summary>
public static readonly DependencyProperty CanIncreaseZoomProperty =
CanIncreaseZoomPropertyKey.DependencyProperty;
/// <summary>
/// Reflects whether the DocumentViewer can zoom in any further
/// (ie not at the highest "zoom level") of the current document.
/// </summary>
public bool CanIncreaseZoom
{
get { return (bool) GetValue(CanIncreaseZoomProperty); }
}
#endregion CanIncreaseZoom
#region CanDecreaseZoom
/// <summary>
/// Reflects whether the DocumentViewer can zoom out any further
/// (ie not at the lowest "zoom level") of the current document.
/// </summary>
private static readonly DependencyPropertyKey CanDecreaseZoomPropertyKey =
DependencyProperty.RegisterReadOnly(
"CanDecreaseZoom",
typeof(bool),
typeof(DocumentViewer),
new FrameworkPropertyMetadata(_canDecreaseZoomDefault));
/// <summary>
/// Reflects whether the DocumentViewer can zoom out any further
/// (ie not at the lowest "zoom level") of the current document.
/// </summary>
public static readonly DependencyProperty CanDecreaseZoomProperty =
CanDecreaseZoomPropertyKey.DependencyProperty;
/// <summary>
/// Reflects whether the DocumentViewer can zoom out any further
/// (ie not at the lowest "zoom level") of the current document.
/// </summary>
public bool CanDecreaseZoom
{
get { return (bool) GetValue(CanDecreaseZoomProperty); }
}
#endregion CanDecreaseZoom
#endregion Dependency Properties
#endregion Public Properties
//------------------------------------------------------
//
// Public Operators
//
//------------------------------------------------------
//------------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
/// <summary>
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
/// </summary>
protected override AutomationPeer OnCreateAutomationPeer()
{
return new DocumentViewerAutomationPeer(this);
}
/// <summary>
/// Attaches DocumentGrid to our document when it changes.
/// </summary>
protected override void OnDocumentChanged()
{
// Validate the new document type
if (!(Document is FixedDocument) && !(Document is FixedDocumentSequence)
&& !(Document == null))
{
throw new NotSupportedException(SR.DocumentViewerOnlySupportsFixedDocumentSequence);
}
//Call the base so that TextEditors are attached.
base.OnDocumentChanged();
//Assign the content to DocumentGrid.
AttachContent();
// Update the toolbar with our current document state.
if (_findToolbar != null)
{
_findToolbar.DocumentLoaded = (Document != null) ? true : false;
}
// We do not automatically go to the first page on the _first_ content
// assignment, for two reasons:
// 1) If this is the first assignment, then we're already there by default.
// 2) The user may have specified vertical or horizontal offsets in markup or
// otherwise (<DocumentViewer VerticalOffset="1000">) and we need to honor
// those settings.
if (!_firstDocumentAssignment)
{
// Go to the first page of new content.
OnGoToPageCommand(1);
}
_firstDocumentAssignment = false;
}
/// <summary>
/// Called when a BringIntoView event is bubbled up from the Document.
/// Base implementation will move the master page to the page
/// on which the element occurs.
/// </summary>
/// <param name="element">The object to make visible.</param>
/// <param name="rect">The rectangular region in the object's coordinate space which should be made visible.</param>
/// <param name="pageNumber"></param>
protected override void OnBringIntoView(DependencyObject element, Rect rect, int pageNumber)
{
// DVBase will give us a 1-indexed page number, we convert to 0-indexed.
int zeroIndexed = pageNumber - 1;
if (zeroIndexed >= 0 && zeroIndexed < PageCount)
{
_documentScrollInfo.MakeVisible(element, rect, zeroIndexed);
}
}
/// <summary>
/// Handler for the PreviousPage command.
/// </summary>
protected override void OnPreviousPageCommand()
{
//Scroll to the previous row.
if (_documentScrollInfo != null)
{
_documentScrollInfo.ScrollToPreviousRow();
}
}
/// <summary>
/// Handler for the NextPage command.
/// </summary>
protected override void OnNextPageCommand()
{
//Scroll to the previous row.
if (_documentScrollInfo != null)
{
_documentScrollInfo.ScrollToNextRow();
}
}
/// <summary>
/// Handler for the FirstPage command.
/// </summary>
protected override void OnFirstPageCommand()
{
//Scroll to the top of the document.
if (_documentScrollInfo != null)
{
_documentScrollInfo.MakePageVisible( 0 );
}
}
/// <summary>
/// Handler for the LastPage command.
/// </summary>
protected override void OnLastPageCommand()
{
//Scroll to the bottom of the document.
if (_documentScrollInfo != null)
{
_documentScrollInfo.MakePageVisible( PageCount - 1 );
}
}
/// <summary>
/// Handler for the GoToPage command.
/// </summary>
protected override void OnGoToPageCommand(int pageNumber)
{
// Check if we can go to the specified page.
// and navigate there.
if (CanGoToPage(pageNumber))
{
//Scroll to the specified page in the document.
if (_documentScrollInfo != null)
{
// CanGoToPage should have guaranteed that this assert is always true.
Invariant.Assert(pageNumber > 0, "PageNumber must be positive.");
_documentScrollInfo.MakePageVisible(pageNumber - 1);
}
}
}
/// <summary>
/// Handler for the ViewThumbnails Command
/// </summary>
protected virtual void OnViewThumbnailsCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.ViewThumbnails();
}
}
/// <summary>
/// Handler for the FitToWidth Command
/// </summary>
protected virtual void OnFitToWidthCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.FitToPageWidth();
}
}
/// <summary>
/// Handler for the FitToHeight Command
/// </summary>
protected virtual void OnFitToHeightCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.FitToPageHeight();
}
}
/// <summary>
/// Handler for the FitToMaxPagesAcross Command
/// </summary>
protected virtual void OnFitToMaxPagesAcrossCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.FitColumns(MaxPagesAcross);
}
}
/// <summary>
/// HAndler for the FitMaxPagesAcross Command
/// </summary>
/// <param name="pagesAcross"></param>
protected virtual void OnFitToMaxPagesAcrossCommand(int pagesAcross)
{
if (ValidateMaxPagesAcross(pagesAcross))
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.FitColumns(pagesAcross);
}
}
else
{
throw new ArgumentOutOfRangeException("pagesAcross");
}
}
/// <summary>
/// Handler for the Find Command
/// </summary>
protected virtual void OnFindCommand()
{
GoToFind();
}
/// <summary>
/// This is the method that responds to the KeyDown event.
/// </summary>
/// <param name="e"></param>
protected override void OnKeyDown(KeyEventArgs e)
{
// Look for Find specific key inputs and process them.
// If the key is processed, this event will be marked handled.
e = ProcessFindKeys(e);
base.OnKeyDown(e);
}
/// <summary>
/// Handler for the ScrollPageUp Command
/// </summary>
protected virtual void OnScrollPageUpCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.PageUp();
}
}
/// <summary>
/// Handler for the ScrollPageDown Command
/// </summary>
protected virtual void OnScrollPageDownCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.PageDown();
}
}
/// <summary>
/// Handler for the ScrollPageLeft Command
/// </summary>
protected virtual void OnScrollPageLeftCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.PageLeft();
}
}
/// <summary>
/// Handler for the ScrollPageRight Command
/// </summary>
protected virtual void OnScrollPageRightCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.PageRight();
}
}
/// <summary>
/// Handler for the MoveUp Command
/// </summary>
protected virtual void OnMoveUpCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.LineUp();
}
}
/// <summary>
/// Handler for the MoveDown Command
/// </summary>
protected virtual void OnMoveDownCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.LineDown();
}
}
/// <summary>
/// Handler for the MoveLeft Command
/// </summary>
protected virtual void OnMoveLeftCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.LineLeft();
}
}
/// <summary>
/// Handler for the MoveRight Command
/// </summary>
protected virtual void OnMoveRightCommand()
{
if (_documentScrollInfo != null)
{
_documentScrollInfo.LineRight();
}
}
/// <summary>
/// Handler for the IncreaseZoom Command
/// </summary>
protected virtual void OnIncreaseZoomCommand()
{
// Check if possible to zoom in
if (CanIncreaseZoom)
{
// Update the zoom level index to the appropriate place.
double oldZoom = Zoom;
FindZoomLevelIndex();
// As long as more zoomLevel's exist, increase zoom.
if (_zoomLevelIndex > 0)
{
_zoomLevelIndex--;
}
// Set the zoom percentage, with _updatingInternalZoomLevel set to true to
// avoid resetting the zoom level index.
_updatingInternalZoomLevel = true;
Zoom = ZoomLevelCollection[_zoomLevelIndex];
_updatingInternalZoomLevel = false;
}
}
/// <summary>
/// Handler for the DecreaseZoom Command
/// </summary>
protected virtual void OnDecreaseZoomCommand()
{
// Check if possible to zoom out.
if (CanDecreaseZoom)
{
// Update the zoom level index to the appropriate place.
double oldZoom = Zoom;
FindZoomLevelIndex();
// If the current zoom value exists in the zoomLevelCollection, and can
// still be zoomed out, then zoom out another level.
if ((oldZoom == ZoomLevelCollection[_zoomLevelIndex]) &&
(_zoomLevelIndex < ZoomLevelCollection.Length - 1))
{
_zoomLevelIndex++;
}
// Set the zoom percentage, with _updatingInternalZoomLevel set to true to
// avoid resetting the zoom level index.
_updatingInternalZoomLevel = true;
Zoom = ZoomLevelCollection[_zoomLevelIndex];
_updatingInternalZoomLevel = false;
}
}
/// <summary>
/// Overrides the base implementation and returns the current collection of
/// DocumentPageViews being displayed in our IDSI.
/// </summary>
/// <param name="changed"></param>
/// <returns></returns>
protected override ReadOnlyCollection<DocumentPageView> GetPageViewsCollection(out bool changed)
{
ReadOnlyCollection<DocumentPageView> pageViews = null;
//Save off the current value of our PageView changed flag so we can
//return it to indicate if the collection has actually changed.
changed = _pageViewCollectionChanged;
//Reset the flag so that if this is called again before InvalidatePageViews is called
//it'll reflect the unchanged-ness of the collection.
_pageViewCollectionChanged = false;
if (_documentScrollInfo != null && _documentScrollInfo.PageViews != null)
{
//Return the current collection.
pageViews = _documentScrollInfo.PageViews;
}
else
{
//Return an empty collection (null is not valid).
pageViews = new ReadOnlyCollection<DocumentPageView>(new List<DocumentPageView>(0));
}
return pageViews;
}
/// <summary>
/// Overrides the OnMouseLeftButtonDown method so that we can take focus when clicked.
/// </summary>
/// <param name="e">The MouseButtonEventArgs associated with this mouse event.</param>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// If no other controls in our Template have handled the event, we'll
// take focus here.
if (!e.Handled)
{
Focus();
e.Handled = true;
}
}
/// <summary>
/// OnPreviewMouseWheel Zooms in/out on the document when the
/// mouse wheel is scrolled and the Ctrl key is depressed.
/// </summary>
/// <param name="e">Event Arguments</param>
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
{
if (e.Handled)
{
return;
}
//Get the state of the Ctrl key -- if it's pressed, we'll Zoom.
//Otherwise we do nothing and let others handle this event.
if (Keyboard.IsKeyDown(Key.LeftCtrl) ||
Keyboard.IsKeyDown(Key.RightCtrl))
{
e.Handled = true;
//Zoom based on the direction of the wheel.
if (e.Delta < 0)
{
DecreaseZoom();
}
else
{
IncreaseZoom();
}
}
}
#endregion Protected Methods
#region Internal Methods
/// <summary>
/// Called when our IDocumentScrollInfo has new layout information to share with us.
/// </summary>
internal void InvalidateDocumentScrollInfo()
{
// We need to see if any IDocumentScrollInfo properties that DocumentViewer
// either exposes or makes use of have changed. If so we invalidate properties
// or take actions here.
// Any properties that are read only need to have the cache set and be invalidated
// Settable DP's can be set directly (and should be, for validation)
//Set our internal change flag.
//DP Invalidation callbacks can check for this flag to see if a change originated
//from our IDocumentScrollInfo object or not and take the appropriate action.
//(Usually, if the IDSI caused the change to a DP, the invalidated callback won't need to
//update the associated property on the IDSI.)
_internalIDSIChange = true;
SetValue(ExtentWidthPropertyKey, _documentScrollInfo.ExtentWidth);
SetValue(ExtentHeightPropertyKey, _documentScrollInfo.ExtentHeight);
SetValue(ViewportWidthPropertyKey, _documentScrollInfo.ViewportWidth);
SetValue(ViewportHeightPropertyKey, _documentScrollInfo.ViewportHeight);
if (HorizontalOffset != _documentScrollInfo.HorizontalOffset)
{
HorizontalOffset = _documentScrollInfo.HorizontalOffset;
}
if (VerticalOffset != _documentScrollInfo.VerticalOffset)
{
VerticalOffset = _documentScrollInfo.VerticalOffset;
}
// IDSI is 0-indexed
SetValue(MasterPageNumberPropertyKey, _documentScrollInfo.FirstVisiblePageNumber + 1);
// Convert IDocumentScrollInfo.Scale into 100-based percentage value for comparison.
double scrollZoom = ScaleToZoom(_documentScrollInfo.Scale);
if (Zoom != scrollZoom)
{
Zoom = scrollZoom;
}
if (MaxPagesAcross != _documentScrollInfo.MaxPagesAcross)
{
MaxPagesAcross = _documentScrollInfo.MaxPagesAcross;
}
//Reset our internal change flag.
_internalIDSIChange = false;
}
/// <summary>
/// Merely calls the base's InvalidatePageViews (which is protected).
/// Used by our IDSI to keep the DPV collection in sync.
/// </summary>
internal void InvalidatePageViewsInternal()
{
//Our PageView collection has changed, set the flag.
_pageViewCollectionChanged = true;
InvalidatePageViews();
}
/// <summary>
/// BringPointIntoView is called by the base if a selection goes outside of the bounds
/// of the current TextView. If this happens it is necessary to scroll our content
/// in an attempt to make that selection point visible, thus moving the scope of the TextView
/// and allowing selection to continue.
/// </summary>
/// <param name="point">The point to be brought into view.</param>
/// <returns>Whether operation is pending or not.</returns>
internal bool BringPointIntoView(Point point)
{
FrameworkElement grid = _documentScrollInfo as FrameworkElement;
if (grid != null)
{
//Calculate the bounds of the DocumentGrid relative to the bounds of DocumentViewer
Transform tr = this.TransformToDescendant(grid) as Transform;
Rect gridRect = Rect.Transform(new Rect(grid.RenderSize),
tr.Value);
double verticalOffset = VerticalOffset;
double horizontalOffset = HorizontalOffset;
//Scroll the point into view Vertically.
if (point.Y > gridRect.Y + gridRect.Height)
{
verticalOffset += (point.Y - (gridRect.Y + gridRect.Height));
}
else if (point.Y < gridRect.Y)
{
verticalOffset -= (gridRect.Y - point.Y);
}
//Scroll the point into view Horizontally.
if (point.X < gridRect.X)
{
horizontalOffset -= (gridRect.X - point.X);
}
else if (point.X > gridRect.X + gridRect.Width)
{
horizontalOffset += (point.X - (gridRect.X + gridRect.Width));
}
VerticalOffset = Math.Max(verticalOffset, 0.0);
HorizontalOffset = Math.Max(horizontalOffset, 0.0);
}
return false;
}
#endregion Internal Methods
#region Internal Properties
/// <summary>
/// Internally exposes our TextEditor's Selection, for use
/// by Annotations code.
/// </summary>
internal ITextSelection TextSelection
{
get
{
if (TextEditor != null)
{
return TextEditor.Selection;
}
else
{
return null;
}
}
}
/// <summary>
/// Internally exposes our IDocumentScrollInfo, for use
/// by Annotations code.
/// </summary>
internal IDocumentScrollInfo DocumentScrollInfo
{
get
{
return _documentScrollInfo;
}
}
/// <summary>
/// Internally exposes out ScrollViewer, for use in DocumentViewerAutomationPeer.
/// </summary>
internal ScrollViewer ScrollViewer
{
get
{
return _scrollViewer;
}
}
#endregion InternalProperties
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
#region Commands
/// <summary>
/// Set up our Command bindings
/// </summary>
/// <summary>
/// Set up our RoutedUICommand bindings
/// </summary>
private static void CreateCommandBindings()
{
// Create our generic ExecutedRoutedEventHandler.
ExecutedRoutedEventHandler executeHandler = new ExecutedRoutedEventHandler(ExecutedRoutedEventHandler);
// Create our generic QueryEnabledStatusHandler
CanExecuteRoutedEventHandler queryEnabledHandler = new CanExecuteRoutedEventHandler(QueryEnabledHandler);
//
// Command: ViewThumbnails
// Tells DocumentViewer to display thumbnails.
_viewThumbnailsCommand = new RoutedUICommand(SR.DocumentViewerViewThumbnailsCommandText,
"ViewThumbnailsCommand",
typeof(DocumentViewer),
null);
CommandHelpers.RegisterCommandHandler( typeof(DocumentViewer),
_viewThumbnailsCommand,
executeHandler,
queryEnabledHandler);
//no key gesture
//
// Command: FitToWidth
// Tells DocumentViewer to zoom to the document width.
_fitToWidthCommand = new RoutedUICommand(
SR.DocumentViewerViewFitToWidthCommandText,
"FitToWidthCommand",
typeof(DocumentViewer),
null);
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
_fitToWidthCommand,
executeHandler,
queryEnabledHandler,
new KeyGesture(Key.D2, ModifierKeys.Control));
//
// Command: FitToHeight
// Tells DocumentViewer to zoom to the document height.
_fitToHeightCommand = new RoutedUICommand(
SR.DocumentViewerViewFitToHeightCommandText,
"FitToHeightCommand",
typeof(DocumentViewer),
null);
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
_fitToHeightCommand,
executeHandler,
queryEnabledHandler);
//no key gesture
//
// Command: MaxPagesAcross
// Sets the MaxPagesAcross to the value provided.
_fitToMaxPagesAcrossCommand = new RoutedUICommand(
SR.DocumentViewerViewFitToMaxPagesAcrossCommandText,
"FitToMaxPagesAcrossCommand",
typeof(DocumentViewer),
null);
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
_fitToMaxPagesAcrossCommand,
executeHandler,
queryEnabledHandler);
//no key gesture
#region Library Commands
// Command: ApplicationCommands.Find - Ctrl+F
// Invokes DocumentViewer's Find dialog.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ApplicationCommands.Find,
executeHandler,
queryEnabledHandler);
//
// Command: ComponentCommands.ScrollPageUp - PageUp
// Causes DocumentViewer to scroll a Viewport up.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.ScrollPageUp,
executeHandler,
queryEnabledHandler,
Key.PageUp);
//
// Command: ComponentCommands.ScrollPageDown - PageDown
// Causes DocumentViewer to scroll a Viewport down.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.ScrollPageDown,
executeHandler,
queryEnabledHandler,
Key.PageDown);
//
// Command: ComponentCommands.ScrollPageLeft
// Causes DocumentViewer to scroll a Viewport to the left.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.ScrollPageLeft,
executeHandler,
queryEnabledHandler);
//no key gesture
//
// Command: ComponentCommands.ScrollPageRight
// Causes DocumentViewer to scroll a Viewport to the right.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.ScrollPageRight,
executeHandler,
queryEnabledHandler);
//no key gesture
//
// Command: ComponentCommands.MoveUp - Up
// Causes DocumentViewer to scroll the Viewport up by 16px.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.MoveUp,
executeHandler,
queryEnabledHandler,
Key.Up);
//
// Command: ComponentCommands.MoveDown - Down
// Causes DocumentViewer to scroll the Viewport down by 16px.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.MoveDown,
executeHandler,
queryEnabledHandler,
Key.Down);
//
// Command: ComponentCommands.MoveLeft - Left
// Causes DocumentViewer to scroll a Viewport left by 16px.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.MoveLeft,
executeHandler,
queryEnabledHandler,
Key.Left);
//
// Command: ComponentCommands.MoveRight - Right
// Causes DocumentViewer to scroll a Viewport right by 16px.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
ComponentCommands.MoveRight,
executeHandler,
queryEnabledHandler,
Key.Right);
//
// Command: NavigationCommands.Zoom
// Sets DocumentViewer's Zoom to the specified level.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
NavigationCommands.Zoom,
executeHandler,
queryEnabledHandler);
//no key gesture
//
// Command: NavigationCommands.IncreaseZoom
// Causes DocumentViewer to zoom in on the content.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
NavigationCommands.IncreaseZoom,
executeHandler,
queryEnabledHandler,
// Ctrl+Numpad '+'
new KeyGesture(Key.Add, ModifierKeys.Control),
// Ctrl+Numpad '+' (In case shift is held down)
new KeyGesture(Key.Add, ModifierKeys.Shift | ModifierKeys.Control),
// Ctrl+'+'
new KeyGesture(Key.OemPlus, ModifierKeys.Control),
// Ctrl+'+' (In case shift is held down)
new KeyGesture(Key.OemPlus, ModifierKeys.Shift | ModifierKeys.Control));
//
// Command: NavigationCommands.DecreaseZoom
// Causes DocumentViewer to zoom out of the content.
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
NavigationCommands.DecreaseZoom,
executeHandler,
queryEnabledHandler,
// Ctrl+Numpad '-'
new KeyGesture(Key.Subtract, ModifierKeys.Control),
// Ctrl+Numpad '-' (In case shift is held down)
new KeyGesture(Key.Subtract, ModifierKeys.Shift | ModifierKeys.Control),
// Ctrl+'-'
new KeyGesture(Key.OemMinus, ModifierKeys.Control),
// Ctrl+'-' (In case shift is held down)
new KeyGesture(Key.OemMinus, ModifierKeys.Shift | ModifierKeys.Control));
// Command: NavigationCommands.PreviousPage
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
NavigationCommands.PreviousPage,
executeHandler,
queryEnabledHandler,
new KeyGesture(Key.PageUp, ModifierKeys.Control));
// Command: NavigationCommands.NextPage
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
NavigationCommands.NextPage,
executeHandler,
queryEnabledHandler,
new KeyGesture(Key.PageDown, ModifierKeys.Control));
// Command: NavigationCommands.FirstPage
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
NavigationCommands.FirstPage,
executeHandler,
queryEnabledHandler,
new KeyGesture(Key.Home, ModifierKeys.Control));
// Command: NavigationCommands.FirstPage
CommandHelpers.RegisterCommandHandler(typeof(DocumentViewer),
NavigationCommands.LastPage,
executeHandler,
queryEnabledHandler,
new KeyGesture(Key.End, ModifierKeys.Control));
#endregion Library Commands
//Register input bindings for keyboard shortcuts that require
//Command Parameters:
//Zoom 100%: Requires a CommandParameter of 100.0 with the Zoom Command.
//Bound to Ctrl+1.
InputBinding zoom100InputBinding =
new InputBinding(NavigationCommands.Zoom,
new KeyGesture(Key.D1, ModifierKeys.Control));
zoom100InputBinding.CommandParameter = 100.0;
CommandManager.RegisterClassInputBinding(typeof(DocumentViewer),
zoom100InputBinding);
//Whole Page: Requires a CommandParameter of 1 with the FitToMaxPagesAcross Command.
//Bound to Ctrl+3.
InputBinding wholePageInputBinding =
new InputBinding(DocumentViewer.FitToMaxPagesAcrossCommand,
new KeyGesture(Key.D3, ModifierKeys.Control));
wholePageInputBinding.CommandParameter = 1;
CommandManager.RegisterClassInputBinding(typeof(DocumentViewer),
wholePageInputBinding);
//Two Pages: Requires a CommandParameter of 2 with the FitToMaxPagesAcross Command.
//Bound to Ctrl+4.
InputBinding twoPagesInputBinding =
new InputBinding(DocumentViewer.FitToMaxPagesAcrossCommand,
new KeyGesture(Key.D4, ModifierKeys.Control));
twoPagesInputBinding.CommandParameter = 2;
CommandManager.RegisterClassInputBinding(typeof(DocumentViewer),
twoPagesInputBinding);
}
/// <summary>
/// Central handler for QueryEnabled events fired by Commands directed at DocumentViewer.
/// </summary>
/// <param name="target">The target of this Command, expected to be DocumentViewer</param>
/// <param name="args">The event arguments for this event.</param>
private static void QueryEnabledHandler(object target, CanExecuteRoutedEventArgs args)
{
DocumentViewer dv = target as DocumentViewer;
Invariant.Assert(dv != null, "Target of QueryEnabledEvent must be DocumentViewer.");
Invariant.Assert(args != null, "args cannot be null.");
// If the target is not a DocumentViewer we silently return (but note that
// we have Asserted this above.)
if (dv == null)
{
return;
}
// Mark this event as handled so that the CanExecute handler on the base
// doesn't override our settings.
args.Handled = true;
// Now we set the IsEnabled flag based on the Command that fired this event
// and the current state of DocumentViewer.
if (args.Command == ViewThumbnailsCommand ||
args.Command == FitToWidthCommand ||
args.Command == FitToHeightCommand ||
args.Command == FitToMaxPagesAcrossCommand ||
args.Command == NavigationCommands.Zoom)
{
// Fit and Zoom operations are always enabled.
args.CanExecute = true;
}
else if (args.Command == ApplicationCommands.Find)
{
//Find can only operate if we have a TextEditor.
args.CanExecute = dv.TextEditor != null;
}
else if (args.Command == ComponentCommands.ScrollPageUp ||
args.Command == ComponentCommands.MoveUp)
{
// Enabling the Move/Scroll Up Commands is tied to the
// state of the CanMoveUp property.
args.CanExecute = dv.CanMoveUp;
}
else if (args.Command == ComponentCommands.ScrollPageDown ||
args.Command == ComponentCommands.MoveDown)
{
// Enabling the Move/Scroll Down Commands is tied to the
// state of the CanMoveDown property.
args.CanExecute = dv.CanMoveDown;
}
else if (args.Command == ComponentCommands.ScrollPageLeft ||
args.Command == ComponentCommands.MoveLeft)
{
// Enabling the Move/Scroll Left Commands is tied to the
// state of the CanMoveLeft property.
args.CanExecute = dv.CanMoveLeft;
}
else if (args.Command == ComponentCommands.ScrollPageRight ||
args.Command == ComponentCommands.MoveRight)
{
// Enabling the Move/Scroll Right Commands is tied to the
// state of the CanMoveRight property.
args.CanExecute = dv.CanMoveRight;
}
else if (args.Command == NavigationCommands.IncreaseZoom)
{
// Zooming in is only allowed if DocumentViewer has a valid Document assigned
// and can increase zoom.
args.CanExecute = dv.CanIncreaseZoom;
}
else if (args.Command == NavigationCommands.DecreaseZoom)
{
// Zooming out is only allowed if DocumentViewer has a valid Document assigned
// and can decrease zoom.
args.CanExecute = dv.CanDecreaseZoom;
}
else if (args.Command == NavigationCommands.PreviousPage
|| args.Command == NavigationCommands.FirstPage)
{
// Enabling the PreviousPage and FirstPage Commands is tied
// to the state of the CanGoToPreviousPage property.
args.CanExecute = dv.CanGoToPreviousPage;
}
else if (args.Command == NavigationCommands.NextPage
|| args.Command == NavigationCommands.LastPage)
{
// Enabling the NextPage and LastPage Commands is tied
// to the state of the CanGoToNextPage property.
args.CanExecute = dv.CanGoToNextPage;
}
else if (args.Command == NavigationCommands.GoToPage)
{
// This command is always enabled as long as there is a document loaded.
args.CanExecute = (dv.Document != null);
}
else
{
args.Handled = false;
// If we get here then we missed a Command above.
// We assert to indicate the failure.
Invariant.Assert(false, "Command not handled in QueryEnabledHandler.");
}
}
/// <summary>
/// Central handler for all ExecuteEvents fired by Commands directed at DocumentViewer.
/// </summary>
/// <param name="target">The target of this Command, expected to be DocumentViewer.</param>
/// <param name="args">The event arguments associated with this event.</param>
private static void ExecutedRoutedEventHandler(object target, ExecutedRoutedEventArgs args)
{
DocumentViewer dv = target as DocumentViewer;
Invariant.Assert(dv != null, "Target of ExecuteEvent must be DocumentViewer.");
Invariant.Assert(args != null, "args cannot be null.");
// If the target is not a DocumentViewer we silently return (but note that
// we have Asserted this above.)
if (dv == null)
{
return;
}
// Now we execute the method corresponding to the Command that fired this event;
// each Command has its own protected virtual method that performs the operation
// corresponding to the Command.
if (args.Command == ViewThumbnailsCommand)
{
dv.OnViewThumbnailsCommand();
}
else if (args.Command == FitToWidthCommand)
{
dv.OnFitToWidthCommand();
}
else if (args.Command == FitToHeightCommand)
{
dv.OnFitToHeightCommand();
}
else if (args.Command == FitToMaxPagesAcrossCommand)
{
DoFitToMaxPagesAcross(dv, args.Parameter);
}
else if (args.Command == ApplicationCommands.Find)
{
dv.OnFindCommand();
}
else if (args.Command == ComponentCommands.ScrollPageUp)
{
dv.OnScrollPageUpCommand();
}
else if (args.Command == ComponentCommands.ScrollPageDown)
{
dv.OnScrollPageDownCommand();
}
else if (args.Command == ComponentCommands.ScrollPageLeft)
{
dv.OnScrollPageLeftCommand();
}
else if (args.Command == ComponentCommands.ScrollPageRight)
{
dv.OnScrollPageRightCommand();
}
else if (args.Command == ComponentCommands.MoveUp)
{
dv.OnMoveUpCommand();
}
else if (args.Command == ComponentCommands.MoveDown)
{
dv.OnMoveDownCommand();
}
else if (args.Command == ComponentCommands.MoveLeft)
{
dv.OnMoveLeftCommand();
}
else if (args.Command == ComponentCommands.MoveRight)
{
dv.OnMoveRightCommand();
}
else if (args.Command == NavigationCommands.Zoom)
{
DoZoom(dv, args.Parameter);
}
else if (args.Command == NavigationCommands.DecreaseZoom)
{
dv.DecreaseZoom();
}
else if (args.Command == NavigationCommands.IncreaseZoom)
{
dv.IncreaseZoom();
}
else if (args.Command == NavigationCommands.PreviousPage)
{
dv.PreviousPage();
}
else if (args.Command == NavigationCommands.NextPage)
{
dv.NextPage();
}
else if (args.Command == NavigationCommands.FirstPage)
{
dv.FirstPage();
}
else if (args.Command == NavigationCommands.LastPage)
{
dv.LastPage();
}
else
{
Invariant.Assert(false, "Command not handled in ExecutedRoutedEventHandler.");
}
}
/// <summary>
/// Helper for the FitToMaxPagesAcross Command, called from ExecutedRoutedEventHandler.
/// Verifies that the data passed into ExecutedRoutedEventHandler is valid for a column count
/// and sets the MaxPagesAcross property appropriately.
/// </summary>
/// <param name="dv">the DocumentViewer that received the command</param>
/// <param name="data">the data associated with this command</param>
private static void DoFitToMaxPagesAcross(DocumentViewer dv, object data)
{
// Check that args is valid
ArgumentNullException.ThrowIfNull(data);
int columnValue = 0;
bool isValidArg = true;
// If data is an int, then cast
if (data is int)
{
columnValue = (int)data;
}
// If args.Data is a string, then parse
else if (data is string)
{
try
{
columnValue = System.Convert.ToInt32((string)data, CultureInfo.CurrentCulture);
}
// Catch only the expected parse exceptions
catch (ArgumentNullException)
{
isValidArg = false;
}
catch (FormatException)
{
isValidArg = false;
}
catch (OverflowException)
{
isValidArg = false;
}
}
// Argument wasn't a valid int, throw an exception.
if (!isValidArg)
{
throw new ArgumentException(SR.DocumentViewerArgumentMustBeInteger, "data");
}
dv.OnFitToMaxPagesAcrossCommand(columnValue);
}
/// <summary>
/// Helper for the Zoom Command, called from ExecutedRoutedEventHandler.
/// Verifies that the data passed into ExecutedRoutedEventHandler is valid for a zoom factor
/// and sets the Zoom property appropriately.
/// </summary>
/// <param name="dv">The DocumentViewer that recieved the command</param>
/// <param name="data">the data associated with this command</param>
private static void DoZoom(DocumentViewer dv, object data)
{
// Check that args is valid
ArgumentNullException.ThrowIfNull(data);
// If a ZoomConverter doesn't exist, create one.
if (dv._zoomPercentageConverter == null)
{
dv._zoomPercentageConverter = new ZoomPercentageConverter();
}
// Use ZoomConverter to convert argument to zoom value.
// We use InvariantCulture because the Command arguments are typically
// defined in XAML or code, which is culture invariant.
object zoomValue = dv._zoomPercentageConverter.ConvertBack(data, typeof(double),
null, CultureInfo.InvariantCulture);
// Argument wasn't a valid percent, throw an exception.
if (zoomValue == DependencyProperty.UnsetValue)
{
throw new ArgumentException(SR.DocumentViewerArgumentMustBePercentage, "data");
}
dv.Zoom = (double)zoomValue;
}
#endregion Commands
/// <summary>
/// Register our properties' metadata so that our DependencyProperties function.
/// </summary>
private static void RegisterMetadata()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DocumentViewer), new FrameworkPropertyMetadata(typeof(DocumentViewer)));
_dType = DependencyObjectType.FromSystemTypeInternal(typeof(DocumentViewer));
}
/// <summary>
/// Initializes our DocumentScrollInfo.
/// </summary>
private void SetUp()
{
// Enable selection.
IsSelectionEnabled = true;
// Set the TextBox.AcceptsReturn flag -- this will disable the "select everything on focus"
// behavior of the TextEditor.
// We reset the TextBox.AcceptsTab property to keep the TextEditor from eating Tab.
SetValue(TextBox.AcceptsTabProperty, false);
// Construct the DocumentGrid.
CreateIDocumentScrollInfo();
}
/// <summary>
/// CreateIDocumentScrollInfo instantiates our IDocumentScrollInfo control
/// and sets/resets default properties.
/// </summary>
private void CreateIDocumentScrollInfo()
{
if (_documentScrollInfo == null)
{
// Construct IDocumentScrollInfo (DocumentGrid).
_documentScrollInfo = new DocumentGrid();
_documentScrollInfo.DocumentViewerOwner = this;
//If IDocumentScrollInfo is a FrameworkElement we can give it a
//Name for automation.
FrameworkElement fe = _documentScrollInfo as FrameworkElement;
if (fe != null)
{
fe.Name = "DocumentGrid";
fe.Focusable = false;
//We don't allow Tabbing to the IDocumentScrollInfo --
//The ScrollViewer parent is what is tabbed to.
fe.SetValue(KeyboardNavigation.IsTabStopProperty, false);
TextEditorRenderScope = fe;
}
}
//Assign our content to the IDSI.
AttachContent();
//Give the IDocumentScrollInfo object default values for important properties.
_documentScrollInfo.VerticalPageSpacing = VerticalPageSpacing;
_documentScrollInfo.HorizontalPageSpacing = HorizontalPageSpacing;
}
/// <summary>
/// Assigns our current Document to our IDSI and gives it a reference to our TextEditor.
/// </summary>
private void AttachContent()
{
_documentScrollInfo.Content = (Document != null) ? Document.DocumentPaginator as DynamicDocumentPaginator : null;
IsSelectionEnabled = true;
}
/// <summary>
/// FindContentHost does 2 things:
/// - It finds "marked" elements (elements with ContentHost attached properties)
/// in the current Visual Tree.
/// - It takes these elements and populates them with the proper UI (for Content)
/// </summary>
/// <exception cref="NotSupportedException">There must be a ScrollViewer in
/// DocumentViewer's visual tree which has the Name PART_ContentHost.</exception>
private void FindContentHost()
{
// Find the "special" element in the tree marked as the
// ContentHost. This element must exist or we throw.
ScrollViewer contentHost = this.Template.FindName(_contentHostName, this) as ScrollViewer;
// Make sure contentHost exists. This wouldn't be much of a DocumentViewer if it didn't,
// since we need someplace to throw our IDocumentScrollInfo so we can display documents.
// Throw an exception if it doesn't exist.
if (contentHost == null)
{
throw new NotSupportedException(SR.DocumentViewerStyleMustIncludeContentHost);
}
_scrollViewer = contentHost;
_scrollViewer.Focusable = false;
Invariant.Assert(_documentScrollInfo != null, "IDocumentScrollInfo cannot be null.");
//Make the IDSI the child of the ScrollViewer.
_scrollViewer.Content = _documentScrollInfo;
_scrollViewer.ScrollInfo = _documentScrollInfo;
// Set IDocumentScrollInfo's content if its content is invalid.
if (_documentScrollInfo.Content != Document)
{
AttachContent();
}
}
#region Find
/// <summary>
/// Instantiates the Find Toolbar and adds it to our Visual tree where appropriate.
/// </summary>
private void InstantiateFindToolBar()
{
//First, find the correct place to insert toolbar.
// Location is defined by named element, FindToolbarHost.
ContentControl findHost = this.Template.FindName(_findToolBarHostName, this) as ContentControl;
// Only create and hook up the toolbar, if we found a place to put it.
if (findHost != null)
{
if( _findToolbar == null )
{
// create the new object
_findToolbar = new FindToolBar();
// add the event handlers
_findToolbar.FindClicked += new EventHandler(OnFindInvoked);
//set initial DocumentLoaded state.
_findToolbar.DocumentLoaded = (Document != null) ? true : false;
}
// Now insert the toolbar, if it isn't already parented elsewhere.
// (It will have been disconnected from DocumentViewer on a Theme or
// Template change.)
if (!_findToolbar.IsAncestorOf(this))
{
((IAddChild)findHost).AddChild(_findToolbar);
}
}
}
/// <summary>
/// Invoked when the "Find" button in the Find Toolbar is clicked.
/// This method invokes the actual Find process.
/// </summary>
/// <param name="sender">The object that sent this event</param>
/// <param name="e">The Click Events associated with this event</param>
private void OnFindInvoked(object sender, EventArgs e)
{
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXFindBegin);
try
{
if (_findToolbar != null && TextEditor != null)
{
ITextRange findResult = Find(_findToolbar);
// If we found something, select it.
if ((findResult != null) && (!findResult.IsEmpty))
{
//Give ourselves focus, this ensures that the selection
//will be made visible after it's made.
this.Focus();
if (_documentScrollInfo != null)
{
_documentScrollInfo.MakeSelectionVisible();
}
//Put the focus back on the Find Toolbar's TextBox to search again.
_findToolbar.GoToTextBox();
}
else
{
// No, we did not find anything. Alert the user.
// build our message string.
string messageString = _findToolbar.SearchUp ?
SR.DocumentViewerSearchUpCompleteLabel :
SR.DocumentViewerSearchDownCompleteLabel;
messageString = String.Format(
CultureInfo.CurrentCulture,
messageString,
_findToolbar.SearchText);
Window wnd = null;
if (Application.Current != null && Application.Current.CheckAccess())
{
wnd = Application.Current.MainWindow;
}
MS.Internal.PresentationFramework.SecurityHelper.ShowMessageBoxHelper(
wnd,
messageString,
SR.DocumentViewerSearchCompleteTitle,
MessageBoxButton.OK,
MessageBoxImage.Asterisk);
}
}
}
finally
{
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXFindEnd);
}
}
/// <summary>
/// This is just a private convenience method for handling the Find command in a
/// localized place.
/// </summary>
private void GoToFind()
{
if (_findToolbar != null)
{
_findToolbar.GoToTextBox();
}
}
/// <summary>
/// This is just a private convenience method to handle those keyboard
/// shortcuts related to the Find Command.
/// localized place.
/// </summary>
private KeyEventArgs ProcessFindKeys(KeyEventArgs e)
{
if (_findToolbar == null || Document == null)
{
// Short-circuit. Find isn't enabled,
// just exit.
return e;
}
// F3 -- Invoke Find
if (e.Key == Key.F3)
{
e.Handled = true;
//If the Shift key is also pressed, then search up.
_findToolbar.SearchUp = ((e.KeyboardDevice.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
OnFindInvoked(this, EventArgs.Empty);
}
return e;
}
#endregion Find
/// <summary>
/// Find the location in the zoomLevelCollection such that it is the closest
/// zoomLevel equal to or lower than the current zoom.
/// </summary>
private void FindZoomLevelIndex()
{
// If the index is not in a valid location, update it to the list start.
if ((_zoomLevelIndex < 0) || (_zoomLevelIndex >= ZoomLevelCollection.Length))
{
_zoomLevelIndex = 0;
_zoomLevelIndexValid = false;
}
// Check if the current index is in the correct location in the list.
if (!_zoomLevelIndexValid)
{
// Since the index is not in the correct location in the list
// (ie the Zoom was set by another means), then
// search the list of possible zooms for the correct location.
double currentZoom = Zoom;
// Currently this search is done using a linear method which is
// fine given the small size of the list. If we increase the list
// size, then a simple binary search could increaes performance.
// Search the list of zoom's from highest to lowest until the
// appropriate "floor" level (level equal to, or
// lower than the current zoom) is found.
int loopIndex;
for (loopIndex = 0; loopIndex < ZoomLevelCollection.Length - 1; loopIndex++)
{
if (currentZoom >= ZoomLevelCollection[loopIndex])
{
// Closest equal or lower match found
break;
}
}
// Assign the current zoom level, and mark that our index is valid
// (for future Increase / Decrease zoom calls).
_zoomLevelIndex = loopIndex;
_zoomLevelIndexValid = true;
}
}
/// <summary>
/// Determines if the parameter represents a valid double (that is a value other
/// than NaN, PositiveInfinity, or NegativeInfinity).
/// </summary>
/// <param name="value">The double value to be checked</param>
/// <returns>True if the double value is valid, false otherwise.</returns>
private static bool DoubleValue_Validate(object value)
{
// Place this helper method
// in a location with standard utilities.
bool ok;
// Ensure value is double
if (value is double)
{
double checkValue = (double)value;
// Check if double is within an assumed range
if ((double.IsNaN(checkValue)) ||
(double.IsInfinity(checkValue)))
{
ok = false;
}
else
{
ok = true;
}
}
else
{
ok = false;
}
return ok;
}
/// <summary>
/// Converts an IDocumentScrollInfo's Scale value to a Zoom
/// </summary>
/// <param name="scale">A valid IDocumentScrollInfo.Scale value.</param>
/// <returns>A Zoom value</returns>
private static double ScaleToZoom(double scale)
{
return scale * 100.0;
}
/// <summary>
/// Converts a Zoom value to an IDocumentScrollInfo.Scale value.
/// </summary>
/// <param name="zoom">A valid Zoom value.</param>
/// <returns>An IDocumentScrollInfo.Scale value.</returns>
private static double ZoomToScale(double zoom)
{
return zoom / 100.0;
}
#region DependencyProperties
#region HorizontalOffset
/// <summary>
/// Validate the value passed in as a possible offset (either vertical or horizontal)
/// </summary>
/// <param name="value">A double representing the requested offset</param>
/// <returns>True if the offset is valid, false otherwise.</returns>
private static bool ValidateOffset(object value)
{
return DoubleValue_Validate(value) && ((double)value >= 0.0);
}
/// <summary>
/// The HorizontalOffset has changed, and needs to be updated.
/// </summary>
private static void OnHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
double newOffset = (double) e.NewValue;
// If the HorizontalOffset has changed and it isn't due to a change that originated
// from our IDocumentScrollInfo object, then set the new offset value on the IDocumentScrollInfo.
if (!dv._internalIDSIChange && (dv._documentScrollInfo != null))
{
dv._documentScrollInfo.SetHorizontalOffset(newOffset);
}
dv.SetValue(CanMoveLeftPropertyKey, newOffset > 0.0);
dv.SetValue(CanMoveRightPropertyKey, newOffset < (dv.ExtentWidth - dv.ViewportWidth));
}
#endregion HorizontalOffset
#region VerticalOffset
/// <summary>
/// The VerticalOffset has changed, and needs to be updated.
/// </summary>
private static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
double newOffset = (double) e.NewValue;
// If the VerticalOffset has changed and it isn't due to a change that originated
// from our IDocumentScrollInfo object, then set the new offset value on the IDocumentScrollInfo.
if ( !dv._internalIDSIChange && (dv._documentScrollInfo != null))
{
dv._documentScrollInfo.SetVerticalOffset(newOffset);
}
dv.SetValue(CanMoveUpPropertyKey, newOffset > 0.0);
dv.SetValue(CanMoveDownPropertyKey, newOffset < (dv.ExtentHeight - dv.ViewportHeight));
}
#endregion VerticalOffset
#region ExtentWidth
private static void OnExtentWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
dv.SetValue(CanMoveRightPropertyKey, dv.HorizontalOffset < ((double) e.NewValue - dv.ViewportWidth));
}
#endregion ExtentWidth
#region ExtentHeight
private static void OnExtentHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
dv.SetValue(CanMoveDownPropertyKey, dv.VerticalOffset < ((double) e.NewValue - dv.ViewportHeight));
}
#endregion ExtentHeight
#region ViewportWidth
private static void OnViewportWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
double newWidth = (double) e.NewValue;
dv.SetValue(CanMoveRightPropertyKey, dv.HorizontalOffset < (dv.ExtentWidth - (double) e.NewValue));
}
#endregion ViewportWidth
#region ViewportHeight
private static void OnViewportHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
double newHeight = (double) e.NewValue;
dv.SetValue(CanMoveDownPropertyKey, dv.VerticalOffset < (dv.ExtentHeight - newHeight));
}
#endregion ViewportHeight
#region ShowPageBorders
/// <summary>
/// ShowPageBorders has changed, and needs to be updated.
/// </summary>
private static void OnShowPageBordersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer) d;
// If the ShowPageBorders has changed, then set the new value on the IDocumentScrollInfo.
if (dv._documentScrollInfo != null)
{
dv._documentScrollInfo.ShowPageBorders = (bool) e.NewValue;
}
}
#endregion ShowPageBorders
#region Zoom
private static object CoerceZoom(DependencyObject d, object value)
{
double checkValue = (double) value;
if (checkValue < DocumentViewerConstants.MinimumZoom)
{
return DocumentViewerConstants.MinimumZoom;
}
if (checkValue > DocumentViewerConstants.MaximumZoom)
{
return DocumentViewerConstants.MaximumZoom;
}
return value;
}
/// <summary>
/// The Zoom has changed, and needs to be updated.
/// </summary>
private static void OnZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
// If the ZoomPercentage has changed, then update the IDocumentScrollInfo.
if (dv._documentScrollInfo != null)
{
double newZoom = (double) e.NewValue;
if (!dv._internalIDSIChange)
{
//Perf Tracing - Mark Zoom Change Start
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXZoom, (int)newZoom);
// Set the new zoom scale on IDocumentScrollInfo
dv._documentScrollInfo.SetScale(DocumentViewer.ZoomToScale(newZoom));
}
dv.SetValue(CanIncreaseZoomPropertyKey, newZoom < DocumentViewerConstants.MaximumZoom);
dv.SetValue(CanDecreaseZoomPropertyKey, newZoom > DocumentViewerConstants.MinimumZoom);
// If the zoom was not set by Increase / DecreaseZoom commands,
// then invalidate the zoom level index.
if (!dv._updatingInternalZoomLevel)
{
dv._zoomLevelIndexValid = false;
}
}
}
#endregion ZoomPercentage
#region MaxPagesAcross
/// <summary>
/// Validate the value passed in as a possible MaxPagesAcross
/// </summary>
/// <param name="value">An int MaxPagesAcross value</param>
/// <returns>True if the value is valid, false otherwise.</returns>
private static bool ValidateMaxPagesAcross(object value)
{
int checkValue = (int)value;
return checkValue > 0 && checkValue <= DocumentViewerConstants.MaximumMaxPagesAcross;
}
/// <summary>
/// The MaxPagesAcross has changed, and needs to be updated.
/// </summary>
private static void OnMaxPagesAcrossChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
//regardless of whether the value of MaxPagesAcross has actually changed.
//Setting this property will cause content to reflow or rescale to fit and we
//want this to happen even if the same number of columns will be visible.
//As an example, if we're at 500% showing 1 page, setting MaxPagesAcross to 1 will
//cause the Zoom to change to show precisely one column.
if (!dv._internalIDSIChange)
{
dv._documentScrollInfo.SetColumns((int) e.NewValue);
}
}
#endregion GridColumnCount
#region VerticalPageSpacing
/// <summary>
/// Validate the value passed in as a possible PageSpacing, either vertical or horizontal
/// </summary>
/// <param name="value">A value representing the requested page spacing</param>
/// <returns>True if the offset is valid, false otherwise.</returns>
private static bool ValidatePageSpacing(object value)
{
return DoubleValue_Validate(value) && ((double)value >= 0.0);
}
/// <summary>
/// The VerticalPageSpacing has changed, and needs to be updated.
/// </summary>
private static void OnVerticalPageSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
// If the VerticalPageSpacing has changed, then set the new value on IDocumentScrollInfo.
if (dv._documentScrollInfo != null)
{
dv._documentScrollInfo.VerticalPageSpacing = (double) e.NewValue;
}
}
#endregion VerticalPageSpacing
#region HorizontalPageSpacing
/// <summary>
/// The HorizontalPageSpacing has changed, and needs to be updated.
/// </summary>
private static void OnHorizontalPageSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DocumentViewer dv = (DocumentViewer)d;
// If the HorizontalPageSpacing has changed, then set the new value on IDocumentScrollInfo.
if (dv._documentScrollInfo != null)
{
dv._documentScrollInfo.HorizontalPageSpacing = (double) e.NewValue;
}
}
#endregion HorizontalPageSpacing
#endregion Dependency Properties
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
// UI Elements
private IDocumentScrollInfo _documentScrollInfo;
private ScrollViewer _scrollViewer;
private ZoomPercentageConverter _zoomPercentageConverter;
//Find ToolBar
private FindToolBar _findToolbar; // The FindToolbar UI
// Default values for DPs
private const double _horizontalOffsetDefault = 0.0;
private const double _verticalOffsetDefault = 0.0;
private const double _extentWidthDefault = 0.0;
private const double _extentHeightDefault = 0.0;
private const double _viewportWidthDefault = 0.0;
private const double _viewportHeightDefault = 0.0;
private const bool _showPageBordersDefault = true;
private const double _zoomPercentageDefault = 100.0;
private const int _maxPagesAcrossDefault = 1;
private const double _verticalPageSpacingDefault = 10.0;
private const double _horizontalPageSpacingDefault = 10.0;
private const bool _canMoveUpDefault = false;
private const bool _canMoveDownDefault = false;
private const bool _canMoveLeftDefault = false;
private const bool _canMoveRightDefault = false;
private const bool _canIncreaseZoomDefault = true;
private const bool _canDecreaseZoomDefault = true;
// private const bool _isToolbarMaximizedDefault = true;
// Our Commands, instantiated on a pay-for-play basis
private static RoutedUICommand _viewThumbnailsCommand;
private static RoutedUICommand _fitToWidthCommand;
private static RoutedUICommand _fitToHeightCommand;
private static RoutedUICommand _fitToMaxPagesAcrossCommand;
// This list is assumed to be in decreasing order.
private static ReadOnlySpan<double> ZoomLevelCollection => [5000.0, 4000.0, 3200.0, 2400.0, 2000.0, 1600.0,
1200.0, 800.0, 400.0, 300.0, 200.0, 175.0, 150.0,
125.0, 100.0, 75.0, 66.0, 50.0, 33.0, 25.0, 10.0, 5.0];
private int _zoomLevelIndex; // = 0
private bool _zoomLevelIndexValid; // = false
private bool _updatingInternalZoomLevel; // = false
private bool _internalIDSIChange; // = false
private bool _pageViewCollectionChanged; // = false
private bool _firstDocumentAssignment = true;
private const string _findToolBarHostName = "PART_FindToolBarHost";
private const string _contentHostName = "PART_ContentHost";
#endregion
#region DTypeThemeStyleKey
// Returns the DependencyObjectType for the registered ThemeStyleKey's default
// value. Controls will override this method to return approriate types.
internal override DependencyObjectType DTypeThemeStyleKey
{
get { return _dType; }
}
private static DependencyObjectType _dType;
#endregion DTypeThemeStyleKey
}
}
|