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

GUI Library for Managed DirectX Applications

0.00/5 (No votes)
24 Jul 2007 1  
This article introduces the Odyssey User Interface, a library of controls that can be used in any Managed DirectX application.

Screenshot - OdysseyUI.jpg

Introduction

I am developing a 4X Space Opera game called Star Odyssey, in the tradition of the Master of Orion series. Every game needs a User Interface and mine is no exception. The DirectX SDK provides a sample UI that looks very cool, but it is very difficult to extrapolate from the project and it relies on textured GUI elements. Since I am not very good at drawing GUI elements, it would have taken ages for me to go that way. Instead, I took the ''programmer art'' way: to create controls and other GUI elements in a dynamic way, trying my best to make it look cool at the same time. The result is presented here. Readers should be somewhat experienced in DirectX terminology. Since the game doesn't need absolute FPS responsiveness, simple Windows messages are used to handle input. Please note that in order to run the sample, you need the DirectX SDK installed. In case you have problems, try using the DirectX web installer.

Using the code

The controls provided in this project are designed to work just like Windows Forms controls. So, people that already have experience in developing Forms application will find these renderable controls very easy to use. In the demo archive, you'll find the Odyssey UI.dll. It can be referenced from any C# MDX project and it is the easiest way to use this library. Otherwise, you can compile the source code. To use the library in your code, you have to follow these easy steps:

  1. Reference the AvengersUTD.Odyssey.UserInterface namespace.
  2. You have to let Odyssey UI manage Windows Forms events. For your convenience, you can call the supplied method UI.SetupHooks(form);).
  3. Assign to UI.Device your DirectX device object. Throughout the code, there are several ''device'' calls, so this avoids you having to pass the device reference every call.
  4. Create a HUD object. The HUD is the on-screen overlay of the User Interface.
  5. Tell the HUD that you're starting to design the UI, hud.BeginDesign();.
  6. Create the controls as you would do when coding Windows Forms applications.
  7. Add the controls to the HUD container, hud.Add(control);. Note that the top level children of the HUD are to be understood as if they were on the desktop. So it is your responsibility to avoid making them overlap. If you want multiple windows, add the control inside a window and then add the window to the HUD instead.
  8. Assign events, if necessary.
  9. When you are done creating the UI, use hud.EndDesign();.
  10. Assign the HUD object created this way to be the CurrentHUD of the User Interface, UI.CurrentHUD = hud;. This allows you to specify different HUDs and change HUD with ease.
  11. Finally, in your render loop you just have to call hud.Render();.

The following is the sample code to render the interface you see in the image:

// Add these lines where appropriate in your code:

// (provided that you did all the above steps also)


UI.Device = yourDirectXDevice;
UI.SetupHooks(form);

// Create a new hud object. The string parameter

// is its ID: it will be used in the future

// when the library will allow users

// to "skin" via xml the user interface.

// ScreenSize refers to a size variable that represents

// your current resolution. The HUD should always be set

// to be as big as it can be.


HUD hud = new HUD("TestHud", Settings.ScreenSize);

