By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
425,763 Members | 1,609 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 425,763 IT Pros & Developers. It's quick & easy.

Modifying Graphics object from separate threads

P: n/a
Hi,

I hope you guys can help me make this simple application work. I'm
trying to create a form displaying 3 circles, which independently
change colors 3 times after a random time period has passed.
I'm struggling with making the delegate/invoke thing work, as I know
GUI objects aren't thread-safe. I don't quite understand the concept
I'm supposed to use to modify the GUI thread-safe.

Below is my form and my Circle class. Currently, the application
crashes - I think because multiple threads are trying to modify the
Graphics object.

Thanks in advance for any help.
-Koschwitz

------------ start Form.cs -----------------------
public partial class Form1 : Form
{

private Random rnd;
private Pen myPen;
public Bitmap DrawArea;
Circle c1,c2,c3;
Graphics xGraph;

public Form1()
{
InitializeComponent();
rnd = new Random((int)DateTime.Now.Ticks); // seeded with
ticks
myPen = new Pen(Color.Red);
DrawArea = new Bitmap(this.ClientRectangle.Width,
this.ClientRectangle.Height,
System.Drawing.Imaging.PixelFormat.Format24bppRgb) ; //
make a persistent drawing area
xGraph = Graphics.FromImage(DrawArea);
}

private void Form1_Load(object sender, System.EventArgs e)
{
InitializeDrawArea();
}

private void InitializeDrawArea()
{
xGraph.Clear(Color.White);
}

private void Form1_Closed(object sender, System.EventArgs e)
{
DrawArea.Dispose();
}

public delegate void DrawCircleDelegate(); // no idea if this
is correct

private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
xGraph = e.Graphics;
xGraph.DrawImage(DrawArea, 0, 0, DrawArea.Width,
DrawArea.Height);
c1 = new Circle(xGraph, 450, 550, 900);
c2 = new Circle(xGraph, 450, 950, 500);
c3 = new Circle(xGraph, 450, 1350, 900);
Thread t1 = new Thread(new ThreadStart(c1.DrawCircle));
Thread t2 = new Thread(new ThreadStart(c2.DrawCircle));
Thread t3 = new Thread(new ThreadStart(c3.DrawCircle));
t1.Start();
t2.Start();
t3.Start();
xGraph.Dispose();
}
}
------------ end Form.cs -----------------------

------------ start Circle.cs --------------------
class Circle
{
public Graphics xGraph;
private Random rnd = new Random((int)DateTime.Now.Ticks); //
seeded with ticks
int r1, x1, y1;

public Circle(Graphics g, int r, int x, int y)
{
xGraph = g;
r1 = r;
x1 = x;
y1 = y;
}

public void DrawCircle()
{
SolidBrush Brush = new SolidBrush(Color.White);
for (int k = 1; k < 3; k++)
{
Brush.Color = Color.FromArgb(
(rnd.Next(0, 255)),
(rnd.Next(0, 255)),
(rnd.Next(0, 255)));
Control.Invoke(new DrawCircleDelegate(c1.DrawCircle),
new object[] { }); //no idea if this is correct
xGraph.FillEllipse(Brush, x1 - r1, y1 - r1, r1, r1);
Thread.Sleep(rnd.Next(5000,10000)); //random wait time
between 5 and 10 seconds
}
}
}
------------ end Circle.cs --------------------
Dec 3 '07 #1
Share this Question
Share on Google+
9 Replies


P: n/a
On Sun, 02 Dec 2007 18:45:53 -0800, <ko*******@gmx.dewrote:
I hope you guys can help me make this simple application work. I'm
trying to create a form displaying 3 circles, which independently
change colors 3 times after a random time period has passed.
I'm struggling with making the delegate/invoke thing work, as I know
GUI objects aren't thread-safe. I don't quite understand the concept
I'm supposed to use to modify the GUI thread-safe.

Below is my form and my Circle class. Currently, the application
crashes - I think because multiple threads are trying to modify the
Graphics object.
From the code you posted, it would take some significantl effort to figure
out for sure how the application "crashes". You haven't been specific as
to what that means, but it's likely you're hitting some sort of unhandled
exception. Still, I see at least two different ways the code could cause
an exception, plus a third problem that makes me wonder how the code
compiles at all.

I can make a guess, but without clarification from you it would be
impossible to say for sure.

Frankly the code looks like the work of a madman. No offense intended, of
course. But it's pretty much all wrong.

