Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Conceptual Children: A powerful new concept in WPF

, 6 Apr 2008
This article describes a new approach by which an element can remove its visual and logical relationships to its children while maintaining a conceptual parental relationship with those children.
Prize winner in Competition "Best C# article of April 2008"
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using DrWPF.Windows.Controls;
using System.ComponentModel;

namespace WpfDiscipleBlogViewer3D
{
    /// <summary>
    /// A Panel that displays its children in a 
    /// Viewport3D hosted in the adorner layer.
    /// </summary>
    public class Panel3D : LogicalPanel
    {
        #region Data

        static readonly Point ORIGIN_POINT = new Point(0, 0);

        readonly DependencyPropertyDescriptor _isSelectedPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ListBoxItem.IsSelectedProperty, typeof(ListBoxItem));
        Viewport3D _viewport;
        readonly Dictionary<DependencyObject, Viewport2DVisual3D> _visualTo3DModelMap = new Dictionary<DependencyObject, Viewport2DVisual3D>();

        #endregion // Data

        #region Constructor

        public Panel3D()
        {
            _viewport = Application.LoadComponent(
                new Uri("Scene.xaml", UriKind.Relative))
                as Viewport3D;
        }

        #endregion // Constructor

        #region MoveItems

        /// <summary>
        /// Moves the items forward or backward one position over one second.
        /// </summary>
        public void MoveItems(int itemCount, bool forward)
        {
            this.MoveItems(itemCount, forward, TimeSpan.FromSeconds(1));
        }

        /// <summary>
        /// Moves the items forward or backward one position over the specified length of time.
        /// </summary>
        public void MoveItems(int itemCount, bool forward, TimeSpan animationLength)
        {
            if (itemCount < 0 || _viewport.Children.Count - 1 < itemCount)
                throw new ArgumentOutOfRangeException("itemCount");

            // We cannot move items less than two items.
            // The first item is a light source, so ignore it.
            if (_viewport.Children.Count < 3)
                return;

            #region Create Lists

            // Get a list of all the Viewport2DVisual3D and TranslateTransform3D objects in the viewport.
            List<Viewport2DVisual3D> viewport2Ds = new List<Viewport2DVisual3D>();
            List<TranslateTransform3D> transforms = new List<TranslateTransform3D>();
            foreach (object model in _viewport.Children)
            {
                if (model is ModelVisual3D)
                    continue;

                var viewport2D = model as Viewport2DVisual3D;
                viewport2Ds.Add(viewport2D);
                transforms.Add(viewport2D.Transform as TranslateTransform3D);
            }

            #endregion // Create Lists

            #region Relocate Target Item

            for (int i = 0; i < itemCount; ++i)
            {
                // Move the first or last item to the opposite end of the list.
                if (forward)
                {
                    var firstGeo = viewport2Ds[0];
                    viewport2Ds.RemoveAt(0);
                    viewport2Ds.Add(firstGeo);

                    // The item at index 0 holds the scene's light
                    // so don't remove that, instead remove the first
                    // model that we added to the scene in code.
                    var firstChild = _viewport.Children[1];
                    _viewport.Children.RemoveAt(1);
                    _viewport.Children.Add(firstChild);
                }
                else
                {
                    int idx = viewport2Ds.Count - 1;
                    var lastGeo = viewport2Ds[idx];
                    viewport2Ds.RemoveAt(idx);
                    viewport2Ds.Insert(0, lastGeo);

                    idx = _viewport.Children.Count - 1;
                    var lastChild = _viewport.Children[idx];
                    _viewport.Children.RemoveAt(idx);
                    _viewport.Children.Insert(1, lastChild);
                }
            }

            #endregion // Relocate Target Item

            #region Select Front Item

            var frontVisual = _viewport.Children[1] as Viewport2DVisual3D;
            var listBoxItem = frontVisual.Visual as ListBoxItem;
            if (listBoxItem != null)
            {
                var listbox = ListBox.GetItemsOwner(this) as ListBox;
                listbox.SelectedItem = listBoxItem.DataContext;
            }

            #endregion // Select Front Item

            #region Animate All Items to New Locations and Opacitys

            // Apply the new transforms via animations.
            for (int i = 0; i < transforms.Count; ++i)
            {
                double targetX = (i + 1) * -1;
                double targetY = (i + 1) * +1;
                double targetZ = (i + 1) * -8;

                var trans = viewport2Ds[i].Transform as TranslateTransform3D;

                Duration duration = new Duration(animationLength);

                DoubleAnimation animX = new DoubleAnimation();
                animX.To = targetX;
                animX.Duration = duration;
                animX.AccelerationRatio = forward ? 0 : 1;
                animX.DecelerationRatio = forward ? 1 : 0;
                trans.BeginAnimation(TranslateTransform3D.OffsetXProperty, animX);

                DoubleAnimation animY = new DoubleAnimation();
                animY.To = targetY;
                animY.AccelerationRatio = forward ? 0.7 : 0.3;
                animY.DecelerationRatio = forward ? 0.3 : 0.7;
                animY.Duration = duration;
                trans.BeginAnimation(TranslateTransform3D.OffsetYProperty, animY);

                DoubleAnimation animZ = new DoubleAnimation();
                animZ.To = targetZ;
                animZ.AccelerationRatio = forward ? 0.3 : 0.7;
                animZ.DecelerationRatio = forward ? 0.7 : 0.3;
                animZ.Duration = duration;
                trans.BeginAnimation(TranslateTransform3D.OffsetZProperty, animZ);

                DoubleAnimation animOpacity = new DoubleAnimation();
                animOpacity.To = 1 / (i + 1.0);
                animOpacity.AccelerationRatio = 0.2;
                animOpacity.DecelerationRatio = 0.8;
                animOpacity.Duration = duration;
                var elem = viewport2Ds[i].Visual as FrameworkElement;
                elem.BeginAnimation(FrameworkElement.OpacityProperty, animOpacity);
            }

            #endregion // Animate All Items to New Locations and Opacitys
        }

