How to send a CBN_SELCHANGE message when using CB_SETCURSEL? - c++

When using the CB_SETCURSEL message, the CBN_SELCHANGE message is not sent.
How to notify a control that the selection was changed ?
P.S.
I found on the Sexchange site, a very ugly hack :
SendMessage( hwnd, 0x014F/*CB_SHOWDROPDOWN*/, 1, 0 );
SendMessage( hwnd, 0x014E/*CB_SETCURSEL*/, ItemIndex, 0 );
SendMessage( hwnd, 0x0201/*WM_LBUTTONDOWN*/, 0, -1 );
SendMessage( hwnd, 0x0202/*WM_LBUTTONUP*/, 0, -1 );
Will do for now... Not really.
P.S.2
For resolving my problem, I'll follow Ken's suggestion in the comments.

This might help the next person out:
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hwnd, int msg, int wParam, int lParam);
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLongPtr64(hWnd, nIndex);
else
return GetWindowLongPtr32(hWnd, nIndex);
}
static int MakeWParam(int loWord, int hiWord)
{
return (loWord & 0xFFFF) + ((hiWord & 0xFFFF) << 16);
}
public const int CB_SETCURSEL = 0x014E;
public const int CBN_SELCHANGE = 0x0001;
public enum GWL
{
GWL_WNDPROC = (-4),
GWL_HINSTANCE = (-6),
GWL_HWNDPARENT = (-8),
GWL_STYLE = (-16),
GWL_EXSTYLE = (-20),
GWL_USERDATA = (-21),
GWL_ID = (-12)
}
public static IntPtr Hwnd_select_control_parent = IntPtr.Zero;
public static IntPtr Hwnd_select_control = IntPtr.Zero;
static void changeit()
{
// Google WinSpy for tips on how to figure out how to get window handles from known ctrl_id
Hwnd_select_control = 14298; // or whatever the handle of the combo box is
// Get the parent of the selectbox control
Hwnd_select_control_parent = GetWindowLongPtr(service_window_control, (int)GWL.GWL_HWNDPARENT);
// Get the control id of the selectbox if you don't already have it
IntPtr nID = GetWindowLongPtr(Hwnd_select_control, (int)GWL.GWL_ID);
int ctrl_id = nID.ToInt32();
// Change the combo box to the value "My Value"
SendMessage(Hwnd_select_control, CB_SETCURSEL, "My Value", null);
// low ID is the ctrl_id of the combo box, high id is CBN_SELCHANGE
int send_cbn_selchange = MakeWParam(ctrl_id, CBN_SELCHANGE);
// Send the WM_COMMAND to the parent, not the control itself
SendMessage(Hwnd_serviceselect_control_parent, 0x111 /* WM_COMMAND */, send_cbn_selchange, Hwnd_serviceselect_control.ToInt32());
}

You're not supposed to use CBN_SELCHANGE unless the change in selection was made by the user.
You don't indicate what language you're using; it would make it easier to provide you with a workaround if you did so.
In Delphi, where an OnChange() would be associated with the combobox, you just call the event method directly:
// Send the CB_SETCURSEL message to the combobox
PostMessage(ComboBox1.Handle, CB_SETCURSEL, Whatever, WhateverElse);
// Directly call the OnChange() handler, which is the equivalent to CBN_SELCHANGE
ComboBox1Change(nil);

I just discovered calling these SendMessages to the Combobox twice works... I know it's not perfect but it worked for me. (Written in VB6)
For looper = 1 To 2
bVal = SendMessage(inHandle, COMBO.CB_SHOWDROPDOWN, True, 0)
count = SendMessage(inHandle, COMBO.CB_SETCURSEL, 1, 0)
count = SendMessage(inHandle, WIND.WM_LBUTTONDOWN, 0, -1)
Next

Related

Where is the definition of MB_ICONASTERISK stored? [duplicate]

