MFC: how to fix redraw properly for inherited CDialogBar? - visual-studio

I make an inherited class from CDialogBar.
class CMyDialogBar : public CDialogBar
{
DECLARE_DYNAMIC(CMyDialogBar)
// Implementation
public:
BOOL Create(CWnd * pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID);
BOOL Create(CWnd * pParentWnd, LPCTSTR lpszTemplateName, UINT nStyle, UINT nID);
protected:
virtual void DoDataExchange(CDataExchange* pDX) { return CDialogBar::DoDataExchange(pDX); }
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
DECLARE_MESSAGE_MAP()
};
The only big change is the function OnEraseBkgnd() because I like the background to be white.
BOOL CMyDialogBar::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
It works OK. However, when I move the rebar around it doesn't redraw correctly as shown in the figure below.
The source code can be downloaded here: https://138.197.210.223/test/My.zip.

You need your OnEraseBkgnd override to actually erase the background! For example, to set the entire client rectangle to white, you could do this:
BOOL CMyDialogBar::OnEraseBkgnd(CDC *pDC)
{
RECT wr; GetClientRect(&wr);
pDC->FillSolidRect(&wr, RGB(255,255,255));
return TRUE;
}
EDIT: Maybe you already have this, but also be sure to add ON_WM_ERASEBKGND to your message map:
BEGIN_MESSAGE_MAP(CMyDialogBar, CDialogBar)
// ... (other message handlers, if any) ...
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()

Related

set transparent border to CComboBox control

I am trying to set transparent border of CComboBox control.
For other control like CEdit it is possible to do so using control properties but I am unable to find any solution for CComboBox. I want result something like shown in image.
I have found a little piece of code, I hope it help you:
// YourComboBox.h : header file
class CYourComboBox : public CComboBox
{
public:
CEdit m_edit;
CListBox m_listbox;
protected:
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
afx_msg void OnDestroy();
...
};
// YourComboBox.cpp : implementation file
HBRUSH CYourComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
if (nCtlColor == CTLCOLOR_EDIT)
{
//Edit control
if (m_edit.GetSafeHwnd() == NULL)
m_edit.SubclassWindow(pWnd->GetSafeHwnd());
}
if (nCtlColor == CTLCOLOR_LISTBOX)
{
//ListBox control
if (m_listbox.GetSafeHwnd() == NULL)
m_listbox.SubclassWindow(pWnd->GetSafeHwnd());
}
RemoveEditBorder();
HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor);
return hbr;
}
void CYourComboBox::OnDestroy()
{
if (m_edit.GetSafeHwnd() != NULL)
m_edit.UnsubclassWindow();
if (m_listbox.GetSafeHwnd() != NULL)
m_listbox.UnsubclassWindow();
CComboBox::OnDestroy();
}
void CYourComboBox::RemoveEditBorder()
{
m_edit.ModifyStyle(WS_BORDER, 0);
}

using CFileDialog::AddCheckButton fails

