65.9K
CodeProject 正在变化。 阅读更多。
Home

Ultima Online 寻宝工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (11投票s)

2002年7月24日

7分钟阅读

viewsIcon

106929

downloadIcon

2769

UOTH 是一系列前沿 UI 技术的集合。哦,也是一个为 UO 游戏玩家准备的工具。

引言

我敢打赌你在想:“一个在线游戏的傻瓜工具怎么会在 Code Project 上?” 这个程序是我参加 新 C++ 竞赛的参赛作品。 UO 寻宝工具 (UOTH) 使用 VC7、WTL7 和 ATL7 作为其核心技术。

那么,是什么让 UOTH 值得在新竞赛中获得考虑呢?嗯,UOTH 中没有一个方面能够脱颖而出,被认为是伟大或创新的编程。然而,UOTH 包含的是一系列各种各样的小型前沿 UI 和编程技术。

  • 扩展默认的工具栏自定义功能,包括图标大小和文本位置组合框。
  • 类似 Explorer 风格的“另存为”对话框自定义,包括重新定位控件,使它们与其他控件对齐。
  • 将模态对话框的内容“悬挂”到另一个对话框作为子对话框。
  • 打印和页面设置支持。这包括图形的打印。
  • 对话框上的工具栏
  • 动态调整对话框大小并在对话框背景上绘制图形。
  • 将对话框显示为模态或非模态的示例。非模态对话框也调整为允许用户最小化它。
  • “置顶”示例代码。
  • 拥有绘制按钮和下拉菜单的示例。
  • 蒙版图像绘制。
  • Alpha 混合单色图像。
  • 拉伸带边框的 UI 图形而不扭曲图像。
  • 从工具栏下拉菜单。
  • 使用 EXPAT XML 解析器解析配置文件。
  • 使用 ZLIB 和包含的 zip 文件支持来打包静态程序数据文件。

以下是 UOTH 中包含的一些编程亮点的简要描述。

花哨的工具栏自定义

如今的工具栏比简单的 16 色、16x15 位图图像复杂得多。作为程序员,我们必须处理大尺寸和小尺寸的图像集。我们必须处理按钮下方或右侧文本的显示。我们还必须处理允许用户配置他们喜欢的工具栏样式的能力。

幸运的是,对于程序员来说,通用控件为我们提供了对当今应用程序中常见的先进工具栏颜色和文本选项的完整支持。但是,仍然有一些小细节被忽略了。

如果你右键单击 IE6 工具栏并选择“自定义”,你将看到标准的工具栏自定义对话框。然而,这个对话框底部有两个额外的组合框,允许用户指定文本和图标选项。乍一看,你可能会认为 IE6 包含自己的私有自定义对话框,但事实并非如此。

如果你查看文件 MainWnd.cpp 中的 CMainWnd::OnCustomizeToolbar 方法,它包含工具栏的通知处理程序。特别是,对 TBN_INITCUSTOMIZE 通知 的处理包含一个用于获取工具栏自定义对话框窗口的技巧。

//
// Ok, this is an UNDOCUMENTED hack.  The initialize message
// actually contains the handle of the dialog for customization.
//

typedef struct hack_tagNMTOOLBARINIT
{
  NMHDR   hdr;
  HWND  hWndDialog;
} hack_NMTOOLBARINIT;

hack_NMTOOLBARINIT *pNMHack = (hack_NMTOOLBARINIT *) pnmh;
HWND hDlg = pNMHack ->hWndDialog;

如代码中所述,TBN_INITCUSTOMIZE 通知结构实际上包含自定义对话框的句柄。与任何未公开的功能一样,使用它总是存在风险。然而,考虑到 IE6 使用它,我怀疑它很快就会消失。

一旦我们获得了自定义对话框的句柄,我们就可以在该对话框中创建一个子对话框。源代码确切地展示了如何做到这一点。

类似 Explorer 风格的“另存为”对话框自定义。