Here are some things that need fixing:

* You are disposing the Graphics instance that you passed to your
threads to use, so if they get a chance to use it (I don't think they do,
but that's a separate issue) they would find it's disposed already and
unusable.

* You keep a Graphics instance in the classes. Not only would this
leads to an exception, it's also just not a good idea. The underlying
Windows OS object, a device context, is expensive and should only be kept
long enough for a given drawing operation or specific sequence of drawing
operations.

* You also fail to dispose of the Graphics instance you created in the
Form1 constructor, but since you shouldn't have the instance in the first
place, that won't matter once you fix the larger problem.

* You are starting threads in your OnPaint() method. This is
definitely not the way to draw. The OnPaint() method has one job: to draw
(or paint, if you like). It needs to draw, right away, and return.

* You are passing the PaintEventArgs.Graphics instance to your
threads. This instance, even if you did not dispose it yourself, would
not live long enough for any of the threads to use it. This is the likely
case no matter what you do, but you ensure it will be true by calling
Control.Invoke() before you use the Graphics instance, as well as by
sleeping in the method. Once the WM_PAINT message that is being handled
by OnPaint() has been finished, the Graphics instance will be disposed.

* You are calling Circle.DrawCircle() from within your
Circle.DrawCircle() method, and in a loop no less. This means that for
each call to DrawCircle(), you theoretically create two new calls to
DrawCircle() (the fact that you do it through Control.Invoke() doesn't
change this basic fact). I write "theoretically", because you never will
get to the second call, because the first call will just endlessly keep
going deeper and deeper. This will eventually cause a stack overflow
exception.

* But, I don't see how you even could get that far, because you are
using "c1.DrawCircle" as the method for your delegate instance, and I
don't see anywhere that "c1" is defined in that block of code. How does
that code even compile?

* And assuming there's some explanation for that compiling that I
don't get, how does "Control.Invoke" compile? Control.Invoke() isn't a
static method; you need a Control instance to call it.

* And last but certainly not least, you are creating a SolidBrush and
failing to dispose it. If you create an object that implements
IDisposable, you need to dispose it when you're done with it.

Finally, some things you should know, because you can avoid some of the
work you're doing or because it might affect your results:

* The Random class already seeds itself based on the time if you use
the parameterless constructor. There's no need for you to pass in a
time-dependent seed yourself.

* The Random.Next(Int32) overload is the same as calling
Random.Next(0, Int32). That is, if your range is from 0 to some number
(say, 255), you can just call Next(255).

* The maximum limit for the Random class is always an exclusive
limit. That is, the random number returned will never equal that number.
So if you want to randomly select from the full range of 0 through 255
inclusive, you need to pass 256 as the maximum, not 255.

* If you want the Image drawn at its actual size, there's no need to
specify the width and height to the Graphics.DrawImage() method. You can
just use an overload that takes just the position (e.g. DrawImage(Image,
Point), DrawImage(Image, Int32, Int32), etc.)

* You don't even need to use Invoke() with the Graphics class. It's
specifically the user-interface objects, like anything derived from
Control, that require that. In this case, you shouldn't be drawing from a
different thread anyway, but there's not a fundamental reason you can't
use a Graphics instance from a different thread than the one in which it
was created.

* The diameter of the circle you might actually draw is only half what
it _seems_ like you're looking for. Assuming you're passing a center
point and a radius to the Circle constructor, what the FillEllipse would
draw (if the code ever got there) is a circle centered on a point offset
by half the radius passed to the constructor in both the x and y
directions, and half the diameter. You probably want the width to be r1 *
2, not just r1.

As for helping with the broader question goes...

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.

Because of all the problems, it's difficult to understand what exactly the
behavior you're looking for is. But I'm guessing that if you can explain
the specifics in a more detailed way, doing what you want would not be
nearly as complicated as the code you've written, and I'm happy to try to
help with that.

Pete
Dec 3 '07 #2

P: n/a
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
}
}
Dec 3 '07 #3

P: n/a
Wow Peter, I can't thank you enough.
Seems like you had a little extra time available? I didn't expect
someone to give such a constructive answer... Again, thanks for all
the work you put into this.

Not sure if you care, but for your information: musicians are going to
play to these circles in a music class next week. :-)

I like your -clean- implementation of the Draw() function and the
triangle idea. It indeed does look really nice.

