📄 listviewex.cs
字号:
internal bool Tracking
{
set { tracking = value; }
get { return tracking; }
}
internal int PressedHeaderItem
{
set { pressedHeaderItem = value; }
get { return pressedHeaderItem; }
}
internal Point LastMousePosition
{
set { lastMousePosition = value; }
get { return lastMousePosition; }
}
public ImageList HeaderImageList
{
set { headerImageList = value; }
get { return headerImageList; }
}
#endregion
#region Methods
public void SetHeaderIcon(int headerIndex, int iconIndex)
{
// Associate an specific header with an specific image index
// in the headerImageList
headerIconsList.Add(new HeaderIconHelper(headerIndex, iconIndex));
}
public void SetColumnSortFormat(int columnIndex, SortedListViewFormatType format)
{
rowSorterList.Add(new RowSorterHelper(columnIndex, format));
}
public void SetColumnSortFormat(int columnIndex, SortedListViewFormatType format, ListSortEvent callBack)
{
rowSorterList.Add(new RowSorterHelper(columnIndex, format, callBack));
}
public Rectangle GetHeaderItemRect(int index)
{
RECT rc = new RECT();
WindowsAPI.SendMessage(hHeader, (int)HeaderControlMessages.HDM_GETITEMRECT, index, ref rc);
return new Rectangle(rc.left, rc.top, rc.right-rc.left,rc.bottom-rc.top);
}
#endregion
#region Implementation
void PaintBackground(IntPtr hDC)
{
Graphics g = Graphics.FromHdc(hDC);
if ( lastSortedColumn == -1 )
return;
Rectangle rc = GetSubItemRect(0, lastSortedColumn);
if ( lastSortedColumn == 0 && Columns.Count > 1)
{
Rectangle rcSecondItem = GetSubItemRect(0, 1);
rc = new Rectangle(rc.Left, rc.Top, rcSecondItem.Left - rc.Left, rc.Height);
}
g.FillRectangle(new SolidBrush(Color.FromArgb(247,247,247)), rc.Left, rc.Top, rc.Width, Height);
}
bool NotifyListCustomDraw(ref Message m)
{
m.Result = (IntPtr)CustomDrawReturnFlags.CDRF_DODEFAULT;
NMCUSTOMDRAW nmcd = (NMCUSTOMDRAW)m.GetLParam(typeof(NMCUSTOMDRAW));
IntPtr thisHandle = Handle;
if ( nmcd.hdr.hwndFrom != Handle)
return false;
switch (nmcd.dwDrawStage)
{
case (int)CustomDrawDrawStateFlags.CDDS_PREPAINT:
// Ask for Item painting notifications
m.Result = (IntPtr)CustomDrawReturnFlags.CDRF_NOTIFYITEMDRAW;
break;
case (int)CustomDrawDrawStateFlags.CDDS_ITEMPREPAINT:
m.Result = (IntPtr)CustomDrawReturnFlags.CDRF_NOTIFYSUBITEMDRAW;
break;
case (int)(CustomDrawDrawStateFlags.CDDS_ITEMPREPAINT | CustomDrawDrawStateFlags.CDDS_SUBITEM):
// Draw background
DoListCustomDrawing(ref m);
break;
default:
break;
}
return false;
}
void DoListCustomDrawing(ref Message m)
{
NMLVCUSTOMDRAW lvcd = (NMLVCUSTOMDRAW)m.GetLParam(typeof(NMLVCUSTOMDRAW));
int row = lvcd.nmcd.dwItemSpec;
int col = lvcd.iSubItem;
// If we don't have any items we must be doing something wrong
// because the list is only going to request custom drawing of items
// in the list, if we have items in the list, the Items cannot possibly
// be zero
Debug.Assert(Items.Count != 0);
ListViewItem lvi = Items[row];
Rectangle rc = GetSubItemRect(row, col);
// Draw the item
// We did not need to actually paint the items that are not selected
// but doing all the painting ourselves eliminates some random bugs where
// the list sometimes did not update a subitem that was not selected anymore
// leaving the subitem with a different background color
// than the rest of the row
Graphics g = Graphics.FromHdc(lvcd.nmcd.hdc);
// Draw Fill Rectangle
if ( IsRowSelected(row) )
{
int subItemOffset = 2;
if ( GridLines )
{
subItemOffset = 3;
}
g.FillRectangle(new SolidBrush(ColorUtil.VSNetSelectionColor), rc.Left+1, rc.Top+1, rc.Width-2, rc.Height-subItemOffset);
// Draw Border
if ( col == 0 )
{
Color borderColor = SystemColors.Highlight;
int heightOffset = 1;
if ( GridLines )
heightOffset = 2;
g.DrawRectangle(new Pen(borderColor), rc.Left+1, rc.Top, rc.Width-2, rc.Height-heightOffset);
}
}
else
{
if ( col == lastSortedColumn )
{
if ( col == 0)
rc = AdjustFirstItemRect(rc);
g.FillRectangle(new SolidBrush(Color.FromArgb(247,247,247)), rc.Left, rc.Top, rc.Width, rc.Height);
}
else
{
if ( col == 0)
rc = AdjustFirstItemRect(rc);
g.FillRectangle(new SolidBrush(SystemColors.Window), rc.Left, rc.Top, rc.Width, rc.Height);
}
}
// Adjust rectangle, when getting the rectangle for column zero
// the rectangle return is the one for the whole control
if ( col == 0)
{
rc = AdjustFirstItemRect(rc);
}
// Draw Text
string text = GetSubItemText(row, col);
Size textSize = TextUtil.GetTextSize(g, text, Font);
int gap = 4;
Point pos = new Point(rc.Left+gap ,rc.Top + ((rc.Height - textSize.Height) / 2));
// I use the Windows API instead of the Graphics object to draw the string
// because the Graphics object draws ellipes without living blank spaces in between
// the DrawText API adds those blank spaces in between
int ellipsingTringgering = 8;
if ( CheckBoxes && col == 0 )
{
// draw checkbox
int checkIndex = 0;
if ( lvi.Checked)
checkIndex = 1;
g.DrawImage(checkBoxesImageList.Images[checkIndex], rc.Left + gap, rc.Top);
pos.X += IMAGE_WIDTH;
rc.Width = rc.Width - IMAGE_WIDTH;
}
else if ( col == 0 && CheckBoxes == false && lvi.ImageIndex != -1 && lvi.ImageList != null )
{
ImageList imageList = lvi.ImageList;
Image image = imageList.Images[lvi.ImageIndex];
if ( image != null )
{
g.DrawImage(imageList.Images[lvi.ImageIndex], rc.Left + gap, rc.Top);
pos.X += IMAGE_WIDTH;
rc.Width = rc.Width - IMAGE_WIDTH;
}
}
Rectangle drawRect = new Rectangle(pos.X+2, pos.Y, rc.Width-gap-ellipsingTringgering, rc.Height);
TextUtil.DrawText(g, text, Font, drawRect);
g.Dispose();
// Put structure back in the message
Marshal.StructureToPtr(lvcd, m.LParam, true);
m.Result = (IntPtr)CustomDrawReturnFlags.CDRF_SKIPDEFAULT;
}
Rectangle AdjustFirstItemRect(Rectangle rc)
{
ListViewItem lvi = Items[0];
if ( Columns.Count > 1)
{
Debug.WriteLine(rc);
Rectangle rcSecondItem = GetSubItemRect(0, 1);
Rectangle RC = new Rectangle(rc.Left, rc.Top, rcSecondItem.Left - rc.Left, rc.Height);
return RC;
}
else
{
return rc;
}
}
bool NotifyHeaderCustomDraw(ref Message m)
{
m.Result = (IntPtr)CustomDrawReturnFlags.CDRF_DODEFAULT;
NMCUSTOMDRAW nmcd = (NMCUSTOMDRAW)m.GetLParam(typeof(NMCUSTOMDRAW));
if ( nmcd.hdr.hwndFrom != hHeader)
return false;
switch (nmcd.dwDrawStage)
{
case (int)CustomDrawDrawStateFlags.CDDS_PREPAINT:
// Ask for Item painting notifications
m.Result = (IntPtr)CustomDrawReturnFlags.CDRF_NOTIFYITEMDRAW;
break;
case (int)CustomDrawDrawStateFlags.CDDS_ITEMPREPAINT:
DoHeaderCustomDrawing(ref m);
break;
case (int)NotificationMessages.NM_NCHITTEST:
break;
default:
break;
}
return false;
}
void DoHeaderCustomDrawing(ref Message m)
{
NMCUSTOMDRAW nmcd = (NMCUSTOMDRAW)m.GetLParam(typeof(NMCUSTOMDRAW));
Graphics g = Graphics.FromHdc(nmcd.hdc);
Rectangle rc = GetHeaderCtrlRect();
rc = GetHeaderItemRect(nmcd.dwItemSpec);
int itemRight = rc.Left + rc.Width;
g.FillRectangle(new SolidBrush(SystemColors.ScrollBar), rc.Left, rc.Top, rc.Width, rc.Height);
if ( nmcd.dwItemSpec == PressedHeaderItem && !IsCursorOnDivider() && Tracking == false )
{
PressedHeaderItem = -1;
rc.Inflate(-1, -1);
g.FillRectangle(new SolidBrush(ColorUtil.VSNetPressedColor), rc.Left, rc.Top, rc.Width, rc.Height);
}
else
{
ControlPaint.DrawBorder3D(g, rc.Left, rc.Top, rc.Width,
rc.Height-1, Border3DStyle.RaisedInner, Border3DSide.All);
}
string text = GetHeaderItemText(nmcd.dwItemSpec);
Debug.WriteLine(text);
Size textSize = TextUtil.GetTextSize(g, text, Font);
int gap = 4;
Point pos = new Point(rc.Left+gap ,rc.Top + ((rc.Height - textSize.Height) / 2));
int headerImageIndex;
if ( headerIconsList != null && HasHeaderImage(nmcd.dwItemSpec, out headerImageIndex) )
{
if ( headerImageIndex != -1 )
{
Image image = headerImageList.Images[headerImageIndex];
if ( image != null )
{
g.DrawImage(headerImageList.Images[headerImageIndex], rc.Left + gap, rc.Top);
pos.X += IMAGE_WIDTH;
rc.Width = rc.Width - IMAGE_WIDTH;
}
}
}
// Draw arrow glyph
if ( nmcd.dwItemSpec == lastSortedColumn)
{
int Left = pos.X+2;
Left += textSize.Width + TEXT_TO_ARROW_GAP;
Rectangle arrowRect = new Rectangle(Left, rc.Top, ARROW_WIDTH, rc.Height);
if ( itemRight >= (Left + ARROW_WIDTH + 4) )
{
if ( Sorting == SortOrder.Ascending || Sorting == SortOrder.None )
DrawUpArrow(g, arrowRect);
else
DrawDownArrow(g, arrowRect);
}
}
// I use the Windows API instead of the Graphics object to draw the string
// because the Graphics object draws ellipes without living blank spaces in between
// the DrawText API adds those blank spaces in between
int ellipsingTringgering = 8;
Rectangle drawRect = new Rectangle(pos.X+2, pos.Y, rc.Width-gap-ellipsingTringgering, rc.Height);
TextUtil.DrawText(g, text, Font, drawRect);
g.Dispose();
m.Result = (IntPtr)CustomDrawReturnFlags.CDRF_SKIPDEFAULT;
}
bool HasHeaderImage(int headerIndex, out int imageIndex)
{
imageIndex = -1;
for ( int i = 0; i < headerIconsList.Count; i++ )
{
HeaderIconHelper hih = (HeaderIconHelper)headerIconsList[i];
if ( hih != null && hih.HeaderIndex == headerIndex )
{
imageIndex = hih.IconIndex;
return true;
}
}
return false;
}
void DrawUpArrow(Graphics g, Rectangle rc)
{
int xTop = rc.Left + rc.Width/2;
int yTop = (rc.Height - 6)/2;
int xLeft = xTop - 6;
int yLeft = yTop + 6;
int xRight = xTop + 6;
int yRight = yTop + 6;
g.DrawLine(new Pen(SystemColors.ControlDarkDark), xLeft, yLeft, xTop, yTop);
g.DrawLine(new Pen(Color.White), xRight, yRight, xTop, yTop);
g.DrawLine(new Pen(Color.White), xLeft, yLeft, xRight, yRight);
}
void DrawDownArrow(Graphics g, Rectangle rc)
{
int xBottom = rc.Left + rc.Width/2;
int xLeft = xBottom - 6;
int yLeft = (rc.Height - 6)/2;;
int xRight = xBottom + 6;
int yRight = (rc.Height - 6)/2;
int yBottom = yRight + 6;
g.DrawLine(new Pen(SystemColors.ControlDarkDark), xLeft, yLeft, xBottom, yBottom);
g.DrawLine(new Pen(Color.White), xRight, yRight, xBottom, yBottom);
g.DrawLine(new Pen(SystemColors.ControlDarkDark), xLeft, yLeft, xRight, yRight);
}
string GetSubItemText(int row, int col)
{
// I am going to use the Windows API since using the .NET
// ListViewSubItem.Text property is causing the nasty side
// effect of changing the text when I draw the string using TextUtil.DrawText,
// even though that is not my intention at all.
// I am not sure about why this is happening but using the API solves the problem
LVITEM lvi = new LVITEM();
lvi.iItem = row;
lvi.mask = (int)ListViewItemFlags.LVIF_TEXT;
lvi.iSubItem = col;
lvi.cchTextMax = BUFFER_SIZE;
lvi.pszText = Marshal.AllocHGlobal(BUFFER_SIZE);
WindowsAPI.SendMessage(Handle, (int)ListViewMessages.LVM_GETITEMTEXTW, row, ref lvi);
string text = Marshal.PtrToStringAuto(lvi.pszText);
return text;
}
Rectangle GetSubItemRect(int row, int col)
{
RECT rc = new RECT();
rc.top = col;
rc.left = (int)SubItemPortion.LVIR_BOUNDS;
WindowsAPI.SendMessage(Handle, (int)ListViewMessages.LVM_GETSUBITEMRECT, row, ref rc);
return new Rectangle(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top);
}
bool IsRowSelected(int row)
{
Debug.Assert(row >= 0 && row < Items.Count);
ListViewItem lvi = Items[row];
return lvi.Selected;
}
Rectangle GetHeaderCtrlRect()
{
RECT rc = new RECT();
WindowsAPI.GetClientRect(hHeader, ref rc);
return new Rectangle(rc.left, rc.top, rc.right-rc.left,rc.bottom-rc.top);
}
protected bool IsCursorOnDivider()
{
HD_HITTESTINFO hti = new HD_HITTESTINFO();
hti.pt.x = LastMousePosition.X;
hti.pt.y = LastMousePosition.Y;
WindowsAPI.SendMessage(hHeader, (int)HeaderControlMessages.HDM_HITTEST, 0, ref hti);
bool hit = (hti.flags == (int)HeaderControlHitTestFlags.HHT_ONDIVIDER);
return hit;
}
protected string GetHeaderItemText(int index)
{
// I get the bug that I get on the ListView if
// I use the columns collection to retreive the text
// That's why I prefer to use the Windows API
HDITEM hdi = new HDITEM();
hdi.mask = (int)HeaderItemFlags.HDI_TEXT;
hdi.cchTextMax = BUFFER_SIZE;
hdi.pszText = Marshal.AllocHGlobal(BUFFER_SIZE);
WindowsAPI.SendMessage(hHeader, (int)HeaderControlMessages.HDM_GETITEMW, index, ref hdi);
string text = Marshal.PtrToStringAuto(hdi.pszText);
return text;
}
internal RowSorterHelper GetRowSorterHelper()
{
for ( int i = 0; i < rowSorterList.Count; i++ )
{
RowSorterHelper rs = (RowSorterHelper)rowSorterList[i];
if ( rs != null && rs.ColumnIndex == LastSortedColumn )
{
return rs;
}
}
return null;
}
#endregion
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -