All the controls missing in WPF. Over 1 million downloads.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

628 lines
18 KiB

/*************************************************************************************
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.Specialized;
using System.Windows;
using System.Windows.Media;
using Xceed.Wpf.Toolkit.Primitives;
using Xceed.Wpf.Toolkit.Core;
using Xceed.Wpf.Toolkit.Core.Utilities;
namespace Xceed.Wpf.Toolkit
{
public sealed class Pie : ShapeBase
{
#region Constructors
static Pie()
{
DefaultStyleKeyProperty.OverrideMetadata( typeof( Pie ), new FrameworkPropertyMetadata( typeof( Pie ) ) );
// The default stretch mode of Pie is Fill
Pie.StretchProperty.OverrideMetadata( typeof( Pie ), new FrameworkPropertyMetadata( Stretch.Fill ) );
Pie.StrokeLineJoinProperty.OverrideMetadata( typeof( Pie ), new FrameworkPropertyMetadata( PenLineJoin.Round ) );
}
public Pie()
: base()
{
}
#endregion
#region EndAngle Property
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register( "EndAngle", typeof( double ), typeof( Pie ),
new FrameworkPropertyMetadata( 360d, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback( Pie.OnEndAngleChanged ), new CoerceValueCallback( Pie.CoerceEndAngleValue ) ) );
public double EndAngle
{
get
{
return ( double )this.GetValue( Pie.EndAngleProperty );
}
set
{
this.SetValue( Pie.EndAngleProperty, value );
}
}
private static void OnEndAngleChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( ( Pie )d ).OnEndAngleChanged( e );
}
private void OnEndAngleChanged( DependencyPropertyChangedEventArgs e )
{
// avoid re-entrancy
if( this.IsUpdatingEndAngle )
return;
if( !( this.IsUpdatingStartAngle || this.IsUpdatingSlice || this.IsUpdatingSweepDirection ) )
{
switch( this.Mode )
{
case PieMode.Slice:
throw new InvalidOperationException( ErrorMessages.GetMessage( "EndAngleCannotBeSetDirectlyInSlice" ) );
}
}
// EndAngle, Slice, and SweepDirection are interrelated and must be kept in sync
this.IsUpdatingEndAngle = true;
try
{
if( this.Mode == PieMode.EndAngle )
{
this.CoerceValue( Pie.SweepDirectionProperty );
}
this.CoerceValue( Pie.SliceProperty );
}
finally
{
this.IsUpdatingEndAngle = false;
}
}
private static object CoerceEndAngleValue( DependencyObject d, object value )
{
// keep EndAngle in sync with Slice and SweepDirection
Pie pie = ( Pie )d;
if( pie.IsUpdatingSlice || pie.IsUpdatingSweepDirection
|| ( pie.IsUpdatingStartAngle && pie.Mode == PieMode.Slice ) )
{
double newValue = pie.StartAngle + ( ( pie.SweepDirection == SweepDirection.Clockwise ) ? 1.0 : -1.0 ) * pie.Slice * 360;
if( !DoubleHelper.AreVirtuallyEqual( ( double )value, newValue ) )
{
value = newValue;
}
}
return value;
}
#endregion
#region Mode Property
public static readonly DependencyProperty ModeProperty =
DependencyProperty.Register( "Mode", typeof( PieMode ), typeof( Pie ),
new FrameworkPropertyMetadata( PieMode.Manual, new PropertyChangedCallback( Pie.OnModeChanged ) ) );
public PieMode Mode
{
get
{
return ( PieMode )this.GetValue( Pie.ModeProperty );
}
set
{
this.SetValue( Pie.ModeProperty, value );
}
}
private static void OnModeChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( ( Pie )d ).OnModeChanged( e );
}
private void OnModeChanged( DependencyPropertyChangedEventArgs e )
{
// disallow reentrancy
if( this.IsUpdatingMode )
return;
this.IsUpdatingMode = true;
try
{
if( this.Mode == PieMode.EndAngle )
{
this.CoerceValue( Pie.SweepDirectionProperty );
}
}
finally
{
this.IsUpdatingMode = false;
}
}
#endregion
#region Slice Property
public static readonly DependencyProperty SliceProperty =
DependencyProperty.Register( "Slice", typeof( double ), typeof( Pie ),
new FrameworkPropertyMetadata( 1d, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback( Pie.OnSliceChanged ), new CoerceValueCallback( Pie.CoerceSliceValue ) ), new ValidateValueCallback( Pie.ValidateSlice ) );
public double Slice
{
get
{
return ( double )this.GetValue( Pie.SliceProperty );
}
set
{
this.SetValue( Pie.SliceProperty, value );
}
}
private static void OnSliceChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( ( Pie )d ).OnSliceChanged( e );
}
private void OnSliceChanged( DependencyPropertyChangedEventArgs e )
{
// avoid re-entrancy
if( this.IsUpdatingSlice )
return;
if( !( this.IsUpdatingStartAngle || this.IsUpdatingEndAngle || this.IsUpdatingSweepDirection ) )
{
if( this.Mode == PieMode.EndAngle )
throw new InvalidOperationException( ErrorMessages.GetMessage( "SliceCannotBeSetDirectlyInEndAngle" ) );
}
// EndAngle and Slice are interrelated and must be kept in sync
this.IsUpdatingSlice = true;
try
{
if( !( this.IsUpdatingStartAngle || this.IsUpdatingEndAngle || ( this.Mode == PieMode.Manual && this.IsUpdatingSweepDirection ) ) )
{
this.CoerceValue( Pie.EndAngleProperty );
}
}
finally
{
this.IsUpdatingSlice = false;
}
}
private static object CoerceSliceValue( DependencyObject d, object value )
{
// keep Slice in sync with EndAngle, StartAngle, and SweepDirection
Pie pie = ( Pie )d;
if( pie.IsUpdatingEndAngle || pie.IsUpdatingStartAngle || pie.IsUpdatingSweepDirection )
{
double slice = Math.Max( -360.0, Math.Min( 360.0, ( pie.EndAngle - pie.StartAngle ) ) ) / ( ( pie.SweepDirection == SweepDirection.Clockwise ) ? 360.0 : -360.0 );
double newValue = DoubleHelper.AreVirtuallyEqual( slice, 0 ) ? 0 : ( slice < 0 ) ? slice + 1 : slice;
if( !DoubleHelper.AreVirtuallyEqual( ( double )value, newValue ) )
{
value = newValue;
}
}
return value;
}
private static bool ValidateSlice( object value )
{
double newValue = ( double )value;
if( newValue < 0 || newValue > 1 || DoubleHelper.IsNaN( newValue ) )
throw new ArgumentException( ErrorMessages.GetMessage( "SliceOOR" ) );
return true;
}
#endregion
#region StartAngle Property
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register( "StartAngle", typeof( double ), typeof( Pie ),
new FrameworkPropertyMetadata( 360d, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback( Pie.OnStartAngleChanged ) ) );
public double StartAngle
{
get
{
return ( double )this.GetValue( Pie.StartAngleProperty );
}
set
{
this.SetValue( Pie.StartAngleProperty, value );
}
}
private static void OnStartAngleChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( ( Pie )d ).OnStartAngleChanged( e );
}
private void OnStartAngleChanged( DependencyPropertyChangedEventArgs e )
{
// avoid re-entrancy
if( this.IsUpdatingStartAngle )
return;
// StartAngle, Slice, and SweepDirection are interrelated and must be kept in sync
this.IsUpdatingStartAngle = true;
try
{
switch( Mode )
{
case PieMode.Manual:
this.CoerceValue( Pie.SliceProperty );
break;
case PieMode.EndAngle:
this.CoerceValue( Pie.SweepDirectionProperty );
this.CoerceValue( Pie.SliceProperty );
break;
case PieMode.Slice:
this.CoerceValue( Pie.EndAngleProperty );
break;
}
}
finally
{
this.IsUpdatingStartAngle = false;
}
}
#endregion
#region SweepDirection Property
public static readonly DependencyProperty SweepDirectionProperty =
DependencyProperty.Register( "SweepDirection", typeof( SweepDirection ), typeof( Pie ),
new FrameworkPropertyMetadata( ( SweepDirection )SweepDirection.Clockwise, FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback( Pie.OnSweepDirectionChanged ), new CoerceValueCallback( Pie.CoerceSweepDirectionValue ) ) );
public SweepDirection SweepDirection
{
get
{
return ( SweepDirection )this.GetValue( Pie.SweepDirectionProperty );
}
set
{
this.SetValue( Pie.SweepDirectionProperty, value );
}
}
private static void OnSweepDirectionChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( ( Pie )d ).OnSweepDirectionChanged( e );
}
private void OnSweepDirectionChanged( DependencyPropertyChangedEventArgs e )
{
// avoid re-entrancy
if( this.IsUpdatingSweepDirection )
return;
// EndAngle, Slice, and SweepDirection are interrelated and must be kept in sync
this.IsUpdatingSweepDirection = true;
try
{
switch( Mode )
{
case PieMode.Slice:
this.CoerceValue( Pie.EndAngleProperty );
break;
default:
this.CoerceValue( Pie.SliceProperty );
break;
}
}
finally
{
this.IsUpdatingSweepDirection = false;
}
}
private static object CoerceSweepDirectionValue( DependencyObject d, object value )
{
// keep SweepDirection in sync with EndAngle and StartAngle
Pie pie = ( Pie )d;
if( pie.IsUpdatingEndAngle || pie.IsUpdatingStartAngle || pie.IsUpdatingMode )
{
if( DoubleHelper.AreVirtuallyEqual( pie.StartAngle, pie.EndAngle ) )
{
// if the values are equal, use previously coerced value
value = pie.SweepDirection;
}
else
{
value = ( pie.EndAngle < pie.StartAngle ) ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
}
}
return value;
}
#endregion
#region GeometryTransform Property
public override Transform GeometryTransform
{
get
{
return Transform.Identity;
}
}
#endregion
#region RenderedGeometry Property
public override Geometry RenderedGeometry
{
get
{
// for a Pie, the RenderedGeometry is the same as the DefiningGeometry
return this.DefiningGeometry;
}
}
#endregion
#region DefiningGeometry Protected Property
protected override Geometry DefiningGeometry
{
get
{
double slice = Slice;
if( _rect.IsEmpty || slice <= 0 )
return Geometry.Empty;
if( slice >= 1 )
return new EllipseGeometry( _rect );
// direction of flow is determined by the SweepDirection property
double directionalFactor = ( this.SweepDirection == SweepDirection.Clockwise ) ? 1.0 : -1.0;
double startAngle = StartAngle;
Point pointA = EllipseHelper.PointOfRadialIntersection( _rect, startAngle );
Point pointB = EllipseHelper.PointOfRadialIntersection( _rect, startAngle + directionalFactor * slice * 360 );
PathSegmentCollection segments = new PathSegmentCollection();
segments.Add( new LineSegment( pointA, true ) );
ArcSegment arc = new ArcSegment();
arc.Point = pointB;
arc.Size = new Size( _rect.Width / 2, _rect.Height / 2 );
arc.IsLargeArc = slice > 0.5;
arc.SweepDirection = SweepDirection;
segments.Add( arc );
PathFigureCollection figures = new PathFigureCollection();
figures.Add( new PathFigure( RectHelper.Center( _rect ), segments, true ) );
return new PathGeometry( figures );
}
}
#endregion
#region IsUpdatingEndAngle Private Property
private bool IsUpdatingEndAngle
{
get
{
return _cacheBits[ ( int )CacheBits.IsUpdatingEndAngle ];
}
set
{
_cacheBits[ ( int )CacheBits.IsUpdatingEndAngle ] = value;
}
}
#endregion
#region IsUpdatingMode Private Property
private bool IsUpdatingMode
{
get
{
return _cacheBits[ ( int )CacheBits.IsUpdatingMode ];
}
set
{
_cacheBits[ ( int )CacheBits.IsUpdatingMode ] = value;
}
}
#endregion
#region IsUpdatingSlice Private Property
private bool IsUpdatingSlice
{
get
{
return _cacheBits[ ( int )CacheBits.IsUpdatingSlice ];
}
set
{
_cacheBits[ ( int )CacheBits.IsUpdatingSlice ] = value;
}
}
#endregion
#region IsUpdatingStartAngle Private Property
private bool IsUpdatingStartAngle
{
get
{
return _cacheBits[ ( int )CacheBits.IsUpdatingStartAngle ];
}
set
{
_cacheBits[ ( int )CacheBits.IsUpdatingStartAngle ] = value;
}
}
#endregion
#region IsUpdatingSweepDirection Private Property
private bool IsUpdatingSweepDirection
{
get
{
return _cacheBits[ ( int )CacheBits.IsUpdatingSweepDirection ];
}
set
{
_cacheBits[ ( int )CacheBits.IsUpdatingSweepDirection ] = value;
}
}
#endregion
internal override Size GetNaturalSize()
{
double strokeThickness = this.GetStrokeThickness();
return new Size( strokeThickness, strokeThickness );
}
internal override Rect GetDefiningGeometryBounds()
{
return _rect;
}
protected override Size ArrangeOverride( Size finalSize )
{
double penThickness = this.GetStrokeThickness();
double margin = penThickness / 2;
_rect = new Rect( margin, margin,
Math.Max( 0, finalSize.Width - penThickness ),
Math.Max( 0, finalSize.Height - penThickness ) );
switch( Stretch )
{
case Stretch.None:
// empty rectangle
_rect.Width = _rect.Height = 0;
break;
case Stretch.Fill:
// already initialized for Fill
break;
case Stretch.Uniform:
// largest square that fits in the final size
if( _rect.Width > _rect.Height )
{
_rect.Width = _rect.Height;
}
else
{
_rect.Height = _rect.Width;
}
break;
case Stretch.UniformToFill:
// smallest square that fills the final size
if( _rect.Width < _rect.Height )
{
_rect.Width = _rect.Height;
}
else
{
_rect.Height = _rect.Width;
}
break;
}
return finalSize;
}
protected override Size MeasureOverride( Size constraint )
{
if( this.Stretch == Stretch.UniformToFill )
{
double width = constraint.Width;
double height = constraint.Height;
if( Double.IsInfinity( width ) && Double.IsInfinity( height ) )
{
return this.GetNaturalSize();
}
else if( Double.IsInfinity( width ) || Double.IsInfinity( height ) )
{
width = Math.Min( width, height );
}
else
{
width = Math.Max( width, height );
}
return new Size( width, width );
}
return this.GetNaturalSize();
}
protected override void OnRender( DrawingContext drawingContext )
{
if( !_rect.IsEmpty )
{
Pen pen = this.GetPen();
drawingContext.DrawGeometry( this.Fill, pen, this.RenderedGeometry );
}
}
#region Private Fields
private Rect _rect = Rect.Empty;
private BitVector32 _cacheBits = new BitVector32( 0 );
#endregion
#region CacheBits Nested Type
private enum CacheBits
{
IsUpdatingEndAngle = 0x00000001,
IsUpdatingMode = 0x00000002,
IsUpdatingSlice = 0x00000004,
IsUpdatingStartAngle = 0x00000008,
IsUpdatingSweepDirection = 0x00000010,
}
#endregion
}
}