Click here to Skip to main content
Click here to Skip to main content

WPF 3D Tab Carousel

By , 16 Jun 2012
 
Prize winner in Competition "Best overall article of January 2010"
Prize winner in Competition "Best C# article of January 2010"

Introduction

This article discusses how to create a 3D tab control for WPF. It will go through 3D rotation and camera calculations as well as show how to maintain a large set of controls in a smaller set of UI visualisers. Although a fair portion of the code for this article relates to the creation of 3D meshes, I'll not go into details around that, but rather try to focus the article around other areas more specific to this article.

There's a YouTube video displaying some of the implemented features available here;
http://www.youtube.com/watch?v=zmDHfsRENug&feature=plcp 

Background

This article has grown from a made-to-order solution I've implemented for Sacha. I initially knocked up a simple prototype, and Sacha wanted it to do some other things (like sliding window for example, discussed below) while I thought other things would be cool. In the end, it turned out to be a pretty neat control.

Using the code

Dead simple, download the source project and build it, the Bornander.UI.TabCarousel project contains a user control called Carousel that takes care of just about everything.

For example purposes, the file MainWindow.xaml.cs has two sections in its constructor that are to be used one at a time to test different aspects of the control.

Requirements

When implementing this control, I started out with a set of requirements:

  • The control must work on a set of FrameworkElements so that pretty much any UI element can be used as a tab page.
  • The different FrameworkElements should be displayed on 3D panels placed in a virtual carousel.
  • Navigation from one panel to another must be available through Next, Previous, and Go to specific index.
  • The number of 3D panels must be allowed to be less than the actual FrameworkElements (this was one of Sacha's requirements and it turned out to be a ball ache).
  • Transition from one tab page to another must be via animation.
  • The camera must attempt to position itself in 3D space in a such way as a best effort of maintaining the desired size of the FrameworkElement.

Implementation

Overview

The solution is split into three projects:

  • Bornander.UI.TabCarousel, this is the project containing the actual user control.
  • Bornander.UI.TabCarousel.Test, this is just a test project showcasing the user control.
  • Bornander.Wpf.Meshes, this is an extract of a larger project I'm working on, designed to simplify 3D using WPF.

Tackling the requirements

Use FrameworkElements

This is as simple as it sounds. I implemented a class called Tab to encapsulate the tab page, and this has a property called Element which can be used to set any FrameworkElement as the visual for that Tab.

public FrameworkElement Element
{
    get { return element; }
    set
    {
      element = value;
      front.Visual = element;
    }
}

The front private member is a Viewport2DVisual3D.

Creating 3D panels

I wanted the tab pages to be blocks where the front side holds the FrameworkElement; this is easy to achieve using the Viewport2DVisual3D class, but as I also wanted the block to have depth, I had to create two meshes, each mesh with its own material.

First, I created a lid-less box, all in one mesh and using a simple DiffuseMaterial.

This is done by creating a Box from Bornander.Wpf.Meshes, specifying that all sides except the front should be included:

boxMesh = Box.CreateBoxMesh(1, 1, depth, 
      Box.Side.Right | 
      Box.Side.Left | 
      Box.Side.Top | 
      Box.Side.Bottom | 
      Box.Side.Back);

Note that the width and height of the box is set to 1.0, that's because the correct aspect ratio (that is, the ratio that the UI element was designed with) is not actually calculated until the assignment of a FrameworkElement, and then a scale transform is calculated to achieve this.

The "lid" of the box is then created the same way, but this time, only the front is included:

visualHostMaterial = new DiffuseMaterial(Brushes.White);
visualHostMaterial.SetValue(
Viewport2DVisual3D.IsVisualHostMaterialProperty, true);

visualMesh = Box.CreateBoxMesh(1, 1, depth, Box.Side.Front);

front = new Viewport2DVisual3D
{
    Geometry = visualMesh,
    Visual = element,
    Material = visualHostMaterial
};

The visual host material is required to display a UIElement as an interactive material on a 3D surface. These two meshes are then added into a model of type ModelVisual3D; that way, whenever I need to move, rotate, or scale the meshes, I can simply apply the transformations to that group of meshes and not have to do it separately for each mesh.

The whole Tab class looks like this:

class Tab
{
    private readonly Material visualHostMaterial;
    private readonly MeshGeometry3D boxMesh;
    private readonly MeshGeometry3D visualMesh;

    private Viewport2DVisual3D front;
    private ModelVisual3D back;

    private FrameworkElement element;

    private double depth;

    public ModelVisual3D Model { get; private set; }

    public Tab(FrameworkElement element, Color color, double depth)
    {
      this.element = element;
      this.depth = depth;

      visualHostMaterial = new DiffuseMaterial(Brushes.White);
      visualHostMaterial.SetValue(
        Viewport2DVisual3D.IsVisualHostMaterialProperty, true);

      boxMesh = Box.CreateBoxMesh(1, 1, depth, 
        Box.Side.Right | 
        Box.Side.Left | 
        Box.Side.Top | 
        Box.Side.Bottom | 
        Box.Side.Back);
      visualMesh = Box.CreateBoxMesh(1, 1, depth, Box.Side.Front);


      front = new Viewport2DVisual3D
      {
        Geometry = visualMesh,
        Visual = element,
        Material = visualHostMaterial
      };


      back = new ModelVisual3D
      {
        Content = new GeometryModel3D
        {
          Geometry = boxMesh,
          Material = new DiffuseMaterial(Brushes.CadetBlue),
        }
      };

      Model = new ModelVisual3D();

      Model.Children.Add(back);
      Model.Children.Add(front);
    }

    public void UpdateTransform(int index, double angle, double radius)
    {
      TranslateTransform3D translaslation = new TranslateTransform3D(
        0, 0, radius - depth / 2.0);
      
      RotateTransform3D rotation = new RotateTransform3D(
        new AxisAngleRotation3D(new Vector3D(0, 1, 0), -index * angle));

      ScaleTransform3D scale = element != null ? 
        new ScaleTransform3D(1.0, double.IsNaN(element.Height) 
          ? 1.0 : 
            element.Height / element.Width, 1.0) 
          : new ScaleTransform3D(1, 1, 1);

      Transform3DGroup transform = new Transform3DGroup();

      transform.Children.Add(scale);
      transform.Children.Add(translaslation);
      transform.Children.Add(rotation);

      Model.Transform = transform;
    }

    public FrameworkElement Element
    {
      get { return element; }
      set
      {
        element = value;
        front.Visual = element;
      }
    }
}

Allowing animated navigation

In order to place the Tabs in a "carousel", several things have to be calculated: the angle between the different 3D panels, the specific location for a panel and the radius, and the distance from an imaginary center to the center of the panel. All these things are dynamic, and change as the number of panels change.

The first thing, the angle is easy; simply divide 360 degrees by the number of tab panels; that means that if there are three panels, they should be separated by 120 degrees each. The second thing, the specific angle for one tab is calculated using an index; the Carousel user control keeps a IList<Tab> and the angle is calculated using the index in this list. The Tab classes can calculate this themselves, and that's what the UpdateTransform method above does. It creates a rotation transform based on the angle and the index (simply multiply the angle by the index), and that transform rotates the panel to the correct slot on the carousel. The last bit is the radius; this needs to get larger and larger as the number of panels increase so that they won't overlap. As one needs to know the number of panels, this has to be calculated by the Carousel:

private static double DegreesToRadians(double degrees)
{
    return (degrees / 180.0) * Math.PI;
}

private double CalculateRadius()
{
    double splitAngle = 360.0 / tabs.Count;
    switch (tabs.Count)
    {
      case 1: return 0.0;
      case 2: return 0.25;
      default:
        return 1.0 / Math.Abs(Math.Sin(DegreesToRadians(splitAngle)));
    }
}

Since all panels are 1.0 wide (this never changes; regardless of the aspect ratio, I only modify the height), I calculate the radius as 1.0 / sin(angle between panels). This isn't the optimal distance (i.e., not the smallest distance possible without overlapping), but it's guaranteed to be larger than that, plus, I think it generates a suitable distance.

In order to actually rotate from one panel to the other, I had to come up with a lot of weird calculations (mostly due to Sacha's unreasonable requirements of sliding windows and wrapping collections); it's not that much code, but it's still fairly confusing. Sacha wanted a go-to function, allowing the user to directly jump from one tab page to another, something which is easy enough to implement, but he wanted it so that it never had to rotate more than one step. That is, in the standard setting, jumping from tab 1 to 4 will rotate past 2 and 3 before getting to 4, but Sacha wanted this to directly find 4. Completely unreasonable, if you ask me.

Below is the code that handles this, but first, it's worth noting that I request rotations by queuing up SpinInstructions that tell the Animate method from where and where to go.

private class SpinInstruction
{
    public int From { get; private set; }
    public int To { get; private set; }

    public SpinInstruction(int from, int to)
    {
      From = from;
      To = to;
    }
}

In the standard setting, whenever a multi-step rotation is requested by the user, it's queued up as all the steps making up that rotation.

private void Animate()
{
    // If no instructions are queue up
    // or if we're already animating, ignore request
    if (instructions.Count == 0 || isAnimating)
      return;

    // Grab the next spin instruction
    SpinInstruction instruction = instructions.Peek();
    bool wrapIt = false;
    
    // If the spin To target is outside the elements list, 
    // this is going to be a wrapping sping
    if (instruction.To < 0 || instruction.To >= elements.Count)
    {
      // If WrapAtEnd is enabled and if the instruction 
      // target is a valid one accept it
      if (WrapAtEnd && (instruction.To == -1 || 
                    instruction.To == elements.Count))
      {
        // Set wrapIt to true to indicate that this 
        // is a wrapping spin and then adjust the instruction to
        // fit the standard logic
        wrapIt = true;
        instruction = new SpinInstruction(
          instruction.From, 
          instruction.To < 0 ? elements.Count - 1 : 0);
      }
      else // Done animating for now, remove instruction and return
      {
        instructions.Dequeue();
        isAnimating = false;
        return;
      }
    }
    
    // Angle between panels
    double angle = 360.0 / tabs.Count;
    
    // Figure out the target index in the tabs list
    int tabToIndex = AlwaysOnlyOneStep ? 
      GetSafeIndex(currentTabIndex + 
          Math.Sign(instruction.To - instruction.From)) 
          : GetSafeIndex(instruction.To);

    // If this is a wrapping spin, the tabToIndex can 
    // be set to either the first or last index
    if (wrapIt)
    {
      if (instruction.To == 0)
        tabToIndex = 0;
      if (instruction.To == elements.Count - 1)
        tabToIndex = tabs.Count - 1;
    }

    // Unhook from visual tree if required because 
    // a Visual cannot have to parents
    foreach (Tab owner in (from tab in tabs 
      where tab.Element == elements[instruction.To] 
        || tab.Element == elements[instruction.From] select tab))
      owner.Element = null;

    // Make sure the current tab contains the From element
    tabs[currentTabIndex].Element = elements[instruction.From];
    tabs[currentTabIndex].UpdateTransform(currentTabIndex, 
                          angle, CalculateRadius());

    // Make sure the target tab contains the To element, 
    // this is what allows less tab panels than elements
    tabs[tabToIndex].Element = elements[instruction.To];
    tabs[tabToIndex].UpdateTransform(tabToIndex, angle, CalculateRadius());
    isAnimating = true;

    // The angles of the carousel for the from and to tabs
    double fromAngle = currentTabIndex * angle;
    double toAngle = tabToIndex * angle;

    // If this is a wrapping spin add/remove
    // a full lap otherwise the animation 
    // would run backwards for these cases
    if (wrapIt)
    {
      if (instruction.To == 0)
        toAngle += 360;
      if (instruction.To == elements.Count - 1)
        toAngle -= 360;
    }

    // If this is spinning to a later element, 
    // but the tab index is less than the current tab index, add a lap
    if (instruction.To - instruction.From > 0 && 
        tabToIndex < currentTabIndex)
      toAngle += 360;

    // If this is spinning to a earlier element,
    // but the tab index is greater than the 
    // current tab index, subtract a lap
    if (instruction.To - instruction.From < 0 && 
      tabToIndex > currentTabIndex)
      toAngle -= 360;

    CreateSpinAnimation(instruction, tabToIndex, fromAngle, toAngle);
}

The CreateSpinAnimation is responsible for creating the actual animations and calling Animate again when the spin animation has completed.

Mid-rotation with the FlipIt flag set to true.

Calculating camera distance

In the code above, the camera distance is calculated on a tab-per-tab basis. This is because although the tabs themselves will scale to the correct aspect ratio, there's also the issue with size on screen. If, for example, a user control was designed to be displayed in 300x400, it's not enough to create a 3D box 300 wide and 400 tall, because one set of units (the first) are in pixels and the second is unit less. It's just distance in 3D, not pixels. Therefore, the Carousel has to calculate the distance from the panel that the camera has to be at in order for the UI element to be rendered correctly. This also depends on the size of the Viewport3D containing all the elements.

Basically, it looks something like this:

And, in math terms: solve the distance y, where y is one leg of a square triangle made up of y itself, 0.5 (half the 3D panel width), and the hypotenuse is formed by extending the camera's field of view (or half field of view). Since we don't know the length of the hypotenuse but can figure out the angle (as it's half the field of view), we can use tan(field of view / 2.0), or in code terms:

private double CalculateCameraDistance(int index, int tabIndex)
{
    Tab tab = tabs[tabIndex];

    double y = 0.5 / Math.Tan(DegreesToRadians(MainCamera.FieldOfView / 2.0));

    double panelWidth = tab.Element != null ? tab.Element.Width : 1.0;
    double ratio = Grid3D.ActualWidth / panelWidth;

    return CalculateRadius() + Math.Max(ratio, 1.0) * y;
}

When y is found, multiply it with the ratio between the designed UI element width and the Viewport3D current width to compensate for the size of the Viewport3D. And lastly, offset it by the distance of the radius of the carousel. By taking the max of 1.0 and the calculated ratio, Math.Max(ratio, 1.0), the distance will make sure the entire width of the panel is always visible, even if the Viewport3D is smaller than the designed size of the panel.

Since most WPF user controls are designed to be used inside a window or another control, their width and height cannot always be determined (hence the need for both Width and ActualWidth properties found on some WPF UI elements). In order for a user control to play nice with this tab control, it's therefore important to set the MinWidth, MaxWidth, and Width at design time.

The user control

The WPF user control that implements the carousel is called Carousel, intuitive, eh? And, as this control is mostly about rotation and camera position calculations, the XAML for it is quite simple:

<UserControl x:Class="Bornander.UI.TabCarousel.Carousel"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    SizeChanged="HandleSizeChanged">
    <Grid x:Name="Grid3D" Width="Auto" Height="Auto">
      <Viewport3D>
        <Viewport3D.Camera>
          <PerspectiveCamera x:Name="MainCamera" 
          FieldOfView="90" 
          Position="0,0,0" 
          LookDirection="0,0,-1"/>
        </Viewport3D.Camera>
        <ModelVisual3D>
          <ModelVisual3D.Content>
            <AmbientLight x:Name="Ambient" Color="#808080"/>
          </ModelVisual3D.Content>
        </ModelVisual3D>
        <ModelVisual3D>
          <ModelVisual3D.Content>
            <DirectionalLight x:Name="Directional" 
            Color="#FFFFFFFF" Direction="0,-1,-1"/>
          </ModelVisual3D.Content>
        </ModelVisual3D>
        <ModelVisual3D x:Name="CarouselContainer"/>
      </Viewport3D>
    </Grid>
</UserControl>

The user control sets up a few things:

  • The camera; it's important that the position for the camera is at (0, 0, 0) in order to get the distance calculations right; also, the look direction has to be along the Z-axis.
  • Ambient light, so that not only surfaces hit by the directional light are visible.
  • Directional light; this is important as the scene looks "flat" without it.
  • CarouselContainer: this is just the ModelVisual3D used to hold all items in the carousel; this is what is actually being rotated when the carousel spins.

Points of interest

I could have had the definitions for the meshes in XAML as well, but I find it easier and more flexible to use code for this. The most complicated part was getting the wrapping rotation right, especially when there's less tab than there are elements in the carousel. This is because the way the rotation animation works, animating from 270 degrees to 360 is different than going from 270 to 0, which kind of makes sense, but still caused me some head aches as 360 and 0 are really the same.

As always, any comments on the code or the article are most welcome.

History

  • 2010-01-01: First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Fredrik Bornander
Software Developer (Senior)
Sweden Sweden
Member
Oakmead Apps Android Games
 
23 Jun 2012: Best C++ article of May 2012
20 Apr 2012: Best VB.NET article of March 2012
22 Feb 2010: Best overall article of January 2010
22 Feb 2010: Best C# article of January 2010

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberAnurag Gandhi12 Mar '13 - 17:34 
Really very nice!!
GeneralRe: My vote of 5memberFredrik Bornander12 Mar '13 - 19:57 
Thanks for commenting, I appreciate the feedback.
 
/Fredrik
My Android apps in Google Play; Oakmead Apps

GeneralMy vote of 5memberAhmed Ibrahim Assaf5 Jan '13 - 22:11 
Well Done Smile | :)
GeneralRe: My vote of 5memberFredrik Bornander12 Mar '13 - 19:56 
Thank you, I'm glad you liked it.
 
/Fredrik
My Android apps in Google Play; Oakmead Apps

GeneralMy vote of 5memberJohn Adams6 Apr '11 - 4:34 
good one
GeneralRe: My vote of 5memberFredrik Bornander25 Apr '11 - 21:29 
Thanks for the vote and the comment.
 
/Fredrik
GeneralCarousel with MVVMmemberFatih Ender13 Mar '11 - 21:13 
Hi Fredrik,
How can we implement the tabs as "view" (views are UserControls actually) using MVVM pattern?
thanks.
NewsRe: Carousel with MVVM [modified]memberFredrik Bornander13 Mar '11 - 21:20 
With great difficulty.
The way this tab carousel is implemented isn't very clever, I've realized, and to change it to allow actual UIElements as tabs will require some rewrite.
 
It's something I'm working on but it's progressing slowly.
 
/Fredrik
modified on Monday, March 14, 2011 3:28 AM

QuestionMultiple instances of carousel?memberstuartmclark28 Feb '11 - 0:32 
I was wondering if you thought it would be possible to create multiple instances of your carousel that the user could browse through? The carousels would be layered on top of each other and you could navigate up and down from one to the next.
 
Thanks, Stuart.
AnswerRe: Multiple instances of carousel?memberFredrik Bornander28 Feb '11 - 0:49 
I'm not sure I follow you. Do you want multiple carousels inside one 3D viewport or just stack multiple viewports ontop of each other?
QuestionRe: Multiple instances of carousel?memberstuartmclark28 Feb '11 - 1:04 
Hmm, I never thought of multiple view ports. Initially I was thinking multiple carousels in a single view port stacked one on top of the other, that way I could move the camera, or the objects around to view the next carousel. The multiple viewports would also help with controlling the rotation of the carousel.
 
What I have at the moment is when the user clicks on one of the panels it moves the camera down so that the carousel has "animated" on the Y axis and another carousel has been generated below the original. The camera would then show the new carousel in place of the original and the user could rotate that one. Generating the second carousel is complicated and is the main part I am stuck on.
 
If I clear the original carousel it shows the new data otherwise it just appends it to the original carousel. I have attached a link to an image to try and help describe it better. in the first section you can see the original carousel, when it is clicked it moves up and generates a new carousel which it then moves into view with the old carousel still visible at the top but you cannot control it.
 
Carousel[^]
 
Thanks for your input, really appreciate it and your work is great!
 
Stuart.
AnswerRe: Multiple instances of carousel?memberFredrik Bornander28 Feb '11 - 1:33 
Your comment has made me realize that this Carousel implementation is fundamentally flawed in it's design.
The Panel and the Carousel part should have been different entities all together. Camera movement and carousel spin should also be different.
 
This has given me stuff to think about, I might have to go back an redo the control to cater for your requirement as I'm interested what that will look like.
 
Thanks for pointing this out, I'll let you know if I manage to find the time to fix it.
You seem clever, if you have time, can you take a look at Adaptive WPF ICommand implementation[^] and tell me if the approach I've gone for makes sense?
 
Cheers,
Fredrik
GeneralRe: Multiple instances of carousel?memberstuartmclark28 Feb '11 - 4:37 
Hey, thanks for the quick reply.
 
I would love to help you out with that but I am only a beginner in WPF and this is my first venture into the world of WPF! I'm glad to hear that you will consider a redesign of the project, I didn't think it was flawed but, it being your project you would know best Big Grin | :-D
 
For now I am just going to try and stack the carousels on top of each other and move the camera down the stack to view each section. I will transfer the control by keeping a list of the stacks and the current position the camera is at.
 
Thanks for your help in this. It is much appreciated!
GeneralCoolgroupzhujinlong1984091313 Dec '10 - 20:28 
Cool
GeneralRe: CoolmemberFredrik Bornander14 Dec '10 - 2:33 
Thanks, I'm glad you liked it.
 
Thanks for commenting,
Fredrik
GeneralGreat job fredrik but...memberFatih Ender15 Jul '10 - 10:18 
hi ,
i am just learning WPF. While trying the code, when i press the spintonext button for a few times app crashes unexpectedly.
could it be associated with my onboard grafic card..
how can i catch the crash point.
 
greets..
GeneralRe: Great job fredrik but...memberFatih Ender15 Jul '10 - 11:17 
this is the error that i got..
 
Application Domain: MyTestApp.exe
Assembly Codebase: file:///C:/prj/Debug/MyTestApp.exe
Assembly Full Name: MyTestApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Assembly Version: 1.0.0.0
Assembly File Version: MyTestApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Assembly Build Date: 7/15/2010 11:47:27 PM
 
Exception Source: PresentationCore
Exception Type: System.InvalidOperationException
Exception Message: An unspecified error occurred on the render thread.
Exception Target Site: NotifyPartitionIsZombie
 
---- Stack Trace ----
System.Windows.Media.MediaContext.NotifyPartitionIsZombie(failureCode As Int32)
MyTestApp.exe: N 00160
System.Windows.Media.MediaContext.NotifyChannelMessage()
MyTestApp.exe: N 8437864
System.Windows.Interop.HwndTarget.HandleMessage(msg As Int32, wparam As IntPtr, lparam As IntPtr)
MyTestApp.exe: N 00291
System.Windows.Interop.HwndSource.HwndTargetFilterMessage(hwnd As IntPtr, msg As Int32, wParam As IntPtr, lParam As IntPtr, handled As Boolean&)
MyTestApp.exe: N 00057
MS.Win32.HwndWrapper.WndProc(hwnd As IntPtr, msg As Int32, wParam As IntPtr, lParam As IntPtr, handled As Boolean&)
MyTestApp.exe: N 00189
MS.Win32.HwndSubclass.DispatcherCallbackOperation(o As Object)
MyTestApp.exe: N 00121
System.Windows.Threading.ExceptionWrapper.InternalRealCall(callback As Delegate, args As Object, isSingleParameter As Boolean)
MyTestApp.exe: N 00137
System.Windows.Threading.ExceptionWrapper.TryCatchWhen(source As Object, callback As Delegate, args As Object, isSingleParameter As Boolean, catchHandler As Delegate)
MyTestApp.exe: N 00073
System.Windows.Threading.Dispatcher.WrappedInvoke(callback As Delegate, args As Object, isSingleParameter As Boolean, catchHandler As Delegate)
MyTestApp.exe: N 00067
System.Windows.Threading.Dispatcher.InvokeImpl(priority As DispatcherPriority, timeout As TimeSpan, method As Delegate, args As Object, isSingleParameter As Boolean)
MyTestApp.exe: N 00144
System.Windows.Threading.Dispatcher.Invoke(priority As DispatcherPriority, method As Delegate, arg As Object)
MyTestApp.exe: N 00063
MS.Win32.HwndSubclass.SubclassWndProc(hwnd As IntPtr, msg As Int32, wParam As IntPtr, lParam As IntPtr)
MyTestApp.exe: N 00219
MS.Win32.UnsafeNativeMethods.DispatchMessage(msg As MSG&)
MyTestApp.exe: N 00000
System.Windows.Threading.Dispatcher.PushFrameImpl(frame As DispatcherFrame)
MyTestApp.exe: N 00198
System.Windows.Threading.Dispatcher.PushFrame(frame As DispatcherFrame)
MyTestApp.exe: N 00072
System.Windows.Threading.Dispatcher.Run()
MyTestApp.exe: N 00075
System.Windows.Application.RunDispatcher(ignore As Object)
MyTestApp.exe: N 00029
System.Windows.Application.RunInternal(window As Window)
MyTestApp.exe: N 00110
System.Windows.Application.Run(window As Window)
MyTestApp.exe: N 00037
System.Windows.Application.Run()
MyTestApp.exe: N 00024
MyTestApp.App.Main()
App.g.cs: line 0000, col 00, IL 0014
GeneralRe: Great job fredrik but...(Solved)memberFatih Ender20 Jul '10 - 0:48 
solved .
it was a bug on DotNet 3.5Sp1
 
details on [microsoft.com]
Generalgreat job Fredrik.memberFatih Ender15 Jul '10 - 10:12 
great job Fredrik.

modified on Thursday, July 15, 2010 4:18 PM

GeneralBack Color fixmemberXmen W.K.3 Jul '10 - 5:13 
You have created a property (Color) but forgot to use it.
 
Here is the fix
 

  • Find Tab constructor
  • Replace
    Material = new DiffuseMaterial(Brushes.CadetBlue),

    with
    Material = new DiffuseMaterial(new SolidColorBrush(color)),


TVMU^P[[IGIOQHG^JSH`A#@`RFJ\c^JPL>;"[,*/|+&WLEZGc`AFXc!L
%^]*IRXD#@GKCQ`R\^SF_WcHbORY87֦ʻ6ϣN8ȤBcRAV\Z^&SU~%CSWQ@#2
W_AD`EPABIKRDFVS)EVLQK)JKQUFK[M`UKs*$GwU#QDXBER@CBN%
R0~53%eYrd8mt^7Z6]iTF+(EWfJ9zaK-i’TV.C\y<pŠjxsg-b$f4ia>
-----------------------------------------------
128 bit encrypted signature, crack if you can

QuestionBug ?memberXmen W.K.27 Jun '10 - 5:43 
1. Run app
2. Set Spin time to 0
3. Click Next rapidly(2 or 3 times), next thing you will notice, it freeze.


TVMU^P[[IGIOQHG^JSH`A#@`RFJ\c^JPL>;"[,*/|+&WLEZGc`AFXc!L
%^]*IRXD#@GKCQ`R\^SF_WcHbORY87֦ʻ6ϣN8ȤBcRAV\Z^&SU~%CSWQ@#2
W_AD`EPABIKRDFVS)EVLQK)JKQUFK[M`UKs*$GwU#QDXBER@CBN%
R0~53%eYrd8mt^7Z6]iTF+(EWfJ9zaK-i’TV.C\y<pŠjxsg-b$f4ia>
-----------------------------------------------
128 bit encrypted signature, crack if you can

GeneralGreat... [modified]memberNavnath R. Kale24 Apr '10 - 20:59 
Hey great article..

modified on Sunday, April 25, 2010 10:06 AM

GeneralRe: Great...memberFredrik Bornander25 Apr '10 - 8:35 
Hey,
 
Thanks for the comment, I appreciate it. Glad you liked it?
What happened to the longer question you posted? Did you figure it out or is it not an issue any more in your application?
 
Best regards,
Fredrik Bornander
GeneralRe: Great...memberNavnath R. Kale26 Apr '10 - 6:37 
Yeah I certainly liked it. Have already voted 5 Smile | :)
Actually I decided to figure it by my own and I did figured it by some extend. I found that DirectionalLight was incorrect.
 
As I said m just learning it, so getting hard time to understand 3D terminologies of WPF. Anyway m glad that u asked Shucks | :->
GeneralSelect on clickmemberMootah18 Mar '10 - 23:30 
Excellent post!
Nice one.
 

I've put a quick hack in Carousel.xaml.cs to select items on mousedown that works ok.
Still need to add some cleanup code to remove element.PreviewMouseDown
 
public void AddTab(FrameworkElement element)
{
element.PreviewMouseDown += ElementPreviewMouseDown;
elements.Add(element);
Organize(NumberOfTabs);
}
 
void ElementPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (navigateOnSelect)
{
int i = elements.IndexOf((FrameworkElement)sender);
if (i == currentIndex) return;
Point p1 = Mouse.GetPosition(elements[currentTabIndex]); // are we left or right of the selected item.
 
if (p1.X > 0)
SpinToPrevious();
else
SpinToNext();

//SpinToIndex(i);
}
}

GeneralRe: Select on clickmemberFredrik Bornander19 Mar '10 - 0:44 
Hey Mootah,
 
It's cool that you think my control is Excellent, your comment is much appreciated!
 
I'll see if I can find the time to work you suggestions into the project and release a new version as I think it's a cool idea you've come up with.
 
Thanks for commenting,
Fredrik Bornander
GeneralCongratulations FredrikmemberMarcelo Ricardo de Oliveira23 Feb '10 - 8:59 
Wow, 1 shot, 2 kills...
 
Very well done, man. Keep it up Smile | :)
 
cheers,
marcelo
Take a look at XNA Snooker Club game here in Code Project.

GeneralRe: Congratulations FredrikmemberFredrik Bornander23 Feb '10 - 12:45 
Hi Marcelo,
 
Thanks man, I like to think even Tom Berenger[^] would have been proud Smile | :)
 
