Introduction
This article describes how to easily implement a dynamic WPF-like image carousel menu page
for XP-based winforms applications using the new RadCarousel navigation control from Telerik's new 2008 winforms UI control suite. This article assumes that the reader has an installed working copy of either SQL Server 2005 or SQL Express 2005 edition and a trial or commercial copy of the RadControls suite from Telerik. Get a copy here
There are three main areas that I will focus on for this project:
(1) Creating a database to store our menu definitions that will be used to load our carousel menu.
(2) Adding a RadCarousel navigation control to our Windows form page and setting it up.
(3) Implementing a LinqToSQL data context that will be used to access the menu elements (including image manipulation, and storage).
Basically, we will start by placing the latest RadCarousel navigation control from Telerik onto a Windows form, setting some initial properties on the control and then adding code to capture several of the control's events that we are interested in. We will end up with a very attractive image-based menu that can be used to launch other program functionality.
Background
My current consulting project required a modern, easily-understood user interface that could be deployed on XP-level laptops without requiring operating system upgrades to Vista/WPF technologies. An evaluation of currently available commercial UI control suites led me to Telerik. Implementing their new RadCarousel navigation control is the subject of this article.
Using the RadCarousel Control
We start this project by dragging and dropping the carousel control onto our Windows form. Note: To run the source code project you will need to download and install a trial copy of the latest (2008) RadControls for Winforms UI Suite from Telerik. Get a copy here
The carousel menu will display a set of images that correspond to various menu selections that a user can choose from. These images move in an elliptical path on the screen in response to the user clicking on either the individual images or on the navigation controls at the bottom of the carousel. As the images move around the screen they are resized automatically by the control, such that they will appear to move from the background (smaller) to the foreground (larger). The currently selected menu item will be displayed front and center as a full-sized image and its caption will be displayed in the middle of the navigation bar. When the user double-clicks on a particluar image within the menu we trap that event and use it to launch the requested application functionality.
This implementation of the carousel control is unique in that it dynamically loads both menu items and their associated images directly from a database at runtime using the latest .NET 3.0 LinqToSQL technologies. I have included "helper" utilities to demonstrate how to load, save, and manipulate image data using varbinary(MAX) fields and Linq
Creating the Database and Linq ORM Data Objects
Our simple database ("SampleDB") contains two tables for storing the information that will be used to create our carousel menu. The table CarouselMenus contains the top-level menu definitions. A unique Guid ID is created for each menu, along with a display name and other attributes. The second table CarouselMenutItems contains the related menu items along with the menu text, carousel image, and the tooltip text that will be displayed.
This article assumes that you are able to install a copy of SQL Server, create a database, and to set security settings on the database to allow a trusted connection by the demo application. These things are beyond the scope of this article. However, I have included some images within the demo project to help you with these issues

With the database created and connected to our application we need to add LINQ support classes to our winforms application. Right-click on your project and select Add New Item from the context menu. We are looking for the LINQ to SQL Classes object on the displayed list of available. Select the class for inclusion into our project and can proceed to identify the tables, views and stored procedures that will become part of our new LINQ DataContext.

Using the Server Explorer panel select the data connection that you want to work with. Start by dragging the two tables from our sample database onto the LINQDataContext that we just created. In a moment the images of the two tables will appear on the context. You can drag any views or stored procedure onto the context in the same manner.

