C++ SendMessage trying to receive string - c++

Working with C++, I've spent way to many hours already trying to figure this out. This code is from a working program, I'm rewriting it in C#, but there is some things happening I do not understand.
The below code is exactly what runs when I press "Step Into". How is it going from ::SendMessage(...) to ::OnCopyData(..) with pCopyDataStruct containing data now?
main.cpp
void COTP::main()
{
//string will be returned using WM_COPYDATA message
::SendMessage(hWnd, 33508, (WPARAM)GetSafeHwnd(), 11);
// WPARAM is a typedef for UINT_PTR which is an unsigned int.
}
afxwin2.inl
_AFXWIN_INLINE HWND CWnd::GetSafeHwnd() const
{ return this == NULL ? NULL : m_hWnd; }
main.cpp
BOOL COTP::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
CString str, str2;
switch (pCopyDataStruct->dwData)
{
case JRC_COPYDATA_ID_TRACK_FILENAME:
str = (LPCTSTR)pCopyDataStruct->lpData;
break;
}
}
Any help super appreciated, I have looked thru all the msdn documents that I can tonight, it just seems like I'm missing something simple somewhere. Thought it might be a callback, but that doesn't seem right.

The code is sending a message (33508) to a specific window. Apparently the receiving window processes that message by sending a WM_COPYDATA message to the HWND you passed in the WPARAM in the original SendMessage() call. It's essentially implementing a callback mechanism.
The original message number (33508) is not one the standard Win32 messages (at least not that I recognize), so it's likely a custom message. Also, the fact that it's using WM_COPYDATA to respond would suggest that the receiving window is in a different process (i.e., not owned by your application).

Alright got it figured out now. With the help of this page and here. Below is what I'm currently using for C#. This is by no means finished code, but this does work and is good for learning purposes.
Click calculate button,
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetActiveWindow();
private void btnCalculate_Click(object sender, EventArgs e)
{
int hwnd = 0;
hwnd = Win32.FindWindow("The App pulling from", "Window");
int s = (int)GetActiveWindow();
int s3 = Win32.SendMessage(hwnd, 33508, s, 11);
Then what happens is a callback occurs and activates WndProc(ref Message m),
protected override void WndProc(ref Message m)
{
// Prevents error creating window handle message.
base.WndProc(ref m);
// WM_COPYDATA
// m.Msg = 0x4a
//msg=0x4a (WM_COPYDATA) hwnd=0x251e62 wparam=0x69063e lparam=0x1c42cca0 result=0x0
if (m.Msg == 0x4a)
{
Console.WriteLine(m);
WndProc(m.HWnd, m.Msg, m.WParam, m.LParam);
}
}
public struct CopyDataStruct : IDisposable
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
public void Dispose()
{
if (this.lpData != IntPtr.Zero)
{
LocalFree(this.lpData);
this.lpData = IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public unsafe struct DataStruct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 300)]
public string s;
public double d;
public char c;
};
protected void WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
CopyDataStruct cps = (CopyDataStruct)Marshal.PtrToStructure(lParam, typeof(CopyDataStruct));
DataStruct data = (DataStruct)Marshal.PtrToStructure(cps.lpData, typeof(DataStruct));
// data.s is what we needed.
Console.WriteLine(data.s);
}
Thank you very much to everyone that replied! I am confident I would have given up if not for your help. :)

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();
}
}
}
}
}

C# Com Object - Out of Memory

