COM - Hide Button in a 3rd Party Windows form

Kyle Abrahams
Kyle Abrahams used Ask the Experts™
on
Hi All,

I'm looking for a way to hide a button on a 3rd party Windows form using COM or some other method.  Is there any way to achieve this?

My code is in C# but if I need to use "unsafe" code I have no issues.

Thanks.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
jkr
Top Expert 2012

Commented:
You don't need any special technologies for that - the worst would be to call the Windows API directly. In order to do that, you need the control's HWND, which you can obtain via 'FindWindow()' (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633499(v=vs.85).aspx, see http://www.codeproject.com/Articles/34981/FindWindow on how to call that API from C#)
Once you have the HWND, all you need to do is calling 'ShowWindow()' (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx) passing that HWND and 'SW_HIDE' as the 2nd parameter.
Kyle AbrahamsSenior .Net Developer

Author

Commented:
Hi Jkr,

Thanks for the help.  This is a button on the form itself . . . now like the maximize button or anything.

So find Window gets me a reference to the window . . . now I need the control (say a button named "Button1") that's on a form in that window.

Will take a look at the find window for now.  

Much appreciated!
jkr
Top Expert 2012

Commented:
Unless it's a web browser control, that approach should work, managed or unmanaged code. In case it still doesn't, you  can still use a Windows hook that does that for you, but this approach IMO is more straightfoward.
HTML5 and CSS3 Fundamentals

Build a website from the ground up by first learning the fundamentals of HTML5 and CSS3, the two popular programming languages used to present content online. HTML deals with fonts, colors, graphics, and hyperlinks, while CSS describes how HTML elements are to be displayed.

Kyle AbrahamsSenior .Net Developer

Author

Commented:
Let's start talking about a windows hook.

The whole ribbon is defined as one window:
Ribbon
jkr
Top Expert 2012

Commented:
Nah, that would be the 2nd best solution, for it's sheer complexity (you'd need control IDs etc).

What's the trouble you are having with findong the contro? You'd use 'FindWindow()' to locate the main window, and then 'EnumChildWindows()' to find the control, e.g. like in http://social.msdn.microsoft.com/Forums/vstudio/en-US/8f74e954-f1a2-4f46-8de0-f6c5423bfe46/how-to-correctly-return-a-systemintptr-value-to-enumchildwindows-lparam?forum=csharpgeneral

private static bool EnumWindowsProc(IntPtr hWnd, int lParam)

{

string title = GetWindowText(hWnd)+" "+hWnd.ToString();

if (title.Contains("something"))  //something is the string you are searching for

Whandle = hWnd;  //Whandle is defined in the class, hWnd is the current handle and is stored

mTitlesList.Add(title);

return true;

}

Open in new window

jkr
Top Expert 2012

Commented:
BTW, that's what I'd do:

- use Spy++ or it's managed counterpart (http://msdn.microsoft.com/en-us/magazine/cc163617.aspx) to get the HWND of the control in question
- hard-code the value in your C# app to test if it works
- if it does, extend the whole thing to autmatially obtain the window handle
Kyle AbrahamsSenior .Net Developer

Author

Commented:
I really appreciate the responses.  I'm going to need some time disecting.  COM is all new to me, and what to understand completely what's going on.  


For more details on what I'm trying to do:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/c69897ca-4d60-4102-ad60-d5576e0c34a4/appointment-seperate-form-hide-from-showgroup?forum=vsto

Thanks again, will post back after I try a few things.
jkr
Top Expert 2012

Commented:
Well, again - there is no COM involved for the task of hiding a control so far, and I doubt that you will have to use it.
Kyle AbrahamsSenior .Net Developer

Author

Commented:
Here's what I have so far.  I'm running into issues now because not all of the windows have titles (fun times).  

I see within the first iteration of appointment that there are some ribbons so I'm diving into those.  Is there a way to get the class?

If this isn't COM . . . I get you're just using an external windows DLL?  I haven't been this low in the windows API before.

Thanks for the help.



using System.Collections.Generic;
using System.Runtime.InteropServices;
using System;
using System.Text;
using System.Collections;

namespace IVCi_Cloud.Classes
{


    /// <summary>
    /// EnumDesktopWindows Demo - shows the caption of all desktop windows.
    /// Authors: Svetlin Nakov, Martin Kulov 
    /// Bulgarian Association of Software Developers - http://www.devbg.org/en/
    /// </summary>
    public class user32
    {
        /// <summary>
        /// filter function
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        public delegate bool EnumDelegate(IntPtr hWnd, int lParam);
        private delegate bool EnumWindowsProc(IntPtr hWnd, ref IntPtr lParam);


