You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF UserControl设置(0,0)绘制矩形却显示在中心的问题排查

问题:WPF UserControl中设置(0,0)的矩形显示在中心位置的原因

我在WPF中开发了一个仅包含Border的UserControl,其高度、宽度和Background属性通过样式设置,默认样式在XAML中定义,后续会根据用户操作通过代码动态修改。现在需要在该控件上绘制两个矩形,但当将第一个矩形的起始位置设置为控件的(0,0)时,它却显示在控件的中心位置,请问是什么原因导致了这种行为?

相关代码

样式代码

<Style x:Key="MyControl_1" TargetType="{x:Type thermovision_views:TristableSwitch}" BasedOn="{StaticResource MyControl_Default}">
        <Setter Property="Content" Value="/Images/1.png"/>
        <Setter Property="Background">
            <Setter.Value>
                <ImageBrush ImageSource="/Images/2.png"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Width" Value="116" />
        <Setter Property="Height" Value="116" />
        <Setter Property="mynamespace:MyControl.Value" Value="0"/>
</Style>

<Style x:Key="MyControl_Default" TargetType="{x:Type mynamespace:MyControl}" BasedOn="{StaticResource Default}">
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type mynamespace:MyControl}">
                    <Border Background="{TemplateBinding Background}">
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

<Style x:Key="Default" TargetType="FrameworkElement">
        <Setter Property="HorizontalAlignment" Value="Left"/>
        <Setter Property="VerticalAlignment" Value="Top"/>
</Style>

控件XAML代码

<UserControl x:Class="MyNamespace.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <Border x:Name="Placeholder" />
    </Grid>
</UserControl>

后台代码片段

private void SetupTouchRegions() {
            int x = (int)Canvas.GetLeft(this); //返回异常值,比如-27890
            int y = (int)Canvas.GetTop(this); //返回异常值,比如-27890
            TouchRegionUp = new Rectangle(0, 0, (int)Width, TouchRegionVerticalOffset);
            TouchRegionDown = new Rectangle(0, (int)Height - TouchRegionVerticalOffset, (int)Width, TouchRegionVerticalOffset);
        }

private void DrawTouchRegionsBorders() {
            var container = new Canvas();
            var touchRegionUp = TypeConverter.Rectangle(TouchRegionUp);
            var touchRegionDown = TypeConverter.Rectangle(TouchRegionDown);

            //TouchRegionUp
            Canvas.SetLeft(touchRegionUp, TouchRegionUp.X);
            Canvas.SetTop(touchRegionUp, TouchRegionUp.Y);
            touchRegionUp.Stroke = Brushes.Red;
            touchRegionUp.StrokeThickness = 2;

            //TouchRegionDown
            Canvas.SetLeft(touchRegionDown, TouchRegionDown.X);
            Canvas.SetTop(touchRegionDown, TouchRegionDown.Y);
            touchRegionDown.Stroke = Brushes.Red;
            touchRegionDown.StrokeThickness = 2;

            //添加容器到控件
            container.Children.Add(touchRegionUp);
            container.Children.Add(touchRegionDown);
            this.Content = container;
        }

类型别名定义

global using Rectangle = System.Drawing.Rectangle;
global using WpfRectangle = System.Windows.Rect;

原因分析

核心问题出在控件模板的ContentPresenter设置了居中对齐:
你在MyControl_Default的ControlTemplate里,给ContentPresenter设置了HorizontalAlignment="Center"和VerticalAlignment="Center"。当你通过代码设置this.Content = container时,这个Canvas会被ContentPresenter托管,ContentPresenter会把Canvas在Border内部居中显示——也就是说,Canvas本身的左上角已经是整个UserControl的中心位置了,你在Canvas里设置(0,0)的矩形自然就显示在整个控件的中心。

另外还有两个次要问题:

  1. Canvas.GetLeft(this)获取的是控件在父Canvas中的位置,只有当控件直接放在Canvas容器里时这个方法才有效,否则会返回未初始化的异常数值(你看到的-27890就是这种情况)。
  2. 你设置this.Content = container会替换掉UserControl原有的Grid和Placeholder Border,因为UserControl的Content只能有一个根元素。

解决方案

方式一:修改控件模板,让Content铺满控件

修改MyControl_Default样式里的ContentPresenter,把对齐方式改成Stretch,这样Content会填满整个Border区域,Canvas的左上角就对应UserControl的左上角:

<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

方式二:不替换Content,将Canvas添加到原有Grid中

保留UserControl原有的Grid布局,把Canvas作为子元素添加进去,而不是替换Content:

private void DrawTouchRegionsBorders() {
    var container = new Canvas();
    var touchRegionUp = TypeConverter.Rectangle(TouchRegionUp);
    var touchRegionDown = TypeConverter.Rectangle(TouchRegionDown);

    // 原有设置矩形的代码不变
    Canvas.SetLeft(touchRegionUp, TouchRegionUp.X);
    Canvas.SetTop(touchRegionUp, TouchRegionUp.Y);
    touchRegionUp.Stroke = Brushes.Red;
    touchRegionUp.StrokeThickness = 2;

    Canvas.SetLeft(touchRegionDown, TouchRegionDown.X);
    Canvas.SetTop(touchRegionDown, TouchRegionDown.Y);
    touchRegionDown.Stroke = Brushes.Red;
    touchRegionDown.StrokeThickness = 2;

    container.Children.Add(touchRegionUp);
    container.Children.Add(touchRegionDown);

    // 找到原有Grid并添加Canvas
    var grid = this.Content as Grid;
    if (grid != null)
    {
        // 设置Canvas铺满Grid
        container.HorizontalAlignment = HorizontalAlignment.Stretch;
        container.VerticalAlignment = VerticalAlignment.Stretch;
        grid.Children.Add(container);
    }
}

额外建议

  • 尽量避免混用System.Drawing.Rectangle和WPF的布局API,统一使用System.Windows.Rect可以减少类型转换的潜在问题。
  • 如果需要获取控件在父容器中的位置,应该用TransformToAncestor方法计算,示例代码:
    var parent = this.Parent as UIElement;
    if (parent != null)
    {
        var transform = this.TransformToAncestor(parent);
        Point position = transform.Transform(new Point(0, 0));
    }
    

内容的提问来源于stack exchange,提问作者Aenye_Cerbin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 04:37:00