CSSSandbox






4.88/5 (13投票s)
一个用于同时编辑和查看 CSS 和 HTML 的工具。
引言
本文提供了一个简单的左右分屏 CSS/HTML 编辑器和网页浏览器,允许用户在一侧编辑 CSS/HTML 代码,并在另一侧的浏览器中立即看到结果。除了提供一个可用的工具外,它还通过集成 CodeProject 的几篇其他文章来介绍创建该工具的过程,并讨论了将这些组件整合在一起所涉及的问题和解决方法。
背景
在使用 C++/MFC 开发 Windows 原生/Win32 应用程序时,我遇到过多种情况,在这些情况下,向用户显示 CSS/HTML 信息比使用一系列常规控件或进行自定义 DC 渲染更具吸引力。从 C++/MFC 应用程序生成 CSS/HTML,然后将其作为界面的一部分呈现给用户,可以为您提供 Windows 应用程序中 CSS/HTML 的强大功能和灵活性,并能为最终用户带来美观、整洁的输出。
创建应用程序将要生成的 CSS/HTML 需要通过试错法来编辑代码并在网页浏览器中查看。由于最终目标是使用 Microsoft Web Browser ActiveX 控件在 MFC 对话框中渲染 HTML,因此我认为编写一个包含该控件和一个简单文本编辑器窗口的小工具会很有用。目标是能够对 CSS/HTML 进行简单实验,并在 Microsoft Web Browser ActiveX 控件中立即看到结果。
我最近在处理将 Microsoft Web Browser ActiveX 控件集成到对话框以达到上述目的的麻烦时,发现了 Gary R. Wheeler 在 Code Project 上的一篇优秀文章,"Using the Web Browser control, simplified"。这个过程有点令人头疼,但找到 Gary 的文章是一个很大的帮助。通过它,您可以轻松地将浏览器控件放入对话框,并将生成的 HTML 字符串直接推送到控件中(而不是必须通过临时文件并使用文件 URL)。在发现这一点之后,似乎是编写一个小型编辑工具的好机会,该工具可以同时使用 Gary 的代码和内置的富文本编辑器控件。这将允许您编辑 CSS/HTML 代码,并立即并排查看渲染结果。
我知道我想要一个简单的对话框应用程序来作为该工具,但在此类应用程序中并排显示编辑器和浏览器控件需要一个分割条和一个可调整大小的对话框才能使用。我花了一些时间研究如何在对话框中使用分割条,偶然发现了 Lucman Abdulrachman 的一篇很棒的文章,"Splitter in an MFC dialog based application"。目标是随后将浏览器控件、分割控件和 Windows 富文本编辑器控件集成到对话框中。将这些组件组合在一起,结果应该是一个允许您同时编辑和查看代码的工具。
集成问题
我遇到的第一个问题是,分割控件并非为可调整大小的对话框设计的。当对话框大小改变时,我编写了代码来以有意义的方式调整控件的大小/位置。因此,例如,如果您增大了对话框,控件最终仍会看起来正确并使用可用的窗口空间。分割条位置(作为位置与客户区宽度的比例)在调整大小时会保持不变。问题是,调整大小后,由于分割控件显然不是为可调整大小的对话框设计的,分割控件会卡住并且无法再移动。导致问题的代码似乎与限制分割控件移动的极端有关。为了使其正常工作,我替换了现有的限制代码,并添加了最小和最大分割位置变量。然后,如下所示,这些变量在修改后的鼠标移动处理程序中使用。
class CControlSplitter : public CButton
{
void SetMinMaxSplitterPos(int minSplitterPos, int maxSplitterPos);
private:
int minSplitterPos;
int maxSplitterPos;
};
CControlSplitter::CControlSplitter()
{
minSplitterPos = -1;
maxSplitterPos = -1;
}
void CControlSplitter::SetMinMaxSplitterPos(int minSplitterPos, int maxSplitterPos)
{
this->minSplitterPos = minSplitterPos;
this->maxSplitterPos = maxSplitterPos;
}
void CControlSplitter::OnMouseMove(UINT nFlags, CPoint point)
{
if(m_bDragging)
{
CRect rect;
::GetWindowRect(m_hWnd,&rect);
CPoint ptMouse;
GetCursorPos(&ptMouse);
CSize sizeDiff = ptMouse - m_ptStartDrag;
CSize sizeMove = m_ptStartPos-rect.TopLeft();
CRect rectBefore = rect;
rect.OffsetRect(sizeMove);
if(m_nType == CS_HORZ)
{
rect.OffsetRect(0, sizeDiff.cy);
}else{
rect.OffsetRect(sizeDiff.cx,0);
}
CRect rectAfter = rect;
CRect cr = rect;
GetParent()->ScreenToClient(cr);
if (m_nType == CS_HORZ)
{
if ((minSplitterPos >= 0) && (rectAfter.top < rectBefore.top))
{
if (cr.top <= minSplitterPos)
return;
}
else if ((maxSplitterPos >= 0) && (rectAfter.bottom > rectBefore.bottom))
{
if (cr.bottom >= maxSplitterPos)
return;
}
}
else if (m_nType == CS_VERT)
{
if ((minSplitterPos >= 0) && (rectAfter.left < rectBefore.left))
{
if (cr.left <= minSplitterPos)
return;
}
else if ((maxSplitterPos >= 0) && (rectAfter.right > rectBefore.right))
{
if (cr.right >= maxSplitterPos)
return;
}
}
// ....
}
}
上述代码强制对分割控件(以客户区坐标表示)进行可选的最小和最大限制。如果分割控件向左/向上移动,并且其新位置违反了最小限制,则停止移动,并对最大限制使用类似的逻辑。使用此修改后的代码似乎解决了对话框调整大小后发生的问题,并且仍然可以防止分割控件被移动到比预期更远的位置。
遇到的另一个有趣问题是浏览器控件的刷新。对话框首次打开时,浏览器显示正常。拖动分割条后,浏览器窗口会被正确调整大小,但不再渲染。就好像控件不存在一样,只能看到背景。通过单击并拖动控件所在区域,您会选中文本,从而使其渲染。因此,似乎在使用浏览器 ActiveX 控件调整对话框大小后,其渲染存在某些问题。这似乎不是 Gary 文章中代码的问题,而是 ActiveX 控件本身某种程度的限制/故障。我最终的解决方法是使用分割条通知,隐藏然后立即显示浏览器窗口,如下所示。有点像一种“ Hack-around”,但它提供了所需的即拖动分割条并且仍然可以看到浏览器内容的结果!
LRESULT CSSSandboxDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// called whenever the splitter control is moved by the user
if (message == UWM_SPLIT_MOVED)
{
// need to hide and show the browser window - otherwise it gets into a state where
// it just doesn't render - not sure why but this workaround seems to help.
browser.ShowWindow(SW_HIDE);
browser.ShowWindow(SW_SHOW);
// get updated splitter vars and save them
UpdateSplitterVars();
SaveOptions();
}
return CDialogEx::WindowProc(message, wParam, lParam);
}
由于该消息是由分割控件代码在运行时注册的,因此它直接在 WindowProc
方法中处理。因此,使用普通的 MFC ON_MESSAGE
宏不起作用。
处理完上述问题后,就可以在富文本编辑器控件中编辑代码,并在浏览器窗口中立即看到结果。在编辑器控件的底部提供了用于保存/加载文件的按钮,很快就显而易见,热键将是可取的,以便能够快速保存工作文件而无需不必要的鼠标点击。另外,在编辑代码时按下 TAB 会将焦点转移到对话框中的下一个控件,而不是在代码中进行缩进。为了让热键和 TAB 工作,使用了下面的代码。
#define CHECKVKEYSTATE(vkey) \
((GetAsyncKeyState(vkey) & (1 << 15)) != 0)
BOOL CSSSandboxDlg::PreTranslateMessage(MSG* pMsg)
{
//
// allows using hotkeys (Ctrl+N, Ctrl+O, etc.) to trigger the buttons
//
if (pMsg->message == WM_KEYDOWN)
{
if (CHECKVKEYSTATE(VK_CONTROL))
{
switch (pMsg->wParam)
{
case _T('N'):
OnBnClickedNewButton();
return TRUE;
case _T('O'):
OnBnClickedLoadButton();
return TRUE;
case _T('S'):
OnBnClickedSaveButton();
return TRUE;
case _T('R'):
OnBnClickedRefreshButton();
return TRUE;
}
}
}
//
// allows TAB key to insert a specified # spaces rather
// than switching focus to the next control in the dialog
//
if (GetFocus()->GetSafeHwnd() == editor.GetSafeHwnd())
{
if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_TAB))
{
CString s;
for (int i = 0; i < NUM_SPACES_ON_TAB; i++)
s += _T(" ");
editor.ReplaceSel(s);
return TRUE;
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
在上述代码中,TAB 键会将三个空格插入到编辑器中,而不是插入实际的 TAB 字符。如果您更喜欢实际的 TAB 或者需要不同数量的空格,可以在代码中轻松更改。
Using the Code
要使用这些代码,您可以从演示 zip 文件中下载并运行它。或者,您可以下载源代码并在 Visual Studio 2010 中进行构建。首次运行时(或上次退出时没有活动文件时),编辑器将最初加载一个默认文档。此默认文档充当该工具的自述文件,提供基本的使用信息。这就是本文截图中所显示的。
当您在编辑器中编辑代码时,可以按 Ctrl+R 将当前内容刷新到浏览器控件。您还可以选中“自动刷新”复选框,它会在您键入时不断刷新浏览器控件。这提供了一个良好的实时编辑环境,您的更改会立即显示在浏览器中。
一旦您保存了当前文档,下次运行时它将自动打开(如果可能)。如果您在退出前单击“新建”按钮,则没有活动文档,下次运行时将再次打开默认文件。
关注点
创建该工具时使用了以下 CodeProject 文章。
- "Using the Web Browser control, simplified",作者 Gary R. Wheeler
- "Splitter in an MFC dialog based application",作者 Lucman Abdulrachman
历史
- 2013.02.11
初始发布。