Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating WPF-Like Dynamic Menus on XP using LinqToSQL and the new Telerik RadCarousel Control

0.00/5 (No votes)
26 May 2008 1  
A project to demonstrate the WPF-Like user interface functionality of a Carousel Menu on Windows XP

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

Database tables

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.

Database tables

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.

Database tables

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.

    /// 
    /// 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

    }


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;

        /// 
        /// 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



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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here