Click here to Skip to main content
11,583,865 members (63,366 online)
Click here to Skip to main content
Add your own
alternative version

Resolving Symbolic References in a CodeDOM (Part 7)

, 2 Dec 2012 CDDL 6.2K 342 13
Resolving symbolic references in a CodeDOM.
Nova.0.6.exe.zip
Mono.Cecil.dll
Nova.CLI.exe
Nova.CodeDOM.dll
Nova.Examples.exe
Nova.Studio.exe
Nova.Test.exe
Nova.UI.dll
Nova.0.6.zip
Nova.CLI
Properties
Nova.CodeDOM
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Interfaces
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Assemblies
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Parsing
Base
Properties
Rendering
Resolving
Utilities
Mono.Cecil
Reflection
Nova.Examples
Properties
Nova.Studio
Images
About.png
Configuration.png
EditCopy.png
EditCut.png
EditDelete.png
EditPaste.png
EditRedo.png
EditUndo.png
Error.png
Exit.png
FileNew.png
FileOpen.png
FileSave.png
FileSaveAll.png
FileSaveAs.png
Find.png
Help.png
Info.png
Logo.png
Options.png
Print.png
PrintPreview.png
Properties.png
Todo.png
Warning.png
Objects.ico
Properties
Settings.settings
Nova.Test
Properties
Nova.UI
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Properties
Resolving
Utilties
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Nova.Studio
{
    /// <summary>
    /// A scrollable TabPanel control.
    /// </summary>
    public class ScrollableTabPanel : Panel, IScrollInfo, INotifyPropertyChanged
    {
        #region --- Members ---

        // For a description of the members below, refer to the respective property's description.
        private ScrollViewer _owningScrollViewer;
        private bool _canScroll_H = true;
        private Size _controlExtent = new Size(0, 0);
        private Size _viewport = new Size(0, 0);
        private Vector _offset;

        // The following GradientStopCollections are being used for assigning an OpacityMask/to child-controls that are only partially visible
        private static readonly GradientStopCollection _gscOpacityMaskStops_TransparentOnLeftAndRight = new GradientStopCollection
                                                                                                            {
                                                                                                                new GradientStop(Colors.Transparent, 0.0),
                                                                                                                new GradientStop(Colors.Black, 0.2),
                                                                                                                new GradientStop(Colors.Black, 0.8),
                                                                                                                new GradientStop(Colors.Transparent, 1.0)
                                                                                                            };

        private static readonly GradientStopCollection _gscOpacityMaskStops_TransparentOnLeft = new GradientStopCollection
                                                                                                    {
                                                                                                        new GradientStop(Colors.Transparent, 0),
                                                                                                        new GradientStop(Colors.Black, 0.5)
                                                                                                    };

        private static readonly GradientStopCollection _gscOpacityMaskStops_TransparentOnRight = new GradientStopCollection
                                                                                                     {
                                                                                                         new GradientStop(Colors.Black, 0.5),
                                                                                                         new GradientStop(Colors.Transparent, 1)
                                                                                                     };

        /// <summary>
        /// This will apply the present scroll-position resp. -offset.
        /// </summary>
        private readonly TranslateTransform _scrollTransform = new TranslateTransform();

        #endregion

        #region --- C'tor ---

        public ScrollableTabPanel()
        {
            RenderTransform = _scrollTransform;
            SizeChanged += ScrollableTabPanel_SizeChanged;
        }

        #endregion

        #region --- Helpers ---

        /// <summary>
        /// Calculates the HorizontalOffset for a given child-control, based on a desired value.
        /// </summary>
        /// <param name="viewport_Left">The left offset of the Viewport.</param>
        /// <param name="viewport_Right">The right offset of the Viewport.</param>
        /// <param name="child_Left">The left offset of the control in question.</param>
        /// <param name="child_Right">The right offset of the control in question.</param>
        /// <returns></returns>
        private static double CalculateNewScrollOffset(double viewport_Left, double viewport_Right, double child_Left, double child_Right)
        {
            // Retrieve basic information about the position of the Viewport within the Extent of the control.
            bool isFurtherToLeft = (child_Left < viewport_Left) && (child_Right < viewport_Right);
            bool isFurtherToRight = (child_Right > viewport_Right) && (child_Left > viewport_Left);
            bool isWiderThanViewport = (child_Right - child_Left) > (viewport_Right - viewport_Left);

            if (!isFurtherToRight && !isFurtherToLeft)
            {
                // Don't change anything - the Viewport is completely visible (inside the Extent's bounds)
                return viewport_Left;
            }

            if (isFurtherToLeft && !isWiderThanViewport)
            {
                // The child is to be placed with its left edge equal to the left edge of the Viewport's present offset.
                return child_Left;
            }

            // The child is to be placed with its right edge equal to the right edge of the Viewport's present offset.
            return (child_Right - (viewport_Right - viewport_Left));
        }

        /// <summary>
        /// Compares the present sizes (Extent/Viewport) against the local values
        /// and updates them, if required.
        /// </summary>
        private void UpdateMembers(Size extent, Size viewportSize)
        {
            if (extent != Extent)
            {
                // The Extent of the control has changed.
                Extent = extent;
                if (ScrollOwner != null) ScrollOwner.InvalidateScrollInfo();
            }

            if (viewportSize != Viewport)
            {
                //The Viewport of the panel has changed.
                Viewport = viewportSize;
                if (ScrollOwner != null)
                    ScrollOwner.InvalidateScrollInfo();
            }

            // Prevent from getting off to the right
            if (HorizontalOffset + Viewport.Width + RightOverflowMargin > ExtentWidth)
                SetHorizontalOffset(HorizontalOffset + Viewport.Width + RightOverflowMargin);

            // Notify UI-subscribers
            NotifyPropertyChanged("CanScroll");
            NotifyPropertyChanged("CanScrollLeft");
            NotifyPropertyChanged("CanScrollRight");
        }

        /// <summary>
        /// Returns the left position of the requested child (in Viewport-coordinates).
        /// </summary>
        /// <param name="child">The child to retrieve the position for.</param>
        private double getLeftEdge(UIElement child)
        {
            double widthTotal = 0;

            // Loop through all child controls, summing up their required width
            foreach (UIElement element in InternalChildren)
            {
                //The width of the current child control
                double width = element.DesiredSize.Width;

                if (child != null && child == element)
                {
                    // The current child control is the one in question, so disregard its width
                    // and return the total width required for all controls further to the left,
                    // equaling the left edge of the requested child control.
                    return widthTotal;
                }

                // Sum up the overall width while the child control in question hasn't been hit.
                widthTotal += width;
            }

            // This shouldn't really be hit as the requested control should've been found beforehand.
            return widthTotal;
        }

        /// <summary>
        /// Determines whether the passed child control is only partially visible
        /// (i.e. whether part of it is outside of the Viewport).
        /// </summary>
        /// <param name="uieChild">The child control to be tested.</param>
        /// <returns>
        /// True if part of the control is further to the left or right of the Viewport, False otherwise.
        /// </returns>
        public bool IsPartlyVisible(UIElement uieChild)
        {
            Rect rctIntersect = GetIntersectionRectangle(uieChild);
            return (rctIntersect != Rect.Empty);
        }

        /// <summary>
        /// Determines the visible part of the passed child control, 
        /// measured between 0 (completely invisible) and 1 (completely visible),
        /// that is overflowing into the right invisible portion of the panel.
        /// </summary>
        /// <param name="child">The child control to be tested.</param>
        /// <returns>
        /// <para>A number between 0 (the control is completely invisible resp. outside of
        /// the Viewport) and 1 (the control is completely visible).</para>
        /// <para>All values between 0 and 1 indicate the part that is visible
        /// (i.e. 0.4 would mean that 40% of the control is visible, the remaining
        /// 60% will overflow into the right invisible portion of the panel.</para>
        /// </returns>
        public double PartlyVisiblePortion_OverflowToRight(UIElement child)
        {
            Rect intersect = GetIntersectionRectangle(child);
            double visiblePortion = 1;
            if (intersect != Rect.Empty && CanScrollRight && intersect.Width < child.DesiredSize.Width && intersect.X > 0)
                visiblePortion = intersect.Width / child.DesiredSize.Width;
            return visiblePortion;
        }

        /// <summary>
        /// Determines the visible part of the passed child control, 
        /// measured between 0 (completely invisible) and 1 (completely visible),
        /// that is overflowing into the left invisible portion of the panel.
        /// </summary>
        /// <param name="child">The child control to be tested.</param>
        /// <returns>
        /// <para>A number between 0 (the control is completely invisible resp. outside of
        /// the Viewport) and 1 (the control is completely visible).</para>
        /// <para>All values between 0 and 1 indicate the part that is visible
        /// (i.e. 0.4 would mean that 40% of the control is visible, the remaining
        /// 60% will overflow into the left invisible portion of the panel.</para>
        /// </returns>
        public double PartlyVisiblePortion_OverflowToLeft(UIElement child)
        {
            Rect intersect = GetIntersectionRectangle(child);
            double visiblePortion = 1;
            if (!(intersect == Rect.Empty) && CanScrollLeft && intersect.Width < child.DesiredSize.Width && intersect.X == 0)
                visiblePortion = intersect.Width / child.DesiredSize.Width;
            return visiblePortion;
        }

        /// <summary>
        /// Returns the currently rendered rectangle that makes up the Viewport.
        /// </summary>
        private Rect GetScrollViewerRectangle()
        {
            return new Rect(new Point(0, 0), ScrollOwner.RenderSize);
        }

        /// <summary>
        /// Returns the rectangle that defines the outer bounds of a child control.
        /// </summary>
        /// <param name="child">The child/control for which to return the bounding rectangle.</param>
        private Rect GetChildRectangle(UIElement child)
        {
            // Retrieve the position of the requested child inside the ScrollViewer control
            GeneralTransform childTransform = child.TransformToAncestor(ScrollOwner);
            return childTransform.TransformBounds(new Rect(new Point(0, 0), child.RenderSize));
        }

        /// <summary>
        /// Returns a Rectangle that contains the intersection between the ScrollViewer's
        /// and the passed child control's boundaries, that is, the portion of the child control
        /// which is currently visibile within the ScrollViewer's Viewport.
        /// </summary>
        /// <param name="child">The child for which to retrieve Rectangle.</param>
        /// <returns></returns>
        private Rect GetIntersectionRectangle(UIElement child)
        {
            // Retrieve the ScrollViewer's rectangle
            Rect scrollViewerRectangle = GetScrollViewerRectangle();
            Rect childRect = GetChildRectangle(child);

            //Return the area/rectangle in which the requested child and the ScrollViewer control's Viewport intersect.
            return Rect.Intersect(scrollViewerRectangle, childRect);
        }

        /// <summary>
        /// Will remove the OpacityMask for all child controls.
        /// </summary>
        private void RemoveOpacityMasks()
        {
            foreach (UIElement child in Children)
                RemoveOpacityMask(child);
        }

        /// <summary>
        /// Will remove the OpacityMask for all child controls.
        /// </summary>
        private void RemoveOpacityMask(UIElement child)
        {
            child.OpacityMask = null;
        }

        /// <summary>
        /// Will check all child controls and set their OpacityMasks.
        /// </summary>
        private void UpdateOpacityMasks()
        {
            foreach (UIElement child in Children)
                UpdateOpacityMask(child);
        }

        /// <summary>
        /// Takes the given child control and checks as to whether the control is completely
        /// visible (in the Viewport). If not (i.e. if it's only partially visible), an OpacityMask
        /// will be applied so that it fades out into nothingness.
        /// </summary>
        private void UpdateOpacityMask(UIElement child)
        {
            if (child == null) return;

            // Retrieve the ScrollViewer's rectangle
            Rect scrollViewerRectangle = GetScrollViewerRectangle();
            if (scrollViewerRectangle == Rect.Empty) return;

            // Retrieve the child control's rectangle
            Rect childRect = GetChildRectangle(child);

            if (scrollViewerRectangle.Contains(childRect))
            {
                // This child is completely visible, so dump the OpacityMask.
                child.OpacityMask = null;
            }
            else
            {
                double partlyVisiblePortion_OverflowToLeft = PartlyVisiblePortion_OverflowToLeft(child);
                double partlyVisiblePortion_OverflowToRight = PartlyVisiblePortion_OverflowToRight(child);

                if (partlyVisiblePortion_OverflowToLeft < 1 && partlyVisiblePortion_OverflowToRight < 1)
                    child.OpacityMask = new LinearGradientBrush(_gscOpacityMaskStops_TransparentOnLeftAndRight, new Point(0, 0), new Point(1, 0));
                else if (partlyVisiblePortion_OverflowToLeft < 1)
                {
                    // A part of the child (to the left) remains invisible, so fade out to the left.
                    child.OpacityMask = new LinearGradientBrush(_gscOpacityMaskStops_TransparentOnLeft, new Point(1 - partlyVisiblePortion_OverflowToLeft, 0), new Point(1, 0));
                }
                else if (partlyVisiblePortion_OverflowToRight < 1)
                {
                    // A part of the child (to the right) remains invisible, so fade out to the right.
                    child.OpacityMask = new LinearGradientBrush(_gscOpacityMaskStops_TransparentOnRight, new Point(0, 0), new Point(partlyVisiblePortion_OverflowToRight, 0));
                }
                else
                {
                    // This child is completely visible, so dump the OpacityMask.
                    // Actually, this part should never be reached as, in this case, the very first
                    // checkup should've resulted in the child-rect being completely contained in
                    // the SV's rect; Well, I'll leave this here anyhow (just to be save).
                    child.OpacityMask = null;
                }
            }
        }

        #endregion

        #region --- Overrides ---

        /// <summary>
        /// This is the 1st pass of the layout process. Here, the Extent's size is being determined.
        /// </summary>
        /// <param name="availableSize">The Viewport's rectangle, as obtained after the 1st pass (MeasureOverride).</param>
        /// <returns>The Viewport's final size.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            // The default size will not reflect any width (i.e., no children) and always the default height.
            Size resultSize = new Size(0, 0);

            // Loop through all child controls ...
            foreach (UIElement child in InternalChildren)
            {
                //... retrieve the desired size of the control ...
                child.Measure(availableSize);
                //... and pass this on to the size we need for the Extent
                resultSize.Width += child.DesiredSize.Width;
                resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
            }

            UpdateMembers(resultSize, availableSize);

            double newWidth = double.IsPositiveInfinity(availableSize.Width) ? resultSize.Width : availableSize.Width;
            resultSize.Width = newWidth;
            return resultSize;
        }

        /// <summary>
        /// This is the 2nd pass of the layout process, where child controls are
        /// being arranged within the panel.
        /// </summary>
        /// <param name="finalSize">The Viewport's rectangle, as obtained after the 1st pass (MeasureOverride).</param>
        /// <returns>The Viewport's final size.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (InternalChildren == null || InternalChildren.Count < 1)
                return finalSize;

            double widthTotal = 0;
            foreach (UIElement child in InternalChildren)
            {
                double width = child.DesiredSize.Width;
                child.Arrange(new Rect(widthTotal, 0, width, child.DesiredSize.Height));
                widthTotal += width;
            }

            return finalSize;
        }

        protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
        {
            base.OnVisualChildrenChanged(visualAdded, visualRemoved);
            UpdateOpacityMasks();
        }

        protected override void OnChildDesiredSizeChanged(UIElement child)
        {
            base.OnChildDesiredSizeChanged(child);
            UpdateOpacityMasks();
        }

        #endregion

        #region --- IScrollInfo Members ---

        /// <summary>
        /// Sets or retrieves whether the control is allowed to scroll horizontally.
        /// </summary>
        public bool CanHorizontallyScroll
        {
            get { return _canScroll_H; }
            set { _canScroll_H = value; }
        }

        /// <summary>
        /// Sets or retrieves whether the control is allowed to scroll vertically.
        /// </summary>
        /// <remarks>
        /// This is DISABLED for the control! Due to the internal plumbing of the ScrollViewer
        /// control, this property needs to be accessible without an exception being thrown;
        /// however, setting this property will do plain nothing.
        /// </remarks>
        public bool CanVerticallyScroll
        {
            // We'll never be able to vertically scroll.
            get { return false; }
            set { }
        }

        /// <summary>
        /// Retrieves the height of the control; since no vertical scrolling has been
        /// implemented, this will return the same value at all times.
        /// </summary>
        public double ExtentHeight
        {
            get { return Extent.Height; }
        }

        /// <summary>
        /// Retrieves the overall width of the content hosted in the panel (i.e., the width
        /// measured between [far left of the scrollable portion] and [far right of the scrollable portion].
        /// </summary>
        public double ExtentWidth
        {
            get { return Extent.Width; }
        }

        /// <summary>
        /// Retrieves the current horizontal scroll offset.
        /// </summary>
        /// <remarks>The setter is private to the class.</remarks>
        public double HorizontalOffset
        {
            get { return _offset.X; }
            private set { _offset.X = value; }
        }

        /// <summary>
        /// Increments the vertical offset.
        /// </summary>
        /// <remarks>This is unsupported.</remarks>
        public void LineDown()
        {
            throw new InvalidOperationException();
        }

        /// <summary>
        /// Decrements the horizontal offset by the amount specified in the <see cref="LineScrollPixelCount"/> property.
        /// </summary>
        public void LineLeft()
        {
            SetHorizontalOffset(HorizontalOffset - LineScrollPixelCount);
        }

        /// <summary>
        /// Increments the horizontal offset by the amount specified in the <see cref="LineScrollPixelCount"/> property.
        /// </summary>
        public void LineRight()
        {
            SetHorizontalOffset(HorizontalOffset + LineScrollPixelCount);
        }

        /// <summary>
        /// Decrements the vertical offset.
        /// </summary>
        /// <remarks>This is unsupported.</remarks>
        public void LineUp()
        {
            throw new InvalidOperationException();
        }

        /// <summary>
        /// Scrolls a child of the panel (Visual) into view.
        /// </summary>
        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            if (rectangle.IsEmpty || visual == null || visual == this || !IsAncestorOf(visual))
                return Rect.Empty;

            double offsetX = 0;
            UIElement controlToMakeVisible = null;
            for (int i = 0; i < InternalChildren.Count; i++)
            {
                if (InternalChildren[i] == visual)
                {
                    controlToMakeVisible = InternalChildren[i];
                    offsetX = getLeftEdge(InternalChildren[i]);
                    break;
                }
            }

            // Set the offset only if the desired element is not already completely visible.
            if (controlToMakeVisible != null)
            {
                // If the first child has been selected, go to the very beginning of the scrollable area
                if (controlToMakeVisible == InternalChildren[0])
                    offsetX = 0;
                // If the last child has been selected, go to the very end of the scrollable area
                else if (controlToMakeVisible == InternalChildren[InternalChildren.Count - 1])
                    offsetX = ExtentWidth - Viewport.Width;
                else
                {
                    offsetX = CalculateNewScrollOffset(HorizontalOffset, HorizontalOffset + Viewport.Width, offsetX,
                                                       offsetX + controlToMakeVisible.DesiredSize.Width);
                }

                SetHorizontalOffset(offsetX);
                rectangle = new Rect(HorizontalOffset, 0, controlToMakeVisible.DesiredSize.Width, Viewport.Height);
            }

            return rectangle;
        }

        public void MouseWheelDown()
        {
            // We won't be responding to the mouse-wheel.
        }

        public void MouseWheelLeft()
        {
            // We won't be responding to the mouse-wheel.
        }

        public void MouseWheelRight()
        {
            // We won't be responding to the mouse-wheel.
        }

        public void MouseWheelUp()
        {
            // We won't be responding to the mouse-wheel.
        }

        public void PageDown()
        {
            // We won't be responding to vertical paging.
        }

        public void PageLeft()
        {
            // We won't be responding to horizontal paging.
        }

        public void PageRight()
        {
            // We won't be responding to horizontal paging.
        }

        public void PageUp()
        {
            // We won't be responding to vertical paging.
        }

        /// <summary>
        /// Sets or retrieves the ScrollViewer control that hosts the panel.
        /// </summary>
        public ScrollViewer ScrollOwner
        {
            get { return _owningScrollViewer; }
            set
            {
                _owningScrollViewer = value;
                if (_owningScrollViewer != null)
                    ScrollOwner.Loaded += ScrollOwner_Loaded;
                else
                    ScrollOwner.Loaded -= ScrollOwner_Loaded;
            }
        }

        public void SetHorizontalOffset(double offset)
        {
            // Remove all OpacityMasks while scrolling.
            RemoveOpacityMasks();

            // Assure that the horizontal offset always contains a valid value
            HorizontalOffset = Math.Max(0, Math.Min(ExtentWidth - Viewport.Width, Math.Max(0, offset)));

            if (ScrollOwner != null) ScrollOwner.InvalidateScrollInfo();

            // If you don't want the animation, you would replace all the code further below (up to but not including)
            // the call to InvalidateMeasure() with the following line:
            //_ttScrollTransform.X = (-this.HorizontalOffset);

            // Animate the new offset
            DoubleAnimation scrollAnimation = new DoubleAnimation(_scrollTransform.X, -HorizontalOffset, new Duration(AnimationTimeSpan), FillBehavior.HoldEnd);

            // Note that, depending on distance between the original and the target scroll-position and
            // the duration of the animation, the  acceleration and deceleration effects might be more
            // or less unnoticeable at runtime.
            scrollAnimation.AccelerationRatio = 0.5;
            scrollAnimation.DecelerationRatio = 0.5;

            // The childrens' OpacityMask can only be set reliably after the scroll-animation
            // has finished its work, so attach to the animation's Completed event where the
            // masks will be re-created.
            scrollAnimation.Completed += ScrollAnimation_Completed;

            _scrollTransform.BeginAnimation(TranslateTransform.XProperty, scrollAnimation, HandoffBehavior.Compose);

            InvalidateMeasure();
        }

        public void SetVerticalOffset(double offset)
        {
            throw new InvalidOperationException();
        }

        public double VerticalOffset
        {
            get { return 0; }
        }

        public double ViewportHeight
        {
            get { return Viewport.Height; }
        }

        public double ViewportWidth
        {
            get { return Viewport.Width; }
        }

        #endregion

        #region --- Additional Properties ---

        /// <summary>
        /// Retrieves the overall resp. internal/inner size of the control/panel.
        /// </summary>
        /// <remarks>The setter is private to the class.</remarks>
        public Size Extent
        {
            get { return _controlExtent; }
            private set { _controlExtent = value; }
        }

        /// <summary>
        /// Retrieves the outer resp. visible size of the control/panel.
        /// </summary>
        /// <remarks>The setter is private to the class.</remarks>
        public Size Viewport
        {
            get { return _viewport; }
            private set { _viewport = value; }
        }

        /// <summary>
        /// Retrieves whether the panel's scroll-position is on the far left (i.e. cannot scroll further to the left).
        /// </summary>
        public bool IsOnFarLeft { get { return HorizontalOffset == 0; } }

        /// <summary>
        /// Retrieves whether the panel's scroll-position is on the far right (i.e. cannot scroll further to the right).
        /// </summary>
        public bool IsOnFarRight { get { return (HorizontalOffset + Viewport.Width) == ExtentWidth; } }

        /// <summary>
        /// Retrieves whether the panel's viewport is larger than the control's extent, meaning there is hidden content 
        /// that the user would have to scroll for in order to see it.
        /// </summary>
        public bool CanScroll { get { return ExtentWidth > Viewport.Width; } }

        /// <summary>
        /// Retrieves whether the panel's scroll-position is NOT on the far left (i.e. can scroll to the left).
        /// </summary>
        public bool CanScrollLeft { get { return CanScroll && !IsOnFarLeft; } }

        /// <summary>
        /// Retrieves whether the panel's scroll-position is NOT on the far right (i.e. can scroll to the right).
        /// </summary>
        public bool CanScrollRight { get { return CanScroll && !IsOnFarRight; } }

        #endregion

        #region --- Additional Dependency Properties ---

        public static readonly DependencyProperty RightOverflowMarginProperty =
           DependencyProperty.Register("RightOverflowMargin", typeof(int), typeof(ScrollableTabPanel),
           new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender));
        /// <summary>
        /// Sets or retrieves the Margin that will be applied to the rightmost item in the panel;
        /// This allows for the item applying a negative margin, i.e. when selected.
        /// If set to a value other than zero (being the default), the control will add the value
        /// specified here to the item's right extent.
        /// </summary>
        public int RightOverflowMargin
        {
            get { return (int)GetValue(RightOverflowMarginProperty); }
            set { SetValue(RightOverflowMarginProperty, value); }
        }

        public static readonly DependencyProperty AnimationTimeSpanProperty =
           DependencyProperty.Register("AnimationTimeSpanProperty", typeof(TimeSpan), typeof(ScrollableTabPanel),
           new FrameworkPropertyMetadata(new TimeSpan(0, 0, 0, 0, 100), FrameworkPropertyMetadataOptions.AffectsRender));
        /// <summary>
        /// Sets or retrieves the the duration (default: 100ms) for the panel's transition-animation that is
        /// started when an item is selected (scroll from the previously selected item to the
        /// presently selected one).
        /// </summary>
        public TimeSpan AnimationTimeSpan
        {
            get { return (TimeSpan)GetValue(AnimationTimeSpanProperty); }
            set { SetValue(AnimationTimeSpanProperty, value); }
        }

        //The amount of pixels to scroll by for the LineLeft() and LineRight() methods.
        public static readonly DependencyProperty LineScrollPixelCountProperty =
           DependencyProperty.Register("LineScrollPixelCount", typeof(int), typeof(ScrollableTabPanel),
           new FrameworkPropertyMetadata(15, FrameworkPropertyMetadataOptions.AffectsRender));
        /// <summary>
        /// Sets or retrieves the count of pixels to scroll by when the LineLeft or LineRight methods
        /// are called (default: 15px).
        /// </summary>
        public int LineScrollPixelCount
        {
            get { return (int)GetValue(LineScrollPixelCountProperty); }
            set { SetValue(LineScrollPixelCountProperty, value); }
        }

        #endregion

        #region --- INotifyPropertyChanged ---

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Called from within this class whenever subscribers (i.e. bindings) are to be notified of a property-change
        /// </summary>
        /// <param name="propertyName">The name of the property that has changed.</param>
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region --- Event Handlers ---

        /// <summary>
        /// Fired when the ScrollViewer is initially loaded/displayed. 
        /// Required in order to initially setup the childrens' OpacityMasks.
        /// </summary>
        void ScrollOwner_Loaded(object sender, RoutedEventArgs e)
        {
            UpdateOpacityMasks();
        }

        /// <summary>
        /// Fired when the scroll-animation has finished its work, that is, at the
        /// point in time when the ScrollViewerer has reached its final scroll-position
        /// resp. offset, which is when the childrens' OpacityMasks can be updated.
        /// </summary>
        void ScrollAnimation_Completed(object sender, EventArgs e)
        {
            UpdateOpacityMasks();

            // This is required in order to update the TabItems' FocusVisual
            foreach (UIElement uieChild in InternalChildren)
                uieChild.InvalidateArrange();
        }

        void ScrollableTabPanel_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateOpacityMasks();
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

Share

About the Author

KenBeckett
Software Developer (Senior)
United States United States
I've been writing software since the late 70's, currently focusing mainly on C#.NET. I also like to travel around the world, and I own a Chocolate Factory (sadly, none of my employees are oompa loompas).

You may also be interested in...

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150603.1 | Last Updated 2 Dec 2012
Article Copyright 2012 by KenBeckett
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid