This article was first published in 2002 by Reach magazine under the title "XOR No More". It is republished here by their permission.
.NET provides a new API for drawing operations called GDI+. It provides significantly more advanced drawing facilities than its predecessor, GDI, but it does not support an XOR drawing mode. This upsets some developers, but GDI+ provides better alternatives for the scenarios in which XOR was usually used with classic GDI programming.
Classic GDI allowed the screen to be painted using an XOR raster operation (R2_XORPEN
).
The resulting colours would be determined by performing a bitwise exclusive or
operation with the colour already on screen and the colour being drawn with.
The upshot is that if you draw the same thing in the same place twice, you end
up back where you started: the second drawing operation undoes the first. This
makes it very easy to draw a shape, and then to erase it shortly afterwards by
drawing it a second time.
The traditional applications for XOR have been temporary overlays, such as selection outlines. It tends to be used when the mouse is performing a drag operation, to indicate what items will be moved to where, or which items will be selected. The advantage of using XOR drawing is that it is fast: it does not require the window’s entire contents to be redrawn in order to move or remove the overlay. High performance is important when tracking mouse movements, since the application will feel sluggish if it fails to keep up with the mouse.
Windows Forms provides very limited support for this kind of drawing. GDI+ does
not support it at all, but the System.Windows.Forms.ControlPaint
class
supplies three methods which bypass GDI+ in order to perform XOR drawing. The
methods are DrawReversibleLine
, DrawReversibleFrame
,
and FillReversibleRectangle
, which draw lines, and empty and
filled rectangles respectively. You cannot specify the colour—the nature of XOR
drawing makes it impossible to guarantee which colour will be shown in all
cases. Instead you must specify the colour of the background over which the
item will be drawn, and the methods will select a colour which will be visible
when drawn with XOR over that particular background.
These methods draw onto the screen, not into a control. This means that you will
normally need to call your control’s PointToScreen
or RectangleToScreen
method to get the right coordinates. You will also need to perform your own
clipping if you only want to draw into your own window.
The main problem with XOR drawing is alluded to in the previous section—it is not possible to guarantee which colour will appear on screen. The resultant colour is influenced both by the colour you choose to draw with and also the colour that is already present on screen, meaning that any area that is painted with an XOR operation will have different colours according to what was there before. The exact results are determined by the colour depth of the display, and with 8-bit displays, the current palette settings will also have an impact.
Another problem with XOR drawing is that it tends to flicker. Because all drawing is being done directly onto the screen, we cannot take advantage of Windows Forms’ automatic double buffering. Whilst certain tricks can be employed to reduce the flicker, it can never be entirely eliminated when drawing with XOR.
It is also extremely easy to get redraw bugs with XOR-based overlays. These most
often manifest themselves when a redraw operation occurs whilst an overlay is
visible. For example, an email notification might pop-up and disappear whilst
an overlay is shown, causing the area under the pop-up to be redrawn. If
overlay was larger than the area that gets redrawn, it becomes difficult to put
the overlay back reliably, because the relevant ControlPaint
methods
provide no way to set a clip rectangle. Many applications that use XOR drawing
fail to deal with this, and simply leave a mess in the window when this
happens.
Given the restrictions and pitfalls, XOR-based drawing would have to look pretty
compelling compared to the alternative for it to be worth using. So let us
consider the alternative. Rather than using XOR, we can just draw the overlay
in our application’s normal redraw code (the OnPaint
method, or Paint
event handler). We simply repaint the control (i.e. call Invalidate()
)
whenever the overlay needs to be moved or removed.
This technique has several advantages. First, it means we can draw in whatever
colour we like. If we will be drawing over a background with a wide variety of
colours, we should draw the overlay in multiple colours; an alternating pale
and dark outline is a good choice. (This is most easily achieved by using a HatchBrush
,
which lets us draw patterns with two colours. The DarkDownwardDiagonal
hatch style works well for selection outlines.) This ensures that the outline
will be visible over both dark and light backgrounds; selection outlines drawn
using XOR have an unhelpful tendency to be invisible over certain background
colours.
We can also draw using any style supported by GDI+, and with shapes more exotic than simple lines or rectangles. For example, we could draw a transparent version of the items being dragged, like current version of Windows Explorer does when dragging files.
Also, because we are drawing using a normal Graphics
object for our
control, our drawing will automatically be clipped, and we will not need to
perform any coordinate translation. Furthermore, because we are doing the
drawing in the normal OnPaint
method or Paint
event
handler, our overlay will continue to be drawn correctly even if any unexpected
redraws occur (e.g. due to email notification pop-ups). So this approach is
intrinsically less likely to suffer from redraw bugs.
So given all of these advantages, why would we ever use the much more
troublesome and restrictive XOR approach? The only reason is that this approach
is more CPU intensive. Instead of just drawing the overlay, everything
underneath the overlay must be redrawn every time the overlay needs to be
moved. (This doesn’t necessarily mean redrawing the entire control—the Control
class’s Invalidate
method allows us to be selective about which
areas are redrawn.) This could potentially lead to even more flicker than
XOR-based overlay drawing, but that is easily addressed by enabling double
buffering on the relevant control. In fact double buffering is the superior
solution for avoiding flicker because it will eliminate it entirely, which
cannot be achieved with an XOR solution.
So in practice XOR has just one advantage: speed. But experience shows that for the vast majority of applications this is not an issue. Redrawing used to be a slow operation, and back in the days when Windows 3.1 shipped, XOR-based drawing was the only realistic way of drawing an overlay fast enough to keep up with the mouse. But machines are now substantially faster, and it is entirely practical to repaint everything under the overlay and still track the mouse with no perceptible lag. In fact a modern PC is fast enough to use advanced effects such as transparency on the overlay and still keep up. Given how much better eschewing XOR can make an application look and feel, there really is no excuse for using XOR any more.