In Depth Banner
Skip Navigation LinksWelcome > In Depth articles > DirectX Primer

Select your preferred language

Using DirectX in Windows Forms controls.

Recently, Microsoft released DirectX9 which has a new set of managed wrappers that enable you to use DirectX through C# or VB.NET applications.

Many articles exist on how to create a game under DirectX but the speed and power of DirectX is not just for games. Some user interfaces can require graphics speed that just isn't available from GDI+ without acceleration.

This article examines the use of DirectX as a valuable addition to the arsenal of the user interface programmer. A prerequisite for this article is the DirectX9 SDK which can be found on the Microsoft site as a free download.

The subject chosen for this article is a scrolling marquee control that uses DirectX to create a fast, smooth scrolling marquee which doesn't load the processor unduly. It uses the 2 dimensional DirectDraw API's.

The DirectDraw system uses some highly optimised code that takes full advantage of all hardware acceleration afforded by the system. Generally, a DirectDraw application uses at least a double buffered approach to drawing, creating an image in one or more passes in memory and finally copying that image to the screen. These in-memory or on-screen images that can be manipulated are called Surfaces. In full screen mode, the most commonly used for DirectDraw applications, the visible image, or primary surface, covers all of the screen. However, in an application such as a form or control DirectDraw is used in windowed mode and only a portion of the screen is affected.

The DXMarquee control uses three direct draw surfaces. The primary surface on which the final output is diplayed, a back-buffer surface used to assemble the image and an off-screen image that contains the text drawn with GDI+ in the font and colour required.

Figure 1 shows the relationship between the application and the various drawing surfaces.

Figure 1: DirectX drawing surfaces and their relation to each other

Although the primary surface covers the whole screen, only the portion covered by the window is updated. The update can be done infrequently but of course, DirectX means speed so often a timer is set to update the window between 30 and 50 times a second.

The AppWizard provided for DirectX doesn't provide an option for creating a Windows Forms control. It's also true to say that the full-screen and windowed applications that are created by the wizard are unnecessarily complex so examining them closely for clues on how to do it will result in a control that's built like a battleship. The points to note are; Create a directX Device, set the cooperative level to windowed mode, create the primary surface and any secondary surfaces you may need then get on with the job of drawing the control.

The Marquee control takes a simple line of text and scrolls it across the surface of the control at speeds both slow and blisteringly fast. To do this, rather than drawing the marquee image at each draw cycle, the image is only recreated when the text changes, The appearance of moving the text is managed by blitting the static image of the text to different positions in the back-buffer and then copying the back-buffer to the screen. The basic control is unremarkable inasmuch as it's created by the new project wizard as a class library. We don't even need to bother with a control library because UserControl has baggage we don't want.

An important difference to note between DirectX blitting and GDI+ is that the source and destination rectangles must be wholly inside their respective surfaces. You cannot attempt to blit a 150 pixel wide segment from a surface that is only 100 pixels wide. If you try this, an exception will be thrown so it's important to ensure that all source and destination rectangles are carefully constructed.

The DXMarquee control

Starting from a simple class library, the DXMarquee control was first given references to the System.Drawing DLL for the text capabilities, the System.Windows.Forms DLL for the control base class, the Microsoft.DirectX and Microsoft.DirectX.DirectDraw DLL's for the DirectX features.

Initializing the control

First the DirectX device is created and the cooperative level set to Normal. This enables Windowed mode. Then the timer is initialized so that updates will be fired

    public DXMarquee()

    {

      this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);

 

      //Create the device and set the cooperative level.

      _device=new Device();

      _device.SetCooperativeLevel(this,CooperativeLevelFlags.Normal);

 

      t.Tick+=new EventHandler(t_Tick);

      t.Interval=Interval;

      t.Enabled=true;

    }

 

Creating the DirectX surfaces.

The three DirectX surfaces, _primary, _backBuffer and _textBuffer are created. _primary covers the whole screen but we only use a portion, _backBuffer is always the same size as the control client area and _textBuffer is as large as the measured string containing the text taking into account the font size. The primary surface is provided with a clipper object that clips output to the area defined by the control surface. This method is called whenever the control size changes or the text is modified because the back-buffer and the off-screen image buffer will probably be different sizes too.

 

    protected void CreateSurfaces(Bitmap bm)

    {

      SurfaceDescription sd=new SurfaceDescription();

      //Create DirectX surfaces. First the primary surface.

      if(_primary==null)

      {

        sd.SurfaceCaps.PrimarySurface=true;

        _primary=new Surface(sd,_device);

      }

 

      //This can be called if the control changes size so we always create a new backbuffer

      if(_backBuffer!=null)

        _backBuffer.Dispose();

      //now the back buffer surface as an offscreen plain buffer built from the bitmap handed in

      sd.Clear();

      sd.Width=this.Width;

      sd.Height=this.Height;

      sd.SurfaceCaps.OffScreenPlain=true;

      _backBuffer=new Surface(sd,_device);

 

      if(_textBuffer!=null)

        _textBuffer.Dispose();

      //now the image surface from which the text is copied

      sd.Clear();

      sd.SurfaceCaps.OffScreenPlain=true;

      sd.Height=bm.Height;

      sd.Width=bm.Width;

      _textBuffer=new Surface(bm,sd,_device);

 

      //Finally, the clipper is set up to ensure the output is limited to the area of this control

      if(_primaryClipper!=null)

        _primaryClipper.Dispose();

      _primaryClipper=new Clipper(_device);

      _primaryClipper.Window=this;

      _primary.Clipper=_primaryClipper;

 

    }

 

Creating the text.

As the control properties such as Font, ForeColor, BackColor and Size change, so too does the appearance of the control. Each time one of these property change events is raised, the CreateText method makes a new offscreen surface containing all the text in the right font and colour.

    protected void CreateText()

    {

      if(this.Height==0 || this.Width==0)

        return;

      Font=new Font(Font.FontFamily,0.6f*this.Height);

      Graphics g=CreateGraphics();

      g.TextRenderingHint=TextRenderingHint.AntiAlias;

      SizeF sf=g.MeasureString(this.Text,this.Font,2048,StringFormat.GenericTypographic);

      _textWidth=(int)(0.5f+sf.Width);

      if(_textWidth==0)

        return;

 

      if(_bm!=null)

        _bm.Dispose();

      _bm=new Bitmap(_textWidth,this.Height);

      

      g=Graphics.FromImage(_bm);

      g.Clear(this.BackColor);

      g.TextRenderingHint=TextRenderingHint.AntiAlias;

      SolidBrush sb=new SolidBrush(this.ForeColor);

      g.DrawString(Text,Font,sb,0,0,StringFormat.GenericTypographic);

      sb.Dispose();

 

      _marqueeOffset=0;

 

      this.CreateSurfaces(_bm);

 

    }

 

The CreateText method also resets the offset of the marquee from the right edge of the control.

Timer tick handler

The timer drives the motion of the display. It updates the offset of the text and invalidates the display

    private void t_Tick(object sender, EventArgs e)

    {

      _marqueeOffset+=Step;

      if(_marqueeOffset>this.Width+_textWidth)

        _marqueeOffset=0;

    }

 

Painting the control

The paint routine does not use the Graphics provided at-all. It drives the assembly of the text into the correct position in the back-buffer along with the placement of the background colour and then copies the assembled back-buffer to the primary surface.

    protected override void OnPaint(PaintEventArgs e)

    {

      //the back bufffer is copied to the primary surface.

      if(_primary==null || this._backBuffer==null)

        return;

 

      //define the destination rectangle

      Rectangle dest=new Rectangle(this.ClientSize.Width-_marqueeOffset,0,this._textWidth,this.Height);

      dest.Intersect(new Rectangle(0,0,this.Width,this.Height));

 

      //The source is a moving window onto the pre-drawn bitmap.

      //The marquee starts from the right and scrolls left

      //so the offset is used to calculate the amount of text seen

 

      Rectangle src=new Rectangle(0,0,Math.Min(dest.Width,_textWidth),this.Height);

      if(_marqueeOffset>this.Width)

        src.Offset(this._marqueeOffset-this.Width,0);

      src.Intersect(new Rectangle(0,0,_textWidth,this.Height));

 

      _backBuffer.ColorFill(this.BackColor);

 

      if(src.Width!=0 && dest.Width!=0)

      {

          _backBuffer.Draw(dest,_textBuffer,src,DrawFlags.DoNotWait);

         _primary.Draw(RectangleToScreen(this.ClientRectangle), _backBuffer, this.ClientRectangle, DrawFlags.DoNotWait);

      }

 

      base.OnPaint(e);

    }

 

Miscellaneous notes.

There are properties Interval and Step that enable you to modify the speed of the display and each of the important On<propertychanged> methods are overridden to re-create the text if the size, font or colours change.

The code for the complete control is listed here

Summary

Once built, the DXMarquee control was dragged onto a form where it shows-off it's scrolling even in design time. The scrolling is quick and smooth but most importantly, in comparison to an equivalent control created using GDI+ and standard GDI+ blitting techniques, the processor usage is miniscule. On my machine a Dell 650mHz laptop, the processor loading was in the region of a percent or two. As a test I created a form with ten DXMarquee controls all running at a good speed and they all ran happily and smoothly with only a 10-12% CPU usage. This was in contrast to the GDI+ version which loaded the CPU 100% and several of the controls ground to a halt entirely.

DirectX can be successfully used in a Windows Forms control to enhance the user interface experience greatly by providing fast, smooth graphics.


Return to the main index.

Copyright © Bob Powell 2003-2009. All rights reserved