Thanks for the comment, appreciate it.
 
Best regards,
Fredrik Bornander
GeneralGreat WorkmemberNuri Ismail19 Feb '10 - 22:59 
Thanks for sharing such a great work, Fredrik!
5 stars from me! Smile | :)
 

Best Regards,
Nuri Ismail
GeneralRe: Great WorkmemberFredrik Bornander20 Feb '10 - 2:14 
Hi Nuri,
 
It's comments such as yours that encourages me to share stuff. Thanks for commenting and voting.
 
Don't forget to vote for it over here[^] and here[^] Smile | :)
 
Best regards,
Fredrik Bornander
GeneralRe: Great WorkmemberNuri Ismail20 Feb '10 - 4:08 
Fredrik Bornander wrote:
Don't forget to vote for it over here[^] and here[^] Smile

Already done! Thumbs Up | :thumbsup:
GeneralGood Onememberashokbngr17 Feb '10 - 18:59 
Nice Article
GeneralRe: Good OnememberFredrik Bornander17 Feb '10 - 21:44 
Hi,
 
Thanks, I'm glad you liked it. Thanks for commenting.
 
Please don't forget to vote for it over
here[^] and here[^].
 
Best regards,
Fredrik Bornander
GeneralBrilliant!!!memberRiaan Lehmkuhl16 Feb '10 - 21:11 
Thumbs Up | :thumbsup:
GeneralRe: Brilliant!!!memberFredrik Bornander16 Feb '10 - 21:39 
Hi Riaan,
 
