CRichEditCtrl 控件背景透明

dcrlj 2010-06-22 11:06:11
CRichEditCtrl 控件怎么做透明效果;就是这个控件贴一张背景图 后能看到控件下面的窗口图片
...全文
215 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
finder_zhang 2010-06-24
  • 打赏
  • 举报
回复
楼主,你说的是透明,还是半透明?
主窗口有一个图,控件也有一个图,控件的图可以看到,再隐约看到下面主窗的图,这种半透明效果?
Hiiishe 2010-06-24
  • 打赏
  • 举报
回复
那就更简单了,得到你的控件的窗口句柄
// Set WS_EX_LAYERED on this window SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);

// Make this window 70% alpha
SetLayeredWindowAttributes(hwnd, 0, (255 * 70) / 100, LWA_ALPHA);
dcrlj 2010-06-23
  • 打赏
  • 举报
回复
急用;那位朋友帮忙; 要求图片透;怎么弄;
dcrlj 2010-06-23
  • 打赏
  • 举报
回复
你说的是把下面的背景图贴到CRichEditCtrl控件上面; 但是要看出上面与下面不一样的颜色;最好是直接把它弄透明就OK
dcrlj 2010-06-23
  • 打赏
  • 举报
回复
先谢谢楼上的哥们;但好像还没有明白我的意思; 我是想在这个控件上贴一个背景图; 然后使控件和背景图都透明;但能看出图片的在上面的效果; 看到下面的窗口
Hiiishe 2010-06-23
  • 打赏
  • 举报
回复
使用这个类创建RICHEDIT,把成员变量m_bOwenDraw设为TRUE就可以了
tianhaixin 2010-06-23
  • 打赏
  • 举报
回复
学习……
Hiiishe 2010-06-23
  • 打赏
  • 举报
回复


bool CAutoRichEditCtrl::ParagraphIsCentered()
{
PARAFORMAT pf = GetParagraphFormat();

if (pf.wAlignment == PFA_CENTER)
return true;
else
return false;
}

bool CAutoRichEditCtrl::ParagraphIsLeft()
{
PARAFORMAT pf = GetParagraphFormat();

if (pf.wAlignment == PFA_LEFT)
return true;
else
return false;
}

bool CAutoRichEditCtrl::ParagraphIsRight()
{
PARAFORMAT pf = GetParagraphFormat();

if (pf.wAlignment == PFA_RIGHT)
return true;
else
return false;
}

PARAFORMAT CAutoRichEditCtrl::GetParagraphFormat()
{
PARAFORMAT pf;
pf.cbSize = sizeof(PARAFORMAT);

pf.dwMask = PFM_ALIGNMENT | PFM_NUMBERING;

GetParaFormat(pf);

return pf;
}

void CAutoRichEditCtrl::SetParagraphBulleted()
{
PARAFORMAT paraformat = GetParagraphFormat();

if ( (paraformat.dwMask & PFM_NUMBERING) && (paraformat.wNumbering == PFN_BULLET) )
{
paraformat.wNumbering = 0;
paraformat.dxOffset = 0;
paraformat.dxStartIndent = 0;
paraformat.dwMask = PFM_NUMBERING | PFM_STARTINDENT | PFM_OFFSET;
}
else
{
paraformat.wNumbering = PFN_BULLET;
paraformat.dwMask = PFM_NUMBERING;
if (paraformat.dxOffset == 0)
{
paraformat.dxOffset = 4;
paraformat.dwMask = PFM_NUMBERING | PFM_STARTINDENT | PFM_OFFSET;
}
}

SetParaFormat(paraformat);

}

bool CAutoRichEditCtrl::ParagraphIsBulleted()
{
PARAFORMAT pf = GetParagraphFormat();

if (pf.wNumbering == PFN_BULLET)
return true;
else
return false;
}

void CAutoRichEditCtrl::SelectColor()
{
CColorDialog dlg;

CHARFORMAT cf = GetCharFormat();

if (cf.dwEffects & CFE_AUTOCOLOR) cf.dwEffects -= CFE_AUTOCOLOR;

// Get a color from the common color dialog.
if( dlg.DoModal() == IDOK )
{
cf.crTextColor = dlg.GetColor();
}

cf.dwMask = CFM_COLOR;

SetSelectionCharFormat(cf);
}

