根据我提供的控件源码,写出一个更好的控件
#ifndef _CONSOLE_H_
#define _CONSOLE_H_
#define _CRT_SECURE_NO_WARNINGS
#define NO_WARN_MBCS_MFC_DEPRECATION
#include <SDKDDKVer.h>
//#include <afxwin.h>
#include <afxcmn.h>
#include <concurrent_vector.h>
#define CONSOLECOLOR_BLACK "\x1E\x1D" //字体 黑色
#define CONSOLECOLOR_RED "\x1E\x1F" //字体 红色
#define CONSOLECOLOR_GREEN "\x1E\x20" //字体 绿色
#define CONSOLECOLOR_YELLOW "\x1E\x21" //字体 黄色
#define CONSOLECOLOR_BLUE "\x1E\x22" //字体 蓝色
#define CONSOLECOLOR_PURPLE "\x1E\x23" //字体 紫色
#define CONSOLECOLOR_CYAN "\x1E\x24" //字体 青色
#define CONSOLECOLOR_DEF "\x1E\x25" //字体 默认色
class ScrollBar;
struct LineInfo
{
DWORD m_dwStrLen;//行内容的长度
DWORD m_dwHead;//行首的sel游标
DWORD m_dwMaxSelWnd;//当前控件x轴尽头的sel
DWORD m_dwTail;//行尾的sel
int m_xPos;//行首的x开始坐标
int m_yPos;//行首的y开始坐标
DWORD m_dwMaxPixl;//此行内容占最大的像素
int* m_pDx;
~LineInfo()
{
if (m_pDx)delete[]m_pDx;
}
};
class Console: public CRichEditCtrl
{
public:
Console(CWnd* _pParent);
~Console();
template<class ...Args>
void Printf(const char* pformat, Args... args)
{
int len = std::snprintf(nullptr, 0, pformat, args...);
if (len > 0)
{
char* szBuf = new char[len+1];
std::snprintf(szBuf, len+1, pformat, args...);
split(szBuf);
delete[]szBuf;
}
}
private:
void split(const char* _szText);
void printf(const char* _szText);
private:
CDC* m_pDC;
CRect m_rcWnd;
BOOL m_bIsTrack;
CFont m_Font;//默认字体,只是方便计算字体占屏幕长度使用的
int m_nFontSize;//默认字体大小(单位Twips) 如220Twip = 220/20 = 11磅
CHARFORMAT2 m_CF;//最终字体的数据
concurrency::concurrent_vector<LineInfo> m_vecLineInfo;
ScrollBar* m_pHScroll;
SCROLLINFO m_infoHScroll;
DWORD m_pixCurMaxChars;
ScrollBar* m_pVScroll;
SCROLLINFO m_infoVScroll;
int m_iLineHeight;
int m_iStartPos_x;
int m_iPrePos_x;
bool m_bIsCapture;
CHARRANGE m_StartSel;
public:
DECLARE_MESSAGE_MAP()
afx_msg void OnEnSelchange(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnEnMsgfilter(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnMouseLeave();
afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
};
extern Console* pConsole;
#define console(_format, ...)pConsole->Printf(_format, ##__VA_ARGS__);
#endif#include "Console.h"
#include <io.h>
#include <thread>
#include "ScrollBar/ScrollBar.h"
Console* pConsole = NULL;
Console::Console(CWnd* _pParent) :
m_nFontSize(10 * 10),
m_bIsTrack(FALSE),
m_pHScroll(NULL),
m_pVScroll(NULL),
m_pixCurMaxChars(0),
m_iPrePos_x(0),
m_iStartPos_x(0),
m_bIsCapture(FALSE)
{
if (_pParent)
{
const char* szFontName = "lucida consoles"; //Arial
_pParent->GetWindowRect(&m_rcWnd);
m_rcWnd = {10, 0, m_rcWnd.Width()-15, m_rcWnd.Height()-15};//682
//| WS_HSCROLL | WS_VSCROLL
if (CreateEx(NULL, WS_CHILD | WS_VISIBLE | ES_LEFT | ES_WANTRETURN | ES_MULTILINE | ES_READONLY | ES_AUTOHSCROLL | ES_AUTOVSCROLL, m_rcWnd, _pParent, 0))//
{
m_pDC = GetDC();
SetBackgroundColor(false, RGB(30, 30, 30));//设置背景颜色
LimitText(32767);//系统默认设置是 32767 = 31.999kb 只能限制用户的输入
//创建新字体数据,此字体只是方便计算屏幕显示的长度用的。最终字体数据以 CHARFORMAT2 为准
//以十分之一磅为单位。例如,如果你想要一个12磅的字体,你应该传递120作为这个参数。
m_Font.CreatePointFont(m_nFontSize, szFontName);// 100/10 =10磅,相当于200Twip CreatePointFont用的单位是Point, 是磅的10倍,刚好是Twips的一半
SetFont(&m_Font);//设置字体
m_pDC->SelectObject(&m_Font);//dc选择当前字体
/*
1缇等于1/1440 英寸或1/567厘米
缇(Twips) (缇:计量单位,等于“磅”的 1/20,英寸的 1/1440。一厘米有 567 缇。
像素(Pixels):监视器或打印机分辨率的最小单位
右键单击桌面,选择属性,选择“设置”选卡,单击高级按钮。
里面出现DPI设置。一般为“正常尺寸(96 DPI)”。
DPI的意思就是 DPI(Dots per Inch)。因此我们可以得到如下换算公式
1 Pixel = 1440 TPI / 96 DPI = 15 Twips
1 Twip = 96 DPI / 1440 TPI = 0.0666667 Pixels
磅:指打印的字符的高度的度量单位, 1磅等于1/72英寸,或大约等于1厘米的 1/28。
英寸:2.54 厘米
一般情况下:1厘米=8505像素
20Twip = 1磅 = 20*0.0666667 = 1.333334 Pixels
*/
LRESULT FontStyle = 0;
FontStyle = SendMessage(EM_GETLANGOPTIONS, 0, 0);
if (FontStyle & IMF_DUALFONT)
{
FontStyle &= ~IMF_DUALFONT;
//"<CRichEdit_Ctrl::CRichEdit_Ctrl>成功去掉IMF_DUALFONT";
}
SendMessage(EM_SETLANGOPTIONS, 0, (LPARAM)FontStyle);
/*
PARAFORMAT2 pf;
ZeroMemory(&pf, sizeof(PARAFORMAT2));
pf.cbSize = sizeof pf;
pf.dwMask |= PFM_ALL;
pf.bLineSpacingRule = 100;
pf.dyLineSpacing = 400;
pf.dySpaceAfter = 400;
pf.dySpaceBefore = 400;
pf.dxStartIndent = 100;
pf.dxRightIndent = 100;
SetParaFormat(pf);
*/
TEXTMETRIC tm;
::GetTextMetrics(m_pDC->GetSafeHdc(), &tm);
m_iLineHeight = tm.tmHeight + (tm.tmDescent*2);//取得行高
//准确的字体数据
memset(&m_CF, 0, sizeof(m_CF));
//StrCpyA(m_CF.szFaceName, szFontName);//设置字体
//m_CF.cbSize = sizeof(CHARFORMAT2);
m_CF.dwMask = CFM_EFFECTS | CFM_COLOR;
m_CF.dwEffects = 0;
m_CF.crTextColor = RGB(165, 165, 165);//设置颜色
//m_CF.yHeight = m_nFontSize;//除20得到磅 如 200/20 = 10磅 //默认的字符磅数
SetDefaultCharFormat(m_CF);//设置字体
SetEventMask(GetEventMask() | ENM_CHANGE | ENM_SELCHANGE | ENM_SCROLL | ENM_MOUSEEVENTS);
//创建滚动动
m_pHScroll = new ScrollBar();
if (m_pHScroll->Create(SB_HORZ, CRect(m_rcWnd.left, m_rcWnd.Height(), m_rcWnd.right, m_rcWnd.Height()+15), _pParent))
{
m_pHScroll->SetTarget(this);
memset(&m_infoHScroll, 0, sizeof m_infoHScroll);
m_infoHScroll.cbSize = sizeof m_infoHScroll;
m_infoHScroll.fMask = SIF_ALL;
m_infoHScroll.nPage = m_rcWnd.Width();
}
m_pVScroll = new ScrollBar();
if (m_pVScroll->Create(SB_VERT, CRect(m_rcWnd.right, m_rcWnd.top, m_rcWnd.right+15, m_rcWnd.bottom), _pParent))
{
m_pVScroll->SetTarget(this);
memset(&m_infoVScroll, 0, sizeof m_infoVScroll);
m_infoVScroll.cbSize = sizeof m_infoVScroll;
m_infoVScroll.fMask = SIF_ALL;
m_infoVScroll.nPage = m_rcWnd.Height();
}
}
}
}
Console::~Console()
{
m_Font.DeleteObject();
if (m_pHScroll)delete m_pHScroll;
if (m_pVScroll)delete m_pVScroll;
for (concurrency::concurrent_vector<LineInfo>::iterator iter = m_vecLineInfo.begin(); iter != m_vecLineInfo.end(); iter++)
{
if (iter->m_pDx)
{
delete[] iter->m_pDx;
iter->m_pDx = NULL;
}
}
}
/*
*0x1E RS (Record Separator) 记录分离符
属性代码 颜色
29 黑色 = 0x1D
31 红色 = 0x1F
32 绿色 = 0x20
33 黄色 = 0x21
34 蓝色 = 0x22
35 紫色 = 0x23
36 青色 = 0x24
*/
void Console::printf(const char* _szText)
{
const char* szText = _szText;
char byteColor = 0;
//如果\n会替换成\r
int indexLine = LineFromChar(-1);//取出当前行号
long lStart = 0;
long lEnd = 0;
GetSel(lStart, lEnd);//取得当前的光标位置
CString szStart;
GetTextRange(lStart - 1, lEnd, szStart);//当前光标向后选择一位字符,并取出此字符
if (szStart == '\r' || szStart == "")//判断字符是否行头
{
CPoint point = PosFromChar(lEnd);
LineInfo info;
memset(&info, 0, sizeof info);
info.m_dwHead = lEnd;
info.m_xPos = point.x;
info.m_yPos = point.y;
m_vecLineInfo.push_back(info);//保存行头的光标位置
}
if (_szText[0] == '\x1E')
{
switch (_szText[1])
{
case '\x1D':
{
m_CF.crTextColor = RGB(0, 0, 0);
break;
}
case '\x1F':
{
m_CF.crTextColor = RGB(255, 0, 0);
break;
}
case '\x20':
{
m_CF.crTextColor = RGB(0, 255, 0);
break;
}
case '\x21':
{
m_CF.crTextColor = RGB(255, 255, 0);
break;
}
case '\x22':
{
m_CF.crTextColor = RGB(0, 0, 255);
break;
}
case '\x23':
{
m_CF.crTextColor = RGB(199, 0, 106);
break;
}
case '\x24':
{
m_CF.crTextColor = RGB(0, 255, 255);
break;
}
default:
{
m_CF.crTextColor = RGB(165, 165, 165);
break;
}
}
szText = _szText+2;
SetSelectionCharFormat(m_CF);
}
ReplaceSel(szText);
m_CF.crTextColor = RGB(165, 165, 165);//重置回原始颜色
SetSelectionCharFormat(m_CF);
LineInfo* pInfo = &(m_vecLineInfo[indexLine]);
//尝试取出控件尽头x轴末尾是否有字符存在
GetSel(lStart, lEnd);//取得当前的光标位置
GetTextRange(lEnd - 1, lEnd, szStart);//当前光标向后选择一位字符,并取出此字符
bool bIsEnter = false;
if (szStart == '\r')
{
//已经完成换行操作
bIsEnter = true;
pInfo->m_dwTail = lEnd - 1;
GetTextRange(pInfo->m_dwHead, pInfo->m_dwTail, szStart);//取出本行内容
CPoint point = PosFromChar(pInfo->m_dwTail);
pInfo->m_dwMaxPixl = point.x-1;//此行文本的最大像素
int len = szStart.GetLength();
int iFit = 0;
CSize size = { 0, 0 };
if (pInfo->m_pDx)
{
delete [] pInfo->m_pDx;
pInfo->m_pDx = NULL;
}
pInfo->m_pDx = new int[len];
pInfo->m_dwStrLen = len;
::GetTextExtentExPoint(m_pDC->GetSafeHdc(), szStart, len, point.x, &iFit, pInfo->m_pDx, &size);//计算出本行每个字符所占的像素,并把每个字符的像素存在m_pDx数组里面
pInfo->m_yPos = indexLine * m_iLineHeight;
}
else
{
pInfo->m_dwTail = lEnd;
CPoint point = PosFromChar(pInfo->m_dwTail);
pInfo->m_dwMaxPixl = point.x;//此行文本的最大像素
}
//重置水平滚动条信息
if (pInfo->m_dwMaxPixl > (DWORD)m_rcWnd.Width() && pInfo->m_dwMaxPixl > m_pixCurMaxChars)
{
m_pixCurMaxChars = pInfo->m_dwMaxPixl;
m_infoHScroll.nMax = m_pixCurMaxChars;
m_pHScroll->SetScrollInfo(m_infoHScroll);
}
if (bIsEnter)
{
//已经完成换行操作
if (((indexLine + 1) * m_iLineHeight) > m_rcWnd.Height())
{
m_infoVScroll.nMax = ((indexLine + 1) * m_iLineHeight);
m_pVScroll->SetScrollInfo(m_infoVScroll);
m_pVScroll->SetScrollPos(m_infoVScroll.nMax - m_rcWnd.Height());//向下滚动
}
LineScroll(1);
}
}
void Console::split(const char* _szText)
{
const char* szCurText = _szText;
const char* szOldText = _szText;
char* szPreText = NULL;
int len = NULL;
const char* szClrText = NULL;
if (_szText[0] != '\x1E')
{
bool bIsFined = false;
while ((szClrText = strchr(szCurText, '\x1E')))
{
bIsFined = true;
len = szClrText - szOldText;
szPreText = new char[len + 1];
szPreText[len] = 0;
memcpy(szPreText, szOldText, len);
Console::printf(szPreText);
delete[]szPreText;
szCurText = szClrText + 1;
szOldText = szClrText;
}
if (bIsFined)
{
if(szOldText)Console::printf(szOldText);
}
else
{
Console::printf(_szText);
}
}
else
{
bool bIsFined = false;
szCurText = _szText + 1;
while ((szClrText = strchr(szCurText, '\x1E')))
{
bIsFined = true;
len = szClrText - szOldText;
szPreText = new char[len + 1];
szPreText[len] = 0;
memcpy(szPreText, szOldText, len);
Console::printf(szPreText);
delete[]szPreText;
szCurText = szClrText + 1;
szOldText = szClrText;
}
if (szOldText)
{
Console::printf(szOldText);
}
}
}
BEGIN_MESSAGE_MAP(Console, CRichEditCtrl)
ON_NOTIFY_REFLECT(EN_SELCHANGE, &Console::OnEnSelchange)
ON_NOTIFY_REFLECT(EN_MSGFILTER, &Console::OnEnMsgfilter)
ON_WM_MOUSELEAVE()
ON_WM_VSCROLL()
END_MESSAGE_MAP()
void Console::OnEnSelchange(NMHDR* pNMHDR, LRESULT* pResult)
{
SELCHANGE* pSelChange = reinterpret_cast<SELCHANGE*>(pNMHDR);
// TODO: 控件将不发送此通知,除非您重写
// CRichEditCtrl::OnInitDialog() 函数,以将 EM_SETEVENTMASK 消息发送
// 到该控件,同时将 ENM_SELCHANGE 标志“或”运算到 lParam 掩码中。
// TODO: 在此添加控件通知处理程序代码
int iLineIndex = -1;
iLineIndex = LineFromChar(pSelChange->chrg.cpMax);
if (iLineIndex != -1 && iLineIndex < (int)m_vecLineInfo.size())
{
CHARRANGE LineStartSel;
LineInfo* pInfo = &m_vecLineInfo[iLineIndex];
switch (pSelChange->seltyp)
{
case 0://鼠标点击在某处文本的某个sel中
{
//开始存储此行的选择sel起始位置
m_StartSel = pSelChange->chrg;
break;
}
case CBN_EDITCHANGE://鼠标左键按住拖动选择文本
{
//通过m_StartSel算出本行的选择起始位置
CPoint point = PosFromChar(m_StartSel.cpMin);//算出sel处于控件内坐标
int x = point.x;
point.y = pInfo->m_yPos;//替换上本行的y坐标
LineStartSel.cpMin = CharFromPos(point);//算出本行起始位置
LineStartSel.cpMax = LineStartSel.cpMin;
point = PosFromChar(LineStartSel.cpMin);
if (point.x < x - 5)
{
//outputdebug_default("行号:%d 检测到起始位置异常,和原位差太远", iLineIndex);
LineStartSel.cpMin = 0;
LineStartSel.cpMax = 0;
if (point.x > m_rcWnd.Width())
{
m_pHScroll->SetScrollPos(point.x - m_rcWnd.Width());//通知滚动条
}
else
{
m_pHScroll->SetScrollPos(0);
}
}
if (pSelChange->chrg.cpMax > LineStartSel.cpMax)
{
//outputdebug_default("行号:%d 从左向右选择文本: %d", iLineIndex, pSelChange->chrg.cpMax);
CString str;
GetTextRange(pInfo->m_dwHead, pSelChange->chrg.cpMax, str);
int len = str.GetLength();
if (--len > -1 && len < (int)pInfo->m_dwStrLen)
{
//outputdebug_default("行号:%d, 当前光标所占像素: %d", iLineIndex, pInfo->m_pDx[len]);
int pix = pInfo->m_pDx[len];
if (pix > m_rcWnd.Width())
{
//outputdebug_default("行号:%d, 当前光标所占像素超出控件长度: %d", iLineIndex, pix);
m_pHScroll->SetScrollPos(pix - m_rcWnd.Width());//通知滚动条
}
}
else
{
if (--len > -1 && pInfo->m_dwStrLen == 0)
{
//此行末尾没有/r回车符
CPoint point = PosFromChar(pSelChange->chrg.cpMax);
if (point.x > m_rcWnd.Width())
{
m_pHScroll->SetScrollPos(point.x - m_rcWnd.Width());//通知滚动条
}
else
{
m_pHScroll->SetScrollPos(0);
}
}
else
{
int i = 0;
}
}
}
else if (pSelChange->chrg.cpMax != pInfo->m_dwHead)
{
//outputdebug_default("行号:%d 从右向左选择文本: head:%d start:%d, min:%d max:%d", iLineIndex, pInfo->m_dwHead, LineStartSel.cpMax, pSelChange->chrg.cpMin, pSelChange->chrg.cpMax);
if (pSelChange->chrg.cpMin == pInfo->m_dwHead)
{
m_pHScroll->SetScrollPos(0);
}
}
else
{
//outputdebug_default("x行号:%d 从右向左选择文本: head:%d start:%d, min:%d max:%d", iLineIndex, pInfo->m_dwHead, LineStartSel.cpMax, pSelChange->chrg.cpMin, pSelChange->chrg.cpMax);
if (pInfo->m_dwHead == pSelChange->chrg.cpMax)//本行头部和上一行行尾重复,定位回上一行
{
if (--iLineIndex > -1)
{
pInfo = &m_vecLineInfo[iLineIndex];
CString str;
GetTextRange(pInfo->m_dwHead, pSelChange->chrg.cpMax, str);
int len = str.GetLength();
//outputdebug_default("行号:%d, 当前光标所占像素: %d len:%d", iLineIndex, pInfo->m_pDx[len-2], len);
if (--len > -1 && len <= (int)pInfo->m_dwStrLen)
{
int pix = pInfo->m_pDx[len-1];
if (pix > m_rcWnd.Width())
{
//outputdebug_default("行号:%d, 当前光标所占像素超出控件长度: %d", iLineIndex, pix);
//m_pHScroll->SetScrollPos(pix - m_rcWnd.Width());//通知滚动条
}
}
}
}
}
break;
}
}
}
//outputdebug_default("min:%d max:%d", pSelChange->chrg.cpMin, pSelChange->chrg.cpMax);
*pResult = 0;
}
void Console::OnEnMsgfilter(NMHDR* pNMHDR, LRESULT* pResult)
{
MSGFILTER* pMsgFilter = reinterpret_cast<MSGFILTER*>(pNMHDR);
// TODO: 控件将不发送此通知,除非您重写
// CRichEditCtrl::OnInitDialog() 函数,以将 EM_SETEVENTMASK 消息发送
// 到该控件,同时将 ENM_KEYEVENTS 或 ENM_MOUSEEVENTS 标志
//“或”运算到 lParam 掩码中。
// TODO: 在此添加控件通知处理程序代码
switch(pMsgFilter->msg)
{
case WM_MOUSEMOVE:
{
/*
if (pMsgFilter->wParam == MK_LBUTTON)
{
int xPos = GET_X_LPARAM(pMsgFilter->lParam);
int yPos = GET_Y_LPARAM(pMsgFilter->lParam);
//outputdebug_default("x: %d, y:%d",xPos, yPos);
if (m_bIsCapture)
{
if (xPos > m_iStartPos_x)
{
//outputdebug_default("正在从左向右选择文本.");
}
else
{
//outputdebug_default("正在从右向左选择文本.");
}
m_iPrePos_x = xPos;
}
}*/
if (!m_bIsTrack)
{
m_bIsTrack = TRUE;
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = m_hWnd;
tme.dwFlags = TME_LEAVE | TME_HOVER;
tme.dwHoverTime = 1;// 鼠标在按钮上停留超过 1ms ,才认为成功
_TrackMouseEvent(&tme);
}
break;
}
case WM_LBUTTONDOWN:
{
if (pMsgFilter->wParam == MK_LBUTTON)
{
int xPos = GET_X_LPARAM(pMsgFilter->lParam);
int yPos = GET_Y_LPARAM(pMsgFilter->lParam);
m_iPrePos_x = xPos;
m_iStartPos_x = xPos;
m_bIsCapture = TRUE;
//outputdebug_default("x: %d, y:%d", xPos, yPos);
}
break;
}
case WM_LBUTTONUP:
{
if (pMsgFilter->wParam == MK_LBUTTON)
{
m_bIsCapture = FALSE;
}
break;
}
}
*pResult = 0;
}
void Console::OnMouseLeave()
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
m_bIsTrack = FALSE;
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
CRichEditCtrl::OnMouseLeave();
}
void Console::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRichEditCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}