0

I want to add the "About" menu to context menu of title bar in my application.

I made many searches over internet and found this great topic How can I customize the system menu of a Windows Form? but there is no solution for visual C++. I tried to adapt solutions form mentioned topic but something goes wrong. I can see that something new appears in context menu but there is no "About" item.

Here is my code:

#pragma once
#include <string>

namespace context_menu {

    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Drawing;
    using namespace System::Runtime::InteropServices;
    using std::string;

    // P/Invoke constants
    const int WM_SYSCOMMAND = 0x112;
    const int MF_STRING = 0x0;
    const int MF_SEPARATOR = 0x800;

    // P/Invoke declarations
    [DllImport("user32.dll", CharSet = System::Runtime::InteropServices::CharSet::Auto, SetLastError = true)]
    extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [DllImport("user32.dll", CharSet = System::Runtime::InteropServices::CharSet::Auto, SetLastError = true)]
    extern bool AppendMenu(IntPtr hMenu, int uFlags, int uIDNewItem, string lpNewItem);

    [DllImport("user32.dll", CharSet = System::Runtime::InteropServices::CharSet::Auto, SetLastError = true)]
    extern bool InsertMenu(IntPtr hMenu, int uPosition, int uFlags, int uIDNewItem, string lpNewItem);


    /// <summary>
    /// Summary for Form1
    ///
    /// WARNING: If you change the name of this class, you will need to change the
    ///          'Resource File Name' property for the managed resource compiler tool
    ///          associated with all .resx files this class depends on.  Otherwise,
    ///          the designers will not be able to interact properly with localized
    ///          resources associated with this form.
    /// </summary>
    public ref class Form1 : public System::Windows::Forms::Form
    {
    // ID for the About item on the system menu
    private: static int SYSMENU_ABOUT_ID = 0x1;
    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
        }

    protected:
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        ~Form1()
        {
            if (components)
            {
                delete components;
            }
        }

    private:
        /// <summary>
        /// Required designer variable.
        /// </summary>
        System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        void InitializeComponent(void)
        {
            this->SuspendLayout();
            // 
            // Form1
            // 
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(284, 261);
            this->Name = L"Form1";
            this->Text = L"Form1";
            this->ResumeLayout(false);
        }
#pragma endregion

    protected: virtual System::Void OnHandleCreated( System::EventArgs^ e ) override {
                System::Windows::Forms::Form::OnHandleCreated( e );
                // Get a handle to a copy of this form's system (window) menu
                IntPtr sysMenuHandle = GetSystemMenu( this->Handle, false );
                // Add a separator
                AppendMenu( sysMenuHandle, MF_SEPARATOR, 0, "" );
                // Add the About menu item
                AppendMenu( sysMenuHandle, MF_STRING, SYSMENU_ABOUT_ID, "&About…\n" );
            }

    protected: virtual System::Void WndProc( Message % m ) override {
                System::Windows::Forms::Form::WndProc( m );
                // Show test message
                if( ( m.Msg == WM_SYSCOMMAND ) && ( (int)m.WParam == SYSMENU_ABOUT_ID ) )
                {
                    MessageBox::Show( "Custom About Dialog" );
                }
            }
    };
}

And here is my result.

EDIT

According to @David Yaw answer I added #include <Windows.h> and removed all unnecessary code. I needed to solve some minor problems and after a while it compiled. But then appeared linker errors:

1>context_menu.obj : error LNK2028: unresolved token (0A00001A) "extern "C" int __stdcall AppendMenuW(struct HMENU__ *,unsigned int,unsigned int,wchar_t const *)" (?AppendMenuW@@$$J216YGHPAUHMENU__@@IIPB_W@Z) referenced in function "protected: virtual void __clrcall context_menu::Form1::OnHandleCreated(class System::EventArgs ^)" (?OnHandleCreated@Form1@context_menu@@$$FM$AAMXP$AAVEventArgs@System@@@Z)
1>context_menu.obj : error LNK2028: unresolved token (0A00001B) "extern "C" struct HMENU__ * __stdcall GetSystemMenu(struct HWND__ *,int)" (?GetSystemMenu@@$$J18YGPAUHMENU__@@PAUHWND__@@H@Z) referenced in function "protected: virtual void __clrcall context_menu::Form1::OnHandleCreated(class System::EventArgs ^)" (?OnHandleCreated@Form1@context_menu@@$$FM$AAMXP$AAVEventArgs@System@@@Z)
1>context_menu.obj : error LNK2019: unresolved external symbol "extern "C" int __stdcall AppendMenuW(struct HMENU__ *,unsigned int,unsigned int,wchar_t const *)" (?AppendMenuW@@$$J216YGHPAUHMENU__@@IIPB_W@Z) referenced in function "protected: virtual void __clrcall context_menu::Form1::OnHandleCreated(class System::EventArgs ^)" (?OnHandleCreated@Form1@context_menu@@$$FM$AAMXP$AAVEventArgs@System@@@Z)
1>context_menu.obj : error LNK2019: unresolved external symbol "extern "C" struct HMENU__ * __stdcall GetSystemMenu(struct HWND__ *,int)" (?GetSystemMenu@@$$J18YGPAUHMENU__@@PAUHWND__@@H@Z) referenced in function "protected: virtual void __clrcall context_menu::Form1::OnHandleCreated(class System::EventArgs ^)" (?OnHandleCreated@Form1@context_menu@@$$FM$AAMXP$AAVEventArgs@System@@@Z)

