// PropertyViewer.h #ifndef PropertyViewerh #define PropertyViewerh #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 /* This is a direct replacement for a CListBox which is used to display property values. The ListBox holds whatever strings you give it but renders them in a formatted way: Name: Value The first colon ':' in the string terminates the Name. All property names are displayed, left-justified. All columns are automatically sized to ensure all data is displayed. If Value is not numeric, it is displayed left-justified. If Value is numeric, it is displayed with decimal point alignment. This uses two further colums - the integral part being right-justified, and the fractional part being left-justified. The control also has a concept of Sections. Sections of properties would be separated with a blank line (See how the "Short Section" is separated in the following code example. If SectionalWidths is set (which it is by default) and the window is too narrow to display the whole of the long section, the Short Section will try to keep the Values visible by moving them closer to the Name while there is space to do so. The closeness of the Values to the Names depends on the available Window Width. This is similar to browser behaviour if each Section were a Table with Width set to 100%.
If AutoCopy is set, and a CEdit control has the focus, clicking on the ListBox will transfer Value to the CEdit. Otherwise you can set up a Right-Click context menu with Copy and use the Copy functions to copy the Value or whole string to the Clipboard. Usage: Put a ListBox in your Dialog I'll assume it's called IDC_LIST1 In the Properties for the Dialog set: Clip Children = Checked In the Properties for the ListBox set: OwnerDraw = Fixed HasStrings = Checked Horizontal Scroll = Checked Vertical Scroll = Checked Add these PropertyViewer.h and PropertyViewer.cpp Files to your Project. In your Dialog Header File include this File: #include "PropertyViewer.h" and add a Member to your Dialog Class: CPropertyViewer PropertyViewer; In the OnInitDialog() put the following lines: PropertyViewer.SubclassDlgItem(IDC_LIST1,this); PropertyViewer.AddString("Very Long Title Text:"); PropertyViewer.AddString(""); PropertyViewer.AddString("Very Long Comment Text."); PropertyViewer.AddString("Text Value: Description."); PropertyViewer.AddString("Integer Value a: 0"); PropertyViewer.AddString("Integer Value b: -123"); PropertyViewer.AddString("Decimal Value a: 3000.0"); PropertyViewer.AddString("Decimal Value b: 1.234"); PropertyViewer.AddString("Decimal Value c: 2.2"); PropertyViewer.AddString(""); PropertyViewer.AddString("Tool Directions"); PropertyViewer.AddString("Direction: CCW"); PropertyViewer.AddString("Inside/Outside: Inside"); PropertyViewer.AddString("Side: Left"); PropertyViewer.AddString(""); PropertyViewer.AddString("Text: Description."); PropertyViewer.AddString("a: 123.4"); PropertyViewer.AddString("b: 12.34"); PropertyViewer.AddString("c: 1.234"); PropertyViewer.AddString("d: 1234"); ASSERT(!PropertyViewer.IsProperty(0)); ASSERT(!PropertyViewer.IsProperty(1)); ASSERT(!PropertyViewer.IsProperty(2)); ASSERT( PropertyViewer.IsProperty(3)); ASSERT( PropertyViewer.IsProperty(4)); ASSERT(!PropertyViewer.IsProperty(100)); */ class CPropertyViewer : public CListBox { DECLARE_DYNAMIC(CPropertyViewer) bool Lines, AutoCopy, SectionalWidths, Dirty, FontChanged; struct CWidths { int Row,Name,Str,Int,Dec,HorizontalExtent; CWidths() {Clear();} ~CWidths() {} void Clear() {Row=Name=Str=Int=Dec=HorizontalExtent=0;} void Adjust(CDC* pDC, CString& S, CString& sName, CString& sStr, CString& sInt, CString& sDec) { if(! S.IsEmpty()) { Row=max( Row, pDC->GetTextExtent( S).cx); S.Empty();} if(!sName.IsEmpty()) {Name=max(Name, pDC->GetTextExtent(sName).cx); sName.Empty();} if(! sInt.IsEmpty()) { int dx=pDC->GetTextExtent( sInt).cx; Int=max( Int, pDC->GetTextExtent( sInt).cx); sInt.Empty();} if(! sDec.IsEmpty()) { Dec=max( Dec, pDC->GetTextExtent( sDec).cx); sDec.Empty();} if(! sStr.IsEmpty()) { Str=max( Str, pDC->GetTextExtent( sStr).cx); sStr.Empty();} } void Finish() { if(Row ) Row +=2; // For Focus Rectangle if(Name) Name+=4; // 1 for Focus Rectangle, 3 for space before Value if(Dec ) Dec +=2; // 1 for Focus Rectangle and 1 for "2.": in MS Sans Serif the '2' and the '.' are too close, so render an extra pixel between the int and any points. if(Str ) ++Str; // For Focus Rectangle HorizontalExtent=(max(Row, Name+max(Str, Int+Dec))); } } Widths; public: CPropertyViewer() : Lines(true), AutoCopy(false), SectionalWidths(true), Dirty(false), FontChanged(true) {} virtual ~CPropertyViewer() {} void ShowLines (bool Set=true) {Lines=Set;} void HaveSectionalWidths(bool Set=true) {SectionalWidths=Set;} void SetAutoCopy (bool Set=true) {AutoCopy=Set;} // If true, just click the List to copy the item (good but not Windows Standard). int AddString(LPCTSTR lpszItem) {Dirty=true; return CListBox::AddString(lpszItem);} int InsertString(int Row, LPCTSTR lpszItem) {Dirty=true; return CListBox::InsertString(Row,lpszItem);} int DeleteString(int Row) {Dirty=true; return CListBox::DeleteString(Row);} void ResetContent() {Dirty=true; CListBox::ResetContent();} void AddNamedDouble(const CString& Name, double Value); bool Adjust(CDC* pDC, CWidths& Columns, int Row, CString& S); bool GetColText(int Row, CString& S, CString& Name, CString& Str, CString& Int, CString& Dec) const; static bool GetColText (CString& S, CString& Name, CString& Str, CString& Int, CString& Dec); static bool IsNumeric(const char* ptr); bool Valid(unsigned Row) const {return Row < (unsigned)GetCount();} bool IsProperty(int Row); // Checks for a ':' in the string: bool CopyCurSelName () {return CopyName (GetCurSel());} bool CopyCurSelValue() {return CopyValue(GetCurSel());} bool CopyName (int Row) {return Copy(GetName (Row));} bool CopyValue(int Row) {return Copy(GetValue(Row));} // Returns 0 on blank lines, -1 if Name was set to a Description line's text, or the length of the Name otherwise. CString GetName (int Row) const; CString GetValue(int Row) const; // Just want the (formatted) text after the colon ':' void GetRects(int Row, CRect* RectS, CRect* RectName, CRect* RectInt, CRect* RectDec, CRect* RectStr); bool CopyCurSel(); bool Copy(CString& Clip); public: // Override if you want the Rows to be in different colours: virtual COLORREF OnSetLineColor () const {return OnSetBackGroundColor()^RGB(0x33,0x33,0);} virtual COLORREF OnSetFocusColor () const {return GetSysColor(COLOR_GRAYTEXT );} virtual COLORREF OnSetBackGroundColor() const {return GetSysColor(COLOR_WINDOW );} // When there is no Row drawn virtual COLORREF OnSetBackGroundColor(int Row) const {return GetSysColor(COLOR_WINDOW );} virtual COLORREF OnSetForeGroundColor(int Row) const {return GetSysColor(COLOR_WINDOWTEXT);} virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); afx_msg BOOL OnEraseBkgnd(CDC* pDC); protected: afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnRButtonDown(UINT nFlags, CPoint point); afx_msg void OnFontChange(); DECLARE_MESSAGE_MAP() }; #endif // ndef PropertyViewerh