EM_SETSEL not working in .docx (word document) - c++

I'm working on a simple program that sends WM_COPY to an open .docx file (word document). I tested my code on other applications and it seems to work all fine. But whenever I try to use my code on an open word document, it returns false at SendMessage(hwndChild, EM_SETSEL, 0, -1).
I have tried a couple things:
Use code on notepad (not richtext) and stickynotes (richttext) and it works perfectly with the code beneath.
Remove the line SendMessage(hwndChild, EM_SETSEL, 0, -1) and then manual highlight the text in the word document and run program. It succeeds and copies the content to clipboard with help of WM_COPY.
Does anyone know why EM_SETSEL is not working in a word document?
int main() {
HWND app = FindWindowEx(0, 0, "OpusApp", 0);
EnumChildWindows(app, EnumChildProc, (LPARAM) NULL);
return 0;
}
BOOL CALLBACK EnumChildProc(HWND hwndChild, LPARAM lParam) {
uint8_t max_path_length = 255;
char *str = new char[max_path_length];
memset(str, 0, max_path_length);
GetClassName(hwndChild, str, max_path_length);
/// Execute when edit handle is obtained
if(strcmpi(str, "_WwG") == 0) {
if(SendMessage(hwndChild, EM_SETSEL, 0, -1)) {
SendMessage(hwndChild, WM_COPY, 0, 0);
} else {
MessageBox(0, "Can't select all!", "Report", MB_OK);
}
}
str = NULL;
delete [] str;
return TRUE;
}

EM_SETSEL only works for an Edit control. I doubt that Word uses the standard control.

Related

winapi - a standard way to retrieve text from running text editor

