Enabling CListCtrl marquee selection - windows

I'm using an MFC CListCtrl in report mode and enabled full-row selection and alpha-blended marquee selection via
const DWORD extendedStyle = m_wndList.GetExtendedStyle();
m_wndList.SetExtendedStyle(extendedStyle | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT);
Marquee selection, i.e., spanning a selection rectangle across multiple items while holding the left mouse button, works if I start the selection in the area of the CListCtrl window which is not occupied by items.
However, if I click on an item and want to start marquee selection from there, the selection rectangle is not created. This is in contrast to, e.g., Windows Explorer where marquee selection also works if initiated from a selected item.
As MCVE, I've used the 'New Project Wizard' on Visual Studio 2017 and created a dialog-based MFC Application and placed a 'List Control' on the dialog resource. This list control is wired to a member variable CListCtrl m_wndList via DDX and gets the extended styles as mentioned above.
ListScroll.h
#pragma once
#ifndef __AFXWIN_H__
#error "include 'stdafx.h' before including this file for PCH"
#endif
#include "resource.h"
class CListScrollApp : public CWinApp
{
public:
CListScrollApp();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
DECLARE_MESSAGE_MAP()
};
extern CListScrollApp theApp;
ListScroll.cpp
#include "stdafx.h"
#include "ListScroll.h"
#include "ListScrollDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
BEGIN_MESSAGE_MAP(CListScrollApp, CWinApp)
END_MESSAGE_MAP()
CListScrollApp::CListScrollApp() {}
CListScrollApp theApp;
BOOL CListScrollApp::InitInstance() {
CWinApp::InitInstance();
CListScrollDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK) {
// Do nothing
} else if (nResponse == IDCANCEL) {
// Do nothing
} else if (nResponse == -1) {
TRACE(traceAppMsg, 0, "Warning: dialog creation failed, so application is terminating unexpectedly.\n");
}
return FALSE;
}
int CListScrollApp::ExitInstance() {
return CWinApp::ExitInstance();
}
ListScrollDlg.h
#pragma once
#include "afxcmn.h"
class CListScrollDlg : public CDialog
{
public:
CListScrollDlg(CWnd* pParent = NULL);
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_LISTSCROLL_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
private:
CListCtrl m_wndList;
};
ListScrollDlg.cpp
#include "stdafx.h"
#include "ListScroll.h"
#include "ListScrollDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CListScrollDlg::CListScrollDlg(CWnd* pParent /*=NULL*/)
: CDialog(IDD_LISTSCROLL_DIALOG, pParent)
{}
void CListScrollDlg::DoDataExchange(CDataExchange* pDX) {
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST1, m_wndList);
}
BEGIN_MESSAGE_MAP(CListScrollDlg, CDialog)
END_MESSAGE_MAP()
BOOL CListScrollDlg::OnInitDialog() {
CDialog::OnInitDialog();
const DWORD extendedStyle = m_wndList.GetExtendedStyle();
m_wndList.SetExtendedStyle(extendedStyle | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT);
m_wndList.InsertColumn(1, _T("Test 1"), 0, 200);
m_wndList.InsertColumn(1, _T("Test 2"), 0, 800);
m_wndList.InsertItem(1, _T("Item 1"), 0);
m_wndList.InsertItem(1, _T("Item 2"), 0);
return TRUE;
}
ListScroll.rc
IDD_LISTSCROLL_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "ListScroll"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,179,50,14
PUSHBUTTON "Cancel",IDCANCEL,263,179,50,14
CTEXT "TODO: Place dialog controls here.",IDC_STATIC,10,96,300,8
CONTROL "",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,306,162
END
Can anyone point me in the right direction and explain, how to enable marquee selection that is starting from an individual item in the list control?
Edit: Here's an example of marquee selection from Windows Explorer in Windows 10

Related

Preview handler only works when application is invoked from Explorer

