On Sun, 02 Dec 2007 21:39:26 -0800, Peter Duniho
<Np*********@nnowslpianmk.comwrote:
[...]
You'll need to be more specific about the exact behavior you want,
because I can't infer it from the code you posted. It _seems_ like you
want some number of randomly colored circles drawn in three specific
locations on your form at random intervals. Even from your description,
I can't tell whether each circle should change color three times, or you
only want three total color changes, once per circle, or you want three
total color changes, with the actual circle changed to also be selected
randomly.
For what it's worth, I made a guess and wrote a short demo application
that does what I _think_ you're trying to do. I've copied it below.
You'll need to create an empty project, add a new code file to the
project, paste this code into that file, and add references to System,
System.Drawing, and System.Windows.Forms. Once you've done all that, it
should compile and run directly.
The basic idea and the implementation is very simple: in the Circle class,
I create a timer that raises an event when it expires, letting me change
the color of the circle. The Circle class exposes an event that the code
using it can use to know when that happens.
Then the code using it, which in this case is a form class, subscribes to
the event when it creates each circle (it makes a list of three of them)..
In its OnPaint() method, it just draws the circles; a simple enumeration
and calling the Circle.Draw() method. The ColorChanged event handler in
the form class calls Control.Invalidate() to signal to Windows that the
circle that changed its color needs to be redrawn.
You'll note that whenever one circle changes its color, _all_ of the
circles are redrawn. This issue comes up as a basic fact of life in
Windows applications.
There are ways to optimize that kind of thing out, so that the drawing
code doesn't waste time calling into objects that don't really need
redrawing, but in most cases there is no need. The most expensive part of
the redrawing is actually copying bits to the screen buffer, and as long
as you only invalidate the areas that have actually changed, that
expensive part is essentially minimized as much as would be possible
anyway. By invalidating only the area that the circle uses, even though
Circle.Draw() is called on each circle, only the circle that actually
changed winds up affecting what's in the screen buffer.
Finally, I should apologize for a few things:
1) In the Circle class, there's a flag to keep track of whether the
code has actually started updating the color. I wanted _something_ like
that, because I didn't want the color changes to start happening until
we'd at least drawn the circle once (there can be delays starting a
Windows application, and I didn't want to miss a color change just because
a timer went off before the circles could even be drawn once). I don't
generally like these kinds of state flags, but sometimes they are a
reasonably simple way to implement some specific behavior like this.
2) In the Circle class, I've used a single Random instance but since
Random isn't thread-safe (the instance members aren't, anyway), I then
need to lock around uses of the instance. I realize that the locking
complicates the code a bit, but the alternative would be to complicate the
code some other way in ensuring that multiple instances of Random all were
seeded with unique, independent numbers (which can actually get kind of
hairy).
3) In the Form1 class, I added a bunch of code to resize the circles
to fit the form nicely. I realize that wasn't necessary for the purpose
of the demonstration and in fact may have the tendency to distract from
what's actually important. But for me that was the part that made the
demo code actually interesting, and I like it better that way. I did put
all of that stuff into Visual Studio regions so it's easy to hide, and I
hope doing so mitigates whatever complication it might have caused in your
life. :)
I'm apologetic for all of those things only because they make the code
possibly more complicated than it need be. This isn't by any means the
only way to solve the problem you've described (or at least the one I
think you have :) ).
I find this implementation I've chosen to be the most modular and simple,
but the issues I was addressing with the first two above complicating
factors could have been avoided by doing an implementation that was
handled entirely using the Forms.Timer class instead of the
Threading.Timer class (and thus making everything run in the same
thread). I wanted my Circle class to be completely independent of the
System.Windows.Forms namespace, which is why I choose Threading.Timer and
the multi-thread issues that it brings with it.
Different people may have different preferences regarding those
trade-offs. :)
Hope it helps.
Pete
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text;
using System.Drawing;
using System.Threading;
using System.ComponentModel;
namespace TestCircleColors
{
#region Stock Program-class-with-main-entry-point
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(fals e);
Application.Run(new Form1());
}
}
#endregion
class Circle
{
// Shared random number generator, because it's a relatively simple
// way to avoid having multiple instances of RNGs all seeded tothe
// same time value (because they were all created at the same time)
static private Random _rnd = new Random();
static private object _objLock = new object();
// Have we started choosing new colors yet?
private bool _fStarted;
// Circle visual characteristics
private Color _color;
private int _radius;
private int _xCenter;
private int _yCenter;
// State variables for color changing
private int _ccolorUse = 4;
private System.Threading.Timer _timer;
// Event so that user of this class knows when color's changed
public event EventHandler ColorChanged;
public Rectangle Bounds
{
get { return new Rectangle(_xCenter - _radius, _yCenter -
_radius, _radius * 2, _radius * 2); }
}
public int Radius
{
get { return _radius; }
set { _radius = value; }
}
public Point Center
{
get { return new Point(_xCenter, _yCenter); }
set
{
_xCenter = value.X;
_yCenter = value.Y;
}
}
public Circle() : this(0, 0, 0) { }
public Circle(int radius, int xCenter, int yCenter)
{
_radius = radius;
_xCenter = xCenter;
_yCenter = yCenter;
_timer = new System.Threading.Timer(_ColorTimerCallback);
}
public void Draw(Graphics gfx, Font font)
{
// If this is the first time we've drawn, we'll need to choose
our
// initial color. Doing so will also start the timer for
picking
// the next color (see _NewColor() method).
if (!_fStarted)
{
_NewColor();
_fStarted = true;
}
using (SolidBrush brush = new SolidBrush(_color))
{
gfx.FillEllipse(brush, Bounds);
// The rest of this is drawing the text...strictly for
// informational purposes and not at all necessary.
string strCount = _ccolorUse.ToString();
SizeF szfText = gfx.MeasureString(strCount, font);
PointF ptfText = new PointF(_xCenter - szfText.Width / 2,
_yCenter - szfText.Height / 2);
gfx.DrawString(strCount, font, Brushes.Black, ptfText);
}
}
private void _NewColor()
{
// Random.Next() isn't thread-safe, so make sure the shared
// Random is used by only one thread at a time
lock (_objLock)
{
// Pick a new color.
_color = Color.FromArgb(_rnd.Next(0x1000000));
_color = Color.FromArgb(255, _color);
// Count down the color changes. If we have any left,
// start a timer so we know when to change it
if (--_ccolorUse 0)
{
// Setting the due time with an infinite interval
causes
// the timer to just fire once, until it's changed
again
_timer.Change(_rnd.Next(5000, 10000),
Timeout.Infinite);
}
}
// The first time we're called, it would be a waste to inform
// the client that the color had changed, since we never had
// a previous valid color. Otherwise, let them know.
if (_fStarted)
{
_RaiseColorChanged();
}
}
// This method is called by the timer when it expires
private void _ColorTimerCallback(object obj)
{
_NewColor();
}
// This method is used to raise the event for the client
private void _RaiseColorChanged()
{
EventHandler handler = ColorChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
public partial class Form1 : Form
{
// The "Auto-circle" regions contain code that isn't really
// part of the example, so much as it's my own desire to make
// the demo look a little nicer. The code in those regions can
// safely be ignored, at least as far as the question of how
// to implement color-changing circles goes.
#region Auto-circle fields
// Percentage of form size used for margin
private const float kpctPadding = 0.05f;
// Locations of triangles within our 4-unit rectangle
private readonly PointF[] _rgptf = new PointF[] {
new PointF(1, (float)Math.Sqrt(3) + 1),
new PointF(2, 1),
new PointF(3, (float)Math.Sqrt(3) + 1) };
// This is the aspect ratio of a box that will fully contain three
// circles with their centers on the corners of an equilateral
triangle
// and with radiuses equal to half the length of a leg of the
triangle
// (that is, each circle touching each other)
private readonly double _ratioAspectTriangleBox = 4 /
(Math.Sqrt(3) + 2);
#endregion
private List<Circle_rgcircle = new List<Circle>(3);
public Form1()
{
InitializeComponent();
// Looks nicer. :)
DoubleBuffered = true;
// Create a new circle for each point we initialized
for (int icircle = 0; icircle < _rgptf.Length; icircle++)
{
Circle circle = new Circle();
circle.ColorChanged += _HandleColorChanged;
_rgcircle.Add(circle);
}
// Initialize the actual in-form positions
// and sizes of each circle. If you hard-code
// the circle position and sizes, this isn't needed.
_UpdateCircles();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Very simple: just draw each circle
foreach (Circle circle in _rgcircle)
{
circle.Draw(e.Graphics, Font);
}
}
// When the circle's color changes, this method will
// be called. In it, all you need to do is invalidate
// the area on the form the circle is drawn in.
private void _HandleColorChanged(object sender, EventArgs e)
{
Circle circle = (Circle)sender;
Invalidate(circle.Bounds);
}
#region Auto-circle methods
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
_UpdateCircles();
}
private void _UpdateCircles()
{
Rectangle rectT = new Rectangle(new Point(), ClientSize);
int cxyPadding = -(int)Math.Max(kpctPadding * Size.Width,
kpctPadding * Size.Height);
int radius;
// Leave a margin within the form
rectT.Inflate(cxyPadding, cxyPadding);
// Fit the triangle in the space that's left
if (_ratioAspectTriangleBox < (double)rectT.Width /
rectT.Height)
{
rectT = new Rectangle(0, 0, (int)(rectT.Height *
_ratioAspectTriangleBox + 0.5), rectT.Height);
}
else
{
rectT = new Rectangle(0, 0, rectT.Width,
(int)(rectT.Height / _ratioAspectTriangleBox + 0.5));
}
// Center the triangle in the form
rectT.Offset((ClientSize.Width - rectT.Width) / 2,
(ClientSize.Height - rectT.Height) / 2);
// Update the circles
radius = rectT.Width / 4;
for (int icircle = 0; icircle < 3; icircle++)
{
Circle circle = _rgcircle[icircle];
PointF ptf = _rgptf[icircle];
Invalidate(circle.Bounds);
circle.Center = new Point((int)(radius * ptf.X)
+ rectT.Left, (int)(radius * ptf.Y) + rectT.Top);
circle.Radius = radius;
Invalidate(circle.Bounds);
}
}
#endregion
#region VS Designer stuff
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
}
#endregion
#endregion
}
}