P
Petr Danes
Normal procedure to prevent a user from doing something illegal, immoral or
fattening is to disable controls that allow such an action whenever
conditions exist that make such an action undesirable. But sometimes testing
for such conditions is too difficult or time-consuming to be practical in
real time. In such a case, the tests are performed only after the user
initiates a request for an action. When it is subsequently found that the
requested action cannot be performed, some method must be employed to inform
the user.
A common choice is to display a dialog box with an error message explaining
the problem, but this can be annoying, since it requires an action from the
user to dismiss the dialog.
Another common choice is the Beep routine, which does not require the user
to acknowledge it. But it also has problems: not all users have speakers, or
have them turned on, or adequate volume or the ability to hear at all. And
even when all that is fine, a simple chirp does little to indicate what the
problem is.
A somewhat better solution is to make visible an error text somewhere on the
active form, explaining what's wrong. This does not irritate the user with
the necessity to dismiss a dialog and clearly indicates what is amiss. But
it makes life more difficult for the programmer: the error message must be
cleared away at some point, and since there is no way to predict the user's
next action, the message-clearing code must be invoked from all possible
routines accessible to the user. When new actions are added, they must
include calls to the clearing code, which is easy to forget, and leaving a
glaring error message displayed until the user does something, anything, can
make the form look unattractive.
I have recently started using a method which addresses all these problems. I
have written a routine which, given a control, changes the color of the
control to something specified, then slowly fades it back to it's previous
state. My first use was the background behind a set of toggle buttons which,
when depressed, can cause certain lookups to fail. Rather than shouting an
error dialog and forcing the user to deal with it, I flash the background
behind the group of toggle buttons red, and let it slowly (about ? second)
fade back to its normal background gray. It shows the user where the problem
is (a depressed toggle somewhere in the group) and does not require any
action on his part to acknowledge it. The effect is somewhat like a caution
light that flashes on and dims away to nothing, sort of a visual chime, but
specifically indicating where there is a problem. I find it much more
pleasing and less intrusive than any other notification method I've invented
so far.
Naturally, this presumes some level of sophistication on the part of the
user. A complete rookie could not be expected to understand the significance
of the color flash, but this is intended to be a low-key reminder to
experienced users that they've overlooked something. Once they are aware of
the problem, they know what to do about it. Counting code could be added to
display a more detailed error message if the same mistake was repeated
several times, to accommodate less experienced users. Instructions would
then be fairly simple - if you don't understand what's wrong when you've
made a mistake, just repeat the action once or twice and the software will
go into more detail about it for you.
The code was designed to flash an area, but it can certainly be modified to
display error text messages in the same way as it now does background -
flash on, then fade away. The time delay should be adequate to allow the
user to read the message, but not so long as to unnecessarily delay his
progress. That pretty much means it should be used only for very short
texts, which can be comprehended at a glance, else the user may have to
repeat the offending action, maybe even several times, to read the entire
message. I've had personal experiences with devices that acted that way, and
can testify that such behavior raises the aggravation factor of using a
device by a lot.
The code calculates the difference between the current color value and the
requested color value for all three color channels, sets the new color, then
loops around stepping the three channels back to their original state. Each
color channel is computed and incremented separately, else the color change
would not be smooth. It does not matter what the original color or
notification color are - the code can handle any combination of colors. I
have it defaulting to red if the flash color is not specified.
Constants are included to control the speed and smoothness of the fade. The
nap value is better to give a fairly constant fade time. If you place too
much reliance on the steps constant to control fade rate, it will vary based
on the abilities of the computer. Nap uses a fixed pause time and so is more
uniform across machines of varying capability. The Sleep API declaration is
only necessary if you want the routine to use this. If not, you can remove
it and the Sleep statement (the first statement inside the Do loop).
My only gripe with the routine is a slight screen flicker during the fade. I
someone knows how to fix that, I'd be happy to hear about it.
Pete
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Sub FlashBlock(ByRef ctl As Control, Optional ByVal fc& = vbRed)
Dim oc&, ro&, go&, bo&
Dim rf!, gf!, bf!, rs!, gs!, bs! ' These must be reals, integers would be
too readily truncated to unusable values.
Const steps& = 15, nap& = 50
' Audio cue, just as an extra
Beep
oc = ctl.BackColor
ctl.BackColor = fc
' Old color values
ro = vbRed And oc
go = (vbGreen And oc) \ 256
bo = (vbBlue And oc) \ 65536
' New color values, which will be stepped back toward the old ones.
rf = vbRed And fc
gf = (vbGreen And fc) \ 256
bf = (vbBlue And fc) \ 65536
' Step values to march back towards the original color.
rs = (rf - ro) / steps
gs = (gf - go) / steps
bs = (bf - bo) / steps
' Cycle around stepping the new color values back towards the old one until
all have returned to their original value.
' Real arithmetic requires some adjusting, else floating point imprecision
might not get us exactly back to our stating values.
' Abs is necessary, since we don't know whether the new values are greater
or smaller than the old values.
Do
If nap > 0 Then Sleep nap
If ro <> rf Then If Abs(ro - rf) > Abs(rs) Then rf = rf - rs Else rf =
ro
If go <> gf Then If Abs(go - gf) > Abs(gs) Then gf = gf - gs Else gf =
go
If bo <> bf Then If Abs(bo - bf) > Abs(bs) Then bf = bf - bs Else bf =
bo
ctl.BackColor = RGB(Int(rf), Int(gf), Int(bf))
DoEvents ' Necessary to let go, so that the display actually gets
updated.
Loop Until ro = rf And go = gf And bo = bf
End Sub
fattening is to disable controls that allow such an action whenever
conditions exist that make such an action undesirable. But sometimes testing
for such conditions is too difficult or time-consuming to be practical in
real time. In such a case, the tests are performed only after the user
initiates a request for an action. When it is subsequently found that the
requested action cannot be performed, some method must be employed to inform
the user.
A common choice is to display a dialog box with an error message explaining
the problem, but this can be annoying, since it requires an action from the
user to dismiss the dialog.
Another common choice is the Beep routine, which does not require the user
to acknowledge it. But it also has problems: not all users have speakers, or
have them turned on, or adequate volume or the ability to hear at all. And
even when all that is fine, a simple chirp does little to indicate what the
problem is.
A somewhat better solution is to make visible an error text somewhere on the
active form, explaining what's wrong. This does not irritate the user with
the necessity to dismiss a dialog and clearly indicates what is amiss. But
it makes life more difficult for the programmer: the error message must be
cleared away at some point, and since there is no way to predict the user's
next action, the message-clearing code must be invoked from all possible
routines accessible to the user. When new actions are added, they must
include calls to the clearing code, which is easy to forget, and leaving a
glaring error message displayed until the user does something, anything, can
make the form look unattractive.
I have recently started using a method which addresses all these problems. I
have written a routine which, given a control, changes the color of the
control to something specified, then slowly fades it back to it's previous
state. My first use was the background behind a set of toggle buttons which,
when depressed, can cause certain lookups to fail. Rather than shouting an
error dialog and forcing the user to deal with it, I flash the background
behind the group of toggle buttons red, and let it slowly (about ? second)
fade back to its normal background gray. It shows the user where the problem
is (a depressed toggle somewhere in the group) and does not require any
action on his part to acknowledge it. The effect is somewhat like a caution
light that flashes on and dims away to nothing, sort of a visual chime, but
specifically indicating where there is a problem. I find it much more
pleasing and less intrusive than any other notification method I've invented
so far.
Naturally, this presumes some level of sophistication on the part of the
user. A complete rookie could not be expected to understand the significance
of the color flash, but this is intended to be a low-key reminder to
experienced users that they've overlooked something. Once they are aware of
the problem, they know what to do about it. Counting code could be added to
display a more detailed error message if the same mistake was repeated
several times, to accommodate less experienced users. Instructions would
then be fairly simple - if you don't understand what's wrong when you've
made a mistake, just repeat the action once or twice and the software will
go into more detail about it for you.
The code was designed to flash an area, but it can certainly be modified to
display error text messages in the same way as it now does background -
flash on, then fade away. The time delay should be adequate to allow the
user to read the message, but not so long as to unnecessarily delay his
progress. That pretty much means it should be used only for very short
texts, which can be comprehended at a glance, else the user may have to
repeat the offending action, maybe even several times, to read the entire
message. I've had personal experiences with devices that acted that way, and
can testify that such behavior raises the aggravation factor of using a
device by a lot.
The code calculates the difference between the current color value and the
requested color value for all three color channels, sets the new color, then
loops around stepping the three channels back to their original state. Each
color channel is computed and incremented separately, else the color change
would not be smooth. It does not matter what the original color or
notification color are - the code can handle any combination of colors. I
have it defaulting to red if the flash color is not specified.
Constants are included to control the speed and smoothness of the fade. The
nap value is better to give a fairly constant fade time. If you place too
much reliance on the steps constant to control fade rate, it will vary based
on the abilities of the computer. Nap uses a fixed pause time and so is more
uniform across machines of varying capability. The Sleep API declaration is
only necessary if you want the routine to use this. If not, you can remove
it and the Sleep statement (the first statement inside the Do loop).
My only gripe with the routine is a slight screen flicker during the fade. I
someone knows how to fix that, I'd be happy to hear about it.
Pete
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Sub FlashBlock(ByRef ctl As Control, Optional ByVal fc& = vbRed)
Dim oc&, ro&, go&, bo&
Dim rf!, gf!, bf!, rs!, gs!, bs! ' These must be reals, integers would be
too readily truncated to unusable values.
Const steps& = 15, nap& = 50
' Audio cue, just as an extra
Beep
oc = ctl.BackColor
ctl.BackColor = fc
' Old color values
ro = vbRed And oc
go = (vbGreen And oc) \ 256
bo = (vbBlue And oc) \ 65536
' New color values, which will be stepped back toward the old ones.
rf = vbRed And fc
gf = (vbGreen And fc) \ 256
bf = (vbBlue And fc) \ 65536
' Step values to march back towards the original color.
rs = (rf - ro) / steps
gs = (gf - go) / steps
bs = (bf - bo) / steps
' Cycle around stepping the new color values back towards the old one until
all have returned to their original value.
' Real arithmetic requires some adjusting, else floating point imprecision
might not get us exactly back to our stating values.
' Abs is necessary, since we don't know whether the new values are greater
or smaller than the old values.
Do
If nap > 0 Then Sleep nap
If ro <> rf Then If Abs(ro - rf) > Abs(rs) Then rf = rf - rs Else rf =
ro
If go <> gf Then If Abs(go - gf) > Abs(gs) Then gf = gf - gs Else gf =
go
If bo <> bf Then If Abs(bo - bf) > Abs(bs) Then bf = bf - bs Else bf =
bo
ctl.BackColor = RGB(Int(rf), Int(gf), Int(bf))
DoEvents ' Necessary to let go, so that the display actually gets
updated.
Loop Until ro = rf And go = gf And bo = bf
End Sub