Auto scroll horizontally while adding columns to a ListView - c#

I want to auto scroll the ListView horizontally to the last column every time a ColumnHeader is added.
I need only to scroll to the last Column, not to the item.
listView1.Alignment is set to Left
listView1.View is set to View.Details
I tried this, but it doesn't do anything:
listView1.AutoScrollOffset = new Point(listView1.AutoScrollOffset.X-10, 0);`
This only works if I add only items:
listView1.EnsureVisible(0);
string rowstr = "Test,";
for (var i = 0; i < 10; i++)
{
Debug.WriteLine(i);
ColumnHeader head = new ColumnHeader();
head.Text = i.toString();
listView1.Columns.Add(head);
listView1.Columns[i].Width = 65;
rowstr += "Test"+",";
string[] row = rowstr.Split(",");
var listViewItem = new ListViewItem(row);
listViewItem.Font = new Font("Consolas", 10f);
listView1.Items.Insert(0, listViewItem);
//listView1.EnsureVisible(0);
listView1.AutoScrollOffset = new Point(listView1.AutoScrollOffset.X-10, 0);
}
I didn't try to override WndProc or to call a User32 function, but if this is the only option any help is appreciated.
If it's possibile without PInvoking, that would be better.

To scroll horizontally a ListView, you can send a LVM_SCROLL message to the Control, setting wParam to a value that correspond to position (in pixels) to scroll to.
The position is relative to the current offset.
Set lParam to scroll vertically.
Since you want to scroll to the last Column and the ListView is in Details mode, you can just pass int.MaxValue as the offset: the Win32 Control will do the adjustment (it does it anyway).
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, int lParam);
private const int LVM_SCROLL = 0x1014;
SendMessage(listView1.Handle, LVM_SCROLL, int.MaxValue, 0);
EDIT:
Modify your code, adding an interval between the each Column/ListItem insertion, to see it in slow motion. You can use a Button.Click handler.
private async void SomeButton_Click(object sender, EventArgs e)
{
for (int idx = 0; idx < 10; idx++) {
var head = new ColumnHeader() {
Text = idx.ToString(),
Width = 65
};
listView1.Columns.Add(head);
SendMessage(listView1.Handle, LVM_SCROLL, int.MaxValue, 0);
var rowArray = new List<string>(Enumerable.Range(0, idx + 1).Select(n => $"Test{n}"));
var listViewItem = new ListViewItem(rowArray.ToArray());
listView1.Items.Insert(0, listViewItem);
SendMessage(listView1.Handle, LVM_SCROLL, int.MaxValue, 0);
await Task.Delay(500);
}
}
Remove the async stuff to let it go free.
To get the current scroll position, if needed, use GetScrollInfo(). E.g.,
var scrollInfo = new SCROLLINFO(SBInfoMask.SIF_ALL);
bool result = GetScrollInfo(listView1.Handle, SBParam.SB_HORZ, ref scrollInfo);
The nPos member of the SCROLLINFO struct returns the current scroll position. nMax the maximum scroll value. Subtract nPage, corresponding to the Control's ClientSize.Width, to get the maximum possible scroll value.
It should be equal to nPos when the thumb is scrolled to the end.
Win32 declarations:
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetScrollInfo(IntPtr hwnd, SBParam nBar, [In, Out] ref Point lpsi);
[StructLayout(LayoutKind.Sequential)]
internal struct SCROLLINFO
{
public uint cbSize;
public SBInfoMask fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
public SCROLLINFO(SBInfoMask mask)
{
cbSize = (uint)Marshal.SizeOf<SCROLLINFO>();
fMask = mask;
nMin = 0; nMax = 0; nPage = 0; nPos = 0; nTrackPos = 0;
}
}
internal enum SBInfoMask : uint
{
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS),
SIF_POSRANGE = (SIF_RANGE | SIF_POS | SIF_PAGE)
}
public enum SBParam : int
{
SB_HORZ = 0x0,
SB_VERT = 0x1,
SB_CTL = 0x2,
SB_BOTH = 0x3
}

You can create a CustomListView and Re_Define the EnsureVisible method
class DataFlowFilterListView : ListView
{
/// <summary>
/// margin from the selected column to the border of listview.
/// </summary>
const int MARGIN = 20;
/// <summary>
/// native windows message to scroll the listview.
/// </summary>
const Int32 LVM_FIRST = 0x1000;
const Int32 LVM_SCROLL = LVM_FIRST + 20;
[DllImport("user32")]
static extern IntPtr SendMessage(IntPtr Handle, Int32 msg, IntPtr wParam,
IntPtr lParam);
private void ScrollHorizontal(int pixelsToScroll)
{
SendMessage(this.Handle, LVM_SCROLL, (IntPtr)pixelsToScroll,
IntPtr.Zero);
}
/// <summary>
/// Ensure visible of a ListViewItem and SubItem Index.
///
///
/// </summary>
/// <param name="item"></param>
/// <param name="subItemIndex"></param>
public void EnsureVisible(ListViewItem item, int subItemIndex)
{
if (item == null || subItemIndex > item.SubItems.Count - 1)
{
throw new ArgumentException();
}
// scroll to the item row.
item.EnsureVisible();
ScrollToRectangle(item.SubItems[subItemIndex].Bounds.Width);
}
/// <summary>
/// Scrolls the listview.
/// </summary>
/// <param name="bounds"></param>
private void ScrollToRectangle(int width)
{
this.ScrollHorizontal(width);
}
}
Now you can use DataFlowFilterListView instead of ListView.
Execute the following code each time you add a column to the list
listView1.EnsureVisible(listView1.Items[0], 0);

My solution is using user32 function and SendMessage().
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
private const int WM_HSCROLL = 0x114; //for horizontal scroll
private const int WPARAM = 7; //to scroll to end
Implementation:
string rowstr = "Code,"
for (var i = 0; i < 10; i++)
{
ColumnHeader head = new ColumnHeader();
head.Text = i.toString();
listView1.Columns.Add(head);
listView1.Columns[i].Width = 65;
rowstr += "Test"+",";
string[] row = rowstr.Split(",");
var listViewItem = new ListViewItem(row);
listViewItem.Font = new Font("Consolas", 10f);
listView1.Items.Insert(0, listViewItem);
SendMessage(listView1.Handle, WM_HSCROLL, WPARAM, 0);
}

Related

How to load lots of icons into ListView quickly from files dragged & dropped?

I would like to load the icons from multiple files dragged onto a listview and some of the information of those files, but the process is going very slow.
In my test, a list of 381 files dragged (some are not exe so those are skipped in my code), takes over 2 minutes to load the icon from the file, and add them to the listview.
Following is condensed version of my code :
public Form1()
{
InitializeComponent();
listView1.DragEnter += ListView1_DragEnter;
listView1.DragDrop += ListView1_DragDrop;
listView1.LargeImageList = new ImageList() { ImageSize = new Size( 64, 64) };
listView1.SmallImageList = new ImageList();
listView1.AllowDrop = true;
}
private void ListView1_DragDrop(object sender, DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] values = (string[])e.Data.GetData(DataFormats.FileDrop);
Convert.IconExtractor i = new Convert.IconExtractor();
foreach (var v in values)
{
var info = new FileInfo(v);
if(info.Extension.ToLower() == ".exe")
{
ListViewItem item = new ListViewItem();
listView1.LargeImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Large));
listView1.SmallImageList.Images.Add(i.Extract(info.FullName, Convert.IconSize.Small));
item.Text = Path.GetFileNameWithoutExtension(info.FullName);
item.ImageIndex = listView1.SmallImageList.Images.Count -1;
item.Tag = info.FullName;
listView1.Items.Add(item);
}
}
listView1.Refresh();
}
}
private void ListView1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.All;
}
}
The method used for extraction is pasted here for convenience :
public enum IconSize
{
Small,
Large
}
public class IconExtractor
{
//----------------------------------------------------------------------------
//
// Description: Extracts the icon associated with any file on your system.
// Author: WidgetMan http://softwidgets.com
//
// Remarks...
//
// Class requires the IconSize enumeration that is implemented in this
// same file. For best results, draw an icon from within a control's Paint
// event via the e.Graphics.DrawIcon method.
//
//----------------------------------------------------------------------------
private const int SHGFI_ICON = 0x100;
private const int SHGFI_SMALLICON = 0x1;
private const int SHGFI_LARGEICON = 0x0;
private struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public int dwAttributes;
[VBFixedString(260), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[VBFixedString(80), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[DllImport("shell32", EntryPoint = "SHGetFileInfoA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int ByValcbFileInfo, int uFlags);
public IconExtractor()
{
}
public System.Drawing.Icon Extract(string File, IconSize Size)
{
SHFILEINFO aSHFileInfo = default(SHFILEINFO);
int cbFileInfo = 0;
int uflags = 0;
System.Drawing.Icon Icon = default(System.Drawing.Icon);
switch (Size)
{
case IconSize.Large:
uflags = SHGFI_ICON | SHGFI_LARGEICON;
break;
default:
uflags = SHGFI_ICON | SHGFI_SMALLICON;
break;
}
cbFileInfo = Marshal.SizeOf(aSHFileInfo);
SHGetFileInfo(File, 0, ref aSHFileInfo, cbFileInfo, uflags);
Icon = System.Drawing.Icon.FromHandle(aSHFileInfo.hIcon);
return Icon;
}
public System.Drawing.Icon Extract(string File)
{
return this.Extract(File, IconSize.Small);
}
}
}
What can I do to make this process quick. The concept is to make a quick launcher for multiple applications.
Also of note, while this process is happening, the drag-collection icon is still 'hung' on windows explorer until the drag & drop task has completed fully (looped through all the files).
Here is a rough draft to give a visual of the application:
(yes, i know the icons extracted look like crap as well, but I think that is a separate issue from the slow issue I am having)
My advice is not to load all 381 icons in one step, particularly if the items are not visible. Load them on demand as items are scrolled into view, of if the view is large enough, load them in the background using your threading/task/concurrency technology of choice.
That's what Windows does.
To speed up loading, you may want to use the System Image List which would most likely benefit from caching. Here's some code for getting the large icon. Just change size to be SHGFI_ICON for your use.
public static Icon GetLargeIcon(string FileName, bool jumbo, bool useFileAttributes=false)
{
var shinfo = new SHFILEINFO();
uint flags;
flags = SHGFI_SYSICONINDEX;
if (useFileAttributes)
{
flags |= SHGFI_USEFILEATTRIBUTES;
}
var res = SHGetFileInfo(FileName, FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint) Marshal.SizeOf(shinfo), flags);
if (res == IntPtr.Zero)
{
throw (new FileNotFoundException());
}
var iconIndex = shinfo.iIcon;
// Get the System IImageList object from the Shell:
var iidImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
IImageList iml;
var size = jumbo
? SHIL_JUMBO
: SHIL_EXTRALARGE;
var hres = SHGetImageList(size, iidImageList, out iml);
if (hres != 0)
{
throw (new Exception("Error SHGetImageList"));
}
IntPtr hIcon;
const int ILD_TRANSPARENT = 1;
hres = iml.GetIcon(iconIndex, ILD_TRANSPARENT, out hIcon);
var icon = Icon.FromHandle(hIcon);
icon = icon.Clone() as Icon;
var bm = icon.ToBitmap();
DestroyIcon(hIcon);
return icon;
}

How to set the client area (ClientRectangle) in a borderless form?

I want to know if it's possible to set the client area of a borderless form. Say for example I define a form like so:
Code
public class MyForm : Form
{
public MyForm()
{
this.FormBorderStyle = FormBorderStyle.None;
}
}
Result
What I want to do is specify the client area, so that the form has a frame (like the standard windows frame, but custom drawn).
Result
Essentially, the blue area would become the non client area, and the gray area would remain as the client area.
I have tried to set the client area, but this just seems to resize the entire form, thus, is does not leave behind a "non-client" area
Is this possible?
This is possible, however I don't know how well this works with a Windows Form with the WindowStyle set to Borderless. Using PInvoke (Platform Invoke) Functions, you can remove window themes which will give you a very basic looking Windows Form. You can then use various PInvoke functions to manipulate the Non-client area of the windows form.
I recommend that you read through these topics. They're designed for Win32 Applications using C++, but PInvoke is the process of calling these native APIs using Managed Code (C#)
WM_NCCALCSIZE: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632634(v=vs.85).aspx
WM_NCPAINT: https://msdn.microsoft.com/en-us/library/windows/desktop/dd145212(v=vs.85).aspx
GetDCEx: https://msdn.microsoft.com/en-us/library/windows/desktop/dd144873(v=vs.85).aspx
GetWindowDC: https://msdn.microsoft.com/en-us/library/windows/desktop/dd144947(v=vs.85).aspx
SetWindowTheme: https://msdn.microsoft.com/en-us/library/windows/desktop/bb759827(v=vs.85).aspx
This example is very very crude, but it provides basic functionality. I don't know how SetWindowTheme works on Windows 8 or 8.1, but in Windows 7, it gives windows the "classic" theme.
public partial class MyForm : Form
{
//Window Messages
public const uint WM_NCPAINT = 0x85;
public const uint WM_NCCALCSIZE = 0x83;
public const uint WM_NCHITTEST = 0x84;
//GetDCEx Flags
public const int DCX_WINDOW = 0x00000001;
public const int DCX_CACHE = 0x00000002;
public const int DCX_PARENTCLIP = 0x00000020;
public const int DCX_CLIPSIBLINGS = 0x00000010;
public const int DCX_CLIPCHILDREN = 0x00000008;
public const int DCX_NORESETATTRS = 0x00000004;
public const int DCX_LOCKWINDOWUPDATE = 0x00000400;
public const int DCX_EXCLUDERGN = 0x00000040;
public const int DCX_INTERSECTRGN = 0x00000080;
public const int DCX_INTERSECTUPDATE = 0x00000200;
public const int DCX_VALIDATE = 0x00200000;
//RECT Structure
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct RECT
{
public int left, top, right, bottom;
}
//WINDOWPOS Structure
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndinsertafter;
public int x, y, cx, cy;
public int flags;
}
//NCCALCSIZE_PARAMS Structure
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct NCCALCSIZE_PARAMS
{
public RECT rgrc0, rgrc1, rgrc2;
public WINDOWPOS lppos;
}
//SetWindowTheme UXtheme Function
[System.Runtime.InteropServices.DllImport("uxtheme.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
public static extern int SetWindowTheme(
IntPtr hWnd,
String pszSubAppName,
String pszSubIdList);
//GetWindowRect User32 Function
[System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true)]
[return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
public static extern bool GetWindowRect(
IntPtr hwnd,
out RECT lpRect
);
//GetWindowDC User32 Function
[System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true)]
public static extern IntPtr GetWindowDC(
IntPtr hWnd
);
//GetDCEx User32 Function
[System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true)]
public static extern IntPtr GetDCEx(
IntPtr hWnd,
IntPtr hrgnClip,
int flags
);
//Window Procedure Hook
protected override void WndProc(ref Message m)
{
//Don't style window in designer...
if (DesignMode)
base.WndProc(ref m);
//Handle Message
switch ((uint)m.Msg)
{
case WM_NCCALCSIZE: WmNCCalcSize(ref m); break;
case WM_NCPAINT: WmNCPaint(ref m); break;
default: base.WndProc(ref m); break;
}
}
//Handle Creation
protected override void OnHandleCreated(EventArgs e)
{
//Base Procedure...
base.OnHandleCreated(e);
//Remove Theme
SetWindowTheme(this.Handle, string.Empty, string.Empty);
}
//WM_NCCALCSIZE
private void WmNCCalcSize(ref Message m)
{
//Get Window Rect
RECT formRect = new RECT();
GetWindowRect(m.HWnd, out formRect);
//Check WPARAM
if (m.WParam != IntPtr.Zero) //TRUE
{
//When TRUE, LPARAM Points to a NCCALCSIZE_PARAMS structure
var nccsp = (NCCALCSIZE_PARAMS)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
//We're adjusting the size of the client area here. Right now, the client area is the whole form.
//Adding to the Top, Bottom, Left, and Right will size the client area.
nccsp.rgrc0.top += 30; //30-pixel top border
nccsp.rgrc0.bottom -= 4; //4-pixel bottom (resize) border
nccsp.rgrc0.left += 4; //4-pixel left (resize) border
nccsp.rgrc0.right -= 4; //4-pixel right (resize) border
//Set the structure back into memory
System.Runtime.InteropServices.Marshal.StructureToPtr(nccsp, m.LParam, true);
}
else //FALSE
{
//When FALSE, LPARAM Points to a RECT structure
var clnRect = (RECT)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(RECT));
//Like before, we're adjusting the rectangle...
//Adding to the Top, Bottom, Left, and Right will size the client area.
clnRect.top += 30; //30-pixel top border
clnRect.bottom -= 4; //4-pixel bottom (resize) border
clnRect.left += 4; //4-pixel left (resize) border
clnRect.right -= 4; //4-pixel right (resize) border
//Set the structure back into memory
System.Runtime.InteropServices.Marshal.StructureToPtr(clnRect, m.LParam, true);
}
//Return Zero
m.Result = IntPtr.Zero;
}
//WM_NCPAINT
private void WmNCPaint(ref Message m)
{
//Store HDC
IntPtr HDC = IntPtr.Zero;
Graphics gfx = null;
//Check the WPARAM
if(m.WParam == (IntPtr)1)
{
//For reasons unknown to me, the update region doesn't contain valid data and calling GetDCEx will do nothing.
//So I call GetWindowDC and exclude the area using System.Drawing.Graphics instead.
//Graphics Object from HDC
HDC = GetWindowDC(m.HWnd);
gfx = Graphics.FromHdc(HDC);
//Exclude Client Area
gfx.ExcludeClip(new Rectangle(4, 30, Width - 8, 34)); //Exclude Client Area (GetWindowDC grabs the WHOLE window's graphics handle)
}
else
{
//Graphics Object from HDC
HDC = GetDCEx(m.HWnd, m.WParam, DCX_WINDOW | DCX_INTERSECTRGN);
gfx = Graphics.FromHdc(HDC);
}
//Call Paint
using (PaintEventArgs ncPaintArgs = new PaintEventArgs(gfx, new Rectangle(0, 0, Width, Height)))
MyForm_NCPaint(this, ncPaintArgs);
//Return Zero
m.Result = IntPtr.Zero;
}
public MyForm()
{
InitializeComponent();
}
private void MyForm_NCPaint(object sender, PaintEventArgs e)
{
//Clear
e.Graphics.Clear(Color.Green);
}
}

Textbox - Maintain scroll bar position during text updates

I have to show information to the user that updates every 100 milliseconds, this means the contents of the textbox I show it in are constantly being changed and if they are scrolling through them when they are being changed the update will cause them to loose their scroll bar position
How do I prevent this? I've reduced the effect a lot by adding all text at once.
Current code:
string textboxStr = "";
foreach (string debugItem in debugItems)
{
textboxStr += debugItem + Environment.NewLine;
}
debugForm.Controls[0].Text = textboxStr;
Update 1:
Used the solution provided below and it's not working, the scroll bar still resets to its default position meaning you loose your position and your pointer resets too.
Implementation:
In class:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
In function:
var originalPosition = ((TextBox)debugForm.Controls[0]).SelectionStart;
LockWindowUpdate(((TextBox)debugForm.Controls[0]).Handle);
debugForm.Controls[0].Text = textboxStr;
((TextBox)debugForm.Controls[0]).SelectionStart = originalPosition;
((TextBox)debugForm.Controls[0]).ScrollToCaret();
LockWindowUpdate(IntPtr.Zero);
Update 2:
Used the 2nd solution provided below and it's not working. The scroll bar still jumps to the top even mid way while scrolling. Then sometimes when you're not even on it the scroll bar will start jumping up and down (Every 100 ms, when it updates the text).
Implementation:
In class:
[DllImport("user32.dll")]
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
private const int SB_VERT = 0x1;
private const int SB_THUMBPOSITION = 4;
private const int WM_VSCROLL = 0x115;
In function:
var currentPosition = GetScrollPos(debugForm.Controls[0].Handle, SB_VERT);
debugForm.Controls[0].Text = textboxStr;
SetScrollPos(debugForm.Controls[0].Handle, SB_VERT, currentPosition, false);
PostMessageA(debugForm.Controls[0].Handle, WM_VSCROLL, SB_THUMBPOSITION + 65535 * currentPosition, 0);
Example text:
Active Scene: Level0
--------------------------------------------------
Settings
Fps: 60
GameSize: {Width=600, Height=600}
FreezeOnFocusLost: False
ShowCursor: False
StaysOnTop: False
EscClose: True
Title:
Debug: True
DebugInterval: 100
--------------------------------------------------
Entities
Entity Name: Player
moveSpeed: 10
jumpSpeed: 8
ID: 0
Type: 0
Gravity: 1
Vspeed: 1
Hspeed: 0
X: 20
Y: 361
Z: 0
Sprites: System.Collections.Generic.List`1[GameEngine.Sprite]
SpriteIndex: 0
SpriteSpeed: 0
FramesSinceChange: 0
CollisionHandlers: System.Collections.Generic.List`1[GameEngine.CollisionHandler]
--------------------------------------------------
Key Events
Key: Left
State: DOWN
Key: Left
State: UP
Key: Right
State: DOWN
Key: Right
State: UP
Key: Up
State: DOWN
Key: Up
State: UP
You can store the SelectionStart then use ScrollToCaret after you update. Use LockWindowUpdate to stop the flicker. Something like this:
[DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
var originalPosition = textBox.SelectionStart;
LockWindowUpdate(textBox.Handle);
// ---- do the update here ----
textBox.SelectionStart = originalPosition;
textBox.ScrollToCaret();
LockWindowUpdate(IntPtr.Zero);
As long as the textbox doesn't change size (which it doesn't sound like it will) this should work fine. The other option is to use EM_LINESCROLL to store and set the scrollbar value for the textbox.. but that's more involved.
EDIT:
Since that didn't work.. here's another option.
First, some Windows APIs:
[DllImport("user32.dll")]
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
..and some values:
private const int SB_VERT = 0x1;
private const int SB_THUMBPOSITION = 4;
private const int WM_VSCROLL = 0x115;
You can now do this:
var currentPosition = GetScrollPos(textBox.Handle, SB_VERT);
// ---- update the text here ----
SetScrollPos(textBox.Handle, SB_VERT, currentPosition, false);
PostMessageA(textBox.Handle, WM_VSCROLL, SB_THUMBPOSITION + 65535 * currentPosition, 0);
This works perfectly for me. The only problem I have is that it jumps around sometimes purely because my randomly generated string of characters has widely varying widths. As long as yours is roughly similar after each update, it should be fine.
After searching and never finding a legitimate solution that works with and without focus as well as horizontally and vertically, I stumbled across an API solution that works (at least for my platform - Win7 / .Net4 WinForms).
using System;
using System.Runtime.InteropServices;
namespace WindowsNative
{
/// <summary>
/// Provides scroll commands for things like Multiline Textboxes, UserControls, etc.
/// </summary>
public static class ScrollAPIs
{
[DllImport("user32.dll")]
internal static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
internal static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
internal static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
public enum ScrollbarDirection
{
Horizontal = 0,
Vertical = 1,
}
private enum Messages
{
WM_HSCROLL = 0x0114,
WM_VSCROLL = 0x0115
}
public static int GetScrollPosition(IntPtr hWnd, ScrollbarDirection direction)
{
return GetScrollPos(hWnd, (int)direction);
}
public static void GetScrollPosition(IntPtr hWnd, out int horizontalPosition, out int verticalPosition)
{
horizontalPosition = GetScrollPos(hWnd, (int)ScrollbarDirection.Horizontal);
verticalPosition = GetScrollPos(hWnd, (int)ScrollbarDirection.Vertical);
}
public static void SetScrollPosition(IntPtr hwnd, int hozizontalPosition, int verticalPosition)
{
SetScrollPosition(hwnd, ScrollbarDirection.Horizontal, hozizontalPosition);
SetScrollPosition(hwnd, ScrollbarDirection.Vertical, verticalPosition);
}
public static void SetScrollPosition(IntPtr hwnd, ScrollbarDirection direction, int position)
{
//move the scroll bar
SetScrollPos(hwnd, (int)direction, position, true);
//convert the position to the windows message equivalent
IntPtr msgPosition = new IntPtr((position << 16) + 4);
Messages msg = (direction == ScrollbarDirection.Horizontal) ? Messages.WM_HSCROLL : Messages.WM_VSCROLL;
SendMessage(hwnd, (int)msg, msgPosition, IntPtr.Zero);
}
}
}
With a multiline textbox (tbx_main) use like:
int horzPos, vertPos;
ScrollAPIs.GetScrollPosition(tbx_main.Handle, out horzPos, out vertPos);
//make your changes
//i did something like the following where m_buffer is a string[]
tbx_main.Text = string.Join(Environment.NewLine, m_buffer);
tbx_main.SelectionStart = 0;
tbx_main.SelectionLength = 0;
ScrollAPIs.SetScrollPosition(tbx_main.Handle, horzPos, vertPos);

Scrolling Notepad using C# and WIN32

I'm trying to scroll a Notepad window using a C# application. The relevant code block is below, the call to move/size the window works so I know the handle is valid
please can you see what I am missing nothing happens when this is run.
[Flags]
public enum SetWindowPosFlags : uint
{
SWP_ASYNCWINDOWPOS = 0x4000,
SWP_DEFERERASE = 0x2000,
SWP_DRAWFRAME = 0x0020,
SWP_FRAMECHANGED = 0x0020,
SWP_HIDEWINDOW = 0x0080,
SWP_NOACTIVATE = 0x0010,
SWP_NOCOPYBITS = 0x0100,
SWP_NOMOVE = 0x0002,
SWP_NOOWNERZORDER = 0x0200,
SWP_NOREDRAW = 0x0008,
SWP_NOREPOSITION = 0x0200,
SWP_NOSENDCHANGING = 0x0400,
SWP_NOSIZE = 0x0001,
SWP_NOZORDER = 0x0004,
SWP_SHOWWINDOW = 0x0040,
}
private const int WM_SCROLL = 276; // Horizontal scroll
private const int WM_VSCROLL = 277; // Vertical scroll
private const int SB_LINEUP = 0; // Scrolls one line up
private const int SB_LINELEFT = 0;// Scrolls one cell left
private const int SB_LINEDOWN = 1; // Scrolls one line down
private const int SB_LINERIGHT = 1;// Scrolls one cell right
private const int SB_PAGEUP = 2; // Scrolls one page up
private const int SB_PAGELEFT = 2;// Scrolls one page left
private const int SB_PAGEDOWN = 3; // Scrolls one page down
private const int SB_PAGERIGTH = 3; // Scrolls one page right
private const int SB_PAGETOP = 6; // Scrolls to the upper left
private const int SB_LEFT = 6; // Scrolls to the left
private const int SB_PAGEBOTTOM = 7; // Scrolls to the upper right
private const int SB_RIGHT = 7; // Scrolls to the right
private const int SB_ENDSCROLL = 8; // Ends scroll
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
public void scroll()
{
IntPtr handle = IntPtr.Zero;
Process[] processes = Process.GetProcessesByName("Notepad");
foreach (Process p in processes)
{
handle = p.MainWindowHandle;
Console.WriteLine("Got Handle: " + p.MainWindowTitle);
break;
}
//this is to test I have a valid handle
SetWindowPos(handle, new IntPtr(0), 10, 10, 1024, 350, SetWindowPosFlags.SWP_DRAWFRAME);
SendMessage(handle, WM_VSCROLL, (IntPtr)SB_LINEDOWN, IntPtr.Zero);
SendMessage(handle, WM_VSCROLL, (IntPtr)SB_PAGEDOWN, IntPtr.Zero);
}
This fails because you are sending the WM_VSCROLL message to the main window. You need to send the message to Notepad's edit control, which is the window with the scrollbar.
You can enumerate Notepad's child windows using EnumChildWindows. The child with class "Edit" is the one you want.

How to test memory leak caused by non-released GDI objects?

In C#, I use this to get the icon of a window:
IntPtr IconHandle = SendMessage(hwnd, WM_GETICON ... );
Of cause, SendMessage is from DllImport("user32.dll").
AFAIK, this is needed to clean up:
DestroyIcon(iconHandle);
(again DestroyIcon via DllImport("user32.dll").)
Things seem to work fine, but
what I want to know is:
How do I determine a memory leak is taking place if I commented out the call to DestroyIcon()?
What I planned to do is to
put the get icon code inside a long loop
without calling DestroyIcon().
To check if memory is leaking, my naive way is to
check if the "commit charge" is accumulating in
"Window Task Manager".
However, after a loop with 100000 iterations ...
Nothing blows up.
Windows XP still runs happily.
I need to find out the way to test this out, because
I want to make sure that my code is correctly
releasing the unmanaged resources, in my development machine and
also in the future end users'.
How do I test it? Or is it that I didn't test it hard enough
(e.g. test with 10^10 iterations instead)?
I post the testing code below:
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.Globalization;
namespace TestLeak
{
public partial class Form1 : Form
{
Thread th;
public Form1()
{
InitializeComponent();
}
private class CHwndItem
{
private IntPtr mHWnd;
private string m_Caption;
public string Caption
{
get { return m_Caption; }
set { m_Caption = value; }
}
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[PreserveSig]
public static extern uint GetModuleFileName
(
[In]
IntPtr hModule,
[Out]
StringBuilder lpFilename,
[In]
[MarshalAs(UnmanagedType.U4)]
int nSize
);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);
private Icon m_Icon;
public Icon Icon
{
get { return m_Icon; }
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct SHFILEINFO
{
public IntPtr hIcon;
public IntPtr iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
}
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
public const int GCL_HICONSM = -34;
public const int GCL_HICON = -14;
public const int ICON_SMALL = 0;
public const int ICON_BIG = 1;
public const int ICON_SMALL2 = 2;
private const Int32 ANYSIZE_ARRAY = 1;
private const UInt32 TOKEN_QUERY = 0x0008;
private const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
private const uint FILE_SHARE_READ = 0x00000001;
private const uint FILE_SHARE_WRITE = 0x00000002;
private const uint FILE_SHARE_DELETE = 0x00000004;
private const uint FILE_ATTRIBUTE_READONLY = 0x00000001;
private const uint FILE_ATTRIBUTE_HIDDEN = 0x00000002;
private const uint FILE_ATTRIBUTE_SYSTEM = 0x00000004;
private const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
private const uint FILE_ATTRIBUTE_ARCHIVE = 0x00000020;
private const uint FILE_ATTRIBUTE_DEVICE = 0x00000040;
private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
private const uint FILE_ATTRIBUTE_TEMPORARY = 0x00000100;
private const uint FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200;
private const uint FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
private const uint FILE_ATTRIBUTE_COMPRESSED = 0x00000800;
private const uint FILE_ATTRIBUTE_OFFLINE = 0x00001000;
private const uint FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000;
private const uint FILE_ATTRIBUTE_ENCRYPTED = 0x00004000;
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const uint GENERIC_EXECUTE = 0x20000000;
private const uint GENERIC_ALL = 0x10000000;
private const int SHGFI_SMALLICON = 0x1;
private const int SHGFI_LARGEICON = 0x0;
private const int SHGFI_ICON = 0x100;
private const int SHGFI_USEFILEATTRIBUTES = 0x10;
public IntPtr HWnd
{
get { return mHWnd; }
set
{
mHWnd = value;
m_Icon = GetAppIcon(mHWnd);
uint thID;
GetWindowThreadProcessId(value, out thID);
IntPtr processHwnd = OpenProcess(0, false, (int)thID);
StringBuilder path = new StringBuilder(' ', 255);
GetModuleFileName(processHwnd, path, path.Length);
SHFILEINFO fi = new SHFILEINFO();
SHGetFileInfo(#"C:\Program Files\Mozilla Firefox\firefox.exe", FILE_ATTRIBUTE_NORMAL, ref fi, (uint)System.Runtime.InteropServices.Marshal.SizeOf(fi), SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES);
//IntPtr hIcon = new IntPtr(
//CloseHandle(processHwnd);
//m_Icon = Icon.FromHandle(hIcon);
//DestroyIcon(hIcon);
}
}
public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size > 4)
return GetClassLongPtr64(hWnd, nIndex);
else
return // new IntPtr(
GetClassLongPtr32(hWnd, nIndex);
}
[DllImport("user32.dll", EntryPoint = "GetClassLong")]
public static extern IntPtr GetClassLongPtr32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
public const int WM_GETICON = 0x7F;
public static Icon GetAppIcon(IntPtr hwnd)
{
int try_icon_type = ICON_SMALL2;
IntPtr iconHandle = SendMessage(hwnd, WM_GETICON, ICON_SMALL2, 0);
if (iconHandle == IntPtr.Zero)
{
try_icon_type = ICON_SMALL;
iconHandle = SendMessage(hwnd, WM_GETICON, try_icon_type, 0);
}
if (iconHandle == IntPtr.Zero)
{
try_icon_type = ICON_BIG;
iconHandle = SendMessage(hwnd, WM_GETICON, try_icon_type, 0);
}
// if (iconHandle == IntPtr.Zero)
// {
//try_icon_type = GCL_HICON;
// iconHandle = GetClassLongPtr(hwnd, try_icon_type);
// }
if (iconHandle == IntPtr.Zero)
{
try_icon_type = GCL_HICONSM;
iconHandle = GetClassLongPtr(hwnd, try_icon_type);
}
if (iconHandle == IntPtr.Zero)
return null;
System.Diagnostics.Debug.WriteLine(try_icon_type);
Icon icn = Icon.FromHandle(iconHandle);
DestroyIcon(iconHandle);
return icn;
}
}
int GetHandle()
{
if (txt_Dec.Text.Trim().Length > 0)
{
return int.Parse(txt_Dec.Text);
}
else
{
return int.Parse(txt_Hex.Text, NumberStyles.HexNumber);
}
}
private void button1_Click(object sender, EventArgs e)
{
th = new Thread(new ThreadStart(ThreadProc));
th.IsBackground = true;
th.Start();
}
private void ThreadProc()
{
for (int i = 0; i < int.Parse(textBox1.Text); i++)
{
CHwndItem hi = new CHwndItem();
hi.HWnd = new IntPtr(GetHandle());
Invoke(new MethodInvoker(delegate()
{
lbl_incr.Text = i.ToString();
}));
}
MessageBox.Show("Done");
}
private void button2_Click(object sender, EventArgs e)
{
CHwndItem hi = new CHwndItem();
hi.HWnd = new IntPtr(GetHandle());
pictureBox1.Image = hi.Icon.ToBitmap();
}
private void button3_Click(object sender, EventArgs e)
{
if (th.ThreadState == ThreadState.Running)
{
btn_Pause.Text = "Resume";
th.Suspend();
}
else
{
btn_Pause.Text = "Pause";
th.Resume();
}
}
}
}
Form1.Designer.cs:
namespace TestLeak
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.txt_Dec = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.button2 = new System.Windows.Forms.Button();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.lbl_incr = new System.Windows.Forms.Label();
this.btn_Pause = new System.Windows.Forms.Button();
this.txt_Hex = new System.Windows.Forms.TextBox();
this.label3 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.SuspendLayout();
//
// button1
//
this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.button1.Location = new System.Drawing.Point(15, 99);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "Start";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(90, 64);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(81, 20);
this.textBox1.TabIndex = 1;
//
// txt_Dec
//
this.txt_Dec.Location = new System.Drawing.Point(90, 23);
this.txt_Dec.Name = "txt_Dec";
this.txt_Dec.Size = new System.Drawing.Size(81, 20);
this.txt_Dec.TabIndex = 2;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(13, 29);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(86, 13);
this.label1.TabIndex = 3;
this.label1.Text = "Handle (decimal)";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 67);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(31, 13);
this.label2.TabIndex = 3;
this.label2.Text = "Loop";
//
// button2
//
this.button2.Location = new System.Drawing.Point(167, 153);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(103, 23);
this.button2.TabIndex = 4;
this.button2.Text = "Test Handle Icon";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// pictureBox1
//
this.pictureBox1.Location = new System.Drawing.Point(167, 182);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(100, 50);
this.pictureBox1.TabIndex = 5;
this.pictureBox1.TabStop = false;
//
// lbl_incr
//
this.lbl_incr.AutoSize = true;
this.lbl_incr.Location = new System.Drawing.Point(23, 166);
this.lbl_incr.Name = "lbl_incr";
this.lbl_incr.Size = new System.Drawing.Size(10, 13);
this.lbl_incr.TabIndex = 3;
this.lbl_incr.Text = "-";
//
// btn_Pause
//
this.btn_Pause.Location = new System.Drawing.Point(15, 182);
this.btn_Pause.Name = "btn_Pause";
this.btn_Pause.Size = new System.Drawing.Size(75, 23);
this.btn_Pause.TabIndex = 6;
this.btn_Pause.Text = "Pause";
this.btn_Pause.UseVisualStyleBackColor = true;
this.btn_Pause.Click += new System.EventHandler(this.button3_Click);
//
// txt_Hex
//
this.txt_Hex.Location = new System.Drawing.Point(236, 23);
this.txt_Hex.Name = "txt_Hex";
this.txt_Hex.Size = new System.Drawing.Size(81, 20);
this.txt_Hex.TabIndex = 2;
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(189, 29);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(32, 13);
this.label3.TabIndex = 3;
this.label3.Text = "(Hex)";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(318, 266);
this.Controls.Add(this.btn_Pause);
this.Controls.Add(this.pictureBox1);
this.Controls.Add(this.button2);
this.Controls.Add(this.lbl_incr);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.txt_Hex);
this.Controls.Add(this.txt_Dec);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.TextBox txt_Dec;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.Label lbl_incr;
private System.Windows.Forms.Button btn_Pause;
private System.Windows.Forms.TextBox txt_Hex;
private System.Windows.Forms.Label label3;
}
}
You have an actual GDI Objects column which you can display in Task Manager (by going to View/Select columns...), which you can monitor.
You also have a Handles counter you can use to monitor USER objects IIRC.
You can typically use the VM Size counter as an indicator of application memory leaks (it tracks how much address space the process has hogged.) This is not the same as handle leaks, and you may not necessarily see an increase in VM Size if you leak handles.
I don't think you are leaking GDI handles as Windows will typically blow up after ~4k GDI handles system0wide (limit can be increased via registry IIRC, but you get my point.)
To be accurate you should use memory profiler and study the memory handles. There are several commercial products available such as Redgate memory profiler, AutomatedQA, DevParner memory profiler or intel VTune Analazer. Alternatively try using CLR profiler from microsoft and watch the memory and handle allocation and reclaimed.
Other than that the poor man approach is to watch the the GDI object allocation in Task Manager. Make sure you tick to show that column in the processes view. Another option is to use process explorer from sysinternal and you can customize to view a whole range of mnanaged/unmanaged resources to be displayed along with your process. The number of iterations you have currently would be more than sufficient to highlight the problem with resource leak.
When reading the MSDN Page for WM_GETICON it doesn't say anything about you being required to destroy the icon. It is not stated on that page, but the two most likely implementations are:
Increment a ref-counter on the icon, or
Just return the icon.
Neither of these approaches would actually allocate a new icon, but if the second approach is actually taken, your failure to release it might lead to one leaked icon per window class.

Categories