[WTL/ATL]_[中级]_[自定义TrackBar]
Posted infoworld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[WTL/ATL]_[中级]_[自定义TrackBar]相关的知识,希望对你有一定的参考价值。
场景
-
开发
WTL/ATL
界面程序时, 有时候会需要微调控件进行缓慢增减数值.
但是微调控件在大数值调整上往往比较麻烦,需要点击很多次。这时就需要使用TrackBar
控件[1]进行大数值的快速变化.
-
标准滑块控件很明显很难看,那么如何进行定制它的
channel
(通道,水平的选中和未选中的部分)和thumb
(滑块)呢?比如如何定义圆形的滑块?
说明
-
WTL
使用在atlctrls.h
声明的CTrackBarCtrl
类来使用Win32
的Trackbar
控件. 这个标准控件本身并没有方法设置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);
-
标准
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;
-
通过
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
-
在
DhTrackThumb
的移动时,为了能把移动的位置反馈给DhTrackBar
,可以借助标准trackbar
的TRBN_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));
使用例子
-
在父窗口
View.h
添加这个DhTrackBar
的成员处理转发宏。BEGIN_MSG_MAP_EX(CView) MESSAGE_HANDLER(WM_PAINT, onPaint) CHAIN_MSG_MAP_MEMBER(trackBar_) REFLECT_NOTIFICATIONS() END_MSG_MAP()
-
创建并绑定值改变回调函数
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]_[初级]_[使用虚拟列表视图来解决新增大量数据卡顿问题]