C# Programming

ImageMaking 3D "like" CPU Meter

開発環境: Visual Studio 2003 

Index

  1. Index
  2. Introduction
  3. References
  4. Making 3D picture
  5. Implementing CPUMeterPanel
  6. Implementing the Main Form
  7. Source Code

2.Introduction

Please take a look at following screen shot! It looks like 3D application using Direct X technology, but it's GDI+ based normal Windows.Forms application with 3D like background picture!  Some of you might be interested in 3D application. This article describes how to create 3D "Like" 2D application.

Image

3.References

  1. Blender.org
  2. Blender Documentation
  3. Blender Documentation Japanese/日本語版

4.Making 3D picture


Before you jump into this, please note "3D Modeling is really time consuming"!! It took almost two weeks for me to create the CPU Meter background image! If you don't have enough time, I'd strongly recommend you to stay out of the 3D modeling!!

Ok, now you are pretty sure to go one step forward:-)

There are many 3D modeling software, but most of them are pretty expensive. If you have tons of money, buy what ever you want and take a training course. But, fortunately, there is greate free 3D modeling software, "Blender". I used "Blender" to make this application, but "Blender" is designed as multi-platform software, and it has very unique GUI and operations. If you know someone who is familiar with "Blender", please do not hesitate to ask him how to use "Blender"! It's so unusual!

  1. Go to Blender.Org and install the latest Blender.
  2. Go to Blender Documentation and read the manual!
  3. Enjoy 3D modeling with Blender.

Now I'm assuming that you can create following 3D image with Blender.

Download 3DCPUBlender Data
3DCPUMeter.blend

When you download "3DCPUMeter.blend" and double click, following "Blender" will come up.

Image

Now, press F12 key to render current frame. Then, following Render image will be created.

Image

Press F3 key to save image as "3DCPU.png".
Please trim black background and save.

Now, finally you can get following background image!

Image

5.Implementing CPUMeterPanel

Now it's time to implement CPU Meter.  There are two steps. First, we create a CPUMeterPanel control class derived from "PictureBox" control.

You don't want the CPU meter heavy enough to consume your CPU power. That's why I'm making the CPUMeterPanel control class derived from the PictureBox control. Because the PictureBox control has double buffering capability and it's very efficient to draw graphics. Also it can handle images easily.

  1. Create a new C# Windows Forms project.
  2. Add new project as "CPUMeterPanel".
  3. Delete form1.cs in the project.
  4. Add new class as "CPUMeterPanel.cs" to the "CPUMeterPanel" project.
  5. Copy and paste the source code below.
    If you are familiar with C# and GDI+ and "Inheritance", it's easy to understand the code.
  6. Now build the solution.
  7. Go to the ToolBox and right click to select customize ToolBox. Then, following dialog will come up. Add compiled CPUMeterPanel.dll to the ToolBox.
    Image
  8. Now you will see the CPUMeterPanel control on the ToolBox as follows.
    Image

Acknowledgements

Please note that the concept of this code comes from Mr. Kawabata, who is the leader of the INETA Japan. All thanks should go to Mr.Kawabata!!

