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

WPF ListView元素拖拽移动失效问题求助

问题描述

我在XAML中创建了一个ListView控件,设置Canvas作为ItemsPanel,将ListViewItem的Canvas.Left和Canvas.Top属性绑定到数据类的X、Y字段,数据集合在C#代码中赋值。我希望拖拽控件内的红色方块(ListViewItem)在窗口内自由移动,但目前移动功能失效,方块无法拖动。需要解决该移动问题,同时保留ListView的选中事件以获取选中元素的数据。


现有代码

XAML代码

<ListView x:Name="ItemsControlCollection" Margin="0,162,0,0" SelectionMode="single" SelectionChanged="ItemsControlCollection_SelectionChanged">
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="Canvas.Left" Value="{Binding X}" />
            <Setter Property="Canvas.Top" Value="{Binding Y}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Canvas Width="50" Height="50" Background="#FFD82B2B" MouseDown="FF_MouseDown" MouseMove="FF_MouseMove" MouseUp="FF_MouseUp"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ListView>

C#代码

数据类定义

public class ItemsControlCollections
{
    public String Title { get; set; }
    public float X { get; set; }
    public float Y { get; set; }
    public List<ItemsControlCollections> _ItemsControlCollections = new List<ItemsControlCollections>();
}

窗口初始化与数据加载

public partial class MainWindow : Window
{
    List<ItemsControlCollections> ObjectItems = new ItemsControlCollections()._ItemsControlCollections;

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        ItemsControlCollection.Items.Clear();
    }

    // 按钮点击从数据库加载数据并设置数据源
    using (SqlDataReader rdr = commandItemXY.ExecuteReader())
    {
        while (rdr.Read())
        {
            ObjectItems.Add(new ItemsControlCollections() { Title = rdr["id_Item"].ToString(), X = Convert.ToInt32(rdr["X"]), Y = Convert.ToInt32(rdr["Y"]) });
        }
    }
    ItemsControlCollection.ItemsSource = ObjectItems;
}

拖拽事件代码

int countZ = 0;
bool _canMove = false;
Point _offsetPoint = new Point(0, 0);
private void FF_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    _canMove = true;
    countZ++;
    FrameworkElement ffElement = (FrameworkElement)sender;
    Grid.SetZIndex(ffElement, countZ);

    Point posCursor = e.MouseDevice.GetPosition(this);
    _offsetPoint = new Point(
        posCursor.X - Canvas.GetLeft(ffElement),
        posCursor.Y - Canvas.GetTop(ffElement)
    );
    e.MouseDevice.Capture(ffElement);
}

private void FF_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    if (_canMove == true)
    {
        FrameworkElement ffElement = (FrameworkElement)sender;

        if (e.MouseDevice.Captured == ffElement)
        {
            Point p = e.MouseDevice.GetPosition(this);

            Canvas.SetLeft(ffElement, p.X - _offsetPoint.X);
            Canvas.SetTop(ffElement, p.Y - _offsetPoint.Y);
        }
    }
}

private void FF_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    _canMove = false;
    e.MouseDevice.Capture(null);
}

private void ItemsControlCollection_SelectionChanged(object sender, SelectionChangedEventArgs e)
{

}

解决方案

问题集中在绑定逻辑、拖拽目标定位和数据通知三个核心点,以下是针对性修正:

1. 修复数据类的属性通知

当前数据类未实现INotifyPropertyChanged,且绑定为单向模式,导致拖拽修改无法同步回数据源,ListViewItem位置被绑定值覆盖。

修改数据类:

public class ItemsControlCollections : INotifyPropertyChanged
{
    private string _title;
    private float _x;
    private float _y;

    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            OnPropertyChanged(nameof(Title));
        }
    }

    public float X
    {
        get => _x;
        set
        {
            _x = value;
            OnPropertyChanged(nameof(X));
        }
    }

    public float Y
    {
        get => _y;
        set
        {
            _y = value;
            OnPropertyChanged(nameof(Y));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

同时修改XAML中绑定为双向模式:

<Style TargetType="{x:Type ListViewItem}">
    <Setter Property="Canvas.Left" Value="{Binding X, Mode=TwoWay}" />
    <Setter Property="Canvas.Top" Value="{Binding Y, Mode=TwoWay}" />
</Style>

2. 修正拖拽事件的目标对象

当前拖拽事件绑定在模板内的Canvas上,但实际控制位置的是ListViewItem(Canvas的父容器),修改拖拽逻辑定位到ListViewItem:

int countZ = 0;
bool _canMove = false;
Point _offsetPoint = new Point(0, 0);

private void FF_MouseDown(object sender, MouseButtonEventArgs e)
{
    _canMove = true;
    countZ++;
    FrameworkElement innerCanvas = (FrameworkElement)sender;
    // 获取对应的ListViewItem容器
    ListViewItem itemContainer = ItemsControl.ContainerFromElement(ItemsControlCollection, innerCanvas) as ListViewItem;
    if (itemContainer == null) return;

    Grid.SetZIndex(itemContainer, countZ);
    // 计算鼠标相对于ListViewItem的偏移
    Point posCursor = e.MouseDevice.GetPosition(ItemsControlCollection);
    _offsetPoint = new Point(
        posCursor.X - Canvas.GetLeft(itemContainer),
        posCursor.Y - Canvas.GetTop(itemContainer)
    );
    e.MouseDevice.Capture(itemContainer);
    // 手动选中当前项
    ItemsControlCollection.SelectedItem = itemContainer.DataContext;
}

private void FF_MouseMove(object sender, MouseEventArgs e)
{
    if (!_canMove) return;

    FrameworkElement innerCanvas = (FrameworkElement)sender;
    ListViewItem itemContainer = ItemsControl.ContainerFromElement(ItemsControlCollection, innerCanvas) as ListViewItem;
    if (itemContainer == null || e.MouseDevice.Captured != itemContainer) return;

    Point p = e.MouseDevice.GetPosition(ItemsControlCollection);
    // 修改ListViewItem的位置,通过双向绑定同步到数据源
    Canvas.SetLeft(itemContainer, p.X - _offsetPoint.X);
    Canvas.SetTop(itemContainer, p.Y - _offsetPoint.Y);
}

private void FF_MouseUp(object sender, MouseButtonEventArgs e)
{
    _canMove = false;
    e.MouseDevice.Capture(null);
}

3. 修正窗口类的集合声明

原集合初始化方式不合理,调整为直接声明:

public partial class MainWindow : Window
{
    private List<ItemsControlCollections> ObjectItems = new List<ItemsControlCollections>();

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        ItemsControlCollection.Items.Clear();
    }

    // 按钮点击加载数据方法(补充完整事件绑定)
    private void LoadData_Click(object sender, RoutedEventArgs e)
    {
        ObjectItems.Clear();
        using (SqlDataReader rdr = commandItemXY.ExecuteReader())
        {
            while (rdr.Read())
            {
                ObjectItems.Add(new ItemsControlCollections() 
                { 
                    Title = rdr["id_Item"].ToString(), 
                    X = Convert.ToInt32(rdr["X"]), 
                    Y = Convert.ToInt32(rdr["Y"]) 
                });
            }
        }
        ItemsControlCollection.ItemsSource = ObjectItems;
    }
}

4. 完善选中事件逻辑

现在可以正常获取选中元素的数据:

private void ItemsControlCollection_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count > 0)
    {
        ItemsControlCollections selectedItem = e.AddedItems[0] as ItemsControlCollections;
        // 此处可使用selectedItem的Title、X、Y等数据
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:14:59