Skip to content

Programming for Vista, Part 1: Creating a Vista CommandLink in Managed Code

As you may be aware, there is no way currently in .NET for accessing the built-in Vista API’s via a managed code interface which gives you access to the new Desktop Windows Manager (DWM) API which enables Aero glass effects featured in Windows Vista and higher (which I will cover in the next article) and also no native managed code accessibility for the suite of Vista controls, namely TaskDialog and Command Links.  I have already blogged about the Vista TaskDialog which is a cool multi-purpose message dialog window which is highly customisable and offers a multitude of options.

However, what I will demonstrate in this multi-part series is a way in which you can access these controls and effects by hooking in via unmanaged code and calling the API directly.  This may sound ugly and scary, but it is relatively non-trivial and the CommandLink especially by itself is very easy to get going.  I am aware of a few articles on the net which describe this behaviour, but I didn’t find one with a solid sample or a fallback solution for pre-Vista OS user’s, so in this article I strive to achive both.

Having said that, I endeavour anyway to make this very easy for you to get up and running, by wrapping up the steps I do here into a sample application with source code posted below for immediate use. (Side note: I do not know a terrible amount on WPF, this article will focus on achieving this within Windows Forms, however I assume this code is re-usable in WPF land)


BACKGROUND

What are we talking about here, a CommandLink button? It boils down to a simple click button derived from the Win32 Button control which has simply been extended and is often found in a range of native Windows applications and tools, one of which most people are familiar with in which the CommandLink button can be found is the “Add Printer” option from the Printers section of the Control Panel.

This is a screenshot of  two CommandLink’s in all their glory featured in the “Add Printer” dialog:


CODE

As I explained earlier, CommandLink’s are derived from the standard Win32 button, so I would recommend creating a new custom control based on Button in a separate file.  I personally prefer storing custom UI components away from my main application, in a separate class library project so they can be re-used for future projects with ease and keeping in line with the DRY principal.

So let’s get started — Create a CommandLink.cs class file and inherit from Button:

        public class CommandLink : System.Windows.Forms.Button
        { ... }

In the constructor we need to force this button’s style to be of type “System” for it to be recognised as a CommandLink.  If you do not wish to fallback to a standard button in the case where Vista is not available, feel free to remove the if statement. I’m not sure what would happen if you did this and ran it on pre-Vista, I imagine an exception would be thrown at the dll import in the next statement…

        if (!isLegacyOS)
            this.FlatStyle = FlatStyle.System;

Now set a new isLegacyOS variable if you wish to handle non-Vista OS users gracefully by rolling back to a standard windows button, setup some hex variables to Interop references of Command Link and initiate an external call to user32.dll which is featured in Vista, you may also be required to reference System.Runtime.InteropServices:

        private bool isLegacyOS = System.Environment.OSVersion.Version.Major < 6;

        const int BS_COMMANDLINK = 0x0000000E;
        const uint BCM_SETNOTE = 0x00001609;
        const uint BCM_SETSHIELD = 0x0000160C;

        // Override used to send the BCM_SETNOTE/BCM_SETSHIELD message
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, IntPtr wParam, string lParam);
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam)

Now we must override the CreateParams property and set the style of the button to the BS_COMMANDLINK variable and tells the button to be of this type. Again, feel free to remove the if (!isLegacyOS) test if you do not require legacy, pre-Vista OS support and also the return base.CreateParams underneath.

        protected override CreateParams CreateParams
        {
            get
            {
                if (!isLegacyOS)
                {
                    CreateParams cp = base.CreateParams;
                    cp.Style |= BS_COMMANDLINK;
                    return (cp);
                }
                return base.CreateParams;
            }
        }

Now create two public helper methods (optional) the first being SetNote, which allows you to one set description text which will appear under the primary text of the CommandLink, and the second SetShieldIcon enables the Vista shield icon to be displayed which is typcically denoted when administrative escalation is required.

        ///

        /// Set Note Text
        /// 

        ///

        public void SetNote(string noteText)
        {
            if (!isLegacyOS)
                SendMessage(new HandleRef(this, this.Handle), BCM_SETNOTE,
                            IntPtr.Zero, noteText);
        }

        ///

        /// Set Shield Icon
        /// 

        ///

        public void SetShieldIcon(bool show)
        {
            if (!isLegacyOS)
                SendMessage(new HandleRef(this, this.Handle),
                            BCM_SETSHIELD, IntPtr.Zero, new IntPtr(show ? 1 : 0));
        }