OK, I am trying to use CFileDialog::AddCheckButton. The function call succeeds and I'm able to see the new check box. I'm unable to see any events and while I can override OnInitDialog, overriding OnOK is ignored. I'm not sure what I'm doing wrong:
//header
class CTPSaveDialog : public CFileDialog
{
DECLARE_DYNAMIC(CTPSaveDialog)
static const CString CTPSaveDialog::m_cstrFilter;
public:
BOOL m_bForce;
CTPSaveDialog(
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
CWnd* pParentWnd = NULL,
DWORD dwSize = 0);
~CTPSaveDialog();
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
afx_msg void OnBnClickedCheckForce();
virtual void OnOK();
};
// implementation
const CString CTPSaveDialog::m_cstrFilter = "JPEG images (*.jpg)|*.jpg|TIFF Format (*.tif)|*.tif|Windows Bitmap (*.bmp)|*.bmp|Portable Network Graphics (*.png)|*.png|GIF (*.gif)|*.gif||";
IMPLEMENT_DYNAMIC(CTPSaveDialog, CFileDialog)
CTPSaveDialog::CTPSaveDialog(LPCTSTR lpszDefExt, LPCTSTR lpszFileName, DWORD dwFlags, CWnd * pParentWnd, DWORD dwSize) :
CFileDialog(FALSE, lpszDefExt, lpszFileName, dwFlags, m_cstrFilter, pParentWnd, dwSize, TRUE)
{
AddCheckButton(IDC_CHK_FORCE, "Force", FALSE);
m_bForce = FALSE;
m_ofn.lpstrTitle = "Write Simulation To File";
}
CTPSaveDialog::~CTPSaveDialog()
{
}
BOOL CTPSaveDialog::OnInitDialog()
{
CFileDialog::OnInitDialog();
if (GetDlgItem(IDC_CHK_FORCE))
SendDlgItemMessage(IDC_CHK_FORCE, BM_SETCHECK, m_bForce ? BST_CHECKED : BST_UNCHECKED);
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
BEGIN_MESSAGE_MAP(CTPSaveDialog, CFileDialog)
ON_BN_CLICKED(IDC_CHK_FORCE, &CTPSaveDialog::OnBnClickedCheckForce)
END_MESSAGE_MAP()
void CTPSaveDialog::CTPSaveDialog()
{
m_bForce = !m_bForce;
}
void CTPSaveDialog::OnOK()
{
// TODO: Add your specialized code here and/or call the base class
CFileDialog::OnOK();
}
In CFileDialog with Vista style, windows messages are not handled in message map. Instead CFileDialog uses specific virtual functions. You only need to declare and define these functions.
Use OnCheckButtonToggled to detect if check box is clicked.
Use OnFileNameOK to detect when file is selected and Open/Save button is clicked.
Use SetCheckButtonState to set/unset the check button (not SendDlgItemMessage)
See CFileDialog for all available methods.
As stated in documentation, OnInitDialog is not supported either:
Some CFileDialog methods are not supported under Windows Vista or
later. Check the individual method topic for information about whether
the method is supported. In addition, the following inherited
functions are not supported under Windows Vista or later:
CDialog::OnInitDialog
...
Just do the initialization in the constructor or before calling DoModal(), and override these functions:
class CTPSaveDialog : public CFileDialog
{
...
virtual void OnCheckButtonToggled(DWORD dwIDCtl, BOOL bChecked);
virtual BOOL OnFileNameOK();
};
void CTPSaveDialog::OnCheckButtonToggled(DWORD dwIDCtl, BOOL bChecked)
{
if (dwIDCtl == IDC_CHK_FORCE)
TRACE("Is checked? %d\n", bChecked);
}
BOOL CTPSaveDialog::OnFileNameOK()
{
TRACE("Clicked Open/Save button\n");
//return FALSE to close the dialog
return FALSE;
}

AnimateWindow() incorrectly draws background on high dpi (Win10)

I am creating a simple Win32/MFC application with a main window, and a child-window that uses AnimateWindow() to show (slide up) and hide (slide down) the window. When running the application on 100% dpi scaling, everything behaves normally.
I have overridden WM_ERASEBKGND to render a random red color, to demonstrate the effect. As the window slides-down (hide) on every "step" of the animation the "update rectangle" of the background is repainted, exactly where the child-window disappeared, and the background is to become visible again.
However, when changing the dpi-scaling via Windows Settings (in this case to 125%), an incorrect area is redrawn. It appears as if the area passed into OnEraseBkgnd() is still using the 100% dpi-scaling size, but also the position is off: it appears as if the x/y position of the upper-left corner is passed in screen-space instead of client-space. So the redrawn area looks different, depending on where the on the screen the window is positioned.
The white area is where the child window was actually positioned, and where the redraw of the background should actually have happened.
I have confirmed this effect on Win10 (1803 and 1809) and on Win8.1
Is this a bug in the OS, or is there something I can do to avoid the problem - other than not using AnimateWindow()? ShowWindow() (with SW_SHOW or SW_HIDE) works just fine, btw.
Update: added the full source-code to reproduce the issue.
The problem occurs when using no dpi-aware manifest at all, but it also occurs when using <gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
class CDialogTestApp : public CWinApp
{
virtual BOOL InitInstance();
};
CDialogTestApp theApp;
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg() : CDialogEx(IDD_ABOUTBOX) {}
};
class CDialogTestDlg : public CDialogEx
{
public:
CDialogTestDlg(CWnd* pParent = nullptr) : CDialogEx(IDD_DIALOGTEST_DIALOG, pParent) {}
protected:
virtual BOOL OnInitDialog();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
private:
CAboutDlg mDialog;
};
BEGIN_MESSAGE_MAP(CDialogTestDlg, CDialogEx)
ON_WM_ERASEBKGND()
ON_WM_LBUTTONUP()
END_MESSAGE_MAP()
BOOL CDialogTestApp::InitInstance()
{
CWinApp::InitInstance();
CDialogTestDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
return FALSE;
}
BOOL CDialogTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
mDialog.Create(IDD_ABOUTBOX, this);
return TRUE;
}
BOOL CDialogTestDlg::OnEraseBkgnd(CDC* pDC)
{
COLORREF color = RGB(rand() & 255, 20, 40);
CRect rect;
GetClipBox(pDC->m_hDC, &rect); // retrieve the update-rectangle
CBrush brush(color);
FillRect(pDC->m_hDC, &rect, (HBRUSH)brush.m_hObject);
return TRUE;
}
void CDialogTestDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (mDialog.IsWindowVisible())
{
mDialog.AnimateWindow(200, AW_HIDE | AW_SLIDE | AW_VER_POSITIVE);
}
else
{
mDialog.SetWindowPos(&CWnd::wndTop, 0, 50, 0, 0, SWP_NOSIZE);
mDialog.AnimateWindow(200, AW_ACTIVATE | AW_SLIDE | AW_VER_NEGATIVE);
}
CDialogEx::OnLButtonUp(nFlags, point);
}