CPUMeterPanel.cs

   1:  using System;
   2:  using System.Drawing;
   3:  using System.Drawing.Drawing2D;
   4:  using System.ComponentModel;
   5:   
   6:  namespace Uchukamen.CPUMeter
   7:  {
   8:      /// <summary>
   9:      /// </summary>
  10:      public class CPUMeterPanel: System.Windows.Forms.PictureBox
  11:      {
  12:          private System.ComponentModel.IContainer components;
  13:   
  14:          public CPUMeterPanel()
  15:          {
  16:              // 
  17:              //
  18:              InitializeComponent();
  19:   
  20:              // Initialize Brush and Image
  21:              handBrush = new SolidBrush(handColor);
  22:              LoadImage();
  23:          }
  24:   
  25:          /// <summary>
  26:          /// </summary>
  27:          protected override void Dispose( bool disposing )
  28:          {
  29:              if( disposing )
  30:              {
  31:                  if (components != null) 
  32:                  {
  33:                      components.Dispose();
  34:                  }
  35:              }
  36:              base.Dispose( disposing );
  37:          }
  38:   
  39:          #region Windows 
  40:          /// <summary>
  41:          /// </summary>
  42:          private void InitializeComponent()
  43:          {
  44:              this.performanceCounter1 = new System.Diagnostics.PerformanceCounter();
  45:              this.folderBrowserDialog1 = new System.Windows.Forms.FolderBrowserDialog();
  46:              ((System.ComponentModel.ISupportInitialize)(this.performanceCounter1)).BeginInit();
  47:              // 
  48:              // performanceCounter1
  49:              // 
  50:              this.performanceCounter1.CategoryName = &quot;Processor&quot;;
  51:              this.performanceCounter1.CounterName = &quot;% Processor Time&quot;;
  52:              this.performanceCounter1.InstanceName = &quot;_Total&quot;;
  53:              // 
  54:              // CPUMeterPanel
  55:              // 
  56:              this.Resize += new System.EventHandler(this.CPUMeterPanel_Resize);
  57:              ((System.ComponentModel.ISupportInitialize)(this.performanceCounter1)).EndInit();
  58:   
  59:          }
  60:          #endregion
  61:   
  62:          #region Properties
  63:          private Color handColor = Color.White;
  64:          [Category( &quot;Meter&quot; )]
  65:          [Description( &quot;Hand Color&quot; )]
  66:          [DefaultValue( typeof(Color), &quot;White&quot; )]
  67:          public Color HandColor
  68:          {
  69:              get {return this.handColor;}
  70:              set 
  71:              {
  72:                  handColor = value;
  73:                  handBrush = new SolidBrush(value);
  74:                  Refresh();
  75:              }
  76:          }
  77:   
  78:          private double startAngle = -125.0;
  79:          [Category( &quot;Meter&quot; )]
  80:          [Description( &quot;Start Angle&quot; )]
  81:          [DefaultValue( typeof(double), &quot;-125.0&quot; )]
  82:          public double StartAngle
  83:          {
  84:              get {return startAngle;}
  85:              set 
  86:              {
  87:                  startAngle = value;
  88:                  Refresh();
  89:              }
  90:          }
  91:   
  92:          private double endAngle = 125.0F;
  93:          [Category( &quot;Meter&quot; )]
  94:          [Description( &quot;End Angle&quot; )]
  95:          [DefaultValue( typeof(double), &quot;125.0&quot; )]
  96:          public double EndAngle
  97:          {
  98:              get {return endAngle;}
  99:              set 
 100:              {
 101:                  endAngle = value;
 102:                  Refresh();
 103:              }
 104:          }
 105:   
 106:          private Bitmap originalBitmap = null;
 107:          private System.Diagnostics.PerformanceCounter performanceCounter1;
 108:      
 109:          [Category( &quot;Meter&quot; )]
 110:          [Description( &quot;Background Bitmap&quot; )]
 111:          [DefaultValue( typeof(Bitmap), &quot;&quot; )]
 112:          public Bitmap OriginalBitmap
 113:          {
 114:              get {return originalBitmap;}
 115:              set 
 116:              {
 117:                  originalBitmap = value;
 118:                  LoadImage();
 119:              }
 120:          }
 121:   
 122:          #endregion
 123:   
 124:   
 125:          private const double pai = Math.PI;
 126:          private SolidBrush handBrush;
 127:  <summary>
 128:          /// </summary>
 129:          private int r;     // Meter Radius
 130:   
 131:          private GraphicsPath handPath;
 132:          private System.Windows.Forms.FolderBrowserDialog folderBrowserDialog1;
 133:   
 134:          // Current Meter Value
 135:          static float meterRead = 0.0F;
 136:          /// <summary>
 137:          /// Draw Hand
 138:          /// </summary>
 139:          /// <param name="g"></param>
 140:          /// <param name="currentSpeed"></param>
 141:          public void drawHand(Graphics g, float currentSpeed)
 142:          {            
 143:              double totalAngle = endAngle - startAngle;
 144:   
 145:              // Delta = Current Value - Current Meter Read
 146:              // Get the current meter closer to the current value with delta/10 step.
 147:              float delta = currentSpeed - meterRead;
 148:              float step = delta/10F;
 149:              meterRead += step;
 150:              GraphicsState gs = g.Save();
 151:              // Rotate and draw the meter hand.
 152:              g.RotateTransform((float)(startAngle + meterRead * totalAngle/100F));
 153:              if (handPath != null)
 154:                  g.FillPath(handBrush, handPath);
 155:              g.Restore(gs);
 156:          }
 157:          
 158:          /// <summary>
 159:          /// Re calculate size related values
 160:          /// </summary>
 161:          public void Recalculate(int width, int height)
 162:          {            
 163:              // Radius
 164:              r = (int)(0.6 * Math.Min(width/2, height/2));
 165:   
 166:              // Graphics Path of the meter hand
 167:              int handWidth = (int)(Math.Max(1.0F, r*0.02F));
 168:              handPath = new GraphicsPath();
 169:              Point [] p = new Point[5];
 170:              p[0] = new Point((int)(-r*0.1F), 0);
 171:              p[1] = new Point(0, -handWidth);
 172:              p[2] = new Point(r, 0);
 173:              p[3] = new Point(0, handWidth);
 174:              p[4] = new Point((int)(-r*0.1F), 0);
 175:              handPath.AddLines(p);
 176:              handPath.CloseAllFigures();
 177:          }
 178:   
 179:          public void LoadImage()
 180:          {
 181:              if (OriginalBitmap == null)
 182:                  return;
 183:              if (Width &lt;= 0 || Height &lt;= 0)
 184:                  return;
 185:              if (BackgroundImage != null)
 186:                  BackgroundImage.Dispose();
 187:              BackgroundImage = new Bitmap(OriginalBitmap, Width, Height);
 188:          }
 189:          
 190:          protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
 191:          {
 192:              float val = performanceCounter1.NextValue();
 193:              Graphics g = e.Graphics;
 194:              g.TranslateTransform(Width/2, Height/2);
 195:              g.RotateTransform(-90.0F);
 196:              g.SmoothingMode = SmoothingMode.AntiAlias;
 197:              drawHand(g, val);
 198:          }
 199:          
 200:          private void CPUMeterPanel_Resize(object sender, System.EventArgs e)
 201:          {    
 202:              Recalculate(Width, Height);
 203:              LoadImage();
 204:          }
 205:      }
 206:  }

