I was adding a Wizard to one of my applications, and the final
screen of this Wizard was a summary of the user's choices. I
wanted the user to be able to copy this to the Clipboard if
required, and so I'd used a
TextBox rather than the
I might have otherwise used. However, this presented a minor
issue as I'd chosen to use tabs to delimit the information, and
the varying length of text meant that this wasn't aligned as
Previously I have "dealt" with this issue by cheating - I'd just add extra tabs to force everything to line up. However, I do plan on fully localising this application at some point, not to mention that even using a different font could potentially trigger the text to misalign once more. And so I decided to do it properly this time.
I knew that Win32
Edit controls (of which the Windows Forms
TextBox wraps) supported customising tab stops, but this was
functionality the Framework developers did not expose.
RichTextBox offers the ability to customise tab
stops, but at a selection level and I really couldn't be fussed
with that approach. So I decided to directly invoke the Win32
API to set the tab stops in a
TextBox control. How hard could
I already knew in principle how to do this, by using the
SendMessage API call and the
although I didn't know the specifics. On checking the
documentation for the message, it stated that when calling
SendMessage with this message,
wParam is used to define how
the tab stops are set, whilst
lParam has the tab stop values.
The following operations are available
|wParam Value||lParam Value||Description|
|0||0||Resets tab stops to default, which is every 32 dialog units|
|1||integer||Sets all tab stops to be every integer dialog units|
|2 or more||integer||Sets custom tab stops (in dialog units) using each value in integer. Although not explicitly documented, the last value in the array is used for any additional tabs the user enters into the control|
Incidentally, there's an odd omission. Almost invariably with the Win32 API, if there's a SET message, there's usually a corresponding GET as well, e.g.
WM_GETTEXT. For some reason though, there is no
EM_GETTABSTOPS- as far as I can tell, there isn't a way of getting tab stop information.
EM_SETTABSTOPS can be called several ways, I'm going to
define 3 different overloads of
The documentation for the message states that the
EM_SETTABSTOPSmessage doesn't cause the
Editcontrol to be refreshed, although in my testing this didn't seem to be the case - the control was always repainted. My assumption is the
TextBoxcontrol is refreshing itself when receiving certain messages, however I have chosen to also explicitly request a repaint by calling
If you want all tab stops to be the same fixed value with no
variations, you send
wParam set to
lParam to the new tab size.
To specify tab stops of different sizes, you send
wParam with a value greater than one,
lParam with an array of tab stop positions.
Although the documentation states that this should be greater than one, I observed (on Windows 10 1809) that unless
wParamwas equal to the length of the array I was passing in, the control didn't behave exactly as expected.
Important! The array of tab stops is specified as absolute positions, not the size of each stop, e.g each item in the array should be the sum of all previous entries plus the size of the tab stop. So for example, if you wanted tab sizes of
60dialog units, the values to send would be
To reset tab stops, we send the
EM_SETTABSTOPS message to our
TextBox with a
wParam value of
lParam is unused in
this case so I send
0 as well.
I mentioned above that tab stops are expressed in terms of dialog units. But what are these? I certainly wasn't familiar with them, pretty much all API calls I've ever used express these types of values in plain pixels.
I can't actually find a dedicated topic in Microsoft's documentation, but essentially a dialog unit is a device independent way of specifying position and size information for dialog controls. I believe they are mostly used in resource templates, but as these are generally the province of C++ applications they aren't actually something I've used before.
As to the actual values of a dialog unit, they are equal to the average width, in pixels, of the characters in the font used by the dialog; the vertical base unit is equal to the height, in pixels, of the font.
There are also a set of base units, which are the same calculations but for the system font. I wonder how many modern Windows developers have seen the system font, given it hasn't been in widespread used since Windows 3.0!
In order to convert from dialog units to pixels, you are
supposed to be able to use the
MapDialogRect function, by
passing in the dialog handle and a
RECT structure populated
with the values to convert. The function will modify the
with the converted values, at least in theory. Unfortunately
this is where things get complicated as the documentation states
this function accepts only handles returned by one of the
dialog box creation functions; handles for other windows are not
valid. However, as Windows Forms doesn't use the dialog
functions for creating windows, I was not able to get this API
call to perform a conversion.
The documentation for
GetDialogBaseUnits notes that you
GetTextMetrics to calculate the values for a given
font. I'd already written about this some time ago and so I
did test this but the results were inconclusive, so I wonder if
MapDialogRect is doing additional actions rather than just
returning the average.
(In a move that doesn't surprise me in the slightest given my experiences with this functionality so far, there isn't a function to take pixel values and convert them into dialog units.)
Fortunately, it doesn't really matter1 as I suppose you don't really want pixel perfect tab stops. I certainly didn't, I just wanted rough values so "columns" would line up, and we can do a naive characters to dialog units conversion by multiplying the number of characters by 4. Not perfect, but good enough.
I mentioned in the article preamble that I wanted to reformat summary text so that columns would align. Below is a function that will calculate rough tab stop positions based on a text string.
Note: This is rough and ready code and is doing a lot of string manipulation via
string.Splitso isn't very efficient at all. Originally I was going to count characters from tabs to avoid any type of string processing at all, but I've more than ran out of the time I'd allocated for this article.
As usual, a demonstration program is available for the link
below, including a helper class for adding some useful tab stop
related extension methods to the
1. Also, I lied. I suppose if you were
displaying a ruler for a word processor then you'd very much
want pixel perfect tab stops. I did try doing a basic ruler for
this demonstration, but ended up having to cheat the
calculations as I could get
MapDialogRect to work, I didn't
have time to do things like add scrolling support and at the end
of the day it didn't really add much to the demo.
- 2019-05-25 - First published
- 2020-11-22 - Updated formatting