Introduction
We are unable to read properties other than identification properties from Windows GUI controls. No automation tool can read these properties unless we use any of their solutions to expose them. This particular framework can be used for any GUI control and have the client code written in any language that could work with Windows DLLs. Suppose you can use JNI to have the communication sent across to a Windows DLL to query for a property on a specific GUI object. I've provided the code for the proxy, testable control, property fetcher, sample server, and the client.
Every control that needs to expose some properties has to provide a corresponding testable control class that extends from TestableControl
(this is available in the TestableControl project). The control's testable control class then has to implement all the properties with methods which are mapped to properties using a property map. These property methods can query the embedded control (check the control's testable control constructor, which will take a parameter that is the pointer to the control) for any properties. These testable controls are called servers as they serve the custom code. The control's initialization should now contain a statement to create an object for the corresponding testable control. The instantiation of the testable control makes sure that this testable control's component (an instance of IPropertyProxy
) be created and associated with a moniker (a filter "**CustomPropertyProxy
**" and the server name -- in our case, we are considering the handle (HWND
) of the control to be passed to the testable control's constructor) in the RuntimeObjectTable
. At the client side, you need to pass the handle of the control for which you require a property value, along with the property name embedded in a variant, to the GetProperty
method of the CustomPropertyFetcher
class. The GetProperty
method of CustomPropertyFetcher
then constructs the moniker (filter and the handle of a control) and queries the RuntimeObjectTable
.
Background
We had a situation where the nature of our application is to have some properties painted (using the device context) on the screen rather than a GUI control, such as static text. We are using one of the commercial automation tools, and they have provided this kind of a solution. I'm developing an in-house automation tool, and the requirement has come to replace their solution with ours.
Using the Code
Server Code
Here is the flow that must be followed in order for a control to be made testable.
- You should have a testable control class corresponding to a control that must be made testable. As is in the example project (PropertyProxyTest), I have made the dialog
CPropertyProxyTestDlg
testable by creating a class CDialogTestableControl
. Following is the declaration....
class CDialogTestableControl : public TestableControl {
public:
CDialogTestableControl();
CDialogTestableControl(CPropertyProxyTestDlg* dlg);
~CDialogTestableControl();
void GetAdditionResult(VARIANT *);
void GetAdd(VARIANT* pszEnabled);
void GetFirstNum(VARIANT * first);
void GetSecondNum(VARIANT * second);
void SetFirstNum(VARIANT * first);
private:
CPropertyProxyTestDlg* m_dlg;
DECLARE_PROPERTY_MAP();
};
In the above code segment, the class CDialogTestableControl
extends from TestableControl
, which is part of the CustomProperty framework. Had you observed the constructor? It takes the pointer to CPropertyProxyTestDlg
, which is the control that must be made testable. Having a pointer means that we can access the behavior of this class through this pointer. We have five methods, GetAdditionResult
, GetAdd
, GetFirstNum
, GetSecondNum
, and SetFirstNum
, that relate to five different properties, which can be seen in the implementation of this class below. Also, all the methods take only a single parameter of type VARIANT*
. Check the below code snippet for the implementation for this class...
#pragma comment(lib, "TestableControl.lib")
BEGIN_PROPERTY_MAP (CDialogTestableControl, TestableControl)
ON_PROPERTY(_T("GetAdditionResult"), VT_I4, GetAdditionResult )
ON_PROPERTY(_T("Add"), VT_I4, GetAdd )
ON_PROPERTY(_T("First"), VT_I4, GetFirstNum )
ON_PROPERTY(_T("Second"), VT_I4, GetSecondNum )
ON_PROPERTY(_T("SetFirst"), VT_I4, SetFirstNum )
END_PROPERTY_MAP()
CDialogTestableControl::CDialogTestableControl(CPropertyProxyTestDlg* dlg) :
TestableControl(dlg->m_hWnd) {
m_dlg = dlg;
}
CDialogTestableControl::~CDialogTestableControl(){}
void CDialogTestableControl::GetAdditionResult(VARIANT * variant) {
int r = m_dlg->GetAdditionResult();
CString res;
res.Format("%d",r);
CComVariant(res).Detach(variant);
}
void CDialogTestableControl::GetAdd(VARIANT* pszEnabled) {
int r = m_dlg->GetAdditionResult();
CString res;
res.Format("%d",r);
pszEnabled->bstrVal = res.AllocSysString();
}
void CDialogTestableControl::GetFirstNum(VARIANT* first) {
int r = m_dlg->m_iFirstNumber;
CString res;
res.Format("%d",r);
CComVariant(r).Detach(first);
AfxMessageBox(res);
}
void CDialogTestableControl::SetFirstNum(VARIANT* first) {
m_dlg->m_iFirstNumber = first->intVal;
CString s;
s.Format("%d",first->intVal);
m_dlg->m_ctrlFirstNumber.SetWindowText(s);
m_dlg->GetDC()->TextOut(50, 50, s, s.GetLength());
}
void CDialogTestableControl::GetSecondNum(VARIANT* second){
int r = m_dlg->m_iSecondNumber;
CString res;
res.Format("%d",r);
second->bstrVal = res.AllocSysString();
}
We will be interested only in the method GetAdditionResult
(functionality of others not tested :)). We have the property map at the top that relates a property string to a property method. Also, we use the control's pointer (m_dlg
, in this case) to get the dialog's behavior, and is exposing it through the testable control.
- Register CustomPropertyProxy.dll (TestableControl.dll should be in the same folder as that of the DLL).
- Prepare the client code referencing the CustomPropertyFetcher library.
Client Code
The following is the client code that explains how we can query for a property real time (this can be found in the project ProxyClientTest).
CString strWindowText;
m_ctrlWindowText.GetWindowText(strWindowText);
CWnd * c = FindWindow(NULL,strWindowText);
CCustomPropertyFetcher * ccpf = new CCustomPropertyFetcher();
VARIANT variant;
VariantInit(&variant);
const char * const strPropName = "GetAdditionResult";
variant.bstrVal = CComBSTR(strPropName);
variant.vt = VT_BSTR;
int retCode = ccpf->GetProperty(c->m_hWnd, &variant);
CString res = variant.bstrVal;14) AfxMessageBox(res);
res.Format("%d", retCode);
AfxMessageBox(res);
Other parts of the project, such as the CustomPropertyProxy, TestableControl, and CustomPropertyFetcher, I leave it for the readers' self understanding.