        /// <summary>
        /// check if windows visible
        /// </summary>
        /// <param name="hWnd"></param>
        /// <returns></returns>
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool IsWindowVisible(IntPtr hWnd);

        /// <summary>
        /// return windows text
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="lpWindowText"></param>
        /// <param name="nMaxCount"></param>
        /// <returns></returns>
        [DllImport("user32.dll", EntryPoint = "GetWindowText",
        ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);

        /// <summary>
        /// enumarator on all desktop windows
        /// </summary>
        /// <param name="hDesktop"></param>
        /// <param name="lpEnumCallbackFunction"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        [DllImport("user32.dll", EntryPoint = "EnumDesktopWindows",
        ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumDelegate lpEnumCallbackFunction, IntPtr lParam);


        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);

        public static List<IntPtr> GetChildWindows(IntPtr parent)
        {
            List<IntPtr> result = new List<IntPtr>();
            GCHandle listHandle = GCHandle.Alloc(result);
            try
            {
                EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
                EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
            }
            finally
            {
                if (listHandle.IsAllocated)
                    listHandle.Free();
            }
            return result;
        }

        private static bool EnumWindow(IntPtr handle, IntPtr pointer)
        {
            GCHandle gch = GCHandle.FromIntPtr(pointer);
            List<IntPtr> list = gch.Target as List<IntPtr>;
            if (list == null)
            {
                throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
            }
            list.Add(handle);
            //  You can modify this to check to see if you want to cancel the operation, then return a null here
            return true;
        }

        /// <summary>
        /// Delegate for the EnumChildWindows method
        /// </summary>
        /// <param name="hWnd">Window handle</param>
        /// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
        /// <returns>True to continue enumerating, false to bail.</returns>
        public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);





        public static IntPtr Whandle;
        public static int i = 0;
        /// <summary>
        /// entry point of the program
        /// </summary>
        public static ArrayList GetAllWindows()
        {
            List<IntPtr> collection = new List<IntPtr>();

            user32.EnumDelegate filter = delegate(IntPtr hWnd, int lParam)
            {
                StringBuilder strbTitle = new StringBuilder(255);
                int nLength = user32.GetWindowText(hWnd, strbTitle, strbTitle.Capacity + 1);
                string strTitle = strbTitle.ToString();

                if (strTitle.Contains("Appointment"))
                    Whandle = hWnd;

                if (user32.IsWindowVisible(hWnd) && string.IsNullOrEmpty(strTitle) == false)
                {
                    collection.Add(hWnd);
                }
                return true;
            };

            EnumedWindow callBackPtr = GetWindowHandle;
            EnumChildWindows(Whandle, callBackPtr, windowHandles);
            i++;
        
            EnumChildWindows(Whandle, callBackPtr, windowHandles);



            return windowHandles;
        }
    
        static void ClearArrays()
        {
            windowHandles.Clear();
            windowTitles.Clear();
        }


        static ArrayList windowHandles = new ArrayList();
        static ArrayList windowTitles = new ArrayList();

        private delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList lParam);

        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool EnumChildWindows(IntPtr window, EnumedWindow callback, ArrayList lParam);

        private static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles)
        {
            StringBuilder strbTitle = new StringBuilder(255);
            int nLength = user32.GetWindowText(windowHandle, strbTitle, strbTitle.Capacity + 1);
            string strTitle = strbTitle.ToString();

            if (strTitle.Contains("Appointment") && i == 0)
            {
                windowHandles.Add(windowHandle);
                windowTitles.Add(strTitle);
                Whandle = windowHandle;
            }
            else if (i==1)
            {
                windowHandles.Add(windowHandle);
                windowTitles.Add(strTitle);
            }

            return true;
        }





    }




}

Open in new window

jkr
Top Expert 2012

Commented:
>>I get you're just using an external windows DLL?

Yup, that's called 'P/Invoke', which is used to allow .NET to directly call the Windows API. The other way around is 'COM/Interop", and that one is used by unmanaged code to use managed components, with the latter acting as COM servers. And the code that you posted seems to be a nice wrapper around these API calls.

But anyway, I'd still first try to check out if that whole aproach works for you by testing that the way I suggested above.
Kyle AbrahamsSenior .Net Developer

Author

Commented:
The find window isn't working for me:

FindWindow("NetUIHWND", null);  

is returning 0.
jkr
Top Expert 2012

Commented:
So 'NetUIHWND' is the window title? Then you need to call it like

FindWindow(null, "NetUIHWND");  

Open in new window