There are 2 more things I'll need to add, and I hope you can help me
again:
The background should gradually turn from white to black while the
count down is running, and the last color change (after a set time)
should turn all circles black (one by one).

Now, I will have to add a second timer to time the total running time,
correct?

To make the color change to black work I replaced
---- start ----
// Pick a new color.
//_color = Color.FromArgb(_rnd.Next(0x1000000));
//_color = Color.FromArgb(255, _color);
---- end ----
with
---- start ----
int CMYcolor = _rnd.Next(1, 4);
if (CMYcolor == 1) { _color = Color.Cyan; }
if (CMYcolor == 2) { _color = Color.Magenta; }
if (CMYcolor == 3) { _color = Color.Yellow; }
if (_ccolorUse == 1) { _color = Color.Black; }
---- end ----
to have the circles turn black at the last count. Also, I am only
allowed to choose between the three CMY colors (no more random
colors).

How would I implement a second timer that sets the _ccolorUse = 1 (end
of the show) after a set time (e.g. 1 minute)? And how could I have
the background gradually turn from white to black during the set time
(e.g. 1 minute)?

Thanks again for all your help Peter,
-Mark
Dec 4 '07 #4

P: n/a
On Mon, 03 Dec 2007 22:16:59 -0800, <ko*******@gmx.dewrote:
Wow Peter, I can't thank you enough.
You're welcome. :)
Seems like you had a little extra time available?
I wish. But perhaps you're familiar with compulsive behavior? :) I
don't believe that I'd qualify as a mental health risk, but I admit to a
certain degree of compulsiveness. Some problems just seem so in need of
an elegant solution that I can't help myself; in those occasions, I am
close to not having a choice in the matter. :)

That said, it didn't really take me that long. The hardest part for me
was working out the math for auto-sizing the circles. The basic timing
stuff is well within my common experience and took hardly longer to do
than the time it took to type it in. Experience does pay off sometimes.
:)
I didn't expect
someone to give such a constructive answer... Again, thanks for all
the work you put into this.

Not sure if you care, but for your information: musicians are going to
play to these circles in a music class next week. :-)
Heh. Funny how things intersect. I myself had a music class (a long time
ago) in which I was tasked with coming up with something like that. We
each (my classmates and I) had to come up with some sort of "abstract"
music generation system. This being pre-Windows, and so of course very
pre-.NET :), mine involved helpers to watch the traffic outside the
classroom window, mapping car types, colors, speed to musical notes.

I guess music teachers are still doing that sort of thing. :)
I like your -clean- implementation of the Draw() function and the
triangle idea. It indeed does look really nice.

There are 2 more things I'll need to add, and I hope you can help me
again:
The background should gradually turn from white to black while the
count down is running, and the last color change (after a set time)
should turn all circles black (one by one).

Now, I will have to add a second timer to time the total running time,
correct?
That's likely the most straightforward solution, yes.
To make the color change to black work I replaced
---- start ----
// Pick a new color.
//_color = Color.FromArgb(_rnd.Next(0x1000000));
//_color = Color.FromArgb(255, _color);
---- end ----
with
---- start ----
int CMYcolor = _rnd.Next(1, 4);
if (CMYcolor == 1) { _color = Color.Cyan; }
if (CMYcolor == 2) { _color = Color.Magenta; }
if (CMYcolor == 3) { _color = Color.Yellow; }
if (_ccolorUse == 1) { _color = Color.Black; }
---- end ----
to have the circles turn black at the last count. Also, I am only
allowed to choose between the three CMY colors (no more random
colors).
As a note: I would initialize a List<Colorwith the colors you want to
use, and then just index that list according to the random number you
generate (but choose a number between 0 and 3, not 1 and 4 :) ). Much
nicer-looking code than the if() statements. Alternatively, at the very
least those if() statements ought to be a single switch().
How would I implement a second timer that sets the _ccolorUse = 1 (end
of the show) after a set time (e.g. 1 minute)? And how could I have
the background gradually turn from white to black during the set time
(e.g. 1 minute)?
You'd need to be more specific about "after a set time". Is the total
duration of the show supposed to be 1 minute? Is that "set time" 1 minute
after the last color change? Are all the circles supposed to change to
black at the same time? Or are they each going to change to black 1
minute after each's last color change?

How best to implement that will depend on the specifics of the behavior
you want. In no case should it be all that complicated, but it's hard to
offer good advice without knowing the specifics.

