Professional UI Solutions
Site Map   /  Register
 
 

Forum

Please Log In to post a new message or reply to an existing one. If you are not registered, please register.

NOTE: Some forums may be read-only if you are not currently subscribed to our technical support services.

Forums » Prof-UIS Tech Support » Problem with CExtGridCell ellipsis button Collapse All
Subject Author Date
Krustys Donuts Sep 6, 2005 - 5:21 PM

I have subclassed CExtGridCellString and now have my own class that has the __EBCS_BUTTON_ELLIPSIS style bit set. I’ve also overridden the OnButtonPressed method.

My problem now is that when I click on the button, the object whose OnButtonPressed method gets called is actually different than the one that I had initially created!

My creation code basically looks like this:

pValue = new CExtPropertyValue( property.name.c_str());
pData = STATIC_DOWNCAST( LabwareEllipsisCell, pValue->ValueActiveGetByRTC( RUNTIME_CLASS(LabwareEllipsisCell)));
// just a test
EllipsisCell* pEllipsisCell = dynamic_cast<LabwareEllipsisCell*>(pData);
if( pEllipsisCell)
  pEllipsisCell->setDonut( donut);


The program crashes when the OnButtonPressed method executes, because the donut variable is invalid (null). This is because the EllipsisCell object is actually different than the one created in the code above! Can you please explain why this is the case? The only reason why I have the setDonut() method is because there doesn’t seem to be a way to pass extra arguments to the EllipsisCell constructor when using the MFC macros. Am I wrong? Can you propose an alternate solution?

Thank you!

Technical Support Sep 7, 2005 - 5:16 AM

It seems you forgot to insert the line below into the declaration of your LabwareEllipsisCell class:

IMPLEMENT_ExtGridCell_Clone( LabwareEllipsisCell, CExtGridCellString );

Krustys Donuts Sep 7, 2005 - 6:42 AM

Hi, actually I do have it in the declaration of my EllipsisCell class, I just didn’t paste the declaration of the class into the code above...

Krustys Donuts Sep 7, 2005 - 11:27 AM

The problem was that I didn’t implement Assign. I’m not an old-school MFC programmer, so it had never occurred to me in the first place that I’d have to write the Assign code for the cell objects that get dynamically created.

Technical Support Sep 7, 2005 - 12:44 PM

The Assign() method should certainly assign data members like numeric or string values. You should put pointers to interfaces like IDonut* in your CExtPropertyValue classes which are outside the property grid window. The CExtPropertyValue::Apply() virtual method is invoked when the property grid’s cell is changed so you can invoke IDonut’s methods. You can see how the property value classes keep properties for star buttons in the PropertyGrid sample application, which is similar to your design.

Krustys Donuts Sep 7, 2005 - 3:59 PM

I have another somewhat related question. When I combine stores, values like checkboxes, strings, and numbers seem to change correctly.

For example, I have two stores with a checkbox cell named "has sprinkles" (defaults to unchecked). If I then select both stores (which internally calls Combine) and check the checkbox, once I select each store individually, the checkbox is still checked. This is perfect.

However, I also have a grid cell that, when checked, pops up a dialog box that allows me to modify properties of a particular member of the grid cell. If I select both stores again and then modify the properties of that object, it only does so for one of them, not both. I would expect this because there isn’t a way for Prof-UIS to know to update the parameters of the other object. So I was just wondering if there’s some mechanism in Prof-UIS to allow this to happen. Sorry if this example is confusing...

Technical Support Sep 8, 2005 - 10:57 AM

Thank you for the good comment. The CExtGridCell paints its check box in the CExtGridCell::OnPaintCheck() method. It supports the indeterminate check box state with the __EGCS_CHK_INDETERMINATE cell style. But property store’s mixing algorithm does not use this style at all. It uses a more universal __EGCS_EX_UNDEFINED_ROLE extended cell style which affects not only the check box but also blank cells for text and other cells, "?" character rendering in the icon area of the color cell and etc. Please use this style for detecting object property differences. Besides, we have improved the CExtGridCell::OnPaintCheck() method in the ExtGridWnd.cpp file and now it paints the indeterminate check box if the __EGCS_EX_UNDEFINED_ROLE extended cell style is applied:

void CExtGridCell::OnPaintCheck(
    const RECT & rcCheck,
    const CExtGridWnd & wndGrid,
    CDC & dc,
    LONG nVisibleColNo,
    LONG nVisibleRowNo,
    LONG nColNo,
    LONG nRowNo,
    INT nColType,
    INT nRowType,
    const RECT & rcCellExtra,
    const RECT & rcCell,
    const RECT & rcVisibleRange,
    DWORD dwAreaFlags,
    DWORD dwHelperPaintFlags
    ) const
{
    ASSERT_VALID( this );
    ASSERT_VALID( (&wndGrid) );
    ASSERT( dc.GetSafeHdc() != NULL );
    if( ! dc.RectVisible(&rcCheck) )
        return;
    rcCellExtra;
    rcCell;
    rcVisibleRange;
DWORD dwCellStyle = GetStyle();
    if( (dwCellStyle&__EGCS_CHK_MASK) == __EGCS_CHK_NONE )
        return;
bool bReadOnly = 
    ( (dwCellStyle&__EGCS_READ_ONLY) != 0 )
        ? true : false;
CSize szCheck = 
        OnCalcCheckSize(
            wndGrid,
            dc,
            nVisibleColNo,
            nVisibleRowNo,
            nColNo,
            nRowNo,
            nColType,
            nRowType,
            dwAreaFlags,
            dwHelperPaintFlags
            );
    ASSERT( szCheck.cx >= 0 && szCheck.cy >= 0 );
DWORD dwCellStyleEx = GetStyleEx();
DWORD dwStylePart = dwCellStyle & __EGCS_CHK_MASK;
bool bIndeterminate = 
        ( (dwCellStyle&__EGCS_CHK_MASK) ==
                __EGCS_CHK_INDETERMINATE )
            ? true : false;
bool bChecked =
        ( (dwCellStyle&__EGCS_CHECKED) != 0 )
            ? true : false;
    if( (dwCellStyleEx&__EGCS_EX_UNDEFINED_ROLE) != 0 )
    {
        dwStylePart = __EGCS_CHK_INDETERMINATE;
        bIndeterminate = true;
        bChecked = true;
    }
bool bDefaultDrawing = true;
    if(        g_PaintManager.m_UxTheme.IsControlsThemed()
        &&    g_PaintManager.m_UxTheme.OpenThemeData(
                wndGrid.GetSafeHwnd(),
                L"BUTTON"
                ) != NULL
        )
    {
        INT nState = 0;
        INT nPartID = 0;
        switch( dwStylePart )
        {
        case __EGCS_CHK_CHECK:
        case __EGCS_CHK_INDETERMINATE:
        {
            nPartID = BP_CHECKBOX;
            if( bChecked )
            {
                nState = 
                    bReadOnly 
                        ? ( bIndeterminate
                            ? CBS_MIXEDDISABLED
                            : CBS_CHECKEDDISABLED
                            ) 
                        : ( bIndeterminate
                            ? CBS_MIXEDNORMAL
                            : CBS_CHECKEDNORMAL
                            );
            }
            else
            {
                nState = 
                    bReadOnly 
                        ? CBS_UNCHECKEDDISABLED 
                        : CBS_UNCHECKEDNORMAL;
            }
        }
        break;
        case __EGCS_CHK_RADIO:
        {
            nPartID = BP_RADIOBUTTON;
            if( bChecked )
            {
                nState = 
                    bReadOnly 
                        ? RBS_CHECKEDDISABLED
                        : RBS_CHECKEDNORMAL;
            }
            else
            {
                nState = 
                    bReadOnly 
                        ? RBS_UNCHECKEDDISABLED 
                        : RBS_UNCHECKEDNORMAL;
            }
        }
        break;
        default:
            ASSERT( FALSE );
        return;
        } // switch( dwStylePart )
        CRect rcPaint( rcCheck );
        rcPaint.SetRect(
            rcPaint.left,
            rcPaint.top,
            rcPaint.left + szCheck.cx,
            rcPaint.top + szCheck.cy
            );
        if( g_PaintManager.m_UxTheme.
                DrawThemeBackground(
                    dc.GetSafeHdc(), 
                    nPartID, 
                    nState, 
                    &rcPaint, 
                    &rcCell
                    ) == S_OK
            )
            bDefaultDrawing = false;
        g_PaintManager.m_UxTheme.
            CloseThemeData( true );
    }
    if( bDefaultDrawing )
    {
        UINT nState = DFCS_FLAT;
        switch( dwStylePart )
        {
        case __EGCS_CHK_CHECK:
            nState |= DFCS_BUTTONCHECK;
        break;
        case __EGCS_CHK_RADIO:
            nState |= DFCS_BUTTONRADIO;
        break;
        case __EGCS_CHK_INDETERMINATE:
            nState |=
                DFCS_BUTTONCHECK
                |DFCS_BUTTON3STATE
                ;
        break;
#ifdef _DEBUG
        default:
            ASSERT( FALSE );
        break;
#endif // _DEBUG
        }; // switch( dwStylePart )
        if( bChecked )
            nState |= DFCS_CHECKED;
        if( bReadOnly )
            nState |= DFCS_INACTIVE;
        CRect rcPaint( rcCheck );
        rcPaint.SetRect(
            rcPaint.left,
            rcPaint.top,
            rcPaint.left + szCheck.cx,
            rcPaint.top + szCheck.cy
            );
        dc.DrawFrameControl(
            (RECT *)&rcPaint,
            DFC_BUTTON,
            nState
            );
    } // if( bDefaultDrawing )
}

Krustys Donuts Sep 7, 2005 - 3:46 PM

Could you please explain why you recommend that interface pointers be stored in the CExtPropertyValue object? Currently, we just use Assign to copy the pointer to the cell copy, and it seems to work okay at the moment.

Technical Support Sep 8, 2005 - 9:39 AM