But, again, I would first:

- use Spy++ or it's managed counterpart (http://msdn.microsoft.com/en-us/magazine/cc163617.aspx) to get the HWND of the control in question
- hard-code the value in your C# app to test if it works

It does not help a lot if you can find the control just to notice that hiding it does not work.
Kyle AbrahamsSenior .Net Developer

Author

Commented:
NetUIHWND is the class, not the window title.

I've tried to use ManagedSpy, but running into an error:
System.BadImageFormatException: Could not load file or assembly 'C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\mscorlib\cf58670896c5313b9b52f026f4455a5d\mscorlib.ni.dll' or one of its dependencies. The module was expected to contain an assembly manifest.
File name: 'C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\mscorlib\cf58670896c5313b9b52f026f4455a5d\mscorlib.ni.dll'
   at System.Reflection.AssemblyName.nGetFileInformation(String s)

I have .Net v1 - v4 all installed on my machine.
jkr
Top Expert 2012

Commented:
Do you happen to have a 'Professional' version of VS? Because in that case, Spy++ should be installed in the 'Tools' folder.
Kyle AbrahamsSenior .Net Developer

Author

Commented:
Premium.
jkr
Top Expert 2012

Commented:
Then you should have it ;o)

If it's still not there, check your installation options or use try one of these surrogates:

http://www.codeproject.com/Articles/33459/Spying-Window-Messages-from-the-Inside
http://www.codeproject.com/Articles/1698/MS-Spy-style-Window-Finder
Kyle AbrahamsSenior .Net Developer

Author

Commented:
Hi jkr,

I was  looking in the program files folder not the actual tools in VS for it.

Here are some screen shots:

I put the cursor where the red dot is (essentially over the appointment button).  The handle returns that whole section . . . there are no drill downs underneath of it.
Spy Window Search
I then captured some logs.  It looks like it's using some kind of pass through based on the mouse location?
spylog

I've highlighted the mousedown / up section for you.


In addition here's the control layout that I'm seeing:
Form layout
Any suggestions greatly appreciated.
jkr
Top Expert 2012

Commented:
I start seeing the problem - and I don't like what I see :-/
The 'Afx' in the name does not make things easier, since it plainly means that it is a MFC control, which rules out the majority of other options (like subclassing the ribbon control, which would be hard enough anyway. Let me put my thinking cap on and get back to you...
Kyle AbrahamsSenior .Net Developer

Author

Commented:
Thanks man.  I started seeing what you were talking about other apps where the button is it's own window.  Wish it were that easy, lol.
Top Expert 2012
Commented:
OK, I see a way, and neither you nor me are going to like it. First of all, because we're back to hooking and subclassing. And that can't be done in C#. Second, it's even tricky in C/C++, and quite low-level. Let me give you an outline:

- you add a global WH_CALLWNDPROCRET hook to the system (http://msdn.microsoft.com/en-us/library/windows/desktop/ms644959(v=vs.85).aspx#wh_callwndproc_wh_callwndprocret) or alternatively inject a DLL into the target process, both requires about the same effort.
- have that hook monitor the message traffic for the ribbon control or install a WndProc for the control that does this (again about the same effort)
- when the rebbon receves a WM_PAINT message, let it perform the paintig action and when it's done (thus the 'RET'), paint over the control part that you don't want to see

I told you that you aren't gonna like it ;o)
Kyle AbrahamsSenior .Net Developer

Author

Commented:
It's an attempt at a solution at least.

I don't know if it's going to work because this is for a VSTO plugin.  That Ribbon is dynamically generated and also is displayed differently depending on the size of the window.  

Eg - My previous screen shots vs:
Form Repainted

Too bad there's not a hook when it loads this DLL to say pain the icon or not.  I'm not sure if it's worth the time at this point, but I may return to it later.  Will leave this open for a few days if you think of another way.  If not I'll close and Re-open specific questions as I approach it.

I knew there was a way, figured it was going to be a hell of hack, but if it were easy, everyone would be doing it.  Microsoft said it couldn't be done, so one step ahead.
jkr
Top Expert 2012

Commented:
At that level, all that matters is "window or not" - and since it is one, this would work. VSTO or not, in the end, everything ends up calling the Windows API after all.

Size and position can be an issue in terms of 'trickyness', but since a window's size is avaiable as well and the offset can be calculated, it is indeed feasible. Yet not simple.
Kyle AbrahamsSenior .Net Developer

Author

Commented:
The premise is sound but at this point I don't think we're going to implement.  Will post follow-up questions if we ever get to that point.  Thanks for the expertise.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial