• 灵活的组合框和编辑控件


    介绍 类似的库 Xceed组合和网格体验 这个库的不同之处 ,xceed dxperience这个库直接分配到任何控制组合是的没有直接分配到任何控制列是的选择前以内联方式存储编辑支持网格xceed网格dxperience网格datagridview可以选择控制,不存在形式不是的免费不轻便简单的库不是的是的是的不有许多其他有用的组件 为什么要创建这个应用程序? 我需要扩展DataGridViewColumn,而不是每次都重写它。我需要一个灵活的组合框,接受任何控制作为下拉。 这个应用程序做什么?它为什么有用? 它可以用一种简单的方式构建自己的网格列或自己的组合。在运行和设计时,使用所包含的控件转换来克隆、复制和动态创建控件。使用所包含的控件编辑器可更改在设计时创建的控件类型。在开发新控件时,控制转换器和控制编辑器都可以作为类型转换器和编辑器属性。 解决了什么问题? 我们的组件有一个控件属性,它在设计时创建不同类型的控件,并将创建的控件存储为简单的文本值。可以在设计时根据需要指定下拉控件或编辑控件的属性名,例如: 控制价值属性名numericupdown文本checkedlistbox checkeditems timeedit价值 值属性名称可以是list或collection,值将被转换为包含集合中所有项的字符串。 使用的代码 在Windows应用程序中创建新表单。添加对AnyControlComboColumn的引用。添加新的数据网格视图,并添加新列并将其类型设置为GridColumn。在属性窗口中,转到EditingControl(Create New)属性并从下拉列表中选择控件类型。从工具箱,添加到表单下拉组合。在属性窗口中,进入下拉按钮属性并展开它。在DropDownButton下,转到DropDownControl(Create New)属性,从下拉列表中选择控件类型。 演示应用程序 演示应用程序包含DropDownCombo,我们可以选择下拉控制:CheckedListBox或目录或者文件列表,它包含一个DataGridView两列,第一个与NumericUpDown作为编辑控制和第二列是面具文本框作为一个编辑控件。 关于这个库 这个库包含以下类: DropDownButton:这个类提供了一个下拉按钮,它接受任何控件作为下拉控件,并接受任何其他控件作为显示区域。DropDownCombo是一个包含上述下拉按钮的文本框,因此它为文本框提供了可在运行时或设计时设置或更改的下拉控件。ControlConverter和controlleditor提供了将任何控件序列化为要存储在表单设计器中的简单文本的技术。EditingControl、GridCell和GridColumn提供了在DataGridView中使用任何控件作为编辑控件的能力。 关于本文中的c#代码 这个库是用VB开发的。这里的c#代码是由代码转换器获得的。有关代码转换器的详细信息,请参阅: vb6c# VB代码转换器 关于DropDownButton 下拉按钮是创建下拉组合的主要类。当它被添加到任何控件时,它将停靠在右侧。它可以通过两个控件来分配:第一个是下拉控件,第二个是显示区域。控件中的显示区域将显示创建的组合的值,可以是文本框或富文本框或任何控件,下拉控件是单击下拉按钮时将被下拉的控件。要设置下拉控件,我们可以选择窗体中的控件,或者从包含system.windows中所有控件类型的列表中进行选择。形成应用程序包含对其引用的程序集和程序集。当单击下拉按钮,下拉值将被改变以满足显示区域的价值下降时弹出关闭,显示区域值将被改变,以满足价值下降控制dropdowncombo是一个文本框,其中包含下拉按钮。 关于GridColumn GridColumn是用于扩展DataGridView以接受任何控件作为编辑控件的类。要使用这个类,我们需要使用DataGridView并从DataGridView设计器窗口向它添加GridColum。要设置编辑控件,我们可以在窗体中选择一个控件,或者在包含system.windows中所有控件类型的窗体列表中选择窗体。形成应用程序包含对其引用的程序集和程序集。 关于控制转换器和控制属性序列化 在构建下拉控件的控件属性之前,我们需要将其序列化为字符串以存储在表单设计器中,但不要使用XmlSerializer来序列化控件属性,因为: XmlSerializer不支持的类型: 任何继承不受支持的类的任何类都有返回不受支持的类的属性 XmlSerializer也可以序列化下列类型,但它不能反序列化: 对象列表框。objectcollection checkedlistbox.objectcollection XmlSerializer返回多行值,最好将控件属性存储为简单的文本来测试XmlSerializer,我们可以使用以下代码: 隐藏,收缩,复制CodeImports System.Xml.Serialization 进口System.Reflection 进口System.ComponentModel 进口AnyControlComboColumn 进口System.Windows.Forms 公共模块Module1 公共函数序列化(ByVal值为对象)为字符串 试一试 如果价值一文不值的话 返回" 退出函数 如果 Dim序列化器作为新的XmlSerializer(Value.GetType) 暗的vMemoryStream作为新的IO.MemoryStream 序列化器。序列化(vMemoryStream值) getstring (vMemoryStream.ToArray) s = s.Replace(“& lt; ?xml version = " 1.0 " ? ", _ “& lt; ?xml version = " 1.0 " encoding = " utf - 8 " ?祝辞”) (system . text . coding. utf8 . getbytes (s)) s = System.Text.Encoding.UTF8.GetString (vMemoryStream.ToArray) “尝试反序列化 试一试 Dim f = serialize . deserialize (vMemoryStream) 捕获ex作为例外 返回" 最后试一试 返回年代 捕获ex作为例外 返回" 最后试一试 结束函数 终端模块 隐藏,收缩,复制Codeusing系统; 使用System.Collections; 使用System.Collections.Generic; 使用System.Diagnostics; 使用System.Xml.Serialization; 使用System.Reflection; 使用System.ComponentModel; 使用AnyControlComboColumn; 使用System.Windows.Forms; 名称空间AnyControlComboColumn { 公共静态类MSerialize { 公共静态字符串序列化(对象值){ 尝试{ if (Value == null) { 返回"; 返回null; } = new XmlSerializer(Value.GetType()); 先。MemoryStream vMemoryStream = new System.IO.MemoryStream(); 序列化器。序列化(vMemoryStream、价值); getstring (vMemoryStream.ToArray()); s = s.Replace(“& lt; ?xml version = \“1.0 \”?在“,”& lt; ?xml version = = \ \ 1.0 \”编码“utf - 8 \”?在“); vMemoryStream = new System.IO.MemoryStream(system . text . encode . utf8 . getbytes (s)); s = System.Text.Encoding.UTF8.GetString (vMemoryStream.ToArray ()); / /在反序列化 尝试{ var f = Serializer.Deserialize(vMemoryStream); 捕获(异常ex) { 返回"; } 返回年代; 捕获(异常ex) { 返回"; } } } } VB。NETC #隐藏,收缩,复制CodeImports System.Xml.Serialization 进口System.Reflection 进口System.ComponentModel 进口AnyControlComboColumn 进口System.Windows.Forms 公共模块Module1 公共函数序列化(ByVal值为对象)为字符串 试一试 如果价值一文不值的话 返回" 退出函数 如果 Dim序列化器作为新的XmlSerializer(Value.GetType) 暗的vMemoryStream作为新的IO.MemoryStream 序列化器。序列化(vMemoryStream值) getstring (vMemoryStream.ToArray) s = s.Replace(“& lt; ?xml version = " 1.0 " ? ", _ “& lt; ?xml version = " 1.0 " encoding = " utf - 8 " ?祝辞”) (system . text . coding. utf8 . getbytes (s)) s = System.Text.Encoding.UTF8.GetString (vMemoryStream.ToArray) “尝试反序列化 试一试 Dim f = serialize . deserialize (vMemoryStream) 捕获ex作为例外 返回" 最后试一试 返回年代 捕获ex作为例外 返回" 最后试一试 结束函数 ModuleHide结束,收缩,复制Codeusing系统; 使用年代ystem.Collections; 使用System.Collections.Generic; 使用System.Diagnostics; 使用System.Xml.Serialization; 使用System.Reflection; 使用System.ComponentModel; 使用AnyControlComboColumn; 使用System.Windows.Forms; 名称空间AnyControlComboColumn { 公共静态类MSerialize { 公共静态字符串序列化(对象值){ 尝试{ if (Value == null) { 返回"; 返回null; } = new XmlSerializer(Value.GetType()); 先。MemoryStream vMemoryStream = new System.IO.MemoryStream(); 序列化器。序列化(vMemoryStream、价值); getstring (vMemoryStream.ToArray()); s = s.Replace(“& lt; ?xml version = \“1.0 \”?在“,”& lt; ?xml version = = \ \ 1.0 \”编码“utf - 8 \”?在“); vMemoryStream = new System.IO.MemoryStream(system . text . encode . utf8 . getbytes (s)); s = System.Text.Encoding.UTF8.GetString (vMemoryStream.ToArray ()); / /在反序列化 尝试{ var f = Serializer.Deserialize(vMemoryStream); 捕获(异常ex) { 返回"; } 返回年代; 捕获(异常ex) { 返回"; } } } } 我们不使用默认的ComponentConverter,因为: 它只允许从表单中预先创建的控件中进行选择。它不允许选择窗体中不存在的控件。它只能在设计时使用,不能在运行时使用。 什么ComponentConverter: 从所选类型创建控件的实例。将控件转换为要存储为控件文本的字符串。将控件文本转换为控件对象。 的兴趣点 要将下拉控件分配给下拉按钮,我们使用以下属性: 隐藏,收缩,复制代码'不添加TypeConverter属性,因此此属性使用默认的ComponentConverter DisplayName("下拉控件(在表单中创建)")_ 公共属性DropDownControl()作为控件 & lt; TypeConverter(方法(ControlConverter)), _ 编辑器(方法(ControlEditor)、方法(UITypeEditor)), _ DisplayName(“DropDownControl(创建新)”)的在_ 作为控件的公共属性NewDropDownControl() 描述(“用于创建新下拉控件的文本”)>_ 公共可重写属性控件文本为字符串 隐藏,收缩,复制代码//不要把DesignerSerializationVisibility( //这只适用于只读属性 //如果属性类型继承了form componenet或控件,则ControlConverter不能自动工作 //所以我们需要一个ControlText属性 编辑[TypeConverter typeof (ControlConverter)) (typeof (ControlEditor) typeof (UITypeEditor)), DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden), 描述(“创建OpenDropDown时显示的新控件”), DefaultValue (typeof(控制),“无”),类别(“下拉”), DisplayName(“DropDownControl(创建新)”),EditorBrowsable (EditorBrowsableState.Advanced)] NewDropDownControl { / /…… } [DefaultValue(" "),可浏览的(错误的), 描述(“用于创建新下拉控件的文本”)] 公共虚拟字符串控件文本{ / /…… } //不要添加TypeConverter属性,因此此属性使用默认的ComponentConverter [说明(" OpenDropDown时显示的控件"), DefaultValue (typeof(控制),“无”),类别(“下拉”), 显示名称("下拉控件(选择窗体控件)")] 公共控件下拉控件{ / /…… } VB。NETC #隐藏,复制代码'不添加TypeConverter属性,因此此属性使用默认的ComponentConverter DisplayName("下拉控件(在表单中创建)")_ 公共属性DropDownControl()作为控件 & lt; TypeConverter(方法(ControlConverter)), _ 编辑器(方法(ControlEditor)、方法(UITypeEditor)), _ DisplayName(“DropDownControl(创建新)”)的在_ 作为控件的公共属性NewDropDownControl() 描述(“用于创建新下拉控件的文本”)>_ 公共可重写属性控件为StringHide收缩,复制代码//不要把DesignerSerializationVisibility( //这只适用于只读属性 //如果属性类型继承了form componenet或控件,则ControlConverter不能自动工作 //所以我们需要一个ControlText属性 编辑[TypeConverter typeof (ControlConverter)) (typeof (ControlEditor) typeof (UITypeEditor)), DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden), 描述(“创建OpenDropDown时显示的新控件”), DefaultValue (typeof(控制),“无”),类别(“下拉”), DisplayName(“DropDownControl(创建新)”),EditorBrowsable (EditorBrowsableState.Advanced)] NewDropDownControl { / /…… } [DefaultValue(" "),可浏览的(错误的), 描述(“用于创建新下拉控件的文本”)] 公共虚拟字符串控件文本{ / /…… } //不要添加TypeConverter属性,因此此属性使用默认的ComponentConverter [说明(" OpenDropDown时显示的控件"), DefaultValue (typeof(控制),“无”),类别(“下拉”), 显示名称("下拉控件(选择窗体控件)")] 公共控件下拉控件{ / /…… } DropDownControl属性命名为房地产windows DropDownControl(形式)中创建的使用已经创建的默认ComponentConverter选择的控制控制存在的父窗体newdropdowncontrol属性命名为房地产windows DropDownControl(创建新)使用我们控制变频器和控制编辑器来从列表中选择一个控制包含所有控件支持的应用程序或者我们可以类型等直接控制的名字:文本框,按钮等等。ControlText属性是一个简单的文本属性,它接受并返回包含控件类型名称后跟程序集名称和控件none默认属性值的文本。 控制转换器使用以下代码来隐藏文本控制: 隐藏,收缩,复制代码公共覆盖函数ConvertFrom(ByVal上下文作为ITypeDescriptorContext, _ ByVal文化作为系统。全球化。CultureInfo, ByVal值作为对象)作为对象 试一试 如果值是空的,则为_ OrElse字符串。IsNullOrEmpty (CType(值,字符串) 返回什么 退出函数 如果 Dim vPropertyValues作为新字典(字符串,字符串) Dim TypeParts = Split(CType(value, String), "@") 如果UBound (TypeParts) & lt;比;2那 ReDim保存TypeParts (2) 如果 如果String.IsNullOrEmpty (TypeParts (1)) TypeParts (1) = WinFormsAssemblyName 如果 vPropertyValues。添加(c_TypeName TypeParts (0)) vPropertyValues。添加(c_AssemblyName TypeParts (1)) Dim aPropertyValues() As String = Split(TypeParts(2), ",") For i As Integer = 0 To UBound(aPropertyValues) If (Not String.IsNullOrEmpty(aPropertyValues(i))) AndAlso _ aPropertyValues (i) .Contains(”:“c) Dim KeyAndValue = Split(aPropertyValues(i), ":") vPropertyValues.Add (KeyAndValue(0)。修剪,KeyAndValue (1) .Trim) 如果 下一个 回报我。调用CreateInstance(没什么,vPropertyValues) 捕获ex作为例外 ErrMsg(特异) 返回什么 最后试一试 结束函数 隐藏,收缩,复制Codepublic覆盖对象ConvertFrom(ITypeDescriptorContext上下文, System.Globalization。文化,对象值){ 尝试{ if (value == null || (!)(value is string)) { 返回null; 返回null; } Dictionary<字符串,比;vPropertyValues = new Dictionary<string,>(); TypeParts = Microsoft.VisualBasic.Strings.Split(Convert.ToString(value)), “@”,1、Microsoft.VisualBasic.CompareMethod.Binary); 如果(TypeParts.GetUpperBound(0) != 2) 数组中。调整(ref TypeParts 3); 如果(string.IsNullOrEmpty (TypeParts [1])) TypeParts [1] = WinFormsAssemblyName; vPropertyValues。添加(c_TypeName TypeParts [0]); vPropertyValues。添加(c_AssemblyName TypeParts [1]); aPropertyValues = Microsoft.VisualBasic.Strings.Split(TypeParts[2], ”、“1 Microsoft.VisualBasic.CompareMethod.Binary); for (int i = 0;我& lt; = aPropertyValues.GetUpperBound (0);我+ +){ 如果((!(string.IsNullOrEmpty (aPropertyValues[我]))),,aPropertyValues[我].Contains(“:”)){ KeyAndValue = Microsoft.VisualBasic.Strings。Split (aPropertyValues[我],“:”, 1、Microsoft.VisualBasic.CompareMethod.Binary); vPropertyValues.Add (KeyAndValue [0] .Trim (), KeyAndValue [1] .Trim ()); } } 返回调用CreateInstance (null, vPropertyValues); 捕获(异常ex) { _ErrMsg.ErrMsg (ex); 返回null; } } VB。NETC #隐藏,收缩,复制代码公共覆盖函数ConvertFrom(ByVal上下文作为ITypeDescriptorContext, _ ByVal文化作为系统。全球化。CultureInfo, ByVal值作为对象)作为对象 试一试 如果值是空的,则为_ OrElse字符串。IsNullOrEmpty (CType(值,字符串) 返回什么 退出函数 如果 Dim vPropertyValues作为新字典(字符串,字符串) Dim TypeParts = Split(CType(value, String), "@") 如果UBound (TypeParts) & lt;比;2那 ReDim保存TypeParts (2) 如果 如果String.IsNullOrEmpty (TypeParts (1)) TypeParts (1) = WinFormsAssemblyName 如果 vPropertyValues。添加(c_TypeName TypeParts (0)) vPropertyValues。添加(c_AssemblyName TypeParts (1)) Dim aPropertyValues() As String = Split(TypeParts(2), ",") For i As Integer = 0 To UBound(aPropertyValues) If (Not String.IsNullOrEmpty(aPropertyValues(i))) AndAlso _ aPropertyValues (i) .Contains(”:“c) Dim KeyAndValue = Split(aPropertyValues(i), ":") vPropertyValues.Add (KeyAndValue(0)。修剪,KeyAndValue (1) .Trim) 如果 下一个 回报我。调用CreateInstance(没什么,vPropertyValues) 捕获ex作为例外 ErrMsg(特异) 返回什么 最后试一试 FunctionHide结束,收缩,复制Codepublic覆盖对象ConvertFrom(ITypeDescriptorContext上下文, System.Globalization。文化,对象值){ 尝试{ if (value == null || (!)(value is string)) { 返回null; 返回null; } Dictionary<字符串,比;vPropertyValues = new Dictionary<string,>(); TypeParts = Microsoft.VisualBasic.Strings.Split(Convert.ToString(value)), “@”,1、Microsoft.VisualBasic.CompareMethod.Binary); 如果(TypeParts.GetUpperBound(0) != 2) 数组中。调整(ref TypeParts 3); 如果(string.IsNullOrEmpty (TypeParts [1])) TypeParts [1] = WinFormsAssemblyName; vPropertyValues。添加(c_TypeName TypeParts [0]); vPropertyValues。添加(c_AssemblyName TypeParts [1]); aPropertyValues = Microsoft.VisualBasic.Strings.Split(TypeParts[2], ”、“1 Microsoft.VisualBasic.CompareMethod.Binary); for (int i = 0;我& lt; = aPropertyValues.GetUpperBound (0);我+ +){ 如果((!(string.IsNullOrEmpty (aPropertyValues[我]))),,aPropertyValues[我].Contains(“:”)){ KeyAndValue = Microsoft.VisualBasic.Strings。Split (aPropertyValues[我],“:”, 1、Microsoft.VisualBasic.CompareMethod.Binary); vPropertyValues.Add (KeyAndValue [0] .Trim (), KeyAndValue [1] .Trim ()); } } 返回调用CreateInstance (null, vPropertyValues); 捕获(异常ex) { _ErrMsg.ErrMsg (ex); 返回null; } } 文本格式为TypeName@AssmblyName@ControlPropertyValues。首先,转换器获取类型名称和程序集名称属性值。它创建了一个属性值字典,其中包含一对属性名称及其值,除了程序集和类型名称外,还使用属性分配类型转换器将其转换为字符串。使用CreateInstance方法创建控件的实例。要从对象类型中创建对象的实例,我们使用以下代码: 隐藏,收缩,复制代码公共重载共享函数CreateInstance(ByVal vType作为类型)作为控件 试一试 '####### Dim c作为新的vType是不正确的 如果vType是什么 返回什么 退出函数 如果 Dim ConstructorInfo = vType.GetConstructor({}) 如果ConstructorInfo什么都不是 返回什么 退出函数 如果 返回CType (ConstructorInfo.Invoke({}),控制) 捕获ex作为例外 ErrMsg(特异) 返回什么 最后试一试 结束函数 隐藏,收缩,复制Codepublic静态控件CreateInstance(类型vType) { 尝试{ / /在Dim c作为新的vType是不正确的 if (vType == null) { 返回null; 返回null; } var ConstructorInfo = vType。GetConstructor(新System.Type [0]); if (ConstructorInfo == null) { 返回null; 返回null; } (控制)(ConstructorInfo返回。调用(新System.Type [0])); 捕获(异常ex) { _ErrMsg.ErrMsg (ex); 返回null; } } VB。NETC #隐藏,复制代码公共重载共享函数CreateInstance(ByVal vType作为类型)作为控件 试一试 '####### Dim c作为新的vType是不正确的 如果vType是什么 返回什么 退出函数 如果 Dim ConstructorInfo = vType.GetConstructor({}) 如果ConstructorInfo什么都不是 返回什么 退出函数 如果 返回CType (ConstructorInfo.Invoke({}),控制) 捕获ex作为例外 ErrMsg(特异) 返回什么 最后试一试 FunctionHide结束,收缩,复制Codepublic静态控件CreateInstance(类型vType) { 尝试{ / /在Dim c作为新的vType是不正确的 if (vType == null) { 返回null; 返回null; } var ConstructorInfo = vType。GetConstructor(新System.Type [0]); if (ConstructorInfo == null) { 返回null; 返回null; } (控制)(ConstructorInfo返回。调用(新System.Type [0])); 捕获(异常ex) { _ErrMsg.ErrMsg (ex); 返回null; } } 要从类型名称中获取类型对象,我们使用以下代码: 隐藏,收缩,复制CodeDim vAssembly = system . reflect . assembly . load (AssemblyName) Dim vType = vAssembly.GetType(TypeName) 隐藏,收缩,复制Codevar vAssembly = system . reflect . assembly . load (AssemblyName); var vType = vAssembly.GetType(TypeName); VB。NETC #隐藏,复制CodeDim vAssembly = system . reflect . assembly . load (AssemblyName) gettype = vAssembly.GetType(TypeName)收缩,复制Codevar vAssembly = system . reflect . assembly . load (AssemblyName); var vType = vAssembly.GetType(TypeName); 为了查看下拉控件,我使用ToolStripDropDown和ToolStripControlHost: 隐藏,收缩,复制Code

    Private WithEvents Popup As ToolStripDropDown
    Private Host As ToolStripControlHost
    
    Public Property DropDownControl() As Control
        Get
            Return _DropDownControl
        End Get
        Set(ByVal value As Control)
            Try
               '....some code for testing and preparing value the cloning it to
                NewControl = ControlConverter.Clone(value)
                Popup.Items.Clear()
                Host = Nothing
                _DropDownControl = NewControl
                _DropDownControl.Margin = Padding.Empty
                _DropDownControl.Padding = Padding.Empty
                Host = New ToolStripControlHost(_DropDownControl)
                Host.Margin = Padding.Empty
                Host.Padding = Padding.Empty
                Host.AutoSize = False
                Host.Size = value.Size
                Popup.Items.Add(Host)
                Popup.Size = Host.Size
                Popup.AutoSize = True
                _DropDownControl.Location = New Point(0, 0)
                If _DisplayArea IsNot Nothing Then
                    DisplayAreaValue = DropDownControlValue
                End If
                OnClosed(Nothing, Nothing)
            Catch ex As Exception
                ErrMsg(ex)
            End Try
        End Set
    End Property
    
    Protected Overrides Sub OnClick(ByVal e As EventArgs)
        Try
            MyBase.OnClick(e)
            If Me.DroppedDown Then
                Me.CloseDropDown()
            Else
                Me.OpenDropDown() 'Using Popup.Show method
            End If
        Catch ex As Exception
            ErrMsg(ex)
        End Try
    End Sub
    
    Public Overridable Sub OpenDropDown()
        Try
            If _DropDownControl Is Nothing OrElse Host Is Nothing OrElse _
            _DisplayArea Is Nothing Then Exit Sub
            Dim frm = _DisplayArea.FindForm
            Dim Pos As Point
            'additional code to adjust the position according to Drop Down Direction
            Pos = New Point(_DisplayArea.Left, _DisplayArea.Bottom + 1)
            Popup.Show(frm, Pos, ToolStripDropDownDirection.BelowRight)
    
        Catch ex As Exception
            ErrMsg(ex)
        End Try
    End Sub

    历史 初始版本 本文转载于:http://www.diyabc.com/frontweb/news246.html

  • 相关阅读:
    LeetCode: Reverse Linked List
    DataBase: MySQL在.NET中的应用
    DataBase: LeetCode
    DirectShow+VS2010+Win7配置说明
    MathType应用:批量改变公式格式
    $LaTeX$笔记:首字下沉
    Latex学习笔记-序
    反思--技术博客的写作应该是怎样的?
    用Latex写学术论文:作者(Author)&摘要(Abstract)
    用Latex写学术论文: IEEE Latex模板和文档设置(documentclass)
  • 原文地址:https://www.cnblogs.com/Dincat/p/13431593.html
Copyright © 2020-2023  润新知