I wrote this little piece of code to linearly interpolate between a winforms control's background colour and any arbirtrarly chosen colour.
I don't like the way I wrote this piece and I was wondering if any of you would be interested in suggesting improvements.
Some improvements/thoughts from the top of my head:
- If the user calls the method while a previous iteration is still ongoing, the background colour will not be predictable. This is not intended. I would require some form of
if(!colourChanging.IsActive)
... and so on. Any ideas on how to implement that? - Do I need to dispose the task? No, right? The task will take a threadpool thread, release all of its resource at its end and return the thread back to the pool?
- Is this a stupid approach to begin with?
- How do I get the complementary colour of
Color _eventColour
? I think it might make sense to change the foreground color property to maintain readability. - Is this approach scalable to lets say 100 controls - to be used in an event driven environment?
private void ChangeControlColour(Control _activeControl, Color _eventColour)
{
int[] _rgbEventColours = new int[3] { _eventColour.R, _eventColour.G, _eventColour.B };
int[] _rgbOriginalColours = new int[3] { _activeControl.BackColor.R, _activeControl.BackColor.G, _activeControl.BackColor.B };
int[] _rgbIntervals = new int[3];
int[] _rgbCurrentValues = _rgbEventColours;
int _intervals = 20;
/* linear steps between each fading interval */
for (int i = 0; i < 3; i++)
_rgbIntervals[i] = (_rgbOriginalColours[i] - _rgbEventColours[i]) / _intervals;
/* Start with EventColour */
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate { _activeControl.BackColor = _eventColour; });
else
_activeControl.BackColor = _eventColour;
/* LinearFading Process isolated in a seperate Task to avoid blocking UI */
Task t = Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(500);
Color _fadingColour;
for (int i = 0; i <= _intervals; i++)
{
_fadingColour = Color.FromArgb(_rgbCurrentValues[0], _rgbCurrentValues[1], _rgbCurrentValues[2]);
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate {_activeControl.BackColor = _fadingColour;});
else
_activeControl.BackColor = _fadingColour;
for (int n = 0; n < 3; n++) _rgbCurrentValues[n] += _rgbIntervals[n];
System.Threading.Thread.Sleep(50);
}
/* More important for non linear interpolation, but for completeness - set Control's Colour back
* to its original backgroundcolour */
Color _startingColour = Color.FromArgb(_rgbOriginalColours[0], _rgbOriginalColours[1], _rgbOriginalColours[2]);
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate { _activeControl.BackColor = _startingColour; });
else
_activeControl.BackColor = _startingColour;
});
}
Edit / Update on Sept. 20th 2014.
ChrisWue has answered the original question in a very helpful and detailed way. Thanks!
I do have a follow up:
Regarding "making sure that only one fading effect is running at a time" is the following theoretical solution a feasible approach or would there be a more efficient, pragmatic approach ? :
(let's assume I create a new instance of the ColorFader on some random event X for a control Y)
I store all ColorFader instances in a List and give them a field to remember their WinForms control Y. so every time the event is fired for a control Y I would check if there already is a ColorFader in the list referencing this particular Control Y.
In the ColorFader Class I would need to implement IDisposable . And as soon as the the object is disposed I would need to get rid of it in the List .
Got a better idea anyone?
The "problem" I see with this is the additional management of objects. It is not nicely encapsulated, because every project I would like to use this in I would have to create a new List just to keep track of the Objects + work to add and remove from said list.
(I will have around 20-40 labels which could fire a ColorFading event at any given time)