3980 lines
128 KiB
C#
3980 lines
128 KiB
C#
/*************************************************************************************
|
|
|
|
Extended WPF Toolkit
|
|
|
|
Copyright (C) 2007-2013 Xceed Software Inc.
|
|
|
|
This program is provided to you under the terms of the Microsoft Public
|
|
License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license
|
|
|
|
For more features, controls, and fast professional support,
|
|
pick up the Plus Edition at http://xceed.com/wpf_toolkit
|
|
|
|
Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids
|
|
|
|
***********************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Data;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using Xceed.Wpf.Toolkit.Core.Input;
|
|
using Xceed.Wpf.Toolkit.Core;
|
|
using Xceed.Wpf.Toolkit.Core.Utilities;
|
|
|
|
namespace Xceed.Wpf.Toolkit.Zoombox
|
|
{
|
|
[TemplatePart( Name = PART_VerticalScrollBar, Type = typeof( ScrollBar ) )]
|
|
[TemplatePart( Name = PART_HorizontalScrollBar, Type = typeof( ScrollBar ) )]
|
|
public sealed class Zoombox : ContentControl
|
|
{
|
|
private const string PART_VerticalScrollBar = "PART_VerticalScrollBar";
|
|
private const string PART_HorizontalScrollBar = "PART_HorizontalScrollBar";
|
|
|
|
#region Constructors
|
|
|
|
static Zoombox()
|
|
{
|
|
Zoombox.DefaultStyleKeyProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( typeof( Zoombox ) ) );
|
|
Zoombox.ClipToBoundsProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( true ) );
|
|
Zoombox.FocusableProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( true ) );
|
|
Zoombox.HorizontalContentAlignmentProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( HorizontalAlignment.Center, new PropertyChangedCallback( Zoombox.RefocusView ) ) );
|
|
Zoombox.VerticalContentAlignmentProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( VerticalAlignment.Center, new PropertyChangedCallback( Zoombox.RefocusView ) ) );
|
|
Zoombox.ContentProperty.OverrideMetadata( typeof( Zoombox ), new FrameworkPropertyMetadata( ( PropertyChangedCallback )null, new CoerceValueCallback( Zoombox.CoerceContentValue ) ) );
|
|
}
|
|
|
|
public Zoombox()
|
|
: base()
|
|
{
|
|
try
|
|
{
|
|
new UIPermission( PermissionState.Unrestricted ).Demand();
|
|
_cacheBits[ ( int )CacheBits.HasUIPermission ] = true;
|
|
}
|
|
catch( SecurityException )
|
|
{
|
|
}
|
|
|
|
this.InitCommands();
|
|
|
|
// use the LayoutUpdated event to keep the Viewport in sync
|
|
this.LayoutUpdated += new EventHandler( this.OnLayoutUpdated );
|
|
this.AddHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnSizeChanged ), true );
|
|
|
|
this.CoerceValue( Zoombox.ViewStackModeProperty );
|
|
|
|
this.Loaded += this.Zoombox_Loaded;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AnimationAccelerationRatio Property
|
|
|
|
public static readonly DependencyProperty AnimationAccelerationRatioProperty =
|
|
DependencyProperty.Register( "AnimationAccelerationRatio", typeof( double ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( 0d ),
|
|
new ValidateValueCallback( Zoombox.ValidateAccelerationRatio ) );
|
|
|
|
public double AnimationAccelerationRatio
|
|
{
|
|
get
|
|
{
|
|
return ( double )this.GetValue( Zoombox.AnimationAccelerationRatioProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.AnimationAccelerationRatioProperty, value );
|
|
}
|
|
}
|
|
|
|
private static bool ValidateAccelerationRatio( object value )
|
|
{
|
|
double newValue = ( double )value;
|
|
if( newValue < 0 || newValue > 1 || DoubleHelper.IsNaN( newValue ) )
|
|
throw new ArgumentException( ErrorMessages.GetMessage( "AnimationAccelerationRatioOOR" ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AnimationDecelerationRatio Property
|
|
|
|
public static readonly DependencyProperty AnimationDecelerationRatioProperty =
|
|
DependencyProperty.Register( "AnimationDecelerationRatio", typeof( double ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( 0d ),
|
|
new ValidateValueCallback( Zoombox.ValidateDecelerationRatio ) );
|
|
|
|
public double AnimationDecelerationRatio
|
|
{
|
|
get
|
|
{
|
|
return ( double )this.GetValue( Zoombox.AnimationDecelerationRatioProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.AnimationDecelerationRatioProperty, value );
|
|
}
|
|
}
|
|
|
|
private static bool ValidateDecelerationRatio( object value )
|
|
{
|
|
double newValue = ( double )value;
|
|
if( newValue < 0 || newValue > 1 || DoubleHelper.IsNaN( newValue ) )
|
|
throw new ArgumentException( ErrorMessages.GetMessage( "AnimationDecelerationRatioOOR" ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AnimationDuration Property
|
|
|
|
public static readonly DependencyProperty AnimationDurationProperty =
|
|
DependencyProperty.Register( "AnimationDuration", typeof( Duration ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( new Duration( TimeSpan.FromMilliseconds( 300 ) ) ) );
|
|
|
|
public Duration AnimationDuration
|
|
{
|
|
get
|
|
{
|
|
return ( Duration )this.GetValue( Zoombox.AnimationDurationProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.AnimationDurationProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AreDragModifiersActive Property
|
|
|
|
private static readonly DependencyPropertyKey AreDragModifiersActivePropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "AreDragModifiersActive", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty AreDragModifiersActiveProperty = Zoombox.AreDragModifiersActivePropertyKey.DependencyProperty;
|
|
|
|
public bool AreDragModifiersActive
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.AreDragModifiersActiveProperty );
|
|
}
|
|
}
|
|
|
|
private void SetAreDragModifiersActive( bool value )
|
|
{
|
|
this.SetValue( Zoombox.AreDragModifiersActivePropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AreRelativeZoomModifiersActive Property
|
|
|
|
private static readonly DependencyPropertyKey AreRelativeZoomModifiersActivePropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "AreRelativeZoomModifiersActive", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty AreRelativeZoomModifiersActiveProperty = Zoombox.AreRelativeZoomModifiersActivePropertyKey.DependencyProperty;
|
|
|
|
public bool AreRelativeZoomModifiersActive
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.AreRelativeZoomModifiersActiveProperty );
|
|
}
|
|
}
|
|
|
|
private void SetAreRelativeZoomModifiersActive( bool value )
|
|
{
|
|
this.SetValue( Zoombox.AreRelativeZoomModifiersActivePropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AreZoomModifiersActive Property
|
|
|
|
private static readonly DependencyPropertyKey AreZoomModifiersActivePropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "AreZoomModifiersActive", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty AreZoomModifiersActiveProperty = Zoombox.AreZoomModifiersActivePropertyKey.DependencyProperty;
|
|
|
|
public bool AreZoomModifiersActive
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.AreZoomModifiersActiveProperty );
|
|
}
|
|
}
|
|
|
|
private void SetAreZoomModifiersActive( bool value )
|
|
{
|
|
this.SetValue( Zoombox.AreZoomModifiersActivePropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AreZoomToSelectionModifiersActive Property
|
|
|
|
private static readonly DependencyPropertyKey AreZoomToSelectionModifiersActivePropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "AreZoomToSelectionModifiersActive", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty AreZoomToSelectionModifiersActiveProperty = Zoombox.AreZoomToSelectionModifiersActivePropertyKey.DependencyProperty;
|
|
|
|
public bool AreZoomToSelectionModifiersActive
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.AreZoomToSelectionModifiersActiveProperty );
|
|
}
|
|
}
|
|
|
|
private void SetAreZoomToSelectionModifiersActive( bool value )
|
|
{
|
|
this.SetValue( Zoombox.AreZoomToSelectionModifiersActivePropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AutoWrapContentWithViewbox Property
|
|
|
|
public static readonly DependencyProperty AutoWrapContentWithViewboxProperty =
|
|
DependencyProperty.Register( "AutoWrapContentWithViewbox", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( true,
|
|
new PropertyChangedCallback( Zoombox.OnAutoWrapContentWithViewboxChanged ) ) );
|
|
|
|
public bool AutoWrapContentWithViewbox
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.AutoWrapContentWithViewboxProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.AutoWrapContentWithViewboxProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnAutoWrapContentWithViewboxChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
o.CoerceValue( Zoombox.ContentProperty );
|
|
}
|
|
|
|
private static object CoerceContentValue( DependencyObject d, object value )
|
|
{
|
|
return ( ( Zoombox )d ).CoerceContentValue( value );
|
|
}
|
|
|
|
private object CoerceContentValue( object value )
|
|
{
|
|
if( value != null && !( value is UIElement ) && !( ( bool )this.GetValue( DesignerProperties.IsInDesignModeProperty ) ) )
|
|
throw new InvalidContentException( ErrorMessages.GetMessage( "ZoomboxContentMustBeUIElement" ) );
|
|
|
|
object oldContent = _content;
|
|
if( value != _trueContent || ( this.IsContentWrapped != this.AutoWrapContentWithViewbox ) )
|
|
{
|
|
// check whether the content is currently wrapped and needs to be unwrapped
|
|
if( this.IsContentWrapped && _content is Viewbox && _content != _trueContent )
|
|
{
|
|
Viewbox viewbox = ( Viewbox )_content;
|
|
|
|
BindingOperations.ClearAllBindings( viewbox );
|
|
( viewbox as Viewbox ).Child = null;
|
|
|
|
this.RemoveLogicalChild( viewbox );
|
|
}
|
|
|
|
// make sure the view finder's visual brush is null
|
|
if( _viewFinderDisplay != null && _viewFinderDisplay.VisualBrush != null )
|
|
{
|
|
_viewFinderDisplay.VisualBrush.Visual = null;
|
|
_viewFinderDisplay.VisualBrush = null;
|
|
}
|
|
|
|
// update the cached content and true content values
|
|
_content = value as UIElement;
|
|
_trueContent = value as UIElement;
|
|
|
|
// if necessary, unparent the existing content
|
|
if( _contentPresenter != null && _contentPresenter.Content != null )
|
|
{
|
|
_contentPresenter.Content = null;
|
|
}
|
|
|
|
// if necessary, wrap the content
|
|
this.IsContentWrapped = false;
|
|
if( this.AutoWrapContentWithViewbox )
|
|
{
|
|
// create a viewbox and make it the logical child of the Zoombox
|
|
Viewbox viewbox = new Viewbox();
|
|
this.AddLogicalChild( viewbox );
|
|
|
|
// now set the new parent to be the viewbox
|
|
viewbox.Child = value as UIElement;
|
|
_content = viewbox;
|
|
viewbox.HorizontalAlignment = HorizontalAlignment.Left;
|
|
viewbox.VerticalAlignment = VerticalAlignment.Top;
|
|
this.IsContentWrapped = true;
|
|
}
|
|
|
|
if( _contentPresenter != null )
|
|
{
|
|
_contentPresenter.Content = _content;
|
|
}
|
|
|
|
if( _viewFinderDisplay != null )
|
|
{
|
|
this.CreateVisualBrushForViewFinder( _content );
|
|
}
|
|
this.UpdateViewFinderDisplayContentBounds();
|
|
}
|
|
|
|
// if the content changes, we need to reset the flags used to first render and arrange the content
|
|
if( oldContent != _content
|
|
&& this.HasArrangedContentPresenter
|
|
&& this.HasRenderedFirstView )
|
|
{
|
|
this.HasArrangedContentPresenter = false;
|
|
this.HasRenderedFirstView = false;
|
|
this.RefocusViewOnFirstRender = true;
|
|
_contentPresenter.LayoutUpdated += new EventHandler( this.ContentPresenterFirstArranged );
|
|
}
|
|
return _content;
|
|
}
|
|
|
|
private UIElement _trueContent; //null
|
|
|
|
#endregion
|
|
|
|
#region CurrentView Property
|
|
|
|
private static readonly DependencyPropertyKey CurrentViewPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "CurrentView", typeof( ZoomboxView ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( ZoomboxView.Empty,
|
|
new PropertyChangedCallback( Zoombox.OnCurrentViewChanged ) ) );
|
|
|
|
public static readonly DependencyProperty CurrentViewProperty = Zoombox.CurrentViewPropertyKey.DependencyProperty;
|
|
|
|
public ZoomboxView CurrentView
|
|
{
|
|
get
|
|
{
|
|
return ( ZoomboxView )this.GetValue( Zoombox.CurrentViewProperty );
|
|
}
|
|
}
|
|
|
|
private void SetCurrentView( ZoomboxView value )
|
|
{
|
|
this.SetValue( Zoombox.CurrentViewPropertyKey, value );
|
|
}
|
|
|
|
private static void OnCurrentViewChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )o;
|
|
if( !zoombox.IsUpdatingView )
|
|
{
|
|
zoombox.ZoomTo( zoombox.CurrentView );
|
|
}
|
|
zoombox.RaiseEvent( new ZoomboxViewChangedEventArgs( e.OldValue as ZoomboxView, e.NewValue as ZoomboxView, zoombox._lastViewIndex, zoombox.CurrentViewIndex ) );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CurrentViewIndex Property
|
|
|
|
private static readonly DependencyPropertyKey CurrentViewIndexPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "CurrentViewIndex", typeof( int ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( -1 ) );
|
|
|
|
public static readonly DependencyProperty CurrentViewIndexProperty = Zoombox.CurrentViewIndexPropertyKey.DependencyProperty;
|
|
|
|
public int CurrentViewIndex
|
|
{
|
|
get
|
|
{
|
|
return ( int )this.GetValue( Zoombox.CurrentViewIndexProperty );
|
|
}
|
|
}
|
|
|
|
internal void SetCurrentViewIndex( int value )
|
|
{
|
|
this.SetValue( Zoombox.CurrentViewIndexPropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DragModifiers Property
|
|
|
|
public static readonly DependencyProperty DragModifiersProperty =
|
|
DependencyProperty.Register( "DragModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( Zoombox.GetDefaultDragModifiers() ) );
|
|
|
|
[TypeConverter( typeof( KeyModifierCollectionConverter ) )]
|
|
public KeyModifierCollection DragModifiers
|
|
{
|
|
get
|
|
{
|
|
return ( KeyModifierCollection )this.GetValue( Zoombox.DragModifiersProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.DragModifiersProperty, value );
|
|
}
|
|
}
|
|
|
|
private static KeyModifierCollection GetDefaultDragModifiers()
|
|
{
|
|
KeyModifierCollection result = new KeyModifierCollection();
|
|
result.Add( KeyModifier.Ctrl );
|
|
result.Add( KeyModifier.Exact );
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DragOnPreview Property
|
|
|
|
public static readonly DependencyProperty DragOnPreviewProperty =
|
|
DependencyProperty.Register( "DragOnPreview", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public bool DragOnPreview
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.DragOnPreviewProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.DragOnPreviewProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region EffectiveViewStackMode Property
|
|
|
|
private static readonly DependencyPropertyKey EffectiveViewStackModePropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "EffectiveViewStackMode", typeof( ZoomboxViewStackMode ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( ZoomboxViewStackMode.Auto ) );
|
|
|
|
public static readonly DependencyProperty EffectiveViewStackModeProperty = Zoombox.EffectiveViewStackModePropertyKey.DependencyProperty;
|
|
|
|
public ZoomboxViewStackMode EffectiveViewStackMode
|
|
{
|
|
get
|
|
{
|
|
return ( ZoomboxViewStackMode )this.GetValue( Zoombox.EffectiveViewStackModeProperty );
|
|
}
|
|
}
|
|
|
|
private void SetEffectiveViewStackMode( ZoomboxViewStackMode value )
|
|
{
|
|
this.SetValue( Zoombox.EffectiveViewStackModePropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region HasBackStack Property
|
|
|
|
private static readonly DependencyPropertyKey HasBackStackPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "HasBackStack", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty HasBackStackProperty = Zoombox.HasBackStackPropertyKey.DependencyProperty;
|
|
|
|
public bool HasBackStack
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.HasBackStackProperty );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region HasForwardStack Property
|
|
|
|
private static readonly DependencyPropertyKey HasForwardStackPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "HasForwardStack", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty HasForwardStackProperty = Zoombox.HasForwardStackPropertyKey.DependencyProperty;
|
|
|
|
public bool HasForwardStack
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.HasForwardStackProperty );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsAnimated Property
|
|
|
|
public static readonly DependencyProperty IsAnimatedProperty =
|
|
DependencyProperty.Register( "IsAnimated", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( true,
|
|
( PropertyChangedCallback )null, new CoerceValueCallback( Zoombox.CoerceIsAnimatedValue ) ) );
|
|
|
|
public bool IsAnimated
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.IsAnimatedProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.IsAnimatedProperty, value );
|
|
}
|
|
}
|
|
|
|
private static object CoerceIsAnimatedValue( DependencyObject d, object value )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )d;
|
|
bool result = ( bool )value;
|
|
if( !zoombox.IsInitialized )
|
|
{
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsDraggingContent Property
|
|
|
|
private static readonly DependencyPropertyKey IsDraggingContentPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "IsDraggingContent", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty IsDraggingContentProperty = Zoombox.IsDraggingContentPropertyKey.DependencyProperty;
|
|
|
|
public bool IsDraggingContent
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.IsDraggingContentProperty );
|
|
}
|
|
}
|
|
|
|
private void SetIsDraggingContent( bool value )
|
|
{
|
|
this.SetValue( Zoombox.IsDraggingContentPropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsSelectingRegion Property
|
|
|
|
private static readonly DependencyPropertyKey IsSelectingRegionPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "IsSelectingRegion", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public static readonly DependencyProperty IsSelectingRegionProperty = Zoombox.IsSelectingRegionPropertyKey.DependencyProperty;
|
|
|
|
public bool IsSelectingRegion
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.IsSelectingRegionProperty );
|
|
}
|
|
}
|
|
|
|
private void SetIsSelectingRegion( bool value )
|
|
{
|
|
this.SetValue( Zoombox.IsSelectingRegionPropertyKey, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsUsingScrollBars Property
|
|
|
|
public static readonly DependencyProperty IsUsingScrollBarsProperty =
|
|
DependencyProperty.Register( "IsUsingScrollBars", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false, ( PropertyChangedCallback )null ) );
|
|
|
|
public bool IsUsingScrollBars
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.IsUsingScrollBarsProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.IsUsingScrollBarsProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region MaxScale Property
|
|
|
|
public static readonly DependencyProperty MaxScaleProperty =
|
|
DependencyProperty.Register( "MaxScale", typeof( double ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( 100d, FrameworkPropertyMetadataOptions.AffectsMeasure,
|
|
new PropertyChangedCallback( Zoombox.OnMaxScaleChanged ), new CoerceValueCallback( Zoombox.CoerceMaxScaleValue ) ) );
|
|
|
|
public double MaxScale
|
|
{
|
|
get
|
|
{
|
|
return ( double )this.GetValue( Zoombox.MaxScaleProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.MaxScaleProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnMaxScaleChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )o;
|
|
zoombox.CoerceValue( Zoombox.MinScaleProperty );
|
|
zoombox.CoerceValue( Zoombox.ScaleProperty );
|
|
}
|
|
|
|
private static object CoerceMaxScaleValue( DependencyObject d, object value )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )d;
|
|
double result = ( double )value;
|
|
if( result < zoombox.MinScale )
|
|
{
|
|
result = zoombox.MinScale;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region MinScale Property
|
|
|
|
public static readonly DependencyProperty MinScaleProperty =
|
|
DependencyProperty.Register( "MinScale", typeof( double ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( 0.01d, FrameworkPropertyMetadataOptions.AffectsMeasure,
|
|
new PropertyChangedCallback( Zoombox.OnMinScaleChanged ), new CoerceValueCallback( Zoombox.CoerceMinScaleValue ) ) );
|
|
|
|
public double MinScale
|
|
{
|
|
get
|
|
{
|
|
return ( double )this.GetValue( Zoombox.MinScaleProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.MinScaleProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnMinScaleChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )o;
|
|
zoombox.CoerceValue( Zoombox.MinScaleProperty );
|
|
zoombox.CoerceValue( Zoombox.ScaleProperty );
|
|
}
|
|
|
|
private static object CoerceMinScaleValue( DependencyObject d, object value )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )d;
|
|
double result = ( double )value;
|
|
if( result > zoombox.MaxScale )
|
|
{
|
|
result = zoombox.MaxScale;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region NavigateOnPreview Property
|
|
|
|
public static readonly DependencyProperty NavigateOnPreviewProperty =
|
|
DependencyProperty.Register( "NavigateOnPreview", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false ) );
|
|
|
|
public bool NavigateOnPreview
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.NavigateOnPreviewProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.NavigateOnPreviewProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PanDistance Property
|
|
|
|
public static readonly DependencyProperty PanDistanceProperty =
|
|
DependencyProperty.Register( "PanDistance", typeof( double ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( 5d ) );
|
|
|
|
public double PanDistance
|
|
{
|
|
get
|
|
{
|
|
return ( double )this.GetValue( Zoombox.PanDistanceProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.PanDistanceProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Position Property
|
|
|
|
public static readonly DependencyProperty PositionProperty =
|
|
DependencyProperty.Register( "Position", typeof( Point ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( PointHelper.Empty, new PropertyChangedCallback( Zoombox.OnPositionChanged ) ) );
|
|
|
|
public Point Position
|
|
{
|
|
get
|
|
{
|
|
return ( Point )this.GetValue( Zoombox.PositionProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.PositionProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnPositionChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )o;
|
|
if( !zoombox.IsUpdatingViewport )
|
|
{
|
|
Point newPosition = ( Point )e.NewValue;
|
|
double scale = zoombox.Scale;
|
|
if( scale > 0 )
|
|
{
|
|
zoombox.ZoomTo( new Point( -newPosition.X, -newPosition.Y ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region RelativeZoomModifiers Property
|
|
|
|
public static readonly DependencyProperty RelativeZoomModifiersProperty =
|
|
DependencyProperty.Register( "RelativeZoomModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( Zoombox.GetDefaultRelativeZoomModifiers() ) );
|
|
|
|
[TypeConverter( typeof( KeyModifierCollectionConverter ) )]
|
|
public KeyModifierCollection RelativeZoomModifiers
|
|
{
|
|
get
|
|
{
|
|
return ( KeyModifierCollection )this.GetValue( Zoombox.RelativeZoomModifiersProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.RelativeZoomModifiersProperty, value );
|
|
}
|
|
}
|
|
|
|
private static KeyModifierCollection GetDefaultRelativeZoomModifiers()
|
|
{
|
|
KeyModifierCollection result = new KeyModifierCollection();
|
|
result.Add( KeyModifier.Ctrl );
|
|
result.Add( KeyModifier.Alt );
|
|
result.Add( KeyModifier.Exact );
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Scale Property
|
|
|
|
public static readonly DependencyProperty ScaleProperty =
|
|
DependencyProperty.Register( "Scale", typeof( double ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( double.NaN,
|
|
new PropertyChangedCallback( Zoombox.OnScaleChanged ), new CoerceValueCallback( Zoombox.CoerceScaleValue ) ) );
|
|
|
|
public double Scale
|
|
{
|
|
get
|
|
{
|
|
return ( double )this.GetValue( Zoombox.ScaleProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ScaleProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnScaleChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )o;
|
|
if( !zoombox.IsUpdatingView )
|
|
{
|
|
double newScale = ( double )e.NewValue;
|
|
zoombox.ZoomTo( newScale );
|
|
}
|
|
}
|
|
|
|
private static object CoerceScaleValue( DependencyObject d, object value )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )d;
|
|
double result = ( double )value;
|
|
|
|
if( result < zoombox.MinScale )
|
|
{
|
|
result = zoombox.MinScale;
|
|
}
|
|
|
|
if( result > zoombox.MaxScale )
|
|
{
|
|
result = zoombox.MaxScale;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewFinder Property
|
|
|
|
private static readonly DependencyPropertyKey ViewFinderPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "ViewFinder", typeof( FrameworkElement ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( null ) );
|
|
|
|
public static readonly DependencyProperty ViewFinderProperty = Zoombox.ViewFinderPropertyKey.DependencyProperty;
|
|
|
|
public FrameworkElement ViewFinder
|
|
{
|
|
get
|
|
{
|
|
return ( FrameworkElement )this.GetValue( Zoombox.ViewFinderProperty );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewFinderVisibility Attached Property
|
|
|
|
public static readonly DependencyProperty ViewFinderVisibilityProperty =
|
|
DependencyProperty.RegisterAttached( "ViewFinderVisibility", typeof( Visibility ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( Visibility.Visible ) );
|
|
|
|
public static Visibility GetViewFinderVisibility( DependencyObject d )
|
|
{
|
|
return ( Visibility )( d.GetValue( Zoombox.ViewFinderVisibilityProperty ) );
|
|
}
|
|
|
|
public static void SetViewFinderVisibility( DependencyObject d, Visibility value )
|
|
{
|
|
d.SetValue( Zoombox.ViewFinderVisibilityProperty, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Viewport Property
|
|
|
|
private static readonly DependencyPropertyKey ViewportPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "Viewport", typeof( Rect ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( Rect.Empty,
|
|
new PropertyChangedCallback( Zoombox.OnViewportChanged ) ) );
|
|
|
|
public static readonly DependencyProperty ViewportProperty = Zoombox.ViewportPropertyKey.DependencyProperty;
|
|
|
|
public Rect Viewport
|
|
{
|
|
get
|
|
{
|
|
return ( Rect )this.GetValue( Zoombox.ViewportProperty );
|
|
}
|
|
}
|
|
|
|
private static void OnViewportChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
// keep the Position property in sync with the Viewport
|
|
Zoombox zoombox = ( Zoombox )o;
|
|
zoombox.Position = new Point( -zoombox.Viewport.Left * zoombox.Scale / zoombox._viewboxFactor, -zoombox.Viewport.Top * zoombox.Scale / zoombox._viewboxFactor );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewStackCount Property
|
|
|
|
private static readonly DependencyPropertyKey ViewStackCountPropertyKey =
|
|
DependencyProperty.RegisterReadOnly( "ViewStackCount", typeof( int ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( -1,
|
|
new PropertyChangedCallback( Zoombox.OnViewStackCountChanged ) ) );
|
|
|
|
public static readonly DependencyProperty ViewStackCountProperty = Zoombox.ViewStackCountPropertyKey.DependencyProperty;
|
|
|
|
public int ViewStackCount
|
|
{
|
|
get
|
|
{
|
|
return ( int )this.GetValue( Zoombox.ViewStackCountProperty );
|
|
}
|
|
}
|
|
|
|
internal void SetViewStackCount( int value )
|
|
{
|
|
this.SetValue( Zoombox.ViewStackCountPropertyKey, value );
|
|
}
|
|
|
|
private static void OnViewStackCountChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
( ( Zoombox )d ).OnViewStackCountChanged( e );
|
|
}
|
|
|
|
private void OnViewStackCountChanged( DependencyPropertyChangedEventArgs e )
|
|
{
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
|
|
return;
|
|
|
|
this.UpdateStackProperties();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewStackIndex Property
|
|
|
|
public static readonly DependencyProperty ViewStackIndexProperty =
|
|
DependencyProperty.Register( "ViewStackIndex", typeof( int ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( -1,
|
|
new PropertyChangedCallback( Zoombox.OnViewStackIndexChanged ), new CoerceValueCallback( Zoombox.CoerceViewStackIndexValue ) ) );
|
|
|
|
public int ViewStackIndex
|
|
{
|
|
get
|
|
{
|
|
return ( int )this.GetValue( Zoombox.ViewStackIndexProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ViewStackIndexProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnViewStackIndexChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
( ( Zoombox )d ).OnViewStackIndexChanged( e );
|
|
}
|
|
|
|
private void OnViewStackIndexChanged( DependencyPropertyChangedEventArgs e )
|
|
{
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
|
|
return;
|
|
|
|
if( !this.IsUpdatingView )
|
|
{
|
|
int viewIndex = this.ViewStackIndex;
|
|
if( viewIndex >= 0 && viewIndex < ViewStack.Count )
|
|
{
|
|
// update the current view, but don't allow the new view
|
|
// to be added to the view stack
|
|
this.UpdateView( this.ViewStack[ viewIndex ], true, false, viewIndex );
|
|
}
|
|
}
|
|
|
|
this.UpdateStackProperties();
|
|
this.RaiseEvent( new IndexChangedEventArgs( Zoombox.ViewStackIndexChangedEvent, ( int )e.OldValue, ( int )e.NewValue ) );
|
|
}
|
|
|
|
private static object CoerceViewStackIndexValue( DependencyObject d, object value )
|
|
{
|
|
Zoombox zoombox = d as Zoombox;
|
|
return ( zoombox.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled ) ? -1 : value;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewStackMode Property
|
|
|
|
public static readonly DependencyProperty ViewStackModeProperty =
|
|
DependencyProperty.Register( "ViewStackMode", typeof( ZoomboxViewStackMode ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( ZoomboxViewStackMode.Default,
|
|
new PropertyChangedCallback( Zoombox.OnViewStackModeChanged ), new CoerceValueCallback( Zoombox.CoerceViewStackModeValue ) ) );
|
|
|
|
public ZoomboxViewStackMode ViewStackMode
|
|
{
|
|
get
|
|
{
|
|
return ( ZoomboxViewStackMode )this.GetValue( Zoombox.ViewStackModeProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ViewStackModeProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnViewStackModeChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
( ( Zoombox )d ).OnViewStackModeChanged( e );
|
|
}
|
|
|
|
private void OnViewStackModeChanged( DependencyPropertyChangedEventArgs e )
|
|
{
|
|
if( ( ZoomboxViewStackMode )e.NewValue == ZoomboxViewStackMode.Disabled && _viewStack != null )
|
|
{
|
|
_viewStack.ClearViewStackSource();
|
|
_viewStack = null;
|
|
}
|
|
}
|
|
|
|
private static object CoerceViewStackModeValue( DependencyObject d, object value )
|
|
{
|
|
Zoombox zoombox = d as Zoombox;
|
|
ZoomboxViewStackMode effectiveMode = ( ZoomboxViewStackMode )value;
|
|
|
|
// if the effective mode is currently disabled, it must be updated first
|
|
if( zoombox.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
|
|
{
|
|
zoombox.SetEffectiveViewStackMode( effectiveMode );
|
|
}
|
|
|
|
// now determine the correct effective mode
|
|
if( effectiveMode != ZoomboxViewStackMode.Disabled )
|
|
{
|
|
if( effectiveMode == ZoomboxViewStackMode.Default )
|
|
{
|
|
effectiveMode = ( zoombox.ViewStack.AreViewsFromSource ? ZoomboxViewStackMode.Manual : ZoomboxViewStackMode.Auto );
|
|
}
|
|
if( zoombox.ViewStack.AreViewsFromSource && ( ZoomboxViewStackMode )effectiveMode != ZoomboxViewStackMode.Manual )
|
|
{
|
|
throw new InvalidOperationException( ErrorMessages.GetMessage( "ViewModeInvalidForSource" ) );
|
|
}
|
|
}
|
|
|
|
// update the effective mode
|
|
zoombox.SetEffectiveViewStackMode( effectiveMode );
|
|
return value;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewStackSource Property
|
|
|
|
public static readonly DependencyProperty ViewStackSourceProperty =
|
|
DependencyProperty.Register( "ViewStackSource", typeof( IEnumerable ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( ( IEnumerable )null,
|
|
new PropertyChangedCallback( Zoombox.OnViewStackSourceChanged ) ) );
|
|
|
|
[Bindable( true )]
|
|
public IEnumerable ViewStackSource
|
|
{
|
|
get
|
|
{
|
|
return ( _viewStack == null ) ? null : ViewStack.Source;
|
|
}
|
|
set
|
|
{
|
|
if( value == null )
|
|
{
|
|
this.ClearValue( Zoombox.ViewStackSourceProperty );
|
|
}
|
|
else
|
|
{
|
|
this.SetValue( Zoombox.ViewStackSourceProperty, value );
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void OnViewStackSourceChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
Zoombox zoombox = ( Zoombox )d;
|
|
IEnumerable oldValue = ( IEnumerable )e.OldValue;
|
|
IEnumerable newValue = ( IEnumerable )e.NewValue;
|
|
|
|
// We need to know whether the new value represents an explicit null value
|
|
// or whether it came from a binding. The latter indicates that we stay in ViewStackSource mode,
|
|
// but with a null collection.
|
|
if( e.NewValue == null && !BindingOperations.IsDataBound( d, Zoombox.ViewStackSourceProperty ) )
|
|
{
|
|
if( zoombox.ViewStack != null )
|
|
{
|
|
zoombox.ViewStack.ClearViewStackSource();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
zoombox.ViewStack.SetViewStackSource( newValue );
|
|
}
|
|
|
|
zoombox.CoerceValue( Zoombox.ViewStackModeProperty );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomModifiers Property
|
|
|
|
public static readonly DependencyProperty ZoomModifiersProperty =
|
|
DependencyProperty.Register( "ZoomModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( Zoombox.GetDefaultZoomModifiers() ) );
|
|
|
|
[TypeConverter( typeof( KeyModifierCollectionConverter ) )]
|
|
public KeyModifierCollection ZoomModifiers
|
|
{
|
|
get
|
|
{
|
|
return ( KeyModifierCollection )this.GetValue( Zoombox.ZoomModifiersProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ZoomModifiersProperty, value );
|
|
}
|
|
}
|
|
|
|
private static KeyModifierCollection GetDefaultZoomModifiers()
|
|
{
|
|
KeyModifierCollection result = new KeyModifierCollection();
|
|
result.Add( KeyModifier.Shift );
|
|
result.Add( KeyModifier.Exact );
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomOnPreview Property
|
|
|
|
public static readonly DependencyProperty ZoomOnPreviewProperty =
|
|
DependencyProperty.Register( "ZoomOnPreview", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( true ) );
|
|
|
|
public bool ZoomOnPreview
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.ZoomOnPreviewProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ZoomOnPreviewProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomOrigin Property
|
|
|
|
public static readonly DependencyProperty ZoomOriginProperty =
|
|
DependencyProperty.Register( "ZoomOrigin", typeof( Point ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( new Point( 0.5d, 0.5d ) ) );
|
|
|
|
public Point ZoomOrigin
|
|
{
|
|
get
|
|
{
|
|
return ( Point )this.GetValue( Zoombox.ZoomOriginProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ZoomOriginProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomPercentage Property
|
|
|
|
public static readonly DependencyProperty ZoomPercentageProperty =
|
|
DependencyProperty.Register( "ZoomPercentage", typeof( double ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( 5d ) );
|
|
|
|
public double ZoomPercentage
|
|
{
|
|
get
|
|
{
|
|
return ( double )this.GetValue( Zoombox.ZoomPercentageProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ZoomPercentageProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomOn Property
|
|
|
|
public static readonly DependencyProperty ZoomOnProperty =
|
|
DependencyProperty.Register( "ZoomOn", typeof( ZoomboxZoomOn ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( ZoomboxZoomOn.Content ) );
|
|
|
|
public ZoomboxZoomOn ZoomOn
|
|
{
|
|
get
|
|
{
|
|
return ( ZoomboxZoomOn )this.GetValue( Zoombox.ZoomOnProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ZoomOnProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomToSelectionModifiers Property
|
|
|
|
public static readonly DependencyProperty ZoomToSelectionModifiersProperty =
|
|
DependencyProperty.Register( "ZoomToSelectionModifiers", typeof( KeyModifierCollection ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( Zoombox.GetDefaultZoomToSelectionModifiers() ) );
|
|
|
|
[TypeConverter( typeof( KeyModifierCollectionConverter ) )]
|
|
public KeyModifierCollection ZoomToSelectionModifiers
|
|
{
|
|
get
|
|
{
|
|
return ( KeyModifierCollection )this.GetValue( Zoombox.ZoomToSelectionModifiersProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.ZoomToSelectionModifiersProperty, value );
|
|
}
|
|
}
|
|
|
|
private static KeyModifierCollection GetDefaultZoomToSelectionModifiers()
|
|
{
|
|
KeyModifierCollection result = new KeyModifierCollection();
|
|
result.Add( KeyModifier.Alt );
|
|
result.Add( KeyModifier.Exact );
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region KeepContentInBounds Property
|
|
|
|
public static readonly DependencyProperty KeepContentInBoundsProperty =
|
|
DependencyProperty.Register( "KeepContentInBounds", typeof( bool ), typeof( Zoombox ),
|
|
new FrameworkPropertyMetadata( false,
|
|
new PropertyChangedCallback( Zoombox.OnKeepContentInBoundsChanged ) ) );
|
|
|
|
public bool KeepContentInBounds
|
|
{
|
|
get
|
|
{
|
|
return ( bool )this.GetValue( Zoombox.KeepContentInBoundsProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( Zoombox.KeepContentInBoundsProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnKeepContentInBoundsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
( ( Zoombox )d ).OnKeepContentInBoundsChanged( e );
|
|
}
|
|
|
|
private void OnKeepContentInBoundsChanged( DependencyPropertyChangedEventArgs e )
|
|
{
|
|
//
|
|
// Update view and see if we need to reposition the content
|
|
//
|
|
bool oldIsAnimated = this.IsAnimated;
|
|
this.IsAnimated = false;
|
|
try
|
|
{
|
|
this.UpdateView( this.CurrentView, false, false, this.ViewStackIndex );
|
|
}
|
|
finally
|
|
{
|
|
this.IsAnimated = oldIsAnimated;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewStack Property
|
|
|
|
public ZoomboxViewStack ViewStack
|
|
{
|
|
get
|
|
{
|
|
if( _viewStack == null && this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
|
|
{
|
|
_viewStack = new ZoomboxViewStack( this );
|
|
}
|
|
return _viewStack;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region HasArrangedContentPresenter Internal Property
|
|
|
|
internal bool HasArrangedContentPresenter
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.HasArrangedContentPresenter ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.HasArrangedContentPresenter ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsUpdatingView Internal Property
|
|
|
|
internal bool IsUpdatingView
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.IsUpdatingView ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.IsUpdatingView ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ContentOffset Private Property
|
|
|
|
private Vector ContentOffset
|
|
{
|
|
get
|
|
{
|
|
// auto-wrapped content is always left and top aligned
|
|
if( this.IsContentWrapped || _content == null || !( _content is FrameworkElement ) )
|
|
return new Vector( 0, 0 );
|
|
|
|
double x = 0;
|
|
double y = 0;
|
|
Size contentSize = this.ContentRect.Size;
|
|
|
|
switch( ( _content as FrameworkElement ).HorizontalAlignment )
|
|
{
|
|
case HorizontalAlignment.Center:
|
|
case HorizontalAlignment.Stretch:
|
|
x = ( this.RenderSize.Width - contentSize.Width ) / 2;
|
|
break;
|
|
|
|
case HorizontalAlignment.Right:
|
|
x = this.RenderSize.Width - contentSize.Width;
|
|
break;
|
|
}
|
|
|
|
switch( ( _content as FrameworkElement ).VerticalAlignment )
|
|
{
|
|
case VerticalAlignment.Center:
|
|
case VerticalAlignment.Stretch:
|
|
y = ( this.RenderSize.Height - contentSize.Height ) / 2;
|
|
break;
|
|
|
|
case VerticalAlignment.Bottom:
|
|
y = this.RenderSize.Height - contentSize.Height;
|
|
break;
|
|
}
|
|
|
|
return new Vector( x, y );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ContentRect Private Property
|
|
|
|
private Rect ContentRect
|
|
{
|
|
get
|
|
{
|
|
return ( _content == null ) ? Rect.Empty
|
|
: new Rect( new Size( _content.RenderSize.Width / _viewboxFactor, _content.RenderSize.Height / _viewboxFactor ) );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region HasRenderedFirstView Private Property
|
|
|
|
private bool HasRenderedFirstView
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.HasRenderedFirstView ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.HasRenderedFirstView ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region HasUIPermission Private Property
|
|
|
|
private bool HasUIPermission
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.HasUIPermission ];
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsContentWrapped Private Property
|
|
|
|
private bool IsContentWrapped
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.IsContentWrapped ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.IsContentWrapped ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsDraggingViewport Private Property
|
|
|
|
private bool IsDraggingViewport
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.IsDraggingViewport ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.IsDraggingViewport ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsMonitoringInput Private Property
|
|
|
|
private bool IsMonitoringInput
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.IsMonitoringInput ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.IsMonitoringInput ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsResizingViewport Private Property
|
|
|
|
private bool IsResizingViewport
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.IsResizingViewport ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.IsResizingViewport ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsUpdatingViewport Private Property
|
|
|
|
private bool IsUpdatingViewport
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.IsUpdatingViewport ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.IsUpdatingViewport ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region RefocusViewOnFirstRender Private Property
|
|
|
|
private bool RefocusViewOnFirstRender
|
|
{
|
|
get
|
|
{
|
|
return _cacheBits[ ( int )CacheBits.RefocusViewOnFirstRender ];
|
|
}
|
|
set
|
|
{
|
|
_cacheBits[ ( int )CacheBits.RefocusViewOnFirstRender ] = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ViewFinderDisplayRect Private Property
|
|
|
|
private Rect ViewFinderDisplayRect
|
|
{
|
|
get
|
|
{
|
|
return ( _viewFinderDisplay == null ) ? Rect.Empty
|
|
: new Rect( new Point( 0, 0 ), new Point( _viewFinderDisplay.RenderSize.Width, _viewFinderDisplay.RenderSize.Height ) );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AnimationBeginning Event
|
|
|
|
public static readonly RoutedEvent AnimationBeginningEvent = EventManager.RegisterRoutedEvent( "AnimationBeginning", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( Zoombox ) );
|
|
|
|
public event RoutedEventHandler AnimationBeginning
|
|
{
|
|
add
|
|
{
|
|
this.AddHandler( Zoombox.AnimationBeginningEvent, value );
|
|
}
|
|
remove
|
|
{
|
|
this.RemoveHandler( Zoombox.AnimationBeginningEvent, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AnimationCompleted Event
|
|
|
|
public static readonly RoutedEvent AnimationCompletedEvent = EventManager.RegisterRoutedEvent( "AnimationCompleted", RoutingStrategy.Bubble, typeof( RoutedEventHandler ), typeof( Zoombox ) );
|
|
|
|
public event RoutedEventHandler AnimationCompleted
|
|
{
|
|
add
|
|
{
|
|
this.AddHandler( Zoombox.AnimationCompletedEvent, value );
|
|
}
|
|
remove
|
|
{
|
|
this.RemoveHandler( Zoombox.AnimationCompletedEvent, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CurrentViewChanged Event
|
|
|
|
public static readonly RoutedEvent CurrentViewChangedEvent = EventManager.RegisterRoutedEvent( "CurrentViewChanged", RoutingStrategy.Bubble, typeof( ZoomboxViewChangedEventHandler ), typeof( Zoombox ) );
|
|
|
|
public event ZoomboxViewChangedEventHandler CurrentViewChanged
|
|
{
|
|
add
|
|
{
|
|
this.AddHandler( Zoombox.CurrentViewChangedEvent, value );
|
|
}
|
|
remove
|
|
{
|
|
this.RemoveHandler( Zoombox.CurrentViewChangedEvent, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public event EventHandler<ScrollEventArgs> Scroll;
|
|
|
|
#region ViewStackIndexChanged Event
|
|
|
|
public static readonly RoutedEvent ViewStackIndexChangedEvent = EventManager.RegisterRoutedEvent( "ViewStackIndexChanged", RoutingStrategy.Bubble, typeof( IndexChangedEventHandler ), typeof( Zoombox ) );
|
|
|
|
public event IndexChangedEventHandler ViewStackIndexChanged
|
|
{
|
|
add
|
|
{
|
|
this.AddHandler( Zoombox.ViewStackIndexChangedEvent, value );
|
|
}
|
|
remove
|
|
{
|
|
this.RemoveHandler( Zoombox.ViewStackIndexChangedEvent, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Back Command
|
|
|
|
public static RoutedUICommand Back = new RoutedUICommand( "Go Back", "GoBack", typeof( Zoombox ) );
|
|
|
|
private void CanGoBack( object sender, CanExecuteRoutedEventArgs e )
|
|
{
|
|
e.CanExecute = ( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
|
|
&& ( this.ViewStackIndex > 0 );
|
|
}
|
|
|
|
private void GoBack( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.GoBack();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Center Command
|
|
|
|
public static RoutedUICommand Center = new RoutedUICommand( "Center Content", "Center", typeof( Zoombox ) );
|
|
|
|
private void CenterContent( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.CenterContent();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Fill Command
|
|
|
|
public static RoutedUICommand Fill = new RoutedUICommand( "Fill Bounds with Content", "FillToBounds", typeof( Zoombox ) );
|
|
|
|
private void FillToBounds( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.FillToBounds();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Fit Command
|
|
|
|
public static RoutedUICommand Fit = new RoutedUICommand( "Fit Content within Bounds", "FitToBounds", typeof( Zoombox ) );
|
|
|
|
private void FitToBounds( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.FitToBounds();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Forward Command
|
|
|
|
public static RoutedUICommand Forward = new RoutedUICommand( "Go Forward", "GoForward", typeof( Zoombox ) );
|
|
|
|
private void CanGoForward( object sender, CanExecuteRoutedEventArgs e )
|
|
{
|
|
e.CanExecute = ( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
|
|
&& ( this.ViewStackIndex < this.ViewStack.Count - 1 );
|
|
}
|
|
|
|
private void GoForward( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.GoForward();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Home Command
|
|
|
|
public static RoutedUICommand Home = new RoutedUICommand( "Go Home", "GoHome", typeof( Zoombox ) );
|
|
|
|
private void CanGoHome( object sender, CanExecuteRoutedEventArgs e )
|
|
{
|
|
e.CanExecute = ( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
|
|
&& ( this.ViewStack.Count > 0 )
|
|
&& ( this.ViewStackIndex != 0 );
|
|
}
|
|
|
|
private void GoHome( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.GoHome();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PanDown Command
|
|
|
|
public static RoutedUICommand PanDown = new RoutedUICommand( "Pan Down", "PanDown", typeof( Zoombox ) );
|
|
|
|
private void PanDownExecuted( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.Position = new Point( _basePosition.X, _basePosition.Y + PanDistance );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PanLeft Command
|
|
|
|
public static RoutedUICommand PanLeft = new RoutedUICommand( "Pan Left", "PanLeft", typeof( Zoombox ) );
|
|
|
|
private void PanLeftExecuted( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.Position = new Point( _basePosition.X - this.PanDistance, _basePosition.Y );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PanRight Command
|
|
|
|
public static RoutedUICommand PanRight = new RoutedUICommand( "Pan Right", "PanRight", typeof( Zoombox ) );
|
|
|
|
private void PanRightExecuted( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.Position = new Point( _basePosition.X + this.PanDistance, _basePosition.Y );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PanUp Command
|
|
|
|
public static RoutedUICommand PanUp = new RoutedUICommand( "Pan Up", "PanUp", typeof( Zoombox ) );
|
|
|
|
private void PanUpExecuted( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.Position = new Point( _basePosition.X, _basePosition.Y - this.PanDistance );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Refocus Command
|
|
|
|
public static RoutedUICommand Refocus = new RoutedUICommand( "Refocus View", "Refocus", typeof( Zoombox ) );
|
|
|
|
private void CanRefocusView( object sender, CanExecuteRoutedEventArgs e )
|
|
{
|
|
e.CanExecute = ( this.EffectiveViewStackMode == ZoomboxViewStackMode.Manual )
|
|
&& ( this.ViewStackIndex >= 0 && this.ViewStackIndex < this.ViewStack.Count )
|
|
&& ( this.CurrentView != this.ViewStack[ this.ViewStackIndex ] );
|
|
}
|
|
|
|
private void RefocusView( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.RefocusView();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomIn Command
|
|
|
|
public static RoutedUICommand ZoomIn = new RoutedUICommand( "Zoom In", "ZoomIn", typeof( Zoombox ) );
|
|
|
|
private void ZoomInExecuted( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.Zoom( this.ZoomPercentage / 100 );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ZoomOut Command
|
|
|
|
public static RoutedUICommand ZoomOut = new RoutedUICommand( "Zoom Out", "ZoomOut", typeof( Zoombox ) );
|
|
|
|
private void ZoomOutExecuted( object sender, ExecutedRoutedEventArgs e )
|
|
{
|
|
this.Zoom( -this.ZoomPercentage / 100 );
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void CenterContent()
|
|
{
|
|
if( _content != null )
|
|
{
|
|
this.SetScrollBars();
|
|
this.ZoomTo( ZoomboxView.Center );
|
|
}
|
|
}
|
|
|
|
public void FillToBounds()
|
|
{
|
|
if( _content != null )
|
|
{
|
|
this.SetScrollBars();
|
|
this.ZoomTo( ZoomboxView.Fill );
|
|
}
|
|
}
|
|
|
|
public void FitToBounds()
|
|
{
|
|
if( _content != null )
|
|
{
|
|
this.SetScrollBars();
|
|
this.ZoomTo( ZoomboxView.Fit );
|
|
}
|
|
}
|
|
|
|
public void GoBack()
|
|
{
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
|
|
return;
|
|
|
|
if( this.ViewStackIndex > 0 )
|
|
{
|
|
this.ViewStackIndex--;
|
|
}
|
|
}
|
|
|
|
public void GoForward()
|
|
{
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
|
|
return;
|
|
|
|
if( this.ViewStackIndex < this.ViewStack.Count - 1 )
|
|
{
|
|
this.ViewStackIndex++;
|
|
}
|
|
}
|
|
|
|
public void GoHome()
|
|
{
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
|
|
return;
|
|
|
|
if( this.ViewStackIndex > 0 )
|
|
{
|
|
this.ViewStackIndex = 0;
|
|
}
|
|
}
|
|
|
|
public override void OnApplyTemplate()
|
|
{
|
|
this.AttachToVisualTree();
|
|
base.OnApplyTemplate();
|
|
}
|
|
|
|
public void RefocusView()
|
|
{
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Disabled )
|
|
return;
|
|
|
|
if( this.ViewStackIndex >= 0 && this.ViewStackIndex < this.ViewStack.Count
|
|
&& this.CurrentView != this.ViewStack[ this.ViewStackIndex ] )
|
|
{
|
|
this.UpdateView( this.ViewStack[ this.ViewStackIndex ], true, false, this.ViewStackIndex );
|
|
}
|
|
}
|
|
|
|
public void Zoom( double percentage )
|
|
{
|
|
// if there is nothing to scale, just return
|
|
if( _content == null )
|
|
return;
|
|
|
|
this.Zoom( percentage, this.GetZoomRelativePoint() );
|
|
}
|
|
|
|
public void Zoom( double percentage, Point relativeTo )
|
|
{
|
|
// if there is nothing to scale, just return
|
|
if( _content == null )
|
|
return;
|
|
|
|
// adjust the current scale relative to the given point
|
|
double scale = this.Scale * ( 1 + percentage );
|
|
this.ZoomTo( scale, relativeTo );
|
|
}
|
|
|
|
public void ZoomTo( Point position )
|
|
{
|
|
// if there is nothing to pan, just return
|
|
if( _content == null )
|
|
return;
|
|
|
|
// zoom to the new region
|
|
this.ZoomTo( new ZoomboxView( new Point( -position.X, -position.Y ) ) );
|
|
}
|
|
|
|
public void ZoomTo( Rect region )
|
|
{
|
|
if( _content == null )
|
|
return;
|
|
|
|
// adjust the current scale and position
|
|
this.UpdateView( new ZoomboxView( region ), true, true );
|
|
}
|
|
|
|
public void ZoomTo( double scale )
|
|
{
|
|
// if there is nothing to scale, just return
|
|
if( _content == null )
|
|
return;
|
|
|
|
// adjust the current scale relative to the center of the content within the control
|
|
this.ZoomTo( scale, true );
|
|
}
|
|
|
|
public void ZoomTo( double scale, Point relativeTo )
|
|
{
|
|
this.ZoomTo( scale, relativeTo, true, true );
|
|
}
|
|
|
|
public void ZoomTo( ZoomboxView view )
|
|
{
|
|
this.UpdateView( view, true, true );
|
|
}
|
|
|
|
internal void UpdateStackProperties()
|
|
{
|
|
this.SetValue( Zoombox.HasBackStackPropertyKey, this.ViewStackIndex > 0 );
|
|
this.SetValue( Zoombox.HasForwardStackPropertyKey, this.ViewStack.Count > this.ViewStackIndex + 1 );
|
|
CommandManager.InvalidateRequerySuggested();
|
|
}
|
|
|
|
protected override Size MeasureOverride( Size constraint )
|
|
{
|
|
if( _content != null )
|
|
{
|
|
// measure visuals according to supplied constraint
|
|
Size size = base.MeasureOverride( constraint );
|
|
|
|
// now re-measure content to let the child be whatever size it desires
|
|
_content.Measure( new Size( double.PositiveInfinity, double.PositiveInfinity ) );
|
|
return size;
|
|
}
|
|
|
|
|
|
// avoid returning infinity
|
|
if( double.IsInfinity( constraint.Height ) )
|
|
{
|
|
constraint.Height = 0;
|
|
}
|
|
|
|
if( double.IsInfinity( constraint.Width ) )
|
|
{
|
|
constraint.Width = 0;
|
|
}
|
|
return constraint;
|
|
}
|
|
|
|
protected override void OnContentChanged( object oldContent, object newContent )
|
|
{
|
|
// disconnect SizeChanged handler from old content
|
|
if( oldContent is FrameworkElement )
|
|
{
|
|
( oldContent as FrameworkElement ).RemoveHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ) );
|
|
}
|
|
else
|
|
{
|
|
this.RemoveHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ) );
|
|
}
|
|
|
|
// connect SizeChanged handler to new content
|
|
if( _content is FrameworkElement )
|
|
{
|
|
( _content as FrameworkElement ).AddHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ), true );
|
|
}
|
|
else
|
|
{
|
|
this.AddHandler( FrameworkElement.SizeChangedEvent, new SizeChangedEventHandler( this.OnContentSizeChanged ), true );
|
|
}
|
|
|
|
// update the Visual property of the view finder display panel's VisualBrush
|
|
if( _viewFinderDisplay != null && _viewFinderDisplay.VisualBrush != null )
|
|
{
|
|
_viewFinderDisplay.VisualBrush.Visual = _content;
|
|
}
|
|
}
|
|
|
|
protected override void OnGotKeyboardFocus( KeyboardFocusChangedEventArgs e )
|
|
{
|
|
this.MonitorInput();
|
|
base.OnGotKeyboardFocus( e );
|
|
}
|
|
|
|
protected override void OnLostKeyboardFocus( KeyboardFocusChangedEventArgs e )
|
|
{
|
|
this.MonitorInput();
|
|
base.OnLostKeyboardFocus( e );
|
|
}
|
|
|
|
protected override void OnInitialized( EventArgs e )
|
|
{
|
|
base.OnInitialized( e );
|
|
this.CoerceValue( Zoombox.IsAnimatedProperty );
|
|
}
|
|
|
|
protected override void OnRender( DrawingContext drawingContext )
|
|
{
|
|
if( this.HasArrangedContentPresenter && !this.HasRenderedFirstView )
|
|
{
|
|
this.HasRenderedFirstView = true;
|
|
if( this.RefocusViewOnFirstRender )
|
|
{
|
|
this.RefocusViewOnFirstRender = false;
|
|
bool oldAnimated = this.IsAnimated;
|
|
this.IsAnimated = false;
|
|
try
|
|
{
|
|
this.RefocusView();
|
|
}
|
|
finally
|
|
{
|
|
this.IsAnimated = oldAnimated;
|
|
}
|
|
}
|
|
}
|
|
|
|
base.OnRender( drawingContext );
|
|
}
|
|
|
|
private static void RefocusView( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
Zoombox zoombox = o as Zoombox;
|
|
zoombox.UpdateView( zoombox.CurrentView, true, false, zoombox.ViewStackIndex );
|
|
}
|
|
|
|
private void AttachToVisualTree()
|
|
{
|
|
// detach from the old tree
|
|
this.DetachFromVisualTree();
|
|
|
|
// create the drag adorner for selection operations
|
|
_dragAdorner = new DragAdorner( this );
|
|
|
|
// check the template for a SelectionBrush resource
|
|
if( this.Template.Resources.Contains( "SelectionBrush" ) )
|
|
_dragAdorner.Brush = this.Template.Resources[ "SelectionBrush" ] as Brush;
|
|
|
|
// check the template for a SelectionPen resource
|
|
if( this.Template.Resources.Contains( "SelectionPen" ) )
|
|
_dragAdorner.Pen = this.Template.Resources[ "SelectionPen" ] as Pen;
|
|
|
|
// check the template for key bindings
|
|
if( this.Template.Resources.Contains( "InputBindings" ) )
|
|
{
|
|
InputBindingCollection inputBindings = this.Template.Resources[ "InputBindings" ] as InputBindingCollection;
|
|
if( inputBindings != null )
|
|
{
|
|
this.InputBindings.AddRange( inputBindings );
|
|
}
|
|
}
|
|
|
|
// locate the content presenter
|
|
_contentPresenter = VisualTreeHelperEx.FindDescendantByType( this, typeof( ContentPresenter ) ) as ContentPresenter;
|
|
if( _contentPresenter == null )
|
|
throw new InvalidTemplateException( ErrorMessages.GetMessage( "ZoomboxTemplateNeedsContent" ) );
|
|
|
|
//locate the vertical scrollBar
|
|
if( _verticalScrollBar != null )
|
|
{
|
|
_verticalScrollBar.Scroll -= this.VerticalScrollBar_Scroll;
|
|
}
|
|
|
|
_verticalScrollBar = this.GetTemplateChild( PART_VerticalScrollBar ) as ScrollBar;
|
|
if( _verticalScrollBar == null )
|
|
throw new InvalidTemplateException( ErrorMessages.GetMessage( "Zoombox vertical scrollBar not found." ) );
|
|
|
|
_verticalScrollBar.Scroll += this.VerticalScrollBar_Scroll;
|
|
|
|
//locate the horizontal scrollBar
|
|
if( _horizontalScrollBar != null )
|
|
{
|
|
_horizontalScrollBar.Scroll -= this.HorizontalScrollBar_Scroll;
|
|
}
|
|
|
|
_horizontalScrollBar = this.GetTemplateChild( PART_HorizontalScrollBar ) as ScrollBar;
|
|
if( _horizontalScrollBar == null )
|
|
throw new InvalidTemplateException( ErrorMessages.GetMessage( "Zoombox horizontal scrollBar not found." ) );
|
|
|
|
_horizontalScrollBar.Scroll += this.HorizontalScrollBar_Scroll;
|
|
|
|
// check the template for an AdornerDecorator
|
|
AdornerLayer al = null;
|
|
AdornerDecorator ad = VisualTreeHelperEx.FindDescendantByType( this, typeof( AdornerDecorator ) ) as AdornerDecorator;
|
|
if( ad != null )
|
|
{
|
|
al = ad.AdornerLayer;
|
|
}
|
|
else
|
|
{
|
|
// look for an inherited adorner layer
|
|
try
|
|
{
|
|
al = AdornerLayer.GetAdornerLayer( this );
|
|
}
|
|
catch( Exception )
|
|
{
|
|
}
|
|
}
|
|
|
|
// add the drag adorner to the adorner layer
|
|
if( al != null )
|
|
{
|
|
al.Add( _dragAdorner );
|
|
}
|
|
|
|
// TODO: Why is it necessary to walk the visual tree the first time through?
|
|
// If we don't do the following, the content is not laid out correctly (centered) initially.
|
|
VisualTreeHelperEx.FindDescendantWithPropertyValue( this, Button.IsPressedProperty, true );
|
|
|
|
// set a reference to the ViewFinder element, if present
|
|
this.SetValue( Zoombox.ViewFinderPropertyKey, this.Template.FindName( "ViewFinder", this ) as FrameworkElement );
|
|
|
|
// locate the view finder display panel
|
|
_viewFinderDisplay = VisualTreeHelperEx.FindDescendantByType( this, typeof( ZoomboxViewFinderDisplay ) ) as ZoomboxViewFinderDisplay;
|
|
|
|
// if a ViewFinder was specified but no display panel is present, throw an exception
|
|
if( this.ViewFinder != null && _viewFinderDisplay == null )
|
|
throw new InvalidTemplateException( ErrorMessages.GetMessage( "ZoomboxHasViewFinderButNotDisplay" ) );
|
|
|
|
// set up the VisualBrush and adorner for the display panel
|
|
if( _viewFinderDisplay != null )
|
|
{
|
|
// create VisualBrush for the view finder display panel
|
|
this.CreateVisualBrushForViewFinder( _content );
|
|
|
|
// hook up event handlers for dragging and resizing the viewport
|
|
_viewFinderDisplay.MouseMove += new MouseEventHandler( this.ViewFinderDisplayMouseMove );
|
|
_viewFinderDisplay.MouseLeftButtonDown += new MouseButtonEventHandler( this.ViewFinderDisplayBeginCapture );
|
|
_viewFinderDisplay.MouseLeftButtonUp += new MouseButtonEventHandler( this.ViewFinderDisplayEndCapture );
|
|
|
|
// bind the ViewportRect property of the display to the Viewport of the Zoombox
|
|
Binding binding = new Binding( "Viewport" );
|
|
binding.Mode = BindingMode.OneWay;
|
|
binding.Converter = new ViewFinderSelectionConverter( this );
|
|
binding.Source = this;
|
|
_viewFinderDisplay.SetBinding( ZoomboxViewFinderDisplay.ViewportRectProperty, binding );
|
|
}
|
|
|
|
// set up event handler to run once the content presenter has been arranged
|
|
_contentPresenter.LayoutUpdated += new EventHandler( this.ContentPresenterFirstArranged );
|
|
}
|
|
|
|
private void CreateVisualBrushForViewFinder( Visual visual )
|
|
{
|
|
_viewFinderDisplay.VisualBrush = new VisualBrush( visual );
|
|
_viewFinderDisplay.VisualBrush.Stretch = Stretch.Uniform;
|
|
_viewFinderDisplay.VisualBrush.AlignmentX = AlignmentX.Left;
|
|
_viewFinderDisplay.VisualBrush.AlignmentY = AlignmentY.Top;
|
|
}
|
|
|
|
private void ContentPresenterFirstArranged( object sender, EventArgs e )
|
|
{
|
|
// remove the handler
|
|
_contentPresenter.LayoutUpdated -= new EventHandler( this.ContentPresenterFirstArranged );
|
|
|
|
// it's now safe to update the view
|
|
this.HasArrangedContentPresenter = true;
|
|
this.InvalidateVisual();
|
|
|
|
// temporarily disable animations
|
|
bool oldAnimated = IsAnimated;
|
|
this.IsAnimated = false;
|
|
try
|
|
{
|
|
// set the initial view
|
|
double scale = this.Scale;
|
|
Point position = this.Position;
|
|
|
|
// if there are items in the ViewStack and a ViewStackIndex is set, use it
|
|
if( this.EffectiveViewStackMode != ZoomboxViewStackMode.Disabled )
|
|
{
|
|
bool isInitialViewSet = false;
|
|
if( this.ViewStack.Count > 0 )
|
|
{
|
|
if( this.ViewStackIndex >= 0 )
|
|
{
|
|
if( this.ViewStackIndex > this.ViewStack.Count - 1 )
|
|
{
|
|
this.ViewStackIndex = this.ViewStack.Count - 1;
|
|
}
|
|
else
|
|
{
|
|
this.UpdateView( this.ViewStack[ this.ViewStackIndex ], false, false, this.ViewStackIndex );
|
|
}
|
|
}
|
|
else if( this.EffectiveViewStackMode != ZoomboxViewStackMode.Auto )
|
|
{
|
|
// if this is not an auto-stack, then ensure the index is set to a valid value
|
|
if( this.ViewStackIndex < 0 )
|
|
{
|
|
this.ViewStackIndex = 0;
|
|
}
|
|
}
|
|
|
|
// if a ViewStackIndex has been set, apply the scale and position values, iff different than the default values
|
|
if( this.ViewStackIndex >= 0 )
|
|
{
|
|
isInitialViewSet = true;
|
|
if( !DoubleHelper.IsNaN( scale ) || !PointHelper.IsEmpty( position ) )
|
|
{
|
|
this.UpdateView( new ZoomboxView( scale, position ), false, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !isInitialViewSet )
|
|
{
|
|
// set initial view according to the scale and position values and push it on the stack, as necessary
|
|
ZoomboxView initialView = new ZoomboxView( DoubleHelper.IsNaN( Scale ) ? 1.0 : Scale,
|
|
PointHelper.IsEmpty( position ) ? new Point() : position );
|
|
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Auto )
|
|
{
|
|
this.ViewStack.PushView( initialView );
|
|
this.ViewStackIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
this.UpdateView( initialView, false, false );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// just set initial view according to the scale and position values
|
|
ZoomboxView initialView = new ZoomboxView( DoubleHelper.IsNaN( Scale ) ? 1.0 : this.Scale, position );
|
|
this.UpdateView( initialView, false, false );
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
IsAnimated = oldAnimated;
|
|
}
|
|
}
|
|
|
|
private void DetachFromVisualTree()
|
|
{
|
|
// remove the drag adorner
|
|
if( _dragAdorner != null )
|
|
AdornerLayer.GetAdornerLayer( this ).Remove( _dragAdorner );
|
|
|
|
// remove the layout updated handler, if present
|
|
if( _contentPresenter != null )
|
|
{
|
|
_contentPresenter.LayoutUpdated -= new EventHandler( this.ContentPresenterFirstArranged );
|
|
}
|
|
|
|
// remove the view finder display panel's visual brush and adorner
|
|
if( _viewFinderDisplay != null )
|
|
{
|
|
_viewFinderDisplay = null;
|
|
}
|
|
|
|
// set object references to null
|
|
_contentPresenter = null;
|
|
}
|
|
|
|
private void Zoombox_Loaded( object sender, RoutedEventArgs e )
|
|
{
|
|
this.SetScrollBars();
|
|
}
|
|
|
|
private void VerticalScrollBar_Scroll( object sender, ScrollEventArgs e )
|
|
{
|
|
double diff = e.NewValue + _relativePosition.Y;
|
|
this.OnDrag( new DragDeltaEventArgs( 0d, -diff / this.Scale ), false );
|
|
|
|
// Raise the Scroll event to user
|
|
EventHandler<ScrollEventArgs> handler = this.Scroll;
|
|
if( handler != null )
|
|
{
|
|
handler( this, e );
|
|
}
|
|
}
|
|
|
|
private void HorizontalScrollBar_Scroll( object sender, ScrollEventArgs e )
|
|
{
|
|
double diff = e.NewValue + _relativePosition.X;
|
|
this.OnDrag( new DragDeltaEventArgs( -diff / this.Scale, 0d ), false );
|
|
|
|
// Raise the Scroll event to user
|
|
EventHandler<ScrollEventArgs> handler = this.Scroll;
|
|
if( handler != null )
|
|
{
|
|
handler( this, e );
|
|
}
|
|
}
|
|
|
|
private void DragDisplayViewport( DragDeltaEventArgs e, bool end )
|
|
{
|
|
// get the scale of the view finder display panel, the selection rect, and the VisualBrush rect
|
|
double scale = _viewFinderDisplay.Scale;
|
|
Rect viewportRect = _viewFinderDisplay.ViewportRect;
|
|
Rect vbRect = _viewFinderDisplay.ContentBounds;
|
|
|
|
// if the entire content is visible, do nothing
|
|
if( viewportRect.Contains( vbRect ) )
|
|
return;
|
|
|
|
// ensure that we stay within the bounds of the VisualBrush
|
|
double dx = e.HorizontalChange;
|
|
double dy = e.VerticalChange;
|
|
|
|
// check left boundary
|
|
if( viewportRect.Left < vbRect.Left )
|
|
{
|
|
dx = Math.Max( 0, dx );
|
|
}
|
|
else if( viewportRect.Left + dx < vbRect.Left )
|
|
{
|
|
dx = vbRect.Left - viewportRect.Left;
|
|
}
|
|
|
|
// check right boundary
|
|
if( viewportRect.Right > vbRect.Right )
|
|
{
|
|
dx = Math.Min( 0, dx );
|
|
}
|
|
else if( viewportRect.Right + dx > vbRect.Left + vbRect.Width )
|
|
{
|
|
dx = vbRect.Left + vbRect.Width - viewportRect.Right;
|
|
}
|
|
|
|
// check top boundary
|
|
if( viewportRect.Top < vbRect.Top )
|
|
{
|
|
dy = Math.Max( 0, dy );
|
|
}
|
|
else if( viewportRect.Top + dy < vbRect.Top )
|
|
{
|
|
dy = vbRect.Top - viewportRect.Top;
|
|
}
|
|
|
|
// check bottom boundary
|
|
if( viewportRect.Bottom > vbRect.Bottom )
|
|
{
|
|
dy = Math.Min( 0, dy );
|
|
}
|
|
else if( viewportRect.Bottom + dy > vbRect.Top + vbRect.Height )
|
|
{
|
|
dy = vbRect.Top + vbRect.Height - viewportRect.Bottom;
|
|
}
|
|
|
|
// call the main OnDrag handler that is used when dragging the content directly
|
|
this.OnDrag( new DragDeltaEventArgs( -dx / scale / _viewboxFactor, -dy / scale / _viewboxFactor ), end );
|
|
|
|
// for a drag operation, update the origin with each delta
|
|
_originPoint = _originPoint + new Vector( dx, dy );
|
|
}
|
|
|
|
private void InitCommands()
|
|
{
|
|
CommandBinding binding = new CommandBinding( Zoombox.Back, this.GoBack, this.CanGoBack );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.Center, this.CenterContent );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.Fill, this.FillToBounds );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.Fit, this.FitToBounds );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.Forward, this.GoForward, this.CanGoForward );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.Home, this.GoHome, this.CanGoHome );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.PanDown, this.PanDownExecuted );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.PanLeft, this.PanLeftExecuted );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.PanRight, this.PanRightExecuted );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.PanUp, this.PanUpExecuted );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.Refocus, this.RefocusView, this.CanRefocusView );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.ZoomIn, this.ZoomInExecuted );
|
|
this.CommandBindings.Add( binding );
|
|
|
|
binding = new CommandBinding( Zoombox.ZoomOut, this.ZoomOutExecuted );
|
|
this.CommandBindings.Add( binding );
|
|
}
|
|
|
|
private void MonitorInput()
|
|
{
|
|
// cannot pre-process input in partial trust
|
|
if( this.HasUIPermission )
|
|
{
|
|
this.PreProcessInput();
|
|
}
|
|
}
|
|
|
|
private void OnContentSizeChanged( object sender, SizeChangedEventArgs e )
|
|
{
|
|
this.UpdateViewFinderDisplayContentBounds();
|
|
|
|
if( this.HasArrangedContentPresenter )
|
|
{
|
|
if( this.HasRenderedFirstView )
|
|
{
|
|
this.SetScrollBars();
|
|
this.UpdateView( this.CurrentView, true, false, this.CurrentViewIndex );
|
|
}
|
|
else
|
|
{
|
|
// if the content size changes after the content presenter has been arranged,
|
|
// but before the first view is rendered, invalidate the render so we can refocus
|
|
// the view on the first render
|
|
this.RefocusViewOnFirstRender = true;
|
|
this.InvalidateVisual();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDrag( DragDeltaEventArgs e, bool end )
|
|
{
|
|
Point relativePosition = _relativePosition;
|
|
double scale = this.Scale;
|
|
Point newPosition = relativePosition + ( this.ContentOffset * scale ) + new Vector( e.HorizontalChange * scale, e.VerticalChange * scale );
|
|
|
|
// update the transform
|
|
this.UpdateView( new ZoomboxView( scale, newPosition ), false, end );
|
|
}
|
|
|
|
private void OnLayoutUpdated( object sender, EventArgs e )
|
|
{
|
|
this.UpdateViewport();
|
|
}
|
|
|
|
private void OnSelectRegion( DragDeltaEventArgs e, bool end )
|
|
{
|
|
// draw adorner rect
|
|
if( end )
|
|
{
|
|
_dragAdorner.Rect = Rect.Empty;
|
|
|
|
if( _trueContent != null )
|
|
{
|
|
// get the selected region (in the content's coordinate space) based on the adorner's last position and size
|
|
Rect selection =
|
|
new Rect(
|
|
this.TranslatePoint( _dragAdorner.LastPosition, _trueContent ),
|
|
this.TranslatePoint( _dragAdorner.LastPosition + new Vector( _dragAdorner.LastSize.Width, _dragAdorner.LastSize.Height ), _trueContent ) );
|
|
|
|
// zoom to the selection
|
|
this.ZoomTo( selection );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_dragAdorner.Rect =
|
|
Rect.Intersect(
|
|
new Rect(
|
|
_originPoint,
|
|
new Vector( e.HorizontalChange, e.VerticalChange ) ),
|
|
new Rect(
|
|
new Point( 0, 0 ),
|
|
new Point( this.RenderSize.Width, this.RenderSize.Height ) ) );
|
|
}
|
|
}
|
|
|
|
private void OnSizeChanged( object sender, SizeChangedEventArgs e )
|
|
{
|
|
if( !this.HasArrangedContentPresenter )
|
|
return;
|
|
|
|
this.SetScrollBars();
|
|
|
|
// when the size is changing, the viewbox factor must be updated before updating the view
|
|
this.UpdateViewboxFactor();
|
|
|
|
bool oldIsAnimated = this.IsAnimated;
|
|
this.IsAnimated = false;
|
|
try
|
|
{
|
|
this.UpdateView( this.CurrentView, false, false, this.ViewStackIndex );
|
|
}
|
|
finally
|
|
{
|
|
this.IsAnimated = oldIsAnimated;
|
|
}
|
|
}
|
|
|
|
private void SetScrollBars()
|
|
{
|
|
if( _content == null || _verticalScrollBar == null || _horizontalScrollBar == null )
|
|
return;
|
|
|
|
var contentSize = ( _content is Viewbox ) ? ( ( Viewbox )_content ).Child.DesiredSize : this.RenderSize;
|
|
|
|
_verticalScrollBar.SmallChange = 10d;
|
|
_verticalScrollBar.LargeChange = 10d;
|
|
_verticalScrollBar.Minimum = 0d;
|
|
_verticalScrollBar.ViewportSize = this.RenderSize.Height;
|
|
_verticalScrollBar.Maximum = contentSize.Height - _verticalScrollBar.ViewportSize;
|
|
|
|
_horizontalScrollBar.SmallChange = 10d;
|
|
_horizontalScrollBar.LargeChange = 10d;
|
|
_horizontalScrollBar.Minimum = 0d;
|
|
_horizontalScrollBar.ViewportSize = this.RenderSize.Width;
|
|
_horizontalScrollBar.Maximum = contentSize.Width - _horizontalScrollBar.ViewportSize;
|
|
}
|
|
|
|
private void PreProcessInput()
|
|
{
|
|
// if mouse is over the Zoombox element or if it has keyboard focus, pre-process input
|
|
// to update the KeyModifier trigger properties (e.g., DragModifiersAreActive)
|
|
if( this.IsMouseOver || this.IsKeyboardFocusWithin )
|
|
{
|
|
if( !this.IsMonitoringInput )
|
|
{
|
|
this.IsMonitoringInput = true;
|
|
InputManager.Current.PreNotifyInput += new NotifyInputEventHandler( this.PreProcessInput );
|
|
this.UpdateKeyModifierTriggerProperties();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( this.IsMonitoringInput )
|
|
{
|
|
this.IsMonitoringInput = false;
|
|
InputManager.Current.PreNotifyInput -= new NotifyInputEventHandler( this.PreProcessInput );
|
|
|
|
this.SetAreDragModifiersActive( false );
|
|
this.SetAreRelativeZoomModifiersActive( false );
|
|
this.SetAreZoomModifiersActive( false );
|
|
this.SetAreZoomToSelectionModifiersActive( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
private void PreProcessInput( object sender, NotifyInputEventArgs e )
|
|
{
|
|
if( e.StagingItem.Input is KeyEventArgs )
|
|
{
|
|
this.UpdateKeyModifierTriggerProperties();
|
|
}
|
|
}
|
|
|
|
private void ProcessMouseLeftButtonDown( MouseButtonEventArgs e )
|
|
{
|
|
if( this.ZoomToSelectionModifiers.AreActive )
|
|
{
|
|
this.SetIsDraggingContent( false );
|
|
this.SetIsSelectingRegion( true );
|
|
}
|
|
else if( this.DragModifiers.AreActive )
|
|
{
|
|
this.SetIsSelectingRegion( false );
|
|
this.SetIsDraggingContent( true );
|
|
}
|
|
else
|
|
{
|
|
this.SetIsSelectingRegion( false );
|
|
this.SetIsDraggingContent( false );
|
|
}
|
|
|
|
// if nothing to do, just return
|
|
if( !this.IsSelectingRegion && !this.IsDraggingContent )
|
|
return;
|
|
|
|
// set the origin point and capture the mouse
|
|
_originPoint = e.GetPosition( this );
|
|
_contentPresenter.CaptureMouse();
|
|
e.Handled = true;
|
|
if( this.IsDraggingContent )
|
|
{
|
|
// execute the Drag operation
|
|
this.OnDrag( new DragDeltaEventArgs( 0, 0 ), false );
|
|
}
|
|
else if( this.IsSelectingRegion )
|
|
{
|
|
this.OnSelectRegion( new DragDeltaEventArgs( 0, 0 ), false );
|
|
}
|
|
}
|
|
|
|
private void ProcessMouseLeftButtonUp( MouseButtonEventArgs e )
|
|
{
|
|
if( !this.IsDraggingContent && !this.IsSelectingRegion )
|
|
return;
|
|
|
|
bool endDrag = this.IsDraggingContent;
|
|
|
|
this.SetIsDraggingContent( false );
|
|
this.SetIsSelectingRegion( false );
|
|
|
|
_originPoint = new Point();
|
|
_contentPresenter.ReleaseMouseCapture();
|
|
e.Handled = true;
|
|
|
|
if( endDrag )
|
|
{
|
|
this.OnDrag( new DragDeltaEventArgs( 0, 0 ), true );
|
|
}
|
|
else
|
|
{
|
|
this.OnSelectRegion( new DragDeltaEventArgs( 0, 0 ), true );
|
|
}
|
|
}
|
|
|
|
private void ProcessMouseMove( MouseEventArgs e )
|
|
{
|
|
if( e.MouseDevice.LeftButton != MouseButtonState.Pressed )
|
|
return;
|
|
|
|
if( !this.IsDraggingContent && !this.IsSelectingRegion )
|
|
return;
|
|
|
|
Point pos = e.GetPosition( this );
|
|
e.Handled = true;
|
|
|
|
if( this.IsDraggingContent )
|
|
{
|
|
Vector delta = ( pos - _originPoint ) / this.Scale;
|
|
this.OnDrag( new DragDeltaEventArgs( delta.X, delta.Y ), false );
|
|
_originPoint = pos;
|
|
}
|
|
else if( this.IsSelectingRegion )
|
|
{
|
|
Vector delta = pos - _originPoint;
|
|
this.OnSelectRegion( new DragDeltaEventArgs( delta.X, delta.Y ), false );
|
|
}
|
|
}
|
|
|
|
private void ProcessMouseWheelZoom( MouseWheelEventArgs e )
|
|
{
|
|
if( _content == null )
|
|
return;
|
|
|
|
// check modifiers to see if there's work to do
|
|
bool doZoom = this.ZoomModifiers.AreActive;
|
|
bool doRelativeZoom = this.RelativeZoomModifiers.AreActive;
|
|
|
|
// can't do both, so assume relative zoom
|
|
if( doZoom && doRelativeZoom )
|
|
{
|
|
doZoom = false;
|
|
}
|
|
|
|
if( !( doZoom || doRelativeZoom ) )
|
|
return;
|
|
|
|
e.Handled = true;
|
|
double percentage = ( ( e.Delta / Zoombox.MOUSE_WHEEL_DELTA ) * this.ZoomPercentage ) / 100;
|
|
|
|
// Are we doing a zoom relative to the current mouse position?
|
|
if( doRelativeZoom )
|
|
{
|
|
this.Zoom( percentage, Mouse.GetPosition( _content ) );
|
|
}
|
|
else
|
|
{
|
|
this.Zoom( percentage );
|
|
}
|
|
}
|
|
|
|
private void ProcessNavigationButton( RoutedEventArgs e )
|
|
{
|
|
if( e is MouseButtonEventArgs )
|
|
{
|
|
MouseButtonEventArgs mbea = e as MouseButtonEventArgs;
|
|
if( mbea.ChangedButton == MouseButton.XButton1
|
|
|| mbea.ChangedButton == MouseButton.XButton2 )
|
|
{
|
|
if( mbea.ChangedButton == MouseButton.XButton2 )
|
|
{
|
|
this.GoForward();
|
|
}
|
|
else
|
|
{
|
|
this.GoBack();
|
|
}
|
|
mbea.Handled = true;
|
|
}
|
|
}
|
|
else if( e is KeyEventArgs )
|
|
{
|
|
KeyEventArgs kea = e as KeyEventArgs;
|
|
if( kea.Key == Key.Back || kea.Key == Key.BrowserBack || kea.Key == Key.BrowserForward )
|
|
{
|
|
if( kea.Key == Key.BrowserForward )
|
|
{
|
|
this.GoForward();
|
|
}
|
|
else
|
|
{
|
|
this.GoBack();
|
|
}
|
|
kea.Handled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResizeDisplayViewport( DragDeltaEventArgs e, ResizeEdge relativeTo )
|
|
{
|
|
// get the existing viewport rect and scale
|
|
Rect viewportRect = _viewFinderDisplay.ViewportRect;
|
|
double scale = _viewFinderDisplay.Scale;
|
|
|
|
// ensure that we stay within the bounds of the VisualBrush
|
|
double x = Math.Max( _resizeViewportBounds.Left, Math.Min( _resizeDraggingPoint.X + e.HorizontalChange, _resizeViewportBounds.Right ) );
|
|
double y = Math.Max( _resizeViewportBounds.Top, Math.Min( _resizeDraggingPoint.Y + e.VerticalChange, _resizeViewportBounds.Bottom ) );
|
|
|
|
// get the selected region in the coordinate space of the content
|
|
Point anchorPoint = new Point( _resizeAnchorPoint.X / scale, _resizeAnchorPoint.Y / scale );
|
|
Vector newRegionVector = new Vector( ( x - _resizeAnchorPoint.X ) / scale / _viewboxFactor, ( y - _resizeAnchorPoint.Y ) / scale / _viewboxFactor );
|
|
Rect region = new Rect( anchorPoint, newRegionVector );
|
|
|
|
// now translate the region from the coordinate space of the content
|
|
// to the coordinate space of the content presenter
|
|
region =
|
|
new Rect(
|
|
_content.TranslatePoint( region.TopLeft, _contentPresenter ),
|
|
_content.TranslatePoint( region.BottomRight, _contentPresenter ) );
|
|
|
|
// calculate actual scale value
|
|
double aspectX = this.RenderSize.Width / region.Width;
|
|
double aspectY = this.RenderSize.Height / region.Height;
|
|
scale = aspectX < aspectY ? aspectX : aspectY;
|
|
|
|
// scale relative to the anchor point
|
|
this.ZoomTo( scale, anchorPoint, false, false );
|
|
}
|
|
|
|
private void UpdateKeyModifierTriggerProperties()
|
|
{
|
|
this.SetAreDragModifiersActive( this.DragModifiers.AreActive );
|
|
this.SetAreRelativeZoomModifiersActive( this.RelativeZoomModifiers.AreActive );
|
|
this.SetAreZoomModifiersActive( this.ZoomModifiers.AreActive );
|
|
this.SetAreZoomToSelectionModifiersActive( this.ZoomToSelectionModifiers.AreActive );
|
|
}
|
|
|
|
private void UpdateView( ZoomboxView view, bool allowAnimation, bool allowStackAddition )
|
|
{
|
|
this.UpdateView( view, allowAnimation, allowStackAddition, -1 );
|
|
}
|
|
|
|
private void UpdateView( ZoomboxView view, bool allowAnimation, bool allowStackAddition, int stackIndex )
|
|
{
|
|
if( _contentPresenter == null || _content == null || !this.HasArrangedContentPresenter )
|
|
return;
|
|
|
|
// if an absolute view is being used and only a Scale value has been specified,
|
|
// use the ZoomTo() function to perform a relative zoom
|
|
if( view.ViewKind == ZoomboxViewKind.Absolute && PointHelper.IsEmpty( view.Position ) )
|
|
{
|
|
this.ZoomTo( view.Scale, allowStackAddition );
|
|
return;
|
|
}
|
|
|
|
// disallow reentrancy
|
|
if( !this.IsUpdatingView )
|
|
{
|
|
this.IsUpdatingView = true;
|
|
try
|
|
{
|
|
// determine the new scale and position
|
|
double newRelativeScale = _viewboxFactor;
|
|
Point newRelativePosition = new Point();
|
|
Rect region = Rect.Empty;
|
|
switch( view.ViewKind )
|
|
{
|
|
case ZoomboxViewKind.Empty:
|
|
break;
|
|
|
|
case ZoomboxViewKind.Absolute:
|
|
newRelativeScale = DoubleHelper.IsNaN( view.Scale ) ? _relativeScale : view.Scale;
|
|
newRelativePosition = PointHelper.IsEmpty( view.Position ) ? _relativePosition
|
|
: new Point( view.Position.X, view.Position.Y ) - this.ContentOffset * newRelativeScale;
|
|
break;
|
|
|
|
case ZoomboxViewKind.Region:
|
|
region = view.Region;
|
|
break;
|
|
|
|
case ZoomboxViewKind.Center:
|
|
{
|
|
// get the current ContentRect in the coordinate space of the Zoombox control
|
|
Rect currentContentRect =
|
|
new Rect(
|
|
_content.TranslatePoint( this.ContentRect.TopLeft, this ),
|
|
_content.TranslatePoint( this.ContentRect.BottomRight, this ) );
|
|
|
|
// inflate (or deflate) the rect by the appropriate amounts in the x & y directions
|
|
region = Rect.Inflate( currentContentRect,
|
|
( this.RenderSize.Width / _viewboxFactor - currentContentRect.Width ) / 2,
|
|
( this.RenderSize.Height / _viewboxFactor - currentContentRect.Height ) / 2 );
|
|
|
|
// now translate the centered rect back to the coordinate space of the content
|
|
region = new Rect( this.TranslatePoint( region.TopLeft, _content ), this.TranslatePoint( region.BottomRight, _content ) );
|
|
}
|
|
break;
|
|
|
|
case ZoomboxViewKind.Fit:
|
|
region = this.ContentRect;
|
|
break;
|
|
|
|
case ZoomboxViewKind.Fill:
|
|
region = this.CalculateFillRect();
|
|
break;
|
|
}
|
|
|
|
if( view.ViewKind != ZoomboxViewKind.Empty )
|
|
{
|
|
if( !region.IsEmpty )
|
|
{ // ZOOMING TO A REGION
|
|
this.CalculatePositionAndScale( region, ref newRelativePosition, ref newRelativeScale );
|
|
}
|
|
else if( view != ZoomboxView.Empty )
|
|
{ // USING ABSOLUTE POSITION AND SCALE VALUES
|
|
|
|
// ensure that the scale value falls within the valid range
|
|
if( newRelativeScale > MaxScale )
|
|
{
|
|
newRelativeScale = MaxScale;
|
|
}
|
|
else if( newRelativeScale < MinScale )
|
|
{
|
|
newRelativeScale = MinScale;
|
|
}
|
|
}
|
|
|
|
double currentScale = _relativeScale;
|
|
double currentX = _relativePosition.X;
|
|
double currentY = _relativePosition.Y;
|
|
|
|
ScaleTransform st = null;
|
|
TranslateTransform tt = null;
|
|
TransformGroup tg = null;
|
|
|
|
if( _contentPresenter.RenderTransform != Transform.Identity )
|
|
{
|
|
tg = _contentPresenter.RenderTransform as TransformGroup;
|
|
st = tg.Children[ 0 ] as ScaleTransform;
|
|
tt = tg.Children[ 1 ] as TranslateTransform;
|
|
currentScale = st.ScaleX;
|
|
currentX = tt.X;
|
|
currentY = tt.Y;
|
|
}
|
|
|
|
if( KeepContentInBounds == true )
|
|
{
|
|
Rect boundsRect = new Rect( new Size( this.ContentRect.Width * newRelativeScale, this.ContentRect.Height * newRelativeScale ) );
|
|
|
|
// Calc viewport rect (should be inside bounds content rect)
|
|
Point viewportPosition = new Point( -newRelativePosition.X, -newRelativePosition.Y );
|
|
Rect viewportRect = new Rect( viewportPosition, _contentPresenter.RenderSize );
|
|
|
|
if( DoubleHelper.AreVirtuallyEqual( _relativeScale, newRelativeScale ) ) // we are positioning the content, not scaling
|
|
{
|
|
// Handle the width and height seperately since the content extent
|
|
// could be contained only partially in the viewport. Also if the viewport is only
|
|
// partially contained within the content extent.
|
|
|
|
//
|
|
// Content extent width is greater than the viewport's width (Zoomed in). Make sure we restrict
|
|
// the viewport X inside the content.
|
|
//
|
|
if( this.IsGreaterThanOrClose( boundsRect.Width, viewportRect.Width ) )
|
|
{
|
|
if( boundsRect.Right < viewportRect.Right )
|
|
{
|
|
newRelativePosition.X = -( boundsRect.Width - viewportRect.Width );
|
|
}
|
|
|
|
if( boundsRect.Left > viewportRect.Left )
|
|
{
|
|
newRelativePosition.X = 0;
|
|
}
|
|
}
|
|
//
|
|
// Viewport width is greater than the content extent's width (Zoomed out). Make sure we restrict
|
|
// the content X inside the viewport.
|
|
//
|
|
else if( this.IsGreaterThanOrClose( viewportRect.Width, boundsRect.Width ) )
|
|
{
|
|
if( viewportRect.Right < boundsRect.Right )
|
|
{
|
|
newRelativePosition.X = viewportRect.Width - boundsRect.Width;
|
|
}
|
|
|
|
if( viewportRect.Left > boundsRect.Left )
|
|
{
|
|
newRelativePosition.X = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Content extent height is greater than the viewport's height (Zoomed in). Make sure we restrict
|
|
// the viewport Y inside the content.
|
|
//
|
|
if( this.IsGreaterThanOrClose( boundsRect.Height, viewportRect.Height ) )
|
|
{
|
|
if( boundsRect.Bottom < viewportRect.Bottom )
|
|
{
|
|
newRelativePosition.Y = -( boundsRect.Height - viewportRect.Height );
|
|
}
|
|
|
|
if( boundsRect.Top > viewportRect.Top )
|
|
{
|
|
newRelativePosition.Y = 0;
|
|
}
|
|
}
|
|
//
|
|
// Viewport height is greater than the content extent's height (Zoomed out). Make sure we restrict
|
|
// the content Y inside the viewport.
|
|
//
|
|
else if( this.IsGreaterThanOrClose( viewportRect.Height, boundsRect.Height ) )
|
|
{
|
|
if( viewportRect.Bottom < boundsRect.Bottom )
|
|
{
|
|
newRelativePosition.Y = viewportRect.Height - boundsRect.Height;
|
|
}
|
|
|
|
if( viewportRect.Top > boundsRect.Top )
|
|
{
|
|
newRelativePosition.Y = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
st = new ScaleTransform( newRelativeScale / _viewboxFactor, newRelativeScale / _viewboxFactor );
|
|
tt = new TranslateTransform( newRelativePosition.X, newRelativePosition.Y );
|
|
tg = new TransformGroup();
|
|
tg.Children.Add( st );
|
|
tg.Children.Add( tt );
|
|
|
|
_contentPresenter.RenderTransform = tg;
|
|
|
|
var initialContentSize = ( _content is Viewbox ) ? ( ( Viewbox )_content ).Child.DesiredSize : this.RenderSize;
|
|
var scaledContentSize = new Size( initialContentSize.Width * newRelativeScale, initialContentSize.Height * newRelativeScale );
|
|
|
|
if( allowAnimation && IsAnimated )
|
|
{
|
|
DoubleAnimation daScale = new DoubleAnimation( currentScale, newRelativeScale / _viewboxFactor, AnimationDuration );
|
|
daScale.AccelerationRatio = this.AnimationAccelerationRatio;
|
|
daScale.DecelerationRatio = this.AnimationDecelerationRatio;
|
|
|
|
DoubleAnimation daTranslateX = new DoubleAnimation( currentX, newRelativePosition.X, AnimationDuration );
|
|
daTranslateX.AccelerationRatio = this.AnimationAccelerationRatio;
|
|
daTranslateX.DecelerationRatio = this.AnimationDecelerationRatio;
|
|
|
|
DoubleAnimation daTranslateY = new DoubleAnimation( currentY, newRelativePosition.Y, AnimationDuration );
|
|
daTranslateY.AccelerationRatio = this.AnimationAccelerationRatio;
|
|
daTranslateY.DecelerationRatio = this.AnimationDecelerationRatio;
|
|
daTranslateY.CurrentTimeInvalidated += new EventHandler( this.UpdateViewport );
|
|
daTranslateY.CurrentStateInvalidated += new EventHandler( this.ZoomAnimationCompleted );
|
|
|
|
// raise animation beginning event before beginning the animations
|
|
RaiseEvent( new RoutedEventArgs( AnimationBeginningEvent, this ) );
|
|
|
|
st.BeginAnimation( ScaleTransform.ScaleXProperty, daScale );
|
|
st.BeginAnimation( ScaleTransform.ScaleYProperty, daScale );
|
|
tt.BeginAnimation( TranslateTransform.XProperty, daTranslateX );
|
|
tt.BeginAnimation( TranslateTransform.YProperty, daTranslateY );
|
|
|
|
if( this.IsUsingScrollBars )
|
|
{
|
|
//Vertical scrollBar animations
|
|
DoubleAnimation verticalMaxAnimation = new DoubleAnimation();
|
|
verticalMaxAnimation.From = _verticalScrollBar.Maximum;
|
|
verticalMaxAnimation.To = scaledContentSize.Height - _verticalScrollBar.ViewportSize;
|
|
verticalMaxAnimation.Duration = AnimationDuration;
|
|
_verticalScrollBar.BeginAnimation( ScrollBar.MaximumProperty, verticalMaxAnimation );
|
|
|
|
DoubleAnimation verticalValueAnimation = new DoubleAnimation();
|
|
verticalValueAnimation.From = _verticalScrollBar.Value;
|
|
verticalValueAnimation.To = -newRelativePosition.Y;
|
|
verticalValueAnimation.Duration = AnimationDuration;
|
|
verticalValueAnimation.Completed += this.VerticalValueAnimation_Completed;
|
|
_verticalScrollBar.BeginAnimation( ScrollBar.ValueProperty, verticalValueAnimation );
|
|
|
|
//Horizontal scrollBar animations
|
|
DoubleAnimation horizontalMaxAnimation = new DoubleAnimation();
|
|
horizontalMaxAnimation.From = _horizontalScrollBar.Maximum;
|
|
horizontalMaxAnimation.To = scaledContentSize.Width - _horizontalScrollBar.ViewportSize;
|
|
horizontalMaxAnimation.Duration = AnimationDuration;
|
|
_horizontalScrollBar.BeginAnimation( ScrollBar.MaximumProperty, horizontalMaxAnimation );
|
|
|
|
DoubleAnimation horizontalValueAnimation = new DoubleAnimation();
|
|
horizontalValueAnimation.From = _horizontalScrollBar.Value;
|
|
horizontalValueAnimation.To = -newRelativePosition.X;
|
|
horizontalValueAnimation.Duration = AnimationDuration;
|
|
horizontalValueAnimation.Completed += this.HorizontalValueAnimation_Completed;
|
|
_horizontalScrollBar.BeginAnimation( ScrollBar.ValueProperty, horizontalValueAnimation );
|
|
}
|
|
}
|
|
else if( this.IsUsingScrollBars )
|
|
{
|
|
//Vertical scrollBar
|
|
_verticalScrollBar.Maximum = scaledContentSize.Height - _verticalScrollBar.ViewportSize;
|
|
_verticalScrollBar.Value = -newRelativePosition.Y;
|
|
//Horizontal scrollBar
|
|
_horizontalScrollBar.Maximum = scaledContentSize.Width - _horizontalScrollBar.ViewportSize;
|
|
_horizontalScrollBar.Value = -newRelativePosition.X;
|
|
}
|
|
|
|
// maintain the relative scale and position for dragging and animating purposes
|
|
_relativePosition = newRelativePosition;
|
|
_relativeScale = newRelativeScale;
|
|
|
|
// update the Scale and Position properties to keep them in sync with the current view
|
|
this.Scale = newRelativeScale;
|
|
_basePosition = newRelativePosition + this.ContentOffset * newRelativeScale;
|
|
this.UpdateViewport();
|
|
}
|
|
|
|
// add the current view to the view stack
|
|
if( this.EffectiveViewStackMode == ZoomboxViewStackMode.Auto && allowStackAddition )
|
|
{
|
|
// if the last view was pushed on the stack within the last 300 milliseconds, discard it
|
|
// since proximally close views are probably the result of a mouse wheel scroll
|
|
if( this.ViewStack.Count > 1
|
|
&& Math.Abs( DateTime.Now.Ticks - _lastStackAddition.Ticks ) < TimeSpan.FromMilliseconds( 300 ).Ticks )
|
|
{
|
|
this.ViewStack.RemoveAt( this.ViewStack.Count - 1 );
|
|
_lastStackAddition = DateTime.Now - TimeSpan.FromMilliseconds( 300 );
|
|
}
|
|
|
|
// if the current view is the same as the last view, do nothing
|
|
if( this.ViewStack.Count > 0 && view == this.ViewStack.SelectedView )
|
|
{
|
|
// do nothing
|
|
}
|
|
else
|
|
{
|
|
// otherwise, push the current view on stack
|
|
this.ViewStack.PushView( view );
|
|
this.ViewStackIndex++;
|
|
stackIndex = this.ViewStackIndex;
|
|
|
|
// update the timestamp for the last stack addition
|
|
_lastStackAddition = DateTime.Now;
|
|
}
|
|
}
|
|
|
|
// update the stack indices used by CurrentViewChanged event
|
|
_lastViewIndex = this.CurrentViewIndex;
|
|
this.SetCurrentViewIndex( stackIndex );
|
|
|
|
// set the new view parameters
|
|
// NOTE: this is the only place where the CurrentView member should be set
|
|
this.SetCurrentView( view );
|
|
}
|
|
finally
|
|
{
|
|
this.IsUpdatingView = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsGreaterThanOrClose( double value1, double value2 )
|
|
{
|
|
return value1 <= value2 ? DoubleHelper.AreVirtuallyEqual( value1, value2 ) : true;
|
|
}
|
|
|
|
private Rect CalculateFillRect()
|
|
{
|
|
// determine the x-y ratio of the current Viewport
|
|
double xyRatio = this.RenderSize.Width / this.RenderSize.Height;
|
|
|
|
// now find the maximal rect within the ContentRect that has the same ratio
|
|
double x = 0;
|
|
double y = 0;
|
|
double width = this.ContentRect.Width;
|
|
double height = this.ContentRect.Height;
|
|
if( xyRatio > width / height )
|
|
{
|
|
height = width / xyRatio;
|
|
y = ( this.ContentRect.Height - height ) / 2;
|
|
}
|
|
else
|
|
{
|
|
width = height * xyRatio;
|
|
x = ( this.ContentRect.Width - width ) / 2;
|
|
}
|
|
|
|
return new Rect( x, y, width, height );
|
|
}
|
|
|
|
private void CalculatePositionAndScale( Rect region, ref Point newRelativePosition, ref double newRelativeScale )
|
|
{
|
|
// if there is nothing to scale, just return
|
|
// if the region has no area, just return
|
|
if( region.Width == 0 || region.Height == 0 )
|
|
return;
|
|
|
|
// verify that the selected region intersects with the content, which prevents
|
|
// the scale operation from zooming the content out of the current view
|
|
if( !this.ContentRect.IntersectsWith( region ) )
|
|
return;
|
|
|
|
// translate the region from the coordinate space of the content
|
|
// to the coordinate space of the content presenter
|
|
region =
|
|
new Rect(
|
|
_content.TranslatePoint( region.TopLeft, _contentPresenter ),
|
|
_content.TranslatePoint( region.BottomRight, _contentPresenter ) );
|
|
|
|
// calculate actual zoom, which must fit the entire selection
|
|
// while maintaining a 1:1 ratio
|
|
double aspectX = this.RenderSize.Width / region.Width;
|
|
double aspectY = this.RenderSize.Height / region.Height;
|
|
newRelativeScale = aspectX < aspectY ? aspectX : aspectY;
|
|
|
|
// ensure that the scale value falls within the valid range
|
|
if( newRelativeScale > this.MaxScale )
|
|
{
|
|
newRelativeScale = this.MaxScale;
|
|
}
|
|
else if( newRelativeScale < this.MinScale )
|
|
{
|
|
newRelativeScale = this.MinScale;
|
|
}
|
|
|
|
// determine the new content position for this zoom operation based
|
|
// on HorizontalContentAlignment and VerticalContentAlignment
|
|
double horizontalOffset = 0;
|
|
double verticalOffset = 0;
|
|
switch( this.HorizontalContentAlignment )
|
|
{
|
|
case HorizontalAlignment.Center:
|
|
case HorizontalAlignment.Stretch:
|
|
horizontalOffset = ( this.RenderSize.Width - region.Width * newRelativeScale ) / 2;
|
|
break;
|
|
|
|
case HorizontalAlignment.Right:
|
|
horizontalOffset = ( this.RenderSize.Width - region.Width * newRelativeScale );
|
|
break;
|
|
}
|
|
switch( VerticalContentAlignment )
|
|
{
|
|
case VerticalAlignment.Center:
|
|
case VerticalAlignment.Stretch:
|
|
verticalOffset = ( this.RenderSize.Height - region.Height * newRelativeScale ) / 2;
|
|
break;
|
|
|
|
case VerticalAlignment.Bottom:
|
|
verticalOffset = ( this.RenderSize.Height - region.Height * newRelativeScale );
|
|
break;
|
|
}
|
|
newRelativePosition =
|
|
new Point( -region.TopLeft.X * newRelativeScale, -region.TopLeft.Y * newRelativeScale )
|
|
+ new Vector( horizontalOffset, verticalOffset );
|
|
}
|
|
|
|
private void UpdateViewFinderDisplayContentBounds()
|
|
{
|
|
if( _content == null || _trueContent == null || _viewFinderDisplay == null )
|
|
return;
|
|
|
|
this.UpdateViewboxFactor();
|
|
|
|
// ensure the display panel has a size
|
|
Size contentSize = _content.RenderSize;
|
|
Size viewFinderSize = _viewFinderDisplay.AvailableSize;
|
|
if( viewFinderSize.Width > 0d && DoubleHelper.AreVirtuallyEqual( viewFinderSize.Height, 0d ) )
|
|
{
|
|
// update height to accomodate width, while keeping a ratio equal to the actual content
|
|
viewFinderSize = new Size( viewFinderSize.Width, contentSize.Height * viewFinderSize.Width / contentSize.Width );
|
|
}
|
|
else if( viewFinderSize.Height > 0d && DoubleHelper.AreVirtuallyEqual( viewFinderSize.Width, 0d ) )
|
|
{
|
|
// update width to accomodate height, while keeping a ratio equal to the actual content
|
|
viewFinderSize = new Size( contentSize.Width * viewFinderSize.Height / contentSize.Height, viewFinderSize.Width );
|
|
}
|
|
|
|
// determine the scale of the view finder display panel
|
|
double aspectX = viewFinderSize.Width / contentSize.Width;
|
|
double aspectY = viewFinderSize.Height / contentSize.Height;
|
|
double scale = aspectX < aspectY ? aspectX : aspectY;
|
|
|
|
// determine the rect of the VisualBrush
|
|
double vbWidth = contentSize.Width * scale;
|
|
double vbHeight = contentSize.Height * scale;
|
|
|
|
// set the ContentBounds and Scale properties on the view finder display panel
|
|
_viewFinderDisplay.Scale = scale;
|
|
_viewFinderDisplay.ContentBounds = new Rect( new Size( vbWidth, vbHeight ) );
|
|
}
|
|
|
|
private void UpdateViewboxFactor()
|
|
{
|
|
if( _content == null || _trueContent == null )
|
|
return;
|
|
|
|
double contentWidth = _content.RenderSize.Width;
|
|
double trueContentWidth = _trueContent.RenderSize.Width;
|
|
|
|
if( DoubleHelper.AreVirtuallyEqual( contentWidth, 0d ) || DoubleHelper.AreVirtuallyEqual( trueContentWidth, 0d ) )
|
|
{
|
|
_viewboxFactor = 1d;
|
|
}
|
|
else
|
|
{
|
|
_viewboxFactor = contentWidth / trueContentWidth;
|
|
}
|
|
}
|
|
|
|
private void UpdateViewport()
|
|
{
|
|
// if we haven't attached to the visual tree yet or we don't have content, just return
|
|
if( _contentPresenter == null || _trueContent == null )
|
|
return;
|
|
|
|
this.IsUpdatingViewport = true;
|
|
try
|
|
{
|
|
// calculate the current viewport
|
|
Rect viewport =
|
|
new Rect(
|
|
this.TranslatePoint( new Point( 0d, 0d ), _trueContent ),
|
|
this.TranslatePoint( new Point( RenderSize.Width, RenderSize.Height ), _trueContent ) );
|
|
|
|
// if the viewport has changed, set the Viewport dependency property
|
|
if( !DoubleHelper.AreVirtuallyEqual( viewport, this.Viewport ) )
|
|
{
|
|
this.SetValue( Zoombox.ViewportPropertyKey, viewport );
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.IsUpdatingViewport = false;
|
|
}
|
|
}
|
|
|
|
private void UpdateViewport( object sender, EventArgs e )
|
|
{
|
|
this.UpdateViewport();
|
|
}
|
|
|
|
private void ViewFinderDisplayBeginCapture( object sender, MouseButtonEventArgs e )
|
|
{
|
|
const double ARBITRARY_LARGE_VALUE = 10000000000;
|
|
|
|
// if we need to acquire capture, the Tag property of the view finder display panel
|
|
// will be a ResizeEdge value.
|
|
if( _viewFinderDisplay.Tag is ResizeEdge )
|
|
{
|
|
// if the Tag is ResizeEdge.None, then its a drag operation; otherwise, its a resize
|
|
if( ( ResizeEdge )_viewFinderDisplay.Tag == ResizeEdge.None )
|
|
{
|
|
this.IsDraggingViewport = true;
|
|
}
|
|
else
|
|
{
|
|
this.IsResizingViewport = true;
|
|
Vector direction = new Vector();
|
|
switch( ( ResizeEdge )_viewFinderDisplay.Tag )
|
|
{
|
|
case ResizeEdge.TopLeft:
|
|
_resizeDraggingPoint = _viewFinderDisplay.ViewportRect.TopLeft;
|
|
_resizeAnchorPoint = _viewFinderDisplay.ViewportRect.BottomRight;
|
|
direction = new Vector( -1, -1 );
|
|
break;
|
|
|
|
case ResizeEdge.TopRight:
|
|
_resizeDraggingPoint = _viewFinderDisplay.ViewportRect.TopRight;
|
|
_resizeAnchorPoint = _viewFinderDisplay.ViewportRect.BottomLeft;
|
|
direction = new Vector( 1, -1 );
|
|
break;
|
|
|
|
case ResizeEdge.BottomLeft:
|
|
_resizeDraggingPoint = _viewFinderDisplay.ViewportRect.BottomLeft;
|
|
_resizeAnchorPoint = _viewFinderDisplay.ViewportRect.TopRight;
|
|
direction = new Vector( -1, 1 );
|
|
break;
|
|
|
|
case ResizeEdge.BottomRight:
|
|
_resizeDraggingPoint = _viewFinderDisplay.ViewportRect.BottomRight;
|
|
_resizeAnchorPoint = _viewFinderDisplay.ViewportRect.TopLeft;
|
|
direction = new Vector( 1, 1 );
|
|
break;
|
|
case ResizeEdge.Left:
|
|
_resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Left,
|
|
_viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
|
|
_resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Right,
|
|
_viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
|
|
direction = new Vector( -1, 0 );
|
|
break;
|
|
case ResizeEdge.Top:
|
|
_resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
|
|
_viewFinderDisplay.ViewportRect.Top );
|
|
_resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
|
|
_viewFinderDisplay.ViewportRect.Bottom );
|
|
direction = new Vector( 0, -1 );
|
|
break;
|
|
case ResizeEdge.Right:
|
|
_resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Right,
|
|
_viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
|
|
_resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Left,
|
|
_viewFinderDisplay.ViewportRect.Top + ( _viewFinderDisplay.ViewportRect.Height / 2 ) );
|
|
direction = new Vector( 1, 0 );
|
|
break;
|
|
case ResizeEdge.Bottom:
|
|
_resizeDraggingPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
|
|
_viewFinderDisplay.ViewportRect.Bottom );
|
|
_resizeAnchorPoint = new Point( _viewFinderDisplay.ViewportRect.Left + ( _viewFinderDisplay.ViewportRect.Width / 2 ),
|
|
_viewFinderDisplay.ViewportRect.Top );
|
|
direction = new Vector( 0, 1 );
|
|
break;
|
|
}
|
|
double scale = _viewFinderDisplay.Scale;
|
|
Rect contentRect = _viewFinderDisplay.ContentBounds;
|
|
Vector minVector = new Vector( direction.X * ARBITRARY_LARGE_VALUE, direction.Y * ARBITRARY_LARGE_VALUE );
|
|
Vector maxVector = new Vector( direction.X * contentRect.Width / MaxScale, direction.Y * contentRect.Height / MaxScale );
|
|
_resizeViewportBounds = new Rect( _resizeAnchorPoint + minVector, _resizeAnchorPoint + maxVector );
|
|
}
|
|
|
|
// store the origin of the operation and acquire capture
|
|
_originPoint = e.GetPosition( _viewFinderDisplay );
|
|
_viewFinderDisplay.CaptureMouse();
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void ViewFinderDisplayEndCapture( object sender, MouseButtonEventArgs e )
|
|
{
|
|
// if a drag or resize is in progress, end it and release capture
|
|
if( this.IsDraggingViewport || this.IsResizingViewport )
|
|
{
|
|
// call the DragDisplayViewport method to end the operation
|
|
// and store the current position on the stack
|
|
this.DragDisplayViewport( new DragDeltaEventArgs( 0, 0 ), true );
|
|
|
|
// reset the dragging state variables and release capture
|
|
this.IsDraggingViewport = false;
|
|
this.IsResizingViewport = false;
|
|
_originPoint = new Point();
|
|
_viewFinderDisplay.ReleaseMouseCapture();
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void ViewFinderDisplayMouseMove( object sender, MouseEventArgs e )
|
|
{
|
|
// if a drag operation is in progress, update the operation
|
|
if( e.MouseDevice.LeftButton == MouseButtonState.Pressed
|
|
&& ( this.IsDraggingViewport || this.IsResizingViewport ) )
|
|
{
|
|
Point pos = e.GetPosition( _viewFinderDisplay );
|
|
Vector delta = pos - _originPoint;
|
|
if( this.IsDraggingViewport )
|
|
{
|
|
this.DragDisplayViewport( new DragDeltaEventArgs( delta.X, delta.Y ), false );
|
|
}
|
|
else
|
|
{
|
|
this.ResizeDisplayViewport( new DragDeltaEventArgs( delta.X, delta.Y ), ( ResizeEdge )_viewFinderDisplay.Tag );
|
|
}
|
|
e.Handled = true;
|
|
}
|
|
else
|
|
{
|
|
// update the cursor based on the nearest corner
|
|
Point mousePos = e.GetPosition( _viewFinderDisplay );
|
|
Rect viewportRect = _viewFinderDisplay.ViewportRect;
|
|
double cornerDelta = viewportRect.Width * viewportRect.Height > 100 ? 5.0
|
|
: Math.Sqrt( viewportRect.Width * viewportRect.Height ) / 2;
|
|
|
|
// if the mouse is within the Rect and the Rect does not encompass the entire content, set the appropriate cursor
|
|
if( viewportRect.Contains( mousePos )
|
|
&& !DoubleHelper.AreVirtuallyEqual( Rect.Intersect( viewportRect, _viewFinderDisplay.ContentBounds ), _viewFinderDisplay.ContentBounds ) )
|
|
{
|
|
if( PointHelper.DistanceBetween( mousePos, viewportRect.TopLeft ) < cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.TopLeft;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeNWSE;
|
|
}
|
|
else if( PointHelper.DistanceBetween( mousePos, viewportRect.BottomRight ) < cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.BottomRight;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeNWSE;
|
|
}
|
|
else if( PointHelper.DistanceBetween( mousePos, viewportRect.TopRight ) < cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.TopRight;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeNESW;
|
|
}
|
|
else if( PointHelper.DistanceBetween( mousePos, viewportRect.BottomLeft ) < cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.BottomLeft;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeNESW;
|
|
}
|
|
else if( mousePos.X <= viewportRect.Left + cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.Left;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeWE;
|
|
}
|
|
else if( mousePos.Y <= viewportRect.Top + cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.Top;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeNS;
|
|
}
|
|
else if( mousePos.X >= viewportRect.Right - cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.Right;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeWE;
|
|
}
|
|
else if( mousePos.Y >= viewportRect.Bottom - cornerDelta )
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.Bottom;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeNS;
|
|
}
|
|
else
|
|
{
|
|
_viewFinderDisplay.Tag = ResizeEdge.None;
|
|
_viewFinderDisplay.Cursor = Cursors.SizeAll;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_viewFinderDisplay.Tag = null;
|
|
_viewFinderDisplay.Cursor = Cursors.Arrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ZoomAnimationCompleted( object sender, EventArgs e )
|
|
{
|
|
if( ( sender as AnimationClock ).CurrentState != ClockState.Active )
|
|
{
|
|
// remove the event handlers
|
|
( sender as AnimationClock ).CurrentStateInvalidated -= new EventHandler( this.ZoomAnimationCompleted );
|
|
( sender as AnimationClock ).CurrentTimeInvalidated -= new EventHandler( this.UpdateViewport );
|
|
|
|
// raise animation completed event
|
|
this.RaiseEvent( new RoutedEventArgs( Zoombox.AnimationCompletedEvent, this ) );
|
|
}
|
|
}
|
|
|
|
private void VerticalValueAnimation_Completed( object sender, EventArgs e )
|
|
{
|
|
//When the animaton is completed, with the FillBehavior to HoldEnd,
|
|
//the ScrollBarValue will be overriden with the final animation value, preventing future scroll.
|
|
//To remove it use BeginAnimation with null.
|
|
//http://msdn.microsoft.com/en-us/library/aa970493(v=vs.110).aspx
|
|
|
|
// only do this when all the overlapped animations are done or limits values reached.
|
|
if( ( _verticalScrollBar.Value == -_relativePosition.Y )
|
|
|| ( _verticalScrollBar.Value == _verticalScrollBar.Maximum )
|
|
|| ( _verticalScrollBar.Value == _verticalScrollBar.Minimum ) )
|
|
{
|
|
var finalValue = _verticalScrollBar.Value;
|
|
//this will reset Value to original value of animation
|
|
_verticalScrollBar.BeginAnimation( ScrollBar.ValueProperty, null );
|
|
//this will set to last value of the animation.
|
|
_verticalScrollBar.Value = finalValue;
|
|
}
|
|
}
|
|
|
|
private void HorizontalValueAnimation_Completed( object sender, EventArgs e )
|
|
{
|
|
//When the animaton is completed, with the FillBehavior to HoldEnd,
|
|
//the ScrollBarValue will be overriden with the final animation value, preventing future scroll.
|
|
//To remove it use BeginAnimation with null.
|
|
//http://msdn.microsoft.com/en-us/library/aa970493(v=vs.110).aspx
|
|
|
|
// only do this when all the overlapped animations are done or limits values reached.
|
|
if( ( _horizontalScrollBar.Value == -_relativePosition.X )
|
|
|| ( _horizontalScrollBar.Value == _horizontalScrollBar.Maximum )
|
|
|| ( _horizontalScrollBar.Value == _horizontalScrollBar.Minimum ) )
|
|
{
|
|
var finalValue = _horizontalScrollBar.Value;
|
|
//this will reset Value to original value of animation
|
|
_horizontalScrollBar.BeginAnimation( ScrollBar.ValueProperty, null );
|
|
//this will set to last value of the animation.
|
|
_horizontalScrollBar.Value = finalValue;
|
|
}
|
|
}
|
|
|
|
private void ZoomTo( double scale, bool allowStackAddition )
|
|
{
|
|
// if there is nothing to scale, just return
|
|
if( _content == null )
|
|
return;
|
|
|
|
// adjust the current scale relative to the zoom origin
|
|
this.ZoomTo( scale, this.GetZoomRelativePoint(), true, allowStackAddition );
|
|
}
|
|
|
|
private void ZoomTo( double scale, Point relativeTo, bool restrictRelativePointToContent, bool allowStackAddition )
|
|
{
|
|
// if there is nothing to scale, just return
|
|
if( _content == null )
|
|
return;
|
|
|
|
if( double.IsNaN( scale ) )
|
|
return;
|
|
|
|
// if necessary, verify that the relativeTo point falls within the content
|
|
if( restrictRelativePointToContent && !( new Rect( _content.RenderSize ) ).Contains( relativeTo ) )
|
|
return;
|
|
|
|
// ensure that the scale value falls within the valid range
|
|
if( scale > this.MaxScale )
|
|
{
|
|
scale = this.MaxScale;
|
|
}
|
|
else if( scale < this.MinScale )
|
|
{
|
|
scale = this.MinScale;
|
|
}
|
|
|
|
// internally, updates are always relative to the Zoombox control
|
|
Point translateFrom = relativeTo;
|
|
if( this.HasRenderedFirstView )
|
|
{
|
|
// Note that this TranslatePoint approach will not work until the first render occurs
|
|
relativeTo = _content.TranslatePoint( relativeTo, this );
|
|
|
|
// adjust translateFrom based on relativeTo
|
|
translateFrom = this.TranslatePoint( relativeTo, _contentPresenter );
|
|
}
|
|
else if( _contentPresenter != null )
|
|
{
|
|
// prior to the first render, just use the ContentPresenter's transform and do not adjust translateFrom
|
|
if( _contentPresenter.RenderTransform == Transform.Identity )
|
|
{
|
|
// in order for this approach to work, we must at least make one pass to update a generic view
|
|
// with Scale = 1.0 and Position = 0,0
|
|
this.UpdateView( new ZoomboxView( 1, new Point( 0, 0 ) ), false, false );
|
|
}
|
|
|
|
// now there should be a valid RenderTransform
|
|
relativeTo = ( _contentPresenter.RenderTransform as Transform ).Transform( relativeTo );
|
|
}
|
|
|
|
// determine the new content position for this zoom operation
|
|
Point translateTo = new Point( relativeTo.X - ( translateFrom.X * scale / _viewboxFactor ),
|
|
relativeTo.Y - ( translateFrom.Y * scale / _viewboxFactor ) )
|
|
+ this.ContentOffset * scale / _viewboxFactor;
|
|
this.UpdateView( new ZoomboxView( scale, translateTo ), !this.IsResizingViewport, allowStackAddition );
|
|
}
|
|
|
|
private Point GetZoomRelativePoint()
|
|
{
|
|
Point zoomPoint;
|
|
|
|
if( ZoomOn == ZoomboxZoomOn.View )
|
|
{
|
|
// Transform the viewport point to the content
|
|
Point viewportZoomOrigin = new Point();
|
|
|
|
viewportZoomOrigin.X = this.Viewport.X + ( this.Viewport.Width * this.ZoomOrigin.X );
|
|
viewportZoomOrigin.Y = this.Viewport.Y + ( this.Viewport.Height * this.ZoomOrigin.Y );
|
|
|
|
Point contentZoomOrigin = _trueContent.TranslatePoint( viewportZoomOrigin, _content );
|
|
|
|
if( contentZoomOrigin.X < 0 )
|
|
{
|
|
contentZoomOrigin.X = 0;
|
|
}
|
|
else if( contentZoomOrigin.X > _content.RenderSize.Width )
|
|
{
|
|
contentZoomOrigin.X = _content.RenderSize.Width;
|
|
}
|
|
|
|
if( contentZoomOrigin.Y < 0 )
|
|
{
|
|
contentZoomOrigin.Y = 0;
|
|
}
|
|
else if( contentZoomOrigin.Y > _content.RenderSize.Height )
|
|
{
|
|
contentZoomOrigin.Y = _content.RenderSize.Height;
|
|
}
|
|
|
|
zoomPoint = contentZoomOrigin;
|
|
}
|
|
else
|
|
{
|
|
zoomPoint = new Point( _content.RenderSize.Width * ZoomOrigin.X, _content.RenderSize.Height * ZoomOrigin.Y );
|
|
}
|
|
|
|
return zoomPoint;
|
|
}
|
|
|
|
#region OnKeyDown Methods
|
|
|
|
protected override void OnPreviewKeyDown( KeyEventArgs e )
|
|
{
|
|
if( this.NavigateOnPreview && !e.Handled )
|
|
{
|
|
this.ProcessNavigationButton( e );
|
|
}
|
|
|
|
base.OnPreviewKeyDown( e );
|
|
}
|
|
|
|
protected override void OnKeyDown( KeyEventArgs e )
|
|
{
|
|
if( !this.NavigateOnPreview && !e.Handled )
|
|
{
|
|
this.ProcessNavigationButton( e );
|
|
}
|
|
|
|
base.OnKeyDown( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnMouseDown Methods
|
|
|
|
protected override void OnPreviewMouseDown( MouseButtonEventArgs e )
|
|
{
|
|
if( this.NavigateOnPreview && !e.Handled )
|
|
{
|
|
this.ProcessNavigationButton( e );
|
|
}
|
|
|
|
base.OnPreviewMouseDown( e );
|
|
}
|
|
|
|
protected override void OnMouseDown( MouseButtonEventArgs e )
|
|
{
|
|
if( !this.NavigateOnPreview && !e.Handled )
|
|
{
|
|
this.ProcessNavigationButton( e );
|
|
}
|
|
|
|
base.OnMouseDown( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnMouseEnter Methods
|
|
|
|
protected override void OnMouseEnter( MouseEventArgs e )
|
|
{
|
|
this.MonitorInput();
|
|
|
|
base.OnMouseEnter( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnMouseLeave Methods
|
|
|
|
protected override void OnMouseLeave( MouseEventArgs e )
|
|
{
|
|
this.MonitorInput();
|
|
|
|
base.OnMouseLeave( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnMouseLeftButton Methods
|
|
|
|
protected override void OnPreviewMouseLeftButtonDown( MouseButtonEventArgs e )
|
|
{
|
|
if( this.DragOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseLeftButtonDown( e );
|
|
}
|
|
|
|
base.OnPreviewMouseLeftButtonDown( e );
|
|
}
|
|
|
|
protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e )
|
|
{
|
|
if( !this.DragOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseLeftButtonDown( e );
|
|
}
|
|
|
|
base.OnMouseLeftButtonDown( e );
|
|
}
|
|
|
|
protected override void OnPreviewMouseLeftButtonUp( MouseButtonEventArgs e )
|
|
{
|
|
if( this.DragOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseLeftButtonUp( e );
|
|
}
|
|
|
|
base.OnPreviewMouseLeftButtonUp( e );
|
|
}
|
|
|
|
protected override void OnMouseLeftButtonUp( MouseButtonEventArgs e )
|
|
{
|
|
if( !this.DragOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseLeftButtonUp( e );
|
|
}
|
|
|
|
base.OnMouseLeftButtonUp( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnMouseMove Methods
|
|
|
|
protected override void OnPreviewMouseMove( MouseEventArgs e )
|
|
{
|
|
if( this.DragOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseMove( e );
|
|
}
|
|
|
|
base.OnPreviewMouseMove( e );
|
|
}
|
|
|
|
protected override void OnMouseMove( MouseEventArgs e )
|
|
{
|
|
if( !this.DragOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseMove( e );
|
|
}
|
|
|
|
base.OnMouseMove( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnMouseWheel Methods
|
|
|
|
protected override void OnPreviewMouseWheel( MouseWheelEventArgs e )
|
|
{
|
|
if( this.ZoomOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseWheelZoom( e );
|
|
}
|
|
|
|
base.OnPreviewMouseWheel( e );
|
|
}
|
|
|
|
protected override void OnMouseWheel( MouseWheelEventArgs e )
|
|
{
|
|
if( !this.ZoomOnPreview && !e.Handled && _contentPresenter != null )
|
|
{
|
|
this.ProcessMouseWheelZoom( e );
|
|
}
|
|
|
|
base.OnMouseWheel( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
|
|
// the default value for a single mouse wheel delta appears to be 28
|
|
private static int MOUSE_WHEEL_DELTA = 28;
|
|
|
|
// the content control's one and only content presenter
|
|
private ContentPresenter _contentPresenter = null;
|
|
|
|
//The Scrollbars
|
|
private ScrollBar _verticalScrollBar = null;
|
|
private ScrollBar _horizontalScrollBar = null;
|
|
|
|
// the content of the Zoombox (cast as a UIElement)
|
|
private UIElement _content = null;
|
|
|
|
// the drag adorner used for selecting a region in a zoom-to-selection operation
|
|
private DragAdorner _dragAdorner = null;
|
|
|
|
// the view stack
|
|
private ZoomboxViewStack _viewStack = null;
|
|
|
|
// the view finder display panel
|
|
// this is used to show the current viewport
|
|
private ZoomboxViewFinderDisplay _viewFinderDisplay = null;
|
|
|
|
// state variables used during drag and select operations
|
|
private Rect _resizeViewportBounds = Rect.Empty;
|
|
private Point _resizeAnchorPoint = new Point( 0, 0 );
|
|
private Point _resizeDraggingPoint = new Point( 0, 0 );
|
|
private Point _originPoint = new Point( 0, 0 );
|
|
|
|
private double _viewboxFactor = 1.0;
|
|
private double _relativeScale = 1.0;
|
|
private Point _relativePosition = new Point();
|
|
private Point _basePosition = new Point();
|
|
|
|
// used to track the time delta between stack operations
|
|
private DateTime _lastStackAddition;
|
|
|
|
// used to provide stack index when view changes
|
|
private int _lastViewIndex = -1;
|
|
|
|
private BitVector32 _cacheBits = new BitVector32( 0 );
|
|
|
|
#endregion
|
|
|
|
#region ViewFinderSelectionConverter Nested Type
|
|
|
|
private sealed class ViewFinderSelectionConverter : IValueConverter
|
|
{
|
|
public ViewFinderSelectionConverter( Zoombox zoombox )
|
|
{
|
|
_zoombox = zoombox;
|
|
}
|
|
|
|
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
|
|
{
|
|
Rect viewport = ( Rect )value;
|
|
if( viewport.IsEmpty )
|
|
return viewport;
|
|
|
|
// adjust the viewport from the coordinate space of the Content element
|
|
// to the coordinate space of the view finder display panel
|
|
double scale = _zoombox._viewFinderDisplay.Scale * _zoombox._viewboxFactor;
|
|
Rect result = new Rect( viewport.Left * scale, viewport.Top * scale,
|
|
viewport.Width * scale, viewport.Height * scale );
|
|
result.Offset( _zoombox._viewFinderDisplay.ContentBounds.Left,
|
|
_zoombox._viewFinderDisplay.ContentBounds.Top );
|
|
return result;
|
|
}
|
|
|
|
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
private readonly Zoombox _zoombox;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DragAdorner Nested Type
|
|
|
|
internal sealed class DragAdorner : Adorner
|
|
{
|
|
public DragAdorner( UIElement adornedElement )
|
|
: base( adornedElement )
|
|
{
|
|
this.ClipToBounds = true;
|
|
}
|
|
|
|
public static readonly DependencyProperty BrushProperty =
|
|
DependencyProperty.Register( "Brush", typeof( Brush ), typeof( DragAdorner ),
|
|
new FrameworkPropertyMetadata( Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender ) );
|
|
|
|
public Brush Brush
|
|
{
|
|
get
|
|
{
|
|
return ( Brush )this.GetValue( DragAdorner.BrushProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( DragAdorner.BrushProperty, value );
|
|
}
|
|
}
|
|
|
|
public static readonly DependencyProperty PenProperty =
|
|
DependencyProperty.Register( "Pen", typeof( Pen ), typeof( DragAdorner ),
|
|
new FrameworkPropertyMetadata( new Pen( new SolidColorBrush( Color.FromArgb( 0x7F, 0x3F, 0x3F, 0x3F ) ), 2d ), FrameworkPropertyMetadataOptions.AffectsRender ) );
|
|
|
|
public Pen Pen
|
|
{
|
|
get
|
|
{
|
|
return ( Pen )this.GetValue( DragAdorner.PenProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( DragAdorner.PenProperty, value );
|
|
}
|
|
}
|
|
|
|
public static readonly DependencyProperty RectProperty =
|
|
DependencyProperty.Register( "Rect", typeof( Rect ), typeof( DragAdorner ),
|
|
new FrameworkPropertyMetadata( Rect.Empty, FrameworkPropertyMetadataOptions.AffectsRender,
|
|
new PropertyChangedCallback( DragAdorner.OnRectChanged ) ) );
|
|
|
|
public Rect Rect
|
|
{
|
|
get
|
|
{
|
|
return ( Rect )this.GetValue( DragAdorner.RectProperty );
|
|
}
|
|
set
|
|
{
|
|
this.SetValue( DragAdorner.RectProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnRectChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
DragAdorner dragAdorner = ( DragAdorner )d;
|
|
Rect rect = ( Rect )e.NewValue;
|
|
|
|
// ignore empty values
|
|
if( rect.IsEmpty )
|
|
return;
|
|
|
|
// if the value is not empty, cache the position and size
|
|
dragAdorner._cachedPosition = ( ( Rect )e.NewValue ).TopLeft;
|
|
dragAdorner._cachedSize = ( ( Rect )e.NewValue ).Size;
|
|
}
|
|
|
|
public Point LastPosition
|
|
{
|
|
get
|
|
{
|
|
return _cachedPosition;
|
|
}
|
|
}
|
|
|
|
public Size LastSize
|
|
{
|
|
get
|
|
{
|
|
return _cachedSize;
|
|
}
|
|
}
|
|
|
|
protected override void OnRender( DrawingContext drawingContext )
|
|
{
|
|
drawingContext.DrawRectangle( Brush, Pen, Rect );
|
|
}
|
|
|
|
private Point _cachedPosition;
|
|
private Size _cachedSize;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CacheBits Nested Type
|
|
|
|
private enum CacheBits
|
|
{
|
|
IsUpdatingView = 0x00000001,
|
|
IsUpdatingViewport = 0x00000002,
|
|
IsDraggingViewport = 0x00000004,
|
|
IsResizingViewport = 0x00000008,
|
|
IsMonitoringInput = 0x00000010,
|
|
IsContentWrapped = 0x00000020,
|
|
HasArrangedContentPresenter = 0x00000040,
|
|
HasRenderedFirstView = 0x00000080,
|
|
RefocusViewOnFirstRender = 0x00000100,
|
|
HasUIPermission = 0x00000200,
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ResizeEdge Nested Type
|
|
|
|
private enum ResizeEdge
|
|
{
|
|
None,
|
|
TopLeft,
|
|
TopRight,
|
|
BottomLeft,
|
|
BottomRight,
|
|
Left,
|
|
Top,
|
|
Right,
|
|
Bottom,
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|