WPF可拖拽ListView

发布于:2025-06-06 ⋅ 阅读:(20) ⋅ 点赞:(0)
1.控件描述

WPF实现一个ListView控件Item子项可删除也可拖拽排序,效果如下图所示
可拖拽ListView

2.实现代码

配合 WrapPanel 实现水平自动换行,并开启拖拽

<ListView
    x:Name="listView"
    Grid.Row="1"
    Width="300"
    AllowDrop="True"
    Background="#DCE1E7"
    DragEnter="ListView_OnDragEnter"
    DragLeave="ListView_OnDragLeave"
    DragOver="ListView_OnDragOver"
    Drop="ListView_OnDrop"
    FocusVisualStyle="{x:Null}"
    ItemContainerStyle="{StaticResource NoSelectionListViewItemStyle}"
    ItemTemplate="{StaticResource ItemTemplate}"
    ItemsSource="{Binding FilterItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
    PreviewKeyDown="ListView_OnPreviewKeyDown"
    PreviewMouseLeftButtonDown="ListView_OnPreviewMouseLeftButtonDown"
    PreviewMouseMove="ListView_OnPreviewMouseMove"
    SelectionChanged="ListView_OnSelectionChanged">
    <ListView.Resources>
        <Style TargetType="ScrollViewer">
            <Setter Property="HorizontalScrollBarVisibility" Value="Disabled" />
            <Setter Property="VerticalScrollBarVisibility" Value="Auto" />
        </Style>
    </ListView.Resources>
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
</ListView>

模版及样式,引用图片自行替换

<Style x:Key="deleteImgStyle" TargetType="{x:Type Image}">
    <Setter Property="Width" Value="16" />
    <Setter Property="Height" Value="16" />
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Opacity" Value="0.8" />
        </Trigger>
    </Style.Triggers>
</Style>
<Style x:Key="NoSelectionListViewItemStyle" TargetType="ListViewItem">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="FocusVisualStyle" Value="{x:Null}" />
    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <Grid x:Name="Grid" Background="{TemplateBinding Background}">
                    <ContentPresenter />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="Grid" Property="Background" Value="Transparent" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<DataTemplate x:Key="ItemTemplate" DataType="local:FilterItem">
    <Border
        x:Name="border"
        Height="30"
        Margin="2,2,3,3"
        HorizontalAlignment="Stretch"
        Background="#f7f7f8"
        CornerRadius="5"
        Cursor="Hand">
        <Grid
            x:Name="innerGrid"
            MinWidth="20"
            VerticalAlignment="Center">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Label
                x:Name="label"
                Margin="2,0,0,0"
                Content="{Binding DisplayText}"
                FontSize="13"
                Foreground="#000" />
            <Image
                Grid.Column="1"
                Margin="5,0"
                MouseLeftButtonDown="Image_MouseLeftButtonDown"
                Source="pack://application:,,,/WPFTest;component/Resources/delete.png"
                Stretch="Uniform"
                Style="{StaticResource deleteImgStyle}"
                Tag="{Binding}" />
        </Grid>
    </Border>
    <DataTemplate.Triggers>
        <Trigger SourceName="border" Property="IsMouseOver" Value="True">
            <Setter TargetName="border" Property="Background" Value="#80f7f7f8" />
        </Trigger>
        <DataTrigger Binding="{Binding IsSelected}" Value="True">
            <Setter TargetName="border" Property="Background" Value="#BCC2C9" />
        </DataTrigger>
        <DataTrigger Binding="{Binding IsDraggedOver}" Value="True">
            <Setter TargetName="border" Property="Background" Value="DarkOrange" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

后台代码

private ObservableCollection<FilterItem> _filterItems;
/// <summary>
/// 绑定数据源
/// </summary>
public ObservableCollection<FilterItem> FilterItems
{
    get => _filterItems;
    set { _filterItems = value; OnPropertyChanged(nameof(FilterItems)); }
}

// 删除Item
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    e.Handled = true;
    if (sender is Image image)
    {
        if (image.Tag is FilterItem item)
        {
            FilterItems.Remove(item);
        }
    }
}

// 键盘方向键实现Item排序
private void ListView_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
    if (FilterItems?.Count > 1 && listView.SelectedIndex >= 0)
    {
        var selIndex = listView.SelectedIndex;
        var item = FilterItems[selIndex];
        if (e.Key == Key.Left && selIndex > 0)
        {
            FilterItems.RemoveAt(selIndex);
            FilterItems.Insert(selIndex - 1, item);
        }
        else if (e.Key == Key.Right && selIndex < FilterItems.Count - 1)
        {
            FilterItems.RemoveAt(selIndex);
            FilterItems.Insert(selIndex + 1, item);
        }
    }
    e.Handled = true;
}

#region 拖拽排序
private ListViewItem _draggedItem;  // 用于存储被拖动的项
private ListViewItem _dropTargetItem;  // 用于存储当前的拖拽目标项
// 标记是否处于拖拽状态
private bool _isDragging;
// 鼠标按下时的位置
private Point _mouseDownPosition;
// 最小拖拽距离
private const double DragThreshold = 10.0;

/// <summary>
/// 处理拖动开始的操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var item = FindVisualParent<ListViewItem>(e.OriginalSource as DependencyObject);
    if (item != null)
    {
        _draggedItem = item;
        _mouseDownPosition = e.GetPosition(null);
    }
}

