VS-Like TreeView Control and updates
Man oh man I have been out of it. A lot has been going on in my life and it's been over a month since I last blogged. I've been really busy with work and have been doing some writing. I am also now engaged and will be getting married May 28th 2005! Now, no more excuses for why I haven't been blogging and let's get to it! ;)
I saw this very cool control on Code Project and thought I'd share. It's a 3 state TreeView Control meaning when you check the CheckBox next to a TreeNode it checks all the children and also when unchecking/checking children, if not all children are checked, but some are, the parents back up the chain will display as checked, but grayed out. There were a few things I needed to change to make this control really the way I wanted it to work though and I thought I'd share:
Visual Styles Awareness
First problem right off the bat is that the control doesn't support visual styles at all. There are a couple different angles we have to look at to get it to really work correctly. We need to make sure that the current system and it's current theming state allows for visual styles. then, if it does, check to see if the application we're currently running in has them turned on.
The first bit of code we need to add are some Win32 API calls (The project was already in C# so I just went with it. Where I mention credits below, Cory Smith has a neat VB version).
[StructLayout(LayoutKind.Sequential)]
public struct DLLVersionInfo
{
public int cbSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformID;
} [DllImport("user32.dll", CharSet=CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, TreeViewMessages msg, int wParam, ref TV_HITTESTINFO lParam);
[DllImport("UxTheme.dll", CharSet=CharSet.Auto)]
private static extern bool IsAppThemed();
[DllImport("UxTheme.dll", CharSet=CharSet.Auto)]
private static extern bool IsThemeActive();
[DllImport("comctl32.dll", CharSet=CharSet.Auto)]
private static extern int DllGetVersion(ref DLLVersionInfo version);
Here's the code needed to wrap it up into a nice little Method.
private bool VisualStylesEnabled()
{
OperatingSystem os = Environment.OSVersion;
bool isAppropriateOS = os.Platform == PlatformID.Win32NT && ((os.Version.Major == 5 && os.Version.Minor >= 1) || os.Version.Major > 5);
bool osFeatureThemesPresent = false;
bool osThemeDLLAvailable = false; if (isAppropriateOS)
{
Version osThemeVersion = OSFeature.Feature.GetVersionPresent(OSFeature.Themes);
osFeatureThemesPresent = osThemeVersion != null;
DLLVersionInfo dllVersion = new DLLVersionInfo();
dllVersion.cbSize = Marshal.SizeOf(typeof(DLLVersionInfo));
int temp = DllGetVersion(ref dllVersion);
osThemeDLLAvailable = dllVersion.dwMajorVersion >= 6;
}
return isAppropriateOS && osFeatureThemesPresent && osThemeDLLAvailable && IsAppThemed() && IsThemeActive();
}
Raghavendra Prabhu has a good explanation of each thing to check for in the credits below. With all this in place, we can actually add the visual styles to the control. Add a new ImageList to the Component called "m_TriStateImagesXP" (for the sake of following the existing naming convention) and add the following images to it:
Index 0
Index 1
Index 2
Now modify the code of the constructor to look like this:
public TriStateTreeView()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent(); if (VisualStylesEnabled())
ImageList = m_TriStateImagesXP;
else
ImageList = m_TriStateImages;
ImageIndex = (int)CheckState.Unchecked;
SelectedImageIndex = (int)CheckState.Unchecked;
}
Not bad, but luckily, Visual Styles is a lot better integrated in Whidbey.
Credits here and here when I was adding this functionality.
AfterCheck Event
The AfterCheck Event is one often use in a TreeView and unfortunately doesn't fire off in the provided code. When something doesn't work how you want it to, change it! In my mind, I would only want the AfterCheck Event to fire once for the actual TreeNode that I checked, but you could easily have it fire off for every CheckBox affected as well or possibly make a new Event like AfterInherentCheck or something like that. To add in AfterCheck, add a call to OnAfterCheck in the ChangeNodeState Method, passing in the TreeViewEventArgs. The resulting code looks like this:
private void ChangeNodeState(TreeNode node)
{
CheckState newState;
BeginUpdate();
if (node.ImageIndex == (int)CheckState.Unchecked || node.ImageIndex < 0)
newState = CheckState.Checked;
else
newState = CheckState.Unchecked;
CheckNode(node, newState);
ChangeParent(node.Parent);
OnAfterCheck(new TreeViewEventArgs(node));
EndUpdate();
} Cleanup Designer
The last thing to do is to take out properties from the designer that are no longer appropriate. Just add the following code to hide them:
[Browsable(
false)]
public new bool CheckBoxes
{
get { return base.CheckBoxes; }
set { base.CheckBoxes = value; }
} [Browsable(
false)]
public new int ImageIndex
{
get { return base.ImageIndex; }
set { base.ImageIndex = value; }
} [Browsable(
false)]
public new ImageList ImageList
{
get { return base.ImageList; }
set { base.ImageList = value; }
} [Browsable(
false)]
public new int SelectedImageIndex
{
get { return base.SelectedImageIndex; }
set { base.SelectedImageIndex = value; }
} The end result is something pretty similar to the Visual Studio Tree View control that is aware of Visual Styles.
UPDATE:
Here are some screen shots at different states:
| No Theming | With Theming No Visual Styles | With Theming And Visual Styles |
 |  |  |