Erik Porter's Blog

Life and Development at Microsoft and Other Technology Discussions

News

    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

    Comments

    Eric said:

    I see this construction:
    bool isAppropriateOS = os.Platform == PlatformID.Win32NT && os.Version.Major >= 5 && os.Version.Minor >= 1;
    in a number of examples, and every time I am struck -- it's not future proof.

    What happens if Longhorn comes out with a version number of 6.0?
    os.Version.Major >= 5 && os.Version.Minor >= 1
    will be false, even though it will support themes. In fact, the *only* construct this is valid for is Windows XP (and possible 2003?). If that's the intent, that's fine, but it seems to me that this error is often made when the programmer meant to say "XP and above".

    The line should be
    bool isAppropriateOS = os.Platform == PlatformID.Win32NT && ((os.Version.Major == 5 && os.Version.Minor >= 1) || os.Version.Major > 5);
    # September 25, 2004 6:00 PM

    Erik Porter said:

    That's very true, but the software we make is usually for small to medium businesses. Most of them won't switch to LongHorn and even if they did, when is it coming out again? ;)

    If you really want to get technical about it, the code you provided won't work in the future either. What about 64 bit Windows? The os.Platform won't be Win32NT, right?

    For the sake of cleanliness though, I changed it to your code...thanks!
    # September 25, 2004 6:10 PM

    nospamplease75@yahoo.com (Haacked) said:

    Hey man, congratulations on getting engaged!
    # September 26, 2004 4:57 AM

    Cory Smith said:

    Thanks for the linkage :-D and thanks again to Raghavendra for providing the answer.
    # September 27, 2004 2:10 PM

    Jay said:

    Dear Erik,
    I do not want to dispaly the checkBox for the root nodes. For the subsequent Nodes I want the functionality same as the TriStateTreeView component. How do I go for It?

    Regards,

    Jay
    # October 21, 2004 9:10 AM

    Erik Porter said:

    Hi Jay, offhand, I honestly don't know, but I'd imagine if you just make a new boolean property on the control (maybe something like ShowRootCheckBox) and then put where in the code the checkbox image is shown, check if it's the root node and that property is set to true and show it or don't show it. Sorry I don't have time to code it.
    # October 21, 2004 2:00 PM

    Jay said:

    Dear Erik,
    Thanks a lot, I will try.
    Regards,
    Jay.
    # October 22, 2004 12:48 AM