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…
- I added error handling code on PictureFrame so that when image fails to load it shows the text message
- 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.