0

0

WPF数据绑定(2 绑定列表数据Binding to List Data)

php中文网

php中文网

发布时间:2016-06-07 15:48:52

|

1651人浏览过

|

来源于php中文网

原创

(读完此系列WPF和Silverlight的数据绑定问题你就轻松搞定 ) 1 Binding to List Data 前面都是绑定到一个对象,下面我们学习绑定到对象列表的方法。 我们还是先组织要绑定的数据,对象所对应的类还是Person,但新增了一个新类People,该类用来组织Person的列

(读完此系列wpf和silverlight的数据绑定问题你就轻松搞定WPF数据绑定(2 绑定列表数据Binding to List Data)

1 Binding to List Data

前面都是绑定到一个对象,下面我们学习绑定到对象列表的方法。

我们还是先组织要绑定的数据,对象所对应的类还是Person,但新增了一个新类People,该类用来组织Person的列表.代码如下:

<span>using </span>System;
            <span>using </span>System.Collections.Generic;
            <span>using </span>System.ComponentModel;<span>//INotifyPropertyChanged
            </span><span>namespace </span>SimpleDataBinding
            {
            <span>class </span><span>Person </span>: <span>INotifyPropertyChanged
            </span>{
            <span>public event </span><span>PropertyChangedEventHandler </span>PropertyChanged;
            <span>protected void </span>Notify(<span>string </span>PropName)
            {
            <span>if </span>(<span>this</span>.PropertyChanged != <span>null</span>)
            {
            PropertyChanged(<span>this</span>, <span>new </span><span>PropertyChangedEventArgs</span>(PropName));
            }
            }
            <span>public </span>Person()
            {
            _Age = 0;
            _name = <span>"Null"</span>;
            <span>this</span>.CurrentDate = <span>DateTime</span>.Now;
            }
            <span>private string </span>_name;
            <span>public string </span>Name
            {
            <span>get </span>{ <span>return </span>_name; }
            <span>set
            </span>{
            <span>if </span>(<span>value </span>== _name)
            { <span>return</span>; }
            _name = <span>value</span>;<span>//注意:不能用this.Name来赋值,如果这样形成循环调用,栈溢出
            </span>Notify(<span>"Name"</span>);
            }
            }
            <span>private int </span>_Age;
            <span>public int </span>Age
            {
            <span>get </span>{ <span>return </span>_Age; }
            <span>set
            </span>{
            <span>if </span>(<span>value </span>== _Age) <span>return</span>;
            _Age = <span>value</span>;
            Notify(<span>"Age"</span>);
            }
            }
            <span>public </span><span>DateTime </span>CurrentDate { <span>get</span>; <span>set</span>; }
            }
            <strong><span><span>//People类
            </span><span>class </span><span>People </span>: <span>List</span><<span>Person</span>>
            {
            }</span></strong>
            }
            

注意在同一命名空间下的代码最后添加了Perople类。

我们在UI里显示的XAML如下:

<span><</span><span>Window </span><span>x</span><span>:</span><span>Class</span><span>="ListDataBinding.BindListDataTest"
            </span><span>xmlns</span><span>="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            </span><span>xmlns</span><span>:</span><span>x</span><span>="http://schemas.microsoft.com/winfx/2006/xaml"
            <u> </u></span><strong><u><span><span>xmlns</span><span>:</span><span>src</span></span></u></strong><span><strong><u><span>="clr-namespace:ListDataBinding"</span></u></strong>
            </span><span>Title</span><span>="BindListDataTest" </span><span>Height</span><span>="113" </span><span>Width</span><span>="300">
            <</span><span>Window.Resources</span><span>>
            <span> <strong><</strong></span></span><span><strong><span>src</span><span>:</span><span>People </span><span>x</span><span>:</span><span>Key</span></strong></span><span><span><strong>="Family"></strong></span>
            <</span><span>src</span><span>:</span><span>Person </span><span>Name</span><span>="Jack" </span><span>Age</span><span>="18"/>
            <</span><span>src</span><span>:</span><span>Person </span><span>Name</span><span>="Tom" </span><span>Age</span><span>="30"/>
            <</span><span>src</span><span>:</span><span>Person </span><span>Name</span><span>="Jone" </span><span>Age</span><span>="14"/>
            <</span><span>src</span><span>:</span><span>Person </span><span>Name</span><span>="Rose" </span><span>Age</span><span>="17"/>
            <</span><span>src</span><span>:</span><span>Person </span><span>Name</span><span>="Mike" </span><span>Age</span><span>="13"/>
            </</span><span>src</span><span>:</span><span>People</span><span>>
            </</span><span>Window.Resources</span><span>>
            <</span><span>Grid </span><span>DataContext</span><span>="{</span><span>StaticResource </span><span>Family</span><span>}">
            <</span><span>Grid.RowDefinitions</span><span>>
            <</span><span>RowDefinition</span><span>/>
            <</span><span>RowDefinition</span><span>/>
            </</span><span>Grid.RowDefinitions</span><span>>
            <</span><span>Grid.ColumnDefinitions</span><span>>
            <</span><span>ColumnDefinition </span><span>Width</span><span>="80"/>
            <</span><span>ColumnDefinition </span><span>Width</span><span>="*"/>
            </</span><span>Grid.ColumnDefinitions</span><span>>
            <</span><span>TextBlock </span><span>Grid.Row</span><span>="0" </span><span>Grid.Column</span><span>="0" </span><span>Text</span><span>="Name" </span><span>TextAlignment</span><span>="Center" </span><span>VerticalAlignment</span><span>="Center"/>
            <</span><span>TextBlock </span><span>Grid.Row</span><span>="1" </span><span>Grid.Column</span><span>="0" </span><span>Text</span><span>="Age" </span><span>TextAlignment</span><span>="Center" </span><span>VerticalAlignment</span><span>="Center"/>
            <</span><span>TextBox </span><span>Grid.Row</span><span>="0" </span><span>Grid.Column</span><span>="1" </span><span>Name</span><span>="txtName" </span><strong><span><span>Text</span><span>="{</span><span>Binding </span><span>Path</span></span></strong><span><strong><span>=Name}"</span></strong> />
            <</span><span>TextBox </span><span>Grid.Row</span><span>="1" </span><span>Grid.Column</span><span>="1" </span><span>Name</span><span>="txtAge" </span><span><strong><span>Text</span><span>="{</span><span>Binding </span><span>Path</span></strong></span><span><span><strong>=Age}"/</strong></span>>
            </</span><span>Grid</span><span>>
            </</span><span>Window</span><span>>
            </span>

我们发现这样的UI只能显示第一个数据项目,也就是说列表的当前项为0,至于其他的就无法显示出来了。

如果要显示其他的只有可通过如下代码的方式来取(注意:书中代码似乎有问题):

<span>private void </span>btnNext_Click(<span>object </span>sender, <span>RoutedEventArgs </span>e)
            {
            <span>People </span>people = (<span>People</span>)<span>this</span>.FindResource(<span>"Family"</span>);
            txtName.Text = people[1].Name;
            txtAge.Text = people[1].Age.ToString();
            }

 

1.1当前项Current Item

取得当前项

可以通过上面的方法取得当前项,当然我们更专业的做法还是使用Collection View

还是代码说明比较简洁:

<span>People </span>people = (<span>People</span>)<span>this</span>.FindResource(<span>"Family"</span>);
            <span>ICollectionView </span>view = <span>CollectionViewSource</span>.GetDefaultView(people);
            <span>Person </span>peron = (<span>Person</span>)view.CurrentItem;

注意:ICollectionView在System.ComponentModel命名空间里。

导航当前项

还是代码来说明更合适点:

<span>      private </span><span>ICollectionView </span>GetView()
            {
            <span>People </span>people = (<span>People</span>)<span>this</span>.FindResource(<span>"Family"</span>);
            <span>ICollectionView </span>view = <span>CollectionViewSource</span>.GetDefaultView(people);
            <span>return </span>view;
            }
            <span>private void </span>btnNext_Click(<span>object </span>sender, <span>RoutedEventArgs </span>e)
            {
            <span>ICollectionView </span>view = GetView();
            view.MoveCurrentToNext();
            <span>if </span>(view.IsCurrentAfterLast)
            {
            view.MoveCurrentToLast();
            }
            }
            <span>private void </span>btnPrior_Click(<span>object </span>sender, <span>RoutedEventArgs </span>e)
            {
            <span>ICollectionView </span>view = GetView();
            view.MoveCurrentToPrevious();
            <span>if </span>(view.IsCurrentBeforeFirst)
            {
            view.MoveCurrentToFirst();
            }
            }
1.2 List Data Targets

我们将列表数据绑定到类似TextBox这样的控件难以很好地展现列表数据。我们考虑ListBox控件来列举多个数据信息。

这时的效果如下:列表确实显示了所有对象的信息,因为我们没有设置Path属性,所以采用默认的Convertation来处理,显示对象类型。同时一定要注意使用IsSynchronizatizedWithCurrentItem=True,这样才能列表信息与其他信息同步。但究竟如何才能更好地表达我们需要的信息呢,请参看下一节:

WPF数据绑定(2 绑定列表数据Binding to List Data)

1.3 Display Members, Value Members, and Look-Up Bindings

代码示例也许更易理解:

<span><</span><span>ListBox </span><span>Grid.Row</span><span>="3" </span><span>Grid.Column</span><span>="1" </span><span>Name</span><span>="lstbox" </span><span>ItemsSource</span><span>="{</span><span>Binding</span><span>}"
            </span><span>DisplayMemberPath</span><span>="Name" </span><span>SelectedValuePath</span><span>="Age" </span><span>IsSynchronizedWithCurrentItem</span><span>="True"/>
            <</span><span>Button </span><span>Grid.Row</span><span>="4" </span><span>Grid.Column</span><span>="0" </span><span>Name</span><span>="btnShowValue" </span><span>Content</span><span>="ShowValue" </span><span>Click</span><span>="btnShowValue_Click" /></span>
<span>private void </span>btnShowValue_Click(<span>object </span>sender, <span>RoutedEventArgs </span>e)
            {
            <span>MessageBox</span>.Show(lstbox.SelectedValue.ToString());
            }
1.4数据模板Data Templates

这是利用ListBox控件有一个ItemTemplate属性下面,他可以接受一个DataTemplate类实例,

该模板可以重复绑定到ListBox的每一个项目元素,注意DataTemplate只能指定一个孩子节点,所以一般使用容器控件来组织下面的布局。

<span><</span><span>ListBox </span><span>Grid.Row</span><span>="3" </span><span>Grid.Column</span><span>="1" </span><span>Name</span><span>="lstbox" </span><span>ItemsSource</span><span>="{</span><span>Binding</span><span>}">
            <</span><span>ListBox.ItemTemplate</span><span>>
            <</span><span>DataTemplate</span><span>>
            <</span><span>TextBlock </span><span>Text</span><span>="{</span><span>Binding </span><span>Path</span><span>=Name}">
            </span><span>的年龄是</span><span><</span><span>TextBlock </span><span>Text</span><span>="{</span><span>Binding </span><span>Path</span><span>=Age}"></</span><span>TextBlock</span><span>>
            </</span><span>TextBlock</span><span>>
            </</span><span>DataTemplate</span><span>>
            </</span><span>ListBox.ItemTemplate</span><span>>
            </</span><span>ListBox</span><span>></span>
<span>我本人不赞同书中这样的做法,添加一个StackPanel更舒服点。</span>

 

1.5 列表改变List Changes

当我们改变列表的数据的时候,却出现如下现象:

WPF数据绑定(2 绑定列表数据Binding to List Data)

只是因为我们需要绑定的列表需要实现INotifyCollectionChanged接口:

Tago AI
Tago AI

AI生成带货视频,专为电商卖货而生

下载
<span>namespace </span>System.Collections.Specialized
            {
            <span>public interface </span><span>INotifyCollectionChanged
            </span>{
            <span>event </span><span>NotifyCollectionChangedEventHandler </span>CollectionChanged;
            }
            }

 

<span>namespace </span>System.Collections.ObjectModel
            {
            <span>public class </span><span>ObservableCollection</span><T> : <span>Collection</span><T>, INotifyCollectionChanged, <span>INotifyPropertyChanged
            </span>{
            ...
            }
            }

欢呼雀跃吧,我们改变上面例题的代码,一切如我们想象的美好。

所有的一切就如此简单,简单代码改动:

<span>//People类
            </span><span>class </span><span>People </span>: <span><strong><span>ObservableCollection</span></strong></span><<span>Person</span>>
            {
            }

WPF数据绑定(2 绑定列表数据Binding to List Data)

1.6 排序Sorting

简单的代码还是足以繁杂的文字,让我们看如下方法:

<span>       private void </span>btnSort_Click(<span>object </span>sender, <span>RoutedEventArgs </span>e)
            {
            <span>ICollectionView </span>view = GetView();
            <span>if </span>(view.SortDescriptions.Count == 0)
            {
            view.SortDescriptions.Add(<span>new </span><span>SortDescription</span>(<span>"Name"</span>, <span>ListSortDirection</span>.Ascending));
            view.SortDescriptions.Add(<span>new </span><span>SortDescription</span>(<span>"Age"</span>, <span>ListSortDirection</span>.Descending));
            }
            <span>else
            </span>{
            view.SortDescriptions.Clear();
            }
            }

当然我们还可以自定义排序方式:

<span>    class </span><span>PersonSorter</span>:<span>IComparer
            </span>{
            <span>public int </span>Compare(<span>object </span>x, <span>object </span>y)
            {
            <span>Person </span>lhs = (<span>Person</span>)x;
            <span>Person </span>rhs = (<span>Person</span>)y;
            <span>// Sort Name ascending and Age descending
            </span><span>int </span>nameCompare = lhs.Name.CompareTo(rhs.Name);
            <span>if </span>(nameCompare != 0) <span>return </span>nameCompare;
            <span>return </span>rhs.Age - lhs.Age;
            }
            }

注意:WPF不使用System.Collection.Generic命名空间的泛型IComparer接口,而是使用System.Collection的。呵呵。

使用代码如下:

<span>       private void </span>btnSort_Click(<span>object </span>sender, <span>RoutedEventArgs </span>e)
            {
            <span>ListCollectionView </span>view = (<span>ListCollectionView</span>)GetView();
            <span>if </span>(view.CustomSort == <span>null</span>)
            {
            view.CustomSort = <span>new </span><span>PersonSorter</span>();
            }
            <span>else
            </span>{
            view.CustomSort = <span>null</span>;
            }
            }

注意:ListCollectionView支持自定义和排序,其他的不支持。

1.7 集合缺省视图类型Default Collection View

WPF数据绑定(2 绑定列表数据Binding to List Data)

1.8 过滤 Filter

依然是我熟悉的表达方式:代码:

<span>       private void </span>btnFilter_Click(<span>object </span>sender, <span>RoutedEventArgs </span>e)
            {
            <span>ListCollectionView </span>view = (<span>ListCollectionView</span>)GetView();
            <span>if </span>(view.Filter == <span>null</span>)
            {
            view.Filter = <span>delegate</span>(<span>object </span>item)
            {
            <span>return </span>((<span>Person</span>)item).Age > 17;
            };
            }
            <span>else
            </span>{
            view.Filter = <span>null</span>;
            }
            }
1.9 分组Grouping

分组的意思大家很明白就是按照某一个或几个关键属性进行分类。

进行分组很简单和sort类似,只需要以下几行代码:

<span>          ICollectionView </span>view = GetView();
            <span>if </span>(view.GroupDescriptions.Count == 0)
            {
            view.GroupDescriptions.Add(<span>new </span><span>PropertyGroupDescription</span>(<span>"Age"</span>));
            }
            <span>else
            </span>{
            view.GroupDescriptions.Clear();
            }

但这在UI层面并没有任何影响,这需要我们对ItemsControl类的控件(例如ListBox)设置GroupStyle属性,GroupStyle类缺省地提供了一个静态的属性实现,我们可以如下设置:

<span>      <</span><span>ListBox </span><span>Grid.Row</span><span>="3" </span><span>Grid.Column</span><span>="1" </span><span>Name</span><span>="lstbox" </span><span>ItemsSource</span><span>="{</span><span>Binding</span><span>}"  </span><span>IsSynchronizedWithCurrentItem</span><span>="True">
            <strong><span><</span></strong></span><span><strong><span>ListBox.GroupStyle</span></strong></span><strong><span><span>>
            <</span><span>x</span><span>:</span><span>Static </span><span>Member</span></span><span><span>="GroupStyle.Default"/>
            </</span><span>ListBox.GroupStyle</span></span></strong><span><strong><span>></span></strong>
            <</span><span>ListBox.ItemTemplate</span><span>>
            <</span><span>DataTemplate</span><span>>
            <</span><span>TextBlock </span><span>Text</span><span>="{</span><span>Binding </span><span>Path</span><span>=Name}">
            </span><span>的年龄是</span><span><</span><span>TextBlock </span><span>Text</span><span>="{</span><span>Binding </span><span>Path</span><span>=Age}"></</span><span>TextBlock</span><span>>
            </</span><span>TextBlock</span><span>>
            </</span><span>DataTemplate</span><span>>
            </</span><span>ListBox.ItemTemplate</span><span>>
            </</span><span>ListBox</span><span>></span>

但也许这并不是我们所喜欢的界面,简单得让人生厌,还好微软提供了这个对象的一个属性:HeaderTemplate用于定义分组的栏目的外观,微软总是为大家想得那么周到,养活那么多天才是需要钱的,希望大家不要老是讲微软的坏话。

<span>           <</span><span>ListBox.GroupStyle</span><span>>
            <</span><span>GroupStyle</span><span>>
            <</span><span>GroupStyle.HeaderTemplate</span><span>>
            <</span><span>DataTemplate</span><span>>
            <</span><span>StackPanel </span><span>Background</span><span>="Green"  </span><span>Orientation</span><span>="Horizontal">
            <</span><span>TextBlock </span><span>Text</span><span>="{</span><span>Binding </span><span>Name</span><span>}"/>
            <</span><span>TextBlock </span><span>Text</span><span>="("/>
            <</span><span>TextBlock </span><span>Text</span><span>="{</span><span>Binding </span><span>ItemCount</span><span>}"/>
            <</span><span>TextBlock </span><span>Text</span><span>=")"/>
            </</span><span>StackPanel</span><span>>
            </</span><span>DataTemplate</span><span>>
            </</span><span>GroupStyle.HeaderTemplate</span><span>>
            </</span><span>GroupStyle</span><span>>
            </</span><span>ListBox.GroupStyle</span><span>></span>

有这模板属性一切由你发挥,真是好也,然而即使这样解决了UI问题,但是如果我们还希望更进一步,能否实现范围内分组呢?呵呵,然也:

这时我们不需要去想着如何继承GroupStyle类,而是采用围魏救赵的方式,定义一个IValueConverter,

<span>   public class </span><span>AgeRangeConvert </span>: <span>IValueConverter
            </span>{
            <span>public object </span>Convert(<span>object </span>value, <span>Type </span>targetType, <span>object </span>parameter, <span>CultureInfo </span>culture)
            {
            <span>int </span>_value = (<span>int</span>)value;
            <span>if </span>(_value <= 10)
            <span>return </span><span>"10岁以下"</span>;
            <span>else if </span>(_value <= 20)
            <span>return </span><span>"20岁以下"</span>;
            <span>else
            return </span><span>"20岁以上"</span>;
            }
            <span>public object </span>ConvertBack(<span>object </span>value, <span>Type </span>targetType, <span>object </span>parameter, <span>CultureInfo </span>culture)
            {
            <span>throw new </span><span>NotImplementedException</span>();
            }
            }

简单调整前面分组代码:

<span>           ICollectionView </span>view = GetView();
            <span>if </span>(view.GroupDescriptions.Count == 0)
            {
            view.GroupDescriptions.Add(<span>new </span><span>PropertyGroupDescription</span>(<span>"Age"</span>,<span>new  </span><span>AgeRangeConvert</span>()));
            }
            <span>else
            </span>{
            view.GroupDescriptions.Clear();
            }

一切搞定,享受成果吧:

WPF数据绑定(2 绑定列表数据Binding to List Data)

既然GroupDescripions是个集合类型,我们不妨看下面代码究竟是什么效果:

<span>            ICollectionView </span>view = GetView();
            <span>if </span>(view.GroupDescriptions.Count == 0)
            {
            view.GroupDescriptions.Add(<span>new </span><span>PropertyGroupDescription</span>(<span>"Age"</span>,<span>new  </span><span>AgeRangeConvert</span>()));
            view.GroupDescriptions.Add(<span>new </span><span>PropertyGroupDescription</span>(<span>"Age"</span>));
            }
            <span>else
            </span>{
            view.GroupDescriptions.Clear();
            }

运行如下:

WPF数据绑定(2 绑定列表数据Binding to List Data)

呵呵,这不正是有时你需要的效果吗?至于界面如何优化,模板如何定义更好看我们以后话题再

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 并发编程模型与工程实践:从语言特性到系统性能
Golang 并发编程模型与工程实践:从语言特性到系统性能

本专题系统讲解 Golang 并发编程模型,从语言级特性出发,深入理解 goroutine、channel 与调度机制。结合工程实践,分析并发设计模式、性能瓶颈与资源控制策略,帮助将并发能力有效转化为稳定、可扩展的系统性能优势。

2

2026.02.27

Golang 高级特性与最佳实践:提升代码艺术
Golang 高级特性与最佳实践:提升代码艺术

本专题深入剖析 Golang 的高级特性与工程级最佳实践,涵盖并发模型、内存管理、接口设计与错误处理策略。通过真实场景与代码对比,引导从“可运行”走向“高质量”,帮助构建高性能、可扩展、易维护的优雅 Go 代码体系。

1

2026.02.27

Golang 测试与调试专题:确保代码可靠性
Golang 测试与调试专题:确保代码可靠性

本专题聚焦 Golang 的测试与调试体系,系统讲解单元测试、表驱动测试、基准测试与覆盖率分析方法,并深入剖析调试工具与常见问题定位思路。通过实践示例,引导建立可验证、可回归的工程习惯,从而持续提升代码可靠性与可维护性。

0

2026.02.27

漫蛙app官网链接入口
漫蛙app官网链接入口

漫蛙App官网提供多条稳定入口,包括 https://manwa.me、https

56

2026.02.27

deepseek在线提问
deepseek在线提问

本合集汇总了DeepSeek在线提问技巧与免登录使用入口,助你快速上手AI对话、写作、分析等功能。阅读专题下面的文章了解更多详细内容。

4

2026.02.27

AO3官网直接进入
AO3官网直接进入

AO3官网最新入口合集,汇总2026年可用官方及镜像链接,助你快速稳定访问Archive of Our Own平台。阅读专题下面的文章了解更多详细内容。

53

2026.02.27

php框架基础教程
php框架基础教程

本合集涵盖2026年最新PHP框架入门知识与基础教程,适合初学者快速掌握主流框架核心概念与使用方法。阅读专题下面的文章了解更多详细内容。

1

2026.02.27

php框架怎么用
php框架怎么用

本合集专为零基础学习者打造,系统介绍主流PHP框架的安装、配置与基础用法,助你快速入门Web开发。阅读专题下面的文章了解更多详细内容。

4

2026.02.27

无禁词AI聊天软件下载大全
无禁词AI聊天软件下载大全

本合集精选多款免费、无违禁词限制的AI聊天软件,支持自定义角色、剧情畅聊,体验真实互动感。阅读专题下面的文章了解更多详细内容。

19

2026.02.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 6.2万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 9.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号