I created a small C# library that exposes a COM object which is used to display a CrystalReport viewer.
I have such a code in my C# test app that works as needed and report is shown
public partial class ReportHolder : UserControl
{
private string _reportFilename;
private string _datasourcePath;
public ReportHolder(string reportFilename, string datasourcePath)
{
InitializeComponent();
_reportFilename = reportFilename;
_datasourcePath = datasourcePath;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (string.IsNullOrEmpty(_reportFilename))
return;
ReportDocument doc = new ReportDocument();
doc.Load(_reportFilename);
ConnectionInfo connectionInfo = new ConnectionInfo();
string serverName = string.Format("Driver={{Driver do Microsoft dBase (*.dbf)}};DBQ={0};DefaultDir={0};DriverId=533;FIL=dBase 5.0;UserCommitSync=Yes", _datasourcePath);
connectionInfo.ServerName = serverName;
connectionInfo.Type = ConnectionInfoType.DBFile;
Tables tables = doc.Database.Tables;
foreach (CrystalDecisions.CrystalReports.Engine.Table table in tables)
{
TableLogOnInfo logonInfo = table.LogOnInfo;
logonInfo.ConnectionInfo = connectionInfo;
table.ApplyLogOnInfo(logonInfo);
}
//doc.VerifyDatabase();
crViewer.UseWaitCursor = true;
crViewer.ReportSource = doc;
crViewer.RefreshReport();
crViewer.UseWaitCursor = false;
}
}
Now I have the same UserControl inside my C# Com Object
[ComVisible(true)]
[Guid("F8FB6B66-5A15-4396-8A81-D7B8655E2869")]
[ClassInterface(ClassInterfaceType.None)]
public class Se32ComBridge : ISe32ComBridge
{
public Se32ComBridge()
{
}
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
public IntPtr ShowReport(IntPtr hWndParent, string reportFileName, string datasourcePath)
{
ReportHolder reportHolder = new ReportHolder(reportFileName, datasourcePath);
SetParent(reportHolder.Handle, hWndParent);
reportHolder.LoadReport(reportFileName, datasourcePath);
return reportHolder.Handle;
}
}
where hwndChild is an actual HWND from a window in WinAPI C++ application.
which I instantiate from a native C++ application, like this
sam32_net_bridge::ISe32ComBridgePtr pIBridge(__uuidof(sam32_net_bridge::Se32ComBridge));
_bstr_t reportFilename(_reportFilename.c_ptr());
_bstr_t datasourcePath(_datasourcePath.c_ptr());
pIBridge->ShowReport((long)hWnd, reportFilename, datasourcePath, &_controlHandle);
And here I'm getting such an exception
System.OutOfMemoryException: Exception of type
'System.OutOfMemoryException' was thrown. at
System.Threading.Thread.StartInternal(IPrincipal principal,
StackCrawlMark& stackMark) at
System.Threading.Thread.Start(StackCrawlMark& stackMark) at
System.Threading.Thread.Start() at
System.Drawing.ImageAnimator.Animate(Image image, EventHandler
onFrameChangedHandler) at
System.Windows.Forms.ButtonBase.Animate(Boolean animate) at
System.Windows.Forms.ButtonBase.Animate() at
System.Windows.Forms.ButtonBase.OnVisibleChanged(EventArgs e) at
System.Windows.Forms.Control.SetVisibleCore(Boolean value) at
System.Windows.Forms.Control.Show() at
CrystalDecisions.Windows.Forms.CrystalReportViewer.ShowProgressAnimation(Boolean
enabled) at
CrystalDecisions.Windows.Forms.CrystalReportViewer.set_ReportSource(Object
value) at sam32.net.bridge.ReportHolder.OnLoad(EventArgs e) in
\app\sam32.net.bridge\ReportHolder.cs:line
52 at System.Windows.Forms.UserControl.OnCreateControl() at
System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl() at
System.Windows.Forms.Control.WmShowWindow(Message& m) at
System.Windows.Forms.Control.WndProc(Message& m) at
System.Windows.Forms.ScrollableControl.WndProc(Message& m) at
System.Windows.Forms.UserControl.WndProc(Message& m) at
System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message&
m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32
msg, IntPtr wparam, IntPtr lparam)
it looks to me like the Threading does something funny when not running in a full .NET environment. So, I'm wondering, what else shall I set properly in this COM object that it will function as needed.
Thx

Worker threads and MFC controls

I'm aware of the fact that MFC GUI controls are not accessible directly from a worker thread, but for example, they getting by passing to this thread a pointer to the object instance that owns the controls. My problem is, that I'm really sure about how it goes when I'm calling functions within the scope of the worker thread, which needs to access MFC controls. Please consider the following code:
//header:
class CMyDlg : public CDialog
{
...
...
...
afx_msg void OnButtonControl();
static UNIT ControlThread(LPVOID pParam);
bool ValidateEditControl();
}
//cpp
void CMyDlg::OnButtonControl()
{
CString Text = "Hello";
GetDlgItem(IDC_EDIT_HELLO)->SetWindowText(Text);
m_hControlThread = AxtBeginThread(ControlThread, this);
}
UINT CMyDlg::ControlThread(LPVOID pParam)
{
CMyDlg *dlg = (CMyDlg*) pParam;
CString Text = "Hello";
while(SomethingIsTrue) {
bool Ret = dlg->ValidateEditControl();
if (!Ret) //Someone changed ControlEntry -> change it back
dlg->GetDlgItem(IDC_EDIT_HELLO)->SetWindowText(Text);
}
AfxEndThread(0);
}
bool CMyDlg::ValidateEditControl()
{
CString Text;
this->GetDlgItem(IDC_EDIT_HELLO)->GetWindowText(Text); // do I need the "this" pointer here, or for general how do I access my MFC control at this point?
if (Text == "Hello")
return true;
else
return false;
}
What is the best way to this?
Thank you in advance
best Greg
Without going into too much details, here is how you should do it. I have't build, judged or modified your basic code, I have just addressed your threading part of question. You should be able to take it from here.
UINT CMyDlg::ControlThread(LPVOID pParam)
{
HWND hWnd = (HWND) pParam;
CString Text = "Hello";
while(SomethingIsTrue) {
bool Ret = SendMessage(HwND, VALIDATE_CONTROL,0,0 );
if (!Ret) //Someone changed ControlEntry -> change it back
SendMessage(CHANGE_EDIT_HELLO, &Text, 0);
}
AfxEndThread(0);
}

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.

How to send a CBN_SELCHANGE message when using CB_SETCURSEL?

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