Is there a standard message that can be sent to a text editor window or a certain WinApi call, that will retrieve the contents of the currently edited text?
For example, to retrieve the current contents of a Notepad window. (assumed that the most up to date text wasn't yet written to a file)
I've tried retrieving the text via SendMessage using WM_GETTEXT, WM_GETTEXTLENGTH but I was able to retrieve the title text only.
In general no there is no standard message for this.
But Windows' Notepad has an "Edit" child which responds to WM_GETTEXT and WM_GETTEXTLENGTH - messages normally used to retrieve text from input controls.
Here's a PoC demonstrating the idea:
#include <iostream>
#include <vector>
#include <string.h>
#include <Windows.h>
BOOL CALLBACK enumProc(HWND hwnd, LPARAM) {
std::vector<char> buf(100);
GetClassNameA(hwnd, buf.data(), 100);
if (strcmp(buf.data(), "Notepad")) return TRUE;
hwnd = FindWindowEx(hwnd, NULL, "Edit", NULL);
if (!hwnd) return TRUE;
int textLength = SendMessageA(hwnd, WM_GETTEXTLENGTH, 0, 0) + 1;
if (textLength <= 0) return TRUE;
buf.resize(textLength);
SendMessage(hwnd, WM_GETTEXT, textLength, (LPARAM)buf.data());
std::cout << buf.data() << "\n";
return TRUE;
}
int main() {
EnumWindows(&enumProc, 0);
}
Works on Windows 10:

C++ How to replace new lines when pasting a text to the Edit control?

I've got a simple chat program. I use "CreateWindow" function for the typing box:
chat_handle_11 = CreateWindow("EDIT", "", WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | WS_EX_CONTROLPARENT, 226, 447, 424, 23, hWnd, NULL, NULL, NULL);
SendMessage(chat_handle_11, EM_LIMITTEXT, ChatTextLimitInBox, 0L);
When I paste any text containing new line characters (using the right mouse click or ctrl+v), for example:
Test line 1 text
Test line 2 text
Test line 3 text
Only the first line is pasted to the typing window:
Test line 1 text
I'd like to change the text on-paste, to ignore new line characters:
Test line 1 text Test line 2 text Test line 3 text
I tried to handle the WM_PASTE message, unfortunately it didn't work:
switch (message)
{
case WM_PASTE:
{
MessageBox(NULL, "pasting", "pasting", MB_YESNO | MB_ICONQUESTION);
break;
}
...
The MessageBox was never shown. Is WM_PASTE the correct message in this case?
Additionally, I tried to add "ES_MULTILINE" to the CreateWindow, but then, when I attempt to paste the text containing multiple lines, no text is pasted at all, I can only hear the "beep" sound.
I know I could remove new lines by detecting for clipboard changes and then overwrite it, but this solution would "invade" users clipboard, so I don't want to use it.
I would be very appreciate any help.
Thanks to #RbMm for help. I was able to fix the problem.
I didn't use the subclass for the Edit Control and I tried to handle the WM_PASTE message in the parent window.
Fixed code:
chat_handle_11 = CreateWindow("EDIT", "", WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | WS_EX_CONTROLPARENT, 226, 447, 424, 23, hWnd, NULL, NULL, NULL);
SendMessage(chat_handle_11, EM_LIMITTEXT, ChatTextLimitInBox, 0L);
SetWindowSubclass(chat_handle_11, EditBoxForPasteFixes, 0, 0);
Then the new CALLBACK:
LRESULT CALLBACK EditBoxForPasteFixes(HWND handle, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR, DWORD_PTR) {
switch (uMsg) {
case WM_PASTE:
{
try {
wstring ClipboardText = GetClipboardText();
find_and_replace_ws(ClipboardText, L"\r\n", L" ");
find_and_replace_ws(ClipboardText, L"\r", L" ");
find_and_replace_ws(ClipboardText, L"\n", L" ");
//We don't need to SETSEL, so we keep original position for pasting
//SendMessage(handle, EM_SETSEL, WPARAM(0), LPARAM(-1));
SendMessageW(handle, EM_REPLACESEL, WPARAM(TRUE), LPARAM(ClipboardText.c_str()));
}
catch (...) {
return FALSE;
}
return TRUE;
break;
}
/*case WM_LBUTTONDOWN:
//std::wcout << handle << L" click\n"; //click event works
break;*/
case WM_NCDESTROY:
{
RemoveWindowSubclass(handle, EditBoxForPasteFixes, 0);
// fall through
}
default:
{
return DefSubclassProc(handle, uMsg, wParam, lParam);
}
}
return 0;
}
And GetClipboardText function:
std::wstring GetClipboardText()
{
bool Failed = false;
std::wstring ReturnText = L"";
// Try opening the clipboard
if (!OpenClipboard(nullptr)) {
Failed = true;
}
// Get handle of clipboard object for ANSI text
if (!Failed) {
//HANDLE hData = GetClipboardData(CF_TEXT);
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
if (hData == nullptr) {
Failed = true;
}
// Lock the handle to get the actual text pointer
if (!Failed) {
wchar_t * pszText = static_cast<wchar_t*>(GlobalLock(hData));
if (pszText == nullptr) {
Failed = true;
}
if (!Failed) {
std::wstring text(pszText);
ReturnText = text;
}
// Release the lock
GlobalUnlock(hData);
}
// Release the clipboard
CloseClipboard();
}
return ReturnText;
}
For find_and_replace_ws I use the boost function, but can be replaced by anything else:
void find_and_replace_ws(wstring& source, wstring const& find, wstring const& replace)
{
boost::replace_all(source, find, replace);
/*for (std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;)
{
source.replace(i, find.length(), replace);
i += replace.length() - find.length() + 1;
}*/
}
Not a perfect code, I know, but enough for my needs :)

GetKeyboardState one key delay

I'm working on ToUnicodeEx function and it requires keyboard state as the input parameter. So, I used the GetKeyboardState function to do that. But I noticed when I'm typing key combinations with modifier keys like SHIFT+A there is one character delay. Here is the example.
aaa (holding SHIFT now) aAAAAAAA (release SHIFT) Aaaa
While I was debugging this I noticed GetKeyboardState is causing this delay. How can I handle or prevent this delay?
Here is my whole keyboard hook proc.
void proc(KBDLLHOOKSTRUCT kbdStruct) {
fdebug = fopen("debug.txt", "a");
foutput= fopen("output.txt", "a");
WCHAR pwszBuff[9];
WCHAR key[9];
char str[8];
BOOL isDead = FALSE;
BYTE lpKeyState[256];
HWND currentHwnd = GetForegroundWindow();
LPDWORD currentProcessID = 0;
DWORD currentWindowThreadID = GetWindowThreadProcessId(currentHwnd, currentProcessID);
DWORD thisProgramThreadId = GetCurrentThreadId();
hkl = GetKeyboardLayout(thisProgramThreadId);
if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID, TRUE))
{
GetKeyboardState(lpKeyState);
AttachThreadInput(thisProgramThreadId, currentWindowThreadID, FALSE);
}
else
{
GetKeyboardState(lpKeyState);
}
int ret = ToUnicodeEx(kbdStruct.vkCode, kbdStruct.scanCode, lpKeyState, pwszBuff, 8, 0, hkl);
fprintf(fdebug, "vkCode: %d\n", (int)kbdStruct.vkCode);
fprintf(fdebug, "ret: %d\n", (int)ret);
fprintf(fdebug, "lastIsDead: %d\n", (int)lastIsDead);
fprintf(fdebug, "lastIsMod: %d\n", (int)lastIsMod);
fprintf(fdebug, "lastVKCode: %d\n", (int)lastVKCode);
if (ret == -1) {
isDead = TRUE;
ClearKeyboardBuffer(kbdStruct.vkCode, kbdStruct.scanCode, hkl);
}
else if (ret == 0) {
}
else {
memcpy(&key, &pwszBuff, sizeof(pwszBuff));
WideCharToMultiByte(CP_UTF8, 0, key, -1, str, sizeof(str), NULL, NULL);
fprintf(fdebug, "str: %s\n", str);
}
if (lastVKCode != 0 && lastIsDead == TRUE) {
ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, pwszBuff, 4, 0, hkl);
memcpy(&key, &pwszBuff, sizeof(pwszBuff));
WideCharToMultiByte(CP_UTF8, 0, key, -1, str, sizeof(str), NULL, NULL);
fprintf(fdebug, "str: %s\n", str);
lastVKCode = 0;
}
fprintf(fdebug, "%s", "---------------------------------------------------\n");
fprintf(foutput, "LSHIFT: %d\n", (int)lpKeyState[160]);
fprintf(foutput, "RSHIFT: %d\n", (int)lpKeyState[161]);
fprintf(foutput, "%s", "---------------------------------------------------\n\n");
lastVKCode = kbdStruct.vkCode;
lastScanCode = kbdStruct.scanCode;
lastIsDead = isDead;
fclose(fdebug);
fclose(foutput);
}
Here is updated version of hookcallback thanks for Ton Plooij. But still, I have the same problem.
LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
LRESULT ret = CallNextHookEx(_hook, nCode, wParam, lParam);
if (nCode >= 0)
{
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
hookStruct = *((KBDLLHOOKSTRUCT*)lParam);
proc(hookStruct);
}
}
return ret;
}
AttachThreadInput(thisProgramThreadId, currentWindowThreadID, FALSE);
This does not do what you hope it does. Sometimes. It is a valiant and necessary effort to get the proper values when you call GetKeyboardState(). Took me a while to find the failure mode, it wasn't obvious at all and I could not get the code to fail the same way. It works just fine when a GUI process is in the foreground, try it with Notepad or VS for example.
But not when it is a console mode process.
Explaining that is a bit convoluted, it is actually GetWindowThreadProcessId() that returns misleading information. It tries too hard to keep up the illusion that it is the console process that owns the console window. It doesn't, it is actually the associated conhost.exe process that owns it. It was csrss.exe on old Windows versions, conhost.exe was added in Windows 7 to solve the drag+drop issues caused by UIPI (aka UAC).
But without any decent way to discover the specific conhost.exe process that owns the window, let alone the thread id. The subject of this Q+A. Pretty doubtful it is going to help you.
Only decent advice is the well-known unpleasant one: if you need to reliably translate keystrokes then you need to use a WH_KEYBOARD hook instead of WH_KEYBOARD_LL. So GetKeyboardState() is always accurate, the hook callback runs in-process.
A LowLevelKeyboardProc receives WM_KEYUP and WM_KEYDOWN messages in its wParam. You could simply keep track of pressed modifier keys yourself, in this case to detect shift down and up. Store the key state information in a static variable and use this to test if shift is pressed while processing other keys instead of using GetKeyState.
You can try GetAsyncKeyState().
This function in my keyboard hook module work perfectly.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms646293(v=vs.85).aspx
It seems that this function can catch hardware interrupt when you press keys.
And your GetKeyboardState has something to do with the Windows Registry.
It seems to have something to do with belows:
“HKEY_USERS\DEFAULT\ControlPanel\Keyboard”,Modify “KeyboardDelay” value to 0(default 1)
And change “KeyboardSpeed” value to 48(dafault 31).
According to Hans Passant's answer I searched for how can I get correct value from GetWindowThreadProcessId() and succeeded with instead of getting hWnd and windowThreadID every time in hook callback, I get these values to global variable just after program start and used variables in hook callback.

Debugging font linking

I have a problem with a program we're developing. It is written using MFC but does not use unicode. We have made a translation into simplified chinese. So far this is our first and only localization. We have moved all strings to resources to make them translateable. Everything seems to work fine on our computers (both Win7 and XP) but for some customers computers running windows XP we get problems:
On those computers all translated strings work except for those we enter into tree cotrols (CTreeCtrl is used directly). I'm not sure if it is the tree control or the text we enter that causes the problem, but the font seems to not get substituted in those controls. My guess is that maybe it does not get substituted because some of the strings also contain latin characters. Still, that kind of substitution seems to work in other places of the program and not all strings entered into the tree contain those characters either.
So my first question is: Is there a way to get to know what is happening inside the control? We can have remote access to the computers where the problem is happening but running a debugger on them may be a little tricky. What kind of tools can be used to diagnose this problem?
One possible solution that crossed my mind was to subclass the tree controls to get more control over the actual text drawing, maybe using the approach from http://blogs.msdn.com/b/oldnewthing/archive/2004/07/16/185261.aspx to get around the problem. Could this be effective or would it just be an awful lot of work for nothing?
Thanks a lot!
Ok, it seems on their system there was either a bug in the tree control implementation or they had some systemwide addin that hooked into the drawing in some way. What I ended up doing was to do custom drawing through notifications in the tree view and then using font linking to resolve the fonts. I dont think font linking would be necessary, though, because I think most of the time it ended up doing just TextOutW() with the preselected font anyway.
Here is a somewhat simplified code sample of what I ended up with:
void MyDlg::OnCustomDrawTreeItem( NMHDR* pNMHDR, LRESULT* pResult )
{
if (!mpFontLink)
{
*pResult = CDRF_DODEFAULT;
return;
}
LPNMTVCUSTOMDRAW pCustomdraw = (LPNMTVCUSTOMDRAW) pNMHDR;
switch(pCustomdraw->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
{
// Ask to do custom draw
*pResult = CDRF_NOTIFYITEMDRAW;
break;
}
case CDDS_ITEMPREPAINT:
{
// Ask for post paint notification
*pResult = CDRF_NOTIFYPOSTPAINT;
break;
}
case CDDS_ITEMPOSTPAINT:
{
// Get the rect of only the item, not the tree stuff
RECT rcItem;
m_pageTree.GetItemRect((HTREEITEM) pCustomdraw->nmcd.dwItemSpec, &rcItem, TRUE);
// Erase the item background in case the previous string drawn was wider
HDC hDC = pCustomdraw->nmcd.hdc;
FillRect( hDC, &rcItem, (HBRUSH) GetClassLongPtr(m_pageTree.GetSafeHwnd(), GCLP_HBRBACKGROUND));
pageStruct *pS = (pageStruct*) pCustomdraw->nmcd.lItemlParam;
DWORD dwFontCodepages = 0, dwStrCodepages = 0;
HFONT hOriginalFont = (HFONT)GetCurrentObject(hDC, OBJ_FONT);
HRESULT hr = mpFontLink->GetFontCodePages( hDC, hOriginalFont, &dwFontCodepages);
OML_CStringW tData = pS->csCaption.GetBuffer();
// Set up position etc
DWORD dwAlignOrig = GetTextAlign(hDC);
if (!(dwAlignOrig & TA_UPDATECP)) {
SetTextAlign(hDC, dwAlignOrig | TA_UPDATECP);
}
POINT ptOrig;
MoveToEx(hDC, 2 + rcItem.left, 1 + rcItem.top, &ptOrig);
SetTextColor( hDC, pCustomdraw->clrText );
SetBkColor( hDC, pCustomdraw->clrTextBk );
// Loop over the parts of the text
TuInt32 nIndex = 1;
while (nIndex <= tData.GetLength())
{
long nActualChars = 0;
wchar_t *pStr = (wchar_t*)tData.BaseGetItemP( nIndex );
TuInt32 nChars = 1 + tData.GetLength() - nIndex;
hr = mpFontLink->GetStrCodePages(pStr, nChars,
dwFontCodepages, &dwStrCodepages, &nActualChars);
if (dwStrCodepages & dwFontCodepages)
{
// We end up here almost every time, that is why TextOutW would probably be enough.
// This part is supported by the original font (or the GDI can help us switch automatically)
TextOutW(hDC, 0, 0, pStr, nActualChars);
}
else
{
// We need to link
HFONT hLinked;
if (FAILED(hr = mpFontLink->MapFont(hDC, dwStrCodepages, 0, &hLinked)))
{
// Fail: Output the rest without linking
TextOutW( hDC, 0, 0, pStr, nChars );
break;
}
// Output with linked font
SelectObject(hDC, hLinked);
TextOutW( hDC, 0, 0, pStr, nActualChars);
SelectObject(hDC, hOriginalFont);
mpFontLink->ReleaseFont( hOriginalFont );
}
nIndex += nActualChars;
}
OML_LOG_1( "_END:");
// Reset alignment mode
if (!(dwAlignOrig & TA_UPDATECP)) {
SetTextAlign(hDC, dwAlignOrig);
MoveToEx(hDC, ptOrig.x, ptOrig.y, NULL);
}
*pResult = CDRF_SKIPDEFAULT;
}
default:
{
*pResult = CDRF_DODEFAULT;
break;
}
}
} /* OnCustomDrawTreeItem */

Prompting a user with an input box? [C++]

My goal is to simply use a pop-up box to ask the user for an input. I've searched around quite a bit and pretty much all the results say that creating a messageBox is really easy:
MessageBox (NULL, "Hello World" , "Hello", MB_OKCANCEL);
But that creating a pop-up that takes input is more involved and there isn't a straight forward way to do it. All of the results I could find on Google were dated somewhere from 2001 to 2005. I guess I'm here asking if some more straight forward solution has come about in recent years.
Hopefully something nice and straight forward like in Java:
int number = JOptionPane.showInputDialog ("Enter an integer");
If that isn't the case, could I get a brief explanation of how to do it?
Edit: I couldn't get anything to work. :( I ended up writing the code to do the work in Java, and then wrote one line of C++ code to call the .jar file. :-/ Since the issue was time sensitive, it was better than nothing.
If you are using Visual C++ Express there are a number of free resource editors that can be used to create dialogs. ResEdit is one of the better ones I've found.
You need to create a dialog resource in a .RC file that you add to your project.
Then, It is a very simple case of calling DialogBox - which will load the dialog box from your resource file and place it on the screen. The passed in DialogProc will be called with a number of notifications. Typically you would want to return FALSE for everything, but handle WM_INITDIALOG as a place to initialize the edit control with text, and WM_COMMAND will be sent when a button is clicked.
There is nothing like that for pure C++. Basically what you're trying to do can only be achieved by using an API call to the OS or by using some GUI library like Qt (which I recommend cause it's waaaaay easier then calling native APIs and it's also multi-platform)
Using Qt you can show an input dialog pretty much the same way you do it on java:
bool ok;
QString text = QInputDialog::getText(
"MyApp 3000", "Enter your name:", QLineEdit::Normal,
QString::null, &ok, this );
if ( ok && !text.isEmpty() ) {
// user entered something and pressed OK
} else {
// user entered nothing or pressed Cancel
}
You can download the Qt library here: qt.nokia.com/products/developer-tools/
Microsoft doesn't consider your use case to be common enough to optimize for, as with MessageBox. They expect you to lay out a dialog with many controls on it, perhaps with some complex interaction with the controls, and only respond once the dialog is fully filled in. What you're asking for is just the simplified version of that.
The resource editor is the easiest way to create a dialog, but that's not included in the free Express version of Visual Studio. You would design the dialog with a text control for the prompt and an edit control for the user to fill in. You present the dialog with the DialogBox Windows function, and it returns when the user hits the OK button or the X in the corner of the dialog. Microsoft has some documentation for it here.
There are a few platforms available that try to make the process easier, such as MFC, WTL, Qt, and wx, but this is how you'd do it with the pure Windows API.
My answer is based on Stephen Quan's answer to How to load & call a VBScript function from within C++? Added full UTF-8 support, as you can gather from the code comments in the CPP file. Unlike using Microsoft Script Control to create the InputBox, this can be used in x86 and x64 executables, libraries, and controls.
"inputbox.h":
extern "C" char *InputBox(char *Prompt, char *Title = (char *)"", char *Default = (char *)"");
extern "C" char *PasswordBox(char *Prompt, char *Title = (char *)"", char *Default = (char *)"");
"inputbox.cpp":
#include "inputbox.h"
// Windows API
#include <windows.h>
// VBScript InputBox
#include <atlbase.h>
#include <activscp.h>
#include <comdef.h>
// UTF-8 Support
#include <wchar.h>
#include <string>
#include <vector>
using std::string;
using std::wstring;
using std::vector;
static wstring StringWiden(string Str) {
const size_t wchar_tCount = Str.size() + 1;
vector<wchar_t> Buffer(wchar_tCount);
return wstring { Buffer.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, Str.c_str(), -1, Buffer.data(), wchar_tCount) };
}
static string StringShorten(wstring Str) {
int nBytes = (size_t)WideCharToMultiByte(CP_UTF8, 0, Str.c_str(), (int)Str.length(), NULL, 0, NULL, NULL);
vector<char> Buffer((size_t)nBytes);
return string { Buffer.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, Str.c_str(), (int)Str.length(), Buffer.data(), nBytes, NULL, NULL) };
}
static string StringReplaceAll(string Str, string SubStr, string NewStr) {
size_t Position = 0;
const size_t SubLen = SubStr.length(), NewLen = NewStr.length();
while ((Position = Str.find(SubStr, Position)) != string::npos) {
Str.replace(Position, SubLen, NewStr);
Position += NewLen;
}
return Str;
}
static string CPPNewLineToVBSNewLine(string NewLine) {
size_t Position = 0;
while (Position < NewLine.length()) {
if (NewLine[Position] == '\r' || NewLine[Position] == '\n')
NewLine.replace(Position, 2, "\" + vbNewLine + \"");
Position += 1;
}
return NewLine;
}
class CSimpleScriptSite :
public IActiveScriptSite,
public IActiveScriptSiteWindow {
public:
CSimpleScriptSite() : m_cRefCount(1), m_hWnd(NULL) { }
// IUnknown
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
// IActiveScriptSite
STDMETHOD(GetLCID)(LCID* plcid) { *plcid = 0; return S_OK; }
STDMETHOD(GetItemInfo)(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown** ppiunkItem, ITypeInfo** ppti) { return TYPE_E_ELEMENTNOTFOUND; }
STDMETHOD(GetDocVersionString)(BSTR* pbstrVersion) { *pbstrVersion = SysAllocString(L"1.0"); return S_OK; }
STDMETHOD(OnScriptTerminate)(const VARIANT* pvarResult, const EXCEPINFO* pexcepinfo) { return S_OK; }
STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) { return S_OK; }
STDMETHOD(OnScriptError)(IActiveScriptError* pIActiveScriptError) { return S_OK; }
STDMETHOD(OnEnterScript)(void) { return S_OK; }
STDMETHOD(OnLeaveScript)(void) { return S_OK; }
// IActiveScriptSiteWindow
STDMETHOD(GetWindow)(HWND* phWnd) { *phWnd = m_hWnd; return S_OK; }
STDMETHOD(EnableModeless)(BOOL fEnable) { return S_OK; }
// Miscellaneous
STDMETHOD(SetWindow)(HWND hWnd) { m_hWnd = hWnd; return S_OK; }
public:
LONG m_cRefCount;
HWND m_hWnd;
};
STDMETHODIMP_(ULONG) CSimpleScriptSite::AddRef() {
return InterlockedIncrement(&m_cRefCount);
}
STDMETHODIMP_(ULONG) CSimpleScriptSite::Release() {
if (!InterlockedDecrement(&m_cRefCount)) {
delete this;
return 0;
}
return m_cRefCount;
}
STDMETHODIMP CSimpleScriptSite::QueryInterface(REFIID riid, void **ppvObject)
{
if (riid == IID_IUnknown || riid == IID_IActiveScriptSiteWindow) {
*ppvObject = (IActiveScriptSiteWindow*)this;
AddRef();
return NOERROR;
}
if (riid == IID_IActiveScriptSite) {
*ppvObject = (IActiveScriptSite*)this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
static HHOOK hHook = 0;
static bool HideInput = false;
static LRESULT CALLBACK InputBoxProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode < HC_ACTION)
return CallNextHookEx(hHook, nCode, wParam, lParam);
if (nCode = HCBT_ACTIVATE) {
if (HideInput == true) {
HWND TextBox = FindWindowExA((HWND)wParam, NULL, "Edit", NULL);
SendDlgItemMessageW((HWND)wParam, GetDlgCtrlID(TextBox), EM_SETPASSWORDCHAR, L'\x25cf', 0);
}
}
if (nCode = HCBT_CREATEWND) {
if (!(GetWindowLongPtr((HWND)wParam, GWL_STYLE) & WS_CHILD))
SetWindowLongPtr((HWND)wParam, GWL_EXSTYLE, GetWindowLongPtr((HWND)wParam, GWL_EXSTYLE) | WS_EX_DLGMODALFRAME);
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
static char *InputBoxHelper(char *Prompt, char *Title, char *Default) {
// Initialize
HRESULT hr = S_OK;
hr = CoInitialize(NULL);
CSimpleScriptSite* pScriptSite = new CSimpleScriptSite();
CComPtr<IActiveScript> spVBScript;
CComPtr<IActiveScriptParse> spVBScriptParse;
hr = spVBScript.CoCreateInstance(OLESTR("VBScript"));
hr = spVBScript->SetScriptSite(pScriptSite);
hr = spVBScript->QueryInterface(&spVBScriptParse);
hr = spVBScriptParse->InitNew();
// Replace quotes with double quotes
string strPrompt = StringReplaceAll(Prompt, "\"", "\"\"");
string strTitle = StringReplaceAll(Title, "\"", "\"\"");
string strDefault = StringReplaceAll(Default, "\"", "\"\"");
// Create evaluation string
string Evaluation = "InputBox(\"" + strPrompt + "\", \"" + strTitle + "\", \"" + strDefault + "\")";
Evaluation = CPPNewLineToVBSNewLine(Evaluation);
wstring WideEval = StringWiden(Evaluation);
// Run InpuBox
CComVariant result;
EXCEPINFO ei = {};
DWORD ThreadID = GetCurrentThreadId();
HINSTANCE ModHwnd = GetModuleHandle(NULL);
hr = pScriptSite->SetWindow(GetAncestor(GetActiveWindow(), GA_ROOTOWNER));
hHook = SetWindowsHookEx(WH_CBT, &InputBoxProc, ModHwnd, ThreadID);
hr = spVBScriptParse->ParseScriptText(WideEval.c_str(), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
UnhookWindowsHookEx(hHook);
// Cleanup
spVBScriptParse = NULL;
spVBScript = NULL;
pScriptSite->Release();
pScriptSite = NULL;
CoUninitialize();
// Result
static string strResult;
_bstr_t bstrResult = (_bstr_t)result;
strResult = StringShorten((wchar_t*)bstrResult);
return (char*)strResult.c_str();
}
char *InputBox(char *Prompt, char *Title, char *Default) {
HideInput = false;
return InputBoxHelper(Prompt, Title, Default);
}
char *PasswordBox(char *Prompt, char *Title, char *Default) {
HideInput = true;
return InputBoxHelper(Prompt, Title, Default);
}
Create the two above files and then add them to your Visual Studio project.
In any file you want the input or password box functions, (found in the header), simply include the header:
#include "inputbox.h"
// use functions here
I also got rid of the default Windows application icon in the title bar of the VBScript InputBox, because a lot of people I've seen complain about how ugly it is to see that there.
Let me know if you have any questions.
I have to admit that I haven't really done much in the way of input boxes in ages, but you basically have to go outside C++ in order to get any kind of graphical input box. There's simply no mechanism built into the language for that kind of stuff for portability reasons. I don't remember if it applied to C++ as well, but C doesn't even assume you have a console. Anyway, your best bet would be something along the lines you were already trying: Win32 API, Qt, etc. If you can use the console, however, feel free to just use the iostream library to get the job done.
Using a console window is better suited to the mode of communication where a program prompts the user, continues, prompts the user again, and so on.
And for that you can use the standard library's facilities like cin and cout.
Unlike Visual Basic and other languages, there is no "built in" Input Box like command in c++. Unlike MessageBox that can be just invoked, InputBox() needs to be written. In fact, I have done so. The following article describes how to implement such InputBox as part of a small Static Library that can be used, with no Resources, from any Win32 c++ program. Source code at Github. It can be used as follow:
LPWSTR GetString(LPCTSTR szCaption, LPCTSTR szPrompt, LPCTSTR szDefaultText = L"");
For example:
LPWSTR result = SG_InputBox::GetString(
L"Code Project Demo",
L"What is your name");
try this:
InputBox in c++ vs2010
#include "stdafx.h"
#using <system.windows.forms.dll>
#using <Microsoft.VisualBasic.dll>
using namespace System;
int main(array<System::String ^> ^args)
{
Microsoft::VisualBasic::Interaction::InputBox(L"Hello", L"Title", L"DefResp", 500, 500);
return 0;
}