For the background, I would probably try to use the Forms.Timer as my
first attempt. In a forms application, it's a natural choice for
relatively low-resolution timings, not only because it's in the same
namespace as the other forms stuff, but because it avoids cross-thread
issues (the timer events are raised on the main GUI thread).

My desire to keep the Circle class completely independent of the forms
steered me toward the Threading.Timer class there, but in the case of the
background you should be able to handle that entirely without any new
classes (just put the logic in the Form1 class). The timer interval is
easy to calculate from the number of shades of grey you want to use as you
fade to black and the total duration of the fade. Just handle the timer
event and with each tick of the timer, change the background color of the
form to one shade closer to black.

As with the circles, it's difficult to offer anything more specific than
that because I don't know when the background is supposed to start fading
to black. But presumably it's tied to the behavior of the circles
somehow, and given that it should be relatively simple to modify the code
I provided so that it provides the form with the necessary signal for it
to start fading out at the appropriate time.

If you can elaborate on the above questions, I'm happy to offer whatever
other advice I can.

Pete
Dec 4 '07 #5

P: n/a
I wish. But perhaps you're familiar with compulsive behavior? :) I
don't believe that I'd qualify as a mental health risk, but I admit to a
certain degree of compulsiveness. Some problems just seem so in need of
an elegant solution that I can't help myself; in those occasions, I am
close to not having a choice in the matter. :)
Well, your compulsive behavior did help me out a lot, so I wouldn't
say you'd qualify as a mental health risk. Not sure what other people
think about it though ;-)
Heh. Funny how things intersect. I myself had a music class (a long time
ago) in which I was tasked with coming up with something like that. We
each (my classmates and I) had to come up with some sort of "abstract"
music generation system. This being pre-Windows, and so of course very
pre-.NET :), mine involved helpers to watch the traffic outside the
classroom window, mapping car types, colors, speed to musical notes.
I guess music teachers are still doing that sort of thing. :)
This is for a friend's aleatoric piece (http://en.wikipedia.org/wiki/
Aleatoric_music) which explains all the randomness. Musicians will
play different melodies, depending on which color his/her circle has.
Percussionists will also play something special, if the circles happen
to have the same color/all different colors etc.
The background should gradually turn from white to black while the
count down is running, and the last color change (after a set time)
should turn all circles black (one by one).
Now, I will have to add a second timer to time the total running time,
correct?
That's likely the most straightforward solution, yes.
Ok
How would I implement a second timer that sets the _ccolorUse = 1 (end
of the show) after a set time (e.g. 1 minute)? And how could I have
the background gradually turn from white to black during the set time
(e.g. 1 minute)?

You'd need to be more specific about "after a set time". Is the total
duration of the show supposed to be 1 minute? Is that "set time" 1 minute
after the last color change? Are all the circles supposed to change to
black at the same time? Or are they each going to change to black 1
minute after each's last color change?
The total duration may be 1 minute, but the composer isn't sure yet.
This value should be flexible. After the total duration has expired,
the 3 circles should turn black one by one (5-10 ms after the last
color change).
As with the circles, it's difficult to offer anything more specific than
that because I don't know when the background is supposed to start fading
to black. But presumably it's tied to the behavior of the circles
somehow, and given that it should be relatively simple to modify the code
I provided so that it provides the form with the necessary signal for it
to start fading out at the appropriate time.
I think given that there's a second timer (for the total duration) it
most likely shouldn't be tied to the circles.
The fading should occur during the whole show, starting right away and
ending after the preset total duration (e.g. 1 minute).
With the circles turning black after the total duration, the piece
ends with a completely black screen.
If you can elaborate on the above questions, I'm happy to offer whatever
other advice I can.
Thanks in advance Peter - I'll try to follow your advise tonight,
unless you already have a very simple idea on how to implement
this. ;-)
Dec 4 '07 #6

P: n/a
Hi Pete,

Thanks again and a lot for your help - sorry it took me a couple of
days to respond.

The UI is a good idea, and I've decided to have the composer enter the
number of seconds (for simplicity) he'd like the piece to run.

However, the background fading is something I have no idea how to
start on. I've done this in Flash before, and it was really easy. ;)
Anyways, I know I'll have to create another thread drawing rectangles
in different shades of gray, or should I set the form's background
color instead?

totalSeconds / 255 * 1000 should give me the milliseconds after which
I will subtract 1 from each the R, G, B values of the background
color, getting to 0 after the total duration. I'm just not sure if I
should do this event-driven, like the circles, or if there's another,
perhaps much easier solution to this. I wish I could just run this in
the main thread, and make it sleep (Thread.Sleep()) for the amount of
milliseconds I calculate with the amount from above - but it didn't
work when I tried it.

