Thursday, January 24, 2008

WM_WINDOWPOSCHANGED.NET

A list view control is handy, but sometimes it needs a little black magic to work properly.

SetStyle (ControlStyles.OptimizedDoubleBuffer, true) ;

in the constructor takes care of the general flicker problem (in Win32, you need to request 6.0 common controls with a manifest and set the LVS_EX_DOUBLEBUFFER style). It seems that suppressing background painting in addition to double-buffer is overkill. Message sniffing and disassembly of comctl32.dll indicates that the background processing is different in 6.0 common controls with «double-buffer» vs. earlier versions. I quote «double-buffer», because I am not sure how much double-buffering is actually involved.

However, if you use the list view in report mode and want to have your columns adjust to the list view's width, you need more magic. The simple solution — setting column width(s) in OnResize — leads to nasty horizontal scrollbar glitching when you shrink the list view. Shuffling the column-resizing code around the various resizing handlers did no good whatsoever. A couple of hours of tedious debugging uncovered the proximate source of this glitch: after the list view gets resized and receives the WM_WINDOWPOSCHANGED message, it passes the message to the list view's original window procedure in comctl32.dll, which faithfully paints the offending horizontal scrollbar, because the columns are now too wide and the OnResize handler wasn't called yet. After this, another, nested WM_WINDOWPOSCHANGED occurs, and this one finally calls UpdateBounds, which calls the OnResize handler. And then the handler of the original WM_WINDOWPOSCHANGED calls UpdateBounds, and thus your handler, again! Why does it work this way? It is beyond me. Conclusion: it seems to be impossible to resize columns properly in response to any resize event.

The remedy is simple enough: run the column-resizing code before the list view is resized if the columns need to be narrower, and after it is resized if the columns need to be wider. The place to do this is the SetBoundsCore function, which is, luckily, virtual:
protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
{
int newClientWidth = EstimateNewClientWidth (width, height) ;
if (newClientWidth < ClientSize.Width)
{
// Reduce column width first to prevent horizontal scrollbar flicker.
FixColumnWidths (newcx) ;
base.SetBoundsCore (x, y, width, height, specified) ;
}
else
{
base.SetBoundsCore (x, y, width, height, specified) ;
FixColumnWidths (ClientSize.Width) ; // the new client width!
}
}

The remaining tricks are in the EstimateNewClientWidth function, which has to guess whether there was a scrollbar before the resize and whether there will be one after it:
private int EstimateNewClientWidth (int width, int height)
{
if (Items.Count == 0) return ClientSize.Width ;

// Estimate the new client size from the old one and the window size change.
int oldcx = ClientSize.Width ;
int oldcy = ClientSize.Height ;
int newcx = oldcx + width - Bounds.Width ;
int newcy = oldcy + height - Bounds.Height ;
int hdrcy = GetHeaderHeight () ;

Rectangle rect0 = GetItemRect (0, ItemBoundsPortion.Entire) ;
Rectangle rectN = GetItemRect (Items.Count - 1, ItemBoundsPortion.Entire) ;

bool needScrollBar = rect0.Top < hdrcy || rectN.Bottom > newcy ;
bool haveScrollBar = rect0.Top < hdrcy || rectN.Bottom > oldcy ;

// Correct the new client size for vertical scrollbar changes.
if (haveScrollBar)
{
if (!needScrollBar) newcx += SystemInformation.VerticalScrollBarWidth ;
}
else
{
if ( needScrollBar) newcx -= SystemInformation.VerticalScrollBarWidth ;
}

return newcx ;
}

I found no pure .NET method to get the current height of the list view's header control, so I have to resort to P/Invoke:
[StructLayout (LayoutKind.Sequential)]
struct RECT
{
public int left ;
public int top ;
public int right ;
public int bottom ;
}

[DllImport ("user32.dll", EntryPoint = "GetWindowRect")]
static extern int GetWindowRect (IntPtr hWnd, ref RECT rect) ;

[DllImport ("user32.dll")]
static extern IntPtr SendMessageW (IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam) ;

private int GetHeaderHeight ()
{
RECT rect = new RECT () ;
GetWindowRect (SendMessageW (Handle, 0x101F /*LVM_GETHEADER*/, (IntPtr)0, (IntPtr)0), ref rect) ;

return PointToClient (new System.Drawing.Point (rect.left, rect.bottom)).Y ;
}

And that's it for the .NET case ^.^

In Win32, there is a more elegant solution for EstimateNewClientWidth, which does not work under .NET: use SetWindowPos with SWP_NOREDRAW to do a fake resize on the list view, measure its new client width, and fake-resize it back. And instead of the SetBoundsCore, the column-resizing code goes into the parent window's WM_WINDOWPOSCHANGED handler, which is where you resize the list view anyway.

Wednesday, January 9, 2008

"ee"

In 1947, Orwell wrote in «The English People» that the English language gravitates towards simpler grammar and syntax. If this trend continues, he wrote, English would have more in common with isolating languages of East Asia than with Indo-European languages. I will write about one of the steps in this direction, and have some fun in the process.

The other day I was reading something somewhere, and bam! the word attackee (800) leapt out of the page (NB: where available, I give Google hit counts, excluding obvious misspellings, in parenthesized small numerals). I recalled that in a contract I had recently translated there was a payee (1,3M), but it did not strike me then as anything unusual. After all, programmers commonly use callee (61K) and sometimes pointee (36K), personifying functions and objects. But attackee somehow seems a peculiarly ugly word, its sole raison'd'etre being that «X being attacked» and «X under attack» are too long while «defender» has somewhat different meaning. attackee tells us that the -ee suffix, having originally appeared in the French loanword employee (138M), has firmly established itself as an independent lexical unit. If attackee, I thought, why not more? Let's have more fun with -ee! Me and my sister almost laughed our backsides off today thinking up all sorts of -ee words and Googling them.

Fun I



Picrelated: the huntee is not visible at all.


Violent transitive verbs seem to eeify particularly well. Some examples: programmers without respect for language sometimes use trapee; sandbaggee (3) is at least funny; kickee, rapee (the Urban Dictionary has it) and killee are too contaminated with accidental foreign matches to give accurate hit counts; murderee (6K) featured in the Independent (I'm not kidding you!); then there's fuckee (21K) with its gross and sometimes misspelt retinue; and the king of them all, pwnee (3K) — I'll be using this one! «pwnee detected» might even have a chance on the various chans.

Fun II

Besides the venerable employee, economists have payee (1,3M) and mortgagee (1M) and such gems as buyee (10K) and bankruptee (1K), while lawyers hold their own with the likes of contractee (73K), insuree (8K), harassee (4K) and slanderee (70). creditee is too confused with a declension of the French crediter to ascertain its usage; other -ee words suffer from French grammar as well. In the political sphere there is electee (10K), and jokers have invented votee. Generally speaking, just about any economic or legal term with the -er suffix can sprout an -ee sibling and, sooner or later, usually does, for the greater benefit of terminological uniformity.

Fun III

On a more (or rather less) forgiving note, some terminally deaf bonehead thought up the abominable words forgivee (2K) and even lovee — they say 1913 Webster had it! I can't imagine using these words for anyone close or important to me. Their natural habitat is probably restricted to badly written self-help books and similar trash.

Fun IV

Finally, for some random fun try postee, bloggee, googlee, trollee, quittee and decidee (hello George), but don't try firee or dumpee (5K)! Remember, I warned you!

Conclusion

eeified transitive verbs surely add to the vibrancy of the English language, and may sometimes help with terminology; but please do remember that not all of them sound and feel equally good.