Professional, Software

Stack3D Reincarnated

I had written a 3DSTack Control after last MIX. In Silverlight Beta2 there is lot better way to do it. One of the problem I was having with my control that too much stuff was hardwired to show image control.

The whole point of that control was to lay different pictures in 2.5 D. Since in Silverlight 2 Beta1 we now have Layout system as well as extensibility for building custom layout, I thought it should rather be a Panel than a control. Being a panel also gives me advantage of…

  1. I am not limited to just image but Since Panel has children collection that can take any UIElement by building it as a panel, I could put anything in that view
  2. I could also use it as Panel for the items control and then visualize a collection.

There was also a thing that became hard to do…

  1. To support move next functionality, when it was a control animating thing inside it was pretty straight forward but animating things inside layout pass. So I basically do multiple layout passes (which basically arranges things on the screen) on a timer.
  2. There is still a bug in the code that I need to track down, my suspicion is due to asynch nature of timer, I need to figure out when I move to next functionality needs to be done. If any of you figure out let me know 🙂

Before we get into the code aspect of here are few links that might explain. I could not actually find Layout extensibility information about Silverlight but this link is about layout extensibility in WPF and those should work to understand what I am doing.

Here are the basic steps in creating custom layout…

  1. Create a class inheriting from Panel (root class for all layout containers)
  2. you need to override MeasureOverride method. The input for this method is the element knows the available size and then it asks all of its children to size themselves for that size and then tell it to parent
  3. You also need to override ArrangeOverride method. The input for this method is the final size that element is going to get. Element then can arrange all its children according to the size that it has available.
  4. I also exposed a method called MoveNext where Panel rotates the items that are in the panel.
  5. I also have functionality where Panel can have N number of children but can visualize only n number of them.

Given these Basics here is how it looks like while running in the picture below you see Images, CheckBox, TextBox, Button. I had media element there but then I realized it will be snapshot anyway. I am using this in search app so that should better use of this anyway.

Stack3D

My goal was to write some XAML like this to make above view work…

<UserControl x:Class="ControlHost.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:control="clr-namespace:MyControls;assembly=MyControls" 
    >
    <control:Stack3DPanel FrameCount="5" 
                          MouseLeftButtonDown="Stack3DPanel_MouseLeftButtonDown" 
                          Background="White">
        <Button Name="button1" Content="Click Me"/>
        <CheckBox Content="Check Me"/>
        <TextBox Name="textbox1" Text="Hi"/>
        <Image Source="Resources/AlkiBeach.jpg"  Stretch="Fill"/>
        <Image Source="Resources/Lion.jpg" Stretch="Fill"/>
        <Image Source="Resources/Oregon.jpg" Stretch="Fill"/>
        <Image Source="Resources/Picture1.jpg"  Stretch="Fill"/>
        <Image Source="Resources/Picture2.jpg"  Stretch="Fill"/>
        <Image Source="Resources/Picture3.jpg"  Stretch="Fill"/>
        <Image Source="Resources/Picture4.jpg"  Stretch="Fill"/>
        <Image Source="Resources/Picture5.jpg"  Stretch="Fill"/>
        <Image Source="Resources/Picture7.jpg" Stretch="Fill"/>
    </control:Stack3DPanel>
</UserControl>

On the MouseLeftButtonDown Event my code looks like this…

   1: private void Stack3DPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
   2: {
   3:     (sender as MyControls.Stack3DPanel).MoveNext();
   4: }