private void TestUI()
{
    // Start designing the UI

    hud.BeginDesign();

    // Create a panel control;

    Panel panelTest = new Panel("UI Test Panel", 
        new Vector2(25, 150), new Size(520, 400));

    // Create two labels. The Vector2 parameter

    // refers to its position in the parent control.

    // Since the labels are going to be

    // added to the previous panel, the

    // absolute position of the first

    // one will then be: (X: 25 + 20, Y: 250 + 20).

    // The alignment parameters are used

    // to specify how you want to draw the text.

    // The first label can be highlighted, hence the 

    // extra color value in the constructor.

    
    Label lTest = new Label("LabelTestPanel", 
                  "The blue window is a Panel" + 
                  " Control and this is a Label",
                  Alignment.Left, Alignment.Top, 
                  new Vector2(10, 10), Color.White, Color.Red);

    Label lTb = new Label("LabelTrackBar", "This TrackBar control " + 
                "goes from 0 to 10 with a\n'TickFrequency' value of 2",
                Alignment.Left, Alignment.Top, 
                new Vector2(20, 40), Color.LightGreen);

    // This is a button. We have attached a delegate

    // to its "MouseClick" event: it will be fired

    // when the user clicks on the button.

    
    Button example = new Button("ButtonExample", "This is a button", 
        new Vector2(20, 320), new Size(200, 50));
    example.MouseClick += delegate(BaseControl sender, 
        System.Windows.Forms.MouseEventArgs e)
    {
        example.Label = "Yep you clicked me";
    };
    
    // This button opens a modal dialog box.

    // In windows forms the MessageBox.Show method is "blocking"

    // it means that the program does not continue to the 

    // "next line" until the user presses one of the dialog's buttons.

    // Since simulating that behavior would have taken too much time

    // for the time being you have pass a delegate method as a parameter

    // that tells what you want to do. Just pass null if you simply

    // want the window to close.

    
    Button dialogTest = new Button("DialogTest", "Show me a dialog", 
        new Vector2(300,320), new Size(200,50) );
    dialogTest.MouseClick += 
        delegate(BaseControl sender, 
        System.Windows.Forms.MouseEventArgs e)
    {
        DialogBox.Show("Test", 
            "Do you like this User Interface?\n\nBy the way, " + 
            "this dialog is modal!",
            DialogBoxButtons.YesNo,
            delegate(BaseControl ctl, DialogResult dialogResult)
        {
            if (dialogResult == DialogResult.No)
                DialogBox.Show("Really?", 
                "Sigh.... :(", DialogBoxButtons.Ok, null);
            else
                DialogBox.Show("Thanks", 
                "I'm glad that you liked it!\n\nIf you have any " + 
                "feedback drop me a line at avengerdragon at gmail.com " + 
                "or visit my forum at " + 
                "[hover=\"Aquamarine\"]http://starodyssey.avengersutd.com[/]",
                DialogBoxButtons.Ok, null);
        });
    };

    // This is a trackbar control. You have to set

    // the trackbar minim value, tick Frequency

    // and maximum values with the SetValues method.

    // You can also attach a delegate method for

    // its "ValueChanged" event.

    
    TrackBar slider = new TrackBar("TrackBar", 
        new Vector2(20, 100), new Size(200, 30));
    slider.SetValues(0, 2, 10);
    slider.ValueChanged += delegate(BaseControl ctl) {
        lTb.Text = "The trackbar value is now: " + slider.Value;
    };

    // This is a textbox. When you click on it, you can start typing.

    
    TextBox tb = new TextBox("TextBox",
        new Vector2(20, 140), new Size(200, 30), 24);
    tb.Text = "This is a textbox";

    // This is a groupbox control: a simple panel

    // with a flat border and a caption.

    
    GroupBox gb = new GroupBox("GroupBox", "This is a groupbox", 
        new Vector2(20, 200), new Size(200, 100),
        Color.White, BorderStyle.Flat);

    // This is an OptionGroup control: a collection of radio buttons.

    // The string array passed as a parameter

    // will be used to dynamically create an option button

    // for each of the strings. You can also attach

    // a delegate too its "SelectedIndexChanged" event

    
    OptionGroup og = new OptionGroup("OptionGroup", new string[] {
        "This is", "the OptionGroup", "control"}, 
        new Vector2(5, 10), new Size(100, 30));
    og.SelectedIndexChanged += delegate(BaseControl ctl)
    {
        gb.Caption = "You clicked the OptionButton number: " + 
            og.SelectedIndex;
    };
    
    // This is the CheckBox Control. You can access its 

    // selected value through its .IsSelected property.

    
    CheckBox cb = new CheckBox("CB1", "Checkbox", 
            new Vector2(300, 300), new Size(100,30));

    // This is the DropDownList control also known as

    // a combobox.

    
    DropDownList ddl = new DropDownList("DDL",
        new string[] { "This", "is the", "DropDownList", "control" },
        new Vector2(300, 100), new Size(150, 30));    

    // Next, we'll  create three windows.

    // We'll then create three controls to go inside those 

    // windows: a RichTextArea, a Table and a TabPanel

    
    Window win1 = new Window("win1", 
        "Test Window #1", new Vector2(500, 100), new Size(640, 480));
    Window win2 = new Window("win2", 
        "Test Window #2", new Vector2(550, 110), new Size(640, 480));
    Window win3 = new Window("win3", 
        "Test Window #3", new Vector2(600, 120), new Size(640, 480));
    
    // This is the RichTextArea control. It is a panel that 

    // automatically formats input depending on BBCode like

    // string. We simply pass the string to be formatted and the

    // style to use as default (if the default one is Arial 20pt 

    // for example, the bold command will apply the bold effect

    // on the Arial 20pt font)

    // Accepted commands are b,i,s for bold, italic and shadowd

    // respectively and c or color for the standard color and

    // h or hover for the color to use when the mouse pointer

    // is over the label. Nested markup is not supported at

    // the moment.

    // We pass an empty vector because we want it to cover

    // the whole internal area of the window. We manually

    // compute its size because there's no "autosize" feature yet.

    
    RichTextArea rta = new RichTextArea("RTA", 
        Vector2.Empty, new Size(632, 300), rtext, TextStyle.Default);
        
    // This is the TabPanel control. A Panel that has some

    // buttons on the top that allow the user to access different

    // pages in it. Each page in this example has a label control.

    // You can switch page by clicking the top buttons.

    
    TabPanel tabPanel = new TabPanel("Tab", 
        new Vector2(30, 50), new Size(310, 200));
    tabPanel.AddTab("Page 1");
    tabPanel.AddControlInTab(new Label("pag1", 
        "Page 1", Alignment.Left, Alignment.Top,
        new Vector2(100, 20), Color.LightGreen), 0);
    tabPanel.AddTab("Page 2");
    tabPanel.AddControlInTab(new Label("pag2", 
        "Page 2", Alignment.Left, Alignment.Top,
        new Vector2(100, 20), Color.LightGreen), 1);
    tabPanel.AddTab("Page 3");
    tabPanel.AddControlInTab(new Label("pag3", 
        "Page 3", Alignment.Left, Alignment.Top,
        new Vector2(100, 20), Color.LightGreen), 2);    
        
    // This is the table control. You can esaily format by choosing

    // different TableStyle parameters

    
    Table table = new Table("Table", 3, 2, new Vector2(15, 15));
    table[0, 0].Text = "This";
    table[0, 1].Text = "is the";
    table[1, 0].Text = "Table";
    table[1, 1].Text = "control!";
    table.Format(new TableStyle(150, 30, 1, 1, Border.All));
            
    // Finally we add each control to its parent container.

    
    gb.Add(og);
    panelTest.Add(lTest);
    panelTest.Add(lTb);
    panelTest.Add(slider);
    panelTest.Add(tb);
    panelTest.Add(gb);
    panelTest.Add(ddl)
    panelTest.Add(example);
    panelTest.Add(dialogTest);
    
    win1.Add(rta);
    win2.Add(tabPanel)
    win3.Add(table);
    
    hud.Add(panelTest);
    hud.Add(win1);
    hud.Add(win2);
    hud.Add(win3);
    
    // Signal the hud object that we're doing creating controls

    
    hud.EndDesign();
}