That's all there is to it! You'll have a brand spankin' new Command Link button to play with. If you are using the isLegacyOS flag, then pre-Vista machines will view a standard Windows button, but Vista or greater OS users will see a great looking CommandLink button!

Download: Windows Vista CommandLink Application Sample Source Code

VN:F [1.9.1_1087]
Rating: 5.0/5 (1 vote cast)
VN:F [1.9.1_1087]
Rating: 0 (from 0 votes)
Programming for Vista, Part 1: Creating a Vista CommandLink in Managed Code, 5.0 out of 5 based on 1 rating
Bookmark and Share
kick it on DotNetKicks.com
Shout it

NOW, FOR A WORD FROM OUR SPONSORS

6 Comments

  1. You have an interesting line here:
    new IntPtr(show ? 1 : 0).ToString()

    Why not use the following instead?
    show ? “1″ : “0″

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 17-Feb-09 at 11:38 pm | Permalink
  2. Okay, I figured it out

    You saw a code sample with new IntPtr(show ? 1 : 0) but your SendMessage accepts a string, so you used ToString. That is not the right thing to do – SetShieldIcon(false) will still set it to true. That is because the string is sent as a pointer and since both “1″ and “0″ are non-null pointers, they are both treated as TRUE. What you should do is call the SendMessage overload that accepts an IntPtr:

    private static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

    I used the following utility function, and it works:
    public static IntPtr SendMessage_Bool(Control control, uint Msg, IntPtr wParam, bool lParam) {
    return SendMessage(GetHandleRef(control), Msg, wParam, new IntPtr(lParam ? 1 : 0));
    }

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 17-Feb-09 at 11:48 pm | Permalink
  3. Oh, and it also uses this:

    private static HandleRef GetHandleRef(Control control) {
    return new HandleRef(control, control.Handle);
    }

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 17-Feb-09 at 11:49 pm | Permalink
  4. Graham O'Neale

    Oh sorry configurator, I briefly remember struggling with that and I believe I had to add ToString like you’re indicating. I think SendMessage_Bool() is still not completely right as we are just doing what we had before – running SendMessage() without IntPtr ToString() which results in ‘IntPtr cannot convert to type string’.

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 18-Feb-09 at 9:23 am | Permalink
  5. Graham O'Neale

    Ps. I see you everywhere configurator, first on Stack Overflow, now on my blog :) Not that I mind the traffic…. =)

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 18-Feb-09 at 9:24 am | Permalink
  6. Graham O'Neale

    @configurator: Got back on this problem and made the necessary fixes, slightly different to your suggested implementation. Thanks for the spot.

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 18-Feb-09 at 1:46 pm | Permalink

3 Trackbacks/Pingbacks

  1. DotNetShoutout on 09-Feb-09 at 9:25 pm

    Programming for Vista, Part 1: Creating a CommandLink in Managed Code…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
  2. [...] & Life Just another WordPress.com weblog AboutUseful « Programming for Vista, Part 1: Creating a Vista CommandLink in Managed Code Unexpected Results using InvokeRequired and Multi-threading Secrets Revealed! [...]

  3. [...] ui, vista, winforms, wpf In part 1 of my Vista programming series I discussed how to add CommandLink’s to your application. In this chapter, part 2, I wish to discuss how you can enhance your Windows smart client [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*
My name is Graham O'Neale and I'm a software architect from Gold Coast, Australia. I am an overtime thinker, full time coder and awake part time in the real world. I have a keen interest in software development, particularly in the realm of programming (C#, ASP.NET, ASP.NET MVC, LINQ (2 SQL), Entity Framework, Silverlight, Blend, WCF, WPF) and a keen interest in the cutting edge and innovation. I have a new found love for design patterns, ALT.NET practices and well crafted software architecture. The purpose of this blog is to express any thoughts, findings, tips and gripes along my travels in the wonderful world of coding and technology...