ItemsControl中Canvas相关方法失效问题求助
在Canvas标签内直接使用Canvas.GetTop()、Canvas.SetTop()等方法一切正常,但在ItemsControl的ItemTemplate中调用这些方法时,会返回NaN,SetTop、SetLeft也无法生效。
问题复现代码
XAML代码
<ItemsControl> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Button Click="Button_Click">Text</Button> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
C#代码
private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show(Canvas.GetTop(sender as Button).ToString()); }
补充现象
如果直接在ItemsControl中添加带命名的Button,调用Canvas.GetTop(命名控件)就能正常返回值,但ItemTemplate中无法给元素命名,示例代码如下:
XAML代码
<ItemsControl ItemsSource="{Binding SomeList}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <Button x:Name="Btn" Click="Button_Click">Text</Button> </ItemsControl>
C#代码
private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show(Canvas.GetTop(Btn).ToString()); }
原因分析
ItemsControl的ItemTemplate生成的元素(比如示例中的Button)并不是直接作为ItemsPanel里Canvas的子元素,而是被包裹在ContentPresenter(即ItemContainer)中,Canvas的附加属性仅对其直接子元素生效。当你调用Canvas.GetTop(Button)时,实际是在查找Button的直接父容器(ContentPresenter)上的Canvas附加属性,而这个容器本身没有设置过Top值,所以返回NaN。
而直接在ItemsControl中添加的Button,会被包装成ItemContainer后直接作为Canvas的子元素,此时通过命名控件调用Canvas.GetTop,实际是间接获取了该ItemContainer上的Canvas属性值,所以能正常工作。
解决方法
方法1:通过VisualTreeHelper找到ItemContainer
在点击事件中,向上遍历VisualTree,找到Button所在的ContentPresenter(ItemContainer),再获取它的Canvas附加属性值:
private void Button_Click(object sender, RoutedEventArgs e) { var button = sender as Button; var container = FindVisualParent<ContentPresenter>(button); if (container != null) { double top = Canvas.GetTop(container); MessageBox.Show(top.ToString()); } } private T FindVisualParent<T>(DependencyObject child) where T : DependencyObject { var parent = VisualTreeHelper.GetParent(child); if (parent is T) return parent as T; return parent != null ? FindVisualParent<T>(parent) : null; }
方法2:通过ItemContainerStyle设置Canvas属性
在ItemsControl中定义ItemContainerStyle,直接给ItemContainer设置Canvas的Top/Left属性(可绑定到数据源的属性),这样后续无论是获取还是设置,都针对ItemContainer操作:
<ItemsControl ItemsSource="{Binding SomeList}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Top" Value="{Binding TopValue}" /> <Setter Property="Canvas.Left" Value="{Binding LeftValue}" /> </Style> </ItemsControl.ItemContainerStyle> <ItemsControl.ItemTemplate> <DataTemplate> <Button Click="Button_Click">Text</Button> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
此时在点击事件中,同样需要找到ItemContainer来获取/设置值,或者直接通过数据源绑定来更新位置。
内容的提问来源于stack exchange,提问作者Danial

