如何修改ItemsControl中元素的Canvas.Left?元素移动功能失效求助
问题:ItemsControl中Canvas内的矩形无法移动,Canvas.GetLeft/SetLeft无效
我在Canvas上通过ItemsControl绑定ViewModel的列表绘制矩形,矩形能正常显示,但移动功能完全失效——事件处理里的Canvas.GetLeft()和Canvas.SetLeft()根本不起作用。
我的ItemsControl代码
<ItemsControl Width="1920" Height="1080" ItemsSource="{Binding Controls,Mode=TwoWay}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <UniformGrid Rows="2" PreviewMouseUp="Container_Control_MouseUp" PreviewMouseMove="Container_Control_MouseMove" PreviewMouseDown="Container_Control_MouseDown"> <Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="Blue" /> <TextBlock Grid.Row="1" Text="move it" /> </UniformGrid> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Canvas.Top" Value="{Binding X}" /> <Setter Property="Canvas.Left" Value="{Binding Y}" /> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl>
事件处理代码(无效)
public partial class ControlsView : UserControl { bool _isMouseDown = false; Point _mouseDownPosition; Point _mouseDownControlPosition; public ControlsView(ControlsViewModel vm) { this.DataContext = vm; InitializeComponent(); } private void Container_Control_MouseUp(object sender, MouseButtonEventArgs e) { var c = sender as UIElement; _isMouseDown = false; c.ReleaseMouseCapture(); } private void Container_Control_MouseMove(object sender, MouseEventArgs e) { if (_isMouseDown) { var c = sender as UIElement; var pos = e.GetPosition(this); var dp = pos - _mouseDownPosition; Canvas.SetLeft(c, _mouseDownControlPosition.X + dp.X); Canvas.SetTop(c, _mouseDownControlPosition.Y + dp.Y); } } private void Container_Control_MouseDown(object sender, MouseButtonEventArgs e) { var c = sender as UIElement; _isMouseDown = true; _mouseDownPosition = e.GetPosition(this); _mouseDownControlPosition = new Point(double.IsNaN(Canvas.GetLeft(c)) ? 0 : Canvas.GetLeft(c), double.IsNaN(Canvas.GetTop(c)) ? 0 : Canvas.GetTop(c)); c.CaptureMouse(); } }
原因分析
你操作的是DataTemplate里的UniformGrid,但Canvas的Top/Left附加属性是设置在ItemsControl的容器元素(ContentPresenter)上的,不是模板里的内部元素。所以对UniformGrid调用Canvas.SetLeft根本不会影响它在Canvas里的位置,而且就算你找到容器元素直接改附加属性,也会被绑定({Binding X})覆盖,因为绑定的优先级更高。
修复方案
1. 给ItemsControl添加Name
先给XAML里的ItemsControl加个名字,方便后台代码获取容器:
<ItemsControl x:Name="itemsControl" Width="1920" Height="1080" ItemsSource="{Binding Controls,Mode=TwoWay}"> <!-- 其余代码不变 --> </ItemsControl>
2. 修改事件处理代码
要拿到正确的容器元素,然后更新ViewModel里的X/Y属性(确保ViewModel模型实现INotifyPropertyChanged):
public partial class ControlsView : UserControl { bool _isMouseDown = false; Point _mouseDownPosition; double _startX; double _startY; private ControlsViewModel _vm; public ControlsView(ControlsViewModel vm) { _vm = vm; DataContext = vm; InitializeComponent(); } private void Container_Control_MouseUp(object sender, MouseButtonEventArgs e) { var c = sender as UIElement; _isMouseDown = false; c.ReleaseMouseCapture(); } private void Container_Control_MouseMove(object sender, MouseEventArgs e) { if (!_isMouseDown) return; var c = sender as UIElement; // 获取当前元素对应的ItemsControl容器(ContentPresenter) var container = ItemsControl.ContainerFromElement(itemsControl, c) as UIElement; if (container == null) return; var pos = e.GetPosition(itemsControl); // 计算鼠标偏移量 var offsetX = pos.X - _mouseDownPosition.X; var offsetY = pos.Y - _mouseDownPosition.Y; // 获取绑定的数据模型 var controlItem = container.DataContext as YourControlModel; if (controlItem != null) { // 更新模型的位置属性,绑定会自动同步UI controlItem.X = _startX + offsetX; controlItem.Y = _startY + offsetY; } } private void Container_Control_MouseDown(object sender, MouseButtonEventArgs e) { var c = sender as UIElement; _isMouseDown = true; _mouseDownPosition = e.GetPosition(itemsControl); var container = ItemsControl.ContainerFromElement(itemsControl, c) as UIElement; if (container != null) { var controlItem = container.DataContext as YourControlModel; if (controlItem != null) { // 记录初始位置 _startX = controlItem.X; _startY = controlItem.Y; } } c.CaptureMouse(); } }
3. 确保数据模型实现INotifyPropertyChanged
你的YourControlModel必须实现通知接口,否则属性更新不会反映到UI:
using System.ComponentModel; using System.Runtime.CompilerServices; public class YourControlModel : INotifyPropertyChanged { private double _x; private double _y; private double _width; private double _height; public double X { get => _x; set { _x = value; OnPropertyChanged(); } } public double Y { get => _y; set { _y = value; OnPropertyChanged(); } } public double Width { get => _width; set { _width = value; OnPropertyChanged(); } } public double Height { get => _height; set { _height = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
内容的提问来源于stack exchange,提问作者xlxdd
相关产品推荐
相关产品推荐

