MFC: How to custom draw a dynamically created CListCtrl - mfc

I need to customize the head/row height of a CListCtrl. After some googling, I know that I need to subclass CListCtrl, so I wrote my own list class, with the outline as follows:
class CListCtrlCl : public CListCtrl
{
DECLARE_DYNAMIC(CListCtrlCl)
public:
...
BEGIN_MESSAGE_MAP(CListCtrlCl, CListCtrl)
ON_WM_MEASUREITEM()
ON_WM_MEASUREITEM_REFLECT()
END_MESSAGE_MAP()
void CListCtrlCl::PreSubclassWindow()
{
ModifyStyle(0,LVS_OWNERDRAWFIXED);
CListCtrl::PreSubclassWindow();
CHeaderCtrl *pHeader = GetHeaderCtrl();
m_Header.SubclassWindow(pHeader->GetSafeHwnd());
}
void CListCtrlCl::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if (m_nRowHeight>0)
{
lpMeasureItemStruct->itemHeight = 100;
}
}
The problem is that this method worked if I drag a CListCtrl control in dialog template, but if I create this listctrl dynamically, like:
BOOL CListCtrlTestDlg::OnInitDialog()
{
CRect rect(7,7,300,300);
this->m_ListCtrl.Create(WS_CHILD|WS_VISIBLE|WS_VSCROLL|LVS_REPORT|LVS_ALIGNLEFT|WS_BORDER | WS_TABSTOP, rect, this,IDC_LIST1 + 1);
SetWindowLong(m_ListCtrl.m_hWnd ,GWL_EXSTYLE,WS_EX_CLIENTEDGE);
m_ListCtrl.SetExtendedStyle(LVS_EX_GRIDLINES);
::SendMessage(m_ListCtrl.m_hWnd, LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
...
Then the customization code just doesn't take effect. Whatever I did, the result listctrl is the standard one without any customization. But I need to create this listctrl dynamically, so could anyone tell me what I need to do to make dynamically created clistctrl customizable?
Thanks.

You've left off the necessary style for custom drawing when you create the control. Add LVS_OWNERDRAWFIXED. That should fix your problem.

The reason is that, PreSubclassWindow is only called when you subclass a control. When you createthe control you have also control over the style.
Simply overwrite the virtual Create function and just add the style like you are doing it in the PreSubclassWindow function. Than call the base class. You can also overwrite PreCreateWindow.
But much simpler than using the ownerdraw feature is cusum draw.

Related

Creating a control in MFC application

How to Create a Button using CButton class inside the Client area. I have just tried but the control is not getting displayed in the client area.
Code i used to create button
void CcontrolsView::OnDraw(CDC* pDC)
{
CcontrolsDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CButton cb;
cb.Create(BS_PUSHBUTTON,CRect(20,20,100,100),this,10000);
// TODO: add draw code for native data here
}
1 Don't do it in the OnDraw() method. Add a message handler for WM_CREATE in there, and instead do it in there.
2 Don't use a stack based object to create the button. I would suggest adding a CButton member to your view class called m_Button (or whatever).
In your CcontrolsView::OnCreate() method add code like:
m_Button.Create(BS_PUSHBUTTON|WS_VISIBLE|WS_CHILD,CRect(20,20,100,100),this,10000);
You need to construct a new CButton, then add it to the dialog by setting its parent or owner (I forget which - set both!) and other properties like a sensible position, and then call ShowWindow(SW_SHOW) on it.

Changing default sort arrows of CMFCListCtrl

I found a problem. I can't change default sort arrows in header of CMFCListCtrl.
I found post on msdn about CMFCHeaderCtrl::OnDrawSortArrow but it didn't help because no examples there.
I tried simple method how I set arrows to CListCtrl header through the CimageList and HDITEM but those arrow sets only to left side because right side has already arrow default.
Google is empty on solutions how change default arrows on CMFCListCTrl.
Please help me)
Thanks!
PS. Please note that this is CMFCListCtrl not a CListCtrl where I can add arrows very easly.
Because CMFCHeaderCtrl is a member inside CMFCListCtrl you can not overwrite it.
Try to derive yor own CMFCListCtrl class, with your own CMFCHeaderCtrl class, that overwrites OnDrawSortHeader OnDrawSortArrow.
Overwrite CMFCListCtrl::InitHeader and subclass to your header control class.
If you start form scratch with a CListCtrl you can directly subclass the header control. The complete stuff inside CMFCListCtrl isn't so complicated and easy to reimplement. Depends what functionality you need.
The CMFCHeaderCtrl calls the currently active Visual Manager to do the actual drawing of sort arrows. It is easy to implement a customized Visual manager which overrides the arrow drawing method in the base class.
class CMyVisualManager:public CMFCVisualManagerOffice2007
{
virtual void OnDrawHeaderCtrlSortArrow(CMFCHeaderCtrl* pCtrl, CDC* pDC, CRect& rect, BOOL bIsUp);
};
void CMyVisualManager::OnDrawHeaderCtrlSortArrow(CMFCHeaderCtrl* pCtrl, CDC* pDC, CRect& rectArrow, BOOL bIsUp)
{
BOOL bDlgCtrl = pCtrl->IsDialogControl();
CPen penDark(PS_SOLID, 1, bDlgCtrl ? afxGlobalData.clrBtnDkShadow : afxGlobalData.clrBarDkShadow);
CPen* pPenOld = pDC->SelectObject(&penDark);;
ASSERT_VALID(pPenOld);
if (!bIsUp)
{
pDC->MoveTo(rectArrow.CenterPoint().x, rectArrow.bottom);
pDC->LineTo(rectArrow.CenterPoint().x, rectArrow.top);
pDC->MoveTo(rectArrow.CenterPoint().x-2, rectArrow.top+4);
pDC->LineTo(rectArrow.CenterPoint().x+3, rectArrow.top+4);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.top+3);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.top+3);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.top+2);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.top+2);
}
else
{
pDC->MoveTo(rectArrow.CenterPoint().x, rectArrow.top);
pDC->LineTo(rectArrow.CenterPoint().x, rectArrow.bottom);
pDC->MoveTo(rectArrow.CenterPoint().x-2, rectArrow.bottom-4);
pDC->LineTo(rectArrow.CenterPoint().x+3, rectArrow.bottom-4);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.bottom-3);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.bottom-3);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.bottom-2);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.bottom-2);
}
pDC->SelectObject(pPenOld);
}

