Professional, Software

Updated Stack 3D Image Viewer

I blogged about the little control (Stack 3D) that I wrote that shows pictures in virtual 3D. Here is the improved code for that. There are two improvements here…

  1. I added error handling code on PictureFrame so that when image fails to load it shows the text message
  2. I also changed the rotation logic so that I don’t add all pictures at the same time in the tree. Instead I only keep things that are needed in visual tree for given view.

Error handling for Picture Frame

This is pretty straight forward.

 public PictureFrame() 
        { 
            Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.PictureFrame.xaml"); 
            _rootCanvas = (Canvas)this.InitializeFromXaml(new StreamReader(s).ReadToEnd()); 
            _image = (Image)_rootCanvas.FindName("image"); 
            _txterror = (TextBlock)_rootCanvas.FindName("txtError"); 
            _image.ImageFailed += new ErrorEventHandler(_image_ImageFailed); 
            _rectborder = (Rectangle)_rootCanvas.FindName("rectborder"); 
            _rootCanvas.Loaded += new EventHandler(_rootCanvas_Loaded); 
        }  

        void _image_ImageFailed(object sender, ErrorEventArgs e) 
        { 
            _txterror.Visibility = Visibility.Visible; 
        }

rotation logic

This was more interesting

using System; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Documents; 
using System.Windows.Ink; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Animation; 
using System.Windows.Shapes; 
using System.Collections.ObjectModel; 
using System.IO;  

namespace Visualization 
{ 
    public class StackThreeD : Control 
    { 
        private Canvas _rootCanvas; 
        private int _numberOfFrames = 2; 
        private Collection<FrameworkElement> _frames; 
        private Collection<Point> _points; 
        private Collection<Size> _sizes; 
        private Collection<int> _currentview; 
        private FrameworkElement _hideframe; 
        private bool _enablemovenext = true;  

        public StackThreeD() 
        { 
            Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.StackThreeD.xaml"); 
            _rootCanvas = (Canvas)this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd()); 
            _rootCanvas.Loaded += new EventHandler(_rootCanvas_Loaded); 
            _currentview = new Collection<int>();  

        }  

        public int NumberOfFrames 
        { 
            get { return _numberOfFrames; } 
            set 
            { 
                _numberOfFrames = value; 
                _currentview.Clear(); 
                for (int i = 0; i < _numberOfFrames; i++) 
                { 
                    _currentview.Add(i); 
                } 
            } 
        }  

        public Collection<FrameworkElement> Frames 
        { 
            get { return _frames; } 
            set 
            { 
                _frames = value; 
                PopulateTree(); 
            } 
        }  

        private void PopulateTree() 
        { 
            _rootCanvas.Children.Clear(); 
            if (_currentview.Count > 0) 
            { 
                foreach (int i in _currentview) 
                { 
                    FrameworkElement pf = _frames[i]; 
                    _rootCanvas.Children.Add(pf); 
                    pf.SetValue(DependencyObject.NameProperty, i.ToString()); 
                    string _transformstring; 
                    Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.FrameTransform.xaml"); 
                    _transformstring = new StreamReader(s).ReadToEnd(); 
                    TransformGroup tg = (TransformGroup)XamlReader.Load(_transformstring); 
                    pf.RenderTransform = tg; 
                    AttachAnimation(pf); 
                    pf.Width = Width / 2; 
                    pf.Height = Height / 2;  

                } 
            } 
        }  

        public void MoveNext() 
        { 
            if (_enablemovenext == true) 
            { 
                _enablemovenext = false; 
                HideFrame(); 
                UpdateCurrentViewCollection(); 
                AddFrame(); 
                for (int i = 0; i < _numberOfFrames; i++) 
                { 
                    MoveFrame(i); 
                } 
            }  

        }  

        private void UpdateCurrentViewCollection() 
        { 
            for (int i = 0; i < _currentview.Count; i++) 
            { 
                _currentview[i] = _currentview[i] - 1; 
                if (_currentview[i] < 0) 
                    _currentview[i] = _frames.Count + _currentview[i]; 
            } 
        }  

