Static Text Color - c++

I have written Following code which will apply color to all static text in one window, but
I want to apply two different colors in one window like ID:1234 where ID is another color and 1234 will be different color in one window. How can I do this? here is what i have done:
case WM_CTLCOLORSTATIC:
SetBkColor( hdc, COLORREF( :: GetSysColor( COLOR_3DFACE) ) );
//sets bckcolor of static text same as window color
if ( ( HWND ) lParam == GetDlgItem( hWnd, IDC_PID) )
{
SetTextColor( ( HDC ) wParam, RGB( 250, 50, 200));
return ( BOOL ) CreateSolidBrush ( GetSysColor( COLOR_3DFACE) );
}
break;

I'm not sure I understand your problem. Your code looks pretty much ok. One point worth noting is that you are responsible for cleaning up resources that you allocate. With the code above you are leaking the HBRUSH object created through a call to CreateSolidBrush. Since you don't need a custom brush you should rather be using GetSysColorBrush.
As a matter of taste I would filter on the control ID rather than its window handle using GetDlgCtrlID. Incorporating the changes your code should look like this:
case WM_CTLCOLORSTATIC:
switch ( GetDlgCtrlID( (HWND)lParam ) )
{
case IDC_PID:
//sets bckcolor of static text same as window color
SetBkColor( (HDC)wParam, COLORREF( GetSysColor( COLOR_3DFACE ) ) );
SetTextColor( (HDC)wParam, RGB( 250, 50, 200) );
return (INT_PTR)GetSysColorBrush( COLOR_3DFACE );
default:
// Message wasn't handled -> pass it on to the default handler
return 0;
}

Related

How to properly paint in WM_PAINT with GDI+

I'm new to using GDI+ and I was wondering how to fix the mess of creating and disposing objects that I have. Right now all the program needs to do is handle repeated WM_PAINT messages and update a DrawPie with increased degrees each time.
I have called GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL ); when the window starts up, and GdiplusShutdown( gdiplusToken ); on WM_DESTROY.
Degrees global variable defined near the top:
volatile double degrees = 0;
Here is my WM_PAINT:
case WM_PAINT: {
hdc = BeginPaint( hWnd, &ps );
Graphics g( hdc );
Pen p( Color::Green );
if ( degrees > 0 ) {
if ( degrees == 360 ) {
g.DrawEllipse( &p, 0, 0, 100, 100 );
} else {
g.DrawPie( &p, 0, 0, 100, 100, -90, degrees );
}
}
EndPaint( hWnd, &ps );
break;
}
And here is the function that updates the degrees and updates the window (It's in a separate thread):
void UpdateDegrees() {
for ( ;; ) {
if ( globalHWND != NULL ) {
degrees += 0.1;
InvalidateRect( globalHWND, NULL, TRUE );
UpdateWindow( globalHWND );
}
}
}
If I run it I get a "solid" pie shape like this, which I assume is it just redrawing itself at every angle. It needs to look like this, in other words, clear the graphic before it redraws itself each time. (Sorry, I guess I need 10 rep to post inline images)
I know that I didn't initialize and/or dispose of my graphics correctly, but I honestly have no idea how to do that. Any help would be greatly appreciated! Thanks.
There are lots of ways you can do this, the standard one being to handle the WM_ERASEBKGND message.
For simplicity, you can just fill a white rectangle across the clip rect (which is specified in ps), which will clear the background being painted.
SolidBrush backGroundBrush(Color(255,255,255));
Rect clipRect(ps->rcPaint.left, ps.rcPaint.top, ps->rcPaint.right - ps->rcPaint.left, ps->rcPaint.bottom - ps.rcPaint.top);
g.FillRectangle(&backgroundBrush, Rect(ps->rcPaint.left, ps.rcPaint));

C++ MFC Refresh Window

I am using Visual Studio 2010 with MFC and I am trying to make a rectangle that is red when a device is disconnected and green when it is. I have made the rectangle with the following code:
CRect lConnectStatus;
GetDlgItem( IDC_CONNECT_STATUS ) -> GetClientRect( &lConnectStatus );
GetDlgItem( IDC_CONNECT_STATUS ) -> ClientToScreen( &lConnectStatus );
ScreenToClient( &lConnectStatus );
mConnected.Create( GetSafeHwnd(), 10000 );
mConnected.SetPosition( lConnectStatus.left, lConnectStatus.top, lConnectStatus.Width(), lConnectStatus.Height() );
if( mDevice.IsConnected() ){
mConnected.SetBackgroundColor(0, 255, 0);
}
else{mConnected.SetBackgroundColor(0, 0, 255);}
I inserted this snippet into the OnInitDlg method and the rectangle does appear, but it doesn't change to green when the device gets connected. Is there anyway I can refresh the window so that the code is executed again and the colour changes to green?
What type of control is IDC_CONNECT_STATUS? If it is a static control you can eliminate all this code and handle WM_CTLCOLOR_STATIC in the parent dialog. Your message handler for that message will control the color of the static control. To refresh the static control call Invalidate on that control. That will cause it to call your WM_CTLCOLOR_STATIC message handler.
Solved It, As I am new to C++ I didn't know that putting the code snippet into the OnInitDlg() method wouldn't work. So I put the code into the OnPaint()method and used the functions Invalidate() and UpdateWindow() to force the window to refresh when the device was connected/disconnected. Thanks for your help.
Edit Thanks to Barmak for suggesting not to create the control in the OnPaint() method. I have updated the code below.
program::OnInitDlg(){
CRect lConnectStatus;
GetDlgItem( IDC_CONNECT_STATUS ) -> GetClientRect( &lConnectStatus );
GetDlgItem( IDC_CONNECT_STATUS ) -> ClientToScreen( &lConnectStatus );
ScreenToClient( &lConnectStatus );
mConnected.Create( GetSafeHwnd(), 10000 );
mConnected.SetPosition( lConnectStatus.left, lConnectStatus.top, lConnectStatus.Width(), lConnectStatus.Height() );
}
program::OnPaint(){
if( mDevice.IsConnected() ){
mConnected.SetBackgroundColor(0, 255, 0);
}
else{mConnected.SetBackgroundColor(0, 0, 255);}
}
program::Connect(){
Invalidate();
UpdateWindow();
}
program::disconnect(){
Invalidate();
UpdateWindow();
}

Expected and actual printing results do not match

INTRODUCTION AND RELEVANT INFORMATION:
I am trying to bypass another problem in my application by trying to do printing/print preview on my own.
I am trying to create a table that would look like in the picture below:
I am using C++ and WinAPI, on WindowsXP SP3. I work in MS Visual Studio 2008.
I do not have a printer, so I am testing the results by printing to MS OneNote and XPS file.
PROBLEM:
Text is obtained from database and is of variable length. Since it might not fit into the original cell, I will need to expand the cell and fit the text appropriately, like in the above image.
SIDE EFFECT:
The result of my testing code gives inconsistent results regarding font size.
In OneNote the print result seems fine :
However, in XPS it looks different :
MY EFFORTS TO SOLVE THIS TASK:
I have checked MSDN documentation to get started. So far I am able to successfully draw text and lines on a printing surface.
I have used DrawTextEx to perform word breaking ( by using flag DT_WORDBREAK ).
To obtain the size of the printing area I have used GetDeviceCaps, and to obtain printer device context I have used print property sheet.
QUESTIONS:
IMPORTANT REMARKS:
If the following questions are considered too broad please leave a comment and I will edit my post. I still believe that my mistakes are minor and can be explained in a single post.
1. Can you explain me how to adjust cells so the entire string can fit ?
Why is my font inconsistently drawn ?
As always, here are the instructions for creating SSCCE :
1) In Visual Studio, create default Win32 project.
2) in stdafx.h file comment out #define WIN32_LEAN_AND_MEAN so print property sheet can work properly.
3) In stdafx.h add the following, below #include <windows.h> line :
#include <windowsx.h>
#include <commctrl.h>
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
4) Add the following function above window procedure :
// hWnd is the window that owns the property sheet.
HRESULT DisplayPrintPropertySheet(HWND hWnd)
{
HRESULT hResult;
PRINTDLGEX pdx = {0};
LPPRINTPAGERANGE pPageRanges = NULL;
// Allocate an array of PRINTPAGERANGE structures.
pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if (!pPageRanges)
return E_OUTOFMEMORY;
// Initialize the PRINTDLGEX structure.
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWnd;
pdx.hDevMode = NULL;
pdx.hDevNames = NULL;
pdx.hDC = NULL;
pdx.Flags = PD_RETURNDC;
pdx.Flags2 = 0;
pdx.ExclusionFlags = 0;
pdx.nPageRanges = 0;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.hInstance = 0;
pdx.lpPrintTemplateName = NULL;
pdx.lpCallback = NULL;
pdx.nPropertyPages = 0;
pdx.lphPropertyPages = NULL;
pdx.nStartPage = START_PAGE_GENERAL;
pdx.dwResultAction = 0;
// Invoke the Print property sheet.
hResult = PrintDlgEx(&pdx);
if ( ( hResult == S_OK )
&& ( pdx.dwResultAction == PD_RESULT_PRINT ) )
{
// User clicked the Print button,
// so use the DC and other information returned in the
// PRINTDLGEX structure to print the document.
/***************** IMPORTANT INFO : ********************/
/****** I have added additional test code here *********/
/**** please refer to the edited part of this post *****/
/***************** at the very bottom !! ***************/
DOCINFO diDocInfo = {0};
diDocInfo.cbSize = sizeof( DOCINFO );
diDocInfo.lpszDocName = L"Testing printing...";
//******************** initialize testing font *****************//
HFONT font, oldFont;
long lfHeight = -MulDiv( 14, GetDeviceCaps( pdx.hDC, LOGPIXELSY), 72 );
font = CreateFont( lfHeight,
0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0,
0, 0, L"Microsoft Sans Serif" );
oldFont = SelectFont( pdx.hDC, font );
SetBkMode( pdx.hDC, TRANSPARENT );
SetTextColor( pdx.hDC, RGB( 255, 0, 0 ) );
//******************** end of initialization ******************//
if( StartDoc( pdx.hDC, &diDocInfo ) > 0 )
{
if( StartPage( pdx.hDC ) > 0 )
{
// get paper dimensions
int pageWidth, pageHeight;
pageWidth = GetDeviceCaps( pdx.hDC, HORZRES );
pageHeight = GetDeviceCaps( pdx.hDC, VERTRES );
/************ draw a testing grid ***************/
// draw vertical lines of the grid
for( int i = 0; i < pageWidth; i += pageWidth / 4 )
{
MoveToEx( pdx.hDC, i, 0, NULL );
LineTo( pdx.hDC, i, pageHeight );
}
// draw horizontal lines of the grid
for( int j = 0; j < pageHeight; j += pageWidth / 10 )
{
MoveToEx( pdx.hDC, 0, j, NULL );
LineTo( pdx.hDC, pageWidth, j );
}
/************************************************/
// test rectangle for drawing the text
RECT r;
r.left = 0;
r.top = 0;
r.right = 550;
r.bottom = 100;
// fill rectangle with light gray brush
// so we can see if text is properly drawn
FillRect( pdx.hDC, &r,
(HBRUSH)GetStockObject(LTGRAY_BRUSH) );
// draw text in test rectangle
if( 0 == DrawTextEx( pdx.hDC,
L"This is test string!",
wcslen( L"This is test string!" ),
&r,
DT_CENTER | DT_WORDBREAK | DT_NOCLIP, NULL ) )
// for now pop a message box saying something went wrong
MessageBox( hWnd, L"DrawText failed!", L"Error", MB_OK );
if( EndPage( pdx.hDC ) < 0 )
// for now pop a message box saying something went wrong
MessageBox( hWnd, L"EndDoc failed!", L"Error", MB_OK );
}
EndDoc( pdx.hDC );
SelectFont( pdx.hDC, oldFont );
DeleteFont( font );
}
}
if (pdx.hDevMode != NULL)
GlobalFree(pdx.hDevMode);
if (pdx.hDevNames != NULL)
GlobalFree(pdx.hDevNames);
if (pdx.lpPageRanges != NULL)
GlobalFree(pPageRanges);
if (pdx.hDC != NULL)
DeleteDC(pdx.hDC);
return hResult;
}
5) In WM_COMMAND handler, modify case IDM_ABOUT like this :
case IDM_ABOUT: // test our printing here
{
if( FAILED( DisplayPrintPropertySheet( hWnd ) ) )
MessageBox( hWnd,
L"Can't display print property sheet!",
L"Error", MB_OK );
}
//DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
EDITED on June, 8th 2014 :
After the block if ( ( hResult == S_OK ) && ( pdx.dwResultAction == PD_RESULT_PRINT ) ) in the submitted SSCCE I have added the following for testing purposes :
int xDpi = GetDeviceCaps( pdx.hDC, LOGPIXELSX ),
yDpi = GetDeviceCaps( pdx.hDC, LOGPIXELSY );
int mapMode = GetMapMode( pdx.hDC );
wchar_t displayDPI[50];
swprintf_s( displayDPI, 50, L" xDPI = %s , yDPI = %s", xDpi, yDpi );
MessageBox( hWnd, displayDPI, L"", MB_OK );
switch( mapMode )
{
case MM_ANISOTROPIC:
MessageBox( hWnd, L"MM_ANISOTROPIC", L"", MB_OK );
break;
case MM_HIENGLISH:
MessageBox( hWnd, L"MM_HIENGLISH", L"", MB_OK );
break;
case MM_HIMETRIC:
MessageBox( hWnd, L"MM_HIMETRIC", L"", MB_OK );
break;
case MM_ISOTROPIC:
MessageBox( hWnd, L"MM_ISOTROPIC", L"", MB_OK );
break;
case MM_LOENGLISH:
MessageBox( hWnd, L"MM_LOENGLISH", L"", MB_OK );
break;
case MM_LOMETRIC:
MessageBox( hWnd, L"MM_LOMETRIC", L"", MB_OK );
break;
case MM_TEXT:
MessageBox( hWnd, L"MM_TEXT", L"", MB_OK );
break;
case MM_TWIPS:
MessageBox( hWnd, L"MM_TWIPS", L"", MB_OK );
break;
default:
MessageBeep(0);
break;
}
In both cases mapping mode was the same ( MM_TEXT ) but for XPS I got xDPI = 600 , yDPI = 600 in the MessageBox while OneNote had xDPI = 300 , yDPI = 300.
This leads to the conclusion that comments made by member * Carey Gregory* were correct -> with the same characteristics virtual printers will reproduce the same result. This also explains why OneNote printed properly into XPS when I tested it, and why my application failed. To solve this problem I need to find DPI aware solution...
EDITED on June, 9th 2014 :
Using GDI+ to create font and draw text I was able to get consistent results ( DPI is no longer a problem ). Still, if anyone knows how to achieve the same result using only GDI I would be still interested.
The only thing left for me is to draw a proper grid so the text can fit into cells properly.
EDITED on June, 10th 2014 :
After carefully reading through this MSDN link I was able to alter the font creating code to achieve ( in my opinion ) stable results ( the actual height of the font ends up way smaller, but I could use bigger number I guess ) :
font = CreateFont(
// DPI aware, thanks to the below equation ( or so it seems... )
lfHeight / ( GetDeviceCaps( pdx.hDC, LOGPIXELSY ) / 96 ),
0, 0, 0, FW_BOLD, TRUE, FALSE, FALSE, 0, 0, 0, // remained the same
0, 0, L"Microsoft Sans Serif" ); // remained the same
Just to be safe, I will try to stick with GDI+ but will update this post with the testing results when GDI and the mentioned equation is used in case someone else stumbles upon the same problem. I just hope it will save that persons time...
The problem is simple. You are adjusting the font size (in pixels) to match the DPI of the device you're drawing to, but you're not adjusting the size of the rectangle. Since your mapping mode is MM_TEXT both are measured in pixels, and your bounding box is effectively half the size on the device with twice the resolution.
The solution is to scale the rectangle similarly to the way you scale the font size. In this case since you've determined that these coordinates are correct at 300 DPI, that's the constant we'll scale relative to.
RECT r;
r.left = 0 * GetDeviceCaps(pdx.hDC, LOGPIXELSX) / 300;
r.top = 0 * GetDeviceCaps(pdx.hDC, LOGPIXELSY) / 300;
r.right = 550 * GetDeviceCaps(pdx.hDC, LOGPIXELSX) / 300;
r.bottom = 100 * GetDeviceCaps(pdx.hDC, LOGPIXELSX) / 300;
Regarding your edit of June 10, it only works because you've made the font much smaller so that it fits in both the full-size bounding box and the half-size one. I'd recommend going back to the original definition you had for font size, which is consistent with most other Windows applications.

Win32 Tab Control w/Multiple Rows

I have a tab control in my Win32 application. The control is multi-row capable. When I resize the window such that the tab control is reduced in width, multiple rows show up. The problem is that when I click on one of the lower rows the tabs in the upper rows are blocked by the current tab's window( the tab control doesn't properly resize the content window of the current tab so that the upper rows are visible ). How do I account for this problem?
Here is code for my resize function:
RECT cr;
GetClientRect( pHdr->hWndTab, &cr );
TabCtrl_AdjustRect( pHdr->hWndTab, FALSE, &cr );
OffsetRect( &cr, cxMargin - cr.left, cyMargin - cr.top );
SetWindowPos( pHdr->hWndDisplay, 0, cr.left, cr.top, cr.right, cr.bottom, SWP_SHOWWINDOW );
This code comes from Microsoft website...
pHdr->hWndTab is the window handle for the tab control
pHdr->hWndDisplay is the window handle for the content window of the current tab
EDIT: Actually, after clicking the lower tabs, the upper tabs move to the top of control...however, they are still blocked by the content window...
I fixed the problem by adjusting the display rectangle after offsetting:
typedef struct tag_dlghdr
{
HWND hWndTab;
HWND hWndDisplay;
RECT rcDisplay;
DLGTEMPLATE *apRes[ MAX_PAGES ];
DLGPROC MsgProc[ MAX_PAGES ];
}DLGHDR
Resize( HWND hWndDlg )
{
DLGHDR *pHdr = ( DLGHDR * )GetWindowLong( hWndDlg, GWL_USERDATA );
DWORD dwDlgBase = GetDialogBaseUnits();
int cxMargin = LOWORD( dwDlgBase ) / 4;
int cyMargin = HIWORD( dwDlgBase ) / 8;
m_niCurTabSel = TabCtrl_GetCurSel( pHdr->hWndTab );
RECT cr;
GetClientRect( pHdr->hWndTab, &cr );
TabCtrl_AdjustRect( pHdr->hWndTab, FALSE, &cr );
OffsetRect( &cr, cxMargin - cr.left, cyMargin - cr.top );
CopyRect( &pHdr->rcDisplay, &cr );
TabCtrl_AdjustRect( pHdr->hWndTab, FALSE, &pHdr->rcDisplay );
SetWindowPos( pHdr->hWndDisplay, 0, pHdr->rcDisplay.left, pHdr->rcDisplay.top, pHdr->rcDisplay.right, pHdr->rcDisplay.bottom, SWP_SHOWWINDOW );
}