I made a simple application that creates a window and then uses preview handlers to display a preview of a file passed as an argument to the application, see code below.
This works perfectly fine when just drag'n'dropping the file to preview onto the .exe file (or alternatively just hard coding the file path instead and then launching the .exe directly from explorer), but fails for almost all preview handlers when invoking it from a terminal (powershell, cmd, whatever) with e.g. ./main.exe testfile.docx. It also fails when trying to invoke it from other processes.
The issue i described happens (among others) for the following preview handlers:
MS Office preview handler for excel files, CLSID {00020827-0000-0000-C000-000000000046}
MS Office preview handler for word files, CLSID {84F66100-FF7C-4fb4-B0C0-02CD7FB668FE}
Edge preview handler for pdf files, CLSID {3A84F9C2-6164-485C-A7D9-4B27F8AC009E}
MS Office preview handler for power point files, CLSID {65235197-874B-4A07-BDC5-E65EA825B718}. For the power point preview handler, the initialize call to the IInitializeWithFile interface fails hresult 0x86420003
It works fine for the following preview handlers:
Windows provided preview handler for text files, CLSID {1531d583-8375-4d3f-b5fb-d23bbd169f22}
Windows provided preview handler for html files, CLSID {f8b8412b-dea3-4130-b36c-5e8be73106ac}
When i say it "fails", i mean that it just doesn't display a preview, none of the function calls fail. See images:
test.xlsx excel file dragged onto .exe file, same result when hardcoding path to test.xlsx and just starting the application from explorer:
Path to test.xlsx passed as command line argument (./main.exe ./test.xlsx in powershell):
I am not sure, what would cause this or where to even start looking or what to search for, so any input on this would be appreciated.
Note: I asked a similar question a few days ago, but that was a seperate issue.
Compile with cl /std:c++20 /EHsc main.cpp, expects path to a file to preview as first command line parameter. Since i used a bit of winrt, this requires windows 10.
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "WindowsApp.lib")
#pragma comment(lib, "Ole32.lib")
#include <iostream>
#include <string>
#include <string_view>
#include <array>
#include <filesystem>
#include <Windows.h>
#include <shlwapi.h>
#include <winrt/base.h>
#include <ShObjIdl.h>
class Preview{
winrt::com_ptr<IPreviewHandler> mPreviewHandler;
bool mIsActive = false;
bool mIsInitialized = false;
std::filesystem::path mFilePath;
HWND mHwnd;
RECT mRect;
public:
Preview(const std::filesystem::path& filePath, const HWND hwnd, const RECT& rect):
mFilePath(filePath), mHwnd(hwnd), mRect(rect){
createPreviewHandler();
initialize();
};
void setRect(const RECT& rect){
mPreviewHandler->SetRect(&rect);
mRect = rect;
}
void setWindow(const HWND hwnd, const RECT& rect){
mPreviewHandler->SetWindow(hwnd, &rect);
mHwnd = hwnd;
setRect(rect);
}
void startPreview(){
if(!mIsActive){
if(!mIsInitialized){
initialize();
}
mPreviewHandler->DoPreview();
setRect(mRect);
mIsActive = true;
}
}
void stopPreview(){
if(mIsActive){
mPreviewHandler->Unload();
mIsActive = false;
mIsInitialized = false;
}
}
private:
void createPreviewHandler(){
CLSID previewHandlerId = getShellExClsidForType(mFilePath.extension());
mPreviewHandler.capture(CoCreateInstance, previewHandlerId, nullptr, CLSCTX_LOCAL_SERVER);
}
CLSID getShellExClsidForType(const std::wstring& extension){
winrt::hresult res;
DWORD size;
std::array<wchar_t, 39> interfaceIdWstr;
size = StringFromGUID2(IID_IPreviewHandler, interfaceIdWstr.data(), interfaceIdWstr.size());
if(!size){
winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
}
std::array<wchar_t, 39> exIdWstr;
res = AssocQueryStringW(ASSOCF_INIT_DEFAULTTOSTAR,
ASSOCSTR_SHELLEXTENSION,
extension.c_str(),
interfaceIdWstr.data(),
exIdWstr.data(),
&size);
winrt::check_hresult(res);
CLSID exId;
res = IIDFromString(exIdWstr.data(), &exId);
winrt::check_hresult(res);
return(exId);
}
void initialize(){
initializeFromPath(mFilePath);
setWindow(mHwnd, mRect);
mIsInitialized = true;
}
void initializeFromPath(const std::filesystem::path& filePath){
if(!initWithFile(filePath) && !initWithStream(filePath)){
winrt::throw_hresult(E_NOINTERFACE);
}
}
bool initWithFile(const std::filesystem::path& filePath){
if(!mPreviewHandler.try_as<IInitializeWithFile>()){
return(false);
}
winrt::check_hresult(mPreviewHandler.as<IInitializeWithFile>()->Initialize(filePath.c_str(), STGM_READ));
return(true);
}
bool initWithStream(const std::filesystem::path& filePath){
if(!mPreviewHandler.try_as<IInitializeWithStream>()){
return(false);
}
winrt::com_ptr<IStream> stream;
stream.capture([](LPCWSTR pszFile, DWORD grfMode, REFIID riid, void **ppstm)
{return(SHCreateStreamOnFileEx(pszFile, grfMode, 0, false, nullptr, reinterpret_cast<IStream**>(ppstm)));},
filePath.c_str(), STGM_READ | STGM_SHARE_DENY_WRITE | STGM_FAILIFTHERE);
winrt::check_hresult(mPreviewHandler.as<IInitializeWithStream>()->Initialize(stream.get(), STGM_READ));
return(true);
}
};
HMODULE getCurrentModule(){
HMODULE hModule;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(LPCTSTR)getCurrentModule,
&hModule);
return(hModule);
}
LRESULT windowProc(HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam){
LPARAM ret = 0;
switch(msg){
case WM_CLOSE:
{
PostQuitMessage(0);
ret = 0;
}break;
default:
{
ret = DefWindowProc(hwnd, msg, wParam, lParam);
}break;
}
return(ret);
}
HWND createTestWindow(){
WNDCLASSW wndClass;
wndClass.style = 0;
wndClass.lpfnWndProc = windowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = getCurrentModule();
wndClass.hIcon = nullptr;
wndClass.hCursor = nullptr;
wndClass.hbrBackground = nullptr;
wndClass.lpszMenuName = nullptr;
wndClass.lpszClassName = L"test";
ATOM wndAtom = RegisterClassW(&wndClass);
if(!wndAtom){
winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
}
HWND window = CreateWindowExW(0,
L"Test",
L"",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
wndClass.hInstance,
nullptr);
if(!window){
winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
}
ShowWindow(window, SW_NORMAL);
return(window);
}
int main(int argc, char *argv[]){
try{
winrt::check_hresult(CoInitialize(nullptr));
if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)){
winrt::throw_hresult(E_FAIL);
}
if(argc != 2){
return(1);
}
std::filesystem::path filePath(argv[1]);
HWND window = createTestWindow();
RECT rect;
GetClientRect(window, &rect);
Preview preview(filePath, window, rect);
preview.startPreview();
MSG msg;
while(GetMessageW(&msg, nullptr, 0, 0) != 0){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}catch(winrt::hresult_error err){
std::wcout << "0x" << std::hex << err.code() << " " << static_cast<std::wstring_view>(err.message());
}catch(...){}
}
Call GetFullPathName on argv[1]. The shell functions can't parse a relative path into a pidl.

CListCtrl resets horizontal scroll position

I'm having an issue with an MFC CListCtrl in report mode where whenever
the horizontal scroll position is not at the beginning
and the Shift key is pressed
as soon as an item is selected via a left-button mouse click, the horizontal scroll state reverts back to the beginning.
I'm using Visual Studio 2015 and was able to reproduce this with an example application. A simple dialog-based MFC application with a CListCtrl, two columns and two items shows this behavior under Windows 7 and Windows 10.
Is there a way to prevent this, so that the CListCtrl keeps the scroll position?
Update
I've added a MCVE below, where I used the New-Project Wizard of VS 2015 to create a dialog-based MFC application named ListScroll where I statically link MFC.
I've also noticed that there doesn't need to be already one item selected. Even selecting one item with the Shift key pressed shows this on my systems.
ListScroll.h
#pragma once
#ifndef __AFXWIN_H__
#error "include 'stdafx.h' before including this file for PCH"
#endif
#include "resource.h"
class CListScrollApp : public CWinApp
{
public:
CListScrollApp();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
DECLARE_MESSAGE_MAP()
};
extern CListScrollApp theApp;
ListScroll.cpp
#include "stdafx.h"
#include "ListScroll.h"
#include "ListScrollDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
BEGIN_MESSAGE_MAP(CListScrollApp, CWinApp)
END_MESSAGE_MAP()
CListScrollApp::CListScrollApp() {}
CListScrollApp theApp;
BOOL CListScrollApp::InitInstance() {
CWinApp::InitInstance();
CListScrollDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK) {
// Do nothing
} else if (nResponse == IDCANCEL) {
// Do nothing
} else if (nResponse == -1) {
TRACE(traceAppMsg, 0, "Warning: dialog creation failed, so application is terminating unexpectedly.\n");
}
return FALSE;
}
int CListScrollApp::ExitInstance() {
return CWinApp::ExitInstance();
}
ListScrollDlg.h
#pragma once
#include "afxcmn.h"
class CListScrollDlg : public CDialog
{
public:
CListScrollDlg(CWnd* pParent = NULL);
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_LISTSCROLL_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
private:
CListCtrl m_wndList;
};
ListScrollDlg.cpp
#include "stdafx.h"
#include "ListScroll.h"
#include "ListScrollDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CListScrollDlg::CListScrollDlg(CWnd* pParent /*=NULL*/)
: CDialog(IDD_LISTSCROLL_DIALOG, pParent)
{}
void CListScrollDlg::DoDataExchange(CDataExchange* pDX) {
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST1, m_wndList);
}
BEGIN_MESSAGE_MAP(CListScrollDlg, CDialog)
END_MESSAGE_MAP()
BOOL CListScrollDlg::OnInitDialog() {
CDialog::OnInitDialog();
m_wndList.InsertColumn(1, _T("Test 1"), 0, 200);
m_wndList.InsertColumn(1, _T("Test 2"), 0, 800);
m_wndList.InsertItem(1, _T("Item 1"), 0);
m_wndList.InsertItem(1, _T("Item 2"), 0);
return TRUE;
}
ListScroll.rc
IDD_LISTSCROLL_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "ListScroll"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,179,50,14
PUSHBUTTON "Cancel",IDCANCEL,263,179,50,14
CTEXT "TODO: Place dialog controls here.",IDC_STATIC,10,96,300,8
CONTROL "",IDC_LIST1,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,306,162
END