        private void MoveFrame(int index) 
        { 
            FrameworkElement currentframe = _frames[_currentview[index]]; 
            currentframe.SetValue(UIElement.ZIndexProperty, index); 
            double startscale;  

            if (index == 0) 
            { 
                currentframe.SetValue(Canvas.TopProperty, 0); 
                currentframe.SetValue(Canvas.LeftProperty, 0); 
                startscale = 0.0001;  

            } 
            else 
                startscale = ((ScaleTransform)((TransformGroup)currentframe.RenderTransform).Children[0]).ScaleY;  

            Point startpoint = new Point((double)currentframe.GetValue(Canvas.LeftProperty), (double)currentframe.GetValue(Canvas.TopProperty)); 
            double endscale = (_sizes[index].Width / (Width / 2)); 
            AnimateFrame(_currentview[index], startpoint, _points[index], startscale, endscale).Begin();  

        }  

        private Storyboard AnimateFrame(int framenumber, Point startpoint, Point endpoint, double startscale, double endscale) 
        { 
            DoubleAnimation topanimation = (DoubleAnimation)_rootCanvas.FindName("topanimation" + framenumber.ToString()); 
            DoubleAnimation leftanimation = (DoubleAnimation)_rootCanvas.FindName("leftanimation" + framenumber.ToString()); 
            DoubleAnimation scalexanimation = (DoubleAnimation)_rootCanvas.FindName("scalexanimation" + framenumber.ToString()); 
            DoubleAnimation scaleyanimation = (DoubleAnimation)_rootCanvas.FindName("scaleyanimation" + framenumber.ToString());  

            scaleyanimation.From = startscale; 
            scalexanimation.From = startscale; 
            scalexanimation.To = endscale; 
            scaleyanimation.To = endscale;  

            topanimation.From = startpoint.Y; 
            leftanimation.From = startpoint.X; 
            topanimation.To = endpoint.Y; 
            leftanimation.To = endpoint.X;  

            return (Storyboard)_rootCanvas.FindName("storyboard" + framenumber.ToString());  

        }  

        private void HideFrame() 
        { 
            int framenumber = _currentview[_currentview.Count - 1]; 
            _hideframe = _frames[framenumber];  

            Point startpoint = new Point((double)_hideframe.GetValue(Canvas.LeftProperty), (double)_hideframe.GetValue(Canvas.TopProperty)); 
            Point endpoint = new Point(Width, Height); 
            Storyboard sb = AnimateFrame(framenumber, startpoint, endpoint, 1, 2); 
            sb.Completed += new EventHandler(sb_Completed); 
            sb.Begin(); 
        }  

        void sb_Completed(object sender, EventArgs e) 
        { 
            _rootCanvas.Children.Remove(_hideframe); 
            _enablemovenext = true; 
        }  

        private void AddFrame() 
        { 
            int framenumber = _currentview[0]; 
            FrameworkElement pf = _frames[framenumber]; 
            _rootCanvas.Children.Add(pf); 
            if (pf.Name == string.Empty) 
            { 
                pf.SetValue(DependencyObject.NameProperty, framenumber.ToString()); 
                string _transformstring; 
                Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.FrameTransform.xaml"); 
                _transformstring = new StreamReader(s).ReadToEnd(); 
                TransformGroup tg = (TransformGroup)XamlReader.Load(_transformstring); 
                pf.RenderTransform = tg; 
                AttachAnimation(pf); 
                pf.Width = Width / 2; 
                pf.Height = Height / 2; 
            }  

            pf.SetValue(UIElement.ZIndexProperty, -1);  

        }  

