WPF 写了一个支持数据绑定的Grid
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; namespace WpfApp4 { /*例子: <ScrollViewer> <local:DataBindGrid x:Name="grid"> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"></ColumnDefinition> <ColumnDefinition Width="100"></ColumnDefinition> <ColumnDefinition Width="auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="22"></RowDefinition> </Grid.RowDefinitions> <TextBlock Text="Header1" Margin="10,0,0,0" Grid.Column="0"></TextBlock> <TextBlock Text="Header2" Margin="10,0,0,0" Grid.Column="1"></TextBlock> <TextBlock Text="Header3" Margin="10,0,0,0" Grid.Column="2"></TextBlock> <local:DataBindGrid.ItemTemplate> <ControlTemplate> <Grid Height="30"> <!-- 这里设置了Height等于设置了DataBindGrid的行高 --> <TextBlock Text="{Binding Name0}" Margin="10,0,0,0" Grid.Column="0" MouseDown="AddDataClick"></TextBlock> <TextBlock Text="{Binding Name1}" Margin="10,0,0,0" Grid.Column="1" MouseDown="DeleteDataClick"></TextBlock> <TextBlock Text="{Binding Name2}" Margin="10,0,0,0" Grid.Column="2"></TextBlock> <TextBlock Text="{Binding Name3}" Margin="10,0,0,0" Grid.Column="3"></TextBlock> </Grid> </ControlTemplate> </local:DataBindGrid.ItemTemplate> </local:DataBindGrid> </ScrollViewer> */ public class DataBindGrid:Grid { public static readonly DependencyProperty ItemTemplateProperty= DependencyProperty.Register("ItemTemplate", typeof(ControlTemplate), typeof(DataBindGrid), null); public ControlTemplate ItemTemplate { get { return (ControlTemplate)GetValue(ItemTemplateProperty); } set { SetValue(ItemTemplateProperty, value); } } public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IEnumerable), typeof(DataBindGrid), new PropertyMetadata(OnItemsSourcePropertyValueChanged)); private static void OnItemsSourcePropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataBindGrid grid = (DataBindGrid)d; if (e.OldValue != null && e.OldValue is INotifyCollectionChanged data) { data.CollectionChanged -= grid._notifyCollectionChanged_CollectionChanged; } grid._notifyCollectionChanged = e.NewValue as INotifyCollectionChanged; if (grid._notifyCollectionChanged != null && grid._notifyCollectionChanged is INotifyCollectionChanged data2) { data2.CollectionChanged += grid._notifyCollectionChanged_CollectionChanged; } grid.Bind(); } public System.Collections.IEnumerable ItemsSource { get { return (System.Collections.IEnumerable)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } public delegate void CreateRowDefinitionHandler(object sender, RowDefinition row, int rowIndex); INotifyCollectionChanged _notifyCollectionChanged; public event CreateRowDefinitionHandler CreateRowDefinition; ConcurrentDictionary<object, int> _dataContextDict = new ConcurrentDictionary<object, int>(); List<FrameworkElement> _headerChildren = new List<FrameworkElement>(); public DataBindGrid() { } private void _notifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { this.Dispatcher.Invoke(() => { if (e.Action == NotifyCollectionChangedAction.Remove) { foreach (var data in e.OldItems) { var index = _dataContextDict[data]; _dataContextDict.TryRemove(data, out int o); removeRow(index); } } else if (e.Action == NotifyCollectionChangedAction.Reset) { this.Bind(); } else if (e.Action == NotifyCollectionChangedAction.Add) { var newStartIndex = e.NewStartingIndex; if (_headerChildren.Count > 0) newStartIndex++; for (int i = 0; i < e.NewItems.Count; i++) { var row = new RowDefinition(); if (this.CreateRowDefinition != null) { this.CreateRowDefinition(this, row, i + newStartIndex); } this.RowDefinitions.Insert(i + newStartIndex, row); } foreach (var pair in _dataContextDict) { if (pair.Value >= newStartIndex) { var index = pair.Value + e.NewItems.Count; _dataContextDict[pair.Key] = index; for (int i = 0; i < this.Children.Count; i++) { var ctrl = (FrameworkElement)this.Children[i]; if (ctrl.DataContext == pair.Key) { ctrl.SetValue(Grid.RowProperty, index); } } } } var rowIndex = newStartIndex; foreach (var data in e.NewItems) { _dataContextDict[data] = rowIndex; createRowControls(data, rowIndex); rowIndex++; } } }); } void removeRow(int rowIndex) { for (int i = 0; i < this.Children.Count; i++) { var ctrl = this.Children[i]; var rowvalue = ctrl.GetValue(Grid.RowProperty); if (rowvalue != null && rowvalue.Equals(rowIndex)) { this.Children.RemoveAt(i); i--; } } this.RowDefinitions.RemoveAt(rowIndex); foreach (var pair in _dataContextDict) { if (pair.Value >= rowIndex) { var index = pair.Value - 1; _dataContextDict[pair.Key] = index; for (int i = 0; i < this.Children.Count; i++) { var ctrl = (FrameworkElement)this.Children[i]; if (ctrl.DataContext == pair.Key) { ctrl.SetValue(Grid.RowProperty, index); } } } } } /// <summary> /// 创建行的子控件 /// </summary> /// <param name="data"></param> /// <param name="rowIndex"></param> void createRowControls(object data, int rowIndex) { var rowDef = this.RowDefinitions[rowIndex]; var templateContainer = this.ItemTemplate.LoadContent() as Panel; if (templateContainer == null) throw new Exception("ItemTemplate的根元素必须是Panel"); if (double.IsNaN(templateContainer.Height) == false) rowDef.Height = new GridLength(templateContainer.Height); rowDef.MinHeight = templateContainer.MinHeight; rowDef.MaxHeight = templateContainer.MaxHeight; while (templateContainer.Children.Count > 0) { var ctrl = (FrameworkElement)templateContainer.Children[0]; templateContainer.Children.RemoveAt(0); ctrl.DataContext = data; ctrl.SetValue(Grid.RowProperty, rowIndex); this.Children.Add(ctrl); } templateContainer = null; } void Bind() { if (_headerChildren.Count == 0) { foreach (FrameworkElement ctrl in this.Children) { _headerChildren.Add(ctrl); } } _dataContextDict.Clear(); for (int i = 0; i < this.Children.Count; i++) { var ctrl = this.Children[i]; if (_headerChildren.Contains(ctrl) == false) { this.Children.RemoveAt(i); i--; } } var rowIndex = 0; if (_headerChildren.Count > 0) { if (rowIndex >= this.RowDefinitions.Count) { var row = new RowDefinition(); if (this.CreateRowDefinition != null) { this.CreateRowDefinition(this, row, rowIndex); } this.RowDefinitions.Add(row); } rowIndex++; } if (this.ItemTemplate == null) return; var list = (System.Collections.IEnumerable)_notifyCollectionChanged; foreach (var dataitem in list) { _dataContextDict[dataitem] = rowIndex; if (rowIndex >= this.RowDefinitions.Count) { var row = new RowDefinition(); if (this.CreateRowDefinition != null) { this.CreateRowDefinition(this, row, rowIndex); } this.RowDefinitions.Add(row); } createRowControls(dataitem, rowIndex); rowIndex++; } } } }
分类:
WPF
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
2016-10-13 MySql允许远程访问