6.Implementing the Main Form

  1. Drag and Drop the CPUMeterPanel control to the Main Form.
    You will see some properties for the CPUMeterPanel in the property window.
    Please set the 3D image created by Blender to the OriginalBitmap.
    Also set Dock property as FULL, then your Visual Designer will be like this.
    Image

  2. Drag and Drop the Timer Control to the Main Form to update the CPUMeterPanel.
  3. You have to implement some event handlers to handle timer event.
    Double click the main form and add Form1_Load event to start the timer.
    Add Form_Load event handler
    private void Form1_Load(object sender, System.EventArgs e)
    {
    	this.timer1.Enabled = true;
    }


  4. To update CPU Meter, you need refresh the CPUMeterPanel. Please add following event handler for the timer control. The timer interval would be 100ms-500ms for smooth move.
    Add Timer Tick event handler.
    private void timer1_Tick(object sender, System.EventArgs e)
    {
    	this.Refresh();
    }


  5. Also when you resize the window, the CPUMeterPanel has to be recalculated to fit the window size. Please add following resize event handler to refresh.

    Add Form_Resize event handler.
    private void Form1_Resize(object sender, System.EventArgs e)
    {
    	if(this.WindowState != FormWindowState.Minimized) 
    		this.Refresh();
    }

     
  6. Now rebuild all and run! And you can add MainMenu or whatever you need!
    Image

7.Source Code


History
2005/6/23   V1.0 for .NET Framework V1.1

All package including Blender data
3DCPUMeter.zip