Ich versuche mich gerade an einer simplen DirectX Anwendung. Meine C++ Kenntnisse möchte ich doch als eher bescheiden bezeichnen da die Programmierung ein reines Hobby ist.
Ich habe bereits eine simple Win32 Fensterumgebung implementiert und möchte nun einen Tastaturhook verwenden um verschiedene Tastenkombinationen zu verarbeiten.
Dabei bin ich auf ein Designproblem gestossen, auf welches ich keine befriedigende Lösung gefunden habe.
Zuerst mal der betreffende Code:
Code: Alles auswählen
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <cassert>
#include <exception>
#include <stdexcept>
#include <Windows.h>
//=================================================================================================
// UtilityNonCopyAssignable.hpp
//=================================================================================================
namespace Tutorial
{
namespace Utility
{
class NonCopyAssignable
{
protected:
NonCopyAssignable() {}
private:
NonCopyAssignable(NonCopyAssignable const &);
NonCopyAssignable & operator=(NonCopyAssignable const &);
};
}
}
//=================================================================================================
// SystemWindow.hpp/cpp
//=================================================================================================
namespace Tutorial
{
namespace System
{
class Window : protected Utility::NonCopyAssignable
{
protected:
::HINSTANCE const myInstance;
::WCHAR const * myClassName;
::HWND myWindow;
protected:
/////////////////////////////////////////////////////////////////////////////
Window(::HINSTANCE itsInstance, WCHAR const * className) :
myInstance(itsInstance),
myClassName(className)
{
auto const windowIcon = ::LoadIconW(nullptr, IDI_APPLICATION); // Todo ...
::WNDCLASSEXW description = { 0 };
description.cbSize = sizeof(description);
description.hbrBackground = reinterpret_cast<::HBRUSH>(::GetStockObject(BLACK_BRUSH));
description.hCursor = ::LoadCursorW(nullptr, IDC_ARROW);
description.hIcon = windowIcon;
description.hIconSm = windowIcon;
description.hInstance = myInstance;
description.lpfnWndProc = &WindowProcedure;
description.lpszClassName = myClassName;
description.style = CS_DBLCLKS;
if (!::RegisterClassExW(&description))
{
throw std::runtime_error("::RegisterClassExW");
}
myWindow = ::CreateWindowExW(
WS_EX_APPWINDOW,
myClassName,
nullptr,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
nullptr,
nullptr,
myInstance,
reinterpret_cast<::LPVOID>(this));
if (myWindow == nullptr)
{
// Die Fensterklasse abmelden da der Destruktor nicht mehr aufgerufen wird.
::UnregisterClassW(myClassName, myInstance);
throw std::runtime_error("::CreateWindowExW");
}
}
/////////////////////////////////////////////////////////////////////////////
virtual ~Window()
{
::DestroyWindow(myWindow);
::UnregisterClassW(myClassName, myInstance);
}
/////////////////////////////////////////////////////////////////////////////
virtual bool MessageHandler(::UINT message, ::WPARAM firstParameter, ::LPARAM secondParameter)
{
return true;
}
private:
/////////////////////////////////////////////////////////////////////////////
static ::LRESULT CALLBACK WindowProcedure(::HWND window, ::UINT message, ::WPARAM firstParameter, ::LPARAM secondParameter)
{
if (message == WM_NCCREATE)
{
// Die beim erstellen des Fensters übergebene Fensterinstanz speichern
// damit deren Nachrichtenhandler aufgerufen werden kann.
::SetWindowLongPtrW(
window,
GWL_USERDATA,
reinterpret_cast<::LONG_PTR>(
reinterpret_cast<::LPCREATESTRUCTW>(secondParameter)->lpCreateParams));
}
else
{
auto const userWindow = reinterpret_cast<System::Window *>(::GetWindowLongPtrW(window, GWL_USERDATA));
// try {
// Den Nachrichtenhandler aufrufen.
if (userWindow && !userWindow->MessageHandler(message, firstParameter, secondParameter))
{
return 0;
}
// catch (...) {}
}
return ::DefWindowProcW(window, message, firstParameter, secondParameter);
}
};
}
}
//=================================================================================================
// SystemKeyboardHook.hpp/cpp
//=================================================================================================
namespace Tutorial
{
namespace System
{
class KeyboardHook : protected Utility::NonCopyAssignable
{
private:
::HINSTANCE const myInstance;
::HOOKPROC const myHookProcedure;
::HHOOK myHook;
public:
////////////////////////////////////////////////////////////////
KeyboardHook(::HINSTANCE const instance, ::HOOKPROC const hookProcedure) :
myInstance(instance),
myHookProcedure(hookProcedure),
myHook(nullptr) {}
////////////////////////////////////////////////////////////////
~KeyboardHook()
{
Enable(false);
}
////////////////////////////////////////////////////////////////
void Enable(bool enable)
{
if (enable)
{
if (myHook == nullptr)
{
myHook = ::SetWindowsHookExW(WH_KEYBOARD_LL, myHookProcedure, myInstance, 0);
}
}
else
{
if (myHook)
{
::UnhookWindowsHookEx(myHook);
myHook = nullptr;
}
}
}
////////////////////////////////////////////////////////////////
::LRESULT CallNextHook(int code, ::WPARAM firstParameter, ::LPARAM secondParameter) const
{
return ::CallNextHookEx(myHook, code, firstParameter, secondParameter);
}
};
}
}
//=================================================================================================
// SystemKeyboardHookListener.hpp/cpp
//=================================================================================================
namespace Tutorial
{
namespace System
{
class KeyboardHookListener : protected Utility::NonCopyAssignable
{
protected:
static KeyboardHookListener * mySelf;
KeyboardHook myKeyboardHook;
protected:
////////////////////////////////////////////////////////////////
explicit KeyboardHookListener(::HINSTANCE const instance) :
myKeyboardHook(instance, &LowLevelKeyboardProcedure)
{
// Nur eine Instanz zulassen.
assert(mySelf == nullptr);
mySelf = this;
}
////////////////////////////////////////////////////////////////
virtual ~KeyboardHookListener()
{
mySelf = nullptr;
}
////////////////////////////////////////////////////////////////
virtual bool KeystrokeHandler(bool alt, bool shift, bool control, ::DWORD key)
{
return true;
}
private:
////////////////////////////////////////////////////////////////
static ::LRESULT CALLBACK LowLevelKeyboardProcedure(int code, ::WPARAM firstParameter, ::LPARAM secondParameter)
{
if (code >= 0 && code == HC_ACTION)
{
auto const keyboardData = reinterpret_cast<::KBDLLHOOKSTRUCT *>(secondParameter);
auto const alt = (keyboardData->flags & LLKHF_ALTDOWN) != 0;
auto const shift = (::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
auto const control = (::GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
// try {
// Den Nachrichtenhandler aufrufen.
if (!mySelf->KeystrokeHandler(alt, shift, control, keyboardData->vkCode))
{
return 1;
}
// catch (...) {}
}
return mySelf->myKeyboardHook.CallNextHook(code, firstParameter, secondParameter);
}
};
// Statischen Member initialisieren.
KeyboardHookListener * KeyboardHookListener::mySelf = nullptr;
}
}
//=================================================================================================
// Application.hpp/cpp
//=================================================================================================
namespace Tutorial
{
class Application : protected System::Window, protected System::KeyboardHookListener
{
private:
bool myFullscreenState;
public:
////////////////////////////////////////////////////////////////
explicit Application(::HINSTANCE const instance) :
Window(instance, L"RenderWindow"),
KeyboardHookListener(instance),
myFullscreenState(false)
{
::SetWindowTextW(myWindow, L"Tutorial");
::ShowWindow(myWindow, SW_SHOWNORMAL);
::SetForegroundWindow(myWindow);
}
////////////////////////////////////////////////////////////////
void Update()
{
// ...
}
private:
////////////////////////////////////////////////////////////////
void FullscreenTaskSwitchHandler()
{
// ...
}
////////////////////////////////////////////////////////////////
bool MessageHandler(::UINT message, ::WPARAM firstParameter, ::LPARAM secondParameter)
{
switch (message)
{
case WM_CLOSE:
{
// Die Nachricht anschliessend nicht weiterverarbeiten da ansonsten ::DestroyWindow
// aufgerufen werden würde. Dies aber erledigt der Destruktor schon für uns.
::PostQuitMessage(0);
return false;
}
case WM_ACTIVATE:
{
// Den Tastaturhook nur aktivieren wenn das Fenster aktiv ist.
myKeyboardHook.Enable(firstParameter != WA_INACTIVE);
break;
}
}
return true;
}
////////////////////////////////////////////////////////////////
bool KeystrokeHandler(bool alt, bool shift, bool control, ::DWORD key)
{
// Verschiedene Tastenkombinationen verwerfen.
if ((alt && key == VK_ESCAPE) ||
(control && key == VK_ESCAPE) ||
(control && shift && key == VK_ESCAPE) ||
(key == VK_LWIN || key == VK_RWIN))
{
return false;
}
// Einen Vollbildmodustaskwechsel selber implementieren da DXGI intern nur den
// Fenstermodus wiederherstellt. Wir aber wollen ein minimiertes Fenster welches
// beim anklicken wieder in den Vollbildmodus wechselt.
if (alt && key == VK_TAB && myFullscreenState)
{
FullscreenTaskSwitchHandler();
return false;
}
return true;
}
};
}
//=================================================================================================
// WinMain.cpp
//=================================================================================================
int WINAPI WinMain(::HINSTANCE instance, ::HINSTANCE previousInstance, char * commandLine, int showCommand)
{
::MSG message = { 0 };
try
{
Tutorial::Application application(instance);
while (message.message != WM_QUIT)
{
// Alle verarbeiteten Nachrichten anschliessend entfernen.
if (::PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE))
{
::TranslateMessage(&message);
::DispatchMessageW(&message);
}
else
{
application.Update();
}
}
}
catch (std::exception const & exception)
{
::MessageBoxA(nullptr, exception.what(), "Tutorial Exception", MB_ICONERROR | MB_SETFOREGROUND);
}
return static_cast<int>(message.wParam);
}
da diese auf private Member der Tastaturhookklasse zugreiffen muss. Im Internet bin ich auf das Singleton-Idom gestossen, welches ich aber nicht als schön emfpinde da
eigentlich nichts gegen mehrere Tastaturhookinstanzen spricht. Also habe ich eine Tastaturhooklistenerklasse implementiert, welche nun einfach einen statischen Member
auf sich selber verwendet. Der Nachteil, auch von der Listenerklasse kann nur noch eine Instanz erstellt werden.
Könnt ihr mir einen Tipp geben, wie ich dies bei mehreren Fensters zum laufen bringen könnte?
Besten Dank