Removing horizontal scrollbar from list control in report mode [duplicate]

I've got a ListView control in Details mode with a single column. It's on a form that is meant to only be used with the keyboard, mostly with the up/down arrows for scrolling and enter to select. So I don't really need to have the scroll bars and would just like them to not show for a cleaner look. However, when I set the ListView.Scrollable property to false, I can still move the selected item up and down, but as soon as it moves to an item not currently in view, the list won't move to show that item. I've tried using EnsureVisible to programmatically scroll the list, but it does nothing when in this mode.
Is there any way to manually move the list up and down to scroll, but without having the scrollbar present?
It's not easy but it can be done. If you try to hide the scroll bar through ShowScrollBar, the ListView will simply put it back again. So you have to do something more devious.
You will have to intercept the WM_NCCALCSIZE message, and in there, turn off the vertical scroll style. Whenever the listview tries to turn it on again, you will turn it off again in this handler.
public class ListViewWithoutScrollBar : ListView
{
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case 0x83: // WM_NCCALCSIZE
int style = (int)GetWindowLong(this.Handle, GWL_STYLE);
if ((style & WS_VSCROLL) == WS_VSCROLL)
SetWindowLong(this.Handle, GWL_STYLE, style & ~WS_VSCROLL);
base.WndProc(ref m);
break;
default:
base.WndProc(ref m);
break;
}
}
const int GWL_STYLE = -16;
const int WS_VSCROLL = 0x00200000;
public static int GetWindowLong(IntPtr hWnd, int nIndex) {
if (IntPtr.Size == 4)
return (int)GetWindowLong32(hWnd, nIndex);
else
return (int)(long)GetWindowLongPtr64(hWnd, nIndex);
}
public static int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong) {
if (IntPtr.Size == 4)
return (int)SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
else
return (int)(long)SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
}
[DllImport("user32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto)]
public static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", CharSet = CharSet.Auto)]
public static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, int dwNewLong);
}
This will give you a ListView without scroll bars that still scrolls when you use the arrow keys to change selection.
i did something more easy. i left scrollable to true and used a custom slider(colorSlider) that i found on codeproject and i drawed the slider over the position where the vscroller would appear and then used the ensureVisible function.
Call the ShowScrollBar API method.
If ShowScrollBar doesn't work, I'm not sure how to do it.
You could put the ListView in a panel and make the ListView wider than the panel so that the scrollbar is cut off (check SystemInformation.VerticalScrollBarWidth), but that's a horrifyingly ugly hack.
Since ShowScrollBar didn't work, maybe this helps:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
private const int WM_VSCROLL = 0x115;
private const int SB_LINEDOWN = 1;
public Form1()
{
InitializeComponent();
for (int i = 0; i < 100; i++)
listView1.Items.Add("foo" + i);
listView1.Scrollable = false;
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
SendMessage(listView1.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
}
You can use ListView.Scrollable Property. Set it to false and Scroll bars won't appear!

Owner draw CSliderCtrl using MFC: why my program-created slider has an incorrect channel rect?

I am trying to implement an owner draw CSliderCtrl. It seems drawing is fine, however the mouse to change the thumb position is very weird. It is limited only in a very small rect. And I found that the rect by GetChannelRect is much smaller than the rect I used to create the slider control.
Below is .h file:
#if !defined(AFX_OWNDRAWSLIDER_H__82981708_4CBC_4D1E_8982_504E99BE489D__INCLUDED_)
#define AFX_OWNDRAWSLIDER_H__82981708_4CBC_4D1E_8982_504E99BE489D__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// OwnDrawSlider.h : header file
//
/////////////////////////////////////////////////////////////////////////////
// COwnDrawSlider window
class COwnDrawSlider : public CSliderCtrl
{
// Construction
public:
COwnDrawSlider();
// Attributes
public:
CBrush black_brush;
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(COwnDrawSlider)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~COwnDrawSlider();
// Generated message map functions
protected:
//{{AFX_MSG(COwnDrawSlider)
afx_msg void OnPaint();
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_OWNDRAWSLIDER_H__82981708_4CBC_4D1E_8982_504E99BE489D__INCLUDED_)
Here is the OwnDrawSlider class cpp:
// OwnDrawSlider.cpp : implementation file
//
#include "stdafx.h"
#include "OwnDrawSlider.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// COwnDrawSlider
COwnDrawSlider::COwnDrawSlider()
{
black_brush.CreateSolidBrush(RGB(0,0,0));
}
COwnDrawSlider::~COwnDrawSlider()
{
}
BEGIN_MESSAGE_MAP(COwnDrawSlider, CSliderCtrl)
//{{AFX_MSG_MAP(COwnDrawSlider)
ON_WM_PAINT()
ON_WM_CTLCOLOR()
ON_WM_ERASEBKGND()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// COwnDrawSlider message handlers
void COwnDrawSlider::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CRect channelRect,thumbRect;
GetChannelRect(&channelRect);
GetThumbRect(&thumbRect);
GetClientRect(&channelRect);
thumbRect=channelRect;
channelRect.top+=5;
channelRect.bottom-=5;
int mid=(channelRect.top+channelRect.bottom)/2;
int w=channelRect.right-channelRect.left;
int pos=GetPos();
int minVal,maxVal;
GetRange(minVal,maxVal);
thumbRect.left=(pos-minVal)*w/(maxVal-minVal);
thumbRect.right=thumbRect.left+(channelRect.right-channelRect.left)*0.025f;
thumbRect.top+=12;
thumbRect.bottom-=12;
//draw the channel
//CBrush whiteBrush(RGB(255,255,255));
//dc.SelectObject(whiteBrush);
//dc.FrameRect(&channelRect,&whiteBrush);
//draw the thumb
CBrush yellowBrush(RGB(255,255,0));
dc.SelectObject(yellowBrush);
dc.Ellipse(&thumbRect);
CFont myFont;
myFont.CreatePointFont(96,"Tahoma");
dc.SetBkColor(RGB(0,0,0));
dc.SetTextColor(RGB(255,255,255));
dc.SelectObject(&myFont);
char msg[128];
sprintf(msg,"%d",pos);
CRect msgRect=thumbRect;
msgRect.top=thumbRect.bottom;
msgRect.bottom=msgRect.top+16;
int mid0=(thumbRect.left+thumbRect.right)/2;
msgRect.left=mid0-6*strlen(msg);
msgRect.right=mid0+6*strlen(msg);
dc.DrawText(msg,strlen(msg),&msgRect,DT_CENTER);
sprintf(msg,"%d",minVal);
msgRect.left=channelRect.left;
msgRect.right=channelRect.left+12*strlen(msg);
dc.DrawText(msg,strlen(msg),&msgRect,DT_LEFT);
sprintf(msg,"%d",maxVal);
msgRect.left=channelRect.right-12*strlen(msg);
msgRect.right=channelRect.right;
dc.DrawText(msg,strlen(msg),&msgRect,DT_RIGHT);
//draw the axis
CPen yellowPen(PS_SOLID,3,RGB(255,255,255));
dc.SelectObject(yellowPen);
dc.MoveTo(channelRect.left,mid);
dc.LineTo(channelRect.right,mid);
dc.MoveTo(channelRect.left,channelRect.top+12);
dc.LineTo(channelRect.left,channelRect.bottom-12);
dc.MoveTo(channelRect.right,channelRect.top+12);
dc.LineTo(channelRect.right,channelRect.bottom-12);
CPen thinPen(PS_SOLID,1,RGB(255,255,0));
dc.SelectObject(thinPen);
int numTick=GetNumTics();
DWORD* ptick=GetTicArray();
float pixelPerTick=w*1.0/numTick;
for(int i=0;i<numTick;i++)
{
dc.MoveTo((ptick[i])*pixelPerTick+channelRect.left,mid-3);
dc.LineTo((ptick[i])*pixelPerTick+channelRect.left,mid+3);
}
// Do not call CSliderCtrl::OnPaint() for painting messages
}
HBRUSH COwnDrawSlider::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CSliderCtrl::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
// TODO: Return a different brush if the default is not desired
return black_brush;
}
BOOL COwnDrawSlider::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
RECT client;
GetClientRect(&client);
//COLORREF color=pDC->GetBkColor();
COLORREF color=::GetSysColor(COLOR_WINDOW);
//FillRect(pDC->m_hDC,&client,::GetStockObject(GRAY_BRUSH));
//pDC->FillSolidRect(&client,color);
pDC->FillSolidRect(&client,RGB(0,0,0));
return true;//do not call OnEraseBkgrnd, it will fill it white
//return CSliderCtrl::OnEraseBkgnd(pDC);
}
Create the slider using program (no resource associated with it):
pFrameSlider=new COwnDrawSlider();
pFrameSlider->Create(WS_CHILD|WS_VISIBLE|BS_OWNERDRAW|TBS_HORZ|TBS_AUTOTICKS,rButton,this,ID_FRAME_SLIDER);
//pFrameSlider->SizeToContent();
pFrameSlider->SetTicFreq(50);
pFrameSlider->ShowWindow(SW_SHOW);
In the parent window, the NM_RELEASECAPTURE is trapped. When created, the rect=[9,57,384,822]. The rect obtained by the GetChannelRect is [19,23, 8, 40]. and the mouse operation is limited in this rect.
My question is: why the two rect are so different? How can I change the channel rect by program (without any resource associated with it)?

Resources