So what now?

Code:

#pragma once
#include <Windows.h>
#include <string>

namespace context_menu {

    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Drawing;
    using namespace System::Runtime::InteropServices;
    using std::string;

    /// <summary>
    /// Summary for Form1
    ///
    /// WARNING: If you change the name of this class, you will need to change the
    ///          'Resource File Name' property for the managed resource compiler tool
    ///          associated with all .resx files this class depends on.  Otherwise,
    ///          the designers will not be able to interact properly with localized
    ///          resources associated with this form.
    /// </summary>
    public ref class Form1 : public System::Windows::Forms::Form
    {
    // ID for the About item on the system menu
    private: static int SYSMENU_ABOUT_ID = 0x1;


    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: Add the constructor code here
            //
        }

    protected:
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        ~Form1()
        {
            if (components)
            {
                delete components;
            }
        }

    private:
        /// <summary>
        /// Required designer variable.
        /// </summary>
        System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        void InitializeComponent(void)
        {
            this->SuspendLayout();
            // 
            // Form1
            // 
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(284, 261);
            this->Name = L"Form1";
            this->Text = L"Form1";
            this->ResumeLayout(false);

        }
#pragma endregion

    protected: virtual System::Void OnHandleCreated( System::EventArgs^ e ) override {
                // Basic function call
                System::Windows::Forms::Form::OnHandleCreated( e );
                // Pointer to this
                HWND hWnd = static_cast<HWND>( this->Handle.ToPointer() );
                // Get a handle to a copy of this form's system (window) menu
                HMENU sysMenuHandle = GetSystemMenu( hWnd, false );
                // Add a separator
                AppendMenu( sysMenuHandle, MF_SEPARATOR, 0, L"" );
                // Add the About menu item
                AppendMenu( sysMenuHandle, MF_STRING, SYSMENU_ABOUT_ID, L"&About…\n" );
            }

    protected: virtual System::Void WndProc( Message % m ) override {
                // Basic function call
                System::Windows::Forms::Form::WndProc( m );
                // komunikat
                if( ( m.Msg == WM_SYSCOMMAND ) && ( (int)m.WParam == SYSMENU_ABOUT_ID ) )
                {
                    MessageBox::Show( "Custom About Dialog" );
                }
            }
    };
}
Simon
  • 123
  • 3
  • [How to show menu item with icon AND text in WinForms system menu](https://stackoverflow.com/a/49065061/7444103). Full customization of the System menu. – Jimi Apr 15 '19 at 20:42

1 Answers1

0

You're already in C++, you don't need to P/Invoke. Just #include <Windows.h> and call the functions directly.

[DllImport("user32.dll", CharSet = System::Runtime::InteropServices::CharSet::Auto, SetLastError = true)]
extern bool AppendMenu(IntPtr hMenu, int uFlags, int uIDNewItem, string lpNewItem);
                                                                 ^^^^^^

string is a different class in C# than in C++/CLI. Since something is appearing, but it's not correct, I think it's highly likely that the string is not getting passed properly.

If you switch to calling the C functions directly, type checking for the string parameter will help you pass that parameter properly.


All that said, standard warning: While it's certainly possible to write the main body of your application in C++/CLI, or even write the GUI in C++/CLI using WinForms, it is not recommended. C++/CLI is intended for interop scenarios: where C# or other .Net code needs to interface with unmanaged C++, C++/CLI can provide the translation between the two. Because of that, C++/CLI has all of the complexities of C++, all of the complexities of C#, and some complexities of its own. For primary development, it is recommended to use C# with either WinForms or WPF if you want managed code, or C++ with MFC if you want unmanaged.

David Yaw
  • 27,383
  • 4
  • 60
  • 93
  • Thank you David Yaw. It helped but now there are linker problems. Please see my edit. BTW I'm an embedded developer: C++, FreeRTOS, etc... and I need small Windows apps that cooperate with my devices. In this apps I operate on data form this devices so there is quite a lot of C++ code that I need to use in app to operate on that data. If it's possible to use C++ and C# in simple app then I'd appreciate a link to a tutorial for that. – Simon Apr 16 '19 at 15:28
  • Per the [documentation](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-appendmenuw#requirements), you need to link User32.lib. – David Yaw Apr 17 '19 at 14:58
  • And no, there is no way to instantiate a C++ class from C#. However, you've already hit on the way to interface C# with C code: Use `[DllImport]` and P/Invoke. – David Yaw Apr 17 '19 at 14:59
  • I've already found it. In the header I need to add `#include `, `#undef GetCurrentDirectory`, `#pragma comment(lib,"user32.lib")`, and change project option from `/clr:pure` to `/clr` and now it works. – Simon Apr 18 '19 at 06:02