void CAutoRichEditCtrl::SetFontName(CString sFontName)
{
CHARFORMAT cf = GetCharFormat();

// Set the font name.
for (int i = 0; i <= sFontName.GetLength()-1; i++)
cf.szFaceName[i] = sFontName[i];


cf.dwMask = CFM_FACE;

SetSelectionCharFormat(cf);
}

void CAutoRichEditCtrl::SetFontSize(int nPointSize)
{
CHARFORMAT cf = GetCharFormat();

nPointSize *= 20; // convert from to twips
cf.yHeight = nPointSize;

cf.dwMask = CFM_SIZE;

SetSelectionCharFormat(cf);
}

void CAutoRichEditCtrl::GetSystemFonts(CStringArray &saFontList)
{
CDC *pDC = GetDC ();

EnumFonts (pDC->GetSafeHdc(),NULL,(FONTENUMPROC) CBEnumFonts,(LPARAM)&saFontList);//Enumerate

}

BOOL CALLBACK CAutoRichEditCtrl::CBEnumFonts(LPLOGFONT lplf, LPTEXTMETRIC lptm, DWORD dwType, LPARAM lpData)
{
// This function was written with the help of CCustComboBox, by Girish Bharadwaj.
// Available from Codeguru.

if (dwType == TRUETYPE_FONTTYPE)
{
((CStringArray *) lpData)->Add( lplf->lfFaceName );
}

return true;
}

CString CAutoRichEditCtrl::GetSelectionFontName()
{
CHARFORMAT cf = GetCharFormat();

CString sName = cf.szFaceName;

return sName;
}

long CAutoRichEditCtrl::GetSelectionFontSize()
{
CHARFORMAT cf = GetCharFormat();
float fScaleTP = 1440.0f/GetDeviceCaps(GetDC()->GetSafeHdc(),LOGPIXELSY);;
long nSize = cf.yHeight/fScaleTP;

return nSize;
}

void CAutoRichEditCtrl::OnRButtonDown( UINT nFlags, CPoint point )
{
//设置为焦点
SetFocus();
//创建一个弹出式菜单
CMenu popmenu;
popmenu.CreatePopupMenu();
//添加菜单项目
popmenu.AppendMenu(0, ID_RICH_UNDO, _T("撤销(&U)"));
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_CUT, _T("剪切(&T)"));
popmenu.AppendMenu(0, ID_RICH_COPY, _T("复制(&C)"));
popmenu.AppendMenu(0, ID_RICH_PASTE, _T("粘贴(&P)"));
popmenu.AppendMenu(0, ID_RICH_CLEAR, _T("删除(&L)"));
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_SELECTALL, _T("全选(&A)"));
//popmenu.AppendMenu(0, MF_SEPARATOR);
//popmenu.AppendMenu(0, ID_RICH_SETFONT, "Select &Font");

//初始化菜单项
UINT nUndo=(CanUndo() ? 0 : MF_GRAYED );
popmenu.EnableMenuItem(ID_RICH_UNDO, MF_BYCOMMAND|nUndo);

UINT nSel=((GetSelectionType()!=SEL_EMPTY) ? 0 : MF_GRAYED) ;
popmenu.EnableMenuItem(ID_RICH_CUT, MF_BYCOMMAND|nSel);
popmenu.EnableMenuItem(ID_RICH_COPY, MF_BYCOMMAND|nSel);
popmenu.EnableMenuItem(ID_RICH_CLEAR, MF_BYCOMMAND|nSel);

UINT nPaste=(CanPaste() ? 0 : MF_GRAYED) ;
popmenu.EnableMenuItem(ID_RICH_PASTE, MF_BYCOMMAND|nPaste);

//显示菜单
CPoint pt;
GetCursorPos(&pt);
popmenu.TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this);
popmenu.DestroyMenu();
CRichEditCtrl::OnRButtonDown(nFlags, point);
}
HBRUSH CAutoRichEditCtrl::CtlColor(CDC* pDC, UINT /*nCtlColor*/)
{
// TODO: 在此更改 DC 的任何属性

pDC->SetBkMode(TRANSPARENT);
return NULL;
}

BOOL CAutoRichEditCtrl::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//CRect rt;
//GetClientRect(rt);
//pDC->FillSolidRect(rt,RGB(0,255,0));
//pDC->MoveTo(rt.TopLeft());
//pDC->LineTo(rt.BottomRight());
return CRichEditCtrl::OnEraseBkgnd(pDC);
return FALSE;
}

HBRUSH CAutoRichEditCtrl::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CRichEditCtrl::OnCtlColor(pDC, pWnd, nCtlColor);

// TODO: 在此更改 DC 的任何属性

// TODO: 如果默认的不是所需画笔,则返回另一个画笔
return hbr;
}

void CAutoRichEditCtrl::OnNcPaint()
{
// TODO: 在此处添加消息处理程序代码
// 不为绘图消息调用 CRichEditCtrl::OnNcPaint()
if (m_bOwenDraw)
{
CDC* pDC = GetDC();
CRect rtWindow;
GetWindowRect(rtWindow);
int xBorder = GetSystemMetrics(SM_CXSIZEFRAME);
int yBorder = GetSystemMetrics(SM_CYSIZEFRAME);
ScreenToClient(rtWindow);
CPen pen(PS_DOT, 0, RGB(0,0,0));
CPen* pOldpen = pDC->SelectObject(&pen);
CGdiObject* pOld = pDC->SelectStockObject(NULL_BRUSH);
//pDC->Rectangle(rtWindow);
rtWindow.DeflateRect(xBorder-2, yBorder-2);
pDC->Rectangle(rtWindow);
pDC->SelectObject(pOld);
pDC->SelectObject(pOldpen);
ReleaseDC(pDC);
return;
}
CRichEditCtrl::OnNcPaint();
}

int CAutoRichEditCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CRichEditCtrl::OnCreate(lpCreateStruct) == -1)
return -1;

if (m_bOwenDraw)
{
SetWindowLong(m_hWnd, GWL_EXSTYLE, GetWindowLong(m_hWnd,GWL_EXSTYLE)|WS_EX_TRANSPARENT);
}

return 0;
}

void CAutoRichEditCtrl::OnEnUpdate()
{
// TODO: 如果该控件是 RICHEDIT 控件,则它将不会
// 发送该通知,除非重写 CRichEditCtrl::OnInitDialog()
// 函数,将 EM_SETEVENTMASK 消息发送到控件,
// 同时将 ENM_UPDATE 标志“或”运算到 lParam 掩码中。

// TODO: 在此添加控件通知处理程序代码
}

// 根据文本改变编辑框大小
void CAutoRichEditCtrl::OnEnRequestresize(NMHDR *pNMHDR, LRESULT *pResult)
{
REQRESIZE *pReqResize = reinterpret_cast<REQRESIZE *>(pNMHDR);
// TODO: 控件将不发送该通知,除非您重写
// CRichEditCtrl::OnInitDialog() 函数以发送 EM_SETEVENTMASK 消息
// 到具有 ENM_REQUESTRESIZE 标志“或”运算到 lParam 掩码中的控件。

// 根据文本改变编辑框大小
CRect rtEditBox;
GetWindowRect(rtEditBox);
rtEditBox.NormalizeRect();
RECT rtNew = pReqResize->rc;
GetParent()->ScreenToClient( rtEditBox );
rtEditBox.NormalizeRect();
if (rtEditBox.Height() < (rtNew.bottom +2*GetSystemMetrics(SM_CYSIZEFRAME) - rtNew.top) )
rtEditBox.bottom = rtNew.bottom + 2*GetSystemMetrics(SM_CYSIZEFRAME);
MoveWindow(rtEditBox);

*pResult = 1;
}
Hiiishe 2010-06-23
  • 打赏
  • 举报
回复

// AutoRichEditCtrl.cpp : implementation file
//

#include "stdafx.h"
#include ".\autoricheditctrl.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define ID_RICH_UNDO 101
#define ID_RICH_CUT 102
#define ID_RICH_COPY 103
#define ID_RICH_PASTE 104
#define ID_RICH_CLEAR 105
#define ID_RICH_SELECTALL 106
//#define ID_RICH_SETFONT 107

/////////////////////////////////////////////////////////////////////////////
// CAutoRichEditCtrl

CAutoRichEditCtrl::CAutoRichEditCtrl()
: m_bOwenDraw(FALSE)
{
}

CAutoRichEditCtrl::~CAutoRichEditCtrl()
{
}


BEGIN_MESSAGE_MAP(CAutoRichEditCtrl, CRichEditCtrl)
//{{AFX_MSG_MAP(CAutoRichEditCtrl)
ON_WM_RBUTTONDOWN()
ON_COMMAND(ID_RICH_COPY, OnCopy)
ON_COMMAND(ID_RICH_CUT, OnCut)
ON_COMMAND(ID_RICH_PASTE, OnPaste)
ON_COMMAND(ID_RICH_SELECTALL, OnSelectall)
ON_COMMAND(ID_RICH_UNDO, OnUndo)
ON_COMMAND(ID_RICH_CLEAR, OnClear)
//}}AFX_MSG_MAP
ON_NOTIFY_REFLECT(EN_REQUESTRESIZE, OnEnRequestresize)
ON_WM_CTLCOLOR_REFLECT()
ON_WM_ERASEBKGND()
ON_WM_CTLCOLOR()
ON_WM_NCPAINT()
ON_WM_CREATE()
ON_CONTROL_REFLECT(EN_UPDATE, OnEnUpdate)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CAutoRichEditCtrl message handlers

CString CAutoRichEditCtrl::GetRTF()
{
// Return the RTF string of the text in the control.

// Stream out here.
EDITSTREAM es;
es.dwError = 0;
es.pfnCallback = CBStreamOut; // Set the callback

CString sRTF = "";

es.dwCookie = (DWORD) &sRTF; // so sRTF receives the string

StreamOut(SF_RTF, es); // Call CRichEditCtrl::StreamOut to get the string.
///

return sRTF;

}

void CAutoRichEditCtrl::SetRTF(CString sRTF)
{
// Put the RTF string sRTF into the rich edit control.

// Read the text in
EDITSTREAM es;
es.dwError = 0;
es.pfnCallback = CBStreamIn;
es.dwCookie = (DWORD) &sRTF;
StreamIn(SF_RTF, es); // Do it.

}

/*
Callback function to stream an RTF string into the rich edit control.
*/
DWORD CALLBACK CAutoRichEditCtrl::CBStreamIn(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
// We insert the rich text here.

/*
This function taken from CodeGuru.com
http://www.codeguru.com/richedit/rtf_string_streamin.shtml
Zafir Anjum
*/

CString *pstr = (CString *) dwCookie;

if (pstr->GetLength() < cb)
{
*pcb = pstr->GetLength();
memcpy(pbBuff, (LPCSTR) *pstr, *pcb);
pstr->Empty();
}
else
{
*pcb = cb;
memcpy(pbBuff, (LPCSTR) *pstr, *pcb);
*pstr = pstr->Right(pstr->GetLength() - cb);
}
///

return 0;
}

/*
Callback function to stream the RTF string out of the rich edit control.
*/
DWORD CALLBACK CAutoRichEditCtrl::CBStreamOut(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
// Address of our string var is in psEntry
CString *psEntry = (CString*) dwCookie;


CString tmpEntry = "";
tmpEntry = (CString) pbBuff;

// And write it!!!
*psEntry += tmpEntry.Left(cb);

return 0;
}

bool CAutoRichEditCtrl::SelectionIsBold()
{
CHARFORMAT cf = GetCharFormat();

if (cf.dwEffects & CFM_BOLD)
return true;
else
return false;
}

bool CAutoRichEditCtrl::SelectionIsItalic()
{
CHARFORMAT cf = GetCharFormat();

if (cf.dwEffects & CFM_ITALIC)
return true;
else
return false;
}

bool CAutoRichEditCtrl::SelectionIsUnderlined()
{
CHARFORMAT cf = GetCharFormat();

if (cf.dwEffects & CFM_UNDERLINE)
return true;
else
return false;
}

CHARFORMAT CAutoRichEditCtrl::GetCharFormat(DWORD dwMask)
{
CHARFORMAT cf;
cf.cbSize = sizeof(CHARFORMAT);

cf.dwMask = dwMask;

GetSelectionCharFormat(cf);

return cf;
}

void CAutoRichEditCtrl::SetCharStyle(int MASK, int STYLE, int nStart, int nEnd)
{
CHARFORMAT cf;
cf.cbSize = sizeof(CHARFORMAT);
//cf.dwMask = MASK;

GetSelectionCharFormat(cf);

if (cf.dwMask & MASK) // selection is all the same
{
cf.dwEffects ^= STYLE;
}
else
{
cf.dwEffects |= STYLE;
}

cf.dwMask = MASK;

SetSelectionCharFormat(cf);

}

void CAutoRichEditCtrl::SetSelectionBold()
{
long start=0, end=0;
GetSel(start, end); // Get the current selection

SetCharStyle(CFM_BOLD, CFE_BOLD, start, end); // Make it bold
}

void CAutoRichEditCtrl::SetSelectionItalic()
{
long start=0, end=0;
GetSel(start, end);

SetCharStyle(CFM_ITALIC, CFE_ITALIC, start, end);
}