// After having called the TestUI method

// somewhere in yor app before rendering,

// in your render loop you have to place

// the following line:


public void Render()
{
    hud.Render();
}

Points of interest

I tried to mimic the Windows Forms event style and the result looks very similar to it. In the development of my game, it showed to be very helpful because once the needed controls are implemented and fully working, you can concentrate on the game itself, speeding things along. These are the main features of the UI:

  • Full windows support: the UI correctly renders multiple windows.
  • Modal and draggable windows.
  • Shape support: circular, rectangular and trapezoidal default shapes are built-in, but you can define new ones through the Graphics static class.
  • Border styles: flat, raised, sunken; more can be implemented.
  • Control status changes: enabled, highlighted, focused, selected, etc.
  • All the mouse and keyboard events you can think of!

The following controls are included:

  • Window
  • DialogBox
  • Label
  • Panel
  • Button
  • TextBox
  • TrackBar
  • GroupBox
  • OptionGroup
  • CheckBox
  • TabPanel
  • Table
  • DropDownList
  • RichTextArea
  • PictureBox

It should be noted that by deriving or improving the PictureBox control you can theoretically develop a whole new set of textured controls, in addition to the renderable ones. You could even combine them to achieve new effects. There are several possibilities. More controls are on the way. To stay updated on the development of this library, refer to the project page at the Star Odyssey website. Please post any bugs you find and requests on the forums.

The Odyssey UI is released under the Creative Commons Attribution NonCommercial license. This source code can be modified as you wish and if you do decide to use it in your project, please contact me telling me about it so we can share links if you'd like. Thanks for reading!

History

  • 22 July, 2007: Version 0.3 - Added the Window, DialogBox, RichTextArea and PictureBox controls.
  • 14 June, 2007: Version 0.2 - Updated version. Added the Table, Tabpanel and Checkbox controls and the preliminary layering feature.
  • 14 August, 2006: Version 0.1 - First CodeProject submission.

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