private void ListView_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
    if (_draggedItem != null && e.LeftButton == MouseButtonState.Pressed)
    {
        var currentPosition = e.GetPosition(null);
        if (Math.Abs(currentPosition.X - _mouseDownPosition.X) > DragThreshold ||
            Math.Abs(currentPosition.Y - _mouseDownPosition.Y) > DragThreshold)
        {
            _isDragging = true;
            // 开始拖动操作
            DragDrop.DoDragDrop(_draggedItem, _draggedItem.Content, DragDropEffects.Move);
            _isDragging = false;
            _draggedItem = null;  // 清除拖拽项
        }
    }
}

/// <summary>
/// 处理拖动过程中的操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnDragOver(object sender, DragEventArgs e)
{
    var listView = sender as ListView;
    var point = e.GetPosition(listView);
    var hitTestResult = VisualTreeHelper.HitTest(listView, point);
    if (hitTestResult != null)
    {
        var targetItem = FindVisualParent<ListViewItem>(hitTestResult.VisualHit);
        if (targetItem != null && targetItem != _draggedItem)
        {
            // 只有在拖拽目标项发生变化时才进行更新
            if (_dropTargetItem != targetItem)
            {
                // 恢复之前目标项的背景色
                if (_dropTargetItem != null)
                {
                    if (_dropTargetItem.DataContext is FilterItem previousViewModel)
                    {
                        previousViewModel.IsDraggedOver = false;
                    }
                }

                // 高亮显示当前拖拽目标项
                if (targetItem.DataContext is FilterItem targetViewModel)
                {
                    targetViewModel.IsDraggedOver = true;
                }

                _dropTargetItem = targetItem;
            }
            e.Effects = DragDropEffects.Move;  // 允许移动操作
            e.Handled = true;
        }
    }
}

private void ListView_OnDragEnter(object sender, DragEventArgs e)
{
    // 设置拖拽目标项的状态为被拖拽
    var listView = sender as ListView;
    var point = e.GetPosition(listView);
    var hitTestResult = VisualTreeHelper.HitTest(listView, point);
    if (hitTestResult != null)
    {
        var targetItem = FindVisualParent<ListViewItem>(hitTestResult.VisualHit);
        if (targetItem != null && targetItem != _draggedItem)
        {
            // 更新之前拖拽目标项的状态
            if (_dropTargetItem != null && _dropTargetItem != targetItem)
            {
                if (_dropTargetItem.DataContext is FilterItem previousViewModel)
                {
                    previousViewModel.IsDraggedOver = false;
                }
            }

            // 更新当前拖拽目标项的状态
            if (targetItem.DataContext is FilterItem currentViewModel)
            {
                currentViewModel.IsDraggedOver = true;
            }

            _dropTargetItem = targetItem;
        }
    }
    e.Effects = DragDropEffects.Move;  // 允许移动操作
    e.Handled = true;  // 标记事件已处理
}

/// <summary>
/// 处理拖动离开目标区域的操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnDragLeave(object sender, DragEventArgs e)
{
    // 恢复目标项的背景色
    if (_dropTargetItem != null)
    {
        if (_dropTargetItem.DataContext is FilterItem viewModel)
        {
            viewModel.IsDraggedOver = false;
        }
        _dropTargetItem = null;  // 清除拖拽目标项
    }
}

/// <summary>
/// 处理放置操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView_OnDrop(object sender, DragEventArgs e)
{
    // 检查拖动项和目标项是否有效
    if (_draggedItem != null && _dropTargetItem != null && _draggedItem != _dropTargetItem)
    {
        var items = listView.Items.OfType<object>().ToList();
        var draggedIndex = items.IndexOf(_draggedItem.Content);
        var dropIndex = items.IndexOf(_dropTargetItem.Content);

        if (draggedIndex != -1 && dropIndex != -1)
        {
            // 移动项的位置
            var item = FilterItems[draggedIndex];
            FilterItems.RemoveAt(draggedIndex);
            FilterItems.Insert(dropIndex, item);

            // 恢复目标项的背景色
            if (_dropTargetItem.DataContext is FilterItem dropViewModel)
            {
                dropViewModel.IsDraggedOver = false;
            }
        }

        // 清除拖拽项和目标项的引用
        _draggedItem = null;
        _dropTargetItem = null;
    }
    else
    {
        // 处理无效的放置操作
        if (_dropTargetItem != null)
        {
            if (_dropTargetItem.DataContext is FilterItem dropViewModel)
            {
                dropViewModel.IsDraggedOver = false;
            }
        }

        _draggedItem = null;
        _dropTargetItem = null;
    }
}
// 查找可视树中的父级项
private T FindVisualParent<T>(DependencyObject child) where T : DependencyObject
{
    while (child != null && !(child is T))
    {
        child = VisualTreeHelper.GetParent(child);
    }
    return child as T;
}
#endregion

绑定项实体类

public class FilterItem : INotifyPropertyChanged
{
    private string _displayText;
    public string DisplayText
    {
        get => _displayText;
        set { _displayText = value; OnPropertyChanged(nameof(DisplayText)); }
    }

    private bool _isSelected;
    public bool IsSelected
    {
        get => _isSelected;
        set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); }
    }

    private bool _isDraggedOver;
    public bool IsDraggedOver
    {
        get => _isDraggedOver;
        set
        {
            if (_isDraggedOver != value)
            {
                _isDraggedOver = value;
                OnPropertyChanged(nameof(IsDraggedOver));
            }
        }
    }

    public FilterItem()
    { }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到