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)的矩形自然就显示在整个控件的中心。
另外还有两个次要问题:
Canvas.GetLeft(this)获取的是控件在父Canvas中的位置,只有当控件直接放在Canvas容器里时这个方法才有效,否则会返回未初始化的异常数值(你看到的-27890就是这种情况)。- 你设置
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

