|
Sorry if this is late, just thought I would post it anyway though.
When I ran the exe from VS2005, I received an exception, though it still ran fine.
Problem:
Line 106 of TreeViewEx.vb:
PaintTreeNode(objTreeNodeEx, objGraphics)
Fix:
if not objTreeNodeEx is nothing then PaintTreeNode(objTreeNodeEx, objGraphics)
Thanks,
Daniel
|
|
|
|
|
Try entering a node with a label having more than 268 characters. The treeview displays only 268 characters and chops off the rest..
Try
AddNodeToTreeView(objRootTreeNodeEx.Nodes, "jhdfmnasfbanmsfbmasbfsm,dfa,sbdsnjmcshkjzxc,mzxbc,mxzbc,mnxzbcm,xcm,xzncm,xzncxzmfchdjfhlisadfhslafmn,xfmnc,mxncmxzncdfhijmcnx,mnbcm,xbcnzxbcjemfndbmgmzsbmzngcmzxbcnmzxbvcmbznvbzxnmvgxzvbzxvb,sdmfdbv,djkfv,dshfjknmxzgcvkfmdbfkufebnmfbj xcvbxzfbmnedbfkjehbm,bnxbcfukefrejbnmbrkujhbm,zdncfbdzmfrgewurmnbdnmbfvmnsdbf,sadfb,msdfnmdsnfmsdnfrjbrjbtnrbt,mrnb", 4, 6)
Vaibhav Deshpande
|
|
|
|
|
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);
}
|
|
|
|
|
This works well, but when I create events like Click or AfterSelect for the TreeViewEx control they do not fire when the program runs. Am I missing something?
|
|
|
|
|
Ahh.. Lothar's correction below fixed this great, had I only read it before posting.
One thing I found, I had to modify the code to avoid getting an exception. In the HandleNotify function, there is a branch of the Select Case for "Case CDDS_ITEMPOSTPAINT" Within that case, I needed to verify that objTreeNode returned by TreeNode.FromHandle was not Nothing. Once I did that, it worked.
Most everything you know about September 11th, 2001 is a lie.
|
|
|
|
|
Great article.
I want to draw my own checkboxes and also put image on the right side of treenode. How do I approach that?
If you have the code.. please help me out!!!
Thanks.
|
|
|
|
|
The brush setting for fore and back color "left as an exercise" was a bit more work than I would have thought (and still isn't 100%). C#:
if (selected)
{
backBrush = SystemBrushes.Highlight;
foreBrush = SystemBrushes.HighlightText;
}
else
{
if (node.BackColor.A != 255)
if (this.BackColor.A != 255)
backBrush = SystemBrushes.Window;
else
backBrush = new SolidBrush(this.BackColor);
else
backBrush = new SolidBrush(node.BackColor);
if (node.ForeColor.A != 255)
if (this.ForeColor.A != 255)
foreBrush = SystemBrushes.WindowText;
else
foreBrush = new SolidBrush(this.ForeColor);
else
foreBrush = new SolidBrush(node.ForeColor);
}
If you use partially-transparent colors, God help you. This should work for opaque colors. FYI, if you *don't* set the colors, the default is transparent for both fore and back color.
Thanks for the code!
GW
|
|
|
|
|
Du to the excellent article I was able to solve my problem with special formatting of tree nodes. However, using regular action like a popup menue or drag and drop are disabled due to a flaw in the WndProc. Although the HandleNotify routine handles only a few events (those return iResult<>0) the WndProc does not forward these, it treats all events as 'custom handled'. To correct this behavior, modify WndProc as follows:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_NOTIFY As Integer = &H4E
Dim iResult As Integer
Dim bHandled As Boolean = False
If m.Msg = (&H2000 Or WM_NOTIFY) Then ' It is the reflected WM_NOTIFY message sent to the parent
If m.WParam.Equals(Me.Handle) Then
iResult = HandleNotify(m)
If iResult <> 0 Then 'fkl 20050812
m.Result = New IntPtr(iResult)
bHandled = True
End If 'fkl 20050812
End If
End If
If Not bHandled Then
MyBase.WndProc(m)
End If
End Sub
Check, whether iResult is <> 0, before setting bHandled to True and MyBase.WndProc(m) will be called whenever there is no custom interception.
|
|
|
|
|
Thanks for the article and the code, very useful! I was just wondering if it was possible to custom draw the icons on the treeview as well? I want to display multiple icons on some of the nodes.
|
|
|
|
|
Thanks for this excellent example of owner-drawn TreeViews. I'm in the process of cooking one up for a slightly different purpose, and you've saved me the trouble of having to wade through the Win32 docs myself.
|
|
|
|
|
you got my 5.. i needed an example just like it for an app i am working on!
|
|
|
|
|