Creating separate MFC GUI thread, can't move/resize/maximize CWnd

I am creating a CWinThread that will have it's own GUI. When I create a CWnd on that thread, it displays, but I can't move the window. I am sure that the message pump is running, because I can performn MoveWindow from another thread, and the window moves.
UIThread.h
#pragma once
class CUIThread : public CWinThread
{
public:
DECLARE_DYNCREATE(CUIThread)
CUIThread();
// Attributes
public:
HWND hwnd;
// Operations
public:
void KillThread();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CGDIThread)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CUIThread();
protected:
virtual BOOL InitInstance();
// Generated message map functions
//{{AFX_MSG(CUIThread)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
UIThread.cpp
#include "stdafx.h"
#include "UIThread.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CUIThread
IMPLEMENT_DYNCREATE(CUIThread, CWinThread)
BEGIN_MESSAGE_MAP(CUIThread, CWinThread)
//{{AFX_MSG_MAP(CUIThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
CUIThread::CUIThread() : hwnd(NULL)
{
m_bAutoDelete = FALSE;
}
BOOL CUIThread::InitInstance()
{
this->m_pMainWnd = new CWnd;
m_pMainWnd->Create(_T("STATIC"), _T("Hi"), WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CRect(0, 0, 500, 500), CWnd::GetDesktopWindow(), 1234);
this->hwnd = m_pMainWnd->GetSafeHwnd();
return TRUE;
}
CUIThread::~CUIThread()
{
}
void CUIThread::KillThread()
{
// Note: this function is called in the context of
// other threads, not the thread itself.
this->PostThreadMessage(WM_QUIT, 0, 0);
// allow thread to run at higher priority during
// kill process
SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL);
WaitForSingleObject(m_hThread, INFINITE);
}
main.cpp
...
CUIThread* pUIThread = static_cast< CUIThread*>(AfxBeginThread(RUNTIME_CLASS(CUIThread)));
getchar();
MoveWindow(pUIThread->hwnd, 100, 100, 500, 500, true); // works
getchar();
CloseWindow(pUIThread->hwnd); // works
getchar();
pUIThread->KillThread(); // works
delete pUIThread;
getchar();
...
I can see the window, I just can't move/maximize/resize it.
I believe you are creating the window the wrong way. You are creating a child window with the desktop as parent window. This should work:
LPCSTR strClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE);
VERIFY(m_pMainWnd->CreateEx(0, strClass, _T("title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CRect(0, 0, 500, 500), NULL, 0));

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)?

How to create a resizable CDialog in MFC?

I have to create a dialog based application, instead of old CFormView type of design. But CDialog produces fixed-size dialogs. How can I create dialog based applications with resizable dialogs?
In the RC resource file if the dialog has this style similar to this it will be fixed size:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
If the dialog has this style it will be sizeable:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
With these sizable frame options the dialog will be re-sizeable but you will still need to do a lot of work handling the WM_SIZE message to manage the sizing an positioning of the controls within the dialog.
In addition to setting the style to WS_THICKFRAME, you'll probably also want to have a system to move and resize the controls in a dialog as the dialog is resized. For my own personal use I've created a base class to replace CDialog that has this capability. Derive from this class and in your InitDialog function call the AutoMove function for each child control to define how much it should move and how much it should resize relative to the parent dialog. The size of the dialog in the resource file is used as a minimum size.
BaseDialog.h:
#if !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)
#define AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <vector>
class CBaseDialog : public CDialog
{
// Construction
public:
CBaseDialog(UINT nIDTemplate, CWnd* pParent = NULL); // standard constructor
void AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct);
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CBaseDialog)
protected:
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CBaseDialog)
virtual BOOL OnInitDialog();
afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
afx_msg void OnSize(UINT nType, int cx, int cy);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
public:
bool m_bShowGripper; // ignored if not WS_THICKFRAME
private:
struct SMovingChild
{
HWND m_hWnd;
double m_dXMoveFrac;
double m_dYMoveFrac;
double m_dXSizeFrac;
double m_dYSizeFrac;
CRect m_rcInitial;
};
typedef std::vector<SMovingChild> MovingChildren;
MovingChildren m_MovingChildren;
CSize m_szInitial;
CSize m_szMinimum;
HWND m_hGripper;
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)
BaseDialog.cpp:
#include "stdafx.h"
#include "BaseDialog.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CBaseDialog::CBaseDialog(UINT nIDTemplate, CWnd* pParent /*=NULL*/)
: CDialog(nIDTemplate, pParent),
m_bShowGripper(true),
m_szMinimum(0, 0),
m_hGripper(NULL)
{
}
BEGIN_MESSAGE_MAP(CBaseDialog, CDialog)
//{{AFX_MSG_MAP(CBaseDialog)
ON_WM_GETMINMAXINFO()
ON_WM_SIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CBaseDialog::AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct)
{
ASSERT((dXMovePct + dXSizePct) <= 100.0); // can't use more than 100% of the resize for the child
ASSERT((dYMovePct + dYSizePct) <= 100.0); // can't use more than 100% of the resize for the child
SMovingChild s;
GetDlgItem(iID, &s.m_hWnd);
ASSERT(s.m_hWnd != NULL);
s.m_dXMoveFrac = dXMovePct / 100.0;
s.m_dYMoveFrac = dYMovePct / 100.0;
s.m_dXSizeFrac = dXSizePct / 100.0;
s.m_dYSizeFrac = dYSizePct / 100.0;
::GetWindowRect(s.m_hWnd, &s.m_rcInitial);
ScreenToClient(s.m_rcInitial);
m_MovingChildren.push_back(s);
}
BOOL CBaseDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// use the initial dialog size as the default minimum
if ((m_szMinimum.cx == 0) && (m_szMinimum.cy == 0))
{
CRect rcWindow;
GetWindowRect(rcWindow);
m_szMinimum = rcWindow.Size();
}
// keep the initial size of the client area as a baseline for moving/sizing controls
CRect rcClient;
GetClientRect(rcClient);
m_szInitial = rcClient.Size();
// create a gripper in the bottom-right corner
if (m_bShowGripper && ((GetStyle() & WS_THICKFRAME) != 0))
{
SMovingChild s;
s.m_rcInitial.SetRect(-GetSystemMetrics(SM_CXVSCROLL), -GetSystemMetrics(SM_CYHSCROLL), 0, 0);
s.m_rcInitial.OffsetRect(rcClient.BottomRight());
m_hGripper = CreateWindow(_T("Scrollbar"), _T("size"), WS_CHILD | WS_VISIBLE | SBS_SIZEGRIP,
s.m_rcInitial.left, s.m_rcInitial.top, s.m_rcInitial.Width(), s.m_rcInitial.Height(),
m_hWnd, NULL, AfxGetInstanceHandle(), NULL);
ASSERT(m_hGripper != NULL);
if (m_hGripper != NULL)
{
s.m_hWnd = m_hGripper;
s.m_dXMoveFrac = 1.0;
s.m_dYMoveFrac = 1.0;
s.m_dXSizeFrac = 0.0;
s.m_dYSizeFrac = 0.0;
m_MovingChildren.push_back(s);
// put the gripper first in the z-order so it paints first and doesn't obscure other controls
::SetWindowPos(m_hGripper, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
}
}
return TRUE; // return TRUE unless you set the focus to a control
}
void CBaseDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
CDialog::OnGetMinMaxInfo(lpMMI);
if (lpMMI->ptMinTrackSize.x < m_szMinimum.cx)
lpMMI->ptMinTrackSize.x = m_szMinimum.cx;
if (lpMMI->ptMinTrackSize.y < m_szMinimum.cy)
lpMMI->ptMinTrackSize.y = m_szMinimum.cy;
}
void CBaseDialog::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
int iXDelta = cx - m_szInitial.cx;
int iYDelta = cy - m_szInitial.cy;
HDWP hDefer = NULL;
for (MovingChildren::iterator p = m_MovingChildren.begin(); p != m_MovingChildren.end(); ++p)
{
if (p->m_hWnd != NULL)
{
CRect rcNew(p->m_rcInitial);
rcNew.OffsetRect(int(iXDelta * p->m_dXMoveFrac), int(iYDelta * p->m_dYMoveFrac));
rcNew.right += int(iXDelta * p->m_dXSizeFrac);
rcNew.bottom += int(iYDelta * p->m_dYSizeFrac);
if (hDefer == NULL)
hDefer = BeginDeferWindowPos(m_MovingChildren.size());
UINT uFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER;
if ((p->m_dXSizeFrac != 0.0) || (p->m_dYSizeFrac != 0.0))
uFlags |= SWP_NOCOPYBITS;
DeferWindowPos(hDefer, p->m_hWnd, NULL, rcNew.left, rcNew.top, rcNew.Width(), rcNew.Height(), uFlags);
}
}
if (hDefer != NULL)
EndDeferWindowPos(hDefer);
if (m_hGripper != NULL)
::ShowWindow(m_hGripper, (nType == SIZE_MAXIMIZED) ? SW_HIDE : SW_SHOW);
}
Since Visual Studio 2015, you can use MFC Dynamic Dialog Layout, but it seems, there is no way to restrict dialog size to minimal size (still only the old way by handling WM_GETMINMAXINFO).
Dynamic layout can be done:
at design time in resource editor by selecting the control and setting the Moving Type and Sizing Type properties (this emits new AFX_DIALOG_LAYOUT section into .rc file);
or programatically using the CMFCDynamicLayout class.
Documentation: Dynamic Layout
If your using a dialog template then open the dialog template in the resource editor and set the Style property to Popup and the Border property to Resizing. I'm pretty sure this will do the same as what jussij said and set the WS_POPUP and WS_THICKFRAME styles. To set these dynamically then override the PreCreateWindow function and add the following:
cs.style |= WS_POPUP | WS_THICKFRAME;
There is no easy way to do this. Basically, you will need to dynamically layout controls when the window size is changed.
See http://www.codeproject.com/KB/dialog/resizabledialog.aspx for an example
I have some blog instructions on how to create a very minimalist re-sizeable dialog in MFC.
It is basically an implementation of Paulo Messina's posting at CodeProject
but with as much extraneous stuff removed as possible, just to help clarify how to do it better.
It is fairly straightforward to implement once you've had a bit of practice: the important bits are to:
i. ensure you have his CodeProject libraries etc pulled into your project and it all compiles correctly.
ii. do the extra initialization required inside the OnInitDialog method: make the gripper visible, set the maximum dilog size, add anchor points to the dialog control items that you wish to 'stretch' etc.
iii. Replace usage of CDialog with CResizableDialog at the appropriate points: in the dialog class definition, constructor, DoDataExchange, BEGIN_MESSAGE_MAP, OnInitDialog etc.
I've tried many MFC layout libraries and found this one the best: http://www.codeproject.com/KB/dialog/layoutmgr.aspx. Check out the comments there for some bug fixes and improvements (disclaimer: some of them by me ;) ). When you use this library, setting the correct resize flags on your window will be handled for you.

Resources