Next, I have created a data access layer class for accessing our menu items. This class contains one simple LINQ query that is used by the carousel menu to load a specified menu and its related menu items by the CarouselMenuID key value.
The nifty part of all of this is that LINQ-to-SQL works like magic and the data context object that you created just "knows" about your data, its structure and any relationships between data elements. This function will return list of CarouselMenuItem objects that will be used to load our dynamic carousel menu.
public class ApplicationDAL
{
#region (CarouselMenuItems) Data Access and Update Functions
public static IEnumerable GetCarouselMenuItemsByMenuID(Guid CarouselMenuID)
{
ApplicationLINQDataContext dbContext = new ApplicationLINQDataContext();
try
{
return
(from CarouselMenuItem in dbContext.CarouselMenuItems
where CarouselMenuItem.CarouselMenuID == CarouselMenuID
orderby CarouselMenuItem.DisplayName ascending
select CarouselMenuItem);
}
catch
{
}
return null;
}
#endregion
}
Coding the Carousel Menu Form
In the form's constructor method I set initial properties on the carousel control and define a path that the images will follow as they rotate around on the screen. The standard size for my carousel menu item images is 165px x 165px and I have included a sample image for you to work from. To implement custom images simply add your own images and text to copies of the base image and then use the program to upload your new image into the database. When you next run the sample project your new images will have replaced my samples.
public partial class CarouselForm : ShapedForm
{
private TextPrimitive selectedItemText = new TextPrimitive();
private CarouselBezierPath carouselBezierPath1 = new
Telerik.WinControls.UI.CarouselBezierPath();
private ThemeSource themeSource1 = new Telerik.WinControls.ThemeSource();
private CarouselBezierPath initialPath;
private Size initialSize;
public CarouselForm()
{
InitializeComponent();
this.Shape = new RoundRectShape();
#region Telerik RadCarousel Control Initialization
try
{
#region Note: Set up a text primitive that will hold the selected
selectedItemText.AutoSize = false;
selectedItemText.Bounds = new Rectangle(190, 404, 502, 39);
this.radCarousel1.CarouselElement.Children.Add(selectedItemText);
selectedItemText.Font = new Font("Arial Rounded MT Bold", 14.25f);
selectedItemText.ForeColor = Color.Black;
selectedItemText.TextAlignment = ContentAlignment.MiddleCenter;
#endregion
#region Note: Define a CarouselBezierPath object and attach it to our
this.carouselBezierPath1.CtrlPoint1 = new Point3D(200, 380, 75);
this.carouselBezierPath1.CtrlPoint2 = new Point3D(800, 380, 75);
this.carouselBezierPath1.FirstPoint = new Point3D(150, 170, -75);
this.carouselBezierPath1.LastPoint = new Point3D(820, 170, -75);
this.carouselBezierPath1.ZScale = 300;
this.radCarousel1.CarouselPath = carouselBezierPath1;
initialPath = carouselBezierPath1;
initialSize = this.radCarousel1.Size;
#endregion
#region Note: Set some general settings on the RadCarousel control
this.radCarousel1.EnableLooping = true;
this.radCarousel1.ForeColor = System.Drawing.Color.Black;
this.radCarousel1.NavigationButtonsOffset =
new System.Drawing.Size(140, 43);
this.radCarousel1.VisibleItemCount = 5;
#endregion
}
catch (Exception ex)
{
Status.Text = "Error: " + ex.Message;
}
#endregion
}
private void CarouselForm_Load(object sender, EventArgs e)
{
BindCarouselData();
SeekToSelectedCarouselItem(ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE1);
}
In our form's onload event we bind the carousel data using our DAL object. Please note the use of application constants such as: ApplicationConstants.CAROUSEL_MENU_ONE. This is a good practise and makes your code much easier to read later. As you can see by the code it is very easy and straightforward to access our data using LINQ technologies.
private void BindCarouselData()
{
try
{
this.radCarousel1.DataSource =
ApplicationDAL.GetCarouselMenuItemsByMenuID(new
Guid(ApplicationConstants.CAROUSEL_MENU_ONE));
}
catch (Exception ex)
{
Status.Text = "Error: " + ex.Message;
}
}
In our code we capture several keys events on the RadCarousel control. The "heavy lifting" is accomplished in the carousel's ItemDataBound event. If you can remember, our DAL function returns an IEnumerable list of CarouselMenuItem objects. Our LINQ DataContext is aware of this object and its structure. We simply cast the data item to this object type during the item data binding event and we are then able to access its properties directly. Our carousel uses Telerik RadButtonElements for its menu items. In the code below you can see that I am attaching the data obect to each carousel item's Tag property so that we can access this information later. I then extract the menu item's image from the data object and pass it to a small utility function that will process the binary data into a format and size that can be used by the RadButton element's image property. Finally, I set the tooltip text property on the image button from the data stored in the database record.
private void radCarousel1_ItemDataBound(object sender,
Telerik.WinControls.UI.ItemDataBoundEventArgs e)
{
RadButtonElement carouselItem = (RadButtonElement)e.DataBoundItem;
try
{
if (carouselItem != null)
{
carouselItem.TextImageRelation = TextImageRelation.TextAboveImage;
carouselItem.Font = new Font("Verdana", 10f);
carouselItem.ForeColor = Color.White;
carouselItem.Tag = (CarouselMenuItem)e.DataItem;
CarouselMenuItem dataObject = (CarouselMenuItem)carouselItem.Tag;
if (dataObject != null)
{
carouselItem.Image = Utility.SQLBlobDataToThumbnailImage
(dataObject.DisplayImage, 165, 165);
}
if (carouselItem != null)
{
carouselItem.Text = ((String)EvaluatePropertyValue(e.DataItem,
"DisplayName"));
}
else
{
carouselItem.Text = "DisplayName???";
}
carouselItem.ToolTipText = ((String)EvaluatePropertyValue
(e.DataItem, "TooltipText"));
carouselItem.DoubleClick += new EventHandler(OnDoubleClick);
}
}
catch (Exception ex)
{
Status.Text = "Error: " + ex.Message;
}
}
private object EvaluateDatePropertyValue(object dataItem, string property)
{
return (TypeDescriptor.GetProperties(dataItem)[property].GetValue(dataItem))
.ToString();
}
private object EvaluatePropertyValue(object dataItem, string property)
{
return TypeDescriptor.GetProperties(dataItem)[property].GetValue(dataItem);
}
private void radCarousel1_SelectedIndexChanged(object sender,
System.EventArgs e)
{
try
{
RadItem item = (this.radCarousel1.SelectedItem as RadItem);
if (item != null && item.Tag != null)
{
this.selectedItemText.Text =
((String)EvaluatePropertyValue(item.Tag, "DisplayName"));
AnimatedPropertySetting setting =
new AnimatedPropertySetting(VisualElement.ForeColorProperty,
Color.Transparent, Color.White, 10, 40);
setting.ApplyValue(selectedItemText);
}
}
catch (Exception ex)
{
Status.Text = "Error: " + ex.Message;
}
}
Launching out to additional application functionality from our carousel-based menu is as simple as double-clicking the menu item image from the carousel. Our code captures the double click event of each carousel menu item. Each time this event is triggered we extract the CarouselMenuItemID associated with the particular carousel menu item and then pass this value to another function that will load the correct application functionality as a modal dialog form.
#region Process Carousel Selections
private void OnDoubleClick(object sender, EventArgs e)
{
try
{
if (radCarousel1.SelectedIndex != -1)
{
RadItem item = (radCarousel1.SelectedItem as RadItem);
if (item != null && item.Tag != null)
{
CarouselMenuItem dataObject = (CarouselMenuItem)item.Tag;
if (dataObject != null)
{
DisplayAppropriateDataEntryForm(dataObject
.CarouselMenuItemID.ToString());
}
}
}
}
catch(Exception ex)
{
Status.Text = "Error: " + ex.Message;
}
}
private void DisplayAppropriateDataEntryForm(String CarouselMenuItemID)
{
try
{
Status.Text = "";
switch (CarouselMenuItemID)
{
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE1:
LoadImages modalEditDetails1 = new LoadImages();
modalEditDetails1.TopMost = true;
modalEditDetails1.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE2:
DataForm1 modalEditDetails2 = new DataForm1();
modalEditDetails2.TopMost = true;
modalEditDetails2.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE3:
DataForm1 modalEditDetails3 = new DataForm1();
modalEditDetails3.TopMost = true;
modalEditDetails3.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE4:
DataForm1 modalEditDetails4 = new DataForm1();
modalEditDetails4.TopMost = true;
modalEditDetails4.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE5:
DataForm1 modalEditDetails5 = new DataForm1();
modalEditDetails5.TopMost = true;
modalEditDetails5.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE6:
DataForm1 modalEditDetails6 = new DataForm1();
modalEditDetails6.TopMost = true;
modalEditDetails6.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE7:
DataForm1 modalEditDetails7 = new DataForm1();
modalEditDetails7.TopMost = true;
modalEditDetails7.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE8:
DataForm1 modalEditDetails8 = new DataForm1();
modalEditDetails8.TopMost = true;
modalEditDetails8.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE9:
DataForm1 modalEditDetails9 = new DataForm1();
modalEditDetails9.TopMost = true;
modalEditDetails9.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE10:
DataForm1 modalEditDetails10 = new DataForm1();
modalEditDetails10.TopMost = true;
modalEditDetails10.ShowDialog();
break;
case ApplicationConstants.CAROUSEL_ITEM_MENU_CHOICE11:
DataForm1 modalEditDetails11 = new DataForm1();
modalEditDetails11.TopMost = true;
modalEditDetails11.ShowDialog();
break;
default:
break;
}
}
catch (Exception ex)
{
Status.Text = "Error: " + ex.Message;
}
}
#endregion
Points of Interest
This project demonstrates just how easy it is to implement WPF-like user interface functionality on current XP-based machines without incurring the expense or enduring the learning curve associated with updates to Windows Vista and the Windows Presentation Foundation (WPF). As well, .NET 3.5 LINQ-to-SQL data technologies show us just how much easier data access and manipulation has become for developers.
History
May 26, 2008 - Initial submission
May 27, 2008 - Formatting updates and additional code comments