CMFCButton::SetToolTip(str) not work in OnInitDialog() and

I want my CMFCButton to show tooltip when mouse over.
It doesn't work if I use SetToolTip() method in OnInitDialog
CMFCButton* bt = ((CMFCButton*)GetDlgItem(IDC_MFCBUTTON1));
bt->SetTooltip(_T("tooltip"));
BUT it does work if I put this code in message handle function like another button's click handle.
What I want is that the CMFCButton could show tooltip when the dialog is created, where should I put these code?
========================
By the way, The tooltip text I set in the Property view does not work for most time.
I just derived a class
class CMyButton : public CMFCButton
{
public:
void SetDelayFullTextTooltipSet(bool DelayFullTextTooltipSet)
{
m_bDelayFullTextTooltipSet = DelayFullTextTooltipSet;
}
};
Instead of a CMFCButton variable on the Dialog class I use the button, I now have a CMyButton.
And in the OnInitDialog, after the SetTooltip call, I do
button.SetDelayFullTextTooltipSet(FALSE);
Have you called the base class' OnInitDialog()? The main point is that the control needs to be created before you call SetToolTip() on it. Step into OnInitDialog() with the debugger and see if m_hWnd of the control has a value at the moment you call SetToolTip().

How to set a CMFCPropertyListCtrl's column width?

I'm adding properties to an object of type CMFCPropertyGridCtrl like this:
myPropertyListCtrl.AddProperty(
new CMFCPropertyGridProperty(
_T("Name"),
foo.GetName())
);
The result is that only the second column is visible but not the first that should contain "Name".
I found CMFCPropertyGridCtrl::GetPropertyColumnWidth() but there appears to be no corresponding Set... method...
I looked at the NewControls sample, in which the column sizing appears to be fully automatic. However, I couldn't find the relevant difference to my code.
What am I missing?
m_nLeftColumnWidth responsible for holding the "Name" column's width is a protected member of the CMFCPropertyGridCtrl class. Create your own class, that derives from CMFCPropertyGridCtrl and you will be able to manipulate m_nLeftColumnWidth.
Note that m_nLeftColumnWidth is initially set to 0 in CMFCPropertyGridCtrl's ctor. One of the few other places that it is set, is in the OnSize() method (ref. AfxPropertyGridCtrl.cpp, line 2783 in VS2010), where it is set to half the width of the grid. This may not be ideal, nor the customized value described by overriding the class to explicitly set it, but may be good enough.
If so, then it is merely to trigger having the CMFCPropertyGridCtrl::OnSize() protected method. When used in a resizable window such as a CDockablePane, OnSize() will be called automatically. But in a CDialog, it needs to be trigger explicitly such as to send a WM_SIZE message:
CRect rect;
myPropGrid.GetWindowRect(&rect);
myPropGrid.PostMessage(WM_SIZE, 0, MAKELONG(rect.Width(),rect.Height()));
The reason the "set" isn't there is because it's left to the header control. The below is the method of handling through MFC versus posting window messages:
HDITEM hdItem;
hdItem.mask = HDI_WIDTH; // indicating cxy is width
hdItem.cxy = 300; // whatever you want the property name column width to be
PropListCtrl.GetHeaderCtrl().SetItem(0, &hdItem);
class CServerPropertyGridCtrl : public CMFCPropertyGridCtrl
{
public:
void SetLeftColumnWidth(int cx)
{
m_nLeftColumnWidth = cx;
AdjustLayout();
}
};

How do I drag and drop something into a Static control?

How would I drag and drop something into a static control? It looks like I need to create a sub class of COleDropTarget and include that as a member variable in a custom CStatic. That doesn't appear to be working though. When I try and drag something onto the Static control I get the drop denied cursor.
The static control's m_hWnd must be valid when you call COleDropTarget::Register, which is why it doesn't work from within your CMyStatic constructor. What you can do is override CWnd::PreSubclassWindow within your CMyStatic class:
class CMyStatic : public CStatic {
...
virtual void PreSubclassWindow();
};
void CMyStatic::PreSubclassWindow()
{
CStatic::PreSubclassWindow();
m_MyDropTarget.Register(this);
}
There's a really good article here on CodeProject that you may find helpful.
In addition to the PreSubClassWindow() addition, you also have to set your CStatic control to have the Notify flag set in its resource parameters. Otherwise the control won't let the app know about mouse movements and hence not trigger the OnDragEnter() method.