【问题标题】:Find control position relative to the parent form when it is inside a splittercontainer当控件位于拆分器容器内时,查找相对于父窗体的控件位置
【发布时间】:2018-08-06 20:38:23
【问题描述】:

场景


我正在开发一个用于镜像目标窗口的用户控件,它在内部使用 Win32 DWM API 注册/取消注册缩略图,并在父窗体调整大小或移动时更新缩略图的位置和位置。

这是我的应用程序的结构:

应用程序/我的用户控件在“正常”条件下按预期工作(这意味着,当在操作系统中使用默认 Windows 主题时),我录制了下一个演示用户控件使用和行为的视频,所以您可以更好地了解这一切,还可以看到控件按预期工作:

问题


当我设法为操作系统使用不同的主题时,问题就开始了,特别是在 Windows 10 中为窗口添加不可见边框的任何主题,这可以像使用像 WindowBlinds 这样的第 3 方软件(使用名为“Flat Dark”的主题),也许修改注册表中的一些 Windows 指标值也可以重现 Windows 10 中可见边框的添加,但我不记得如何通过注册表来做到这一点,抱歉。

嗯,主要的是,在 Windows 10 下,当设法使用具有不可见边框的窗口时(通过提到的第 3 方软件或其他可能的方式),我在我的用户控制类中拥有的算法要检索其与父窗体的相对坐标,它会中断,然后我得到意外的坐标,因此 DWM 缩略图没有绘制在应该绘制的正确位置。

我录制了下一个视频,您可以在其中看到差异并了解问题:

在视频中,我首先显示程序在“正常”条件下运行,然后关闭程序,更改操作系统主题,再次运行程序,从这一点您可以看到 DWM 缩略图未绘制在正确的坐标...

我的所有猜想都表明我遇到的问题与我的窗体的客户端/非客户端区域有关,当窗体/窗口应用了不可见的 Windows 10 边框时。

为什么我会这样想?,因为如果我将主题更改为具有可见边框的窗口,然后像这样删除表单的边框:

this.FormBorderStyle = FormBorderStyle.None;

...然后我的应用程序再次正常工作,而我的表单是无边界的,所以在这些特定情况下,它一定是与我的表单的客户端/非客户端区域相关的问题,我不明白我的意思当我在这些情况下计算我的控件的相对位置时,当表单有边框时,我做错了。

源代码


最后,我在这里分享完整的解决方案,它包括我正在开发的用户控件以及一个演示应用程序(您可以在上面的视频中看到)。

请注意,源代码是用 VB.NET 编写的,但这一事实与我在这个问题中标记的语言无关,因为我接受 C# 或 VB.NET 中的任何解决方案,所以请不要指责这是因为问题中的标记语言是一回事,用一种特定语言编写的共享解决方案是另一回事。

源代码不需要下载和查看,所有源代码中唯一相关的部分是relativePos的坐标分配,这里:

Public Class ElektroDwmThumbnail : Inherits UserControl

    Protected Function GetThumbnailRectangle() As Rectangle
        Dim relativePos As Point = Me.ParentForm.PointToClient(Me.PointToScreen(Point.Empty))
        ' ...
        Dim dstRectangle As New Rectangle(relativePos, thumbnailSize)
        Return dstRectangle
    End Function

End Class

在 C# 中是:

public class ElektroDwmThumbnail: UserControl {

    protected Rectangle GetThumbnailRectangle() {
        Point relativePos = this.ParentForm.PointToClient(this.PointToScreen(Point.Empty));
        // ...
        Rectangle dstRectangle = new Rectangle(relativePos, thumbnailSize);
        return dstRectangle;
    }

}

...它在我解释的情况下将意外坐标分配给relativePos,这是我需要解决的问题,我需要确定我的用户控件的真实相对坐标无论父窗体窗口的边框大小如何,都有效地(普遍地)父窗体...