        private void AttachAnimation(FrameworkElement pf) 
        { 
            string _frameanimation; 
            Stream s = this.GetType().Assembly.GetManifestResourceStream("Visualization.FrameAnimation.xaml"); 
            _frameanimation = new StreamReader(s).ReadToEnd(); 
            _frameanimation = _frameanimation.Replace("frame", pf.Name); 
            _frameanimation = _frameanimation.Replace("scalexanimation", "scalexanimation" + pf.Name); 
            _frameanimation = _frameanimation.Replace("scaleyanimation", "scaleyanimation" + pf.Name); 
            _frameanimation = _frameanimation.Replace("topanimation", "topanimation" + pf.Name); 
            _frameanimation = _frameanimation.Replace("leftanimation", "leftanimation" + pf.Name); 
            Storyboard sb = (Storyboard)XamlReader.Load(_frameanimation); 
            sb.SetValue(DependencyObject.NameProperty, "storyboard" + pf.Name); 
            _rootCanvas.Resources.Add(sb); 
        }  

        private void UpdateLayout() 
        {  

            if (Height > 0 && Width > 0) 
            { 
                _rootCanvas.Height = Height; 
                _rootCanvas.Width = Width; 
                //update clipping area 
                RectangleGeometry clip = new RectangleGeometry(); 
                clip.Rect = new Rect(0, 0, Width, Height); 
                _rootCanvas.Clip = clip; 
                CalculateSteps(); 
                if (_currentview.Count > 0) 
                { 
                    foreach (int i in _currentview) 
                    { 
                        FrameworkElement pf = _frames[i]; 
                        ((PictureFrame)pf).Height = Height / 2; 
                        ((PictureFrame)pf).Width = Width / 2; 
                        ((ScaleTransform)((TransformGroup)pf.RenderTransform).Children[0]).ScaleX = (_sizes[i].Width / (Width / 2)); 
                        ((ScaleTransform)((TransformGroup)pf.RenderTransform).Children[0]).ScaleY = (_sizes[i].Height / (Height / 2)); 
                        pf.SetValue(Canvas.TopProperty, _points[i].Y); 
                        pf.SetValue(Canvas.LeftProperty, _points[i].X); 
                    } 
                }  

            }  

        }  

        private void CalculateSteps() 
        { 
            _points = new Collection<Point>(); 
            _sizes = new Collection<Size>();  

            double xstep = Width / (2 * _numberOfFrames); 
            double ystep = Height / (2 * _numberOfFrames);  

            Point startpoint = new Point(xstep, ystep); 
            Size startsize = new Size(2 * xstep, 2 * ystep);  

            Point endpoint = new Point(Width / 2, Height / 2); 
            Size endsize = new Size(Width / 2, Height / 2);  

            _points.Add(startpoint); 
            _sizes.Add(startsize);  

            double widthstep = (Width / 2) / (_numberOfFrames + 1); 
            double heightstep = (Height / 2) / (_numberOfFrames + 1);  

            for (int i = 1; i < (_numberOfFrames - 1); i++) 
            { 
                Point p = new Point(startpoint.X + (i * xstep), startpoint.Y + (i * ystep)); 
                Size s = new Size(startsize.Width + (i * widthstep), startsize.Height + (i * heightstep)); 
                _points.Add(p); 
                _sizes.Add(s); 
            }  

            _points.Add(endpoint); 
            _sizes.Add(endsize); 
        }  

        private void _rootCanvas_Loaded(object sender, EventArgs e) 
        { 
            UpdateLayout(); 
        }  

        public new double Height 
        { 
            get { return ((FrameworkElement)this).Height; } 
            set 
            { 
                ((FrameworkElement)this).Height = value; 
                UpdateLayout(); 
            } 
        }  

        public new double Width 
        { 
            get { return ((FrameworkElement)this).Width; } 
            set 
            { 
                ((FrameworkElement)this).Width = value; 
                UpdateLayout(); 
            } 
        } 
    } 
} 

I think control behaves much better now and I was able to integrate with my search application. I will add the full source to search application with this control integrated.  Here is the source code for updated control.

Standard