WPF可拖拽ListView

1.控件描述

WPF實現一個ListView控件Item子項可刪除也可拖拽排序,效果如下圖所示
可拖拽ListView

2.實現代碼

配合 WrapPanel 實現水平自動換行,并開啟拖拽

<ListViewx: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"><Borderx:Name="border"Height="30"Margin="2,2,3,3"HorizontalAlignment="Stretch"Background="#f7f7f8"CornerRadius="5"Cursor="Hand"><Gridx:Name="innerGrid"MinWidth="20"VerticalAlignment="Center"><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions><Labelx:Name="label"Margin="2,0,0,0"Content="{Binding DisplayText}"FontSize="13"Foreground="#000" /><ImageGrid.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;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85702.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85702.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85702.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

相機--雙目立體相機

教程 鏈接1 教程匯總 立體匹配算法基礎概念 視頻講解攝像機標定和雙目立體原理 兩個鏡頭。 雙目相機也叫立體相機--Stereo Camera&#xff0c;屬于深度相機。 作用 1&#xff0c;獲取圖像特征&#xff1b; 2&#xff0c;獲取圖像深度信息&#xff1b; 原理 原理和標定 …

Unity3D仿星露谷物語開發59之定制角色襯衫

1、目標 自定義角色襯衫、褲子、手臂顏色。 2、概念 在Assets -> Sprites -> Output Textures下&#xff0c;Customised_farmer為目前角色所用的精靈表。 如果上面是輸出紋理&#xff0c;那么輸入紋理是什么呢&#xff1f;它位于Assets/Sprites/Sprite Textures/Chara…

【HarmonyOS 5】游戲開發教程

一、開發環境搭建 ?工具配置? 安裝DevEco Studio 5.1&#xff0c;啟用CodeGenie AI助手&#xff08;Settings → Tools → AI Assistant&#xff09;配置游戲模板&#xff1a;選擇"Game"類型項目&#xff0c;勾選手機/平板/折疊屏多設備支持 二、游戲引擎核心架構…

深度探索:如何用DeepSeek重構你的工作流

前言:AI時代的工作革命 在人工智能浪潮席卷的今天,DeepSeek作為國產大模型的代表之一,正以其強大的自然語言處理能力、代碼生成能力和多模態交互特性,重新定義著人類的工作方式。根據IDC報告顯示,2024年企業級AI應用市場規模已突破800億美元,其中智能辦公場景占比達32%,…

Linux 進程調度與管理:從內核管理到調度機制的深度解析

文章目錄 引言一、進程基礎&#xff1a;概念與核心數據結構1.1 進程的本質&#xff1a;程序的動態化身1.2 進程控制塊&#xff08;PCB&#xff09;&#xff1a;內核管理的靈魂1.2.1 鏈表節點嵌入1.2.2 鏈表操作宏1.2.3 全局鏈表管理 1.3 進程查看與系統調用1.3.1 通過系統調用獲…

信息學奧賽一本通 1570:【例 2】能量項鏈 | 1843:【06NOIP提高組】能量項鏈 | 洛谷 P1063 [NOIP 2006 提高組] 能量項鏈

【題目鏈接】 ybt 1570&#xff1a;【例 2】能量項鏈 ybt 1843&#xff1a;【06NOIP提高組】能量項鏈 洛谷 P1063 [NOIP 2006 提高組] 能量項鏈 【題目考點】 1. 動態規劃&#xff1a;區間動規 2. 環形序列 解決方法&#xff1a;破環為鏈 模板題&#xff1a;洛谷 P1880 [N…

旅游微信小程序制作指南

想創建旅游微信小程序嗎&#xff1f;知道旅游業企業怎么打造自己的小程序嗎&#xff1f;這里有零基礎小白也能學會的教程&#xff0c;教你快速制作旅游類微信小程序&#xff01; 旅游行業能不能開發微信小程序呢&#xff1f;答案是肯定的。微信小程序對旅游企業來說可是個寶&am…

Vue3+Vite中lodash-es安裝與使用指南

在 Vue 3 Vite 項目中安裝和使用 lodash-es 的詳細指南如下&#xff1a; 一、為什么選擇 lodash-es&#xff1f; ES 模塊支持&#xff1a;lodash-es 以原生 ES 模塊格式發布&#xff0c;支持現代構建工具的 Tree Shaking 按需加載&#xff1a;只引入需要的函數&#xff0c;顯…

法律模型選型

當然可以&#xff0c;以下是關于法律法規相關模型的技術選型調研建議&#xff0c;適合算法實習生從0入手&#xff0c;并能交付有深度的調研報告&#xff1a; 一、調研背景與目標 目標&#xff1a;調研用于處理法律法規類任務的大模型與技術方案&#xff0c;明確適合本團隊的模…