自定义“另存为”对话框在如今非常普遍。然而,许多开发人员只是将他们的控件放在自定义对话框中,而不顾它们与“另存为”对话框中其他控件的对齐方式。可悲的是,这很简单。CUOAMExportDlg 类包含一个自定义“另存为”对话框的示例。我们将重点关注正确重新定位控件所需的内容。此示例假定我们的自定义对话框将出现在“另存为”对话框的下方。

“另存为”对话框上的每个控件都有特定的控件 ID,我们可以依赖它们作为常数。这些 ID 定义在系统头文件“dlgs.h”中。我们可以使用这些控件 ID 来定位“另存为”对话框上的控件,并根据它们的位置相对地重新定位我们的控件。

在 UOTH 中,控件的重新定位由一个名为 RepositionControl 的例程处理(而且我还声称自文档化代码是神话)。在这个例子中,由于我们的自定义对话框在底部,我们只关心我们控件的水平位置和大小。它们的垂直位置和大小由它们在我们对话框中的位置和大小决定。

//--------------------------------------------------------------------------
//
// @mfunc Reposition a control
//
// @parm CWindow & | wnd | Control to be reposition
//
// @parm UINT | nID | ID of the control used for positioning
//
// @parm bool | fSize | If true, adjust the width of the control
//
// @rdesc None.
//
//--------------------------------------------------------------------------

void CUOAMExportDlg::RepositionControl (CWindow &wnd, UINT nID, bool fSize)
{

  //
  // Get the window rect in the client area of the 
  // control we are interested in.
  //

  CWindow wndParent = GetParent ();
  CWindow wndAnchor = wndParent .GetDlgItem (nID);
  CRect rectAnchor;
  wndAnchor .GetWindowRect (&rectAnchor);
  wndParent .ScreenToClient (&rectAnchor);

  //
  // Reposition the control
  //

  DWORD dwSWFlags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE;
  CRect rectCtrl;
  wnd .GetWindowRect (&rectCtrl);
  ScreenToClient (&rectCtrl);
  rectCtrl .OffsetRect (rectAnchor .left - rectCtrl .left, 0);
  if (fSize)
  {
    rectCtrl .right = rectCtrl .left + rectAnchor .Width ();
    dwSWFlags &= ~SWP_NOSIZE;
  }
  wnd .SetWindowPos (NULL, rectCtrl .left, rectCtrl .top,
    rectCtrl .Width (), rectCtrl .Height (), dwSWFlags);
  return;
}

提供给这个例程的是要重新定位的控件的窗口、“另存为”对话框控件的 ID,以及一个表示我们是否希望我们的控件被调整大小的标志。从代码中可以看出,这实际上是一个简单的过程。这个例程在对话框初始化和调整大小时被调用。

还有最后一点。如果你有一个控件,比如另一个按钮,需要放在“确定”按钮下方,你可能会在对话框右下角的调整大小手柄处遇到问题。OnInitDialog 方法中的第三块代码解决了这个问题。

将一个对话框“悬挂”到另一个对话框中

在许多应用程序中,你可能会发现需要在多个位置放置一个通用的对话框元素。在 UOTH 中,过滤器对话框不仅可以作为独立的对话框使用,还会出现在“打印...”对话框和“UOAM 导出...”对话框中。一个普通程序员的自然反应是复制代码并在多个地方放置对话框。然而,通过一些简单的调整,一个模态框架对话框可以像子对话框一样工作。

当对话框既要作为模态框架对话框又要作为子对话框时,需要考虑的第一件事是,在子对话框的情况下,永远不会收到针对 IDOKIDCANCEL 按钮的 WM_COMMAND 消息。由于程序员完全控制对话框,这是一个很容易解决的问题。与其将所有保存对话框设置的代码放在 OnOK 例程中,不如将其放在另一个例程中,该例程将由 OnOK 和将使用此对话框作为子对话框的对话框调用。对于 DDX 的用户来说,这将非常简单。就我而言,我不用 DDX,因为它对我来说总是比好处麻烦。

处理用户按下“确定”按钮时如何保存数据只是战斗的一半。为了将对话框用作子窗口,你必须调用对话框的 Create 方法而不是 DoModal。然而,对话框资源仍然设置为使对话框显示为带对话框框架的弹出窗口。幸运的是,只需少量代码,就可以动态修复此问题。以下是 CFilterDlg::Create 方法。

