From cbb8c4e67d769284ffe8c604ceb7913af621e278 Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Fri, 4 Oct 2024 17:06:58 +0100 Subject: [PATCH 1/1] C version of the UI Accessibility custom control demo --- .editorconfig | 19 ++ .gitignore | 3 + ButtonProvider.c | 384 +++++++++++++++++++++++++++++++++++++++++ ButtonProvider.h | 6 + CMakeLists.txt | 19 ++ PolyButton.c | 205 ++++++++++++++++++++++ PolyButton.h | 8 + UIAutomationTypedefs.h | 43 +++++ main.c | 58 +++++++ main.rc | 165 ++++++++++++++++++ resource.h | 24 +++ 11 files changed, 934 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 ButtonProvider.c create mode 100644 ButtonProvider.h create mode 100644 CMakeLists.txt create mode 100644 PolyButton.c create mode 100644 PolyButton.h create mode 100644 UIAutomationTypedefs.h create mode 100644 main.c create mode 100644 main.rc create mode 100644 resource.h diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..05fb160 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +trim_trailing_whitespace = true +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +[*.{c,h,cxx,cpp}] +indent_size = 4 + +# Powershell files +[*.{ps1,psm1,psd1}] +indent_size = 4 + +# Xml based files +[*.{proj,xml,nuspec}] +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcc9ffb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +build/ +*.aps \ No newline at end of file diff --git a/ButtonProvider.c b/ButtonProvider.c new file mode 100644 index 0000000..c2cfaf7 --- /dev/null +++ b/ButtonProvider.c @@ -0,0 +1,384 @@ +#include "ButtonProvider.h" +#include +#include + +#include "UIAutomationTypedefs.h" +#include +#include +#include + +#pragma comment(lib, "uiautomationcore") + +#define BUTTONPROVIDER_MAGIC 0xFA1520BA + +static STDMETHODIMP IRawElementProviderSimple_QueryInterface(IRawElementProviderSimple *This, REFIID riid, void *ppvObject); +static STDMETHODIMP_(ULONG) IRawElementProviderSimple_AddRef(IRawElementProviderSimple *This); +static STDMETHODIMP_(ULONG) IRawElementProviderSimple_Release(IRawElementProviderSimple *This); +static STDMETHODIMP IRawElementProviderSimple_get_ProviderOptions(IRawElementProviderSimple *This, enum ProviderOptions *pRetVal); +static STDMETHODIMP IRawElementProviderSimple_GetPatternProvider(IRawElementProviderSimple *This, PATTERNID patternId, IUnknown **pRetVal); +static STDMETHODIMP IRawElementProviderSimple_GetPropertyValue(IRawElementProviderSimple *This, PROPERTYID propertyId, VARIANT *pVal); +static STDMETHODIMP IRawElementProviderSimple_get_HostRawElementProvider(IRawElementProviderSimple *This, IRawElementProviderSimple **pRetVal); + +static STDMETHODIMP IInvokeProvider_QueryInterface(IInvokeProvider *This, REFIID riid, void *ppvObject); +static STDMETHODIMP_(ULONG) IInvokeProvider_AddRef(IInvokeProvider *This); +static STDMETHODIMP_(ULONG) IInvokeProvider_Release(IInvokeProvider *This); +static STDMETHODIMP IInvokeProvider_Invoke(IInvokeProvider *This); + +static STDMETHODIMP IToggleProvider_QueryInterface(IToggleProvider *This, REFIID riid, void **ppvObject); +static STDMETHODIMP_(ULONG) IToggleProvider_AddRef(IToggleProvider *This); +static STDMETHODIMP_(ULONG) IToggleProvider_Release(IToggleProvider *This); +static STDMETHODIMP IToggleProvider_Toggle(IToggleProvider *This); +static STDMETHODIMP IToggleProvider_get_ToggleState(IToggleProvider *This, ToggleState *pVal); + +static IRawElementProviderSimpleVtbl vtbl = { + IRawElementProviderSimple_QueryInterface, + IRawElementProviderSimple_AddRef, + IRawElementProviderSimple_Release, + IRawElementProviderSimple_get_ProviderOptions, + IRawElementProviderSimple_GetPatternProvider, + IRawElementProviderSimple_GetPropertyValue, + IRawElementProviderSimple_get_HostRawElementProvider +}; + +static IInvokeProviderVtbl vtbl2 = { + IInvokeProvider_QueryInterface, + IInvokeProvider_AddRef, + IInvokeProvider_Release, + IInvokeProvider_Invoke +}; + +static IToggleProviderVtbl vtbl3 = { + IToggleProvider_QueryInterface, + IToggleProvider_AddRef, + IToggleProvider_Release, + IToggleProvider_Toggle, + IToggleProvider_get_ToggleState +}; + +typedef struct { + IRawElementProviderSimpleVtbl *lpVtbl; + IInvokeProviderVtbl *lpVtbl2; + IToggleProviderVtbl *lpVtbl3; + DWORD magic; + ULONG refcount[3]; + HWND m_hWnd; +} InstanceData; + +static void _cdecl TraceW(LPCWSTR format, ...) +{ + WCHAR buffer[512] = { 0 }; + va_list args; + va_start(args, format); + vswprintf_s(buffer, sizeof(buffer)/sizeof(WCHAR), format, args); + OutputDebugStringW(buffer); + va_end(args); +} + +#ifdef _DEBUG +#define TRACE TraceW +#else +#define TRACE 1?((void)0):TraceW +#endif + +// Check the reference count for _all_ interfaces which are managing their own +// individual counts and only delete the instance if the sum is 0. +static ULONG CheckInstance(InstanceData* instance) +{ + ULONG refcount = instance->refcount[0] + instance->refcount[1] + instance->refcount[2]; + if (refcount == 0) + { + TRACE(L"Destroy %p\n", instance); + free(instance); + } + return refcount; +} + +static STDMETHODIMP IRawElementProviderSimple_QueryInterface(IRawElementProviderSimple *This, REFIID riid, void **ppv) +{ + InstanceData *instance = (InstanceData *)This; + assert(instance->magic == BUTTONPROVIDER_MAGIC); + HRESULT hr = E_POINTER; + if (ppv) + { + *ppv = NULL; + hr = S_OK; + if (IsEqualIID(&IID_IUnknown, riid) || IsEqualIID(&IID_IRawElementProviderSimple, riid)) { + *ppv = &instance->lpVtbl; + instance->lpVtbl->AddRef((IRawElementProviderSimple*)(*ppv)); + } + else if (IsEqualIID(&IID_IInvokeProvider, riid)) { + *ppv = &instance->lpVtbl2; + instance->lpVtbl2->AddRef((IInvokeProvider*)(*ppv)); + } + else if (IsEqualIID(&IID_IToggleProvider, riid)) { + *ppv = &instance->lpVtbl3; + instance->lpVtbl3->AddRef((IToggleProvider*)(*ppv)); + } + else + hr = E_NOINTERFACE; + } + return hr; +} + +STDMETHODIMP_(ULONG) IRawElementProviderSimple_AddRef(IRawElementProviderSimple *This) +{ + InstanceData * instance = (InstanceData *)This; + assert(instance->magic == BUTTONPROVIDER_MAGIC); + ULONG result = InterlockedIncrement(&instance->refcount[0]); + TRACE(L"IRawElementProviderSimple AddRef %lu\n", result); + return result; +} + +STDMETHODIMP_(ULONG) IRawElementProviderSimple_Release(IRawElementProviderSimple *This) +{ + InstanceData *instance = (InstanceData *)This; + assert(instance->magic == BUTTONPROVIDER_MAGIC); + ULONG result = InterlockedDecrement(&instance->refcount[0]); + TRACE(L"IRawElementProviderSimple Release %lu\n", result); + return CheckInstance(instance); +} + +STDMETHODIMP IRawElementProviderSimple_get_ProviderOptions(IRawElementProviderSimple *This, enum ProviderOptions *pRetVal) +{ + InstanceData *instance = (InstanceData *)This; + assert(instance->magic == BUTTONPROVIDER_MAGIC); + HRESULT hr = E_POINTER; + if (pRetVal) + { + if (!IsWindow(instance->m_hWnd)) + hr = UIA_E_ELEMENTNOTAVAILABLE; + else + { + *pRetVal = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading; + hr = S_OK; + } + } + return hr; +} + +STDMETHODIMP IRawElementProviderSimple_GetPatternProvider(IRawElementProviderSimple *This, PATTERNID patternId, IUnknown **ppVal) +{ + InstanceData *instance = (InstanceData *)This; + assert(instance->magic == BUTTONPROVIDER_MAGIC); + HRESULT hr = E_POINTER; + if (ppVal) + { + hr = S_OK; + *ppVal = NULL; + if (patternId == UIA_InvokePatternId || patternId == UIA_TogglePatternId) + { + hr = instance->lpVtbl->QueryInterface(This, &IID_IUnknown, ppVal); + } + } + return hr; +} + +STDMETHODIMP IRawElementProviderSimple_GetPropertyValue(IRawElementProviderSimple *This, PROPERTYID propertyId, VARIANT *pVal) +{ + HRESULT hr = E_POINTER; + if (pVal) + { + hr = S_OK; + VariantInit(pVal); + pVal->vt = VT_EMPTY; + + InstanceData *instance = (InstanceData *)This; + assert(instance->magic == BUTTONPROVIDER_MAGIC); + if (propertyId == UIA_ControlTypePropertyId) + { + pVal->vt = VT_I4; + pVal->lVal = UIA_CheckBoxControlTypeId;// UIA_ButtonControlTypeId; + } + else if (propertyId == UIA_IsControlElementPropertyId) + { + pVal->vt = VT_BOOL; + pVal->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_ClassNamePropertyId) + { + WCHAR name[32] = { 0 }; + GetClassNameW(instance->m_hWnd, name, 32); + pVal->vt = VT_BSTR; + pVal->bstrVal = SysAllocString(name); + } + else if (propertyId == UIA_NamePropertyId) + { + int len = GetWindowTextLengthW(instance->m_hWnd) + 1; + WCHAR *name = (WCHAR *)malloc(sizeof(WCHAR) * len); + if (name != NULL) + { + GetWindowTextW(instance->m_hWnd, name, len); + pVal->vt = VT_BSTR; + pVal->bstrVal = SysAllocString(name); + free(name); + } + } + } + return hr; +} + +STDMETHODIMP IRawElementProviderSimple_get_HostRawElementProvider(IRawElementProviderSimple *This, IRawElementProviderSimple **ppVal) +{ + InstanceData *instance = (InstanceData *)This; + assert(instance->magic == BUTTONPROVIDER_MAGIC); + HRESULT hr = E_POINTER; + if (ppVal) + { + hr = UiaHostProviderFromHwnd(instance->m_hWnd, ppVal); + } + return hr; +} + +STDMETHODIMP IInvokeProvider_QueryInterface(IInvokeProvider *This, REFIID riid, void **ppvObject) +{ + InstanceData *instance = (InstanceData *)(This - 1); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + return instance->lpVtbl->QueryInterface((IRawElementProviderSimple *)instance, riid, ppvObject); +} + +STDMETHODIMP_(ULONG) IInvokeProvider_AddRef(IInvokeProvider *This) +{ + InstanceData *instance = (InstanceData *)(This - 1); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + ULONG result = InterlockedIncrement(&instance->refcount[1]); + TRACE(L"IInvokeProvider AddRef %lu\n", result); + return result; +} + +STDMETHODIMP_(ULONG) IInvokeProvider_Release(IInvokeProvider *This) +{ + InstanceData *instance = (InstanceData *)(This - 1); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + ULONG result = InterlockedDecrement(&instance->refcount[1]); + TRACE(L"IInvokeProvider Release %lu\n", result); + return CheckInstance(instance); +} + +STDMETHODIMP IInvokeProvider_Invoke(IInvokeProvider *This) +{ + InstanceData *instance = (InstanceData *)(This - 1); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + PostMessage(instance->m_hWnd, BM_CLICK, 0L, 0L); + return S_OK; +} + +STDMETHODIMP IToggleProvider_QueryInterface(IToggleProvider *This, REFIID riid, void **ppvObject) +{ + InstanceData *instance = (InstanceData *)(This - 2); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + return instance->lpVtbl->QueryInterface((IRawElementProviderSimple *)instance, riid, ppvObject); +} + +STDMETHODIMP_(ULONG) IToggleProvider_AddRef(IToggleProvider *This) +{ + InstanceData *instance = (InstanceData *)(This - 2); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + ULONG result = InterlockedIncrement(&instance->refcount[2]); + TRACE(L"IToggleProvider AddRef %lu\n", result); + return result; +} + +STDMETHODIMP_(ULONG) IToggleProvider_Release(IToggleProvider *This) +{ + InstanceData *instance = (InstanceData *)(This - 2); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + ULONG result = InterlockedDecrement(&instance->refcount[2]); + TRACE(L"IToggleProvider Release %lu\n", result); + return CheckInstance(instance); +} + +STDMETHODIMP IToggleProvider_Toggle(IToggleProvider *This) +{ + InstanceData *instance = (InstanceData *)(This - 2); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + HRESULT hr = UIA_E_ELEMENTNOTAVAILABLE; + if (IsWindow(instance->m_hWnd)) + { + PostMessage(instance->m_hWnd, BM_CLICK, 0L, 0L); + hr = S_OK; + } + return hr; +} + +STDMETHODIMP IToggleProvider_get_ToggleState(IToggleProvider *This, ToggleState *pVal) +{ + InstanceData *instance = (InstanceData *)(This - 2); + assert(instance->magic == BUTTONPROVIDER_MAGIC); + HRESULT hr = E_POINTER; + if (pVal != NULL) + { + if (!IsWindow(instance->m_hWnd)) + hr = UIA_E_ELEMENTNOTAVAILABLE; + else + { + int state = (int)SendMessage(instance->m_hWnd, BM_GETCHECK, 0L, 0L); + *pVal = (state == BST_CHECKED) ? ToggleState_On : ToggleState_Off; + hr = S_OK; + } + } + return hr; +} + +static HRESULT ButtonProvider_CreateInstance(HWND hwnd, IRawElementProviderSimple **ppProvider) +{ + HRESULT hr = E_POINTER; + if (ppProvider) + { + hr = E_OUTOFMEMORY; + InstanceData *instance = (InstanceData *)malloc(sizeof(InstanceData)); + if (instance) + { + instance->lpVtbl = &vtbl; + instance->lpVtbl2 = &vtbl2; + instance->lpVtbl3 = &vtbl3; + instance->magic = BUTTONPROVIDER_MAGIC; + instance->m_hWnd = hwnd; + ZeroMemory(instance->refcount, sizeof(instance->refcount)); + TRACE(L"CreateInstance %p\n", instance); + hr = instance->lpVtbl->QueryInterface((IRawElementProviderSimple *)instance, &IID_IRawElementProviderSimple, ppProvider); + } + } + return hr; +} + +LRESULT ButtonProvider_OnGetObject(_In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam, _Inout_ IUnknown **ppunk) +{ + LRESULT result = 0L; + if (UiaRootObjectId == lParam) + { + IRawElementProviderSimple *provider = NULL; + if (*ppunk == NULL) + { + ButtonProvider_CreateInstance(hwnd, &provider); + provider->lpVtbl->QueryInterface(provider, &IID_IUnknown, ppunk); + } + else + { + (*ppunk)->lpVtbl->QueryInterface(*ppunk, &IID_IRawElementProviderSimple, &provider); + } + + result = UiaReturnRawElementProvider(hwnd, wParam, lParam, provider); + provider->lpVtbl->Release(provider); + } + return result; +} + +LRESULT ButtonProvider_OnDestroy(_In_ HWND hwnd, _In_ IUnknown *punkProvider) +{ + UiaReturnRawElementProvider(hwnd, 0, 0, NULL); + UiaDisconnectProvider((IRawElementProviderSimple *)punkProvider); + return 0L; +} + +void ButtonProvider_OnInvoke(_In_opt_ IUnknown *punk) +{ + if (punk != NULL && UiaClientsAreListening()) + { + IRawElementProviderSimple *provider = NULL; + if (SUCCEEDED(punk->lpVtbl->QueryInterface(punk, &IID_IRawElementProviderSimple, &provider))) + { + UiaRaiseAutomationEvent(provider, UIA_Invoke_InvokedEventId); + provider->lpVtbl->Release(provider); + } + } +} \ No newline at end of file diff --git a/ButtonProvider.h b/ButtonProvider.h new file mode 100644 index 0000000..633cc38 --- /dev/null +++ b/ButtonProvider.h @@ -0,0 +1,6 @@ +#include +#include + +LRESULT ButtonProvider_OnGetObject(_In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam, _Inout_ IUnknown **ppunk); +LRESULT ButtonProvider_OnDestroy(_In_ HWND hwnd, _In_ IUnknown *punkProvider); +void ButtonProvider_OnInvoke(_In_opt_ IUnknown *provider); diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5b1af0d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +# CMAKE_BUILD_TYPE has no effect on the Visual Studio generator. +# cmake -G"Visual Studio 15 2017 Win64" .. +# cmake --build . --config Release +# +# For LLVM / CLang build: +# cmake -G"Visual Studio 15 2017 Win64" -T LLVM .. +# cmake --build . --config Release + +cmake_minimum_required(VERSION 3.26) + +project(UIADemo VERSION 1.0 LANGUAGES C RC) + +set(SOURCE main.c main.rc resource.h + PolyButton.c PolyButton.h + ButtonProvider.c ButtonProvider.h + UIAutomationTypedefs.h) +set(CMAKE_C_STANDARD 11) +add_definitions(-DUNICODE -D_UNICODE -DSTRICT -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=_WIN32_WINNT_WIN7) +add_executable(${PROJECT_NAME} WIN32 ${SOURCE}) diff --git a/PolyButton.c b/PolyButton.c new file mode 100644 index 0000000..738d46b --- /dev/null +++ b/PolyButton.c @@ -0,0 +1,205 @@ +#include "PolyButton.h" +#include "ButtonProvider.h" +#include +#define _USE_MATH_DEFINES +#include + +typedef enum { + PolyButton_Pressed = (1 << 0), + PolyButton_Checked = (1 << 1), + PolyButton_Disabled = (1 << 2), + PolyButton_Hot = (1 << 3), + PolyButton_Focussed = (1 << 4), + PolyButton_Tracking = (1 << 5) +} PolyButtonFlags; + +typedef struct { + UINT sides; // number of sides + COLORREF checkedColor; + COLORREF normalColor; + COLORREF activeColor; + PolyButtonFlags flags; + TRACKMOUSEEVENT track; + POINT* points; + IUnknown *provider; +} PolyButtonData; + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT messageId, WPARAM wparam, LPARAM lparam); + +void PolyButton_RegisterControl(_In_ HINSTANCE hInstance) +{ + WNDCLASS wc = { 0 }; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.hInstance = hInstance; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.lpszClassName = L"POLYBUTTON"; + RegisterClass(&wc); +} + +static void EnableMouseTracking(HWND hwnd, PolyButtonData *data) +{ + data->track.cbSize = sizeof(TRACKMOUSEEVENT); + data->track.dwFlags = TME_LEAVE; + data->track.dwHoverTime = 0L; + data->track.hwndTrack = hwnd; + TrackMouseEvent(&data->track); + data->flags |= PolyButton_Tracking; +} + +static void CalculatePoints(RECT rc, int sides, POINT *points) +{ + POINT center = { (rc.right - rc.left) / 2 + rc.left, (rc.bottom - rc.top) / 2 + rc.top}; + SIZE radius = { (rc.right - rc.left) / 2, (rc.bottom - rc.top) / 2 }; + double angle = 3 * M_PI_2; + double delta = 2 * M_PI / sides; + for (int n = 0; n < sides; ++n, angle += delta) + { + points[n].x = (int)(radius.cx * cos(angle) + center.x + 0.5); + points[n].y = (int)(radius.cy * sin(angle) + center.y + 0.5); + } +} + +static void OnSize(HWND hwnd, UINT flag, SIZE size, PolyButtonData* data) +{ + if (data->points) + free(data->points); + data->points = (POINT *)calloc(data->sides, sizeof(POINT)); + if (data->points != NULL) + { + RECT rc = { 1, 1, size.cx - 2, size.cy - 2 }; + CalculatePoints(rc, data->sides, data->points); + } +} + +static LRESULT OnPaint(HWND hwnd, PolyButtonData *data) +{ + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + HDC hdc = (ps.hdc != NULL) ? ps.hdc : GetWindowDC(hwnd); + RECT rc = ps.rcPaint; + if (data->points) + { + COLORREF color = data->normalColor; + if (!IsWindowEnabled(hwnd)) + color = GetSysColor(COLOR_INACTIVEBORDER); + else if (data->flags & PolyButton_Checked) + color = data->checkedColor; + //else if (data->flags & PolyButton_Hot) + // color = data->activeColor; + COLORREF border = GetSysColor(COLOR_ACTIVEBORDER); + if ((data->flags & PolyButton_Hot)) + border = RGB(0x00, 0xFF, 0xFF); + HPEN borderPen = CreatePen(PS_SOLID, 1, border); + HBRUSH brush = CreateSolidBrush(color); + HGDIOBJ oldPen = SelectObject(hdc, borderPen); + HGDIOBJ oldBrush = SelectObject(hdc, (HGDIOBJ)brush); + Polygon(hdc, data->points, data->sides); + Polyline(hdc, data->points, data->sides); + if ((data->flags & PolyButton_Focussed) == PolyButton_Focussed) + { + HPEN focusPen = CreatePen(PS_DOT, 1, RGB(0, 0, 0)); + HGDIOBJ prevPen = SelectObject(hdc, (HGDIOBJ)focusPen); + POINT *points = (POINT*)calloc(data->sides, sizeof(POINT)); + if (points != NULL) + { + CalculatePoints(rc, data->sides, points); + Polyline(hdc, points, data->sides); + free(points); + } + SelectObject(hdc, prevPen); + DeleteObject(focusPen); + } + SelectObject(hdc, oldPen); + SelectObject(hdc, oldBrush); + DeleteObject(brush); + DeleteObject(borderPen); + } + EndPaint(hwnd, &ps); + return 1L; +} + +LRESULT OnInvoke(HWND hwnd, PolyButtonData *data) +{ + data->flags ^= PolyButton_Checked; + ButtonProvider_OnInvoke(data->provider); + InvalidateRect(hwnd, NULL, TRUE); + return 1L; +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT messageId, WPARAM wParam, LPARAM lParam) +{ + PolyButtonData *data = (PolyButtonData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (messageId) + { + case WM_CREATE: + data = (PolyButtonData *)calloc(1, sizeof(PolyButtonData)); + if (data == NULL) + abort(); + else + { + data->activeColor = RGB(0xFF, 0x80, 0x80); + data->checkedColor = RGB(0, 0xFF, 0); + data->normalColor = RGB(0xFF, 0xFF, 0xFF); + data->sides = 6; + data->points = NULL; + data->provider = NULL; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)data); + } + break; + case WM_GETOBJECT: + return ButtonProvider_OnGetObject(hwnd, wParam, lParam, &data->provider); + case WM_DESTROY: + if (data->provider) + { + ButtonProvider_OnDestroy(hwnd, data->provider); + data->provider->lpVtbl->Release(data->provider); + data->provider = NULL; + } + free(data); + break; + case WM_SIZE: + { + SIZE size = { LOWORD(lParam), HIWORD(lParam) }; + OnSize(hwnd, (UINT)wParam, size, data); + return 1L; + } + case WM_PAINT: + return OnPaint(hwnd, data); + case WM_SETFOCUS: + data->flags |= PolyButton_Focussed; + InvalidateRect(hwnd, NULL, TRUE); + break; + case WM_KILLFOCUS: + data->flags &= ~PolyButton_Focussed; + InvalidateRect(hwnd, NULL, TRUE); + break; + case WM_ENABLE: + InvalidateRect(hwnd, NULL, TRUE); + break; + case WM_MOUSEMOVE: + if (!(data->flags & PolyButton_Tracking)) + { + data->flags |= PolyButton_Hot; + EnableMouseTracking(hwnd, data); + InvalidateRect(hwnd, NULL, TRUE); + } + break; + case WM_MOUSELEAVE: + data->flags &= ~(PolyButton_Hot | PolyButton_Tracking); + InvalidateRect(hwnd, NULL, TRUE); + break; + case BM_GETCHECK: + return (data->flags & PolyButton_Checked) ? BST_CHECKED : BST_UNCHECKED; + case BM_SETCHECK: + if (wParam == BST_CHECKED) + data->flags |= PolyButton_Checked; + else + data->flags &= ~PolyButton_Checked; + return 0L; + case WM_LBUTTONDOWN: + case BM_CLICK: + return OnInvoke(hwnd, data); + } + return DefWindowProc(hwnd, messageId, wParam, lParam); +} diff --git a/PolyButton.h b/PolyButton.h new file mode 100644 index 0000000..35d3633 --- /dev/null +++ b/PolyButton.h @@ -0,0 +1,8 @@ +#ifndef _PolyButton_h_INCLUDE +#define _PolyButton_h_INCLUDE + +#include + +void PolyButton_RegisterControl(_In_ HINSTANCE hInstance); + +#endif // _PolyButton_h_INCLUDE diff --git a/UIAutomationTypedefs.h b/UIAutomationTypedefs.h new file mode 100644 index 0000000..3652b15 --- /dev/null +++ b/UIAutomationTypedefs.h @@ -0,0 +1,43 @@ +// declare missing typedefs for C import of UIA interfaces +// +// This file should be included _before_ the UIAutomation headers +// when using the UIAutomationCode headers in C programs. + +#pragma once + +typedef enum ConditionType ConditionType; +typedef enum PropertyConditionFlags PropertyConditionFlags; +typedef enum AutomationElementMode AutomationElementMode; +typedef enum TreeScope TreeScope; +typedef enum NavigateDirection NavigateDirection; +typedef enum NormalizeState NormalizeState; +typedef enum TreeTraversalOptions TreeTraversalOptions; +typedef enum ProviderType ProviderType; +typedef enum AutomationIdentifierType AutomationIdentifierType; +typedef enum EventArgsType EventArgsType; +typedef enum AsyncContentLoadedState AsyncContentLoadedState; +typedef enum StructureChangeType StructureChangeType; +typedef enum TextEditChangeType TextEditChangeType; +typedef enum NotificationKind NotificationKind; +typedef enum NotificationProcessing NotificationProcessing; +typedef enum DockPosition DockPosition; +typedef enum ScrollAmount ScrollAmount; +typedef enum WindowVisualState WindowVisualState; +typedef enum SupportedTextSelection SupportedTextSelection; +typedef enum TextPatternRangeEndpoint TextPatternRangeEndpoint; +typedef enum TextUnit TextUnit; +typedef enum TextPatternRangeEndpoint TextPatternRangeEndpoint; +typedef enum SynchronizedInputType SynchronizedInputType; +typedef enum ToggleState ToggleState; + +typedef struct UiaCondition UiaCondition; +typedef struct UiaPropertyCondition UiaPropertyCondition; +typedef struct UiaAndOrCondition UiaAndOrCondition; +typedef struct UiaNotCondition UiaNotCondition; +typedef struct UiaCacheRequest UiaCacheRequest; +typedef struct UiaFindParams UiaFindParams; +typedef struct UiaEventArgs UiaEventArgs; +typedef struct UiaPropertyChangedEventArgs UiaPropertyChangedEventArgs; +typedef struct UiaStructureChangedEventArgs UiaStructureChangedEventArgs; +typedef struct UiaChangeInfo UiaChangeInfo; +typedef struct UiaPoint UiaPoint; diff --git a/main.c b/main.c new file mode 100644 index 0000000..cb35f29 --- /dev/null +++ b/main.c @@ -0,0 +1,58 @@ +#include +#include +#include "PolyButton.h" +#include "resource.h" + +//#pragma comment(lib, "uiautomationcore") +#pragma comment(lib, "ole32") +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + + +LRESULT OnCommand(HWND hwnd, int code, UINT nID, HWND hwndCtrl) +{ + switch (nID) + { + case IDOK: + case IDCANCEL: + EndDialog(hwnd, nID); + return TRUE; + case IDC_CUSTOM_ENABLE: + { + HWND custom = GetDlgItem(hwnd, IDC_CUSTOM1); + EnableWindow(custom, (IsDlgButtonChecked(hwnd, IDC_CUSTOM_ENABLE) == BST_CHECKED)); + InvalidateRect(custom, NULL, TRUE); + return TRUE; + } + case IDC_CUSTOM_PRESSED: + // UINT SendMessage(custom, PBM_GETSTATE, 0L, 0L); + // SendMessage(custom, PBM_SETSTATE, (WPARAM)state, 0L); + return TRUE; + } + return FALSE; +} + +static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + CheckDlgButton(hDlg, IDC_CUSTOM_ENABLE, + IsWindowEnabled(GetDlgItem(hDlg, IDC_CUSTOM_ENABLE)) ? BST_CHECKED : BST_UNCHECKED); + break; + case WM_COMMAND: + return OnCommand(hDlg, (int)HIWORD(wParam), (UINT)LOWORD(wParam), (HWND)lParam); + } + return FALSE; +} + +int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) +{ + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (SUCCEEDED(hr)) + { + PolyButton_RegisterControl(hInstance); + DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc); + CoUninitialize(); + } + return SUCCEEDED(hr) ? ERROR_SUCCESS : 1; +} \ No newline at end of file diff --git a/main.rc b/main.rc new file mode 100644 index 0000000..bf59131 --- /dev/null +++ b/main.rc @@ -0,0 +1,165 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Direct2D sample application" + VALUE "CompanyName", "Renishaw plc." + VALUE "FileDescription", "Direct2D sample application" + VALUE "FileVersion", "1, 0, 0, 0" + VALUE "InternalName", "D2DDemo" + VALUE "LegalCopyright", "Copyright (c) 2020 Renishaw plc." + VALUE "OriginalFilename", "D2DDemo.exe" + VALUE "ProductVersion", "1, 0, 0, 0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (United Kingdom) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include \r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOGEX 0, 0, 309, 176 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "UIA Custom Control Demo" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,176,7,112,10 + GROUPBOX "Static",IDC_STATIC,176,18,105,38 + CONTROL "Option 1",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,184,28,91,10 + CONTROL "Option 2",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,184,42,92,10 + LTEXT "Label",IDC_STATIC,176,62,18,8 + EDITTEXT IDC_EDIT1,202,59,100,14,ES_AUTOHSCROLL + CONTROL "Hello",IDC_CUSTOM1,"POLYBUTTON",WS_TABSTOP,29,26,47,37 + DEFPUSHBUTTON "OK",IDOK,198,155,50,14 + PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 + GROUPBOX "Custom control",IDC_STATIC,7,7,95,120 + CONTROL "Enabled",IDC_CUSTOM_ENABLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,73,82,10 + CONTROL "Pressed",IDC_CUSTOM_PRESSED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,87,82,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 302 + TOPMARGIN, 7 + BOTTOMMARGIN, 169 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_DIALOG1 AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (United Kingdom) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..17eda44 --- /dev/null +++ b/resource.h @@ -0,0 +1,24 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by main.rc +// +#define IDD_DIALOG1 101 +#define IDC_CHECK1 102 +#define IDC_RADIO1 103 +#define IDC_RADIO2 104 +#define IDC_EDIT1 105 +#define IDC_CUSTOM1 106 +#define IDC_CUSTOM_ENABLE 1005 +#define IDC_CUSTOM_ENABLE2 1006 +#define IDC_CUSTOM_PRESSED 1006 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif -- 2.23.0