軟件工程專業的本科生應該具備哪些技能

軟件工程專業的本科生需要具備扎實的技術基礎、良好的開發流程認知和一定的軟技能&#xff0c;以適應軟件開發行業的需求。以下從技術技能、開發流程與工具、軟技能、實踐能力等維度整理核心技能清單&#xff0c;供參考&#xff1a; 一、核心技術技能 1. 編程語言 - 必學基礎語…

[Java 基礎]類,面向對象的藍圖

首先需要區分類和對象都是啥&#xff1f; 類&#xff1a;類是一個模板&#xff0c;它描述一類對象的行為和狀態&#xff0c;類這個概念更像是下定義&#xff0c;更像是模板&#xff08;橡皮泥膜具&#xff09;。 對象&#xff1a;對象&#xff08;不是女朋友&#xff09;是類…

selenium-自動更新谷歌瀏覽器驅動

1、簡介 selenium最初是一個自動化測試工具&#xff0c;而爬蟲中使用它主要是為了解決requests無法直接執行JavaScript代碼的問題&#xff0c;因為有些網頁數據是通過JavaScript動態加載的。selenium本質是通過驅動瀏覽器&#xff0c;完全模擬瀏覽器的操作&#xff0c;比如輸入…

java從azure中讀取用戶信息

以下是用 Java 從 Azure AD 獲取用戶信息的完整實現方案&#xff0c;使用 Spring Boot 框架和 Microsoft 身份驗證庫 (MSAL)&#xff1a; 1. 添加 Maven 依賴 <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.…

C# 數據庫訪問與ORM框架全面指南:從ADO.NET到Entity Framework Core

在現代應用開發中&#xff0c;數據持久化是核心需求之一。作為.NET生態系統中的主力語言&#xff0c;C#提供了豐富多樣的數據庫訪問技術和工具。本文將全面探討C#中的數據庫訪問方式&#xff0c;重點介紹三種主流ORM&#xff08;對象關系映射&#xff09;框架&#xff1a;Entit…

day19 leetcode-hot100-37(二叉樹2)

104. 二叉樹的最大深度 - 力扣&#xff08;LeetCode&#xff09; 1.深度優先遍歷&#xff08;遞歸&#xff09;ps:不好理解&#xff0c;所以我一般不喜歡用遞歸 思路 典型算法&#xff0c;用遞歸求出高度&#xff0c;每次都是深度優先。 具體算法 /*** Definition for a bi…

【LLMs篇】13:LLaDA—大型語言擴散模型

欄目內容論文標題大型語言擴散模型 (Large Language Diffusion Models)核心思想提出LLaDA&#xff0c;一種基于擴散模型的LLM&#xff0c;通過前向掩碼和反向預測過程建模語言分布&#xff0c;挑戰自回歸模型&#xff08;ARM&#xff09;在LLM領域的主導地位&#xff0c;并展示…

Deepfashion2 數據集使用筆記

目錄 數據類別: 篩選類別數據: 驗證篩選前2個類別: Deepfashion2 的解壓碼 數據類別: 類別含義: Class idx類別名稱英文名稱0短上衣short sleeve top1長上衣long sleeve top2短外套short sleeve outwear3長外套long sleeve outwear4裙子skirt5褲子trousers6連衣裙dre…

Java并發編程哲學系列匯總

文章目錄 并發編程基礎并發編程進階并發編程實踐 并發編程基礎 Java并發編程基礎小結 Java線程池知識點小結 詳解JUC包下各種鎖的使用 并發編程利器Java CAS原子類全解 深入理解Java中的final關鍵字 Java并發容器深入解析&#xff1a;HashMap與ArrayList線程安全問題及解…

git 之 stash

一、git stash&#xff1a;臨時保存工作區修改 作用 將當前工作目錄和暫存區的未提交修改保存到棧中&#xff0c;并恢復工作區到上一次提交的干凈狀態。 適用場景&#xff1a; 臨時切換分支修復緊急 Bug拉取遠程代碼前清理工作區保存實驗性代碼避免生成無效提交 常用命令&am…

vxe-grid 雙擊行,打開expand的內容

1、官網api Vxe Table v4.6&#xff08;根據版本&#xff09; 要調用這個事件&#xff0c;雙擊單元格&#xff0c;我們打開type"expand"的內容 2、打開的事件toggleRowExpand 3、事件的說明 這個方法&#xff0c;會自動判斷當前展開的狀態&#xff0c;然后去觸發相…