C version of the UI Accessibility custom control demo master
authorPat Thoyts <patthoyts@users.sourceforge.net>
Fri, 4 Oct 2024 16:06:58 +0000 (17:06 +0100)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Fri, 4 Oct 2024 16:06:58 +0000 (17:06 +0100)
.editorconfig [new file with mode: 0644]
.gitignore [new file with mode: 0644]
ButtonProvider.c [new file with mode: 0644]
ButtonProvider.h [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
PolyButton.c [new file with mode: 0644]
PolyButton.h [new file with mode: 0644]
UIAutomationTypedefs.h [new file with mode: 0644]
main.c [new file with mode: 0644]
main.rc [new file with mode: 0644]
resource.h [new file with mode: 0644]

diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..05fb160
--- /dev/null
@@ -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 (file)
index 0000000..fcc9ffb
--- /dev/null
@@ -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 (file)
index 0000000..c2cfaf7
--- /dev/null
@@ -0,0 +1,384 @@
+#include "ButtonProvider.h"
+#include <stdio.h>
+#include <assert.h>
+
+#include "UIAutomationTypedefs.h"
+#include <UIAutomationCore.h>
+#include <UIAutomationClient.h>
+#include <UIAutomationCoreApi.h>
+
+#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 (file)
index 0000000..633cc38
--- /dev/null
@@ -0,0 +1,6 @@
+#include <Windows.h>
+#include <Ole2.h>
+
+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 (file)
index 0000000..5b1af0d
--- /dev/null
@@ -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 (file)
index 0000000..738d46b
--- /dev/null
@@ -0,0 +1,205 @@
+#include "PolyButton.h"
+#include "ButtonProvider.h"
+#include <stdlib.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+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 (file)
index 0000000..35d3633
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef _PolyButton_h_INCLUDE
+#define _PolyButton_h_INCLUDE
+
+#include <Windows.h>
+
+void PolyButton_RegisterControl(_In_ HINSTANCE hInstance);
+
+#endif // _PolyButton_h_INCLUDE
diff --git a/UIAutomationTypedefs.h b/UIAutomationTypedefs.h
new file mode 100644 (file)
index 0000000..3652b15
--- /dev/null
@@ -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 (file)
index 0000000..cb35f29
--- /dev/null
+++ b/main.c
@@ -0,0 +1,58 @@
+#include <Windows.h>
+#include <objbase.h>
+#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 (file)
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 <winres.h>
+
+/////////////////////////////////////////////////////////////////////////////
+#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 <winres.h>\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 (file)
index 0000000..17eda44
--- /dev/null
@@ -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