void CAutoRichEditCtrl::SetSelectionUnderlined()
{
long start=0, end=0;
GetSel(start, end);

SetCharStyle(CFM_UNDERLINE, CFE_UNDERLINE, start, end);
}

void CAutoRichEditCtrl::SetParagraphCenter()
{
PARAFORMAT paraFormat;
paraFormat.cbSize = sizeof(PARAFORMAT);
paraFormat.dwMask = PFM_ALIGNMENT;
paraFormat.wAlignment = PFA_CENTER;

SetParaFormat(paraFormat); // Set the paragraph.
}

void CAutoRichEditCtrl::SetParagraphLeft()
{
PARAFORMAT paraFormat;
paraFormat.cbSize = sizeof(PARAFORMAT);
paraFormat.dwMask = PFM_ALIGNMENT;
paraFormat.wAlignment = PFA_LEFT;

SetParaFormat(paraFormat);
}

void CAutoRichEditCtrl::SetParagraphRight()
{
PARAFORMAT paraFormat;
paraFormat.cbSize = sizeof(PARAFORMAT);
paraFormat.dwMask = PFM_ALIGNMENT;
paraFormat.wAlignment = PFA_RIGHT;

SetParaFormat(paraFormat);
}
Hiiishe 2010-06-23
  • 打赏
  • 举报
回复
我做过,为了在我的图形背景上实现所见即所得文字输入,而不是弹出子窗口来输入。
所以拿到一个RichEdit扩展类后改写了


#if !defined(AFX_AUTORICHEDITCTRL_H__C26D1E0E_DD32_11D2_B39F_000092914562__INCLUDED_)
#define AFX_AUTORICHEDITCTRL_H__C26D1E0E_DD32_11D2_B39F_000092914562__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// AutoRichEditCtrl.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CAutoRichEditCtrl window

class CAutoRichEditCtrl : public CRichEditCtrl
{
// Construction
public:
CAutoRichEditCtrl();

// Attributes
public:
BOOL m_bOwenDraw;

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAutoRichEditCtrl)
//}}AFX_VIRTUAL

// Implementation
public:
long GetSelectionFontSize();
CString GetSelectionFontName();
CStringArray m_saFontList;
void GetSystemFonts(CStringArray &saFontList);
void SetFontSize(int nPointSize);
void SetFontName(CString sFontName);
void SelectColor();
bool ParagraphIsBulleted();
void SetParagraphBulleted();

PARAFORMAT GetParagraphFormat();

bool ParagraphIsRight();
bool ParagraphIsLeft();
bool ParagraphIsCentered();

void SetParagraphRight();
void SetParagraphLeft();
void SetParagraphCenter();

CHARFORMAT GetCharFormat(DWORD dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE);

bool SelectionIsBold();
bool SelectionIsItalic();
bool SelectionIsUnderlined();

void SetSelectionBold();
void SetSelectionItalic();
void SetSelectionUnderlined();

void SetRTF(CString sRTF);
CString GetRTF();
virtual ~CAutoRichEditCtrl();

// Generated message map functions
protected:
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);

DECLARE_MESSAGE_MAP()
private:
void SetCharStyle(int MASK, int STYLE, int nStart, int nEnd);
static DWORD CALLBACK CBStreamIn(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
static DWORD CALLBACK CBStreamOut(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb);
static BOOL CALLBACK CBEnumFonts(LPLOGFONT lplf, LPTEXTMETRIC lptm, DWORD dwType, LPARAM lpData);
public:
afx_msg void OnCopy() { Copy(); }
afx_msg void OnCut() { Cut(); }
afx_msg void OnPaste() { Paste(); }
afx_msg void OnSelectall() { SetSel(0, -1); }
afx_msg void OnUndo() { Undo(); }
afx_msg void OnClear() { Clear(); }
afx_msg void OnEnRequestresize(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg HBRUSH CtlColor(CDC* /*pDC*/, UINT /*nCtlColor*/);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
afx_msg void OnNcPaint();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnEnUpdate();
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_AUTORICHEDITCTRL_H__C26D1E0E_DD32_11D2_B39F_000092914562__INCLUDED_)

Eleven 2010-06-22
  • 打赏
  • 举报
回复
重载CRichEditCtrl类,在WM_ERASEBKGND消息,贴个背景图片不行吗??

16,548

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • AIGC Browser
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