欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自绘时间轴控件

程序员文章站 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);
}