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.
1312 lines
39 KiB
1312 lines
39 KiB
/************************************************************************
|
|
|
|
Extended WPF Toolkit
|
|
|
|
Copyright (C) 2010-2012 Xceed Software Inc.
|
|
|
|
This program is provided to you under the terms of the Microsoft Reciprocal
|
|
License (Ms-RL) as published at http://wpftoolkit.codeplex.com/license
|
|
|
|
This program can be provided to you by Xceed Software Inc. under a
|
|
proprietary commercial license agreement for use in non-Open Source
|
|
projects. The commercial version of Extended WPF Toolkit also includes
|
|
priority technical support, commercial updates, and many additional
|
|
useful WPF controls if you license Xceed Business Suite for WPF.
|
|
|
|
Visit http://xceed.com and follow @datagrid on Twitter.
|
|
|
|
**********************************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Media;
|
|
|
|
namespace Xceed.Wpf.Toolkit
|
|
{
|
|
public class TimelinePanel : Panel, IScrollInfo
|
|
{
|
|
#region Members
|
|
|
|
private List<DateElement> _visibleElements = null;
|
|
|
|
#endregion //Members
|
|
|
|
#region Properties
|
|
|
|
#region BeginDate Property
|
|
|
|
public static readonly DependencyProperty BeginDateProperty = DependencyProperty.Register( "BeginDate", typeof( DateTime ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( DateTime.MinValue, FrameworkPropertyMetadataOptions.AffectsMeasure ) );
|
|
public DateTime BeginDate
|
|
{
|
|
get
|
|
{
|
|
return ( DateTime )GetValue( BeginDateProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( BeginDateProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region EndDate Property
|
|
|
|
public static readonly DependencyProperty EndDateProperty = DependencyProperty.Register( "EndDate", typeof( DateTime ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( DateTime.MinValue, FrameworkPropertyMetadataOptions.AffectsMeasure ) );
|
|
public DateTime EndDate
|
|
{
|
|
get
|
|
{
|
|
return ( DateTime )GetValue( EndDateProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( EndDateProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OverlapBehavior Property
|
|
|
|
public static readonly DependencyProperty OverlapBehaviorProperty = DependencyProperty.Register( "OverlapBehavior", typeof( OverlapBehavior ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( default( OverlapBehavior ), FrameworkPropertyMetadataOptions.AffectsMeasure ) );
|
|
public OverlapBehavior OverlapBehavior
|
|
{
|
|
get
|
|
{
|
|
return ( OverlapBehavior )GetValue( OverlapBehaviorProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( OverlapBehaviorProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region KeepOriginalOrderForOverlap Property
|
|
|
|
public static readonly DependencyProperty KeepOriginalOrderForOverlapProperty = DependencyProperty.Register( "KeepOriginalOrderForOverlap", typeof( bool ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.AffectsMeasure ) );
|
|
public bool KeepOriginalOrderForOverlap
|
|
{
|
|
get
|
|
{
|
|
return ( bool )GetValue( KeepOriginalOrderForOverlapProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( KeepOriginalOrderForOverlapProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Orientation Property
|
|
|
|
public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner( typeof( TimelinePanel ), new FrameworkPropertyMetadata( Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure ) );
|
|
public Orientation Orientation
|
|
{
|
|
get
|
|
{
|
|
return ( Orientation )GetValue( OrientationProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( OrientationProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region UnitTimeSpan Property
|
|
|
|
public static readonly DependencyProperty UnitTimeSpanProperty = DependencyProperty.Register( "UnitTimeSpan", typeof( TimeSpan ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( TimeSpan.Zero, FrameworkPropertyMetadataOptions.AffectsMeasure ) );
|
|
public TimeSpan UnitTimeSpan
|
|
{
|
|
get
|
|
{
|
|
return ( TimeSpan )GetValue( UnitTimeSpanProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( UnitTimeSpanProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region UnitSize Property
|
|
|
|
public static readonly DependencyProperty UnitSizeProperty = DependencyProperty.Register( "UnitSize", typeof( double ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( 0d, FrameworkPropertyMetadataOptions.AffectsMeasure ) );
|
|
public double UnitSize
|
|
{
|
|
get
|
|
{
|
|
return ( double )GetValue( UnitSizeProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( UnitSizeProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Date Attached Property
|
|
|
|
public static readonly DependencyProperty DateProperty = DependencyProperty.RegisterAttached( "Date", typeof( DateTime ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( DateTime.MinValue, FrameworkPropertyMetadataOptions.AffectsParentMeasure ) );
|
|
|
|
public static DateTime GetDate( DependencyObject obj )
|
|
{
|
|
return ( DateTime )obj.GetValue( DateProperty );
|
|
}
|
|
|
|
public static void SetDate( DependencyObject obj, DateTime value )
|
|
{
|
|
obj.SetValue( DateProperty, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DateEnd Attached Property
|
|
|
|
public static readonly DependencyProperty DateEndProperty = DependencyProperty.RegisterAttached( "DateEnd", typeof( DateTime ), typeof( TimelinePanel ), new FrameworkPropertyMetadata( DateTime.MinValue, FrameworkPropertyMetadataOptions.AffectsParentMeasure ) );
|
|
|
|
public static DateTime GetDateEnd( DependencyObject obj )
|
|
{
|
|
return ( DateTime )obj.GetValue( DateEndProperty );
|
|
}
|
|
|
|
public static void SetDateEnd( DependencyObject obj, DateTime value )
|
|
{
|
|
obj.SetValue( DateEndProperty, value );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion //Properties
|
|
|
|
#region Constructors
|
|
|
|
static TimelinePanel()
|
|
{
|
|
DefaultStyleKeyProperty.OverrideMetadata( typeof( TimelinePanel ), new FrameworkPropertyMetadata( typeof( TimelinePanel ) ) );
|
|
}
|
|
|
|
#endregion //Constructors
|
|
|
|
#region Base Class Overrides
|
|
|
|
protected override Size MeasureOverride( Size availableSize )
|
|
{
|
|
DateTime calcBeginDate = DateTime.MaxValue;
|
|
DateTime calcEndDate = DateTime.MinValue;
|
|
|
|
foreach( UIElement child in InternalChildren )
|
|
{
|
|
DateTime date = GetDate( child );
|
|
DateTime dateEnd = GetDateEnd( child );
|
|
|
|
if( date < calcBeginDate )
|
|
{
|
|
calcBeginDate = date;
|
|
}
|
|
|
|
if( date > calcEndDate )
|
|
{
|
|
calcEndDate = date;
|
|
}
|
|
|
|
if( dateEnd > calcEndDate )
|
|
{
|
|
calcEndDate = dateEnd;
|
|
}
|
|
}
|
|
|
|
if( BeginDate == DateTime.MinValue )
|
|
{
|
|
BeginDate = calcBeginDate;
|
|
}
|
|
|
|
if( EndDate == DateTime.MinValue )
|
|
{
|
|
EndDate = calcEndDate;
|
|
}
|
|
|
|
foreach( UIElement child in InternalChildren )
|
|
{
|
|
DateTime date = GetDate( child );
|
|
DateTime dateEnd = GetDateEnd( child );
|
|
|
|
Size childSize = availableSize;
|
|
|
|
if( dateEnd > DateTime.MinValue && dateEnd > date )
|
|
{
|
|
if( Orientation == Orientation.Horizontal )
|
|
{
|
|
if( UnitTimeSpan != TimeSpan.Zero && UnitSize > 0 )
|
|
{
|
|
double size = ( double )( dateEnd.Ticks - date.Ticks ) / ( double )UnitTimeSpan.Ticks;
|
|
|
|
childSize.Width = size * UnitSize;
|
|
}
|
|
else if( !double.IsPositiveInfinity( availableSize.Width ) )
|
|
{
|
|
// width is DateEnd - Date
|
|
childSize.Width = CalculateTimelineOffset( dateEnd, availableSize.Width ) - CalculateTimelineOffset( date, availableSize.Width );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( UnitTimeSpan != TimeSpan.Zero && UnitSize > 0 )
|
|
{
|
|
double size = ( double )( dateEnd.Ticks - date.Ticks ) / ( double )UnitTimeSpan.Ticks;
|
|
|
|
childSize.Height = size * UnitSize;
|
|
}
|
|
else if( !double.IsPositiveInfinity( availableSize.Height ) )
|
|
{
|
|
// height is DateEnd - Date
|
|
childSize.Height = CalculateTimelineOffset( dateEnd, availableSize.Height ) - CalculateTimelineOffset( date, availableSize.Height );
|
|
}
|
|
}
|
|
}
|
|
|
|
child.Measure( childSize );
|
|
}
|
|
|
|
Size newAvailableSize = new Size( availableSize.Width, availableSize.Height );
|
|
|
|
if( UnitTimeSpan != TimeSpan.Zero && UnitSize > 0 )
|
|
{
|
|
double size = ( double )( EndDate.Ticks - BeginDate.Ticks ) / ( double )UnitTimeSpan.Ticks;
|
|
|
|
if( Orientation == Orientation.Horizontal )
|
|
{
|
|
newAvailableSize.Width = size * UnitSize;
|
|
}
|
|
else
|
|
{
|
|
newAvailableSize.Height = size * UnitSize;
|
|
}
|
|
}
|
|
|
|
Size desiredSize = new Size();
|
|
|
|
if( ( Orientation == Orientation.Vertical && double.IsPositiveInfinity( newAvailableSize.Height ) ) ||
|
|
( Orientation == Orientation.Horizontal && double.IsPositiveInfinity( newAvailableSize.Width ) ) )
|
|
{
|
|
// Our panel cannot layout items when we have positive infinity in the layout direction
|
|
// so defer to arrange pass.
|
|
_visibleElements = null;
|
|
}
|
|
else
|
|
{
|
|
LayoutItems( InternalChildren, newAvailableSize );
|
|
|
|
Rect desiredRect = new Rect();
|
|
|
|
foreach( DateElement child in _visibleElements )
|
|
{
|
|
desiredRect.Union( child.PlacementRectangle );
|
|
}
|
|
|
|
if( Orientation == Orientation.Horizontal )
|
|
{
|
|
desiredSize.Width = newAvailableSize.Width;
|
|
desiredSize.Height = desiredRect.Size.Height;
|
|
}
|
|
else
|
|
{
|
|
desiredSize.Width = desiredRect.Size.Width;
|
|
desiredSize.Height = newAvailableSize.Height;
|
|
}
|
|
}
|
|
|
|
if( IsScrolling )
|
|
{
|
|
Size viewport = new Size( availableSize.Width, availableSize.Height );
|
|
Size extent = new Size( desiredSize.Width, desiredSize.Height );
|
|
Vector offset = new Vector( Math.Max( 0, Math.Min( _offset.X, extent.Width - viewport.Width ) ),
|
|
Math.Max( 0, Math.Min( _offset.Y, extent.Height - viewport.Height ) ) );
|
|
|
|
SetScrollingData( viewport, extent, offset );
|
|
|
|
desiredSize.Width = Math.Min( desiredSize.Width, availableSize.Width );
|
|
desiredSize.Height = Math.Min( desiredSize.Height, availableSize.Height );
|
|
|
|
_physicalViewport = availableSize;
|
|
}
|
|
|
|
return desiredSize;
|
|
}
|
|
|
|
protected override Size ArrangeOverride( Size finalSize )
|
|
{
|
|
Rect finalRect = new Rect();
|
|
|
|
if( _visibleElements == null )
|
|
{
|
|
LayoutItems( InternalChildren, finalSize );
|
|
}
|
|
|
|
foreach( DateElement child in _visibleElements )
|
|
{
|
|
if( IsScrolling )
|
|
{
|
|
Rect placement = new Rect( child.PlacementRectangle.Location, child.PlacementRectangle.Size );
|
|
placement.Offset( -_offset );
|
|
|
|
child.Element.Arrange( placement );
|
|
}
|
|
else
|
|
{
|
|
child.Element.Arrange( child.PlacementRectangle );
|
|
}
|
|
|
|
finalRect.Union( child.PlacementRectangle );
|
|
}
|
|
|
|
Size renderSize;
|
|
|
|
if( Orientation == Orientation.Horizontal )
|
|
{
|
|
renderSize = new Size( finalSize.Width, finalRect.Size.Height );
|
|
}
|
|
else
|
|
{
|
|
renderSize = new Size( finalRect.Size.Width, finalSize.Height );
|
|
}
|
|
|
|
return renderSize;
|
|
}
|
|
|
|
#endregion //Base Class Overrides
|
|
|
|
#region Methods
|
|
|
|
private void ResetScrollInfo()
|
|
{
|
|
_offset = new Vector();
|
|
_physicalViewport = _viewport = _extent = new Size( 0, 0 );
|
|
}
|
|
|
|
private void SetScrollingData( Size viewport, Size extent, Vector offset )
|
|
{
|
|
_offset = offset;
|
|
|
|
if( !AreVirtuallyEqual( viewport, _viewport ) ||
|
|
!AreVirtuallyEqual( extent, _extent ) ||
|
|
!AreVirtuallyEqual( offset, _computedOffset ) )
|
|
{
|
|
_viewport = viewport;
|
|
_extent = extent;
|
|
_offset = offset;
|
|
|
|
OnScrollChange();
|
|
}
|
|
}
|
|
|
|
private double ValidateInputOffset( double offset, string parameterName )
|
|
{
|
|
if( double.IsNaN( offset ) )
|
|
throw new ArgumentOutOfRangeException( parameterName );
|
|
|
|
return Math.Max( 0d, offset );
|
|
}
|
|
|
|
private void OnScrollChange()
|
|
{
|
|
if( ScrollOwner != null )
|
|
{
|
|
ScrollOwner.InvalidateScrollInfo();
|
|
}
|
|
}
|
|
|
|
private double CalculateTimelineOffset( DateTime d, double finalWidth )
|
|
{
|
|
double offset;
|
|
|
|
long tickRange = EndDate.Ticks - BeginDate.Ticks;
|
|
long tickOffset = d.Ticks - BeginDate.Ticks;
|
|
|
|
if( UnitTimeSpan != TimeSpan.Zero && UnitSize > 0 )
|
|
{
|
|
offset = ( ( double )tickOffset / ( double )UnitTimeSpan.Ticks ) * UnitSize;
|
|
}
|
|
else
|
|
{
|
|
if( tickRange > 0 )
|
|
{
|
|
offset = ( ( double )tickOffset / ( double )tickRange ) * finalWidth;
|
|
}
|
|
else
|
|
{
|
|
offset = 0;
|
|
}
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
private static int CompareElementsByLeft( DateElement a, DateElement b )
|
|
{
|
|
return a.PlacementRectangle.Left.CompareTo( b.PlacementRectangle.Left );
|
|
}
|
|
|
|
private static int CompareElementsByTop( DateElement a, DateElement b )
|
|
{
|
|
return a.PlacementRectangle.Top.CompareTo( b.PlacementRectangle.Top );
|
|
}
|
|
|
|
private void LayoutItems( UIElementCollection children, Size availableSize )
|
|
{
|
|
_visibleElements = new List<DateElement>();
|
|
List<DateElement> overlappingElements = new List<DateElement>();
|
|
|
|
int index = 0;
|
|
foreach( UIElement child in children )
|
|
{
|
|
if( child == null )
|
|
continue;
|
|
|
|
DateTime date = GetDate( child );
|
|
DateTime dateEnd = GetDateEnd( child );
|
|
|
|
if( child.Visibility != Visibility.Collapsed )
|
|
{
|
|
if( KeepOriginalOrderForOverlap )
|
|
{
|
|
_visibleElements.Add( new DateElement( child, date, dateEnd, index ) );
|
|
}
|
|
else
|
|
{
|
|
_visibleElements.Add( new DateElement( child, date, dateEnd ) );
|
|
}
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
_visibleElements.Sort();
|
|
|
|
foreach( DateElement child in _visibleElements )
|
|
{
|
|
DateTime date = GetDate( child.Element );
|
|
DateTime dateEnd = GetDateEnd( child.Element );
|
|
|
|
if( Orientation == Orientation.Vertical )
|
|
{
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// Begin Layout Algorithm (Vertical Orientation)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
// calculate the values for y (top) and height
|
|
|
|
// y
|
|
child.PlacementRectangle.Y = CalculateTimelineOffset( date, availableSize.Height );
|
|
|
|
// height
|
|
if( dateEnd > DateTime.MinValue && dateEnd > date )
|
|
{
|
|
// height is DateEnd - Date
|
|
child.PlacementRectangle.Height = CalculateTimelineOffset( dateEnd, availableSize.Height ) - CalculateTimelineOffset( date, availableSize.Height );
|
|
}
|
|
else
|
|
{
|
|
// height is the desired size
|
|
child.PlacementRectangle.Height = child.Element.DesiredSize.Height;
|
|
}
|
|
|
|
// now calcualte the values for x (left) and width based on the OverlapBehavior
|
|
|
|
switch( OverlapBehavior )
|
|
{
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == None (Vertical)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.None:
|
|
|
|
#region OverlapBehavior == None (Vertical)
|
|
|
|
child.PlacementRectangle.X = 0;
|
|
child.PlacementRectangle.Width = child.Element.DesiredSize.Width;
|
|
|
|
break;
|
|
|
|
#endregion
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == Hide (Vertical)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.Hide:
|
|
|
|
#region OverlapBehavior = Hide
|
|
|
|
overlappingElements.Clear();
|
|
|
|
foreach( DateElement compare in _visibleElements )
|
|
{
|
|
if( child == compare )
|
|
break;
|
|
|
|
Rect childRect = child.PlacementRectangle;
|
|
Rect compareRect = compare.PlacementRectangle;
|
|
|
|
if( childRect.Top >= compareRect.Top && childRect.Top < compareRect.Bottom )
|
|
{
|
|
overlappingElements.Add( compare );
|
|
}
|
|
}
|
|
|
|
if( overlappingElements.Count > 0 )
|
|
{
|
|
child.PlacementRectangle.X = 0;
|
|
child.PlacementRectangle.Y = 0;
|
|
child.PlacementRectangle.Width = 0;
|
|
child.PlacementRectangle.Height = 0;
|
|
}
|
|
else
|
|
{
|
|
child.PlacementRectangle.X = 0;
|
|
child.PlacementRectangle.Width = child.Element.DesiredSize.Width;
|
|
}
|
|
|
|
break;
|
|
|
|
#endregion
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == Stretch (Vertical)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.Stretch:
|
|
|
|
#region OverlapBehavior = Stretch
|
|
|
|
// find the first gap at the desired vertical (Y) location (note that in doing this, we
|
|
// only need to look for elements that intersect at the "top" of the item that is being
|
|
// placed because the _VisibleElements collection has been sorted so that the first
|
|
// items come first--this means there won't be anything "below" the current item that
|
|
// isn't also below the current item
|
|
|
|
// find all overlapping elements
|
|
|
|
overlappingElements.Clear();
|
|
|
|
foreach( DateElement compare in _visibleElements )
|
|
{
|
|
if( child == compare )
|
|
break;
|
|
|
|
Rect childRect = child.PlacementRectangle;
|
|
Rect compareRect = compare.PlacementRectangle;
|
|
|
|
if( childRect.Top >= compareRect.Top && childRect.Top < compareRect.Bottom )
|
|
{
|
|
overlappingElements.Add( compare );
|
|
}
|
|
}
|
|
|
|
// sort the elements according to their "left" value so that as we search through
|
|
// the list we are able to identiy gaps
|
|
|
|
overlappingElements.Sort( CompareElementsByLeft );
|
|
|
|
// initialize left and width such that the item will be stretch to fill the available
|
|
// space and if there are no overlapping items, skip to the end
|
|
|
|
double left = 0;
|
|
double width = availableSize.Width;
|
|
|
|
if( overlappingElements.Count > 0 )
|
|
{
|
|
|
|
bool foundGap = false;
|
|
|
|
// now, look for a gap (there is a good chance that there won't be one, but if there is
|
|
// then we will place the item in it)
|
|
|
|
for( int i = 0; i < overlappingElements.Count; i++ )
|
|
{
|
|
Rect r = overlappingElements[ i ].PlacementRectangle;
|
|
|
|
// if this is the first overlapping element, then look for a gap at the beginning
|
|
if( i == 0 )
|
|
{
|
|
if( r.Left > 0 )
|
|
{
|
|
left = 0;
|
|
width = r.Left;
|
|
foundGap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if this is the last overlapping element
|
|
if( i == overlappingElements.Count - 1 )
|
|
{
|
|
//left = r.Right;
|
|
break;
|
|
}
|
|
|
|
// if this is an element somewhere in the middle, then
|
|
else
|
|
{
|
|
Rect n = overlappingElements[ i + 1 ].PlacementRectangle;
|
|
if( ( n.Left - r.Right ) > 0 )
|
|
{
|
|
left = r.Right;
|
|
width = n.Left - r.Right;
|
|
foundGap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// if we didn't find a gap, we need to make one by scooting the overlapping elements
|
|
// over and then placing the item at the end
|
|
|
|
if( !foundGap )
|
|
{
|
|
width = Math.Min( availableSize.Width / ( overlappingElements.Count + 1 ), overlappingElements[ 0 ].PlacementRectangle.Width );
|
|
left = 0;
|
|
|
|
foreach( DateElement e in overlappingElements )
|
|
{
|
|
e.PlacementRectangle.Width = width;
|
|
e.PlacementRectangle.X = left;
|
|
left += width;
|
|
}
|
|
}
|
|
}
|
|
|
|
child.PlacementRectangle.X = left;
|
|
|
|
if( double.IsPositiveInfinity( width ) )
|
|
{
|
|
child.PlacementRectangle.Width = child.Element.DesiredSize.Width;
|
|
}
|
|
else
|
|
{
|
|
child.PlacementRectangle.Width = width;
|
|
}
|
|
|
|
break;
|
|
|
|
#endregion
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == Stack (Vertical)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.Stack:
|
|
|
|
#region OverlapBehavior = Stack;
|
|
|
|
overlappingElements.Clear();
|
|
|
|
foreach( DateElement compare in _visibleElements )
|
|
{
|
|
if( child == compare )
|
|
break;
|
|
|
|
Rect childRect = child.PlacementRectangle;
|
|
Rect compareRect = compare.PlacementRectangle;
|
|
|
|
if( childRect.Top >= compareRect.Top && childRect.Top < compareRect.Bottom )
|
|
{
|
|
overlappingElements.Add( compare );
|
|
}
|
|
}
|
|
|
|
// sort the elements according to their "left" value so that as we search through
|
|
// the list we are able to identiy gaps
|
|
|
|
overlappingElements.Sort( CompareElementsByLeft );
|
|
|
|
// initialize left and width values, width will always be it's desired size
|
|
|
|
left = 0;
|
|
child.PlacementRectangle.Width = child.Element.DesiredSize.Width;
|
|
|
|
// find the first gap that is big enough to accomodate the current item
|
|
|
|
for( int i = 0; i < overlappingElements.Count; i++ )
|
|
{
|
|
Rect r = overlappingElements[ i ].PlacementRectangle;
|
|
|
|
if( i == 0 )
|
|
{
|
|
if( r.Left >= child.PlacementRectangle.Width )
|
|
{
|
|
left = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i == overlappingElements.Count - 1 )
|
|
{
|
|
left = r.Right;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Rect n = overlappingElements[ i + 1 ].PlacementRectangle;
|
|
if( ( n.Left - r.Right ) >= child.PlacementRectangle.Width )
|
|
{
|
|
left = r.Right;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
child.PlacementRectangle.X = left;
|
|
|
|
break;
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// Begin Layout Algorithm (Horizontal Orientation)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
// calculate the values for x (left) and width
|
|
|
|
// x
|
|
child.PlacementRectangle.X = CalculateTimelineOffset( date, availableSize.Width );
|
|
|
|
// width
|
|
if( dateEnd > DateTime.MinValue && dateEnd > date )
|
|
{
|
|
// width is DateEnd - Date
|
|
child.PlacementRectangle.Width = CalculateTimelineOffset( dateEnd, availableSize.Width ) - CalculateTimelineOffset( date, availableSize.Width );
|
|
}
|
|
else
|
|
{
|
|
// width is the desired size
|
|
child.PlacementRectangle.Width = child.Element.DesiredSize.Width;
|
|
}
|
|
|
|
// now calcualte the values for y (top) and height based on the OverlapBehavior
|
|
|
|
switch( OverlapBehavior )
|
|
{
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == None (Horizontal)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.None:
|
|
|
|
#region OverlapBehavior == None (Horizontal)
|
|
|
|
child.PlacementRectangle.Y = 0;
|
|
child.PlacementRectangle.Height = child.Element.DesiredSize.Height;
|
|
|
|
break;
|
|
|
|
#endregion
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == Hide (Horizontal)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.Hide:
|
|
|
|
#region OverlapBehavior = Hide (Horizontal)
|
|
|
|
overlappingElements.Clear();
|
|
|
|
foreach( DateElement compare in _visibleElements )
|
|
{
|
|
if( child == compare )
|
|
break;
|
|
|
|
Rect childRect = child.PlacementRectangle;
|
|
Rect compareRect = compare.PlacementRectangle;
|
|
|
|
if( childRect.Left >= compareRect.Left && childRect.Left < compareRect.Right )
|
|
{
|
|
overlappingElements.Add( compare );
|
|
}
|
|
}
|
|
|
|
if( overlappingElements.Count > 0 )
|
|
{
|
|
child.PlacementRectangle.X = 0;
|
|
child.PlacementRectangle.Y = 0;
|
|
child.PlacementRectangle.Width = 0;
|
|
child.PlacementRectangle.Height = 0;
|
|
}
|
|
else
|
|
{
|
|
child.PlacementRectangle.Y = 0;
|
|
child.PlacementRectangle.Height = child.Element.DesiredSize.Height;
|
|
}
|
|
|
|
break;
|
|
|
|
#endregion
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == Stretch (Horizontal)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.Stretch:
|
|
|
|
#region OverlapBehavior = Stretch
|
|
|
|
// find the first gap at the desired vertical (Y) location (note that in doing this, we
|
|
// only need to look for elements that intersect at the "top" of the item that is being
|
|
// placed because the _VisibleElements collection has been sorted so that the first
|
|
// items come first--this means there won't be anything "below" the current item that
|
|
// isn't also below the current item
|
|
|
|
// find all overlapping elements
|
|
|
|
overlappingElements.Clear();
|
|
|
|
foreach( DateElement compare in _visibleElements )
|
|
{
|
|
if( child == compare )
|
|
break;
|
|
|
|
Rect childRect = child.PlacementRectangle;
|
|
Rect compareRect = compare.PlacementRectangle;
|
|
|
|
if( childRect.Left >= compareRect.Left && childRect.Left < compareRect.Right )
|
|
{
|
|
overlappingElements.Add( compare );
|
|
}
|
|
}
|
|
|
|
// sort the elements according to their "left" value so that as we search through
|
|
// the list we are able to identiy gaps
|
|
|
|
overlappingElements.Sort( CompareElementsByTop );
|
|
|
|
// initialize left and width such that the item will be stretch to fill the available
|
|
// space and if there are no overlapping items, skip to the end
|
|
|
|
double top = 0;
|
|
double height = availableSize.Height;
|
|
|
|
if( overlappingElements.Count > 0 )
|
|
{
|
|
|
|
bool foundGap = false;
|
|
|
|
// now, look for a gap (there is a good chance that there won't be one, but if there is
|
|
// then we will place the item in it)
|
|
|
|
for( int i = 0; i < overlappingElements.Count; i++ )
|
|
{
|
|
Rect r = overlappingElements[ i ].PlacementRectangle;
|
|
|
|
// if this is the first overlapping element, then look for a gap at the beginning
|
|
if( i == 0 )
|
|
{
|
|
if( r.Top > 0 )
|
|
{
|
|
top = 0;
|
|
height = r.Top;
|
|
foundGap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if this is the last overlapping element
|
|
if( i == overlappingElements.Count - 1 )
|
|
{
|
|
//left = r.Right;
|
|
break;
|
|
}
|
|
|
|
// if this is an element somewhere in the middle, then
|
|
else
|
|
{
|
|
Rect n = overlappingElements[ i + 1 ].PlacementRectangle;
|
|
if( ( n.Top - r.Bottom ) > 0 )
|
|
{
|
|
top = r.Bottom;
|
|
height = n.Top - r.Bottom;
|
|
foundGap = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// if we didn't find a gap, we need to make one by scooting the overlapping elements
|
|
// over and then placing the item at the end
|
|
|
|
if( !foundGap )
|
|
{
|
|
height = Math.Min( availableSize.Height / ( overlappingElements.Count + 1 ), overlappingElements[ 0 ].PlacementRectangle.Height );
|
|
top = 0;
|
|
|
|
foreach( DateElement e in overlappingElements )
|
|
{
|
|
e.PlacementRectangle.Height = height;
|
|
e.PlacementRectangle.Y = top;
|
|
top += height;
|
|
}
|
|
}
|
|
}
|
|
|
|
child.PlacementRectangle.Y = top;
|
|
|
|
if( double.IsPositiveInfinity( height ) )
|
|
{
|
|
child.PlacementRectangle.Height = child.Element.DesiredSize.Height;
|
|
}
|
|
else
|
|
{
|
|
child.PlacementRectangle.Height = height;
|
|
}
|
|
|
|
break;
|
|
|
|
#endregion
|
|
|
|
//---------------------------------------------------------------------
|
|
//
|
|
// OverlapBehavior == Stack (Horizontal)
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
case OverlapBehavior.Stack:
|
|
|
|
#region OverlapBehavior = Stack;
|
|
|
|
overlappingElements.Clear();
|
|
|
|
foreach( DateElement compare in _visibleElements )
|
|
{
|
|
if( child == compare )
|
|
break;
|
|
|
|
Rect childRect = child.PlacementRectangle;
|
|
Rect compareRect = compare.PlacementRectangle;
|
|
|
|
if( childRect.Left >= compareRect.Left && childRect.Left < compareRect.Right )
|
|
{
|
|
overlappingElements.Add( compare );
|
|
}
|
|
}
|
|
|
|
// sort the elements according to their "left" value so that as we search through
|
|
// the list we are able to identiy gaps
|
|
|
|
overlappingElements.Sort( CompareElementsByTop );
|
|
|
|
// initialize left and width values, width will always be it's desired size
|
|
|
|
top = 0;
|
|
child.PlacementRectangle.Height = child.Element.DesiredSize.Height;
|
|
|
|
// find the first gap that is big enough to accomodate the current item
|
|
|
|
for( int i = 0; i < overlappingElements.Count; i++ )
|
|
{
|
|
Rect r = overlappingElements[ i ].PlacementRectangle;
|
|
|
|
if( i == 0 )
|
|
{
|
|
if( r.Top >= child.PlacementRectangle.Height )
|
|
{
|
|
top = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i == overlappingElements.Count - 1 )
|
|
{
|
|
top = r.Bottom;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Rect n = overlappingElements[ i + 1 ].PlacementRectangle;
|
|
if( ( n.Top - r.Bottom ) >= child.PlacementRectangle.Height )
|
|
{
|
|
top = r.Bottom;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
child.PlacementRectangle.Y = top;
|
|
|
|
break;
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool AreVirtuallyEqual( double d1, double d2 )
|
|
{
|
|
if( double.IsPositiveInfinity( d1 ) )
|
|
return double.IsPositiveInfinity( d2 );
|
|
|
|
if( double.IsNegativeInfinity( d1 ) )
|
|
return double.IsNegativeInfinity( d2 );
|
|
|
|
if( double.IsNaN( d1 ) )
|
|
return double.IsNaN( d2 );
|
|
|
|
double n = d1 - d2;
|
|
double d = ( Math.Abs( d1 ) + Math.Abs( d2 ) + 10 ) * 1.0e-15;
|
|
return ( -d < n ) && ( d > n );
|
|
}
|
|
|
|
private static bool AreVirtuallyEqual( Size s1, Size s2 )
|
|
{
|
|
return AreVirtuallyEqual( s1.Width, s2.Width )
|
|
&& AreVirtuallyEqual( s1.Height, s2.Height );
|
|
}
|
|
|
|
private static bool AreVirtuallyEqual( Vector v1, Vector v2 )
|
|
{
|
|
return AreVirtuallyEqual( v1.X, v2.X )
|
|
&& AreVirtuallyEqual( v1.Y, v2.Y );
|
|
}
|
|
|
|
#endregion //Methods
|
|
|
|
#region Interfaces
|
|
|
|
#region IScrollInfo
|
|
|
|
public bool CanHorizontallyScroll
|
|
{
|
|
get
|
|
{
|
|
return _allowHorizontal;
|
|
}
|
|
set
|
|
{
|
|
_allowHorizontal = value;
|
|
}
|
|
}
|
|
|
|
public bool CanVerticallyScroll
|
|
{
|
|
get
|
|
{
|
|
return _allowVertical;
|
|
}
|
|
set
|
|
{
|
|
_allowVertical = value;
|
|
}
|
|
}
|
|
|
|
public double ExtentHeight
|
|
{
|
|
get
|
|
{
|
|
return _extent.Height;
|
|
}
|
|
}
|
|
|
|
public double ExtentWidth
|
|
{
|
|
get
|
|
{
|
|
return _extent.Width;
|
|
}
|
|
}
|
|
|
|
public double HorizontalOffset
|
|
{
|
|
get
|
|
{
|
|
return _offset.X;
|
|
}
|
|
}
|
|
|
|
public void LineDown()
|
|
{
|
|
SetVerticalOffset( VerticalOffset + ( ( Orientation == Orientation.Vertical ) ? 1d : 16d ) );
|
|
}
|
|
|
|
public void LineLeft()
|
|
{
|
|
SetHorizontalOffset( HorizontalOffset - ( ( Orientation == Orientation.Horizontal ) ? 1d : 16d ) );
|
|
}
|
|
|
|
public void LineRight()
|
|
{
|
|
SetHorizontalOffset( HorizontalOffset + ( ( Orientation == Orientation.Horizontal ) ? 1d : 16d ) );
|
|
}
|
|
|
|
public void LineUp()
|
|
{
|
|
SetVerticalOffset( VerticalOffset - ( ( Orientation == Orientation.Vertical ) ? 1d : 16d ) );
|
|
}
|
|
|
|
public Rect MakeVisible( Visual visual, Rect rectangle )
|
|
{
|
|
return rectangle;
|
|
}
|
|
|
|
public void MouseWheelDown()
|
|
{
|
|
SetVerticalOffset( VerticalOffset + ( SystemParameters.WheelScrollLines * ( ( Orientation == Orientation.Vertical ) ? 1d : 16d ) ) );
|
|
}
|
|
|
|
public void MouseWheelLeft()
|
|
{
|
|
SetHorizontalOffset( HorizontalOffset - ( 3d * ( ( Orientation == Orientation.Horizontal ) ? 1d : 16d ) ) );
|
|
}
|
|
|
|
public void MouseWheelRight()
|
|
{
|
|
SetHorizontalOffset( HorizontalOffset + ( 3d * ( ( Orientation == Orientation.Horizontal ) ? 1d : 16d ) ) );
|
|
}
|
|
|
|
public void MouseWheelUp()
|
|
{
|
|
SetVerticalOffset( VerticalOffset - ( SystemParameters.WheelScrollLines * ( ( Orientation == Orientation.Vertical ) ? 1d : 16d ) ) );
|
|
}
|
|
|
|
public void PageDown()
|
|
{
|
|
SetVerticalOffset( VerticalOffset + ViewportHeight );
|
|
}
|
|
|
|
public void PageLeft()
|
|
{
|
|
SetHorizontalOffset( HorizontalOffset - ViewportWidth );
|
|
}
|
|
|
|
public void PageRight()
|
|
{
|
|
SetHorizontalOffset( HorizontalOffset + ViewportWidth );
|
|
}
|
|
|
|
public void PageUp()
|
|
{
|
|
SetVerticalOffset( VerticalOffset - ViewportHeight );
|
|
}
|
|
|
|
public ScrollViewer ScrollOwner
|
|
{
|
|
get
|
|
{
|
|
return _scrollOwner;
|
|
}
|
|
set
|
|
{
|
|
if( _scrollOwner != value )
|
|
{
|
|
_scrollOwner = value;
|
|
|
|
ResetScrollInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetHorizontalOffset( double offset )
|
|
{
|
|
offset = ValidateInputOffset( offset, "HorizontalOffset" );
|
|
|
|
if( !AreVirtuallyEqual( offset, _offset.X ) )
|
|
{
|
|
_offset.X = offset;
|
|
|
|
InvalidateMeasure();
|
|
}
|
|
}
|
|
|
|
public void SetVerticalOffset( double offset )
|
|
{
|
|
offset = ValidateInputOffset( offset, "VerticalOffset" );
|
|
|
|
if( !AreVirtuallyEqual( offset, _offset.Y ) )
|
|
{
|
|
_offset.Y = offset;
|
|
|
|
InvalidateMeasure();
|
|
}
|
|
}
|
|
|
|
public double VerticalOffset
|
|
{
|
|
get
|
|
{
|
|
return _offset.Y;
|
|
}
|
|
}
|
|
|
|
public double ViewportHeight
|
|
{
|
|
get
|
|
{
|
|
return _viewport.Height;
|
|
}
|
|
}
|
|
|
|
public double ViewportWidth
|
|
{
|
|
get
|
|
{
|
|
return _viewport.Width;
|
|
}
|
|
}
|
|
|
|
private bool IsScrolling
|
|
{
|
|
get
|
|
{
|
|
return ( _scrollOwner != null );
|
|
}
|
|
}
|
|
|
|
private bool _allowHorizontal = false;
|
|
private bool _allowVertical = false;
|
|
private Vector _computedOffset = new Vector( 0d, 0d );
|
|
private Size _extent = new Size( 0, 0 );
|
|
private Vector _offset = new Vector( 0, 0 );
|
|
private ScrollViewer _scrollOwner = null;
|
|
private Size _viewport;
|
|
private Size _physicalViewport;
|
|
|
|
#endregion //IScrollInfo
|
|
|
|
#endregion //Interfaces
|
|
}
|
|
}
|
|
|