mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-10-06 07:00:16 +08:00
Fix wxCheckBox and wxRadioButton accessibility in wxMSW dark mode
Previously these controls acted like generic buttons when they were owner-drawn which was, in particular, always the case when using dark mode, because the built-in implementation doesn't do anything useful for owner-drawn buttons. Implement IAccessible support for them ourselves in order to make it work in this case too. Closes #26184. Closes #26187.
This commit is contained in:
committed by
Vadim Zeitlin
parent
e43d57fd40
commit
b5ea9cfe86
@@ -58,6 +58,10 @@ public:
|
||||
// implementation only from now on
|
||||
virtual WXDWORD MSWGetStyle(long flags, WXDWORD *exstyle = nullptr) const override;
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
virtual wxAccessible* CreateAccessible() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual wxSize DoGetBestClientSize() const override;
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ public:
|
||||
return MSWDrawButton(item) || Base::MSWOnDraw(item);
|
||||
}
|
||||
|
||||
using wxMSWOwnerDrawnButtonBase::MSWIsOwnerDrawn;
|
||||
|
||||
protected:
|
||||
bool IsOwnerDrawn() const { return MSWIsOwnerDrawn(); }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: wx/msw/private/accessible.h
|
||||
// Purpose: Accessible helpers for wxMSW owner-drawn controls
|
||||
// Author: Quin Gillespie
|
||||
// Created: 2026-02-13
|
||||
// Copyright: (c) 2026 Quin Gillespie
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _WX_PRIVATE_ACCESSIBLE_H_
|
||||
#define _WX_PRIVATE_ACCESSIBLE_H_
|
||||
|
||||
#include "wx/access.h"
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
|
||||
// When controls such as wxCheckBox and wxRadioButton are switched to
|
||||
// owner-drawn mode (e.g. to support custom foreground colours in dark mode),
|
||||
// Windows replaces the native button style with BS_OWNERDRAW. The standard
|
||||
// IAccessible implementation that Windows provides via
|
||||
// CreateStdAccessibleObject() inspects the window style to determine the
|
||||
// control role, so it reports owner-drawn controls as generic buttons.
|
||||
//
|
||||
// This base class provides the correct role (specified as the template
|
||||
// parameter) and state (via virtual MSWGetCheckedState() function) for
|
||||
// owner-drawn controls.
|
||||
//
|
||||
// Note that it only takes effect when the control is actually owner-drawn;
|
||||
// otherwise it returns wxACC_NOT_IMPLEMENTED to fall back to the standard
|
||||
// Windows accessible.
|
||||
template <class TControl, wxAccRole Role>
|
||||
class wxOwnerDrawnAccessible : public wxWindowAccessible
|
||||
{
|
||||
public:
|
||||
explicit wxOwnerDrawnAccessible(TControl* win)
|
||||
: wxWindowAccessible(win)
|
||||
{
|
||||
}
|
||||
|
||||
virtual wxAccStatus GetRole(int childId, wxAccRole* role) override
|
||||
{
|
||||
if ( childId != wxACC_SELF )
|
||||
return wxACC_NOT_IMPLEMENTED;
|
||||
|
||||
TControl* const ctrl = wxStaticCast(GetWindow(), TControl);
|
||||
wxCHECK(ctrl, wxACC_FAIL);
|
||||
|
||||
if ( !ctrl->MSWIsOwnerDrawn() )
|
||||
return wxACC_NOT_IMPLEMENTED;
|
||||
|
||||
*role = Role;
|
||||
|
||||
return wxACC_OK;
|
||||
}
|
||||
|
||||
virtual wxAccStatus GetState(int childId, long* state) override
|
||||
{
|
||||
if ( childId != wxACC_SELF )
|
||||
return wxACC_NOT_IMPLEMENTED;
|
||||
|
||||
TControl* const ctrl = wxStaticCast(GetWindow(), TControl);
|
||||
wxCHECK(ctrl, wxACC_FAIL);
|
||||
|
||||
if ( !ctrl->MSWIsOwnerDrawn() )
|
||||
return wxACC_NOT_IMPLEMENTED;
|
||||
|
||||
long st = 0;
|
||||
if ( !ctrl->IsEnabled() )
|
||||
st |= wxACC_STATE_SYSTEM_UNAVAILABLE;
|
||||
if ( !ctrl->IsShown() )
|
||||
st |= wxACC_STATE_SYSTEM_INVISIBLE;
|
||||
if ( ctrl->IsFocusable() )
|
||||
st |= wxACC_STATE_SYSTEM_FOCUSABLE;
|
||||
if ( ctrl->HasFocus() )
|
||||
st |= wxACC_STATE_SYSTEM_FOCUSED;
|
||||
|
||||
st |= MSWGetCheckedState(ctrl);
|
||||
|
||||
*state = st;
|
||||
return wxACC_OK;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Return checked state flags (e.g. wxACC_STATE_SYSTEM_CHECKED) for the
|
||||
// current state of the control.
|
||||
virtual long MSWGetCheckedState(TControl* ctrl) const = 0;
|
||||
};
|
||||
|
||||
#endif // wxUSE_ACCESSIBILITY
|
||||
|
||||
#endif // _WX_PRIVATE_ACCESSIBLE_H_
|
||||
@@ -53,6 +53,10 @@ public:
|
||||
|
||||
virtual WXDWORD MSWGetStyle(long style, WXDWORD *exstyle) const override;
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
virtual wxAccessible* CreateAccessible() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual wxBorder GetDefaultBorder() const override { return wxBORDER_NONE; }
|
||||
virtual wxSize DoGetBestSize() const override;
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
#include "wx/private/window.h"
|
||||
#include "wx/msw/missing.h"
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
#include "wx/msw/private/accessible.h"
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// implementation
|
||||
// ============================================================================
|
||||
@@ -277,4 +281,52 @@ void wxCheckBox::MSWDrawButtonBitmap(wxDC& dc, const wxRect& rect, int flags)
|
||||
wxRendererNative::Get().DrawCheckBox(this, dc, rect, flags);
|
||||
}
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// When the checkbox is owner-drawn (e.g. in dark mode), the native BS_CHECKBOX
|
||||
// style is replaced with BS_OWNERDRAW, causing the standard Windows accessible
|
||||
// object to report the control as a generic button. This custom accessible
|
||||
// ensures the correct role and checked state are always reported.
|
||||
class wxCheckBoxAccessible
|
||||
: public wxOwnerDrawnAccessible<wxCheckBox, wxROLE_SYSTEM_CHECKBUTTON>
|
||||
{
|
||||
public:
|
||||
explicit wxCheckBoxAccessible(wxCheckBox* win)
|
||||
: wxOwnerDrawnAccessible(win)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
long MSWGetCheckedState(wxCheckBox* cb) const override
|
||||
{
|
||||
switch ( cb->Get3StateValue() )
|
||||
{
|
||||
case wxCHK_CHECKED:
|
||||
return wxACC_STATE_SYSTEM_CHECKED;
|
||||
|
||||
case wxCHK_UNDETERMINED:
|
||||
return wxACC_STATE_SYSTEM_MIXED;
|
||||
|
||||
case wxCHK_UNCHECKED:
|
||||
return 0;
|
||||
}
|
||||
|
||||
wxFAIL_MSG( wxT("unexpected Get3StateValue() return value") );
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
wxAccessible* wxCheckBox::CreateAccessible()
|
||||
{
|
||||
return new wxCheckBoxAccessible(this);
|
||||
}
|
||||
|
||||
#endif // wxUSE_ACCESSIBILITY
|
||||
|
||||
#endif // wxUSE_CHECKBOX
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
#include "wx/renderer.h"
|
||||
#include "wx/msw/uxtheme.h"
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
#include "wx/msw/private/accessible.h"
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// wxRadioButton implementation
|
||||
// ============================================================================
|
||||
@@ -347,4 +351,39 @@ void wxRadioButton::MSWDrawButtonBitmap(wxDC& dc, const wxRect& rect, int flags)
|
||||
wxRendererNative::Get().DrawRadioBitmap(this, dc, rect, flags);
|
||||
}
|
||||
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// When the radio button is owner-drawn (e.g. in dark mode), the native
|
||||
// BS_RADIOBUTTON style is replaced with BS_OWNERDRAW, causing the standard
|
||||
// Windows accessible object to report the control as a generic button. This
|
||||
// custom accessible ensures the correct role and checked state are always
|
||||
// reported.
|
||||
class wxRadioButtonAccessible :
|
||||
public wxOwnerDrawnAccessible<wxRadioButton, wxROLE_SYSTEM_RADIOBUTTON>
|
||||
{
|
||||
public:
|
||||
explicit wxRadioButtonAccessible(wxRadioButton* win)
|
||||
: wxOwnerDrawnAccessible(win)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
long MSWGetCheckedState(wxRadioButton* rb) const override
|
||||
{
|
||||
return rb->GetValue() ? wxACC_STATE_SYSTEM_CHECKED : 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
wxAccessible* wxRadioButton::CreateAccessible()
|
||||
{
|
||||
return new wxRadioButtonAccessible(this);
|
||||
}
|
||||
|
||||
#endif // wxUSE_ACCESSIBILITY
|
||||
|
||||
#endif // wxUSE_RADIOBTN
|
||||
|
||||
Reference in New Issue
Block a user