Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Providing a generic framework to Windows (MFC) applications to fetch custom properties from any GUI object

0.00/5 (No votes)
29 Apr 2010 1  
This is a framework that allows application developers or automation architects to expose custom properties on GUI objects.

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.

  1. 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....
  2. 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...

    //Need this to use the property map macros
    #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()
    /*
    Construct the TestableControl with contained instance 
    of the actual control. The handle of the dialog passed to super class 
    constructor be used to create the moniker.
    */
    CDialogTestableControl::CDialogTestableControl(CPropertyProxyTestDlg* dlg) : 
                            TestableControl(dlg->m_hWnd) {
       m_dlg = dlg;
    }
    // Destructor
    CDialogTestableControl::~CDialogTestableControl(){}
    /*
    This is the method assigned to "GetAdditionResult" property. 
    This uses the dialog pointer to call the GetAdditionResult method of the 
    dialog which return the addtion of the two numbers input thru the 
    two controls in the dialog, FirstNumber and SecondNumber respectively.
    */
    void CDialogTestableControl::GetAdditionResult(VARIANT * variant) {
       int r = m_dlg->GetAdditionResult();
       CString res;
       res.Format("%d",r);
       CComVariant(res).Detach(variant);
    }
    /*
    All the following methods can be ignored as of now as, we have used only 
    the GetAdditionResult property. You can add as many number of
    properties as you want is the intention behind providing the following methods.
    */
    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.

  3. Register CustomPropertyProxy.dll (TestableControl.dll should be in the same folder as that of the DLL).
  4. 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; 
// Get the caption/title of the window of a server.
m_ctrlWindowText.GetWindowText(strWindowText); 
// windows method to query for the window which can be used
// to get the handle of the server window which was made testable. 
CWnd * c = FindWindow(NULL,strWindowText); 
// Instantiate the property fetcher class, which hosts the method GetProperty
CCustomPropertyFetcher * ccpf = new CCustomPropertyFetcher(); 
VARIANT variant; 
VariantInit(&variant); 
const char * const strPropName = "GetAdditionResult"; 
// The property name for which we are required to verify the value
variant.bstrVal = CComBSTR(strPropName); 
variant.vt = VT_BSTR; 
// The property value will be returned thru variant.bstrVal
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here