In fact, you can keep interface pointers in grid cells and this design will always work OK in property grid windows. But generally, the CExtGridCell class is designed to be a cloneable and serializable object for keeping only data values. The grid cell serialization can be used for copy, paste and drag-and-drop between processes.

Krustys Donuts Sep 6, 2005 - 6:55 PM

I have a little more information from my recent debugging session. It seems that the problem stems from my calling PropertyStoreSynchronize(). I call this method every time someone clicks on a tree control. However, when PropertyStoreSychronize is invoked, it ends up creating another instance of the grid cell. I’m not sure why it would do this, as I have already explicitly created the cell (please see previous post).

The call stack when the user clicks on a tree item looks like this:

krusty.dll!EllipsisCell::EllipsisCell(CExtGridDataProvider * pDataProvider=0x00000000)  Line 16	C++
krusty.dll!EllipsisCell::CreateObject()  Line 13 + 0x6a	C++
mfc71d.dll!CRuntimeClass::CreateObject()  Line 137 + 0x6	C++
ProfUIS242md.dll!CExtGridCell::InstanceCreate(CRuntimeClass * pRTC=0x01128a00, IMalloc * pMalloc=0x01f95510, bool bThrowException=false)  Line 13505 + 0x8	C++
ProfUIS242md.dll!CExtGridDataProviderMemory::CellGet(unsigned long nColNo=2, unsigned long nRowNo=3, CRuntimeClass * pInitRTC=0x01128a00, bool bAutoFindValue=true, bool bUseColumnDefaultValue=true)  Line 30983 + 0x12	C++
ProfUIS242md.dll!CExtTreeGridDataProvider::ExtractGridCell(CExtTreeGridCellNode * pNode=0x01f959b0, unsigned long nColNo=2, CRuntimeClass * pInitRTC=0x01128a00, bool bAutoFindValue=true, bool bUseColumnDefaultValue=true)  Line 1319 + 0x21	C++
ProfUIS242md.dll!CExtTreeGridWnd::ItemGetCell(_TREEITEM * hTreeItem=0x01f959b0, long nColNo=1, int nColType=0, CRuntimeClass * pInitRTC=0x01128a00, bool bAutoFindValue=true, bool bUseColumnDefaultValue=true)  Line 1813 + 0x38	C++
ProfUIS242md.dll!CExtPropertyGridWnd::PropertyItemInsert(CExtPropertyItem * pPropertyItem=0x01fb4d78, long nIndex=-1, _TREEITEM * htiParent=0x01f9594c, bool bRedraw=false)  Line 3477 + 0x18	C++
ProfUIS242md.dll!CExtPropertyGridWnd::PropertyStoreSynchronizeOneLevel(CExtPropertyItem * pPropertyItem=0x01fb4d78, CExtPropertyItem * pParentItem=0x01f8dc40)  Line 3390 + 0x1c	C++
ProfUIS242md.dll!CExtPropertyGridWnd::PropertyStoreSynchronizeOneLevel(CExtPropertyItem * pPropertyItem=0x01f8dc40, CExtPropertyItem * pParentItem=0x01f92ff0)  Line 3406 + 0x18	C++
ProfUIS242md.dll!CExtPropertyGridWnd::PropertyStoreSynchronizeOneLevel(CExtPropertyItem * pPropertyItem=0x01f92ff0, CExtPropertyItem * pParentItem=0x00000000)  Line 3406 + 0x18	C++
ProfUIS242md.dll!CExtPropertyGridWnd::PropertyStoreSynchronizeAll()  Line 3314 + 0x16	C++
ProfUIS242md.dll!CExtPropertyGridCtrl::PropertyStoreSynchronize()  Line 5632 + 0x10	C++
krusty.dll!CDlgTree::OnClickTree(tagNMHDR * pNMHDR=0x0012f6a8, long * pResult=0x0012f09c)  Line 147 + 0x24	C++

Technical Support Sep 7, 2005 - 5:21 AM

Please ensure that the IMPLEMENT_ExtGridCell_Clone macro is used in the declaration of your grid cell class. This macro is required for the grid data provider object that creates your cell type in its internal memory storage using a custom C++ new operator with the IMalloc parameter.

Krustys Donuts Sep 7, 2005 - 6:47 AM

Hi, here’s my declaration:

class EllipsisCell : public CExtGridCellString
{
	DECLARE_DYNCREATE(EllipsisCell)
	IMPLEMENT_ExtGridCell_Clone( EllipsisCell, CExtGridCellString)
	IDonut* pDonut;

public:
	EllipsisCell( CExtGridDataProvider * pDataProvider=0);
	virtual ~EllipsisCell();
	virtual void OnButtonPressed( CExtGridWnd& wndGrid, INT nButtonType, const RECT& rcCellExtra, const RECT& rcCell,
								  LONG nVisibleColNo, LONG nVisibleRowNo, LONG nColNo, LONG nRowNo, INT nColType, INT nRowType);
	void setDonut( IDonut& donut);
};


I don’t ever make use of pDataProvider, but that should be okay, as I’m not using external data, like a database... right?

Technical Support Sep 13, 2005 - 7:37 AM

This cell class should work without problems in the property grid control.