Glad you liked it!
 
Thanks for commenting,
Fredrik Bornander
GeneralCool!memberNematjon Rahmanov12 Feb '10 - 23:33 
Thumbs Up | :thumbsup:
We are haven't bug,just temporarily undecided problems.

GeneralRe: Cool!memberFredrik Bornander15 Feb '10 - 22:44 
Hi Nematjon,
 
I like your brief, to the point comment Smile | :)
 
Thanks for commenting,
Fredrik Bornander
GeneralThanks for the post.membersalimos10 Feb '10 - 8:07 
Nice Job Fredrik, i am new to WPF i am still learning and this article was very helpful. Thanks
GeneralRe: Thanks for the post.memberFredrik Bornander15 Feb '10 - 22:43 
Hey,
 
I'm glad you liked it, it's cool that you found it helpful.
 
I appreciate you commenting (and voting, I hope Smile | :) ).
 
Best regards,
Fredrik Bornander
GeneralNice Jobmembercplotts18 Jan '10 - 4:57 
Very nice ... I love it. A 5 from me! Big Grin | :-D
GeneralRe: Nice JobmemberFredrik Bornander18 Jan '10 - 5:27 
Hi,
 
Thanks man!
I'm glad you liked it (loved it, even Smile | :) ), thanks for the feedback.
 