        #endregion // MoveItems

        #region Layout Overrides

        protected override Size ArrangeOverride(Size finalSize)
        {
            _viewport.Arrange(new Rect(ORIGIN_POINT, finalSize));
            return finalSize;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            // make sure the viewport is parented on first measure
            if (_viewport.Tag == null)
            {
                AddVisualChild(_viewport);
                _viewport.Tag = string.Empty;
            }
            _viewport.Measure(availableSize);

            return _viewport.DesiredSize;
        }

        #endregion  // Layout Overrides

        #region Create 3D Objects

        protected override void OnLogicalChildrenChanged(UIElement visualAdded, UIElement visualRemoved)
        {
            // Do not create a model for the Viewport3D.
            if (visualAdded == _viewport) 
                return;

            bool add = visualAdded != null && !_visualTo3DModelMap.ContainsKey(visualAdded);
            if (add)
            {
                var model = BuildInteractive3DModel(visualAdded as FrameworkElement);
                _visualTo3DModelMap.Add(visualAdded, model);
                _viewport.Children.Add(model);

                if (visualAdded is ListBoxItem)
                    _isSelectedPropertyDescriptor.AddValueChanged(visualAdded, this.OnListBoxItemSelected);
            }

            bool remove = visualRemoved != null && _visualTo3DModelMap.ContainsKey(visualRemoved);
            if (remove)
            {
                var model = _visualTo3DModelMap[visualRemoved];                
                _visualTo3DModelMap.Remove(visualRemoved);
                _viewport.Children.Remove(model);

                if (visualAdded is ListBoxItem)
                    _isSelectedPropertyDescriptor.RemoveValueChanged(visualAdded, this.OnListBoxItemSelected);
            }
        }

        void OnListBoxItemSelected(object sender, EventArgs e)
        {
            var listBoxItem = sender as ListBoxItem;
            if (listBoxItem == null || !listBoxItem.IsSelected)
                return;

            var model = _visualTo3DModelMap[listBoxItem];

            // The first item in the Viewport3D's Children is the
            // light source, so ignore it.
            int idx = _viewport.Children.IndexOf(model) - 1;

            // Bring the selected item to the front.
            this.MoveItems(idx, true);
        }

        Viewport2DVisual3D BuildInteractive3DModel(FrameworkElement cp)
        {
            cp.Opacity = 1.0 / Math.Max(_viewport.Children.Count, 1);

            var model = new Viewport2DVisual3D
            {
                Geometry = new MeshGeometry3D
                {
                    TriangleIndices = new Int32Collection(
                        new int[] { 0, 1, 2, 2, 3, 0 }),
                    TextureCoordinates = new PointCollection(
                        new Point[] 
                            { 
                                new Point(0, 1), 
                                new Point(1, 1), 
                                new Point(1, 0), 
                                new Point(0, 0) 
                            }),
                    Positions = new Point3DCollection(
                        new Point3D[] 
                            { 
                                new Point3D(-1, -1, 0), 
                                new Point3D(+1, -1, 0), 
                                new Point3D(+1, +1, 0), 
                                new Point3D(-1, +1, 0) 
                            })
                },
                Material = new DiffuseMaterial
                {
                    Brush = Brushes.Transparent
                },
                Transform = new TranslateTransform3D
                {
                    OffsetX = _viewport.Children.Count * -1,
                    OffsetY = _viewport.Children.Count * +1,
                    OffsetZ = _viewport.Children.Count * -8
                },
                // Host ContentPresenter in the 3D object.
                Visual = cp
            };

            Viewport2DVisual3D.SetIsVisualHostMaterial(
                model.Material, true);
            return model;
        }

        #endregion // Create 3D Objects
    }
}

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 BSD License

Share

About the Author

Dr. WPF

United States United States
Dr. WPF is a WPF Disciple! Check out the doctor's blog and bio for more information.

| Advertise | Privacy | Mobile
Web01 | 2.8.140821.2 | Last Updated 6 Apr 2008
Article Copyright 2008 by Dr. WPF
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid