![]() |
Desktop Development »
Menus »
Menus and Toolbars
Intermediate
License: The Code Project Open License (CPOL)
Creating WPF-Like Dynamic Menus on XP using LinqToSQL and the new Telerik RadCarousel ControlBy Ken NorwickA project to demonstrate the WPF-Like user interface functionality of a Carousel Menu on Windows XP |
C# (C# 3.0), SQL, Windows (WinXP, Vista), Win32, SQL Server (SQL 2005), Visual Studio (VS2008), WPF, WinForms, LINQ, Architect, Dev, Design
|
||||||||
|
Advanced Search |
|
|
|
||||||||||||||||
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.
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.
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
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
////// Class: ApplicationDAL /// ©2008 Advance Guard Software - All Rights reserved /// This class provide the Linq-to-SQL data access support functions for /// information stored in the application-specific datastore. /// public class ApplicationDAL { #region (CarouselMenuItems) Data Access and Update Functions /// /// Method: GetCarouselMenuItemsByMenuID() /// ©2008 Advance Guard Software - All Rights reserved /// This function will return a list of Carousel Menu Items for the specified /// Carousel menu /// /// Guid containing the unique Carousel Menu identifier /// A strongly-typed list of CarouselMenuItem objects for the Carousel control public static IEnumerable GetCarouselMenuItemsByMenuID(Guid CarouselMenuID) { //get a reference to our LINQ-to-SQL data context. The //data context object was created for us automatically //when we added our LINQtoSQLClasses object into our //project and dragged our tables, views, and stored //procedures onto it from the Server Explorer pane 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 }
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; ////// Form Constructor: /// ©2008 Advance Guard Software - All Rights reserved /// During the form build up we setup our Telerik RadCarousel /// for Winforms control. The settings that we have used here /// are a pretty good bet for getting up and running quickly /// without a big learning curve. /// public CarouselForm() { InitializeComponent(); //we are using a Telerik ShapedForm control as our main //page. Here we are setting it to one of the standard //default shapes this.Shape = new RoundRectShape(); #region Telerik RadCarousel Control Initialization try { #region Note: Set up a text primitive that will hold the selected //A Telerik TextPrimitive control is used in this example //to hold each menu item's caption. Set initial values //for font size, color, and alignment 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 //RadCarousel 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 //Here we are setting general properties on the RadCarousel //control to allow the images to be displayed in a continuous //loop. As well, you can set the number of visible menu items //that will be displayed 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.
////// Method: BindCarouselData() /// ©2008 Advance Guard Software - All Rights reserved /// This function will retireve a IEnumerable list of CarouselMenuItem objects /// for the specified menu id. /// private void BindCarouselData() { try { //Use our Linq Data Access Layer (DAL) class to get //the specified menu elements. Menu definitions use //a Guid key field value and we have application //constants defined for our default (pre-installed) //menu definitions that exist in our SampleDB database 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.
////// Event Handler: /// ©2008 Advance Guard Software - All Rights reserved /// /// /// private void radCarousel1_ItemDataBound(object sender, Telerik.WinControls.UI.ItemDataBoundEventArgs e) { //get a reference to the current carousel's RadButtonElement //so that we can updates its property values as we load each //CarouselMenuItem object from the data layer RadButtonElement carouselItem = (RadButtonElement)e.DataBoundItem; try { if (carouselItem != null) { //set the relative position of the button's text caption //and menu image objects carouselItem.TextImageRelation = TextImageRelation.TextAboveImage; carouselItem.Font = new Font("Verdana", 10f); carouselItem.ForeColor = Color.White; //Store data to the .Tag property to use later //Note: the Linq ORM layer will extract objects from //the underlying database into its data context in the //following manner: Our table: CarouselMenuItems (plural) //will result in an object named: CarouselMenuItem //(singular form) within our LinqtoSQL data context carouselItem.Tag = (CarouselMenuItem)e.DataItem; //Create an instance of a CarouselMenuItem object //from the current dataitem passed as an argument //to this event handler CarouselMenuItem dataObject = (CarouselMenuItem)carouselItem.Tag; if (dataObject != null) { //each data record has a varbinary(MAX) column //that contains an image specific to the menu item. //We will use a utility function (included with the //source code download package) to extract the image //binary data and to reconstruct it back into an //image with a specified size (165px x 165px) that //we attach to the current carousel menu item carouselItem.Image = Utility.SQLBlobDataToThumbnailImage (dataObject.DisplayImage, 165, 165); } //Pick up the display name for the current menu item //and use it to set the text property of our //RadCarouselItem that we are loading into the carousel //controls collection if (carouselItem != null) { carouselItem.Text = ((String)EvaluatePropertyValue(e.DataItem, "DisplayName")); } else { carouselItem.Text = "DisplayName???"; } //Our CarouselMenuItem object in the database contains //tooltip text specific to an individual menu item. Here //we extract it from the data object using a helper //function and then we attach it to the current menu item carouselItem.ToolTipText = ((String)EvaluatePropertyValue (e.DataItem, "TooltipText")); //Note: Here we attach an event handler to each carousel //menu item as it is data bound to our RadCarousel control //Code in the event handler for each menu item will launch //the requested menu action functionality carouselItem.DoubleClick += new EventHandler(OnDoubleClick); } } catch (Exception ex) { Status.Text = "Error: " + ex.Message; } } /// /// Method: EvaluateDatePropertyValue() /// ©2008 Telerik - All Rights reserved /// This function will extract the value of a specified /// DateTime property from a data item and will return it /// as a string representation /// /// /// /// private object EvaluateDatePropertyValue(object dataItem, string property) { return (TypeDescriptor.GetProperties(dataItem)[property].GetValue(dataItem)) .ToString(); } /// /// Method: EvaluatePropertyValue() /// ©2008 Telerik - All Rights reserved /// This function will extract the value of a specified /// field property from a data item and will return it /// as a string representation /// /// /// /// private object EvaluatePropertyValue(object dataItem, string property) { return TypeDescriptor.GetProperties(dataItem)[property].GetValue(dataItem); } /// /// Event handler: radCarousel1_SelectedIndexChanged /// ©2008 Advance Guard Software - All Rights reserved /// This event handler will process a change of selected index /// event from a RadCarousel control. It will extract the menu /// item's display caption from the menu item .Tag property and /// will use it to update the displayed name of the current menu /// choice on the navigation bar below the carousel menu /// /// /// 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 ////// Event Handler: radCarousel1_DoubleClick() /// ©2008 Advance Guard Software - All Rights reserved /// This function processes a user request to launch the /// selected menu action item /// /// /// private void OnDoubleClick(object sender, EventArgs e) { try { if (radCarousel1.SelectedIndex != -1) { RadItem item = (radCarousel1.SelectedItem as RadItem); if (item != null && item.Tag != null) { //Cast the previously stored record data to a //CarouselMenuItem object so that we can read //the current item's record id (a Guid value) //This value will be passed to our "launcher" //function that will display the requested data //entry form as a modal dialog above our carousel //menu CarouselMenuItem dataObject = (CarouselMenuItem)item.Tag; if (dataObject != null) { //pick up the carousel menu id and //display the appropriate data entry form DisplayAppropriateDataEntryForm(dataObject .CarouselMenuItemID.ToString()); } } } } catch(Exception ex) { Status.Text = "Error: " + ex.Message; } } /// /// Method: DisplayAppropriateDataEntryForm() /// ©2008 Advance Guard Software - All Rights reserved /// This function will display the correct data entry screen for /// a specified CarouselMenuID (record key value from our database) /// /// String /// containing the a Daily Report Activity constant private void DisplayAppropriateDataEntryForm(String CarouselMenuItemID) { try { Status.Text = ""; //We have predefined application-level constants that are //used to make our code more readable and easier to maintain 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
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.
May 26, 2008 - Initial submission
May 27, 2008 - Formatting updates and additional code comments
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 26 May 2008 Editor: |
Copyright 2008 by Ken Norwick Everything else Copyright © CodeProject, 1999-2009 Web12 | Advertise on the Code Project |