Best regards,
Fredrik Bornander
GeneralMy vote of 5memberDr.Luiji10 Jan '10 - 0:06 
I agree with Sacha and Marcelo,
don't worry about it, mother of idiots is always pregnant.
 
Great 3d carousel, really usefull, I like it a lot.
Good work Fredrik, 5 from me.
 
Dr.Luiji
 
Trust and you'll be trusted.
 
My Blog : The Code Is Art

GeneralRe: My vote of 5memberFredrik Bornander10 Jan '10 - 0:23 
Hey,
 
A vote of 5 from the good doctor is always appreciated, thanks man, I'm glad you liked it.
 
Best regards,
Fredrik Bornander
GeneralReally quite goodmemberRichard E King5 Jan '10 - 2:54 
Really quite good
 
Richard

GeneralRe: Really quite goodmvpSacha Barber5 Jan '10 - 2:58 
What would you know you chi chi man.
Laugh | :laugh:
 
Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: Really quite goodmemberRichard E King5 Jan '10 - 3:01 
I know the Barber(ou)s tab and this is better than that
 
Richard

JokeRe: Really quite goodmemberFredrik Bornander7 Jan '10 - 1:07 
Yeah, but is it grizzly-bear-chair[^] good?
 
/Fredrik
GeneralRe: Really quite goodmvpSacha Barber7 Jan '10 - 5:04 
Or even this good http://www.blogadilla.com/wp-content/uploads/2009/08/crappy-taxidermy.jpg[^]
 
Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralAmazingmemberMarcelo Ricardo de Oliveira4 Jan '10 - 3:39 
5 stars! What a wonderful work, man. I agree with Sacha, too bad some idiot voted 3...
 
Please keep sharing, Frederik!
 
Take a look at full source code C# Snooker game here in Code Project.

GeneralRe: AmazingmemberFredrik Bornander4 Jan '10 - 4:11 
Hi Marcelo,
 
Glad you liked it, comments such as yours really encourages sharing.
 
Thanks for the feedback,
Fredrik

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 16 Jun 2012
Article Copyright 2010 by Fredrik Bornander
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid