Click here to Skip to main content
15,885,767 members
Articles / Desktop Programming / WPF

200% Reflective Class Diagram Creation Tool

Rate me:
Please Sign up or sign in to vote.
4.92/5 (200 votes)
20 Feb 2014CPOL22 min read 579.3K   11.5K   437  
WPF: Version II of my 100% Reflective class diagram creation tool.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

namespace AutoDiagrammer
{
    /// <summary>
    /// FishEyePanel
    /// </summary>

    public class FishEyePanel : Panel
    {
        enum AnimateState { None, Up, Down };

        public FishEyePanel()
        {
            this.Background = Brushes.Transparent;
            this.MouseMove += new MouseEventHandler(FishEyePanel_MouseMove);
            this.MouseEnter += new MouseEventHandler(FishEyePanel_MouseEnter);
            this.MouseLeave += new MouseEventHandler(FishEyePanel_MouseLeave);
        }

        public double Magnification
        {
            get { return (double)GetValue(MagnificationProperty); }
            set { SetValue(MagnificationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Magnification.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MagnificationProperty =
            DependencyProperty.Register("Magnification", typeof(double), typeof(FishEyePanel), new UIPropertyMetadata(2d));

        public int AnimationMilliseconds
        {
            get { return (int)GetValue(AnimationMillisecondsProperty); }
            set { SetValue(AnimationMillisecondsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AnimationMilliseconds.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimationMillisecondsProperty =
            DependencyProperty.Register("AnimationMilliseconds", typeof(int), typeof(FishEyePanel), new UIPropertyMetadata(125));


        // If set true we scale different sized children to a constant width
        public bool ScaleToFit
        {
            get { return (bool)GetValue(ScaleToFitProperty); }
            set { SetValue(ScaleToFitProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ScaleToFit.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ScaleToFitProperty =
            DependencyProperty.Register("ScaleToFit", typeof(bool), typeof(FishEyePanel), new UIPropertyMetadata(true));

        private bool animating = false;
        private Size ourSize;
        private double totalChildWidth = 0;
        private bool wasMouseOver = false;

        void FishEyePanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (!animating)
                this.InvalidateArrange();
        }

        void FishEyePanel_MouseEnter(object sender, MouseEventArgs e)
        {
            this.InvalidateArrange();
        }

        void FishEyePanel_MouseLeave(object sender, MouseEventArgs e)
        {
            this.InvalidateArrange();
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            Size idealSize = new Size(0, 0);

            // Allow children as much room as they want - then scale them
            Size size = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
            foreach (UIElement child in Children)
            {
                child.Measure(size);
                idealSize.Width += child.DesiredSize.Width;
                idealSize.Height = Math.Max(idealSize.Height, child.DesiredSize.Height);
            }

            // EID calls us with infinity, but framework doesn't like us to return infinity
            if (double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width))
                return idealSize;
            else
                return availableSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (this.Children == null || this.Children.Count == 0)
                return finalSize;

            ourSize = finalSize;
            totalChildWidth = 0;

            foreach (UIElement child in this.Children)
            {
                // If this is the first time we've seen this child, add our transforms
                if (child.RenderTransform as TransformGroup == null)
                {
                    child.RenderTransformOrigin = new Point(0, 0.5);
                    TransformGroup group = new TransformGroup();
                    child.RenderTransform = group;
                    group.Children.Add(new ScaleTransform());
                    group.Children.Add(new TranslateTransform());
                    //                    group.Children.Add(new RotateTransform());
                }

                child.Arrange(new Rect(0, 0, child.DesiredSize.Width, child.DesiredSize.Height));
                totalChildWidth += child.DesiredSize.Width;
            }

            AnimateAll();

            return finalSize;
        }

        void AnimateAll()
        {
            if (this.Children == null || this.Children.Count == 0)
                return;

            animating = true;

            double childWidth = ourSize.Width / this.Children.Count;
            // Scale the children so they fit in our size
            double overallScaleFactor = ourSize.Width / totalChildWidth;

            UIElement prevChild = null;
            UIElement theChild = null;
            UIElement nextChild = null;

            double widthSoFar = 0;
            double theChildX = 0;
            double ratio = 0;

            if (this.IsMouseOver)
            {
                double x = Mouse.GetPosition(this).X;
                foreach (UIElement child in this.Children)
                {
                    if (theChild == null)
                        theChildX = widthSoFar;
                    widthSoFar += (ScaleToFit ? childWidth : child.DesiredSize.Width * overallScaleFactor);
                    if (x < widthSoFar && theChild == null)
                        theChild = child;
                    if (theChild == null)
                        prevChild = child;
                    if (nextChild == null && theChild != child && theChild != null)
                    {
                        nextChild = child;
                        break;
                    }
                }
                if (theChild != null)
                    ratio = (x - theChildX) / (ScaleToFit ? childWidth : (theChild.DesiredSize.Width * overallScaleFactor));    // Range 0-1 of where the mouse is inside the child
            }

            // These next few lines took two of us hours to write!
            double mag = Magnification;
            double extra = 0;
            if (theChild != null)
                extra += (mag - 1);

            if (prevChild == null)
                extra += (ratio * (mag - 1));
            else if (nextChild == null)
                extra += ((mag - 1) * (1 - ratio));
            else
                extra += (mag - 1);

            double prevScale = this.Children.Count * (1 + ((mag - 1) * (1 - ratio))) / (this.Children.Count + extra);
            double theScale = (mag * this.Children.Count) / (this.Children.Count + extra);
            double nextScale = this.Children.Count * (1 + ((mag - 1) * ratio)) / (this.Children.Count + extra);
            double otherScale = this.Children.Count / (this.Children.Count + extra);       // Applied to all non-interesting children

            // Adjust for different sized children - we overmagnify large children, so shrink the others
            if (!ScaleToFit && this.IsMouseOver)
            {
                double bigWidth = 0;
                double actualWidth = 0;
                if (prevChild != null)
                {
                    bigWidth += prevScale * prevChild.DesiredSize.Width * overallScaleFactor;
                    actualWidth += prevChild.DesiredSize.Width;
                }
                if (theChild != null)
                {
                    bigWidth += theScale * theChild.DesiredSize.Width * overallScaleFactor;
                    actualWidth += theChild.DesiredSize.Width;
                }
                if (nextChild != null)
                {
                    bigWidth += nextScale * nextChild.DesiredSize.Width * overallScaleFactor;
                    actualWidth += nextChild.DesiredSize.Width;
                }
                double w = (totalChildWidth - actualWidth) * overallScaleFactor * otherScale;
                otherScale *= (ourSize.Width - bigWidth) / w;
            }

            widthSoFar = 0;
            double duration = 0;
            if (wasMouseOver != this.IsMouseOver)
                duration = AnimationMilliseconds;

            foreach (UIElement child in this.Children)
            {
                double scale = otherScale;
                if (child == prevChild)
                {
                    scale = prevScale;
                }
                else if (child == theChild)
                {
                    scale = theScale;
                }
                else if (child == nextChild)
                {
                    scale = nextScale;
                }

                if (ScaleToFit)
                {
                    // Now scale each individual child so it is a standard width
                    scale *= childWidth / child.DesiredSize.Width;
                }
                else
                {
                    // Apply overall scale so all children fit our width
                    scale *= overallScaleFactor;
                }

                AnimateTo(child, 0, widthSoFar, (ourSize.Height - child.DesiredSize.Height) / 2, scale, duration);
                widthSoFar += child.DesiredSize.Width * scale;
            }

            wasMouseOver = this.IsMouseOver;
        }

        private void AnimateTo(UIElement child, double r, double x, double y, double s, double duration)
        {
            TransformGroup group = (TransformGroup)child.RenderTransform;
            ScaleTransform scale = (ScaleTransform)group.Children[0];
            TranslateTransform trans = (TranslateTransform)group.Children[1];
            //            RotateTransform rot = (RotateTransform)group.Children[2];

            if (duration == 0)
            {
                trans.BeginAnimation(TranslateTransform.XProperty, null);
                trans.BeginAnimation(TranslateTransform.YProperty, null);
                scale.BeginAnimation(ScaleTransform.ScaleXProperty, null);
                scale.BeginAnimation(ScaleTransform.ScaleYProperty, null);
                //                rot.BeginAnimation(RotateTransform.AngleProperty, null);
                trans.X = x;
                trans.Y = y;
                scale.ScaleX = s;
                scale.ScaleY = s;
                //                rot.AngleProperty = r;
                animation_Completed(null, null);
            }
            else
            {
                trans.BeginAnimation(TranslateTransform.XProperty, MakeAnimation(x, duration, animation_Completed));
                trans.BeginAnimation(TranslateTransform.YProperty, MakeAnimation(y, duration));
                scale.BeginAnimation(ScaleTransform.ScaleXProperty, MakeAnimation(s, duration));
                scale.BeginAnimation(ScaleTransform.ScaleYProperty, MakeAnimation(s, duration));
                //                rot.BeginAnimation(RotateTransform.AngleProperty, MakeAnimation(r, duration));
            }
        }

        private DoubleAnimation MakeAnimation(double to, double duration)
        {
            return MakeAnimation(to, duration, null);
        }

        private DoubleAnimation MakeAnimation(double to, double duration, EventHandler endEvent)
        {
            DoubleAnimation anim = new DoubleAnimation(to, TimeSpan.FromMilliseconds(duration));
            anim.AccelerationRatio = 0.2;
            anim.DecelerationRatio = 0.7;
            if (endEvent != null)
                anim.Completed += endEvent;
            return anim;
        }

        void animation_Completed(object sender, EventArgs e)
        {
            animating = false;
        }
    }
}

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 Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions