I tried to create this window class which creates and shows a window. When I run this class, GetMessage keep on sending WM_PAINT message, and task manager shows me about 50% CPU usage just from my process.
main.cpp:
#include "Window.h"
int WINAPI WinMain(
HINSTANCE /* hInstance */,
HINSTANCE /* hPrevInstance */,
LPSTR /* lpCmdLine */,
int /* nCmdShow */
)
{
Window::GlobalInitialize();
Window window(L"abcd", 500, 500);
if (SUCCEEDED(window.Initialize()))
window.RunMessageLoop();
Window::GlobalTerminate();
return 0;
}
Window.h:
#ifndef WINDOW_HEADER
#define WINDOW_HEADER
#include <Windows.h>
#include <functional>
#pragma comment(lib, "d2d1.lib")
class Window;
typedef std::function<void(Window *window, UINT id, LPCWSTR message)> ErrorCallback;
class Window {
public:
#define ERROR_FAILED_HINSTANCE 1
#define ERROR_FAILED_HINSTANCE_STR L"Failed to retrieve hInstance"
#define ERROR_FAILED_REGISTER 2
#define ERROR_FAILED_REGISTER_STR L"Failed to register window class"
#define ERROR_FAILED_CREATION 3
#define ERROR_FAILED_CREATION_STR L"Failed to create window"
typedef std::function<HRESULT(Window *window)> WEOnCreate;
typedef std::function<HRESULT(Window *window)> WEOnDestroy;
typedef std::function<HRESULT(Window *window)> WEOnRender;
typedef std::function<HRESULT(Window *window, UINT width, UINT height)> WEOnResize;
typedef std::function<HRESULT(Window *window, UINT horizontalResolution, UINT verticalResolution)> WEOnScreenResolutionChange;
Window(LPCWSTR title, UINT width, UINT height);
~Window();
HRESULT SetSize(UINT width, UINT height);
HRESULT SetTitle(LPCWSTR title);
inline UINT GetWidth() { return _width; }
inline UINT GetHeight() { return _height; }
inline LPCWSTR GetTitle() { return _title; }
inline HWND GetHandle() { return hWnd; }
inline void SetOnCreateCallback(WEOnCreate fun) { _onCreate = fun; }
inline void SetOnDestroyCallback(WEOnDestroy fun) { _onDestroy = fun; }
inline void SetOnRenderCallback(WEOnRender fun) { _onRender = fun; }
inline void SetOnResizeCallback(WEOnResize fun) { _onResize = fun; }
inline void SetOnScreenResolutionChangeCallback(WEOnScreenResolutionChange fun) { _onResChange = fun; }
inline void SetExtraAllocatedSpace(void *ptr) { extra = ptr; }
inline void *GetExtraAllocatedSpace() { return extra; }
inline void Terminate() { if (hWnd) DestroyWindow(hWnd); }
static inline void SetErrorCallback(ErrorCallback fun) { _errorCallback = fun; }
HRESULT Initialize();
void RunMessageLoop();
static HRESULT GlobalInitialize();
static HRESULT GlobalTerminate();
private:
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static inline void throwError(Window *window, UINT id, LPCWSTR message) {
if (_errorCallback)
_errorCallback(window, id, message);
}
WEOnCreate _onCreate;
WEOnDestroy _onDestroy;
WEOnRender _onRender;
WEOnResize _onResize;
WEOnScreenResolutionChange _onResChange;
static ErrorCallback _errorCallback;
static LPCWSTR szClassName;
static HINSTANCE hInstance;
HWND hWnd;
int _width, _height;
LPCWSTR _title;
void *extra;
};
#endif
Window.cpp:
#include "Window.h"
//Initialize static variables
ErrorCallback Window::_errorCallback = nullptr;
LPCWSTR Window::szClassName = L"WindowClass";
HINSTANCE Window::hInstance;
Window::Window(LPCWSTR title = L"Window", UINT width = 640, UINT height = 480) :
_onCreate(nullptr),
_onDestroy(nullptr),
_onRender(nullptr),
_onResize(nullptr),
hWnd(NULL),
extra(NULL),
_width(width),
_height(height),
_title(title) {}
Window::~Window() {
if (hWnd) {
DestroyWindow(hWnd);
hWnd = NULL;
}
}
HRESULT Window::GlobalInitialize() {
// Retreive hInstance
hInstance = GetModuleHandle(NULL);
if (!hInstance) {
throwError(NULL, ERROR_FAILED_HINSTANCE, ERROR_FAILED_HINSTANCE_STR);
return E_FAIL;
}
// Create window class
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = Window::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = hInstance;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION);
wcex.lpszClassName = szClassName;
if (!RegisterClassEx(&wcex)) {
throwError(NULL, ERROR_FAILED_REGISTER, ERROR_FAILED_REGISTER_STR);
return E_FAIL;
}
return S_OK;
}
HRESULT Window::GlobalTerminate() {
if (UnregisterClass(szClassName, hInstance))
return S_OK;
else
return E_FAIL;
}
void Window::RunMessageLoop() {
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
HRESULT Window::Initialize() {
// Create the window
hWnd = CreateWindow(
szClassName,
_title,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
_width,
_height,
NULL,
NULL,
hInstance,
this
);
if (!hWnd) {
throwError(this, ERROR_FAILED_CREATION, ERROR_FAILED_CREATION_STR);
return E_FAIL;
}
// Show and render the window
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
return S_OK;
}
LRESULT CALLBACK Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_CREATE) {
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
Window *window = (Window *)pcs->lpCreateParams;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, PtrToUlong(window));
if (window->_onCreate != nullptr)
window->_onCreate(window);
}
Window *pWnd = reinterpret_cast<Window *>(static_cast<LONG_PTR>(GetWindowLongPtr(hWnd, GWLP_USERDATA)));
HRESULT hr = S_OK;
if (!pWnd) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
switch (message) {
case WM_PAINT:
{
if (pWnd->_onRender)
hr = pWnd->_onRender(pWnd);
else
hr = S_OK;
}
break;
case WM_SIZE:
{
if (pWnd->_onResize)
hr = pWnd->_onResize(pWnd, LOWORD(lParam), HIWORD(lParam));
else
hr = S_OK;
}
break;
case WM_DISPLAYCHANGE:
{
if (pWnd->_onResChange)
hr = pWnd->_onResChange(pWnd, LOWORD(lParam), HIWORD(lParam));
else
hr = S_OK;
}
break;
case WM_DESTROY:
{
if (pWnd->_onDestroy && FAILED(pWnd->_onDestroy(pWnd)))
break;
}
PostQuitMessage(0);
hWnd = NULL;
break;
default:
hr = DefWindowProc(hWnd, message, wParam, lParam);
}
return hr;
}
HRESULT Window::SetSize(UINT width, UINT height) {
if (hWnd)
if (!::SetWindowPos(hWnd, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER))
return E_FAIL;
_width = width;
_height = height;
return S_OK;
}
HRESULT Window::SetTitle(LPCWSTR title) {
if (hWnd)
if (!::SetWindowText(hWnd, title))
return E_FAIL;
_title = title;
return S_OK;
}
I hope someone can help me since everything looks OK(the window even runs fine).
Firstly, you seem to be treating the window procedure as if it was a COM method, but window procedures do not return an HRESULT - they return an LRESULT whose meaning is different for each message.
In the case of WM_PAINT it's not possible to return a value that indicates "I don't need to paint this time". The system will send WM_PAINT messages as long as a portion of your window is marked as dirty, and the way you mark the dirty area as "painted" is by calling BeginPaint and EndPaint. If you don't do this, the system will continue to consider your window as dirty and continue to send WM_PAINT messages.
You haven't shown the source code for your _onRender function but the very fact that you have made WM_PAINT handling optional (i.e. if nothing calls SetOnRenderCallback then no callback will be registered) means that you are probably not processing WM_PAINT correctly. At the very least, if you don't do the painting yourself, you should pass the message through to DefWindowProc to allow the default processing to take place.
Related
I would like to know when the system language has changed in my application, even when the application is not active. So I created an implementation using ITfLanguageProfileNotifySink. But, ITfLanguageProfileNotifySink::OnLanguageChange seems to only be executing when the window is active. How can I have this execute when the window is not the top active window?
Sample Code
#include <windows.h>
#include <msctf.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
class NotifyMe : protected ITfLanguageProfileNotifySink {
public:
ITfSource *m_tfSource;
DWORD m_dwCookie;
void Init();
virtual HRESULT STDMETHODCALLTYPE OnLanguageChange(LANGID langid, __RPC__out BOOL *pfAccept);
virtual HRESULT STDMETHODCALLTYPE OnLanguageChanged();
// IUnknown implementation
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
ULONG m_ulRefCount; ///< COM object reference count
};
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if(hwnd == NULL)
{
return 0;
}
CoInitialize(nullptr);
NotifyMe notify;
notify.Init();
ShowWindow(hwnd, nCmdShow);
MSG msg = {};
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
void NotifyMe::Init() {
m_tfSource = NULL;
ITfInputProcessorProfiles *pProfiles;
HRESULT hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (LPVOID*)&pProfiles);
if(SUCCEEDED(hr)) {
hr = pProfiles->QueryInterface(IID_ITfSource, (LPVOID*)&m_tfSource);
if(SUCCEEDED(hr)) {
hr = m_tfSource->AdviseSink(IID_ITfLanguageProfileNotifySink, (ITfLanguageProfileNotifySink*)this, &m_dwCookie);
if(FAILED(hr) || m_dwCookie == -1) {
m_tfSource->Release();
m_tfSource = NULL;
}
}
pProfiles->Release();
}
}
HRESULT STDMETHODCALLTYPE NotifyMe::OnLanguageChange(LANGID langid, __RPC__out BOOL *pfAccept)
{
if(pfAccept) *pfAccept = TRUE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE NotifyMe::OnLanguageChanged()
{
OutputDebugStringA("Language Changed");
return S_OK;
}
HRESULT STDMETHODCALLTYPE NotifyMe::QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject)
{
if(!ppvObject)
return E_INVALIDARG;
if(riid == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else if(riid == IID_ITfLanguageProfileNotifySink)
*ppvObject = static_cast<ITfLanguageProfileNotifySink*>(this);
else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
ULONG STDMETHODCALLTYPE NotifyMe::AddRef()
{
InterlockedIncrement(&m_ulRefCount);
return m_ulRefCount;
}
ULONG STDMETHODCALLTYPE NotifyMe::Release()
{
// Decrement the object's internal counter.
ULONG ulRefCount = InterlockedDecrement(&m_ulRefCount);
if(m_ulRefCount == 0)
delete this;
return ulRefCount;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
return 0;
}
// i want to listen to these events when the window is not in focus
case WM_SETFONT:
OutputDebugStringA("Font Changed");
break;
case WM_INPUTLANGCHANGE:
OutputDebugStringA("Language Changed - WndProc");
break;
// -- along with paint
//case WM_PAINT:
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
That's not how the ITfLanguageProfileNotifySink work. It is only for YOUR application.
What you need to understand is that every application got its own language just like yours. So when you switch to an app, the language there could be English, and when you switch back to any other app, it could be French, Japanese, Russian, or whatever language you set there.
What you want to achieve is to detect that change for "every" application, right? Then ITfLanguageProfileNotifySink is not the way.
You have to use GetKeyboardLayout instead because it allows you to get the language of any thread. But looks like you only want to detect the "active" thread, right? So you'd also want to pair it with GetWindowThreadProcessId(GetForegroundWindow(), NULL) because this would get the thread of the currently active window for you.
And for that to be integrated correctly with your application, you also have to modify your loop so that it doesn't "wait" for UI messages and block your detection scheme by using PeekMessage().
MSG msg = {};
const int period_ms = 10;
while (true)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
static LANGID currentLangId = LOWORD(::GetKeyboardLayout(0));
// Get active window thred
DWORD threadId = ::GetWindowThreadProcessId(::GetForegroundWindow(), NULL);
LANGID newLangId = LOWORD(::GetKeyboardLayout(threadId));
if (newLangId != currentLangId)
{
currentLangId = newLangId;
wchar_t szLangName[256];
GetLocaleInfo(MAKELCID(newLangId, SORT_DEFAULT), LOCALE_SENGLANGUAGE, szLangName, 256);
OutputDebugString(szLangName);
OutputDebugString(L"\n");
}
Sleep(period_ms);
}
Note that if the language changed in an app from X -> Y, it'll print 'Y', then when you switch to another app, it'll mostly print 'X' first and then in the next iteration, it'd print 'Y' right after. That's because "most" apps change their language accordingly to the previous window after you open them.
And some other applications would keep their language changes only inside the application no matter what how you change it outside of it. It is different from one app to another. But with that loop, you'll be able to detect all changes.
I am using ISpVoice to speak an input string. Now, even though I use SPF_ASYNC and SPF_PURGEBEFORESPEAK tags in the Speak method, the tts doesn't stop whenever Pause is called instead it continues until the tts finishes a word.
Here's how I do it:
void speakSentence()
{
pVoice->Pause();
pVoice->Speak(L"This is a sentence.", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
pVoice->Resume();
}
Whenever I try to call this function at the middle of the word "sentence", the tts doesn't pause and instead continues to say the word until the end.
From microsoft documentation:
ISpVoice::Pause pauses the voice at the nearest alert boundary and closes the output device, allowing access to pending speak requests from other voices.
I tried changing the alert boundary by:
pVoice->SetAlertBoundary(SPEI_PHONEME);
and it doesn't work.
There is NVDA Screen Reader that solved this problem but I don't know how they did it.
Is there anyway to solve my problem?
EDIT:
Here's my full code. I am creating a small screen reader program that uses both UIAutomation and MSAA.
The program may somewhat unstable when comparing UI objects but most times it works.
screeenreader.h:
#ifndef _SCREENREADER_H_
#define _SCREENREADER_H_
#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <memory>
#include "speechsynthesis.h"
#include "uiautomator.h"
class ScreenReader
{
public:
explicit ScreenReader(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow);
virtual ~ScreenReader();
LRESULT CALLBACK MessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int Exec();
private:
void InitializeWindows();
void InitRawInputDevices();
bool IsMouseMove();
private:
LPCWSTR m_applicationName;
HINSTANCE m_hInstance;
HINSTANCE m_hPrevInstance;
PSTR m_pScmdline;
int m_iCmdShow;
HWND m_hWnd;
SpeechSynthesis *m_pSpeech;
UIAutomator *m_pAutomator;
RAWINPUTDEVICE rid[2];
LONG m_prevMouseX;
LONG m_prevMouseY;
BSTR currItem;
};
static ScreenReader *application;
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
#endif
screenreader.cpp: In this part I called ISpVoice at the messageloop section. At ScreenReader::MessageHandler() function at IsMouseMove condition.
#include "screenreader.h"
ScreenReader::ScreenReader(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow)
{
CoInitialize(NULL);
m_pSpeech = new SpeechSynthesis;
m_pAutomator = new UIAutomator;
// Get current Cursor position.
POINT pt;
GetCursorPos(&pt);
m_prevMouseX = pt.x;
m_prevMouseY = pt.y;
// Notify user the program is loading.
m_pSpeech->Speak(L"Loading Rescan. Please wait.", SPF_DEFAULT, NULL);
m_hInstance = hInstance;
m_hPrevInstance = hPrevInstance;
m_pScmdline = pScmdline;
m_iCmdShow = iCmdShow;
application = this;
InitializeWindows();
InitRawInputDevices();
}
ScreenReader::~ScreenReader()
{
if (m_pSpeech != nullptr)
{
delete m_pSpeech;
m_pSpeech = nullptr;
}
if (m_pAutomator != nullptr)
{
delete m_pAutomator;
m_pAutomator = nullptr;
}
if (currItem != NULL)
{
SysFreeString(currItem);
currItem = NULL;
}
CoUninitialize();
}
LRESULT CALLBACK ScreenReader::MessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INPUT:
{
UINT dwSize;
GetRawInputData(
(HRAWINPUT)lParam,
RID_INPUT,
NULL,
&dwSize,
sizeof(RAWINPUTHEADER)
);
std::unique_ptr<BYTE[]> lpb(new BYTE[dwSize]);
if (!lpb)
return 0;
if (GetRawInputData(
(HRAWINPUT)lParam,
RID_INPUT,
lpb.get(),
&dwSize,
sizeof(RAWINPUTHEADER)
) != dwSize)
OutputDebugString(L"GetRawInputData does not return correct size!\n");
RAWINPUT *raw = (RAWINPUT*)lpb.get();
if (raw->header.dwType == RIM_TYPEKEYBOARD)
{
UINT mess = raw->data.keyboard.Message;
UINT vKey = raw->data.keyboard.VKey;
if (mess == WM_KEYDOWN)
{
}
}
else if (raw->header.dwType == RIM_TYPEMOUSE)
{
if (IsMouseMove())
{
BSTR item;
HRESULT hr = m_pAutomator->GetUIAutomationItemNameAtMousePoint(&item);
if (item == NULL)
return 0;
if (currItem == NULL)
currItem = SysAllocString(item);
if (wcscmp(currItem, item) != 0)
{
m_pSpeech->Stop();
m_pSpeech->Speak(item);
if (currItem != NULL)
SysFreeString(currItem);
currItem = SysAllocString(item);
}
SysFreeString(item);
}
}
}
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
int ScreenReader::Exec()
{
MSG msg;
ShowWindow(m_hWnd, m_iCmdShow);
// Tell the user that the program is ready.
m_pSpeech->Speak(L"Rescan ready.", SPF_PURGEBEFORESPEAK);
// The message loop
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
void ScreenReader::InitializeWindows()
{
// Create Window class.
WNDCLASSEX wc;
m_applicationName = L"Rescan Screen Reader";
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = m_hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_applicationName;
wc.hIconSm = wc.hIcon;
// Register the window class.
RegisterClassEx(&wc);
m_hWnd = CreateWindowEx(
WS_EX_OVERLAPPEDWINDOW,
m_applicationName,
L"Rescan Screen Reader",
WS_CAPTION | WS_MINIMIZEBOX | WS_OVERLAPPED | WS_SYSMENU,
(GetSystemMetrics(SM_CXSCREEN) - 500) / 2,
(GetSystemMetrics(SM_CYSCREEN) - 300) / 2,
500,
300,
NULL,
NULL,
m_hInstance,
NULL
);
}
void ScreenReader::InitRawInputDevices()
{
// Initialize Keyboard
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = m_hWnd;
// Initialize Mouse
rid[1].usUsagePage = 0x01;
rid[1].usUsage = 0x02;
rid[1].dwFlags = RIDEV_INPUTSINK;
rid[1].hwndTarget = m_hWnd;
// Register RIDs
RegisterRawInputDevices(rid, 2, sizeof(RAWINPUTDEVICE));
}
bool ScreenReader::IsMouseMove()
{
POINT pt;
GetCursorPos(&pt);
bool result = !(m_prevMouseX == pt.x && m_prevMouseY == pt.y);
m_prevMouseX = pt.x;
m_prevMouseY = pt.y;
return result;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_QUIT:
PostQuitMessage(0);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return application->MessageHandler(hWnd, message, wParam, lParam);
}
}
I wrapped ISpVoice into the SpeechSynthesis class.
speechsynthesis.h:
#ifndef _SPEECHSYNTHESIS_H_
#define _SPEECHSYNTHESIS_H_
#pragma warning(disable : 4996)
#define SPCAT_VOICES_ONECORE L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech_OneCore\\Voices"
#include <sapi.h>
#include <sphelper.h>
#include <atlbase.h>
class SpeechSynthesis
{
public:
SpeechSynthesis();
~SpeechSynthesis();
HRESULT Speak(LPCWSTR pwcs, DWORD dwFlags = SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, ULONG *pulStreamNumber = NULL);
HRESULT Resume();
HRESULT Pause();
HRESULT Stop();
ISpVoice* getVoice();
private:
CComPtr<ISpObjectToken> cpVoiceToken;
CComPtr<IEnumSpObjectTokens> cpEnum;
ISpVoice* pVoice;
ULONG count;
};
#endif
speechsynthesis.cpp:
#include "speechsynthesis.h"
SpeechSynthesis::SpeechSynthesis()
{
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if (SUCCEEDED(hr))
hr = SpEnumTokens(SPCAT_VOICES_ONECORE, NULL, NULL, &cpEnum);
if (SUCCEEDED(hr))
hr = cpEnum->GetCount(&count);
if (SUCCEEDED(hr))
{
cpEnum->Item(1, &cpVoiceToken);
pVoice->SetPriority(SPVPRIORITY::SPVPRI_ALERT);
pVoice->SetAlertBoundary(SPEI_PHONEME);
pVoice->SetOutput(NULL, TRUE);
pVoice->SetVoice(cpVoiceToken);
}
if (FAILED(hr))
{
MessageBox(NULL, "A fatal error has occured", "Error Message", MB_ABORTRETRYIGNORE);
}
}
SpeechSynthesis::~SpeechSynthesis()
{
pVoice->Release();
}
HRESULT SpeechSynthesis::Speak(LPCWSTR pwcs, DWORD dwFlags, ULONG *pulStreamNumber)
{
return pVoice->Speak(pwcs, dwFlags, pulStreamNumber);
}
HRESULT SpeechSynthesis::Resume()
{
return pVoice->Resume();
}
HRESULT SpeechSynthesis::Pause()
{
return pVoice->Pause();
}
HRESULT SpeechSynthesis::Stop()
{
return Speak(NULL);
}
ISpVoice * SpeechSynthesis::getVoice()
{
return pVoice;
}
uiautomator.h
#ifndef _UIAUTOMATOR_H_
#define _UIAUTOMATOR_H_
#include <windows.h>
#include <oleacc.h>
#include <uiautomation.h>
#pragma comment(lib, "oleacc.lib")
class UIAutomator
{
public:
UIAutomator();
~UIAutomator();
HRESULT GetItemNameAtMousePoint(BSTR *pStr);
HRESULT GetUIAutomationItemNameAtMousePoint(BSTR *pStr);
private:
HRESULT InitUIAutomation();
private:
IUIAutomation *m_automation;
};
#endif
uiautomator.cpp
#include "uiautomator.h"
UIAutomator::UIAutomator()
{
SetProcessDPIAware();
HRESULT hr = InitUIAutomation();
}
UIAutomator::~UIAutomator()
{
}
HRESULT UIAutomator::GetItemNameAtMousePoint(BSTR * pStr)
{
POINT pt;
GetPhysicalCursorPos(&pt);
VARIANT varItem;
IAccessible *pAcc;
HRESULT hr = AccessibleObjectFromPoint(pt, &pAcc, &varItem);
if (SUCCEEDED(hr))
{
hr = pAcc->get_accName(varItem, pStr);
VariantClear(&varItem);
pAcc->Release();
}
return hr;
}
HRESULT UIAutomator::GetUIAutomationItemNameAtMousePoint(BSTR * pStr)
{
CONTROLTYPEID id;
POINT pt;
IUIAutomationElement *elem;
VARIANT val;
GetCursorPos(&pt);
HRESULT hr = m_automation->ElementFromPoint(pt, &elem);
if (SUCCEEDED(hr))
{
hr = elem->get_CurrentControlType(&id);
if (SUCCEEDED(hr))
{
if (id == UIA_PaneControlTypeId)
GetItemNameAtMousePoint(pStr);
else if (id == UIA_EditControlTypeId)
{
hr = elem->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &val);
if (SUCCEEDED(hr))
{
*pStr = SysAllocString(val.bstrVal);
VariantClear(&val);
}
}
else
{
hr = elem->get_CurrentName(pStr);
}
}
elem->Release();
}
return hr;
}
HRESULT UIAutomator::InitUIAutomation()
{
HRESULT hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER,
__uuidof(IUIAutomation), (void**)&m_automation);
return hr;
}
main.cpp
#include "vld.h"
#include "screenreader.h"
#include <memory>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow)
{
std::unique_ptr<ScreenReader> app(new ScreenReader(hInstance, hPrevInstance, pScmdline, iCmdShow));
return app->Exec();
}
If you don't have time to compile here's the program.
If you launch it and hover the mouse on the program window there is a lag when highlighting minimize and close button. Also sometimes the tts doesn't stop immediately when you hover at another object.
Compare this one to NVDA Screen Reader. You will notice the big difference.
It works for me with or without setting SetAlertBoundary(SPEI_PHONEME).
The following is my test code, you can have a try.
HRESULT hr = ::CoInitialize(nullptr);
if (FAILED(hr))
{
return EXIT_FAILURE;
}
std::wstring text;
text = L"This is a sentence.";
CComPtr<ISpVoice> cpVoice;
// Create a SAPI voice
hr = cpVoice.CoCreateInstance(CLSID_SpVoice);
//cpVoice->SetAlertBoundary(SPEI_PHONEME);
// set the output to the default audio device
if (SUCCEEDED(hr))
{
hr = cpVoice->SetOutput(NULL, TRUE);
}
// Speak the text
if (SUCCEEDED(hr))
{
hr = cpVoice->Speak(text.c_str(), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
}
text = L"The third type, a logarithm of the unsigned fold change, is undoubtedly the most tractable.";
Sleep(600);
hr = cpVoice->Pause();
hr = cpVoice->Resume();
hr = cpVoice->Speak(text.c_str(), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
Sleep(10000);
::CoUninitialize();
if (SUCCEEDED(hr))
{
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
I finally got it!
CComPtr<ISpAudio> audio;
CSpStreamFormat format;
format.AssignFormat(SPSF_11kHz8BitMono);
Initialize audio
SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOOUT, &audio);
Then, set its format and set it as output to pVoice.
audio->SetFormat(format.FormatId(), format.WaveFormatExPtr());
pVoice->SetOutput(audio, FALSE);
Now I have access to the audio stream!
Now to Immediately stop the audio, call:
audio->SetState(SPAS_STOP, 0);
Then speak again using:
audio->SetState(SPAS_RUN, 0);
pVoice->Speak(L"This is a sentence", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
I am trying to create an edit control using oop techniques but i do not really understand what i am doing wrong. If someone can point out how I should go about doing this that will be very helpful. I have tried googling it but there is not alot out there because people do not code in win32 using Microsoft API i would try mfc but i am sticking with the win32 api.
#include <windows.h>
class winMaker
{
public:
winMaker(char * className,HINSTANCE hInstance);
HWND create(const char * title);
protected:
HINSTANCE _hInst;
char const *_className;
DWORD _style;
DWORD _exStyle;
char const *_title;
int _x;
int _y;
int _width;
int _height;
HWND _hWndParent;
HMENU _hMenu;
void * _data;
};
winMaker::winMaker(char * className,HINSTANCE hInstance) :
_style (WS_OVERLAPPEDWINDOW),
_exStyle (0),
_className (className),
_x (CW_USEDEFAULT),
_y (0),
_width (CW_USEDEFAULT),
_height (0),
_hWndParent (0),
_hMenu (0),
_data (0),
_hInst (hInstance)
{
}
HWND winMaker::create(const char * title)
{
HWND hwnd = CreateWindowEx(
_exStyle,
_className,
title,
_style,
_x,
_y,
_width,
_height,
_hWndParent,
_hMenu,
_hInst,
NULL
);
return hwnd;
}
class childMaker : public winMaker
{
public:
childMaker(char * className,winApp appInstance, int childId);
HWND create(const char * text = "");
};
childMaker::childMaker(char * className, winApp appInstance, int childId):
winMaker(className,appInstance.getInstance())
{
_style = WS_CHILD;
_hWndParent = appInstance;
_hMenu = reinterpret_cast<HMENU> (childId);
}
HWND childMaker::create(const char * text)
{
HWND hwnd = CreateWindowEx(
_exStyle,
_className,
text,
_style,
_x,
_y,
_width,
_height,
_hWndParent,
_hMenu,
_hInst,
0 //controller parameter goes here
);
return hwnd;
}
the code below is the window entry point where i make the call to my edit control class
#include <windows.h>
#include "winApp.h"
#include "winMaker.h"
LRESULT CALLBACK WndProcedure(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG Msg;
HWND hWnd;
HWND hnd;
winApp myWindow(hInstance,"window1","simp");
char * ClsName = myWindow.RegisterWindowStructure();
winMaker myWindowMaker(ClsName,hInstance);
hWnd = myWindowMaker.create("Simple Window");
myWindow.saveHwnd(hWnd);
childMaker editControl("EDIT",myWindow,0);
hnd = editControl.create();
// Find out if the window was created
if( !hWnd ) // If the window was not created,
return 0; // stop the application
// Display the window to the user
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
// Decode and treat the messages
// as long as the application is running
while( GetMessage(&Msg, NULL, 0, 0) )
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
the code below is winMan class which contain methods for creating window class structure
#include <windows.h>
#include "windows.h"
class winApp
{
public:
friend LRESULT CALLBACK WndProcedure(HWND hWnd, UINT Msg,
WPARAM wParam, LPARAM lParam);
winApp(HINSTANCE hInst,char nameOfStructWindow[],char MenuName[]);
char * RegisterWindowStructure();
HINSTANCE getInstance() const { return _hInstance; }
operator HWND () const { return _h; }
void saveHwnd(HWND h){ _h = h; }
HWND returnHandle() { return _h; }
private:
char * ClsName;
char * menuName;
HINSTANCE _hInstance;
WNDCLASSEX _WndClsEx;
HWND _h;
};
LRESULT CALLBACK WndProcedure(HWND hWnd, UINT Msg,
WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
// If the user wants to close the application
case WM_DESTROY:
// then close it
PostQuitMessage(WM_QUIT);
break;
default:
// Process the left-over messages
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
// If something was not done, let it go
return 0;
}
winApp::winApp(HINSTANCE hInst,char nameOfStructWindow[],char MenuName[])
: ClsName(nameOfStructWindow),menuName(MenuName),_hInstance(hInst)
{
_WndClsEx.cbSize = sizeof(WNDCLASSEX);
_WndClsEx.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
_WndClsEx.lpfnWndProc = WndProcedure;
_WndClsEx.cbClsExtra = 0;
_WndClsEx.cbWndExtra = 0;
_WndClsEx.hInstance = hInst;
_WndClsEx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
_WndClsEx.hCursor = LoadCursor(NULL, IDC_ARROW);
_WndClsEx.hbrBackground = static_cast<HBRUSH>
(GetStockObject(GRAY_BRUSH));
_WndClsEx.lpszMenuName = MenuName;
_WndClsEx.lpszClassName = ClsName;
_WndClsEx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
}
char * winApp::RegisterWindowStructure()
{
RegisterClassEx(&_WndClsEx);
return ClsName;
}
The edit box got created with the default styles which is zero in this case 0 as it is not loaded from the resource in which the default style is specified.
So the size should be specified manually to make the edit box visible.
::SetWindowPos (hnd, HWND_TOP,0,0,100,100, SWP_SHOWWINDOW);
I have used 100 for representational purpose.
You can also use other Win32 libraries available for window management. Reinventing is not a better option.
I have left some //Comments to help you navigate through the issue
I have Main.cpp
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nShowCmd)
{
Game game;
game.Initialize(hInst);
game.MainMenu();
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Game.h
#pragma once
#define WIN32_LEAN_AND_MEAN
/*Various includes*/
template<class Interface>
inline void SafeRelease(Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
class Game{
public:
Game();
~Game();
HRESULT Initialize(HINSTANCE);
void MainMenu();
void OnResize(UINT width, UINT height);
void OnRender();
private:
static LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
private:
HWND m_hWnd;
ID2D1Factory* pD2DFactory;
ID2D1HwndRenderTarget* pRT;
};
And Game.cpp
#include "Game.h"
#include <comdef.h>
Game::Game() :
pD2DFactory(NULL),
pRT(NULL)
{
}
Game::~Game()
{
SafeRelease(&pD2DFactory);
SafeRelease(&pRT);
}
HRESULT Game::Initialize(HINSTANCE hInst)
{
HRESULT hr;
// Create factory
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
// Create a window class
WNDCLASSEX wClass;
ZeroMemory(&wClass,sizeof(WNDCLASSEX));
wClass.cbClsExtra=NULL;
wClass.cbSize=sizeof(WNDCLASSEX);
wClass.cbWndExtra=NULL;
wClass.hbrBackground=(HBRUSH)COLOR_WINDOW;
wClass.hCursor=LoadCursor(NULL,IDC_ARROW);
wClass.hIcon=NULL;
wClass.hIconSm=NULL;
wClass.hInstance=hInst;
wClass.lpfnWndProc=Game::WinProc;
wClass.lpszClassName="Window Class";
wClass.lpszMenuName=NULL;
wClass.style=CS_HREDRAW|CS_VREDRAW;
m_hWnd=CreateWindowEx(NULL,
"Window Class",
"Game", // Replace with gameName
WS_OVERLAPPEDWINDOW|WS_MAXIMIZE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInst,
NULL);
RECT rc;
GetClientRect(m_hWnd,&rc);
// Creates render target
if(SUCCEEDED(hr))
{
pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(
m_hWnd,
D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top)),
&pRT);
}
ShowWindow(m_hWnd,SW_MAXIMIZE); //When my program gets there, WinProc gets called with the WM_PAINT message. The problem then happens.
UpdateWindow(m_hWnd);
return hr;
}
void Game::OnRender()
{
// The following code will eventually be deleted. It is left for test purpose only.
HRESULT hr;
RECT rc;
GetClientRect(m_hWnd,&rc); // The error happens when my program gets to this line of code.
ID2D1SolidColorBrush* pBlackBrush = NULL;
pRT->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::White),
&pBlackBrush);
pRT->BeginDraw();
pRT->DrawRectangle(
D2D1::RectF(
rc.left + 100.0f,
rc.top + 100.0f,
rc.right - 100.0f,
rc.bottom - 100.0f),
pBlackBrush);
hr = pRT->EndDraw();
}
LRESULT Game::WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if(msg==WM_CREATE)
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
Game* pGame = (Game*)pcs->lpCreateParams;
::SetWindowLongPtrW(
hWnd,
GWLP_USERDATA,
PtrToUlong(pGame)
);
}
else
{
Game* pGame = reinterpret_cast<Game*>(static_cast<LONG_PTR>(
::GetWindowLongPtrW(
hWnd,
GWLP_USERDATA
)));
switch(msg)
{
case WM_PAINT:
{
pGame->OnRender();
ValidateRect(hWnd, NULL);
}
break;
case WM_SIZE:
{
UINT width = LOWORD(lParam);
UINT height = HIWORD(lParam);
pGame->OnResize(width, height); //OnResize is called here
}
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect(hWnd, NULL, FALSE);
}
break;
case WM_DESTROY:
{
//SafeRelease(p2D2Factory);
PostQuitMessage(0);
return 0;
}
break;
}
}
return DefWindowProc(hWnd,msg,wParam,lParam);
}
This is the exception I receive:
Unhandled exception at 0x001f1666 in Arena Clash client.exe: 0xC0000005: Access violation reading location 0x00000000.
I removed some code to make it easier to read. What happens is I get an unhandled exception. For reasons I don't understand, m_hWnd seems to have no value.
I've tried to find the solution for hours. Almost for all day actually. I really need help.
Actually, what I want is to redraw my image upon resize and similar changes.
You're passing NULL for the lpParam parameter (i.e. the last one) to CreateWindowEx.
Then, in WinProc, you say
Game* pGame = (Game*)pcs->lpCreateParams;
::SetWindowLongPtrW(
hWnd,
GWLP_USERDATA,
PtrToUlong(pGame)
);
So you'll set the user data pointer to NULL, as well.
You should pass thisto CreateWindowEx instead.
I built my own c++framwork that is similar with MFC, I can create child window when WM_CREATE message was received, but I can not deal with Edit Box, I got the idea from Ivan Shcherbakov's post, I post a WM_CREATEMYWINDOW message after call OnCreate, and Create something in OnCreateMyWindow function when the message was received, and, of course, it works. but I want to know why and how to fix this problem.
my CWnd is CMyWindow, I created a CBT-hook to hold the HWND and CMyWindow* of new window, then add to a HWND-WINDOW map as WindowManager, the message loop callback function MyWndProc, got the CMyWindow* from WindowManger by hWnd parameter, then call CMyWindow's message functions OnCreate, OnSize, OnMove ..., etc. it just like the CWnd class.
CMyWindow seems work well, it can hold all messages and do something to response, but it can not create a edit box when WM_CREATE, it's so wired, because it create a new window with any style when WM_CREATE.
I built it with vs2010, win7.
the WindowManager (HWND-WINDOW map) in MyWindow.h
#define WM_CREATEMYWINDOW WM_USER + 123
//the CWindowManager is a map of HWND-CMyWindow
class CMyWindow;
//define the HWND-CMyWindow map
typedef map <HWND, CMyWindow*> CWindowMap;
typedef pair<HWND, CMyWindow*> WindowPair;
typedef map <HWND, CMyWindow*>::iterator WndIterator;
typedef pair<WndIterator, bool> IterBool;
class CWindowManager : private CWindowMap
{
private:
CWindowManager(void);
~CWindowManager(void);
public:
bool Add(CMyWindow* pwnd); //add a window to map
bool Remove(HWND hwnd); //remove a window by hwnd
void Clear(); //remove all items
CMyWindow* Find(HWND hwnd); //find the window by hwnd
public:
//get CWindowManager instance as singleton pattern
static CWindowManager * GetInstance();
};
CMyWindow class is similar with CWnd of MFC
class CMyWindow
{
public:
CMyWindow(void);
~CMyWindow(void);
inline HWND GetSafeHwnd(); //get HWND
//Create a window
bool Create( HINSTANCE hInstance,
TCHAR * szWindowName,
DWORD dwStyle,
RECT& rect,
HWND hParentWnd,
HMENU hMenu,
LPVOID lpParam);
//add the window to WindowManager
void Attach(HWND hwnd);
//remove the window from WindowManager
void Dettach();
//process the WM_CREATE message
virtual int OnCreate(LPCREATESTRUCT ps);
/*
other message function
*/
//process the WM_CREATEMYWINDOW message
virtual void OnCreateMyWindow();
protected:
HWND _hwnd;
HINSTANCE _hInst;
};
WindowManager and CBT-HOOK part in MyWindow.cpp
#include "StdAfx.h"
#include "LockEx.h"
#include "MyWindow.h"
//window class name
static TCHAR * _MY_WINDOW_CLASS_NAME_ = L"MYWINDOWCLASS";
//window-proc
LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
///an Mutex-Lock class for keep current hwnd of new window, but you can ignore it when using single thread to crate window
static CLockEx gLockInitWnd;
///keep current hwnd of new window
static CMyWindow * gpInitWnd = 0;
///an hook to create new window
static HHOOK ghook = 0;
///set gpInitWnd when create a new window
static void SetInitWnd(CMyWindow * pWnd)
{
CLockEx::Scoped lock(gLockInitWnd);
gpInitWnd = pWnd;
}
///clear gpInitWnd after new window created
static void UnsetInitWnd()
{
CLockEx::Scoped lock(gLockInitWnd);
gpInitWnd = 0;
}
///get the HWND of new window that is creating
static CMyWindow * GetInitPwnd()
{
CLockEx::Scoped lock(gLockInitWnd);
return gpInitWnd;
}
//CBT-Proc for SetWindowsHookEx
static LRESULT CALLBACK MyCBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HCBT_CREATEWND) {
//get the new window from gpInitWnd
CMyWindow * pwnd = GetInitPwnd();
if (pwnd) {
//first time call this proc
//add this window to WindowManager
pwnd->Attach((HWND)wParam);
return (0);
} else {
//sencond time call this proc
CREATESTRUCT * cs = ((CBT_CREATEWND *)lParam)->lpcs;
if (!(cs->style & WS_CHILD)) {
//we can do something here
return (0);
} else {
return (1); //error, destroy the window
//or, maybe, CallNextHookEx
}
}
} else
return CallNextHookEx(ghook, nCode, wParam, lParam);
}
//Create a WH_CBT Hook
static bool HookCrate()
{
HANDLE hThread = GetCurrentThread();
DWORD dwThreadId = GetThreadId(hThread);
if (hThread) {
ghook = SetWindowsHookEx(
WH_CBT,
MyCBTProc, //set the CBT proc
0,
dwThreadId);
if (!ghook)
return false;
}
return (0);
}
//Destroy WH_CBT Hook
static void HookDestroy()
{
if (ghook) {
UnhookWindowsHookEx(ghook);
ghook = 0;
}
}
///////////////////////////////////////////////
//this is a vector for keep all CMyWindow*
CWindowManager::CWindowManager(void)
{
}
CWindowManager::~CWindowManager(void)
{
clear();
}
//insert new window
bool CWindowManager::Add(CMyWindow* pwnd)
{
IterBool ib = insert(WindowPair(pwnd->GetSafeHwnd(), pwnd));
return ib.second;
}
//remove a window by hwnd
bool CWindowManager::Remove(HWND hwnd)
{
WndIterator wi = find(hwnd);
if (wi == end( )) {
return false;
} else {
erase(wi);
return true;
}
}
//find a window by hwnd
CMyWindow* CWindowManager::Find(HWND hwnd)
{
WndIterator wi = find(hwnd);
if (wi == end( )) {
return (0);
} else {
return wi->second;
}
}
//remove all items
void CWindowManager::Clear()
{
clear();
}
//get instance as singleton pattern.
CWindowManager * CWindowManager::GetInstance()
{
static CWindowManager wm;
return &wm;
}
register window class, set the WndProc to MyWIndowProc
ATOM RegisteWindowClass(HINSTANCE hInstance, TCHAR * szClassName)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = MyWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = 0;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = 0;
wcex.lpszClassName = szClassName;
wcex.hIconSm = 0;
return RegisterClassEx(&wcex);
}
CMyWindow class, please pay attention on my trouble at WM_CREATE and WM_CREATEMYWINDOW message
CMyWindow::CMyWindow(void)
: _hwnd(0)
, _hInst(0)
{
}
CMyWindow::~CMyWindow(void)
{
}
inline HWND CMyWindow::GetSafeHwnd()
{
return _hwnd;
}
//Craete a window
bool CMyWindow::Create( HINSTANCE hInstance,
TCHAR * szWindowName,
DWORD dwStyle,
RECT& rect,
HWND hParentWnd,
HMENU hMenu,
LPVOID lpParam)
{
//get safe instance
HINSTANCE hInst = hInstance;
if (!hInstance)
if (!hParentWnd)
return false;
else
hInst = (HINSTANCE) GetWindowLong(hParentWnd, GWL_HINSTANCE);
if (!hInst)
return false;
//register window class
if (!RegisteWindowClass(hInst, _MY_WINDOW_CLASS_NAME_)) {
DWORD dwErr = GetLastError();
if (dwErr != ERROR_CLASS_ALREADY_EXISTS) //0x00000582
return false;
}
//claim i am creating
SetInitWnd(this);
//create CBT hook, then this window will add to WindowManager
HookCrate();
//create window
HWND hwnd = CreateWindow(
_MY_WINDOW_CLASS_NAME_,
szWindowName,
dwStyle,
rect.left, rect.right, rect.right - rect.left, rect.bottom - rect.top,
hParentWnd,
hMenu,
hInstance,
lpParam);
//destroy CBT hook
HookDestroy();
if (!hwnd)
return false;
_hwnd = hwnd;
_hInst = hInst;
//show window
ShowWindow(_hwnd, SW_SHOW);
UpdateWindow(_hwnd);
return true;
}
//add the this window to WindowManager
void CMyWindow::Attach(HWND hwnd)
{
_hwnd = hwnd;
CWindowManager::GetInstance()->Add(this);
UnsetInitWnd();
}
//remove the this window to WindowManager
void CMyWindow::Dettach()
{
CWindowManager::GetInstance()->Remove(_hwnd);
_hwnd = 0;
}
int CMyWindow::OnCreate(LPCREATESTRUCT ps)
{
return (0);
}
void CMyWindow::OnCreateMyWindow()
{
}
//the WndProc callback function
LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//Get the CMyWindow instance from WindowManager by hWnd
CMyWindow * pwnd = CWindowManager::GetInstance()->Find(hWnd);
//can not find thi window in WindowManager
if (!pwnd) return DefWindowProc(hWnd, message, wParam, lParam);
switch (message)
{
case WM_CREATE:
{
//perform the OnCreate function, just like MFC's OnCreate
int r = pwnd->OnCreate(reinterpret_cast<LPCREATESTRUCT>(lParam));
if (r) //some error occurred, will destory the window
return (r);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//I can not create any edit box in OnCreate function,
//I must do it leater, when the window was created and
//WM_CREATEMYWINDOW was received
::PostMessage(hWnd, WM_CREATEMYWINDOW, 0, 0);
}
break;
/*
case WM_.....
other message
case WM_.....
*/
case WM_DESTROY:
::PostQuitMessage(0);
break;
case WM_CREATEMYWINDOW:
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//I can not create any edit box in OnCreate function,
//I must do it when WM_CREATEMYWINDOW was received
pwnd->OnCreateMyWindow();
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Now, inherit the class CMyWindow to create 2 windows, first window will create the second one that with a edit box
class CMyWnd2 : public CMyWindow
{
public:
CMyWnd2(void) : _hCmdEdit(0) {}
~CMyWnd2(void){}
protected:
HWND _hCmdEdit;
//create edit box
virtual void OnCreateMyWindow();
};
class CMyWnd1 : public CMyWindow
{
public:
CMyWnd1(void) {}
~CMyWnd1(void){}
protected:
virtual int OnCreate(LPCREATESTRUCT ps);
//create window2
virtual void OnCreateMyWindow();
CMyWnd2 _wnd2;
};
int CMyWnd1::OnCreate(LPCREATESTRUCT /*ps*/)
{
//Can create window2, but can not crate window2's edit-boxs
return (0);
}
//create window2 with edit box
void CMyWnd1::OnCreateMyWindow()
{
RECT rect = {0, 0, 400, 300};
_wnd2.Create(this->_hInst,
0,
WS_VISIBLE | WS_POPUP,
rect,
_hwnd,
0,
0);
}
//create edit box
void CMyWnd2::OnCreateMyWindow()
{
RECT rect;
GetClientRect(_hwnd, &rect);
_hCmdEdit = CreateWindowEx(
WS_EX_STATICEDGE,
L"EDIT",
NULL,
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
0,
rect.bottom - 80,
rect.right - rect.left,
80,
_hwnd,
(HMENU) 100,
(HINSTANCE) GetWindowLong(_hwnd, GWL_HINSTANCE),
NULL);
}
Finally, WinMain function create a instance of CMyWnd1
CMyWnd1 mainwnd;
RECT rect;
rect.left = rect.top = 0, rect.right = 500, rect.bottom = 400;
mainwnd.Create(hInstance,
L"MyWindow",
WS_OVERLAPPEDWINDOW,
rect,
0,
0,
0);
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Here must have something I missed, any one can help me fix this problem?
Please note that WM_CREATE message is sent before CreateWindow() returns. This means window handle member _hwnd (note: identifiers starting with _ and __ shall not be used, they are reserved for compiler extensions) is not yet initialized and contains 0. Therefore subsequent CreateWindow() call fails because you must pass valid window handle to create child window (that has WS_CHILD style). But it is allowed for popup windows (WS_POPUP style). That is very probably the reason why Wnd2 is created, but edit box is not.
i got it, a stupid mistake occurred in CBThook callback function, i refused the creation request for a window with WS_CHILD style when this function was called by second time. it only need return a zero!! please see the error in comments. now it works! thanks Rost's hint.
static LRESULT CALLBACK MyCBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HCBT_CREATEWND) {
//get the new window from gpInitWnd
CMyWindow * pwnd = GetInitPwnd();
if (pwnd) {
//first time call this proc
//add this window to WindowManager
pwnd->Attach((HWND)wParam);
return (0);
} else {
//sencond time call this proc
return (0);
////
/// below was my stupid code,
////
/*CREATESTRUCT * cs = ((CBT_CREATEWND *)lParam)->lpcs;
if (!(cs->style & WS_CHILD)) {
//we can do something here
return (0);
} else {
return (1); //error, destroy the window
//or, maybe, CallNextHookEx
}*/
}
} else
return CallNextHookEx(ghook, nCode, wParam, lParam);
}