Design of colour fading for WinForms controls effect
$begingroup$
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)
c# design-patterns winforms
$endgroup$
add a comment |
$begingroup$
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)
c# design-patterns winforms
$endgroup$
add a comment |
$begingroup$
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)
c# design-patterns winforms
$endgroup$
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)
c# design-patterns winforms
c# design-patterns winforms
edited Sep 20 '14 at 14:59
Aeolus
asked Sep 13 '14 at 18:01
AeolusAeolus
85127
85127
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
The standard naming convention in C# for local variables and parameters is
camelCase. An underscore prefix is usually used for private class fields.You repeat the code so the the background color three times - this should be extracted into a separate method.
This will perform an integer division:
_rgbIntervals[i] = (_rgbOriginalColours[i] - _rgbEventColours[i]) / _intervals;
which means if the difference between two color part is less than
_intervalsthe calculated step is0. Also you will get quite big accumulated errors due to the rounding. For example if you start with the color(30, 30, 30)and want it to fade to(60, 60, 60)then you will end up only fading to(50, 50, 50)because60 - 30 = 30and30 / 20=1.5but the1.5will be truncated to1due to integer division.
You do set the final color to the target color but it would be smoother to calculate the step sizes as floats.
You keep track of the various part and intermediate values in a bunch of arrays which do not have any direct relationship. It's also not very re-usable this way. I would encapsulate it in it's own class.
So the refactored code could look like this:
Class to encapsulate the fading between two colors:
public class ColorFader
{
private readonly Color _From;
private readonly Color _To;
private readonly double _StepR;
private readonly double _StepG;
private readonly double _StepB;
private readonly uint _Steps;
public ColorFader(Color from, Color to, uint steps)
{
if (steps == 0)
throw new ArgumentException("steps must be a positive number");
_From = from;
_To = to;
_Steps = steps;
_StepR = (double)(_To.R - _From.R) / _Steps;
_StepG = (double)(_To.G - _From.G) / _Steps;
_StepB = (double)(_To.B - _From.B) / _Steps;
}
public IEnumerable<Color> Fade()
{
for (uint i = 0; i < _Steps; ++i)
{
yield return Color.FromArgb((int)(_From.R + i * _StepR), (int)(_From.G + i * _StepG), (int)(_From.B + i * _StepB));
}
yield return _To; // make sure we always return the exact target color last
}
}
And the refactored method in your form:
private void ChangeControlColour(Control activeControl, Color eventColour)
{
int intervals = 20;
var colorFader = new ColorFader(eventColour, activeControl.BackColor, intervals);
SetControlBackColor(eventColor);
/* LinearFading Process isolated in a seperate Task to avoid blocking UI */
Task t = Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(500);
foreach (var color in colorFader.Fade())
{
SetControlBackColor(color);
System.Threading.Thread.Sleep(50);
}
});
}
private void SetControlBackColor(Color color)
{
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate { _activeControl.BackColor = color; });
else
_activeControl.BackColor = color;
}
$endgroup$
$begingroup$
ChrisWue, that was a very helpful and enlightening answer to my question.
$endgroup$
– Aeolus
Sep 20 '14 at 14:36
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f62840%2fdesign-of-colour-fading-for-winforms-controls-effect%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
The standard naming convention in C# for local variables and parameters is
camelCase. An underscore prefix is usually used for private class fields.You repeat the code so the the background color three times - this should be extracted into a separate method.
This will perform an integer division:
_rgbIntervals[i] = (_rgbOriginalColours[i] - _rgbEventColours[i]) / _intervals;
which means if the difference between two color part is less than
_intervalsthe calculated step is0. Also you will get quite big accumulated errors due to the rounding. For example if you start with the color(30, 30, 30)and want it to fade to(60, 60, 60)then you will end up only fading to(50, 50, 50)because60 - 30 = 30and30 / 20=1.5but the1.5will be truncated to1due to integer division.
You do set the final color to the target color but it would be smoother to calculate the step sizes as floats.
You keep track of the various part and intermediate values in a bunch of arrays which do not have any direct relationship. It's also not very re-usable this way. I would encapsulate it in it's own class.
So the refactored code could look like this:
Class to encapsulate the fading between two colors:
public class ColorFader
{
private readonly Color _From;
private readonly Color _To;
private readonly double _StepR;
private readonly double _StepG;
private readonly double _StepB;
private readonly uint _Steps;
public ColorFader(Color from, Color to, uint steps)
{
if (steps == 0)
throw new ArgumentException("steps must be a positive number");
_From = from;
_To = to;
_Steps = steps;
_StepR = (double)(_To.R - _From.R) / _Steps;
_StepG = (double)(_To.G - _From.G) / _Steps;
_StepB = (double)(_To.B - _From.B) / _Steps;
}
public IEnumerable<Color> Fade()
{
for (uint i = 0; i < _Steps; ++i)
{
yield return Color.FromArgb((int)(_From.R + i * _StepR), (int)(_From.G + i * _StepG), (int)(_From.B + i * _StepB));
}
yield return _To; // make sure we always return the exact target color last
}
}
And the refactored method in your form:
private void ChangeControlColour(Control activeControl, Color eventColour)
{
int intervals = 20;
var colorFader = new ColorFader(eventColour, activeControl.BackColor, intervals);
SetControlBackColor(eventColor);
/* LinearFading Process isolated in a seperate Task to avoid blocking UI */
Task t = Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(500);
foreach (var color in colorFader.Fade())
{
SetControlBackColor(color);
System.Threading.Thread.Sleep(50);
}
});
}
private void SetControlBackColor(Color color)
{
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate { _activeControl.BackColor = color; });
else
_activeControl.BackColor = color;
}
$endgroup$
$begingroup$
ChrisWue, that was a very helpful and enlightening answer to my question.
$endgroup$
– Aeolus
Sep 20 '14 at 14:36
add a comment |
$begingroup$
The standard naming convention in C# for local variables and parameters is
camelCase. An underscore prefix is usually used for private class fields.You repeat the code so the the background color three times - this should be extracted into a separate method.
This will perform an integer division:
_rgbIntervals[i] = (_rgbOriginalColours[i] - _rgbEventColours[i]) / _intervals;
which means if the difference between two color part is less than
_intervalsthe calculated step is0. Also you will get quite big accumulated errors due to the rounding. For example if you start with the color(30, 30, 30)and want it to fade to(60, 60, 60)then you will end up only fading to(50, 50, 50)because60 - 30 = 30and30 / 20=1.5but the1.5will be truncated to1due to integer division.
You do set the final color to the target color but it would be smoother to calculate the step sizes as floats.
You keep track of the various part and intermediate values in a bunch of arrays which do not have any direct relationship. It's also not very re-usable this way. I would encapsulate it in it's own class.
So the refactored code could look like this:
Class to encapsulate the fading between two colors:
public class ColorFader
{
private readonly Color _From;
private readonly Color _To;
private readonly double _StepR;
private readonly double _StepG;
private readonly double _StepB;
private readonly uint _Steps;
public ColorFader(Color from, Color to, uint steps)
{
if (steps == 0)
throw new ArgumentException("steps must be a positive number");
_From = from;
_To = to;
_Steps = steps;
_StepR = (double)(_To.R - _From.R) / _Steps;
_StepG = (double)(_To.G - _From.G) / _Steps;
_StepB = (double)(_To.B - _From.B) / _Steps;
}
public IEnumerable<Color> Fade()
{
for (uint i = 0; i < _Steps; ++i)
{
yield return Color.FromArgb((int)(_From.R + i * _StepR), (int)(_From.G + i * _StepG), (int)(_From.B + i * _StepB));
}
yield return _To; // make sure we always return the exact target color last
}
}
And the refactored method in your form:
private void ChangeControlColour(Control activeControl, Color eventColour)
{
int intervals = 20;
var colorFader = new ColorFader(eventColour, activeControl.BackColor, intervals);
SetControlBackColor(eventColor);
/* LinearFading Process isolated in a seperate Task to avoid blocking UI */
Task t = Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(500);
foreach (var color in colorFader.Fade())
{
SetControlBackColor(color);
System.Threading.Thread.Sleep(50);
}
});
}
private void SetControlBackColor(Color color)
{
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate { _activeControl.BackColor = color; });
else
_activeControl.BackColor = color;
}
$endgroup$
$begingroup$
ChrisWue, that was a very helpful and enlightening answer to my question.
$endgroup$
– Aeolus
Sep 20 '14 at 14:36
add a comment |
$begingroup$
The standard naming convention in C# for local variables and parameters is
camelCase. An underscore prefix is usually used for private class fields.You repeat the code so the the background color three times - this should be extracted into a separate method.
This will perform an integer division:
_rgbIntervals[i] = (_rgbOriginalColours[i] - _rgbEventColours[i]) / _intervals;
which means if the difference between two color part is less than
_intervalsthe calculated step is0. Also you will get quite big accumulated errors due to the rounding. For example if you start with the color(30, 30, 30)and want it to fade to(60, 60, 60)then you will end up only fading to(50, 50, 50)because60 - 30 = 30and30 / 20=1.5but the1.5will be truncated to1due to integer division.
You do set the final color to the target color but it would be smoother to calculate the step sizes as floats.
You keep track of the various part and intermediate values in a bunch of arrays which do not have any direct relationship. It's also not very re-usable this way. I would encapsulate it in it's own class.
So the refactored code could look like this:
Class to encapsulate the fading between two colors:
public class ColorFader
{
private readonly Color _From;
private readonly Color _To;
private readonly double _StepR;
private readonly double _StepG;
private readonly double _StepB;
private readonly uint _Steps;
public ColorFader(Color from, Color to, uint steps)
{
if (steps == 0)
throw new ArgumentException("steps must be a positive number");
_From = from;
_To = to;
_Steps = steps;
_StepR = (double)(_To.R - _From.R) / _Steps;
_StepG = (double)(_To.G - _From.G) / _Steps;
_StepB = (double)(_To.B - _From.B) / _Steps;
}
public IEnumerable<Color> Fade()
{
for (uint i = 0; i < _Steps; ++i)
{
yield return Color.FromArgb((int)(_From.R + i * _StepR), (int)(_From.G + i * _StepG), (int)(_From.B + i * _StepB));
}
yield return _To; // make sure we always return the exact target color last
}
}
And the refactored method in your form:
private void ChangeControlColour(Control activeControl, Color eventColour)
{
int intervals = 20;
var colorFader = new ColorFader(eventColour, activeControl.BackColor, intervals);
SetControlBackColor(eventColor);
/* LinearFading Process isolated in a seperate Task to avoid blocking UI */
Task t = Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(500);
foreach (var color in colorFader.Fade())
{
SetControlBackColor(color);
System.Threading.Thread.Sleep(50);
}
});
}
private void SetControlBackColor(Color color)
{
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate { _activeControl.BackColor = color; });
else
_activeControl.BackColor = color;
}
$endgroup$
The standard naming convention in C# for local variables and parameters is
camelCase. An underscore prefix is usually used for private class fields.You repeat the code so the the background color three times - this should be extracted into a separate method.
This will perform an integer division:
_rgbIntervals[i] = (_rgbOriginalColours[i] - _rgbEventColours[i]) / _intervals;
which means if the difference between two color part is less than
_intervalsthe calculated step is0. Also you will get quite big accumulated errors due to the rounding. For example if you start with the color(30, 30, 30)and want it to fade to(60, 60, 60)then you will end up only fading to(50, 50, 50)because60 - 30 = 30and30 / 20=1.5but the1.5will be truncated to1due to integer division.
You do set the final color to the target color but it would be smoother to calculate the step sizes as floats.
You keep track of the various part and intermediate values in a bunch of arrays which do not have any direct relationship. It's also not very re-usable this way. I would encapsulate it in it's own class.
So the refactored code could look like this:
Class to encapsulate the fading between two colors:
public class ColorFader
{
private readonly Color _From;
private readonly Color _To;
private readonly double _StepR;
private readonly double _StepG;
private readonly double _StepB;
private readonly uint _Steps;
public ColorFader(Color from, Color to, uint steps)
{
if (steps == 0)
throw new ArgumentException("steps must be a positive number");
_From = from;
_To = to;
_Steps = steps;
_StepR = (double)(_To.R - _From.R) / _Steps;
_StepG = (double)(_To.G - _From.G) / _Steps;
_StepB = (double)(_To.B - _From.B) / _Steps;
}
public IEnumerable<Color> Fade()
{
for (uint i = 0; i < _Steps; ++i)
{
yield return Color.FromArgb((int)(_From.R + i * _StepR), (int)(_From.G + i * _StepG), (int)(_From.B + i * _StepB));
}
yield return _To; // make sure we always return the exact target color last
}
}
And the refactored method in your form:
private void ChangeControlColour(Control activeControl, Color eventColour)
{
int intervals = 20;
var colorFader = new ColorFader(eventColour, activeControl.BackColor, intervals);
SetControlBackColor(eventColor);
/* LinearFading Process isolated in a seperate Task to avoid blocking UI */
Task t = Task.Factory.StartNew(() =>
{
System.Threading.Thread.Sleep(500);
foreach (var color in colorFader.Fade())
{
SetControlBackColor(color);
System.Threading.Thread.Sleep(50);
}
});
}
private void SetControlBackColor(Color color)
{
if (_activeControl.InvokeRequired)
_activeControl.Invoke((MethodInvoker)delegate { _activeControl.BackColor = color; });
else
_activeControl.BackColor = color;
}
edited 51 mins ago
Hannobo
1032
1032
answered Sep 15 '14 at 21:37
ChrisWueChrisWue
19.1k333100
19.1k333100
$begingroup$
ChrisWue, that was a very helpful and enlightening answer to my question.
$endgroup$
– Aeolus
Sep 20 '14 at 14:36
add a comment |
$begingroup$
ChrisWue, that was a very helpful and enlightening answer to my question.
$endgroup$
– Aeolus
Sep 20 '14 at 14:36
$begingroup$
ChrisWue, that was a very helpful and enlightening answer to my question.
$endgroup$
– Aeolus
Sep 20 '14 at 14:36
$begingroup$
ChrisWue, that was a very helpful and enlightening answer to my question.
$endgroup$
– Aeolus
Sep 20 '14 at 14:36
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f62840%2fdesign-of-colour-fading-for-winforms-controls-effect%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown