// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Diagnostics; using System.Runtime.InteropServices; using System.ComponentModel; using System.Drawing.Design; using System.Collections.Specialized; namespace MemoryProfiler2 { /// /// Represents a Windows list view control, which displays a collection of items that can be displayed using one of four different views. /// This version adds following extensions: /// Tooltips for column headers /// Ability to set the sort arrow to a particular column /// public class ListViewEx : System.Windows.Forms.ListView { /// Instance of list view header class. FListViewHeader ListViewHeader; /// Collection of string used later as a text for tooltip in column header. StringCollection _ColumnsTooltips = new StringCollection(); protected override void OnHandleCreated( EventArgs e ) { // Create a new ListViewHeader object ListViewHeader = new FListViewHeader( this ); base.OnHandleCreated( e ); } /// Sets the sort arrow to a particular column in the list view. /// Index of the column where the sort arrow will be displayed public void SetSortArrow( int ColumnToShowArrow, bool bSortModeAscending ) { if( ListViewHeader == null ) { return; } IntPtr ColumnHeader = ListViewHeader.Handle; for( int ColumnIndex = 0; ColumnIndex < Columns.Count; ColumnIndex++ ) { IntPtr ColumnPtr = new IntPtr( ColumnIndex ); ListViewWin32.HDITEM ListViewColumn = new ListViewWin32.HDITEM(); ListViewColumn.mask = ListViewWin32.HDI_FORMAT; ListViewWin32.SendMessageHeaderItem( ColumnHeader, ListViewWin32.HDM_GETITEM, ColumnPtr, ref ListViewColumn ); bool bIsSortArrowDown = ( ( ListViewColumn.fmt & ListViewWin32.HDF_SORTDOWN ) == ListViewWin32.HDF_SORTDOWN ); bool bIsSortArrowUp = ( ( ListViewColumn.fmt & ListViewWin32.HDF_SORTUP ) == ListViewWin32.HDF_SORTUP ); // Change the sort arrow to opposite direction. if( ColumnToShowArrow == ColumnIndex ) { if( bSortModeAscending ) { ListViewColumn.fmt &= ~ListViewWin32.HDF_SORTDOWN; ListViewColumn.fmt |= ListViewWin32.HDF_SORTUP; } else { ListViewColumn.fmt &= ~ListViewWin32.HDF_SORTUP; ListViewColumn.fmt |= ListViewWin32.HDF_SORTDOWN; } } else { ListViewColumn.fmt &= ~ListViewWin32.HDF_SORTDOWN & ~ListViewWin32.HDF_SORTUP; } ListViewWin32.SendMessageHeaderItem( ColumnHeader, ListViewWin32.HDM_SETITEM, ColumnPtr, ref ListViewColumn ); } } [Editor( "System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof( UITypeEditor ) )] [MergableProperty( false )] [DesignerSerializationVisibility( DesignerSerializationVisibility.Content )] [Localizable( true )] [ Category( "Behavior" ), Description( "Strings for tooltips on the header columns. Number of items in this list must match number of columns" ) ] /// Strings for tooltips on the header columns. Number of items in this list must match number of columns. public StringCollection ColumnsTooltips { get { return _ColumnsTooltips; } } } /// /// Class used to bypass default message processing of the header control used by the list-view control. /// This version adds following extensions: /// Tooltips for column headers /// internal class FListViewHeader : NativeWindow { /// Reference to the parent list view control. ListViewEx Parent; /// Tooltip used by the header control. ToolTip HeaderToolTip = new ToolTip(); /// Index of the header column currently pointed by the mouse. int CurrentColumnIndex = -1; /// Index of the header column previously pointed by the mouse. int LastColumnIndex = -1; /// Whether to show tooltips. bool bAllowTooltips = false; /// Default constructor. public FListViewHeader( ListViewEx InParent ) { Parent = InParent; // Get the header control window. IntPtr HeaderWindow = ListViewWin32.SendMessage( Parent.Handle, ListViewWin32.LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero ); AssignHandle( HeaderWindow ); // Only allow tooltips if data is valid. bAllowTooltips = Parent.ColumnsTooltips.Count == Parent.Columns.Count; // Assign tooltip to the parent control. HeaderToolTip.SetToolTip( Parent, "ListViewEx" ); } /// Overridden window message processing. protected override void WndProc( ref Message Msg ) { if( Msg.Msg == ListViewWin32.WM_MOUSEMOVE ) { int PosX = ListViewWin32.GET_X_LPARAM( ( uint )Msg.LParam ); int PosY = ListViewWin32.GET_Y_LPARAM( ( uint )Msg.LParam ); int ColumnStart = 0; int ColumnEnd = 0; CurrentColumnIndex = -1; // Find on which column we should show the tooltip. for( int ColumnIndex = 0; ColumnIndex < Parent.Columns.Count; ColumnIndex++ ) { int ColumnWidth = Parent.Columns[ ColumnIndex ].Width; ColumnEnd = ColumnStart + ColumnWidth; if( PosX >= ColumnStart && PosX < ColumnEnd ) { CurrentColumnIndex = ColumnIndex; break; } ColumnStart = ColumnEnd; } if( CurrentColumnIndex == -1 ) { HeaderToolTip.Hide( Parent ); } else if( CurrentColumnIndex != LastColumnIndex ) { ListViewWin32.TRACKMOUSEEVENT trackMouseEvent = new ListViewWin32.TRACKMOUSEEVENT(ListViewWin32.ETrackMouseEvent.TME_HOVER, Handle, ListViewWin32.HOVER_DEFAULT ); ListViewWin32.TrackMouseEvent( ref trackMouseEvent ); HeaderToolTip.Hide( Parent ); } LastColumnIndex = CurrentColumnIndex; } else if( Msg.Msg == ListViewWin32.WM_MOUSELEAVE ) { HeaderToolTip.Hide( Parent ); LastColumnIndex = -1; } else if( Msg.Msg == ListViewWin32.WM_MOUSEHOVER ) { if( bAllowTooltips && CurrentColumnIndex != -1 ) { int PosX = ListViewWin32.GET_X_LPARAM( ( uint )Msg.LParam ); int PosY = ListViewWin32.GET_Y_LPARAM( ( uint )Msg.LParam ); // Show tooltip. HeaderToolTip.Show( Parent.ColumnsTooltips[ CurrentColumnIndex ], Parent, PosX, PosY + 32 ); } } // Default processing. base.WndProc( ref Msg ); } } /// Helper class with native Win32 functions, constants and structures. public class ListViewWin32 { [DllImport( "user32.dll", EntryPoint = "SendMessage" )] public static extern IntPtr SendMessage( IntPtr hwnd, UInt32 wMsg, IntPtr wParam, IntPtr lParam ); [DllImport( "user32.dll" )] [return: MarshalAs( UnmanagedType.Bool )] public static extern bool GetWindowRect( HandleRef hWnd, out RECT lpRect ); [DllImport( "user32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] public static extern bool TrackMouseEvent( ref TRACKMOUSEEVENT lpEventTrack ); [DllImport( "kernel32.dll", SetLastError = true )] public static extern int GetLastError(); [System.Runtime.InteropServices.DllImport( "user32.dll", EntryPoint = "SendMessage" )] public static extern IntPtr SendMessageHeaderItem( IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref HDITEM PtrHDITEM ); /// Posted to a window when the cursor move. public const UInt32 WM_MOUSEMOVE = 0x200; /// Posted to a window when the cursor leaves the client area of the window. public const UInt32 WM_MOUSELEAVE = 0x2a3; /// Posted to a window when the cursor hovers over the client area of the window for the period of time specified in a prior call to TrackMouseEvent. public const UInt32 WM_MOUSEHOVER = 0x02A1; /// The system default hover time-out. public const UInt32 HOVER_DEFAULT = 0xFFFFFFFF; public const UInt32 LVM_FIRST = 0x1000; /// Gets the handle to the header control used by the list-view control. public const UInt32 LVM_GETHEADER = ( LVM_FIRST + 31 ); /// The fmt member is valid. public const UInt32 HDI_FORMAT = 0x4; /// /// Draws an up-arrow on this item. /// This is typically used to indicate that information in the current window is sorted on this column in ascending order. /// This flag cannot be combined with HDF_IMAGE or HDF_BITMAP. /// public const Int32 HDF_SORTUP = 0x400; /// /// Draws a down-arrow on this item. /// This is typically used to indicate that information in the current window is sorted on this column in descending order. /// This flag cannot be combined with HDF_IMAGE or HDF_BITMAP. /// public const Int32 HDF_SORTDOWN = 0x200; /// Gets information about an item in a header control. public const UInt32 HDM_GETITEM = 0x120b; /// Sets the attributes of the specified item in a header control.. public const UInt32 HDM_SETITEM = 0x120c; [StructLayout( LayoutKind.Sequential )] /// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle. public struct RECT { /// X position of upper-left corner. public int Left; /// Y position of upper-left corner. public int Top; /// X position of lower-right corner. public int Right; /// Y position of lower-right corner. public int Bottom; } /// Used by the TrackMouseEvent function to track when the mouse pointer leaves a window or hovers over a window for a specified amount of time. [StructLayout( LayoutKind.Sequential )] public struct TRACKMOUSEEVENT { /// The size of the TRACKMOUSEEVENT structure, in bytes. public UInt32 cbSize; /// The services requested. This member can be a combination of the following values. public UInt32 dwFlags; /// A handle to the window to track. public IntPtr hwndTrack; /// The hover time-out (if TME_HOVER was specified in dwFlags), in milliseconds. Can be HOVER_DEFAULT, which means to use the system default hover time-out. public UInt32 dwHoverTime; /// Constructor. public TRACKMOUSEEVENT( ETrackMouseEvent InFlags, IntPtr InHwnd, UInt32 InHoverTime ) { cbSize = ( uint )Marshal.SizeOf( typeof( TRACKMOUSEEVENT ) ); dwFlags = ( uint )InFlags; hwndTrack = InHwnd; dwHoverTime = InHoverTime; } } /// The services requested. This member can be a combination of the following values. [Flags] public enum ETrackMouseEvent : uint { /// /// The caller wants to cancel a prior tracking request. The caller should also specify the type of tracking that it wants to cancel. /// For example, to cancel hover tracking, the caller must pass the TME_CANCEL and TME_HOVER flags. /// TME_CANCEL = 0x80000000, /// /// The caller wants hover notification. Notification is delivered as a WM_MOUSEHOVER message. /// If the caller requests hover tracking while hover tracking is already active, the hover timer will be reset. /// This flag is ignored if the mouse pointer is not over the specified window or area. /// TME_HOVER = 0x00000001, /// /// The caller wants leave notification. Notification is delivered as a WM_MOUSELEAVE message. /// If the mouse is not over the specified window or area, a leave notification is generated immediately and no further tracking is performed. /// TME_LEAVE = 0x00000002, /// /// The caller wants hover and leave notification for the non client areas. Notification is delivered as WM_NCMOUSEHOVER and WM_NCMOUSELEAVE messages. /// TME_NONCLIENT = 0x00000010, /// /// The function fills in the structure instead of treating it as a tracking request. /// The structure is filled such that had that structure been passed to TrackMouseEvent, it would generate the current tracking. /// The only anomaly is that the hover time-out returned is always the actual time-out and not HOVER_DEFAULT, /// if HOVER_DEFAULT was specified during the original TrackMouseEvent request. /// TME_QUERY = 0x40000000, } [StructLayout( LayoutKind.Sequential )] /// Contains information about an item in a header control. This structure supersedes the HD_ITEM structure. public struct HDITEM { /// Flags indicating which other structure members contain valid data or must be filled in. public UInt32 mask; /// The width or height of the item. public Int32 cx; /// A pointer to an item string. public IntPtr pszText; /// A handle to the item bitmap. public IntPtr hbm; /// The length of the item string, in TCHARs. public Int32 cchTextMax; /// Flags that specify the item's format. public Int32 fmt; /// Application-defined item data. public IntPtr lParam; /// The zero-based index of an image within the image list. public Int32 iImage; /// The order in which the item appears within the header control, from left to right. public Int32 iOrder; } /// Retrieves the signed x-coordinate from the specified LPARAM value. public static int GET_X_LPARAM( uint Param ) { return ( int )( Param & 0xffff ); } /// Retrieves the signed y-coordinate from the specified LPARAM value. public static int GET_Y_LPARAM( uint Param ) { return ( int )( ( Param >> 16 ) & 0xffff ); } } }