[WTL/ATL]_[中级]_[自定义TrackBar]

Posted infoworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[WTL/ATL]_[中级]_[自定义TrackBar]相关的知识,希望对你有一定的参考价值。

场景

  1. 开发WTL/ATL界面程序时, 有时候会需要微调控件进行缓慢增减数值.

    但是微调控件在大数值调整上往往比较麻烦,需要点击很多次。这时就需要使用TrackBar控件[1]进行大数值的快速变化.

  2. 标准滑块控件很明显很难看,那么如何进行定制它的channel(通道,水平的选中和未选中的部分)和thumb(滑块)呢?比如如何定义圆形的滑块?

说明

  1. WTL使用在atlctrls.h声明的CTrackBarCtrl类来使用Win32Trackbar控件. 这个标准控件本身并没有方法设置thumb的形状,或者设置BITMAP图形作为thumb。也不能设置thumb的区域CRect,只有一个获取区域的方法GetThumbRect,对应着TBM_GETTHUMBRECT[4]消息。无法设置thumb区域大小,也就意味着无法设置绘制圆形所需的正方形区域, 以下会说明为什么定制圆形的thumb.

    CTrackBarCtrl progressQuality_;
    progressQuality_.Create(m_hWnd,CRect(CPoint(100,100),CSize(400,60)),
    		NULL,WS_VISIBLE|WS_CHILD |TBS_ENABLESELRANGE |TBS_TOOLTIPS,0,kConverterContentQualitySpinner);
    progressQuality_.SetRangeMax(100);
    progressQuality_.SetPos(50);
    progressQuality_.SetSelEnd(100);
    progressQuality_.SetSel(0,50);
    
  2. 标准Trackbar支持使用NM_CUSTOMDRAW通知[5]来部分定制Trackbar[6]的外观。在WTL里可以扩展CCustomDraw来处理NM_CUSTOMDRAW消息。在自定义ListView[3]文章里有介绍这个类的使用. 这里trackbar的定制并不能控制绘制thumb的大小,而标准控件的thumb大小默认是长方形的区域,无法改为正方形,所以自定义绘制定制有限,这个方案不行。简单说下它就是在OnPrePaint方法里返回CDRF_NOTIFYSUBITEMDRAW值,才会调用OnItemPrePaint, 之后判断dwItemSpec当前是TBCD_THUMB才能对thumb进行自绘。

    DWORD BASTrackBar::OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw)
    
    	return CDRF_NOTIFYSUBITEMDRAW;
    
    
    DWORD BASTrackBar::OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw)
    
    	if(lpNMCustomDraw->dwItemSpec == TBCD_THUMB)
    		
    		auto rc = lpNMCustomDraw->rc;
    		lpNMCustomDraw->rc = rc;
    		CMemoryDC dc(lpNMCustomDraw->hdc,rc);
    		dc.FillRect(&rc,(HBRUSH) GetStockObject(WHITE_BRUSH));
    		dc.Ellipse(rc.left,rc.top,rc.right,rc.bottom);
    		return CDRF_SKIPDEFAULT;
    	else if(lpNMCustomDraw->dwItemSpec == TBCD_CHANNEL)
    		auto rc = lpNMCustomDraw->rc;
    		CMemoryDC dc(lpNMCustomDraw->hdc,rc);
    		dc.FillRect(&lpNMCustomDraw->rc, (HBRUSH) GetStockObject(GRAY_BRUSH));
    		return CDRF_SKIPDEFAULT;
    	
    	return CDRF_DODEFAULT;
    
    

  1. 通过NM_CUSTOMDRAW方案无法解决绘制圆形thumb的问题,那么只能重新绘制一个Trackbar。观察trackbar标准控件,最重要的就是做一个可移动的thumb控件,之后限制它只能水平直线移动,再通过它移动的距离来换算代表的值,也可以通过它移动过的值来绘制它左边选中的channel. 移动控件最主要就是这3个消息. 在按下左键WM_LBUTTONDOWN的时候捕抓鼠标,之后在鼠标移动WM_MOUSEMOVE的时候移动thumb, 最后就是在左键松开WM_LBUTTONUP的时候释放捕抓鼠标。之后就是WM_PAINT消息里绘制thumb. 至于channel可以定制一个控件,也可以绘制在父窗口上。我这里写的例子为了避免创建过多的窗口,channel部分是绘制在父窗口上。之后使用一个管理类DhTrackBar来管理DhTrackThumb的移动反馈和channel的绘制,本身DhTrackBar并不是一个窗口。

    class DhTrackBar : public CMessageMap
    
  2. DhTrackThumb的移动时,为了能把移动的位置反馈给DhTrackBar,可以借助标准trackbarTRBN_THUMBPOSCHANGING发送通知给父窗口。

