一、问题描述
利用Grid定义列将其分成三列,为每列填充元素,元素为自定义控件,即通过Generic声明样式,继承基类控件实现类的行为和方法。此时,元素的通过`HorizontalAlignment被赋值Left或Right,但未对VerticalAlignment赋值,导致其垂直对齐方式为拉伸填充,虽然其样式内部设置了宽高。此时元素通过PointToScreen获取屏幕坐标,得到的并非其实际显示的坐标。
二、知识查阅
- 一些对齐方式的依赖属性?
HorizontalAlignment/VerticalAlignment
,指自身相对于父元素的位置
HorizontalContentAlignment/VerticalContentAlignment
,指子元素在自身元素中的相对位置 - VerticalAlignment 与 HorizontalAlignment的默认值是什么?
两个属性的值可以是Stretch、Left、Right、Top、Bottom、Center
。默认值为Stretch
,即元素将会在其可用空间内拉伸以填充其父元素。 - Stretch何时起作用?
如果元素设置了固定宽高,那么Stretch将不会影响其显示。如果元素的Alignment
为Left、Right、Top、Bottom、Center
任意值,Stretch
则不起作用。另外,如果你想元素保持原始宽高比展示,可以将其Stretch属性设为Uniform/UniformToFill
。 - 为什么Stretch会导致控件显示坐标与获取不一致的问题?
其实并非控件显示坐标与获取不一致,而是获取的控件坐标实际是外层的Grid,对齐方式使用了Stretch导致元素对可用空间进行拉伸填充,而元素内部虽然指定了宽高不影响其显示,但借助控件x:Name得到的元素依旧是外层的Grid而不是控件模板内部的元素。
三、问题复现
<Window x:Class="WpfApp20.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp20"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid x:Name="rectanglegrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--模板外的控件 模拟自定义控件-->
<Grid x:Name="rec1">
<!--模板内的grid 模拟自定义控件内部Panel-->
<Grid>
<Rectangle Fill="Pink" Width="100" Height="100"/>
</Grid>
</Grid>
<Button Grid.Column="1" Width="200" Height="50" Click="Button_Click"/>
<Rectangle Grid.Column="2" Fill="Green"/>
</Grid>
</Window>
// 获取第一列显示元素的屏幕坐标
private void Button_Click(object sender, RoutedEventArgs e)
{
Point point= rec1.PointToScreen(new Point(0, 0));
Console.WriteLine($"x={ point.X},y={point.Y}");
}
- 直接运行程序,得到的rec1是外层Grid拉伸后的实际坐标;
- 如果为rec1的Grid定义
VerticalAlignment="Center"
,得到的rec1是内层粉红色矩形的实际坐标;因为其保证了rec1不是以拉伸方式填充可用空间。 - 如果为rec1的Grid定义
Width="100" Height="100"
,得到的rec1也是内层粉红色矩形的实际坐标;因为其已经有了固定大小,Strech将不会影响其显示。但是如果对内部Grid定义 `Width=“100” Height="100"将不起作用,因为rec1.PointToScreen()获取的是外层,即其本身的实际坐标。
四、反思与总结
对于自定义内部控件,在使用的时候还是需要指定其水平对齐方式和垂直对齐方式,即HorizontalAlignment/VerticalAlignment
;如果没有指定,将会导致对齐方式使用Stretch方式对可用空间进行拉伸。尽管控件样式内部定死的宽高,但还是无法保证定义控件外层的Grid不被拉伸,即表面上看是获取了显示控件的实际位置,其实是获取wpf为自定义控件创建的一个隐形Grid的实际位置。