|
The VB version is very good, but it's not easy to convert to C#, Do you have one or andy advice?
Thanks!
Stephen
|
|
|
|
|
<pre>using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;
namespace TreeViewTester
{
/// <summary>
/// This treeview can mix bold and normal text
/// </summary>
public partial class TreeViewBold : TreeView
{
#region Structs for HandleNotify
private struct RECT
{
internal int left;
internal int top;
internal int right;
internal int bottom;
}
private struct NMHDR
{
internal IntPtr hwndFrom;
internal IntPtr idFrom;
internal int code;
}
private struct NMCUSTOMDRAW
{
internal NMHDR hdr;
internal int dwDrawStage;
internal IntPtr hdc;
internal RECT rc;
internal IntPtr dwItemSpec;
internal int uItemState;
internal IntPtr lItemlParam;
}
private struct NMTVCUSTOMDRAW
{
internal NMCUSTOMDRAW nmcd;
internal int clrText;
internal int clrTextBk;
internal int iLevel;
}
#endregion
#region Constructor
public TreeViewBold()
{
InitializeComponent();
}
#endregion
#region Overrides
protected override void WndProc(ref Message m)
{
const int WM_NOTIFY = 0x4E;
bool isHandled = false;
if (m.Msg == (0x2000 | WM_NOTIFY)) // It is the reflected WM_NOTIFY message sent to the parent
{
if (m.WParam.Equals(this.Handle))
{
int result = HandleNotify(m);
m.Result = new IntPtr(result);
isHandled = true;
}
}
if (!isHandled)
{
base.WndProc(ref m);
}
}
#endregion
#region Private Methods
private int HandleNotify(Message m)
{
// Reference:
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/custdraw/custdraw.asp
#region Constants
const int NM_FIRST = 0;
const int NM_CUSTOMDRAW = NM_FIRST - 12;
// Drawstage flags
const int CDDS_PREPAINT = 0x1;
const int CDDS_POSTPAINT = 0x2;
const int CDDS_PREERASE = 0x3;
const int CDDS_POSTERASE = 0x4;
const int CDDS_ITEM = 0x10000;
const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
const int CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT);
const int CDDS_ITEMPREERASE = (CDDS_ITEM | CDDS_PREERASE);
const int CDDS_ITEMPOSTERASE = (CDDS_ITEM | CDDS_POSTERASE);
const int CDDS_SUBITEM = 0x20000;
// Custom draw return flags
const int CDRF_DODEFAULT = 0x0;
const int CDRF_NEWFONT = 0x2;
const int CDRF_SKIPDEFAULT = 0x4;
const int CDRF_NOTIFYPOSTPAINT = 0x10;
const int CDRF_NOTIFYITEMDRAW = 0x20;
const int CDRF_NOTIFYSUBITEMDRAW = 0x20; // Flags are the same, we can distinguish by context
const int CDRF_NOTIFYPOSTERASE = 0x40;
#endregion
int retVal = 0;
try
{
if (!m.LParam.Equals(IntPtr.Zero))
{
object objObject = m.GetLParam(typeof(NMHDR));
if (objObject is NMHDR)
{
NMHDR tNMHDR = (NMHDR)objObject;
if (tNMHDR.code == NM_CUSTOMDRAW)
{
objObject = m.GetLParam(typeof(NMTVCUSTOMDRAW));
if (objObject is NMTVCUSTOMDRAW)
{
NMTVCUSTOMDRAW tNMTVCUSTOMDRAW = (NMTVCUSTOMDRAW)objObject;
switch (tNMTVCUSTOMDRAW.nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
retVal = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
retVal = CDRF_NOTIFYPOSTPAINT;
break;
case CDDS_ITEMPOSTPAINT:
TreeNodeBold treeNode = (TreeNodeBold)TreeNode.FromHandle(this, tNMTVCUSTOMDRAW.nmcd.dwItemSpec);
using (Graphics graphics = Graphics.FromHdc(tNMTVCUSTOMDRAW.nmcd.hdc))
{
// Paint this tree node
PaintTreeNode(treeNode, graphics);
}
retVal = CDRF_DODEFAULT;
break;
}
}
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "TreeView.HandleNotify", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
// Exit Function
return retVal;
}
private void PaintTreeNode(TreeNodeBold treeNode, Graphics graphics)
{
if (treeNode == null)
{
return;
}
// Get the fonts
Font fontNormal = this.Font;
using (Font fontBold = new Font(fontNormal, FontStyle.Bold))
{
// Get the position and dimensions of the treenode (these get modified later, so I can't store them in a rectangle)
float x = treeNode.Bounds.X + 1f;
float y = treeNode.Bounds.Y + 1f;
float width = treeNode.Bounds.Width - 2f;
float height = treeNode.Bounds.Height - 2f;
#region Calculate Locations
int treeNodeWidth;
string textPortion1 = "";
string textPortion2 = "";
string textPortion3 = "";
// If there is text in bold, we need to get the width of each portion
if (treeNode.BoldTextInitialPosition >= 0 && treeNode.BoldTextLength > 0)
{
#region Contains Bold Text
// Get the text before the bold portion
textPortion1 = treeNode.Text.Substring(0, treeNode.BoldTextInitialPosition);
// Get the text of the bold portion
textPortion2 = treeNode.Text.Substring(treeNode.BoldTextInitialPosition, treeNode.BoldTextLength);
// Get the text after the bold portion
if (treeNode.BoldTextInitialPosition + treeNode.BoldTextLength < treeNode.Text.Length)
{
textPortion3 = treeNode.Text.Substring(treeNode.BoldTextInitialPosition + treeNode.BoldTextLength);
}
// Get the width of each portion, taking into account the font
float treeNodeWidthPortion1 = MeasureCorrectedTextWidth(graphics, fontNormal, width, height, textPortion1);
float treeNodeWidthPortion2 = MeasureCorrectedTextWidth(graphics, fontBold, width, height, textPortion2);
float treeNodeWidthPortion3 = MeasureCorrectedTextWidth(graphics, fontNormal, width, height, textPortion3);
// Get the total width
treeNodeWidth = Convert.ToInt32(treeNodeWidthPortion1 + treeNodeWidthPortion2 + treeNodeWidthPortion3);
#endregion
}
else
{
// Standard Text
treeNodeWidth = treeNode.Bounds.Width;
}
// Make a correction to ensure always a correct width
treeNodeWidth += 6;
#endregion
#region Get Brushes
// Get the brushes. Note: we should take into account the BackColor and ForeColor of the treenode (left as exercise)
Brush objBackgroundBrush = null;
Brush objForegroundBrush = null;
if (this.SelectedNode == treeNode)
{
objBackgroundBrush = SystemBrushes.Highlight;
objForegroundBrush = SystemBrushes.HighlightText;
}
else
{
objBackgroundBrush = SystemBrushes.Window;
objForegroundBrush = SystemBrushes.WindowText;
}
#endregion
// Fill the background rectangle
graphics.FillRectangle(objBackgroundBrush, treeNode.Bounds.X, treeNode.Bounds.Y, treeNodeWidth, treeNode.Bounds.Height);
// Draw focus rectangle if it is the selected treenode
if (this.SelectedNode == treeNode)
{
using (Pen objPen = new Pen(Color.Gray, 1))
{
objPen.DashStyle = DashStyle.Dot;
graphics.DrawRectangle(objPen, treeNode.Bounds.X, treeNode.Bounds.Y, treeNodeWidth - 1, treeNode.Bounds.Height - 1);
}
}
#region Draw Text
// Draw the text
if (treeNode.BoldTextInitialPosition >= 0)
{
// Part 1
if (textPortion1 != "")
{
x += PaintText(graphics, textPortion1, fontNormal, objForegroundBrush, x, y, width, height);
}
// Part 2
if (textPortion2 != "")
{
x += PaintText(graphics, textPortion2, fontBold, objForegroundBrush, x, y, width, height);
}
// Part 3
if (textPortion3 != "")
{
// If the first character after the bold portion is a space character, add an extra pixel
if (textPortion3.StartsWith(" "))
{
x += 1;
}
x += PaintText(graphics, textPortion3, fontNormal, objForegroundBrush, x, y, width, height);
}
}
else
{
x += PaintText(graphics, treeNode.Text, fontNormal, objForegroundBrush, x, y, width, height);
}
#endregion
}
}
private float PaintText(Graphics graphics, string text, Font font, Brush brush, float x, float y, float width, float height)
{
graphics.DrawString(text, font, brush, x, y);
float retVal = MeasureCorrectedTextWidth(graphics, font, width, height, text);
return retVal;
}
private float MeasureCorrectedTextWidth(Graphics graphics, Font font, float width, float height, string text)
{
float retVal = 0f;
if (text != "")
{
// The measurement routine (MeasureCharacterRanges) adds some extra pixels to the result, that we want to discard.
// To do this, we meausure the string and the string duplicated, and the difference is the measure that we want.
// That is:
// A = X + C
// B = 2X + C
// Where A and B are known (the measures) and C is unknown. We are interested in X, which is X = B - A
float singleTextWidth = MeasureTextWidth(graphics, font, width, height, text);
float doubleTextWidth = MeasureTextWidth(graphics, font, width * 2, height, text + text);
retVal = doubleTextWidth - singleTextWidth;
}
return retVal;
}
private float MeasureTextWidth(Graphics graphics, Font font, float width, float height, string text)
{
// Allow enough width for the bold case
float actualWidth = width;
if (font.Bold)
{
actualWidth *= 2;
}
RectangleF layoutRect = new RectangleF(0, 0, actualWidth, height);
CharacterRange[] charRanges = new CharacterRange[1];
charRanges[0] = new CharacterRange(0, text.Length);
StringFormat stringFormat = new StringFormat();
stringFormat.SetMeasurableCharacterRanges(charRanges);
Region[] regions = graphics.MeasureCharacterRanges(text, font, layoutRect, stringFormat);
RectangleF measureRect = regions[0].GetBounds(graphics);
// Exit Function
return measureRect.Width;
}
#endregion
}
#region Class: TreeNodeBold
public partial class TreeNodeBold : TreeNode
{
#region Declaration Section
//TODO: Support a list of bold selections
private int _boldTextInitialPosition;
private int _boldTextLength;
#endregion
#region Constructor
public TreeNodeBold(string text, int boldTextInitialPosition, int boldTextLength)
{
base.Text = text;
_boldTextInitialPosition = boldTextInitialPosition;
_boldTextLength = boldTextLength;
}
#endregion
#region Public Properties
public int BoldTextInitialPosition
{
get
{
return _boldTextInitialPosition;
}
}
public int BoldTextLength
{
get
{
return _boldTextLength;
}
}
#endregion
}
#endregion
}
</pre>
and the designer file:
<pre>namespace TreeViewTester
{
partial class TreeViewBold
{
/// <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 Component 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()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}
</pre>
|
|
|
|
|
New to 2.0 is the DrawItem/DrawNode methods, which eliminate the need to override WndProc. You could even make the PaintTreeNode method a static method in a helper utility. If you do, you would need to tell it whether the node is selected or not.
But these snippets assume that you are creating a derived treeview.
In the constructor, set this:
this.DrawMode = TreeViewDrawMode.OwnerDrawText;
Then override DrawNode:
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
PaintTreeNode((TreeNodeBold)e.Node, e.Graphics);
}
|
|
|
|