Blue Theme Orange Theme Green Theme Red Theme
 
Home | Forums | Videos | Photos | Blogs | E-Books | Interviews | Jobs | Beginners | Training
 | Consulting  
Submit an Article 
 Login Close
User Id:
Password:
 
Forgot Password
Forgot Username
Why Register
 Jump to
Skip Navigation Links
TechnologyExpand Technology
WebsiteExpand Website
 Resources  
Close
 Our Network  
Close
Search :       Advanced Search »
Home » Silverlight » Tutorial: 2D Game Development in Silverlight

Tutorial: 2D Game Development in Silverlight

This article describes how to create a simple arcade game in Silverlight. The article describes how to implement a game loop, render images and shapes, handling keyboard events, and doing simple collision detection

Author Rank:
Technologies: Database, Silverlight, SQL Server 2005, Web Services, WPF, XLINQ,Visual C# .NET
Total downloads : 55
Total page views :  7599
Rating :
 4.75/5
This article has been rated :  4 times
   Print Read/Post comments Post a comment  Rate  
   Email to a friend  Bookmark  Similar Articles  Author's other articles  
 
ArticleAd
Become a Sponsor




Figure 1 - Eat Dots Game in Silverlight 2.0

Introduction

In the previous article we got you started setting up Silverlight and creating a simple budget application.  In this article we'll talk about how to take advantage of Silverlight for creating games on the web.  This  unsophisticated game, called the Silverlight Stone Eater, uses a pacman-like character to eat dots placed randomly around the screen.  The player can move the pacman by pressing down on one of the four arrow keys. Each dot is worth a certain amount of points as follows.

Bronze Dot = 10 pts

Silver Dot  = 150 pts

Gold Dot = 350 pts

Red Dot (Bomb) = -1000 pts

On each level (easy, medium, hard)  the player has a given amount of time to eat as many dots as possible (avoiding the red dots).  The game ends when the timer times out.  On the higher skill level the player is faster and harder to control.  Also on the higher skill level, you have less time before the clock runs out.

Design

The game was designed using object-oriented techniques encapsulating much of the drawing into classes.  The Page class holds a Player, a Board, a ScoreBoard, and a GameTimer class.  The Board handles the layout and collision detection of all the stonesThe Stone class handles the translation and the collision detection of each stone on the board.  The ScoreBoard encapsulates displaying and updating the score from each Stone, the HighScore class persists the highest score for the user,  and the GameTimer tracks the time from the game engine.  The game executes the the game engine by using a trick with the Storyboard class built into Silverlight.  The Player class handles all player movement of the player on the page.

 

Figure 2 - Design of Silverlight Game in UML Class Diagram

The Code

In most graphic games, you need the following elements:   the rendered graphics, input detection, collision detection, and a game loop.  As we mentioned, Silverlight's game loop is handled with a trick using a StoryBoard which conveniently provides us a place to update our images shown in Listing 1.  Every time the game loop hits the completed event of the StoryBoard,  the event handler calls Begin() again on the loop to force it to trigger the completed event forever.  Inside the GameLoop_Completed event handler is where we can do all our game updates and check for game collisions.

Listing 1 - Using the Storyboard as a Game Loop

Storyboard _gameLoop;


public void CreateGameLoop()
{

//  create the game loop and add it to the resources on the page
_gameLoop = new Storyboard();
_gameLoop.SetValue(FrameworkElement.NameProperty, "gameloop");
this.Resources.Add("gameloop", _gameLoop);

//  wire up the Completed event
_gameLoop.Completed += new EventHandler(gameLoop_Completed);

//  kick off the game loop
_gameLoop.Begin();
}

