【问题标题】:Why EM_SETMARGINS does not work under Windows 7?为什么 EM_SETMARGINS 在 Windows 7 下不起作用?
【发布时间】:2011-05-12 04:36:29
【问题描述】:

我有一个由编辑框和下拉按钮组成的复合视觉控件。下拉按钮不是窗口控件,而是绘制在编辑框上。我通过以下调用限制编辑的宽度:

SendMessage(Handle, EM_SETMARGINS, EC_RIGHTMARGIN, 
  (DropDownButtonWidth + 2) shl 16);

它在 Windows XP 下可以正常工作,但在 Windows 7 下无法正常工作。在后一种情况下,当焦点编辑框重叠下拉按钮并擦除其图像时。

在两种操作系统下限制编辑框矩形的正确方法是什么?

PS:我也尝试了另一种方法:

  SendMessage(Handle, EM_GETRECT, 0, LongInt(@Loc));
  Loc.Bottom := ClientHeight + 1;  
  Loc.Right := ClientWidth - FButton.Width - 2;
  Loc.Top := 0;
  Loc.Left := 0;
  SendMessage(Handle, EM_SETRECTNP, 0, LongInt(@Loc));

但它也不适用于 Windows 7。

【问题讨论】:

    标签: windows delphi winapi user-interface windows-7


    【解决方案1】:

    您设置边距的第一个代码是正确的。

    由于 VCL 的工作方式,事情变得棘手,为给定 VCL 控件创建的底层窗口可能会重新创建以响应在运行时对 VCL 属性所做的更改(某些属性更改只能在 Windows API 级别应用通过破坏和重建窗口)。尽管消息的顺序(通常)在不同的 Windows 版本中没有改变,但可能会引入额外的消息,其中一些可能会改变包裹在这些窗口周围的 VCL 代码可能触发的顺序,或干扰该代码的行为.

    也有可能是在底层 Windows API 窗口中引入了 VCL 无法满足的行为(同样,在这种情况下,最有可能在混合低级别 API 调用时发生)。

    在将 VCL 行为与较低级别的直接 API 调用混合时尤其如此 - 在本例中。

    一旦应用某些设置,还有其他可能会干扰某些设置,这需要您自己销毁并重新创建窗口并重新应用您自己的设置。

    我已经看到其他关于在不同版本的 XP 上使用相同代码(不是 Delphi)的问题的报告 - SP2 中引入的更改似乎对该领域产生了一些影响。

    EM_SETMARGINS 的情况下,我遇到了与您相同的问题,并通过查看 TButtonEdit 控件如何设法应用所需的边距(这是有效的)来解决它至少在我的 Windows 7 安装中)。

    由于我正在实现自己的自定义控件,而不是尝试将边距应用于某些现有的编辑控件,这对我来说可能变得更容易了。在下面的代码 sn-ps 中,TCustomPickEdit 是我的自定义控件类,它包含一个 fButton 对象,该对象包含与选择器按钮相关的所有设置。您将需要进行适当的调整以在您的特定情况下应用此代码。

    我发现如下:

    1. 需要在至少 3 个位置应用边距。首先,每当更改可能影响边距的设置时,其次是创建编辑控件窗口句柄时,最后是每当更改编辑控件上的字体时:

    2. 即使正确设置了边距,也需要调整控件的剪切矩形以确保正确绘制。这需要覆盖编辑控件的 WndProc 并拦截一些消息。此 WndProc 还需要响应字体更改通知,以便在编辑控件字体更改时重新应用边距。

    在我的例子中处理这些的代码如下所示:

      procedure TCustomPickEdit.ConfigureButton;
      // 1. Apply margins when button settings are changed 
      begin
        fButton.Caption   := Button.Caption;
        fButton.Flat      := Button.Flat;
        fButton.Glyph     := Button.Glyph;
        fButton.NumGlyphs := Button.NumGlyphs;
        fButton.Visible   := Button.Visible;
        ApplyMargins;
      end;
    
      procedure TCustomPickEdit.CreateHandle;
      // 2. Apply margins when underlying window handle is created 
      begin
        inherited;
        ApplyMargins;
      end;
    
      procedure TCustomPickEdit.WndProc(var aMessage: TMessage);
      // 3. Adjust clipping rectangle for correct drawing
      // 4. Apply margins when font is changed
      var
        top: Integer;
      begin
        case aMessage.Msg of
          CN_CTLCOLORSTATIC,
          CN_CTLCOLOREDIT    : if Button.Visible then
                               begin
                                 top := fButton.Top;
                                 if ThemeServices.ThemesEnabled and Ctl3D then
                                   Inc(top);
    
                                 ExcludeClipRect(aMessage.WParam, fButton.Left,
                                                                  top + 1,
                                                                  fButton.Left + fButton.Width,
                                                                  fButton.Height);
                               end;
        end;
    
        inherited;
    
        case aMessage.Msg of
          CM_FONTCHANGED : if NOT (csLoading in ComponentState) then
                             ApplyMargins;
        end;
      end;
    

    【讨论】:

    • 为什么投反对票?给出的另一个答案(有 2 个莫名其妙的赞成票)实际上并不是一个答案,因为它没有解决问题,而且基于对文档的误解,它确实提出的建议是完全错误的。 EC_USEFONTINFO 与该问题没有任何关系,这一点可以从我完全解决问题而没有涉及它的事实中得到证明。那么,谁能解释下投票的原因?
    • 您说得对,EC_USEFONTINFO 没有参与其中,但反对票可能与此无关。我没有投反对票,但我发现您的回答存在两个缺陷。首先,第一段 - 它不应该与重新创建的窗口相关,因为海报告诉相同的代码在 XP 上运行。其次,您包含的文本和代码没有显示如何应用边距,而是显示何时应用它们。问题是前者。
    • 顺便说一句,我在此页面上有一个已删除的答案。我已经测试了问题中的代码,并且确实使用确切参数的确切调用不起作用。但是,如果我没记错的话,设置两个边距(或只设置左边距)或在多行控件中设置右边距是有效的......(我删除了我的答案,因为它基本上说控件必须是多行的让 EM_SETMARGINS 工作)
    • 问题中设置边距的代码已经正确,但你是对的,我应该提到这一点。相同代码在不同版本的 Windows 上运行的事实并不一定意味着代码不受重新创建的窗口或其他可能在 Windows 版本之间发生的消息更改的影响。 VCL 代码是在考虑给定版本(或多个版本)的 Windows 的情况下编写的,因此如果该 VCL 代码没有发生,则在新版本的操作系统中这方面的更改很容易在给定版本的 VCL 中产生意想不到的后果以适应差异。
    • 另外我应该补充一点,设置边距的代码对我有用,无论是设置左、右还是两个边距。多线样式对我来说似乎不是必需的(我没有应用那种样式)。我仍然感到沮丧的是,一个根本没有解决问题的答案得到了赞成,但一个包含解决方案的答案(即使不完整 - 或者可能是过度完整)被否决了。这些天 StackOverflow 到底发生了什么?
    【解决方案2】:

    我怀疑您可能需要进一步阅读the documentation on EM_SETMARGINS。它指出:

    HIWORD 指定新的宽度 右边距,以像素为单位。这 如果 wParam 没有,则忽略值 包括 EC_RIGHTMARGIN。

    编辑控件和 Rich Edit 3.0 和 稍后:HIWORD 可以指定 EC_USEFONTINFO 值设置正确 边缘到一个狭窄的宽度计算 使用文本度量 控件的当前字体。如果没有字体 已为控件设置,边距 设置为零。

    请注意关于 EC_USEFONTINFO 设置窄宽度的第二段。这可能意味着这是设置窄宽度的唯一方法。我不确定,因为我没有尝试过,但它可能会有所帮助。

    另请注意,Rich Edit Controls 和常规编辑框具有不同的行为,因此请检查您使用的是哪一个。

    【讨论】:

    • EC_USEFONTINFO 不能解决问题中的问题。指定此附加标志会导致正确应用边距,但不会阻止重绘/擦除问题。根据我的回答,这只能通过调整剪切矩形来解决。结合对字体更改通知的适当响应,实际上根本不需要 EC_USEFONTINFO。
    猜你喜欢
    • 2015-01-12
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    • 2016-01-26
    • 1970-01-01
    相关资源
    最近更新 更多