自绘时间轴控件
程序员文章站
2022-05-28 23:31:23
...
在开发视频播放器之前,需要自绘一个时间轴控件,用来控制视频播放,大体的功能有:
1、显示6个时间区域的时间轴,可以互相切换
2、点击时间轴,显示时间竖线
3、可以拖动时间竖线,当越过时间轴时,移动时间轴
4、拉伸控件,时间轴不变
控件代码:
头文件
#pragma once
// CTimeAxisCtrl
class CTimeAxisCtrl : public CWnd
{
DECLARE_DYNAMIC(CTimeAxisCtrl)
public:
CTimeAxisCtrl();
virtual ~CTimeAxisCtrl();
protected:
DECLARE_MESSAGE_MAP()
private:
struct STimePoint
{
int nPos, nTimeMS;
};
private:
COLORREF m_clrBackGround, m_clrText, m_clrGrid, m_clrLeftArea;
CFont m_FontYMD, m_FontTime;
CPen m_Pen;
static const int m_nLeftAreaWidth = 110;//左边区域的宽度
static const int m_nRightAreaWidth = 25;//右边区域的宽度
static const int m_nTopYMD = 50;//年月日的高度
STimePoint m_tpLBtnDown;//记录鼠标左击的位置和对应的时间
unsigned char m_ucTimeType;//时间类型
enum{M10, M30, H1, H3, H6, H12, H24, TT_COUNT};
static const int m_nTimeMS[TT_COUNT];//事件类型对应的总毫秒数
int m_nLeftTimeMS;//最左边的时间
bool m_bOnSize, m_bLBtnDown, m_bMouseMove;
CPoint m_ptMouseMove;
private:
void DrawAll(CDC& memDC, CRect& rect);
void DrawGrid(CDC& memDC, CRect& rectGrid, unsigned char ucTimeType);
unsigned char GetVLineType(unsigned char ucTimeType, int nHour, int nMinute, int nSecond);
public:
virtual BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID=NULL);
afx_msg void OnPaint();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnMButtonDown(UINT nFlags, CPoint point);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
};
源文件
// TimeAxisCtrl.cpp : 实现文件
//
#include "stdafx.h"
#include "MFC_Controler.h"
#include "TimeAxisCtrl.h"
const int CTimeAxisCtrl::m_nTimeMS[TT_COUNT] = {10*60*1000, 30*60*1000, 1*60*60*1000,
3*60*60*1000, 6*60*60*1000, 12*60*60*1000, 24*60*60*1000};
// CTimeAxisCtrl
IMPLEMENT_DYNAMIC(CTimeAxisCtrl, CWnd)
CTimeAxisCtrl::CTimeAxisCtrl()
{
m_clrBackGround = RGB(15,22,31);
m_clrText = RGB(96,96,96);
m_clrGrid = RGB(8,89,180);
m_clrLeftArea = RGB(32,47,64);
m_FontYMD.CreateFont(20, 0, 0, 0, 300,
FALSE, FALSE, 0, ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS, _T("Arial"));
m_FontTime.CreateFont(12, 0, 0, 0, 300,
FALSE, FALSE, 0, ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH|FF_SWISS, _T("Arial"));
m_Pen.CreatePen(PS_SOLID, 0, m_clrGrid);
m_tpLBtnDown.nPos = m_nLeftAreaWidth;
m_tpLBtnDown.nTimeMS = 0;
m_ucTimeType = H24;
m_nLeftTimeMS = 0;
m_bOnSize = false;
m_bLBtnDown = false;
m_bMouseMove = false;
}
CTimeAxisCtrl::~CTimeAxisCtrl()
{
}
BEGIN_MESSAGE_MAP(CTimeAxisCtrl, CWnd)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_MBUTTONDOWN()
ON_WM_SIZE()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
// CTimeAxisCtrl 消息处理程序
BOOL CTimeAxisCtrl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
BOOL result = FALSE;
static CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS) ;
result = CWnd::CreateEx(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE,
className, _T("Time Axis Ctrl"), dwStyle, rect, pParentWnd, nID);
return result;
}
void CTimeAxisCtrl::OnPaint()
{
CRect rect;
GetClientRect(&rect);
CPaintDC dc(this);
CDC memDC;
CBitmap memBitmap;
CBitmap* oldBitmap;
memDC.CreateCompatibleDC(&dc) ;
memBitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height()) ;
oldBitmap = (CBitmap*)memDC.SelectObject(&memBitmap) ;
if(memDC.GetSafeHdc() != NULL)
{
DrawAll(memDC, rect);
dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
}
memDC.SelectObject(oldBitmap);
}
void CTimeAxisCtrl::DrawAll(CDC& memDC, CRect& rect)
{
int nGridWidth = rect.Width()-m_nLeftAreaWidth-m_nRightAreaWidth;
int nGridMinWidth = 300;
if(nGridWidth < nGridMinWidth)
return;
//绘画整个背景
memDC.FillSolidRect(&rect, m_clrBackGround);
//绘画左边区域
CRect rectLeft(0,m_nTopYMD,m_nLeftAreaWidth,rect.Height());
memDC.FillSolidRect(&rectLeft, m_clrLeftArea);
//绘画年月日
CRect rectYMD(0, 0, m_nLeftAreaWidth, m_nTopYMD);
memDC.SelectObject(&m_FontYMD);
memDC.SetTextColor(m_clrText);
memDC.SetBkColor(m_clrBackGround);
memDC.DrawText(_T("2019/02/18"), &rectYMD, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
//选择线条铅笔
memDC.SelectObject(&m_Pen);
CRect rectGrid(m_nLeftAreaWidth, 0, rect.Width()-m_nRightAreaWidth, rect.Height());
DrawGrid(memDC, rectGrid, m_ucTimeType);
}
void CTimeAxisCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
m_tpLBtnDown.nPos = point.x;
m_tpLBtnDown.nTimeMS = -1;
m_bLBtnDown = true;
SetCapture();
Invalidate();
CWnd::OnLButtonDown(nFlags, point);
}
/*TODO:修改为外部触发时间范围缩小*/
void CTimeAxisCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
if(m_ucTimeType > 0)
{
m_ucTimeType--;
Invalidate();
}
CWnd::OnRButtonDown(nFlags, point);
}
/*TODO:修改为外部触发时间范围放大*/
void CTimeAxisCtrl::OnMButtonDown(UINT nFlags, CPoint point)
{
if(m_ucTimeType < TT_COUNT-1)
{
m_ucTimeType++;
Invalidate();
}
CWnd::OnMButtonDown(nFlags, point);
}
void CTimeAxisCtrl::DrawGrid(CDC& memDC, CRect& rectGrid, unsigned char ucTimeType)
{
int nGridWidth = rectGrid.Width();
int nGridHeight = rectGrid.Height();
//绘画时间轴横线
memDC.MoveTo(m_nLeftAreaWidth, m_nTopYMD);
memDC.LineTo(m_nLeftAreaWidth+nGridWidth+1, m_nTopYMD);
//计算最左边时间
int nMSecond = m_nTimeMS[ucTimeType], nMSecondPerPixel = nMSecond/nGridWidth;
int nLeftTimeMS = m_tpLBtnDown.nTimeMS - nMSecondPerPixel*(m_tpLBtnDown.nPos-m_nLeftAreaWidth);
//当最左边时间小于零点,则将最左边时间向右挪动
if(nLeftTimeMS < 0)
{
nLeftTimeMS = 0;
}
//当总时间超过24小时,则将最左边时间向左挪动
if(nLeftTimeMS+nMSecond > m_nTimeMS[H24])
{
nLeftTimeMS = m_nTimeMS[H24]-nMSecondPerPixel*nGridWidth;
}
if(m_bOnSize || m_bLBtnDown || m_bMouseMove)//当触发了OnSize,LButtonDown,MouseMove消息,使用原先的最左边时间
{
nLeftTimeMS = m_nLeftTimeMS;
m_bOnSize = false;
//m_bLBtnDown会在LButtonUp消息中赋值为false
//m_bMouseMove会在后续操作中赋值为false
}
else
{
m_nLeftTimeMS = nLeftTimeMS;
}
//绘画时间轴竖线
int nTimePosMS = 0;
CRect rectTime;
CString cstrTime;
int nHour = 0, nMinute = 0, nSecond = 0;
int nHourOld = -1, nMinuteOld = -1, nSecondOld = -1;//不能初始化为零,否则零点就画不出来
for(int i = 0; i <= nGridWidth; i++)
{
nTimePosMS = nLeftTimeMS+nMSecondPerPixel*i;
for(int j = nTimePosMS/100; j < nTimePosMS/100+nMSecondPerPixel/100; j++)
{//以毫秒作循环会很卡,以秒作循环会丢失精度,所以拆分开,循环条件除以100,循环处理除以10
nHour = j/10/3600; nMinute = (j/10%3600)/60; nSecond = j/10%3600%60;
if(nHour == nHourOld && nMinute == nMinuteOld && nSecond == nSecondOld)
continue;
nHourOld = nHour; nMinuteOld = nMinute; nSecondOld = nSecond;
unsigned char ucVLineType = GetVLineType(m_ucTimeType, nHour, nMinute, nSecond);
if(ucVLineType == 1)
{
//绘画大竖线
memDC.MoveTo(m_nLeftAreaWidth+i, m_nTopYMD-10);
memDC.LineTo(m_nLeftAreaWidth+i, m_nTopYMD);
//绘画时间标量
rectTime.left = m_nLeftAreaWidth+i;rectTime.top = m_nTopYMD-22;
rectTime.right = m_nLeftAreaWidth+i+m_nRightAreaWidth;rectTime.bottom = m_nTopYMD-10;
cstrTime.Format(_T("%02d:%02d"), nHour, nMinute);
memDC.SelectObject(&m_FontTime);
memDC.SetTextColor(m_clrText);
memDC.SetBkColor(m_clrBackGround);
memDC.DrawText(cstrTime, &rectTime, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
break;
}
else if(ucVLineType == 2)
{
//绘画小竖线
memDC.MoveTo(m_nLeftAreaWidth+i, m_nTopYMD-5);
memDC.LineTo(m_nLeftAreaWidth+i, m_nTopYMD);
break;
}
}
}
//绘画时间竖线
if(m_tpLBtnDown.nTimeMS == -1)//绘画鼠标左击的时间竖线
{
if(m_bMouseMove)//如果处于鼠标移动状态,并且竖线超出时间轴的距离范围,则将竖线锁定在范围边缘
{
if(m_nLeftAreaWidth > m_tpLBtnDown.nPos)
m_tpLBtnDown.nPos = m_nLeftAreaWidth;
else if(m_tpLBtnDown.nPos > m_nLeftAreaWidth+nGridWidth)
m_tpLBtnDown.nPos = m_nLeftAreaWidth+nGridWidth;
m_bMouseMove = false;
}
//竖线在时间轴的距离范围内
if(m_nLeftAreaWidth <= m_tpLBtnDown.nPos && m_tpLBtnDown.nPos <= m_nLeftAreaWidth+nGridWidth)
{
//计算坐标对应的时间
int nTimeMS = nLeftTimeMS + nMSecondPerPixel*(m_tpLBtnDown.nPos-m_nLeftAreaWidth);
m_tpLBtnDown.nTimeMS = nTimeMS;
int nHour_LBD = nTimeMS/1000/3600;
int nMinute_LBD = (nTimeMS/1000%3600)/60;
int nSecond_LBD = nTimeMS/1000%3600%60;
//时间文本格式化
CString cstrTime;
cstrTime.Format(_T("%02d:%02d:%02d"), nHour_LBD, nMinute_LBD, nSecond_LBD);
//计算时间文本对应的矩形
CRect rectTime(m_tpLBtnDown.nPos-12, m_nTopYMD-34, m_tpLBtnDown.nPos+m_nRightAreaWidth, m_nTopYMD-22);
//绘画时间文本
memDC.SelectObject(&m_FontTime);
memDC.SetTextColor(m_clrText);
memDC.SetBkColor(m_clrBackGround);
memDC.DrawText(cstrTime, &rectTime, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
//绘画竖线
memDC.MoveTo(m_tpLBtnDown.nPos, m_nTopYMD-22);
memDC.LineTo(m_tpLBtnDown.nPos, nGridHeight);
}
}
else//绘画缩放后的时间竖线
{
//竖线时间在时间轴的时间范围内
if(nLeftTimeMS <= m_tpLBtnDown.nTimeMS && m_tpLBtnDown.nTimeMS <= nLeftTimeMS+nGridWidth*(nMSecondPerPixel+1))
{
//计算时间对应的坐标
int nPos = m_nLeftAreaWidth + (m_tpLBtnDown.nTimeMS-nLeftTimeMS)/nMSecondPerPixel;
//时间文本格式化
int nHour = m_tpLBtnDown.nTimeMS/1000/3600;
int nMinute = (m_tpLBtnDown.nTimeMS/1000%3600)/60;
int nSecond = m_tpLBtnDown.nTimeMS/1000%3600%60;
CString cstrTime;
cstrTime.Format(_T("%02d:%02d:%02d"), nHour, nMinute, nSecond);
//计算时间文本对应的矩形
CRect rectTime(nPos-12, m_nTopYMD-34, nPos+m_nRightAreaWidth, m_nTopYMD-22);
//绘画时间文本
memDC.SelectObject(&m_FontTime);
memDC.SetTextColor(m_clrText);
memDC.SetBkColor(m_clrBackGround);
memDC.DrawText(cstrTime, &rectTime, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
//绘画竖线
memDC.MoveTo(nPos, m_nTopYMD-22);
memDC.LineTo(nPos, nGridHeight);
}
}
}
void CTimeAxisCtrl::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
m_bOnSize = true;
}
unsigned char CTimeAxisCtrl::GetVLineType(unsigned char ucTimeType, int nHour, int nMinute, int nSecond)
{//0代表不画线,1代表大竖线,1代表小竖线
if(ucTimeType == H24)
{
if(nHour%2 == 0 && nMinute == 0 && nSecond == 0)//每2小时
{
return 1;
}
else if(nMinute == 0 && nSecond == 0)//每1小时
{
return 2;
}
}
else if(ucTimeType == H12 || ucTimeType == H6)
{
if(nMinute == 0 && nSecond == 0)//每1小时
{
return 1;
}
else if(nMinute%10 == 0 && nSecond == 0)//每10分钟
{
return 2;
}
}
else if(ucTimeType == H3)
{
if(nMinute%20 == 0 && nSecond == 0)//每20分钟
{
return 1;
}
else if(nMinute%2 == 0 && nSecond == 0)//每2分钟
{
return 2;
}
}
else if(ucTimeType == H1)
{
if(nMinute%6 == 0 && nSecond == 0)//每6分钟
{
return 1;
}
else if(nSecond == 0)//每1分钟
{
return 2;
}
}
else if(ucTimeType == M30)
{
if(nMinute%3 == 0 && nSecond == 0)//每3分钟
{
return 1;
}
else if(nSecond%30 == 0)//每30秒钟
{
return 2;
}
}
else if(ucTimeType == M10)
{
if(nSecond == 0)//每1分钟
{
return 1;
}
else if(nSecond%10 == 0)//每10秒钟
{
return 2;
}
}
return 0;
}
void CTimeAxisCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
m_bLBtnDown = false;
ReleaseCapture();
CWnd::OnLButtonUp(nFlags, point);
}
void CTimeAxisCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
if(m_bLBtnDown)
{
CRect rect;
GetClientRect(&rect);
int nGridWidth = rect.Width()-m_nLeftAreaWidth-m_nRightAreaWidth;
int nMSecondPerPixel = m_nTimeMS[m_ucTimeType]/nGridWidth;
//当鼠标超出时间轴的距离范围,移动时间轴
if(point.x < m_nLeftAreaWidth || point.x > m_nLeftAreaWidth+nGridWidth)
{
//计算最左边时间
int nLeftTimeMS = m_nLeftTimeMS;
nLeftTimeMS += (point.x-m_ptMouseMove.x)*nMSecondPerPixel;
if(nLeftTimeMS < 0)//最左边时间小于零点
nLeftTimeMS = 0;
else if(nLeftTimeMS+m_nTimeMS[m_ucTimeType] > m_nTimeMS[H24])//总时间超过24小时
nLeftTimeMS = m_nLeftTimeMS;
m_nLeftTimeMS = nLeftTimeMS;
}
m_tpLBtnDown.nPos = point.x;
m_tpLBtnDown.nTimeMS = -1;
m_bMouseMove = true;
Invalidate();
}
m_ptMouseMove = point;
CWnd::OnMouseMove(nFlags, point);
}