//--------------------------------------------------------------------------
//
// @mfunc Create a modeless dialog
//
// @parm HWND | hWndParent | Parent window
//
// @parm LPARAM | dwInitParam | Initialization param
//
// @rdesc Window handle
//
//---------------------------------------------------------------------------

HWND CFilterDlg::Create (HWND hWndParent, LPARAM dwInitParam)
{

  //
  // Find the region
  //

  HRSRC hRsrc = FindResource (_Module .GetResourceInstance (),
        MAKEINTRESOURCE (IDD), RT_DIALOG);
  if (hRsrc == NULL)
    return NULL;

  //
  // Get the size of the resource
  //

  DWORD dwSize = ::SizeofResource (_Module .GetResourceInstance (), hRsrc);

  //
  // Allocate the global memory to contain the regions
  //

  HGLOBAL hTemplate = ::GlobalAlloc (GPTR, dwSize);
  if (hTemplate == NULL)
    return NULL;
  DLGTEMPLATE *pTemplate = (DLGTEMPLATE *) ::GlobalLock (hTemplate);
  DLGTEMPLATEEX *pTemplateEx = (DLGTEMPLATEEX *) pTemplate;

  //
  // Load and lock the resource
  //

  HGLOBAL hSource = ::LoadResource (_Module .GetResourceInstance (), hRsrc);
  LPVOID pSource = ::LockResource (hSource);
  memcpy (pTemplate, pSource, dwSize);
  UnlockResource (hSource);
  ::FreeResource (hSource);

  //
  // Adjust the flags 
  //

  DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_BORDER | WS_DLGFRAME |
    DS_3DLOOK | DS_FIXEDSYS | DS_SETFONT | DS_CONTROL;
  DWORD dwExStyle = 0;
  if (pTemplateEx ->signature == 0xFFFF)
  {
    pTemplateEx ->exStyle = dwExStyle;
    pTemplateEx ->style = dwStyle;
  }
  else
  {
    pTemplate ->dwExtendedStyle = dwExStyle;
    pTemplate ->style = dwStyle;
  }

  //
  // Create the window
  //

  ATLASSERT (m_hWnd == NULL);
  _AtlWinModule .AddCreateWndData (&m_thunk.cd, 
    (CDialogImplBaseT <CWindow> *) this);
#ifdef _DEBUG
  m_bModal = false;
#endif //_DEBUG
  HWND hWnd = ::CreateDialogIndirectParam (
    _AtlBaseModule.GetResourceInstance(), 
    pTemplate, hWndParent, StartDialogProc,
    dwInitParam);
  ATLASSERT (m_hWnd == hWnd);

  //
  // If we created the window, delete OK and CANCEL :)
  //

  if (m_hWnd)
  {
    ::DestroyWindow (GetDlgItem (IDOK));
    ::DestroyWindow (GetDlgItem (IDCANCEL));
  }

  //
  // Unlock the globals
  //

  ::GlobalUnlock (hTemplate);
  ::GlobalFree (hTemplate);
  return hWnd;
}

首先要做的是将对话框资源加载到易失性内存中。这允许我们修改创建参数。然后,修改样式和扩展样式标志,强制对话框显示为无边框的子窗口。最后,在对话框创建后,删除“确定”和“取消”按钮。

此代码将在默认窗口位置创建对话框。对话框需要由父窗口重新定位到正确的位置。这通常在 WM_SIZE 消息处理程序中完成。

其他值得注意的代码(奉承部分)

  • 如果没有 CodeProject 上其他人的代码贡献,这个程序将无法完成。
  • 颜色按钮是基于 Maunder、Alexander Bischofberger 和 James White 的工作(链接链接)。
  • 位图蒙版绘制是基于 Raja Segar 和 Chris Becke 的工作(链接链接)。
  • Alpha 绘制是基于 Christian Graus 的工作(链接)。

关于作者

Tim 从事专业编程已经太久了。他目前在他共同创立的一家公司工作,该公司专注于工业自动化数据采集软件。

© . All rights reserved.