Reputation: 5760
I'm currently working on a project where I have to make use of GDI+ (System.Drawing
). I am trying to make a multi-purpose-meter (a clock-like wheel) with dynamic neeldes on it.
I do not want to draw the background image (with all of the marks) every frame. The way I have solved this right now is by setting it as the background image of the graphical "container", which is a panel (please keep this in mind, I probably have to do this differently).
So then I draw the needles. The needles are currently updated 25 times per second (I might optimize this so that it only updates when necessary). The problem is however, that I have to clear the buffer before starting with a new frame, causing the background image to get covered by the buffer's background color. Obviously I don't want this.
I have tried setting the background color of the buffer to Color.Transparent
, but instead it gives the buffer a color according to the parent's transparency key (in my case it turns out black).
Here is my current code used to draw the needles (in the Wheel
class):
/// <summary>
/// Draws the wheel and the needles.
/// </summary>
public void Draw()
{
Graphics.Clear(Color.Transparent);
// Draws all needles, works fine
foreach (Needle Ndl in Needles)
{
Ndl.Draw(ref this.Graphics);
}
// Draws the little circle in the center of the wheel. Looks only.
Graphics.FillEllipse(new Pen(Color.White).Brush, new Rectangle(120, 120, 16, 16));
}
Yes, I have followed the Stack Overflow rules, I did my homework. Other people solved it by just drawing the image every frame, using BitBlt, which is way to advanced for my brains to understand.
I am very open-minded about this, so if you think I should use BitBlt anyway, and you know where I can find some comperhensive in-depth examples about that, I will definitely do it with BitBlt. Of course it would be even better if there are solutions without using BitBlt or if it doesn't make sense to use it here anyway.
Is it possible to make the buffer's background transparent or should I take a completely different approach?
Note: I preferable do not want use XNA, since this application will primarily be used for maintenance purposes on simple/old computers, so I don't want to have anything to do with redistributables.
Upvotes: 4
Views: 1478
Reputation: 4093
You could put your base image in a PictureBox. Then your code can handle the Paint event of the PictureBox to draw your needles. That way you don't have to worry about transparency or the slow operation of Graphics.Clear(): your code simply needs to calculate the positions of the needles.
As a quick test, I wrote a little Forms app that creates a clock with an hour hand (red), a second hand (greed), and a millisecond hand (blue). The minute hand gets no respect, and hence is not shown. The background is an image of a black rounded rectangle created in GIMP.
To simulate your environment, I added a timer with an interval of 10 milliseconds (100 cycles/second). When the event Timer.Tick is handled, a repaint of the PictureBox is forced.
I hand edited the code to leave only the code for the millisecond hand:
public Form1()
{
InitializeComponent();
pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
Center = new PointF((float)pBox1.Width / 2, (float)pBox1.Height / 2);
MillisecondLength = (float)pBox1.Width / 4; //length of millisecond hand
timer1.Interval = 10;
timer1.Enabled = true;
timer1.Tick += new EventHandler(timer1_Tick);
}
PointF Center;
float MillisecondLength;
Pen MillisecondsPen = new Pen(Brushes.Blue, (float)3);
double HALF_PI = Math.PI / 2;
double TWO_PI = 2 * Math.PI;
double millisecondHandAngle(DateTime dt)
{
//12 o'clock is at the top! (- PI / 2)
return TWO_PI * dt.Millisecond / 1000 - HALF_PI;
}
void pBox1_Paint(object sender, PaintEventArgs e)
{
DateTime dt = DateTime.Now;
double mangle = millisecondHandAngle(dt);
SizeF millisecondOffset = new SizeF(
(float)(MillisecondLength * Math.Cos(mangle)),
(float)(MillisecondLength * Math.Sin(mangle))
);
PointF endMillisecond = PointF.Add(Center, millisecondOffset);
e.Graphics.DrawLine(MillisecondsPen, Center, endMillisecond);
}
void timer1_Tick(object sender, EventArgs e)
{
pBox1.Invalidate();
}
Although it's not shown in that code, I added a System.Diagnostics.Stopwatch and a counter variable to calculate the actual frame rate, which on my older laptop peaked at about 64 frames/second. Even if you set the timer interval very small, there's a max repaint rate.
Upvotes: 1