So with that goal in mind here is the code for

   1: using System;
   2: using System.Windows;
   3: using System.Windows.Controls;
   4: using System.Windows.Media.Animation;
   5: using System.Collections.ObjectModel;
   6: using System.IO;
   7: using System.Windows.Threading;
   8:  
   9: namespace MyControls
  10: {
  11:     public class Stack3DPanel : Panel
  12:     {
  13:         //Collection of rectangles representing size for different frames
  14:         Collection<Rect> _arrangerects;
  15:         //first item in the stack (frontmost)
  16:         int _currentfirstindex = 0;
  17:         //last item in the stack (backmost)
  18:         int _currentlastindex = 0;
  19:         //length of track over while elements are moving
  20:         double _tracklength;
  21:         //distance between frames
  22:         double _framegap;
  23:         //collection of elements are visible
  24:         Collection<UIElement> _visibleelements = null;
  25:         //Timer used for animation
  26:         DispatcherTimer _timer;
  27:         //number of ticks
  28:         int _timercount = 0;
  29:         //interval between ticks
  30:         int _tickinterval = 10;
  31:         //Duration overwhich ticks should happen
  32:         int _duration = 100;
  33:         Size _size;
  34:  
  35:         /// <summary>
  36:         /// Constructors for Panel, It initializes few things and creates Timers used for 
  37:         /// Pseudo-Animation. 
  38:         /// </summary>
  39:         public Stack3DPanel()
  40:         {
  41:             //hook up size changed event handler for the panel
  42:             SizeChanged += new SizeChangedEventHandler(Stack3DPanel_SizeChanged);
  43:             //collection of elements that represent the visible elements in the panel
  44:             _visibleelements = new Collection<UIElement>();
  45:             //setup timer
  46:             _timer = new DispatcherTimer();
  47:             _timer.Interval = TimeSpan.FromMilliseconds(_tickinterval);
  48:             _timer.Tick += new EventHandler(_timer_Tick);
  49:         }
  50:  
  51:         /// <summary>
  52:         /// Size Changed event handler for the tpanel. When the size of Panel changes 
  53:         /// Panel calculates new set rectangles that represent sizes of different 
  54:         /// rectangle at different stages.
  55:         /// </summary>
  56:         /// <param name="sender">Panel itself</param>
  57:         /// <param name="e">Size changed event args with new size</param>
  58:         void Stack3DPanel_SizeChanged(object sender, SizeChangedEventArgs e)
  59:         {
  60:             //stop the timer
  61:             _timer.Stop();
  62:             //cache the new size
  63:             _size = e.NewSize;
  64:             //calculate new set of rectangles representing sizes of different frames
  65:             CalculateArrangeRects();
  66:             //invalidate messure for another layout pass so that elements get new sizes
  67:             InvalidateMeasure();
  68:         }
  69:  
  70:         /// <summary>
  71:         /// Measure Override for the panel. 
  72:         /// It initializes the visible elments collection
  73:         /// Also calls Measure on visible elements with rectangles representing sizes of different frames
  74:         /// </summary>
  75:         /// <param name="availableSize">Available size for the panel</param>
  76:         /// <returns>Desired size for the panel</returns>
  77:         protected override Size MeasureOverride(Size availableSize)
  78:         {
  79:             if (Children.Count == 0)
  80:             {
  81:                 return availableSize;
  82:             }
  83:             //initializes the visible element collection
  84:             if (_visibleelements.Count == 0)
  85:             {
  86:                 for (int count = 0; count < FrameCount; count++)
  87:                     _visibleelements.Add(Children[count]);
  88:             }
  89:  
  90:             //Measure for each visible element
  91:             foreach (UIElement visibleelement in _visibleelements)
  92:             {
  93:                 visibleelement.Measure(new Size(_arrangerects[_visibleelements.IndexOf(visibleelement)].Width, _arrangerects[_visibleelements.IndexOf(visibleelement)].Height));
  94:             }
  95:             //size returned is same as available since elements will be resized
  96:             return availableSize;
  97:         }
  98:  
  99:  
 100:         /// <summary>
 101:         /// Arrange over ride for the panel. Arranges visible elements according to sizes of rectangles
 102:         /// Also sets the size of all other elements to 0, 0. Also sets the Z index for all the visible elements so that 
 103:         /// element stack behind other to give 2.5D
 104:         /// </summary>
 105:         /// <param name="finalSize">Final size available to panel</param>
 106:         /// <returns>returns the final size of the panel after all the arrange have been called</returns>
 107:         protected override Size ArrangeOverride(Size finalSize)
 108:         {
 109:  
 110:             bool isvisible = false;
 111:  
 112:             //Sets the ZIndex and arranges visible elements to their final size. 
 113:             foreach (UIElement visibleelement in _visibleelements)
 114:             {
 115:                 visibleelement.SetValue(Canvas.ZIndexProperty, (FrameCount - _visibleelements.IndexOf(visibleelement)));
 116:                 visibleelement.Arrange(_arrangerects[_visibleelements.IndexOf(visibleelement)]);
 117:             }
 118:  
 119:             //sets the size for the elements that are not visible to 0, 0
 120:             foreach (UIElement child in Children)
 121:             {
 122:                 foreach (UIElement visibleelement in _visibleelements)
 123:                 {
 124:                     if (child.Equals(visibleelement))
 125:                     {
 126:                         isvisible = true;
 127:                         break;
 128:                     }
 129:                 }
 130:  
 131:                 if (isvisible != true)
 132:                 {
 133:                     child.Arrange(new Rect(0, 0, 0, 0));
 134:                 }
 135:                 isvisible = false;
 136:             }
 137:             return finalSize;
 138:         }
 139:  
 140:  
 141:        /// <summary>
 142:        /// Consumers of the panel calls this method to move items forward.
 143:        /// </summary>
 144:         public void MoveNext()
 145:         {
 146:             //start the timer to do the animation
 147:             _timer.Start();
 148:         }
 149:  
 150:         /// <summary>
 151:         /// This code gets executed on each tick of the timer to create Animation
 152:         /// </summary>
 153:         /// <param name="sender">Timer</param>
 154:         /// <param name="e"></param>
 155:         void _timer_Tick(object sender, EventArgs e)
 156:         {
 157:             //intrapolates the intermediate sizes based on the tick
 158:             //calling arrange gives that size to the elements and hence animation
 159:             if (_timercount < (_duration/_tickinterval))
 160:             {
 161:                 foreach (UIElement visibleelement in _visibleelements)
 162:                 {
 163:                     Rect newarrangerect = CalculateNewPosition(visibleelement, _visibleelements.IndexOf(visibleelement));
 164:                     visibleelement.Arrange(newarrangerect);
 165:                 }
 166:                 _timercount++;
 167:             }
 168:             else
 169:             {
 170:                 //if the animation is finished, need to remove the front most element (set the size to 0, 0)
 171:                 //Also add the element in the back. so visible element collection is updated
 172:                 _timer.Stop();
 173:                 _visibleelements.Remove(Children[_currentfirstindex]);
 174:                 Children[_currentfirstindex].Arrange(new Rect(0,0,0,0));
 175:  
 176:                 if (_currentfirstindex < (Children.Count - 1) && _currentlastindex < (Children.Count - 1))
 177:                 {
 178:                     _currentfirstindex++;
 179:                     _currentlastindex++;
 180:                 }
 181:                 else if (_currentfirstindex < (Children.Count - 1) && _currentlastindex == (Children.Count - 1))
 182:                 {
 183:                     _currentfirstindex++;
 184:                     _currentlastindex = 0;
 185:                 }
 186:                 else if (_currentfirstindex == (Children.Count - 1) && _currentlastindex < (Children.Count - 1))
 187:                 {
 188:                     _currentfirstindex = 0;
 189:                     _currentlastindex++;
 190:                 }
 191:                 else if (_currentfirstindex > (Children.Count - 1) || _currentlastindex > (Children.Count - 1))
 192:                 {
 193:                     _currentfirstindex = 0;
 194:                     _currentlastindex = FrameCount - 1;
 195:                 }
 196:  
 197:                 _visibleelements.Add(Children[_currentlastindex]);
 198:  
 199:                 foreach (UIElement visibleelement in _visibleelements)
 200:                 {
 201:                     visibleelement.SetValue(Canvas.ZIndexProperty, (FrameCount - _visibleelements.IndexOf(visibleelement)));
 202:                     visibleelement.Arrange(_arrangerects[_visibleelements.IndexOf(visibleelement)]);
 203:                 }
 204:                 _timercount = 0;
 205:                 InvalidateMeasure();
 206:             }
 207:         }
 208:  
 209:  
 210:         /// <summary>
 211:         /// Intrapolates intermediate sizes during ticks
 212:         /// </summary>
 213:         /// <param name="element">element that is being animated</param>
 214:         /// <param name="index">position of the elements in the stack</param>
 215:         /// <returns>intermediage size at given time</returns>
 216:         Rect CalculateNewPosition(UIElement element, int index)
 217:         {
 218:             double top = _arrangerects[index].Height + (_size.Height * _timercount) * _tickinterval / (2 * FrameCount * _duration);
 219:             double left = _arrangerects[index] .Width+ (_size.Width * _timercount * _tickinterval) / (2 * FrameCount * _duration);
 220:             return (new Rect (left, top, left, top));
 221:         }
 222:  
 223:         /// <summary>
 224:         /// Calculates sets of rectangles  representing sizes of different frames in the stack
 225:         /// </summary>
 226:         private void CalculateArrangeRects()
 227:         {
 228:             _tracklength = Math.Sqrt((_size.Height * _size.Height) + (_size.Width * _size.Width)) / 2;
 229:             _framegap = _tracklength / FrameCount;
 230:             _arrangerects = new Collection<Rect>();
 231:             _currentlastindex = _currentfirstindex + FrameCount - 1;
 232:  
 233:             for (int count = 0; count < FrameCount; count++)
 234:             {
 235:                 double trackposition = _tracklength - (count * _framegap);
 236:                 double trackx = (trackposition * _size.Width) / (2 * _tracklength);
 237:                 double tracky = (trackposition * _size.Height) / (2 * _tracklength);
 238:                 Rect arrangerect = new Rect(trackx, tracky, trackx, tracky);
 239:                 _arrangerects.Add(arrangerect);
 240:             }
 241:         }
 242:  
 243:         //Dependency Property declaration for the FrameCount property
 244:         public static readonly DependencyProperty FrameCountProperty = DependencyProperty.Register("FrameCount", typeof(int), typeof(MyControls.Stack3DPanel), new PropertyChangedCallback(OnFrameCountPropertyChanged));
 245:  
 246:         /// <summary>
 247:         /// Property Changed call back. If the Frame count is changed then all rect for visible element
 248:         /// need to be recalculated
 249:         /// </summary>
 250:         /// <param name="obj"></param>
 251:         /// <param name="args"></param>
 252:         private static void OnFrameCountPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
 253:         {
 254:             Stack3DPanel stack = obj as Stack3DPanel;
 255:             if (args.OldValue != args.NewValue)
 256:                 stack.CalculateArrangeRects();
 257:         }
 258:  
 259:         /// <summary>
 260:         /// Public setter/getter for FrameCount property
 261:         /// </summary>
 262:         public int FrameCount
 263:         {
 264:             get { return (int)GetValue(Stack3DPanel.FrameCountProperty); }
 265:             set { SetValue(Stack3DPanel.FrameCountProperty, (int)value); }
 266:         }
 267:     }
 268: }

here is the link to the project file

Advertisements
Standard

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s