MFC - change text color of a cstatic text control

How do you change the text color of a CStatic text control? Is there a simple way other that using the CDC::SetTextColor?
You can implement ON_WM_CTLCOLOR in your dialog class, without having to create a new CStatic-derived class:
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
//{{AFX_MSG_MAP(CMyDialog)
ON_WM_CTLCOLOR()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd *pWnd, UINT nCtlColor)
{
switch (nCtlColor)
{
case CTLCOLOR_STATIC:
pDC->SetTextColor(RGB(255, 0, 0));
return (HBRUSH)GetStockObject(NULL_BRUSH);
default:
return CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
}
}
Notice that the code above sets the text of all static controls in the dialog. But you can use the pWnd variable to filter the controls you want.
unfortunately you won't find a SetTextColor method in the CStatic class. If you want to change the text color of a CStatic you will have to code a bit more.
In my opinion the best way is creating your own CStatic-derived class (CMyStatic) and there cacth the ON_WM_CTLCOLOR_REFLECT notification message.
BEGIN_MESSAGE_MAP(CMyStatic, CStatic)
//{{AFX_MSG_MAP(CMyStatic)
ON_WM_CTLCOLOR_REFLECT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
HBRUSH CColorStatic::CtlColor(CDC* pDC, UINT nCtlColor)
{
pDC->SetTextColor(RGB(255,0,0));
return (HBRUSH)GetStockObject(NULL_BRUSH);
}
Obviously you can use a member variable and a setter method to replace the red color (RGB(255,0,0)).
Regards.
Just a follow up to the painting issue (a transparent background), which caused by *return (HBRUSH)GetStockObject(NULL_BRUSH);*
Easy change as below:
HBRUSH hBrush = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
if (nCtlColor == CTLCOLOR_STATIC &&
pWnd->GetSafeHwnd() == GetDlgItem(XXX)->GetSafeHwnd()
) pDC->SetTextColor(RGB(255, 0, 0));
return hBrush;
Hope this helps.
From the Answers given here and other places, it was not obvious how to create a derived class to be used instead of CStatic that handles coloring itself.
So following is what works for me, using MSVS 2013 Version 12.0.40629.00 Update 5. I can place a "Static Text"-control in the resource editor, then replace the type of the member variable with TColorText.
In the .h-file:
class TColorText : public CStatic
{
protected:
DECLARE_MESSAGE_MAP( )
public:
// make the background transparent (or if ATransparent == true, restore the previous background color)
void setTransparent( bool ATransparent = true );
// set background color and make the background opaque
void SetBackgroundColor( COLORREF );
void SetTextColor( COLORREF );
protected:
HBRUSH CtlColor( CDC* pDC, UINT nCtlColor );
private:
bool MTransparent = true;
COLORREF MBackgroundColor = RGB( 255, 255, 255 ); // default is white (in case someone sets opaque without setting a color)
COLORREF MTextColor = RGB( 0, 0, 0 ); // default is black. it would be more clean
// to not use the color before set with SetTextColor(..), but whatever...
};
in the .cpp-file:
void TColorText::setTransparent( bool ATransparent )
{
MTransparent = ATransparent;
Invalidate( );
}
void TColorText::SetBackgroundColor( COLORREF AColor )
{
MBackgroundColor = AColor;
MTransparent = false;
Invalidate( );
}
void TColorText::SetTextColor( COLORREF AColor )
{
MTextColor = AColor;
Invalidate( );
}
BEGIN_MESSAGE_MAP( TColorText, CStatic )
ON_WM_CTLCOLOR_REFLECT( )
END_MESSAGE_MAP( )
HBRUSH TColorText::CtlColor( CDC* pDC, UINT nCtlColor )
{
pDC->SetTextColor( MTextColor );
pDC->SetBkMode( TRANSPARENT ); // we do not want to draw background when drawing text.
// background color comes from drawing the control background.
if( MTransparent )
return nullptr; // return nullptr to indicate that the parent object
// should supply the brush. it has the appropriate background color.
else
return (HBRUSH) CreateSolidBrush( MBackgroundColor ); // color for the empty area of the control
}
Very helpful.
https://msdn.microsoft.com/de-de/library/0wwk06hc.aspx
Alike to
HBRUSH hBrush = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
if (nCtlColor == CTLCOLOR_STATIC &&
pWnd->GetSafeHwnd() == GetDlgItem(XXX)->GetSafeHwnd()
) pDC->SetTextColor(RGB(255, 0, 0));
return hBrush;
I believe you can't return nullptr for a HBRUSH rather (HBRUSH)GetStockObject(NULL_BRUSH);