I want to get the MessageBoxIcons, that get displayed when the user is presented with a MessageBox. Earlier I used SystemIcons for that purpose, but now it seems that it returns icons different than the ones on the MessageBox.
This leads to the conclusion that in Windows 8.1 SystemIcons and MessageBoxIcons are different. I know that icons are taken using WinApi MessageBox, but I can't seem to get the icons themselves in any way.
I would like to ask for a way of retrieving those icons.
Update:
You should use the SHGetStockIconInfo function.
To do that in C# you will have to define a few enums and structs (consult this excellent page for more information):
public enum SHSTOCKICONID : uint
{
//...
SIID_INFO = 79,
//...
}
[Flags]
public enum SHGSI : uint
{
SHGSI_ICONLOCATION = 0,
SHGSI_ICON = 0x000000100,
SHGSI_SYSICONINDEX = 0x000004000,
SHGSI_LINKOVERLAY = 0x000008000,
SHGSI_SELECTED = 0x000010000,
SHGSI_LARGEICON = 0x000000000,
SHGSI_SMALLICON = 0x000000001,
SHGSI_SHELLICONSIZE = 0x000000004
}
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHSTOCKICONINFO
{
public UInt32 cbSize;
public IntPtr hIcon;
public Int32 iSysIconIndex;
public Int32 iIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260/*MAX_PATH*/)]
public string szPath;
}
[DllImport("Shell32.dll", SetLastError = false)]
public static extern Int32 SHGetStockIconInfo(SHSTOCKICONID siid, SHGSI uFlags, ref SHSTOCKICONINFO psii);
After that you can easily get the required icon:
SHSTOCKICONINFO sii = new SHSTOCKICONINFO();
sii.cbSize = (UInt32)Marshal.SizeOf(typeof(SHSTOCKICONINFO));
Marshal.ThrowExceptionForHR(SHGetStockIconInfo(SHSTOCKICONID.SIID_INFO,
SHGSI.SHGSI_ICON ,
ref sii));
pictureBox1.Image = Icon.FromHandle(sii.hIcon).ToBitmap();
This is how the result will look like:
Please note:
If this function returns an icon handle in the hIcon member of the
SHSTOCKICONINFO structure pointed to by psii, you are responsible for
freeing the icon with DestroyIcon when you no longer need it.
i will not delete my original answer, as - I think - it contains useful information regarding this issue, and another way (or workaround) of retrieving this icon.
Original answer:
Quite interestingly the icons present in the SystemIcons differ from the ones displayed on the MessageBoxes in the case of Asterisk, Information and Question. The icons on the dialog look much flatter.
In all other cases they look exactly the same, e.g.: in case of Error:
When you try to get the icon using the SystemIcons you get the one on the left in the above images.
// get icon from SystemIcons
pictureBox1.Image = SystemIcons.Asterisk.ToBitmap();
If you try a little bit harder, using the LoadIcon method from user32.dll, you still get the same icon (as it can be seen in center of the above images).
[DllImport("user32.dll")]
static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
...
public enum SystemIconIds
{
...
IDI_ASTERISK = 32516,
...
}
...
// load icon by ID
IntPtr iconHandle = LoadIcon(IntPtr.Zero, new IntPtr((int)SystemIconIds.IDI_ASTERISK));
pictureBox2.Image = Icon.FromHandle(iconHandle).ToBitmap();
But when you show a MessagBox you get a different one (as seen in the MessageBox on the images). One has no other choice, but to get that very icon from the MessageBox.
For that we will need a few more DllImports:
// To be able to find the dialog window
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// To be able to get the icon window handle
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
// To be able to get a handle to the actual icon
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
The idea is the following: First we display a MessageBox, after that (while it is still displayed) we find it's handle, using that handle we will get another handle, now to the static control which is containing the icon. In the end we will send a message to that control (an STM_GETICON message), which will return with a handle to the icon itself. Using that handle we can create an Icon, which we can use anywhere in our application.
In code:
// show a `MessageBox`
MessageBox.Show("test", "test caption", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
...
var hwnd = FindWindow(null, "test caption");
if (hwnd != IntPtr.Zero)
{
// we got the messagebox, get the icon from it
IntPtr hIconWnd = GetDlgItem(hwnd, 20);
if (hIconWnd != IntPtr.Zero)
{
var iconHandle = SendMessage(hIconWnd, 369/*STM_GETICON*/, IntPtr.Zero, IntPtr.Zero);
pictureBox3.Image = Icon.FromHandle(iconHandle).ToBitmap();
}
}
After the code runs the PictureBox called pictureBox3 will display the same image as the MessageBox (as it can be seen on the right on the image).
I really hope this helps.
For reference here is all the code (it's a WinForms app, the Form has three PicturBoxes and one Timer, their names can be deducted from the code...):
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
public enum SystemIconIds
{
IDI_APPLICATION = 32512,
IDI_HAND = 32513,
IDI_QUESTION = 32514,
IDI_EXCLAMATION = 32515,
IDI_ASTERISK = 32516,
IDI_WINLOGO = 32517,
IDI_WARNING = IDI_EXCLAMATION,
IDI_ERROR = IDI_HAND,
IDI_INFORMATION = IDI_ASTERISK,
}
public Form1()
{
InitializeComponent();
// Information, Question and Asterix differ from the icons displayed on MessageBox
// get icon from SystemIcons
pictureBox1.Image = SystemIcons.Asterisk.ToBitmap();
// load icon by ID
IntPtr iconHandle = LoadIcon(IntPtr.Zero, new IntPtr((int)SystemIconIds.IDI_ASTERISK));
pictureBox2.Image = Icon.FromHandle(iconHandle).ToBitmap();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
MessageBox.Show("test", "test caption", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
private void timer1_Tick(object sender, EventArgs e)
{
var hwnd = FindWindow(null, "test caption");
if (hwnd != IntPtr.Zero)
{
// we got the messagebox, get the icon from it
IntPtr hIconWnd = GetDlgItem(hwnd, 20);
if (hIconWnd != IntPtr.Zero)
{
var iconHandle = SendMessage(hIconWnd, 369/*STM_GETICON*/, IntPtr.Zero, IntPtr.Zero);
pictureBox3.Image = Icon.FromHandle(iconHandle).ToBitmap();
}
}
}
}
}

Display formatted text on selecting item in the Combobox

I have a combobox in that I want to display different string on selecting an item in Combo.
My combo box is a dropdown combobox.
For eg: I have following in my combobox.
Alex - Manager
Rain - Project Lead
Shiney - Engineer
Meera - Senior Engineer
OnSelecting an item in combobox I want to diaply only name i.e. Alex.
I tried below code
struct details{
CString name;
CString des;
};
BOOL CComboTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
details d1;
d1.name = _T("alex");
d1.des =_T("manager");
m_vec.push_back(d1);
details d2;
d2.name = _T("Rain");
d2.des =_T("Engineer");
m_vec.push_back(d2);
// TODO: Add extra initialization here
for(int i=0;i<m_vec.size();i++)
{
m_ctrlCombo.AddString(m_vec[i].name+m_vec[i].des);
m_ctrlCombo.SetItemData(i,(DWORD_PTR)&m_vec[i]);
}
m_ctrlCombo.SelectString(-1,m_vec[0].name);
m_ctrlCombo.SetWindowText(m_vec[0].name);
return TRUE; // return TRUE unless you set the focus to a control
}
void CComboTestDlg::OnCbnSelchangeCombo1()
{
int nItem = m_ctrlCombo.GetCurSel();
details* det = (details*)m_ctrlCombo.GetItemData(nItem);
PostMessage(SETCOMBOTEXT,IDC_COMBO1,(LPARAM)(LPCTSTR)det->name);
}
BOOL CComboTestDlg::PreTranslateMessage(MSG* pMsg)
{
MSG msg1=*pMsg;//I am loosing the value after checking ..so storing temp.
MSG msg;
CopyMemory(&msg, pMsg, sizeof(MSG));
HWND hWndParent = ::GetParent(msg.hwnd);
while (hWndParent && hWndParent != this->m_hWnd)
{
msg.hwnd = hWndParent;
hWndParent = ::GetParent(hWndParent);
}
if (pMsg->message==SETCOMBOTEXT && (pMsg->wParam == IDC_COMBO1))
SetDlgItemText(IDC_COMBO1, (LPCTSTR)pMsg->lParam);
if(pMsg->message==WM_KEYDOWN)
{
if(pMsg->wParam==VK_RETURN && msg.hwnd ==m_ctrlCombo.m_hWnd )
{
OnCbnSelchangeCombo1();
}
}
return CDialog::PreTranslateMessage(pMsg);
}
I am able to achieve my requirement OnComboSelChange() and Arrow Keys event but on pressing enter key after using arrow keys in combo box, it is not showing formatted text in combo box.
I think the most reliable and easy to implement solution is to subclass the edit control of the combobox. Intercept the WM_SETTEXT message and change the text as you like before forwarding it to the rest of the chain (finally the original window proc).
Install the sub class proc in OnInitDialog():
COMBOBOXINFO cbi{ sizeof(cbi) };
if( m_ctrlCombo.GetComboBoxInfo( &cbi ) )
{
SetWindowSubclass( cbi.hwndItem, ComboEditSubClassProc, 0, 0 );
}
ComboEditSubClassProc() could look like this:
LRESULT CALLBACK ComboEditSubClassProc( HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch( uMsg )
{
case WM_SETTEXT:
{
CString text = reinterpret_cast<LPCTSTR>( lParam );
// Extract the name (everything before "-").
CString name = text.SpanExcluding( _T("-") );
name.TrimRight();
// Forward the modified text to any other sub class procs, aswell
// as the original window proc at the end of the chain.
return DefSubclassProc( hWnd, uMsg, 0, reinterpret_cast<LPARAM>( name.GetString() ) );
}
case WM_NCDESTROY:
{
// We must remove our subclass before the subclassed window gets destroyed.
// This message is our last chance to do that.
RemoveWindowSubclass( hWnd, ComboEditSubClassProc, uIdSubclass );
break;
}
}
return DefSubclassProc( hWnd, uMsg, wParam, lParam );
}
Notes:
Contrary to my original solution of processing CBN_SELCHANGE, the current solution also works correctly if the combobox drop-down list is closed by pressing Return or is dismissed.
I think it is in general more reliable because we don't have to rely on the order of the notifications. The combobox has to finally call WM_SETTEXT to change the content of the edit control so this message will always be received.
There will also be no flickering as in the original solution where the text was first changed by the combobox and then modified by our code only after the fact.

wxWidgets wxBORDER_NONE and wxRESIZE_BORDER makes white area

White Border
How can I remove this white area? it Ruins my GUI design.
I want to make a shadow and a blue line which generated by windows.
so I found a option that makes the blue line(wxRESIZE_BORDER) but it makes a white area like the image.
//MainFrame.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <wx/frame.h>
class MainFrame : public wxFrame
{
public:
MainFrame(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(310, 390), long style = wxSUNKEN_BORDER|wxRESIZE_BORDER);
};
//MainFrame.cpp
#include "MainFrame.h"
MainFrame::MainFrame(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxFrame(parent, id, title, pos, size, style)
{
this->Centre(wxBOTH);
}
//Main.h
#pragma once
#include <wx/wx.h>
class App : public wxApp
{
public:
virtual bool OnInit();
};
//Main.cpp
#include "Main.h"
#include "MainFrame.h"
IMPLEMENT_APP(App)
bool App::OnInit()
{
MainFrame *frame = new MainFrame(NULL);
frame->Show(true);
return true;
}
This white top border is the resize border and it belongs to the non client area of a window. So for removing it you should handle window messages related to the resizing and activating of non-client area of a window like below:
WXLRESULT MSWWindowProc( WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam )
{
/* When we have a custom titlebar in the window, we don't need the non-client area of a normal window
* to be painted. In order to achieve this, we handle the "WM_NCCALCSIZE" which is responsible for the
* size of non-client area of a window and set the return value to 0. Also we have to tell the
* application to not paint this area on activate and deactivation events so we also handle
* "WM_NCACTIVATE" message. */
switch( nMsg )
{
case WM_NCACTIVATE:
{
/* Returning 0 from this message disable the window from receiving activate events which is not
desirable. However When a visual style is not active (?) for this window, "lParam" is a handle to an
optional update region for the nonclient area of the window. If this parameter is set to -1,
DefWindowProc does not repaint the nonclient area to reflect the state change. */
lParam = -1;
break;
}
/* To remove the standard window frame, you must handle the WM_NCCALCSIZE message, specifically when
its wParam value is TRUE and the return value is 0 */
case WM_NCCALCSIZE:
if( wParam )
{
/* Detect whether window is maximized or not. We don't need to change the resize border when win is
* maximized because all resize borders are gone automatically */
HWND hWnd = ( HWND ) this->GetHandle();
WINDOWPLACEMENT wPos;
// GetWindowPlacement fail if this member is not set correctly.
wPos.length = sizeof( wPos );
GetWindowPlacement( hWnd, &wPos );
if( wPos.showCmd != SW_SHOWMAXIMIZED )
{
RECT borderThickness;
SetRectEmpty( &borderThickness );
AdjustWindowRectEx( &borderThickness,
GetWindowLongPtr( hWnd, GWL_STYLE ) & ~WS_CAPTION, FALSE, NULL );
borderThickness.left *= -1;
borderThickness.top *= -1;
NCCALCSIZE_PARAMS* sz = reinterpret_cast< NCCALCSIZE_PARAMS* >( lParam );
// Add 1 pixel to the top border to make the window resizable from the top border
sz->rgrc[ 0 ].top += 1;
sz->rgrc[ 0 ].left += borderThickness.left;
sz->rgrc[ 0 ].right -= borderThickness.right;
sz->rgrc[ 0 ].bottom -= borderThickness.bottom;
return 0;
}
}
break;
}
return wxFrame::MSWWindowProc( nMsg, wParam, lParam );
}
With wxSUNKEN_BORDER you get something like this:
Then it does not need the wxRESIZE_BORDER part. But note that this makes the cross disappear.
As Reza said, the best approach is to override MSWWindowProc.
As for my recommendation:
Set main frame flags/style this way in constructor body of your main frame class:
this->SetWindowStyle(wxSYSTEM_MENU | wxRESIZE_BORDER| wxCLIP_CHILDREN);
In main frame class, create a declaration for MSWWindowProc to override:
WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) wxOVERRIDE;
Create a body for MSWWindowProc:
WXLRESULT _YOUR_MAIN_FRAME_CLASS::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
switch (nMsg)
{
case WM_NCACTIVATE:
{
lParam = -1;
break;
}
case WM_NCCALCSIZE:
if (wParam)
{
HWND hWnd = (HWND)this->GetHandle();
WINDOWPLACEMENT wPos;
wPos.length = sizeof(wPos);
GetWindowPlacement(hWnd, &wPos);
if (wPos.showCmd != SW_SHOWMAXIMIZED)
{
RECT borderThickness;
SetRectEmpty(&borderThickness);
AdjustWindowRectEx(&borderThickness,
GetWindowLongPtr(hWnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
borderThickness.left *= -1;
borderThickness.top *= -1;
NCCALCSIZE_PARAMS* sz = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
sz->rgrc[0].top += 1;
sz->rgrc[0].left += borderThickness.left;
sz->rgrc[0].right -= borderThickness.right;
sz->rgrc[0].bottom -= borderThickness.bottom;
return 0;
}
}
break;
}
return wxFrame::MSWWindowProc(nMsg, wParam, lParam);
}

How to change NotifyIcon's context menu in explorer.exe?

I want to extend the default Speakers notificon's (tray icon) right-click context menu with a new item. Also, I want to handle the mouseclick using C++.
Illustration
What I know so far
I learned how to dll-inject using CreateRemoteThread(), because I think that's the way to go. My problem is: what to do inside the injected dll? For example, how to access the NotifyIcon object?
Maybe it is possible with a simple Windows API call, but I'm not familiar with it.
Thanks Luke for the hint. I got it working using EasyHook. I chose it, because it also supports 64-bit dll-inject.
DLL to inject:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using EasyHook;
namespace InjectDLL
{
public class Main : EasyHook.IEntryPoint
{
LocalHook CreateTrackPopupMenuExHook;
public Main(RemoteHooking.IContext InContext){}
public void Run(RemoteHooking.IContext InContext)
{
try
{
CreateTrackPopupMenuExHook = LocalHook.Create(
LocalHook.GetProcAddress("user32.dll", "TrackPopupMenuEx"),
new DTrackPopupMenuEx(TrackPopupMenuEx_Hooked),
this);
CreateTrackPopupMenuExHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
}
catch
{
return;
}
while (true)
{
Thread.Sleep(500);
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool AppendMenu(IntPtr hMenu, long uFlags, int uIDNewItem, string lpNewItem);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern IntPtr TrackPopupMenuEx(
IntPtr hMenu,
uint fuFlags,
int x,
int y,
IntPtr hwnd,
IntPtr lptpm);
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
delegate IntPtr DTrackPopupMenuEx(
IntPtr hMenu,
uint fuFlags,
int x,
int y,
IntPtr hwnd,
IntPtr lptpm);
const long MF_STRING = 0x00000000L;
const long MF_SEPARATOR = 0x00000800L;
static IntPtr TrackPopupMenuEx_Hooked(
IntPtr hMenu,
uint fuFlags,
int x,
int y,
IntPtr hwnd,
IntPtr lptpm)
{
IntPtr returnValue = IntPtr.Zero;
try
{
//Separator
AppendMenu(hMenu, MF_SEPARATOR, 0, null);
//New menu item
AppendMenu(hMenu, MF_STRING, 40010, "TestMenuItem");
//call the default procedure
returnValue = TrackPopupMenuEx(hMenu, fuFlags, x, y, hwnd, lptpm);
//our menu item is selected
if (returnValue == (IntPtr)40010)
{
/* CODE HERE */
returnValue = IntPtr.Zero;
}
return returnValue;
}
catch
{
return;
}
return returnValue;
}
}
}

Receive WM_COPYDATA messages in a Qt app

I am working on a Windows-only Qt application, and I need to receive data from a Microsoft OneNote plugin. The plugin is written in C#, and can send WM_COPYDATA messages. How do I receive these messages in a C++ Qt app?
I need to:
Be able to specify the "class name" a window registers as when it calls RegisterClassEx, so that I can make sure the plugin sends WM_COPYDATA messages to the correct window.
Have access to the message id to check if it's WM_COPYDATA and lParam, which contains the COPYDATASTRUCT with the actual data. This information is passed in WndProc, but I am unable to find a hook where I can intercept these messages.
This can all be handled within Qt:
Extend QWidget with a class that will capture the WM_COPYDATA messages:
class EventReceiverWindow : public QWidget
{
Q_OBJECT
public:
EventReceiverWindow();
signals:
void eventData(const QString & data);
private:
bool winEvent ( MSG * message, long * result );
};
Generate a GUID to set as the QWidget's windowTitle:
EventReceiverWindow::EventReceiverWindow()
{
setWindowTitle("ProjectName-3F2504E0-4F89-11D3-9A0C-0305E82C3301::EventReceiver");
}
Override winEvent to handle the WM_COPYDATA structure and emit a signal when you get it:
bool EventReceiverWindow::winEvent ( MSG * message, long * result )
{
if( message->message == WM_COPYDATA ) {
// extract the string from lParam
COPYDATASTRUCT * data = (COPYDATASTRUCT *) message->lParam;
emit eventData(QString::fromAscii((const char *)data->lpData, data->cbData));
// keep the event from qt
*result = 0;
return true;
}
// give the event to qt
return false;
}
In another class, you can use this class to receive the message strings:
EventReceiverWindow * eventWindow = new EventReceiverWindow;
QObject::connect(eventWindow, SIGNAL(eventData(const QString &)), this, SLOT(handleEventData(const QString &)));
...
void OneNoteInterface::handleEventData(const QString &data)
{
qDebug() << "message from our secret agent: " << data;
}
And in the program that is sending the messages, simply find the window by the unique window caption. Here's an example in C#:
private struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
private const int WM_COPYDATA = 0x4A;
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref COPYDATASTRUCT lParam);
private void sendMessageTo(IntPtr hWnd, String msg)
{
int wParam = 0;
int result = 0;
if (hWnd != IntPtr.Zero )
{
byte[] sarr = System.Text.Encoding.Default.GetBytes(msg);
int len = sarr.Length;
COPYDATASTRUCT cds;
cds.dwData = IntPtr.Zero;
cds.lpData = msg;
cds.cbData = len + 1;
result = SendMessage(hWnd, WM_COPYDATA, wParam, ref cds);
}
}
then you can:
IntPtr hwnd = FindWindowByCaption(IntPtr.zero, "ProjectName-3F2504E0-4F89-11D3-9A0C-0305E82C3301::EventReceiver");
sendMessageTo(hwnd, "omg hai");
You can also create a dummy window just for receiving that message with the Win32 API. I guess you won't have access to a Qt-Window's window proc, so this should be the easiest way.
You could (I wouldn't) also subclass the window by setting a new WndProc (with SetWindowLong(Ptr), the window's handle can be obtained with QWidget::winId()). In this WndProc, you could just handle your specific WM_COPYDATA and pass all other window messages to the old WndProc.
To handle messages your window receives, override your QCoreApplication::winEventFilter. If that doesn't work you can take a look at QAbstractEventDispatcher.
For the class name you could try using QWidget::winId along with Win32 API. I would try and find it for you but I can't right now, maybe try GetClassName.
You can use QWinHost from Qt solutions to create a dummy window. Following the guide will show you how to specify your class name and check the event loop for your message.