NMTRBTHUMBPOSCHANGING thumb = 0;
thumb.nReason = TB_THUMBTRACK;
thumb.dwPos = x;
thumb.hdr.hwndFrom = m_hWnd;
thumb.hdr.idFrom = GetWindowLong(GWL_ID);
thumb.hdr.code = TRBN_THUMBPOSCHANGING;

::SendMessage(GetParent(),WM_NOTIFY,0,(LPARAM)&thumb);

例子

dh_track_thumb.h

#ifndef __DH_TRACk_THUMB
#define __DH_TRACk_THUMB


class DhTrackThumb :public CWindowImpl<DhTrackThumb,CWindow>

public:
	DhTrackThumb();
	~DhTrackThumb();

	BEGIN_MSG_MAP_EX(DhTrackThumb)
	MSG_WM_LBUTTONDOWN(OnLButtonDown)
	MSG_WM_LBUTTONUP(OnLButtonUp)
	MSG_WM_MOUSEMOVE(OnMouseMove)
	MSG_WM_PAINT(OnPaint)
	END_MSG_MAP()

	// 所在区域的位置,主要是为了在限定区域平移.
	void setPositionRange(CPoint pointStart,CPoint pointEnd);
	CPoint getPosition();
	void setPosition(CPoint point);
	void setPositionX(float x);
	void syncToPosition();
	void setColor(COLORREF color);

protected:
	void OnMouseMove(UINT nFlags, CPoint point);
	void OnLButtonDown(UINT nFlags, CPoint point);
	void OnLButtonUp(UINT nFlags, CPoint point);
	void OnPaint(CDCHandle dc);

private:
	CPoint pointStart_;
	CPoint pointEnd_;
	CPoint pointCurrent_;
;

#endif

dh_track_thumb.cpp

#include "stdafx.h"

#include "dh_track_thumb.h"
#include <GdiPlus.h>

DhTrackThumb::DhTrackThumb()




DhTrackThumb::~DhTrackThumb()

	


void DhTrackThumb::setPositionRange(CPoint pointStart,CPoint pointEnd)

	pointEnd_ = pointEnd;
	pointStart_ = pointStart;


void DhTrackThumb::OnLButtonDown(UINT nFlags, CPoint point)

	OutputDebugString(L"OnLButtonDown\\n");
	SetCapture();


void DhTrackThumb::OnLButtonUp(UINT nFlags, CPoint point)

	OutputDebugString(L"OnLButtonUp\\n");
	if(GetCapture() == m_hWnd)
		ReleaseCapture();


CPoint DhTrackThumb::getPosition()

	return pointCurrent_;


void DhTrackThumb::setPosition(CPoint point)

	pointCurrent_ = point;


void DhTrackThumb::syncToPosition()

	SetWindowPos(NULL,pointCurrent_.x,pointCurrent_.y,0,0,SWP_NOSIZE);


void DhTrackThumb::setPositionX(float x)

	pointCurrent_.x = x;
	setPosition(pointCurrent_);