【问题讨论】:

  • WindowBlinds 因引起此类问题而臭名昭著。在你的代码中你无能为力。我能想到的只有两个基本的解决方法:一定要添加一个清单来声明你的应用程序是 dpiAware,如果 dpi 虚拟化是原因,将解决问题。而在Win10之前的Windows版本中你必须做的事情,如果你改变了主题并且非客户区指标发生了变化,那么建议你注销并重新登录。先试试吧。
  • @Hans Passant 感谢您的评论,但是请看,我不确定如何将问题归因于 WindowBlinds 或算法,我的意思是,我的程序的坐标计算返回完全相同的坐标时使用一个主题或另一个主题,好吧,如果这是一个归因于 WindowBlinds 的问题,那么根据三规则,它不应该所有控件的位置在操作系统中变得疯狂吗?(因为所有控件都将返回相同的坐标,而不管使用的主题),逻辑上不会发生,只有当我尝试计算这个特定控件的坐标时才会发生。
  • (没有足够的空间来回答)除非我做错了什么,我通过 Visual Studio“新项目”菜单添加清单文件,然后我声明它 dpiAware,我也尝试了 gdiScaling 属性,我让他们知道和不知道,结果是一样的。关于重新登录用户会话的第二种解决方法不是原因。
  • 我想说这个问题发生在使用 3rd 方主题时,是的,但只有当我的表单有边框时,也就是说,如果我制作一个无边框的表单,那么坐标计算工作正常,我的 DWM 缩略图绘制在正确的位置。然后......目前我尝试进行坐标计算,边界大小计算或其他东西可能有问题/丢失......我不知道。

标签: c# vb.net winforms windows-10 rectangles


【解决方案1】:

无论控件类型或控件的父级或控件在控件树中的位置有多深,这里有一个扩展方法可以帮助您找到相对于宿主窗体的控件边界:

using System;
using System.Drawing;
using System.Windows.Forms;

public static class ControlExtensions
{
    public static Rectangle GetBoundsRelativeToForm(this Control c)
    {
        if (c == null)
            throw new ArgumentNullException(nameof(c));

        var form = c.FindForm();
        if (form == null)
            throw new InvalidOperationException("The control is not located on a form.");

        var parent = c.Parent;
        if (parent == null)
            throw new InvalidOperationException("The control does not have a parent.");

        var p = form.PointToClient(parent.PointToScreen(c.Location));
        return new Rectangle(p, c.Size);
    }
}

例如:

var r = textBox1.GetBoundsRelativeToForm();

我重现了该问题,并且发现位置计算正确。但是DwmRegisterThumbnail 假设整个窗口区域为客户区,而它预计会使用客户区。

我认为这是主题的问题,作为快速修复,我以这种方式更正了位置:

Dim p0 As Point = Me.ParentForm.PointToScreen(Point.Empty)
Dim p1 As Point = Me.ParentForm.DesktopLocation
Dim relativePos As Point = Me.ParentForm.PointToClient(Me.PointToScreen(Point.Empty)) 
relativePos.X += (p0.X - p1.X)
relativePos.Y += (p0.Y - p1.Y) 

事实上,使用这段代码,我将边框宽度和标题栏高度添加到结果中。

【讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • 由于标准主题不存在问题,因此很可能是第 3 方主题的问题。目前我不知道如何检测主题是否存在此类问题,因此只需分享问题中提到的特定主题的修复。希望它有所帮助:)
【解决方案2】:

下面的示例代码将帮助您找到控件相对于窗体的位置。表单可以是无边框或有边框的。

使用下面的方法作为控件类型的扩展。或者只是从参数中删除这个关键字使其成为普通方法

public static Point GetPositionInForm(this Control ctrl)
{
    Point p = ctrl.Location;
    Control parent = ctrl.Parent;
    Form frm = ctrl.FindForm();
    Rectangle screenRectangle = frm.RectangleToScreen(frm.ClientRectangle);
    int titleHeight = screenRectangle.Top - frm.Top;
    int leftMargin = screenRectangle.Left - frm.Left;
    p.Offset(leftMargin, titleHeight);

    while (!(parent is Form))
    {
        p.Offset(parent.Location.X, parent.Location.Y);
        parent = parent.Parent;
    }
    return p;
}

【讨论】:

    猜你喜欢
    • 2013-08-04
    • 2021-01-29
    • 1970-01-01
    • 2015-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-09
    • 1970-01-01
    相关资源
    最近更新 更多