Showing posts with label mundane. Show all posts
Showing posts with label mundane. Show all posts

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.

Monday, November 5, 2007

16:10 amenities


Two big Far's fit nicely side-by-side on my 2007WFP. Now I have two Far's on my start menu, a right Far and a left Far, and two keyboard shortcuts. The only drawback of this arrangement is that panel operations between the two Far's don't work as visually expected. I had to resource-hack two copies of Far.exe for full effect, though. Gaheh ^..^

Friday, October 12, 2007

Gentleman's things

Came back from a visit to friends' with these picturesque items.

Sister's bag
Radish
New 500GB hard drive
Spectacles

Saturday, October 6, 2007

Claymore Extra Scene 4

Yippee! Claymore Extra Scene 4 is out. As usual, the Chinese got it scanned and online first[1], or so I gather. It's not even half bad. Now it's until November 2 when hopefully we'll get Chapter 74.
A pity the anime directors decided to modify the plot so much in the final episodes (and that fight amidst lava looked so cheap after Star Wars III...) There's no way they can go back to the manga now for the second season which their half-assed ending seems to suggest. Besides, in action manga it seems that the normal rate is 3 chapters per episode (with non-action manga it's more like 1 chapter per episode), so they'd have to accumulate 3 years worth of monthly instalments of the manga to get enough material for even a 13-episode season. Business, meh.

[1] Claymore Extra Scene 4 – link is for informative purposes only.

Finally, a new monitor


Finally got my backside off the chair and ordered this Dell UltraSharp 2007WFP to replace my ancient ViewSonic pf775. Also, my anime collection has expanded so much that I had to order a 500Gb hard drive in addition to the 300Gb one I have now. I hate keeping stuff on DVD-Rs and similar media. HDDs are only marginally more expensive per Gb, they don't scratch, take up less space and are always available.