Related
I'm new to C++ so I'm not exactly sure what to put into the title of this problem. Anyway, I created a class whose purpose is to create a Label then use it later to create another Label again and again.
CALLBACK MyClassName::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) noexcept -> LRESULT {
....
switch (msg) {
....
case WM_CREATE:
{
ControlLabel controlLabel, anotherLabel; //declare two control label
controlLabel.Label(123, hwnd); //Set new id and window handle for label
controlLabel.SetXPosition(68); //Set x position
controlLabel.SetYPosition(110); //Set y position
controlLabel.SetText("This is Label"); //Set the text of Label
controlLabel.SetFontSize(14); //Set the font size of the text
anotherLabel.Label(456, hwnd); //Create and set new id and window handle for another label
anotherLabel.SetXPosition(68); //Set x position of another label
anotherLabel.SetYPosition(140); //Set y position of another label
anotherLabel.SetText("This is another Label"); //Set the text of another label
anotherLabel.SetFontSize(14); //Set the font size of another label
break;
}
....
return ::DefWindowProcW(hwnd, msg, wparam, lparam);
}
I'm expecting it to have an output of two different labels, e.g.
This is Label
This is another Label
Instead, I get two Labels with the same text.
This is another Label
This is another Label
Anyway, here's the full source of the class.
ControlLabel.H
#pragma once
#ifndef CONTROLLABEL_H
#define CONTROLLABEL_H
#include "Header.h"
class ControlLabel {
public:
ControlLabel();
HWND Label(int Label_ID, HWND WindowHandle);
void SetXPosition(int xPosition);
void SetYPosition(int yPosition);
void SetText(string Text);
void SetFontFamily(string FontFamily);
void SetFontSize(int FontSize);
void SetFontColor(int R, int G, int B);
void SetBackgroundColor(int Rr, int Gg, int Bb, bool SetBGColor);
private:
void UpdateLabel();
void SetWidthAndHeights();
static std::wstring StringConverter(const std::string& s);
static LRESULT CALLBACK LabelProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
static HWND LabelHandle;
static SolidBrush vFontColor;
static string text, vFontFamily;
static bool SetBGColor;
static int xPosition, yPosition, width, height, LABEL_ID, vFontSize, R, G, B, BckR, BckG, BckB;
};
#endif
ControlLabel.cpp
#include "ControlLabel.h"
HWND ControlLabel::LabelHandle = NULL;
int ControlLabel::xPosition = 0;
int ControlLabel::yPosition = 0;
int ControlLabel::width = 0;
int ControlLabel::height = 0;
int ControlLabel::LABEL_ID = 0;
int ControlLabel::vFontSize = 12;
int ControlLabel::R = 0;
int ControlLabel::G = 0;
int ControlLabel::B = 0;
int ControlLabel::BckR = 0;
int ControlLabel::BckG = 0;
int ControlLabel::BckB = 0;
bool ControlLabel::SetBGColor = FALSE;
string ControlLabel::text = "Label";
string ControlLabel::vFontFamily = "Segoe UI";
ControlLabel::ControlLabel() {}
/** This function is used to convert string into std::wstring. **/
std::wstring ControlLabel::StringConverter(const std::string& s) {
int len;
int slength = (int)s.length() + 1;
len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
wchar_t* buf = new wchar_t[len];
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
std::wstring r(buf);
delete[] buf;
return r;
}
/** This function is used to automatically set the Width and Height of static control base on the length of the text. **/
void ControlLabel::SetWidthAndHeights() {
std::wstring fontFamilyTemp = StringConverter(vFontFamily);
std::wstring textTemp = StringConverter(text);
LPCWSTR textLabel = textTemp.c_str();
HDC hdc = GetDC(LabelHandle);//static control
HFONT hFont = CreateFont(
-MulDiv(vFontSize, GetDeviceCaps(hdc, LOGPIXELSX), 90), //calculate the actual cHeight.
0, 0, 0, // normal orientation
FW_NORMAL, // normal weight--e.g., bold would be FW_BOLD
false, false, false, // not italic, underlined or strike out
DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, // select only outline (not bitmap) fonts
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH | FF_SWISS, fontFamilyTemp.c_str());
SIZE size;
HFONT oldfont = (HFONT)SelectObject(hdc, hFont);
GetTextExtentPoint32(hdc, textLabel, wcslen(textLabel), &size);
width = size.cx;
height = size.cy;
SelectObject(hdc, oldfont); //don't forget to select the old.
DeleteObject(hFont); //always delete the object after creating it.
ReleaseDC(LabelHandle, hdc); //alway reelase dc after using.
/*char buffer[100];
sprintf_s(buffer, "WIDTH: %d | HEIGHT: %d\n", width, height);
OutputDebugStringA(buffer);*/
}
/** This function will be called when new option is set. For example, fontSize is set. **/
void ControlLabel::UpdateLabel() {
if(LabelHandle != NULL) {
SetWidthAndHeights();
SetWindowPos(LabelHandle, nullptr, xPosition, yPosition, width, height, SWP_NOZORDER | SWP_NOOWNERZORDER);
InvalidateRect(LabelHandle, NULL, FALSE);
UpdateWindow(LabelHandle);
}
}
/** This is the callback function of static control. **/
LRESULT CALLBACK ControlLabel::LabelProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch(uMsg) {
case WM_ERASEBKGND: {
if(SetBGColor) { //We only want to do this if the SetColor is modified to true, meaning we want to set the color of background.
RECT rect;
GetClientRect(hwnd, &rect);
FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(BckR, BckG, BckB))); //set titlebar background color.
return 1; //return 1, meaning we take care of erasing the background.
}
return 0;
}case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics g(hdc);
std::wstring fontFamilyTemp = StringConverter(vFontFamily);
std::wstring textTemp = StringConverter(text);
FontFamily theFontFamily(fontFamilyTemp.c_str());
Font font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel);
SolidBrush brush(Color(255, R, G, B));
PointF pointF(0.0f, 0.0f);
TextRenderingHint hint = g.GetTextRenderingHint(); // Get the text rendering hint.
g.SetTextRenderingHint(TextRenderingHintAntiAlias); // Set the text rendering hint to TextRenderingHintAntiAlias.
g.DrawString(textTemp.c_str(), -1, &font, pointF, &brush);
EndPaint(hwnd, &ps);
return TRUE;
}case WM_NCDESTROY: {
RemoveWindowSubclass(hwnd, LabelProc, uIdSubclass);
return 0;
}
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
/** Use this function to create a Label. Parent or WindowHandle must be specified, this is where the Label will be drawn. Unique Label ID must be specified. **/
HWND ControlLabel::Label(int Label_ID, HWND WindowHandle) {
LABEL_ID = Label_ID;
LabelHandle = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, xPosition, yPosition, width, height, WindowHandle, NULL, NULL, NULL); //create the static control.
SetWindowSubclass(LabelHandle, &LabelProc, LABEL_ID, 0);
return LabelHandle;
}
/** Use this function to set the X Position of the Label. **/
void ControlLabel::SetXPosition(int xxPosition) {
if(LabelHandle != NULL) {
xPosition = xxPosition; //set xposition
UpdateLabel();
}
}
/** Use this function to set the Y Position of the Label. **/
void ControlLabel::SetYPosition(int yyPosition) {
if(LabelHandle != NULL) {
yPosition = yyPosition; //set xposition
UpdateLabel();
}
}
/** Use this function to set the text of the Label. **/
void ControlLabel::SetText(string ttext) {
if(LabelHandle != NULL) {
text = ttext; //set text
UpdateLabel();
}
}
/** Use this function to set the font family of the Label. **/
void ControlLabel::SetFontFamily(string font_family) {
if(LabelHandle != NULL) {
vFontFamily = font_family; //set font family
UpdateLabel();
}
}
/** Use this function to set the font size of the Label. **/
void ControlLabel::SetFontSize(int size) {
if(LabelHandle != NULL) {
vFontSize = size; //set font size
UpdateLabel();
}
}
/** Use this Function to set the font color of the Label using RGB. **/
void ControlLabel::SetFontColor(int Rr, int Gg, int Bb) {
if(LabelHandle != NULL) {
R = Rr;
G = Gg;
B = Bb;
UpdateLabel();
}
}
/** Use this Function to set the background color of the Label using RGB. Last parameter must be TRUE if you want to set your own background color. **/
void ControlLabel::SetBackgroundColor(int Rr, int Gg, int Bb, bool setColor) {
if(LabelHandle != NULL) {
SetBGColor = setColor;
BckR = Rr;
BckG = Gg;
BckB = Bb;
UpdateLabel();
}
}
Static class members are shared between all instances of a class. There's a static string text in your class so it is to be expected that all instances share that. If you need to store per-instance data you need to use non-static class members.
Presumably, you've used static class members so that you can put your window procedure inside the class' implementation (which needs to be static). To have a static window procedure access per-instance data has been asked and answered before (like here, here, here, or here).
There's no such thing as "instance" when all your variables are declared static. static means the variable is shared among all instances of the class, making it essentially global. So, when you call:
controlLabel.SetText("This is Label");
You assign your text to the global text variable. Then, calling
anotherLabel.SetText("This is another Label");
Assigns the new string to that same global text variable. Your original string was forgotten at this point.
How can you solve this? I can think of multiple ways off the top of my head, maybe you can think of something better. The idea is to somehow bind the text (or the entire controlLabel instance) to a label.
Putting the label text directly into the window data (using WM_SETTEXT. Then you can pull it up in LabelProc and draw it, or just let the default STATIC window procedure handle it.
Making the labels a custom window class that has some extra space reserved for each window instance. Then use SetWindowLong to put a pointer to a whole controlLabel in there. Raw pointers are generally not a great thing in C++, but then again, Win32 API was made for C. Then pull the instance up when needed with GetWindowLong. Just remember to un-static the text member, so it doesn't get overwritten.
Use a global/static std::map<HWND, controlLabel> to associate each label with an instance of controlLabel. Again, if you do this, remember to un-static the text.
Oh, and when you called any controlLabel method that somehow uses the label handle, you just randomly happened to have the handle that you wanted in the LabelHandle variable, since it's also static.
That doesn't make any sense, you won't be able to have a different instance if all your members are static.
However, since the callback of window procedure needs to be static, then you won't be able to access those non-static members inside that function.
What you can do is use the dwRefData parameter of the subclass to pass the instance of your class into your callback function. You can then cast that parameter to access non-static members.
For example in your ::Label function;
SetWindowSubclass(LabelHandle, &LabelProc, LABEL_ID, <DWORD_PTR>(this)); //notice the last parameter, pass `this` instance so you can use `dwRefData`.
Then on your callback;
ControlLabel* controlLabel = reinterpret_cast<ControlLabel*>(dwRefData); //type cast the value of dwRefData.
controlLabel->text //you can now access your non-static variable text
controlLabel->vFontFamily //you can now access your non-static variable vFontFamily
Something like that.
Another problem is you shouldn't declare your callback as private, make it public instead. And do not declare your ControlLabel object inside the WM_CREATE, make it global instead.
ControlLabel controlLabel, anotherLabel; //declare two control label as Global.
CALLBACK MyClassName::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) noexcept -> LRESULT {
....
switch (msg) {
....
case WM_CREATE:
{
controlLabel.Label(123, hwnd); //Set new id and window handle for label
...
anotherLabel.Label(456, hwnd); //Create and set new id and window handle for another label
...
break;
}
....
return ::DefWindowProcW(hwnd, msg, wparam, lparam);
}
Anyway, I noticed your class doesn't have destructor to destroy your LabelHandle. Make sure to do that.
ControlLabel.h //In your ControlLabel.h
~ControlLabel(); //should be in public
ControlLabel.cpp //In your ControlLabel.cpp
ControlLabel::~ControlLabel() {
if(LabelHandle) DestroyWindow(LabelHandle); //destroy when done.
}
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();
}
}
}
}
}
I've attempted to inherit some public globals from a base class and successfully got the window up and running and initialized directx similarly in a simple way- in it's own class, inheriting globals, such as HWND hWnd.
However when the program is run, D3D11CreateDeviceAndSwapChain() fails. On further inspection, debugger gives:
DXGI ERROR: IDXGIFactory::CreateSwapChain: No target window specified in DXGI_SWAP_CHAIN_DESC, and no window associated with owning factory. [ MISCELLANEOUS ERROR #6: ]
The DXGI_SWAP_CHAIN_DESC structure is as follows:
SwapChainDesc.BufferCount = 1;
SwapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.BufferDesc.Width = 1024;
SwapChainDesc.BufferDesc.Height = 768;
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapChainDesc.OutputWindow = hWnd;
SwapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
SwapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
SwapChainDesc.SampleDesc.Count = 4;
SwapChainDesc.Windowed = TRUE;
SwapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
Where hWnd is stored in:
class Infinity{
public:
Infinity();
~Infinity();
HWND hWnd;
};
And inherited as such:
class Direct3D : public Infinity{
public:
Direct3D();
~Direct3D();
IDXGISwapChain *Swapchain; //Display modes.
ID3D11Device *Device;
ID3D11DeviceContext *DeviceContext;
ID3D11RenderTargetView *RenderTargetView;
void D3D_Start(int width, int height);
void D3D_Render();
void D3D_Terminate();
}Direct3D;
Checked the value for SwapChainDesc.OutputWindow = hWnd; at runtime and it's null. (0x00000000) and I think that's what is causing Swapchain->GetBuffer to fail, since D3D11CreateDeviceAndSwapChain needs a working HWND. If this is true, Why is ShowWindow() successful?
Edit: I should also add that ShowWindow()is in a similar class inheriting from class Infinity:
class Windows : public Infinity{
public:
Windows();
~Windows();
bool DisplayWindow(int width, int height, HINSTANCE hInstance);
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
}Windows;
Observe the following code:
class A {
public:
int memA;
static int memB;
};
int A::memB = 0;
class B : public A {};
class C : public A {};
int main() {
A a;
B b;
C c;
a.memA = 4;
b.memA = 5;
c.memA = 6;
A::memB = 4;
B::memB = 5;
C::memB = 6;
printf("a.memA = %d\n", a.memA);
printf("b.memA = %d\n", b.memA);
printf("c.memA = %d\n", c.memA);
printf("A::memB = %d\n", A::memB);
printf("B::memB = %d\n", B::memB);
printf("C::memB = %d\n", C::memB);
return 0;
}
The output of this code is:
a.memA = 4
b.memA = 5
c.memA = 6
A::memB = 6
B::memB = 6
C::memB = 6
By making a member static, you ensure that: A) There is only ever one instance of that member and B) All child classes have direct access to that member (assuming it isn't private). Your hWnd is not global as your code is written, however making it static will accomplish what you want.
The following code does this:
//header file
class Infinity {
public:
static HWND hWnd;
};
//cpp file
HWND Infinity::hWnd = NULL;
Your Direct3D class will be able to access the non-null hWnd written to by your Window class. This is only a valid solution if you intend to create one window - otherwise, you will have to resort to a more complicated parent-child relationship between the two classes (probably without using inheritance).
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;
}
}
}
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