void DhTrackThumb::OnMouseMove(UINT nFlags, CPoint point)

	if(nFlags != MK_LBUTTON || GetCapture() != m_hWnd)
		return;

	CPoint op;
	auto pos = GetParent().ClientToScreen(&op);
	ClientToScreen(&point);

	auto x = point.x - op.x;
	x = (x >= pointEnd_.x)?pointEnd_.x:
		(x <= pointStart_.x)? pointStart_.x: x;

	pointCurrent_.x = x;
	pointCurrent_.y = pointStart_.y;

	SetWindowPos(NULL,x,pointStart_.y,0,0,SWP_NOSIZE);

	NMTRBTHUMBPOSCHANGING thumb = 0;
	thumb.nReason = TB_THUMBTRACK;
	thumb.dwPos = x;
	thumb.hdr.hwndFrom = m_hWnd;
	thumb.hdr.idFrom = GetWindowLong(GWL_ID);
	thumb.hdr.code = TRBN_THUMBPOSCHANGING;

	::SendMessage(GetParent(),WM_NOTIFY,0,(LPARAM)&thumb);



void DhTrackThumb::OnPaint(CDCHandle dc1)

	CPaintDC hdc(m_hWnd);
	hdc.SetBkMode(TRANSPARENT);

	CRect rect;
	GetClientRect(&rect);
	Gdiplus::Bitmap bmp(int(rect.right),int(rect.bottom));

	Gdiplus::Graphics graphics(&bmp);
	graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
	graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	Gdiplus::Color color;
	color.SetFromCOLORREF(RGB(255,255,255));
	Gdiplus::SolidBrush brushBkg(color);
	graphics.FillRectangle(&brushBkg,rect.left,rect.top,rect.Width(),rect.Height());

	color.SetFromCOLORREF(RGB(91,155,213));
	Gdiplus::SolidBrush brush(color);
	graphics.FillEllipse(&brush,rect.left,rect.top,rect.Width(),rect.Height());

	Gdiplus::Graphics graphics1(hdc);
	Gdiplus::CachedBitmap cachedBmp(&bmp,&graphics1);
	graphics1.DrawCachedBitmap(&cachedBmp,0,0);


dh_track_bar.h

#ifndef DH_TRACK_BAR_H
#define DH_TRACK_BAR_H

#include <GdiPlus.h>
#include <functional>

using namespace std;

class DhTrackThumb;


class DhTrackBar : public CMessageMap

public:
	DhTrackBar();
	~DhTrackBar();

	BEGIN_MSG_MAP_EX(DhTrackBar)
	NOTIFY_HANDLER_EX(thumbId_,TRBN_THUMBPOSCHANGING,onThumbPosChanging)
	END_MSG_MAP()

	void ready(HWND parent,DWORD thumbId);
	void setValueRange(int nMin,int nMax);

	void paint(Gdiplus::Graphics& graphics);
	void updateLayout(CRect rect);
	void show(int flag);
	void reset();

	void setFuncValueChanging(function<void(int)>& func);
	void setValue(int value);

public:
	inline void setChannelHeight(int height)
		channelHeight_ = height;
	

protected:
	LRESULT onThumbPosChanging(LPNMHDR pnmh);
	function<void(int)> funcValueChanging_;

private:
	DhTrackThumb* thumb_;
	DWORD thumbId_;
	HWND parent_;

	CRect rect_;

	int channelUsedWidth_;
	int channelHeight_;

	int nMin_;
	int nMax_;
	int value_;
;


#endif

dh_track_bar.cpp

#include "stdafx.h"

#include <sstream>
#include "dh_track_bar.h"
#include "dh_track_thumb.h"

using namespace std;

DhTrackBar::DhTrackBar()

	thumb_ = nullptr;
	value_ = 0;


DhTrackBar::~DhTrackBar()




void DhTrackBar::reset()

	channelUsedWidth_ = 0;
	channelHeight_ = 0;


void DhTrackBar::show(int flag)

	thumb_->ShowWindow(flag);


void DhTrackBar::ready(HWND parent,DWORD thumbId)

	parent_ = parent;
	thumbId_ = thumbId;
	nMin_ = 0;
	nMax_ = 0;

	thumb_ = new DhTrackThumb();
	thumb_->Create(parent_,CRect(),
		NULL,WS_CHILD,0,thumbId);


void DhTrackBar::setValueRange(int nMin,int nMax)

	nMin_ = nMin;
	nMax_ = nMax;


void DhTrackBar::setValue(int value)

	value_ = value;
	channelUsedWidth_ = value_*rect_.Width()/100;

	thumb_->setPositionX(rect_.left+channelUsedWidth_);
	thumb_->syncToPosition();

	::InvalidateRect(parent_,rect_,FALSE);
	::UpdateWindow(parent_);


LRESULT DhTrackBar::onThumbPosChanging(LPNMHDR pnmh)

	auto thumbPosData = (NMTRBTHUMBPOSCHANGING*)pnmh;

	channelUsedWidth_ = thumbPosData->dwPos - rect_.left;
	::InvalidateRect(parent_,rect_,FALSE);
	::UpdateWindow(parent_);

	if(!funcValueChanging_)
		return 0;
		
	auto value = channelUsedWidth_*100/rect_.Width();
	if(value_ != value)
		value_ = value;
		funcValueChanging_(value);
	

	return 0;


void DhTrackBar::setFuncValueChanging(function<void(int)>& func)

	funcValueChanging_ = func;


void DhTrackBar::paint(Gdiplus::Graphics& graphics)

	Gdiplus::Color colorUnUsed;
	colorUnUsed.SetFromCOLORREF(RGB(234,236,238));

	Gdiplus::RectF rect(rect_.left,rect_.top,rect_.Width(),channelHeight_);
	rect.Offset(0,(rect_.Height()-channelHeight_)/2);
	Gdiplus::SolidBrush brush(colorUnUsed);
	graphics.FillRectangle(&brush,rect);

	if(channelUsedWidth_)
		Gdiplus::Color colorUsed;
		colorUsed.SetFromCOLORREF(RGB(153,206,243));
		rect.Width = channelUsedWidth_;
		Gdiplus::SolidBrush usedBrush(colorUsed);
		graphics.FillRectangle(&usedBrush,rect);
	


void DhTrackBar::updateLayout(CRect rect)

	rect_ = rect;

	auto thumbWidth = rect.Height();
	thumb_->SetWindowPos(NULL,rect_.left,rect_.top,thumbWidth,thumbWidth,0);
	thumb_->setPosition(rect_.TopLeft());

	thumb_->setPositionRange(rect.TopLeft(),CPoint(rect.right,rect.top));


使用例子

  1. 在父窗口View.h添加这个DhTrackBar的成员处理转发宏。

    BEGIN_MSG_MAP_EX(CView)
        MESSAGE_HANDLER(WM_PAINT, onPaint)
    	CHAIN_MSG_MAP_MEMBER(trackBar_)
    	REFLECT_NOTIFICATIONS()
    END_MSG_MAP()
    
  2. 创建并绑定值改变回调函数onTrackBarValueChange,这样。

    function<void(int)> func(bind(&CView::onTrackBarValueChange,this,placeholders::_1));
    trackBar_.setFuncValueChanging(func);
    trackBar_.ready(m_hWnd,kConverterContentThumb);
    trackBar_.setChannelHeight(6);
    trackBar_.updateLayout(CRect(CPoint(100,350),CSize(400,14

    以上是关于[WTL/ATL]_[中级]_[自定义TrackBar]的主要内容,如果未能解决你的问题,请参考以下文章

    [WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]

    [WTL/ATL]_[初级]_[关于窗口子类析构时崩溃的原因]

    [WTL/ATL]_[初级]_[微调控件CUpDownCtrl的使用]

    [WTL/ATL]_[初级]_[使用虚拟列表视图来解决新增大量数据卡顿问题]

    [WTL/ATL]_[初级]_[使用虚拟列表视图来解决新增大量数据卡顿问题]

    [WTL/ATL]_[Gdiplus]_[绘制虚线并设置破折号空格的宽度]