Hoping you're still a bit interested,
-Mark

Dec 11 '07 #7

P: n/a
On Mon, 10 Dec 2007 23:30:35 -0800, <ko*******@gmx.dewrote:
[...]
Anyways, I know I'll have to create another thread drawing rectangles
in different shades of gray, or should I set the form's background
color instead?
I would set the background color. Otherwise, you just add more stuff you
need to draw, without really needing to.
totalSeconds / 255 * 1000 should give me the milliseconds after which
I will subtract 1 from each the R, G, B values of the background
color, getting to 0 after the total duration. I'm just not sure if I
should do this event-driven, like the circles, or if there's another,
perhaps much easier solution to this. I wish I could just run this in
the main thread, and make it sleep (Thread.Sleep()) for the amount of
milliseconds I calculate with the amount from above - but it didn't
work when I tried it.
No, you're right it wouldn't. Your main thread must always handle
whatever event is going on and then return asap. Otherwise, the graphical
part of the program just stops working.

However, you _can_ get this to work in the main thread. Just use the
Timer class found in the System.Windows.Forms namespace. It is similar
to, but not exactly the same as, the System.Threading.Timer class that's
already in use for the circles. The basic idea will be to set the timer
to fire repeatedly using the duration you've calculated above. Each time
the timer event is raised, you'll set the background to one shade closer
to black.

The main difference between the two Timer classes is that the Forms.Timer
class will raise the event on your main GUI thread, eliminating any need
to worry about the cross-thread stuff that was needed with the
Threading.Timer class.

(Or rather, I should say: "that _should have been_ needed with the
Threading.Timer class". Looking at the code I posted, I see that I forgot
to use Invoke() in the event handler dealing with the ColorChanged event..
That not only shouldn't have worked, it should have raised a cross-thread
MDA exception when I ran the program in the debugger. I have no idea why
it worked without the exception happening. It's _possible_ that for some
reason I don't understand, it was actually legal and I just didn't know
it. In the meantime, you should probably change the
Form1._HandleColorChanged() method to look like this:

private void _HandleColorChanged(object sender, EventArgs e)
{
Circle circle = (Circle)sender;

Invoke((MethodInvoker)delegate() { Invalidate(circle.Bounds);
});
}

That way you're assured that the call to Invalidate() will always happen
on the right thread. Sorry for the confusion. If I have time to look
into it and I actually find an answer, I'll get back to you here on why it
worked without the Invoke().)

Note: I would just keep a single counter for the background color.
Subtract one from it, then set the background color to "new
Color.FromArgb(255, shade, shade, shade)". That's readable, and yet still
reasonably efficient (and especially with respect to not storing three
different variables that are always the same value :) ).
Hoping you're still a bit interested,
No, not any more. I sat here doing nothing else but waiting for your
reply, and after two days I got hungry and gave up.

:) Just kidding. It doesn't matter to me how long it takes for you to
follow up or even if you do. I'm happy to try to help if I can.

Pete
Dec 11 '07 #8

P: n/a
Well Pete,

I just wanted to thank you again for helping me out here - I did what
you suggested and it worked right away.
Without your help I wouldn't have known where to start, and I honestly
didn't expect someone in a news group to spend so much time on this.

The piece was performed last Wednesday and the program worked like a
charm. If you'd like to hear a recording of it, please let me know.

Happy Holidays to you & your family!
-Mark
Dec 14 '07 #9

P: n/a
On Fri, 14 Dec 2007 15:08:14 -0800, <ko*******@gmx.dewrote:
I just wanted to thank you again for helping me out here - I did what
you suggested and it worked right away.
Without your help I wouldn't have known where to start, and I honestly
didn't expect someone in a news group to spend so much time on this.
I have found that I frequently learn something new trying to explain
something I already know to someone. This was no exception (note the
little tangent regarding Invalidate() and thread-safety). You're welcome
for the help, but I hope you don't think I'm being entirely selfless. :)
The piece was performed last Wednesday and the program worked like a
charm. If you'd like to hear a recording of it, please let me know.
Sure, if it's convenient I think it'd be fun to hear.

Thanks,
Pete
Dec 14 '07 #10

This discussion thread is closed

Replies have been disabled for this discussion.