void gameLoop_Completed(object sender, EventArgs e)
{
 
// do your game loop processing here
 
_gameLoop.Begin();
 
if (_started)
   {
    
// update man movement
    
UpdatePlayerMovement();
    ...
    }

Handling Input in Silverlight

Silverlight gives us convenient event handling for mouse and keyboard input similar to Windows Forms.  For the keyboard we hook into the KeyDown event on the page.  When an arrow key is pressed, we can handle it in the Page_KeyDown event handler.  Depending upon the arrow key pressed, we'll set the appropriate delta movement for the player.  When the game loop is reentered, it will apply the offset to the player position.

Listing 2 - Handling Keyboard Input

int _speed = 3;

void CreateKeyPressHandlers()
 {
  
// wire up the key down event
  
this.KeyDown += new KeyEventHandler(Page_KeyDown);
 }

void Page_KeyDown(object sender, KeyEventArgs e)
{
 
// handle player movement based upon the key pressed
 
switch (e.Key)
   {
        
case Key.Up:
          
_player.OffsetX = 0;
          
_player.OffsetY = -1;
          
break;
        
case Key.Down:
           
_player.OffsetX = 0;
           
_player.OffsetY = 1;
            
break;
        
case Key.Left:
           
_player.OffsetX = -1;
           
_player.OffsetY = 0;
        
break;
        
case Key.Right:
           
_player.OffsetX = 1;
          
_player.OffsetY = 0;
           
break;
        
default:
           
_player.OffsetX = 0;
           
_player.OffsetY = 0;
            
break;
        }

// take into account the speed of the player
           
_player.OffsetX *= _speed;
          
_player.OffsetY *= _speed;
}

Drawing the Game

The game consists of images, drawn shapes, and controls.  The player is a set of 2 images, one with the player's mouth open, and one with the player's mouth closed.  By toggling the drawing of these 2 images, we can give the appearance of the player opening and closing its mouth.  The stones are simply ellipses drawn and filled with a color.  The ScoreBoard and the Timer are both controls.  A lot of our initial game components can be declared and constructed directly in the XAML.  For example the player images are created in the following XAML lines:

Listing 3 - XAML Markup defining the player Images and Transforms

<!-- image pacman mouth open -->

<Image x:Name="_pacman" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Width="20" Height="20" Source="pacman.jpg" Canvas.ZIndex="2" >
  <
Image.RenderTransform>
      <
TranslateTransform x:Name="_translateTransform1" X="0" Y="0" />
  </
Image.RenderTransform>
</
Image>

<!-- image pacman mouth closed -->

<Image x:Name="_pacman2" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Width="20" Height="20" Source="pacman2.jpg" Canvas.ZIndex="2"
     Visibility
="Collapsed">
   <
Image.RenderTransform>
           <
TranslateTransform x:Name="_translateTransform2" X="0" Y="0" />
   </
Image.RenderTransform>
</
Image>

 

All of the Image information, transform information and variable names are defined in here in Listing3.  Notice that we can even initially hide the second image by setting it's Visibility to Collapsed.  The transforms allow us to translate the pacman position on the screen in the X-Y coordinate system.  We can use the _translateTransform1 and _translateTransform2 objects directly in our code to update the players position as shown in listing 4:

Listing 4 - Updating the translation and toggling of the player images

public void UpdatePlayerPosition()
 {
  
_translateTransform1.X += _offsetX;
  
_translateTransform1.Y += _offsetY;
  
_translateTransform2.X += _offsetX;
  
_translateTransform2.Y += _offsetY;
  
   
_throttleCount++;  // throtte the toggling of the image by a factor of 30x, otherwise it opens and closes its mouth too fast!!

if (_throttleCount % 30 == 0)
  {
   
if (_toggleImage)
     {
         
_playerImage.Visibility = Visibility.Visible;   //  show mouth open
         
_playerImage2.Visibility = Visibility.Collapsed;
      }
    
else
      {
          
_playerImage.Visibility = Visibility.Collapsed;   //  show mouth closed
           
_playerImage2.Visibility = Visibility.Visible;
       }

          _toggleImage = !_toggleImage;
     }

     // don't allow position outside boundaries
        
RestrainCoordinates();
 }

private void RestrainCoordinates()
 {
  
if (_translateTransform1.X < (-_boundsWidth/2 + _playerImage.Width/2))
       
_translateTransform1.X = -_boundsWidth/2 + _playerImage.Width/2;
   
if (_translateTransform1.Y < -_boundsHeight/2 + _playerImage.Height)
           
_translateTransform1.Y = -_boundsHeight/2 + _playerImage.Height;

    if (_translateTransform1.X > _boundsWidth/2)
          
_translateTransform1.X = _boundsWidth/2;
   
if (_translateTransform1.Y > _boundsHeight/2)
         
_translateTransform1.Y = _boundsHeight/2;
}

Stones are created dynamically, so they don't show up in the XAML markup.  The creation of the stone is shown in the Stone constructor in listing 5.  The stone is an ellipse, so we construct an ellipse and fill in its properties to create the stone.  Here, in listing 5 the translation object is also created dynamically and assigned to the shapes RenderTransform property:

Listing 5 - Constructing the Silverlight Stone Object Dynamically

public class Stone

{

Ellipse _shape = new Ellipse();
TransformGroup _transformGroup = new TransformGroup();
TranslateTransform _translation = new TranslateTransform();

public Stone(int x, int y, Color fill)
  {
   
_translation.X = x;    //  set the position of the stone
  
_translation.Y = y;
   
_shape.Fill = new SolidColorBrush(fill);   //  fill the stone with the specified color

   
_shape.Width = 7;   //  set the stones size (diameter)
   
_shape.Height = 7;

_transformGroup.Children.Add(_translation);   //  add the translation transform to the transform group

_shape.RenderTransform = _transformGroup;   // add the transform group to the ellipse's RenderTransform

}...

The variety of stones (GoldStone, SilverStone, BronzeStone, BombStone) are created as subclasses of the Stone class.  We can use inheritance and polymorphism to extract the score from a collection of these stones since each Stone subclass has it's own Score method. Listing 6 shows a typical Stone subclass.  Note that a SilverStone simply needs to pass its color into the Stone base class to construct itself

Listing 6 - The SilverStone class

namespace SilverLightGame.Stones
 {
   
public class SilverStone : Stone
     {
       
public SilverStone(int x, int y) : base (x, y, Colors.LightGray)
          {
          }

public override int Score()
  {
     
return 100;   // eating a silver stone gives you 100 points
   }

  }

}

Stones are constructed and tracked by the Board class.  In the constructor of the Board class, each stone's position is randomly generated to fit inside the 1st cell of the grid. The stone is also added to an overall stone collection used to track stone collisions.

Listing 7 - Constructing all the stones on the Board

public Board (int width, int height, Grid containingGrid)
 {
  
Random generator = new Random((int)DateTime.Now.Ticks);
  
int x = 0;
  
int y = 0;

 

    // add gold stones
   
for (int i = 0; i < 10; i++)
      {
       
// generate location for stone
       
GenerateRandomStoneLocation(width, height, generator, out x, out y);
       
     
// construct gold stone at that location
      
Stone nextStone = new GoldStone(x, y);
 
      
// place stone inside of the grid
     
PlaceStoneIntoCellOfGrid(containingGrid, nextStone);
     }

      // add silver stones
      
for (int i = 0; i < 30; i++)
        {
          
GenerateRandomStoneLocation(width, height, generator, out x, out y);
          
Stone nextStone = new SilverStone(x, y);
          
PlaceStoneIntoCellOfGrid(containingGrid, nextStone);
        }

        // add bronze stones
       
for (int i = 0; i < 60; i++)
        {
           
GenerateRandomStoneLocation(width, height, generator, out x, out y);
           
Stone nextStone = new BronzeStone(x, y);
          
PlaceStoneIntoCellOfGrid(containingGrid, nextStone);
        }

      // add bomb stones
       
for (int i = 0; i < 10; i++)
        {
          
GenerateRandomStoneLocation(width, height, generator, out x, out y);
          
Stone nextStone = new BombStone(x, y);
         
PlaceStoneIntoCellOfGrid(containingGrid, nextStone);
      }

}

private void PlaceStoneIntoCellOfGrid(Grid containingGrid, Stone nextStone)
 {
   nextStone
.Shape.SetValue(Grid.RowProperty, 0);  //  add stone shape to row 0, column 0, spanning 2 columns
   nextStone
.Shape.SetValue(Grid.ColumnProperty, 0);
   nextStone
.Shape.SetValue(Grid.ColumnSpanProperty, 2);
   containingGrid
.Children.Add(nextStone.Shape);  //  add the stone to the LayoutRoot (grid)
   dots
.Add(nextStone);  //  add the stone to the collection for detecting collisions
}

// Randomly generate the stones location based on the width and height of the grid cell
private
static void GenerateRandomStoneLocation(int width, int height, Random generator, out int x, out int y)
 {
    x
= generator.Next(width / 10) * 10 - (width - 10) / 2;
    y
= generator.Next(height / 10) * 10 - (height - 10) / 2;
  }

Detecting Collisions

Collisions are detected by seeing if the center point of the stone is inside the player bounding rectangle.  The collides method in listing 8 takes advantage of the Silverlight Rect  class (uh oh, we are going back to abbreviations again, hope we are not slipping backwards to the days of cryptic MFC coding).  The Rect class (as you may have guessed) is a class that allows us to do calculations on a rectangle.  I assume that Microsoft called it Rect instead of Rectangle so as not to step on the Rectangle Shape class. However, I suggest that a longer more descriptive name for the Rectangle calculation class would be better than a short ambiguous name like Rect (which could be short for other things).   The code for detecting the stone inside the player is shown in listing 8:

Listing 8 - Testing for Collision between Player and Stone

public bool Collides(Rect boundsPlayer)
{

//  Using the infamous Rect class to check if the stone is contained in the player

   Rect boundsStone = new Rect(_translation.X + _shape.Width/2, _translation.Y + _shape.Height/2, _shape.Width/2, _shape.Height/2);
   return (
boundsPlayer.Contains(new Point(boundsStone.X, boundsStone.Y)));
}

The collision of each stone is tested for collision in the DoCollision method of the Board class.  First the method finds all the stones that collide, and then removes those stones from the collection of viable stones as illustrated in Listing 9.  This is one technique for removing items from a collection you are iterating through.  Another technique would have been to go through the list backwards in a for loop (instead of a foreach loop) and removed them in place.Iterating backwards through the list prevents you from skipping indices.

Listing 9 - Detecting the Collision of Each Stone

public int DoCollision(Player pacman)
 {
  
int scoreAddition = 0;
  
List<Stone> removalList = new List<Stone>();  //   list of stones that have collided with the player
 
  foreach
(Stone stone in dots)
   {
     
if (stone.Collides(pacman.GetBounds()))
        {
             
// add the points gained (each type of stone knows how to score itself through polymorphism)
            
scoreAddition += stone.Score();

              
// add  the stone to the removal list
             
removalList.Add(stone);
      }

}

 // remove all the stones that have collided
 
foreach (Stone stone in removalList)
   {
        ((
Grid)stone.Shape.Parent).Children.Remove(stone.Shape);
      
dots.Remove(stone);
  }

return scoreAddition; // at least one collision happened

}

Game Timer

The GameTimer class inherits from the Silverlight TextBox control.  Each time through the Storyboard Completed event, we update the timer simply by decrementing it.  The GameTimer knows how to display itself through its Display method which is called whenever we decrement the timer.  It seems like the timer's Decrement method is entered every 1/60 second, so we display time based on this assumption.

Listing 10 - Displaying and Decrementing the GameTimer

public void Decrement()
 {

 //  subtract 1 form the timer value and display it
  
if (_value > 0)
    {
      
_value -= 1;
      
Display();
    }
}

public bool IsTimerFinished()
 {
      
return (_value == 0);
  }

private void Display()
  {
    
// calculate the display time based on a loop increment of 1/60 second
    
Text = String.Format("{0:00}:{1:00}:{2:00}", _value/3600, (_value % 3600)/60, _value % 60);
  }

Saving the High Score

Silverlight introduces the idea of saving data from  the client application in a safe and secure manner.   Silverlight allows programmers to write code into their Silverlight application that will save data in isolated storage.  Data created using isolated storage is allowed read and write access as defined by the client's security policy.  I found a pretty good blog explaining this in more detail.  We used isolated storage to persist the high score for the eater game.  To track high score, we created a HighScore class that has  methods WriteOutHighScore and ReadHighScore.  Listing 11 shows the two methods in action.  The isolated storage file for the user is obtained from the IsolatedStorageFile class.  A stream is then obtained with a particular file name. For the case of reading the file, the application uses the StreamReader to read the isolated file stream the same way you would a regular FileStream.  For writing the isolated storage file, we use the StreamWriter class on the isolated storage stream.

Listing 11 - Using isolated storage to read and write the high score

/// <summary>
///
Persist the high score
/// </summary>

public void WriteOutHighScore()
 {
  
// get the isolated storage object for the use
  
using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
    {
           
// get the stream associated with the user and a unique filename
          
using (IsolatedStorageFileStream isoStream =
                  
new IsolatedStorageFileStream(m_fileName, FileMode.Create, isoFile))
        {
           
// use .net's built in stream writer to write to the stream
        
using (StreamWriter sw = new StreamWriter(isoStream))
            {
                 sw.WriteLine(_highestScore);
            }
      }
  }

}

/// <summary>
///
read the high score from isolated storage
/// </summary>
///
<returns></returns>

public string ReadHighScore()
 {
  
// get the users isolated storage object
  
using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
    {
        
// get the stream associated with the isolated stored and a unique filename
        
using (IsolatedStorageFileStream isoStream =
                  
new IsolatedStorageFileStream(m_fileName, FileMode.OpenOrCreate, isoFile))
             {
                
using (StreamReader sr = new StreamReader(isoStream))
                     {
                        
string highScore = sr.ReadLine();
                        
if (highScore != null) // file may be empty
                              {
                                     _highestScore = highScore;
                              }
                   

                        return
_highestScore;
                  }
            }
     }

}

Animating Objects in the Game

One fun thing to do inside a 2D game is to animate certain objects with some visual effect.  In the eater game, we added an animation to the game to cause the gold stones to look like they are pulsating.  By altering the Width and Height properties of the ellipse shape, we can grow and shrink the gold stones throughout their lifetimes, giving them the pulsing effect.  Animation is carried out by creating a storyboard for each stone and adding two animations to the stone's storyboard: one animation to pulse the width and one animation to pulse the height.  Since the Height and Width properties of the ellipse shape are both doubles, we can use the DoubleAnimation class to pulse the two properties.  Listing 12 shows the animation code for pulsing a stone.  Note that the storyboard and animations are all created dynamically, rather than through the XAML file.

Listing 12 - Pulsing the Gold Stones in the Eater Game

private void AnimateStone()
 {
   _stoneFlash =
new Storyboard();  // create a storyboard for the stone

  
// setup width animation to grow and shrink forever
 
DoubleAnimation animation = new DoubleAnimation();
    animation.From = 7.0d; 
//  the smallest stone width
    animation.To = 14.0d;   
// the largest stone width 

  Duration duration = new Duration(new TimeSpan(0, 0, 1));  // pulse in  1 second intervals

  animation.AutoReverse =
true; // allows to grow, then shrink the width
  animation.RepeatBehavior =
RepeatBehavior.Forever; // we want this to repeat
  animation.Duration = duration;

  
Storyboard.SetTarget(animation, _shape);  //  hook the  animation to the stone ellipse shape
   Storyboard
.SetTargetProperty(animation, new PropertyPath("Width"));  //   hook the animation to the shape's Width property

  
// setup height animation to grow and shrink forever, and repeat the above procedure for the height animation
 
DoubleAnimation animation2 = new DoubleAnimation();
  animation2.From = 7.0d;
   animation2.To = 14.0d;
 
Duration duration2 = new Duration(new TimeSpan(0, 0, 0, 1));
  animation2.AutoReverse =
true;
  animation2.RepeatBehavior =
RepeatBehavior.Forever;
  animation2.Duration = duration2;
 

  Storyboard
.SetTarget(animation2, _shape);
 
Storyboard.SetTargetProperty(animation2, new PropertyPath("Height"));

// add the width and height animations to the storyboard
    _stoneFlash.Children.Add(animation);
   _stoneFlash.Children.Add(animation2);

   // start the animation of the stone
 
_stoneFlash.Begin();

}

Game Loop

Once we have created all the elements, we can exercise them in our game loop.  The game loop is the heartbeat of the application and is in charge of keeping the game alive.  Each time the loop executes, it updates all the elements of the game.  Listing 13 shows the full game loop along with all the updates to the different game elements performed at the high level of the game's individual objects.  The game loop is repeatedly called within itself through the game loop's Begin method which retriggers the storyboard.

Listing 13 - Exercising the Game Loop

void gameLoop_Completed(object sender, EventArgs e)
 {
    
// do your game loop processing here
    
_gameLoop.Begin();
   
      
// if the start button was pressed, update the game elements 
     
if (_started)
       {
          
// update man movement
        
UpdatePlayerMovement();

        
// test for collision
        
int scoreChange = TestCollision();

        // update Score
        
UpdateScore(scoreChange);

          // play sound
        
if (scoreChange > 0)
            _blip.Play();

         // update time
       
UpdateTimer();

        // check to see if there is any time left
       
if (TimeHasRunOut())
         {
           
_started = false;
           
_gameOver.Show();
        }
      }
}

private bool TimeHasRunOut()
  {
        
return _timer.IsTimerFinished();
  }

 private void UpdatePlayerMovement()
     {
        
_player.UpdatePlayerPosition();
      }

private void UpdateTimer()
      {
        
_timer.Decrement();
      }

void UpdateScore(int scoreChange)
{
   
_score.AddScore(scoreChange);
}

private int TestCollision()
  {
       
return _board.DoCollision(_player);
  }

Conclusion

This was a simple demonstration on how you can easily develop a 2D game using Silverlight and share it with all your friends on the web.  This game could use a bit of improvement to make it more fun to play.  It would be nice if there was a Level class, so that if you clear all the dots on the board before time runs out, you get to go to the next level.  It would also be nice to have a HighScoreTracker to track the score you need to beat.  Also it would be fun if the red bombs moved around the screen, and you had to dodge them.  Finally, I thought it would be fun to add some special stones that allowed you to eat bombs for a small duration.  Look for these enhancements in the next Silverlight Update of the Silverlight Stone Eater.  In any case, enjoy the game and watch out for those red dots!


Login to add your contents and source code to this article
 [Top] Rate this article
 About the author
 
Mike Gold
Michael Gold is President of Microgold Software Inc., makers of the WithClass UML Tool. His company is a Microsoft VBA Partner and Borland Partner. Mike is a Microsoft MVP and founding member of C# Corner. He has a BSEE and MEng EE from Cornell University and has consulted for Chase Manhattan Bank, JP Morgan, Merrill Lynch, and Charles Schwab. Currently he is a senior developer at Finisar Corp. He has been involved in several .NET book projects, and is currently working on a book for using .NET with embedded systems. He can be reached at mike@c-sharpcorner.com
Looking for C# Consulting?
C# Consulting is founded in 2002 by the founders of C# Corner. Unlike a traditional consulting company, our consultants are well-known experts in .NET and many of them are MVPs, authors, and trainers. We specialize in Microsoft .NET development and utilize Agile Development and Extreme Programming practices to provide fast pace quick turnaround results. Our software development model is a mix of Agile Development, traditional SDLC, and Waterfall models.
Click here to learn more about C# Consulting.
 
Introducing MaxV - one click. infinite control. Hyper-V Hosting from MaximumASP.
Finally – a virtual platform that delivers next-generation Windows Server 2008 Hyper-V virtualization technology from a managed hosting partner you can truly depend on. Visit www.maximumasp.com/max for a FREE 30 day trial. Hurry offer ends soon. Climb aboard the MaxV platform and take advantage of High Availability, Intelligent Monitoring, Recurrent Backups, and Scalability – with no hassle or hidden fees. As a managed hosting partner focused solely on Microsoft technologies since 2000, MaximumASP is uniquely qualified to provide the superior support that our business is built on. Unparalleled expertise with Microsoft technologies lead to working directly with Microsoft as first to offer IIS 7 and SQL 2008 betas in a hosted environment; partnering in the Go Live Program for Hyper-V; and product co-launches built on WS 2008 with Hyper-V technology.
Dynamic PDF
ceTE software specializes in components for dynamic PDF generation and manipulation. The DynamicPDF™ product line allows you to dynamically generate PDF documents, merge PDF documents and new content to existing PDF documents from within your applications.
Boost the performance of your .NET applications
“ANTS Profiler took us straight to the specific areas of our code which were the cause of our performance issues." Terry Phillips, Sr. Developer, Harley-Davidson Dealer Systems. Download your free trial of ANTS Profiler.
Go.NET
Build custom interactive diagrams, network, workflow editors, flowcharts, or software design tools. Includes many predefined kinds of nodes, links, and basic shapes. Supports layers, scrolling, zooming, selection, drag-and-drop, clipboard, in-place editing, tooltips, grids, printing, overview window, palette. 100% implemented in C# as a managed .NET Control. Document/View/Tool architecture with many properties&events. Optional automatic layout.
Dundas Software
Dundas Chart for .NET is the most advanced .NET charting package available today.  With an extremely complete feature set, elegant architecture and easy implementation, Dundas Chart can quickly add advanced Charting functionality to enhance and transform ASP.NET and Windows Forms applications.  Whether you are implementing charting into internal projects, or building applications for clients, Dundas Chart offers advanced technology and advanced results to get the most out of data.
 
   Print Read/Post comments Post a comment  Rate  
   Email to a friend  Bookmark  Similar Articles  Author's other articles  
 
 Post a Feedback, Comment, or Question about this article
Subject:  
Comment:  
ArticleAd
Become a Sponsor
Latest Comments:
Subject Posted By Posted On

 Hosted by MaximumASP  |  Found a broken link?  |  Contact Us  |  Terms & conditions  |  Privacy Policy  |  Site Map  |  Suggest an Idea  |  Media Kit
Current Version: 5.2009.6.2
 © 1999 - 2009  Mindcracker LLC. All Rights Reserved