none
Necesito ayuda con una propiedad tipo lista en un UserControl RRS feed

  • Pregunta

  • ¿Cómo están compañeros? Saludos.

    Les cuento que he tenido muchos dolores de cabeza al intentar crear una propiedad tipo lista, colección o array en un UserControl; llevo días averigüando pero sigo teniendo problemas, así que me paso por acá en busca de su valiosa ayuda.

    Antes de nada, les explico qué es lo que estoy haciendo. Estoy creando un UserControl llamado MyButtonsPanel que contendrá dentro de sí otros UserControls ya creados por mí (MyButton), que son sencillamente unos botones personalizados. Ahora bien, la idea no es cargar los MyButtons dentro de MyButtonsPanel arrastrándolos desde la ToolBox, sino a través de una propiedad tipo lista en MyButtonsPanel llamada Items. Para que lo entiendan mejor, lo que quiero es que la inclusión de los MyButtons se haga exactamente igual a como se incluyen columnas o items en un ListView, o items en un ListBox, ComboBox, etc. Como saben, en esos casos solo se puede hacer a través de una propiedad, generalmente llamada Items. Además, en esos casos los items sólo pueden modificarse a través del editor de colecciones, y eso también lo quiero copiar.

    Pues bien, en mi intento por lograrlo he tenido éxito en algunos aspectos, pero también me he encontrado con varios obstáculos. Entre ellos surgió algo relacionado con la serialización, cosa que me hizo leer algunas ayudas por ahí. Hasta donde supe, solo fue necesario declarar la clase como serializable, pues no volví a tener el mensaje de error correspondiente. Sin embargo, sí he seguido teniendo otros errores similares, pero esta vez relacionados con un tal Pointer; de verdad no he logrado saber qué es, pero creo que tiene que ver con lo mismo de la serialización. Parece que esto es un tanto complejo. Si pueden echarme una mano al respecto se los agradeceré.

    Otra cosa es un problema al declarar mi propiedad Items como Collection. Siguiendo algunas ayudas, tuve que crear mi propia colección que heredara de Collections.ObjectModel.Collection (Of ObjectType). Después de hacerlo todo parecía funcionar, salvo por otros errores. Por ejemplo, aunque sobrecargo el evento RemoveItem, la compilación no pasa por ese bloque de código al remover un item de la colección; no tengo idea de por qué. Y sé que no pasa por ese bloque porque usé un BreakPoint en él y nunca se detuvo allí.

    Otro error relacionado también con mi propia colección, es que la declaración de los nuevos controles de la colección en el Designer se escribe mal. Es decir, cuando inserto un MyButtonsPanel en un formulario X, y le cargo los items, estos se declaran de un modo incorrecto, por lo que luego se marcan como Syntaxis Error. Supongo que tengo algo mal en la colección, pero no sé qué es. (Aquí un ejemplo de este problema: http://social.msdn.microsoft.com/Forums/es-ES/vbes/thread/c492144f-ce05-4307-b6de-3700ca0a26a2)

    Así pues, lo que necesito saber es lo siguiente:

    1) Cómo cubrir todas las exigencias de la serialización en la clase para evitar cualquier error relacionado.

    2) Cómo hacer que se ejecute el bloque RemoveItem de la colección, o por qué no se ejecuta al remover un item.

    3) Cómo hacer que los elementos de la colección queden correctamente declarados en el control o formulario que contenga un MyButtonsPanel entre sus controles.

    4) Cómo hacer que los MyButtons cargados dentro de MyButtonsPanel no puedan modificarse de ninguna manera, sino a través de la propiedad Items.

    De verdad les agradezco su ayuda. Ya llevo días intentado lograr esto, pues necesito implementar las propiedades tipo colección en varios controles que tengo pensado diseñar.

     


    Something new to learn everyday.


    sábado, 19 de marzo de 2011 23:42

Respuestas

  • La propiedad Items debe ser una colección de MyButton y como no podrás hacer la modificacion de esta colección en forma directa tendrás que implementar la interfaz de usuario para modificarla según el siguiente link: 

    http://msdn.microsoft.com/en-us/library/ms171840.aspx

    En todo caso si queres subir el código que tenes a skydrive podría ayudarte un poco mas,

    Saludos.

     

    viernes, 15 de julio de 2011 12:11
  • Después de un largo tiempo de prueba y error, al fin logré satisfacer mis necesidades, y sin tener que modificar el editor de colecciones. Voy a plasmar las soluciones para los que consulten el post en busca de respuestas.
     
    Antes de nada, les comento que decidí crear una clase representante ─por decirlo así─ para componer los items de la colección, en vez de hacer una colección de MyButton. A dicha clase le asigné algunas de las propiedades de MyButton, solo las que necesito especificar al cargar la colección, como Name, Text, entre otras. Luego, en un método del propio MyButtonsPanel, recorro cada item de la colección, y creo un nuevo MyButton con los valores del item representante. De este modo no tuve que estar preocupándome por la serialización, que por cierto, nunca pude dominar.
     
    En el código que pondré a continuación, no usaré MyButtons ni MyButtonsPanel, sino un UserControl llamado ButtonPanel que contendrá Buttons normales. Igual el código se puede adaptar a cualquier necesidad. Solo habrá que cambiar los tipos y las clases. Agregué varios comentarios aclaratorios para mejorar la comprensión del mismo.
     
    Primeramente, creé estas dos clases necesarias para la propiedad Items del ButtonsPanel:
     
     
    'Clase que compondrá la colección (items).
    Public Class ButtonPanelItem
    
        Private pIndex As Integer
        <System.ComponentModel.Browsable(False)> _
        ReadOnly Property Index() As Integer
            Get
                Return pIndex
            End Get
        End Property
    
        Private pName As String = MyClass.GetType.Name
        <System.ComponentModel.Category("Design")> _
        Property Name() As String
            Get
                Return pName
            End Get
            Set(ByVal value As String)
                pName = value
            End Set
        End Property
    
        Private pText As String = MyClass.GetType.Name
        <System.ComponentModel.Category("Appearance")> _
        Property Text() As String
            Get
                Return pText
            End Get
            Set(ByVal value As String)
                pText = value
            End Set
        End Property
    
        Friend Sub SetIndex(ByVal index As Integer)
            pIndex = index
        End Sub
    
    End Class
    
    'Clase colección
    Public Class ButtonPanelItemCollection
        Inherits System.Collections.ObjectModel.Collection(Of ButtonPanelItem)
    
        'Variable tipo lista para almacenar los nombres enumerados de los items.
        Private itemsNames As New List(Of String)
        'Evento que se desencadena al cambiar la colección.
        Public Event Changed As EventHandler
    
        'Sobrescribo este método para asignar el índice al nuevo item y enumerar su nombre.
        'Luego envío el evento Changed.
        Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As ButtonPanelItem)
            IndexName(item)
            item.SetIndex(index)
            MyBase.InsertItem(index, item)
            RaiseEventChanged()
        End Sub
    
        'Sobrescribo este método para enviar el evento Changed.
        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            MyBase.RemoveItem(index)
            RaiseEventChanged()
        End Sub
    
        'Método que agrega el índice del nuevo item al final de su nombre.
        Private Sub IndexName(ByRef item As ButtonPanelItem)
            If item.Name = item.GetType.Name Then
                Dim nIndex As Integer = 1
                If itemsNames.IndexOf(item.Name & nIndex) > -1 Then
                    Do Until itemsNames.IndexOf(item.Name & nIndex) = -1
                        nIndex += 1
                    Loop
                End If
                item.Name = item.GetType.Name & nIndex
                item.Text = item.GetType.Name & nIndex
                itemsNames.Add(item.Name)
            End If
        End Sub
    
        'Método que envía el evento Changed y ejecuta tareas finales.
        Private Sub RaiseEventChanged()
            RaiseEvent Changed(Me, New System.EventArgs)
            itemsNames.Clear()
            For Each item As ButtonPanelItem In Me.Items
                itemsNames.Add(item.Name)
            Next
        End Sub
    
    End Class
     
     
    Y por último está la clase ButtonPanel, que es el UserControl con la propiedad Items, tipo colección:
     
     
    Imports System.ComponentModel
    
    Public Class ButtonPanel
        Inherits System.Windows.Forms.UserControl
    
        Public Sub New()
            MyBase.New()
            Me.BackColor = Color.FromArgb(10, 0, 0, 0)
            Me.Dock = DockStyle.Left
        End Sub
    
        'Propiedad tipo colección, y variable correspondiente.
        'Es de suma importancia indicar el DesignerSerializationVisibility
        'Atribute, pues de lo contrario, los elementos de la colección no
        'persistirán en el diseñador, por lo cual desaparecerán cada vez
        'que el formulario se refresque.
        Private WithEvents pItems As New ButtonPanelItemCollection
        <Category("Data")> _
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
        ReadOnly Property Items() As ButtonPanelItemCollection
            Get
                Return pItems
            End Get
        End Property
    
        'Variable donde almaceno los botones representados en la colección.
        'Esto para efectos de manipulación sobre los botones. Recuerde que
        'la colección de Items es de ButtonPanelItems, no de Buttons. Así,
        'esta variable contendrá la verdadera colección de Buttons.
        Private buttonList As New List(Of Button)
    
        'Metodo que se ejecuta al cambiar la colección.
        Private Sub ItemCollectionChanged() Handles pItems.Changed
    
            'Limpio la colección de Buttons.
            Me.Controls.Clear()
            buttonList.Clear()
    
            'Creo y administro los nuevos Buttons que se agregarán al control
            'partiendo de las propiedades definidas en los items representantes
            '(ButtonPanelItems). Otras propiedades se asignan directamente al
            'momento de incluirlos (Width, Top).
            Dim bTop As Integer = 0
            For i As Integer = 0 To Me.Items.Count - 1
                Dim item As ButtonPanelItem = Me.Items.Item(i)
                Dim but As New Button
                Me.Controls.Add(but)
                buttonList.Add(but)
                With but
                    .Name = item.Name
                    .Text = item.Text
                    .Width = Me.Width - 1
                    .Top = bTop - 1
                    bTop += .Height
                End With
            Next
        End Sub
    
    End Class
     
     
    Y listo. Con esto obtengo precisamente lo que planteaba al inicio, exceptuando lo de la serialización, que no fue necesario:
     
    1) No aplica.
     
    2) El bloque Remove de la colección no se ejecuta al usar el editor de colecciones en tiempo-diseño, pero sí al compilar la aplicación. Igual no es problema, pues aunque los Buttons no se quitan de inmediato al remover los items en el editor de colecciones, sí se remueven al oprimir el botón OK de dicho editor.
     
    3) El problema de declaración en la clase Designer del formulario se solucionó al indicar y establecer el atributo DesignerSerializationVisibility a DesignerSerializationVisibility.Content.
     
    4) Al agregar los Buttons desde el código usando la colección personalizada, estos por defecto quedan inaccesibles desde fuera del ButtonPanel. Solo pueden modificarse a través de la propiedad Items, o cualquier otra que haga alusión a la colección privada buttonList.
     
    Espero de verdad que el tema sirva para otros que como yo, hayan estado buscando cómo hacer esto. Adjunto además un enlace para que bajen el proyecto en el que estuve trabajando. En él agregué otras cosas como la inclusión de un evento en ButtonPanel que da salida al evento Click de los Buttons, la implementación del mismo en un formulario contenedor, y propiedades que cambian, a su vez, algunas propiedades de todos los Buttons a través de buttonList. Recomiendo que prueben agregar items en tiempo de diseño, así como en tiempo de ejecución.
     
     
    Cualquier duda avisen. ;)

    Aquel que pregunta es un tonto por cinco minutos, pero el que no pregunta permanece tonto por siempre
    Proverbio chino
    martes, 13 de septiembre de 2011 17:38

Todas las respuestas

  • La propiedad Items debe ser una colección de MyButton y como no podrás hacer la modificacion de esta colección en forma directa tendrás que implementar la interfaz de usuario para modificarla según el siguiente link: 

    http://msdn.microsoft.com/en-us/library/ms171840.aspx

    En todo caso si queres subir el código que tenes a skydrive podría ayudarte un poco mas,

    Saludos.

     

    viernes, 15 de julio de 2011 12:11
  • Después de un largo tiempo de prueba y error, al fin logré satisfacer mis necesidades, y sin tener que modificar el editor de colecciones. Voy a plasmar las soluciones para los que consulten el post en busca de respuestas.
     
    Antes de nada, les comento que decidí crear una clase representante ─por decirlo así─ para componer los items de la colección, en vez de hacer una colección de MyButton. A dicha clase le asigné algunas de las propiedades de MyButton, solo las que necesito especificar al cargar la colección, como Name, Text, entre otras. Luego, en un método del propio MyButtonsPanel, recorro cada item de la colección, y creo un nuevo MyButton con los valores del item representante. De este modo no tuve que estar preocupándome por la serialización, que por cierto, nunca pude dominar.
     
    En el código que pondré a continuación, no usaré MyButtons ni MyButtonsPanel, sino un UserControl llamado ButtonPanel que contendrá Buttons normales. Igual el código se puede adaptar a cualquier necesidad. Solo habrá que cambiar los tipos y las clases. Agregué varios comentarios aclaratorios para mejorar la comprensión del mismo.
     
    Primeramente, creé estas dos clases necesarias para la propiedad Items del ButtonsPanel:
     
     
    'Clase que compondrá la colección (items).
    Public Class ButtonPanelItem
    
        Private pIndex As Integer
        <System.ComponentModel.Browsable(False)> _
        ReadOnly Property Index() As Integer
            Get
                Return pIndex
            End Get
        End Property
    
        Private pName As String = MyClass.GetType.Name
        <System.ComponentModel.Category("Design")> _
        Property Name() As String
            Get
                Return pName
            End Get
            Set(ByVal value As String)
                pName = value
            End Set
        End Property
    
        Private pText As String = MyClass.GetType.Name
        <System.ComponentModel.Category("Appearance")> _
        Property Text() As String
            Get
                Return pText
            End Get
            Set(ByVal value As String)
                pText = value
            End Set
        End Property
    
        Friend Sub SetIndex(ByVal index As Integer)
            pIndex = index
        End Sub
    
    End Class
    
    'Clase colección
    Public Class ButtonPanelItemCollection
        Inherits System.Collections.ObjectModel.Collection(Of ButtonPanelItem)
    
        'Variable tipo lista para almacenar los nombres enumerados de los items.
        Private itemsNames As New List(Of String)
        'Evento que se desencadena al cambiar la colección.
        Public Event Changed As EventHandler
    
        'Sobrescribo este método para asignar el índice al nuevo item y enumerar su nombre.
        'Luego envío el evento Changed.
        Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As ButtonPanelItem)
            IndexName(item)
            item.SetIndex(index)
            MyBase.InsertItem(index, item)
            RaiseEventChanged()
        End Sub
    
        'Sobrescribo este método para enviar el evento Changed.
        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            MyBase.RemoveItem(index)
            RaiseEventChanged()
        End Sub
    
        'Método que agrega el índice del nuevo item al final de su nombre.
        Private Sub IndexName(ByRef item As ButtonPanelItem)
            If item.Name = item.GetType.Name Then
                Dim nIndex As Integer = 1
                If itemsNames.IndexOf(item.Name & nIndex) > -1 Then
                    Do Until itemsNames.IndexOf(item.Name & nIndex) = -1
                        nIndex += 1
                    Loop
                End If
                item.Name = item.GetType.Name & nIndex
                item.Text = item.GetType.Name & nIndex
                itemsNames.Add(item.Name)
            End If
        End Sub
    
        'Método que envía el evento Changed y ejecuta tareas finales.
        Private Sub RaiseEventChanged()
            RaiseEvent Changed(Me, New System.EventArgs)
            itemsNames.Clear()
            For Each item As ButtonPanelItem In Me.Items
                itemsNames.Add(item.Name)
            Next
        End Sub
    
    End Class
     
     
    Y por último está la clase ButtonPanel, que es el UserControl con la propiedad Items, tipo colección:
     
     
    Imports System.ComponentModel
    
    Public Class ButtonPanel
        Inherits System.Windows.Forms.UserControl
    
        Public Sub New()
            MyBase.New()
            Me.BackColor = Color.FromArgb(10, 0, 0, 0)
            Me.Dock = DockStyle.Left
        End Sub
    
        'Propiedad tipo colección, y variable correspondiente.
        'Es de suma importancia indicar el DesignerSerializationVisibility
        'Atribute, pues de lo contrario, los elementos de la colección no
        'persistirán en el diseñador, por lo cual desaparecerán cada vez
        'que el formulario se refresque.
        Private WithEvents pItems As New ButtonPanelItemCollection
        <Category("Data")> _
        <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
        ReadOnly Property Items() As ButtonPanelItemCollection
            Get
                Return pItems
            End Get
        End Property
    
        'Variable donde almaceno los botones representados en la colección.
        'Esto para efectos de manipulación sobre los botones. Recuerde que
        'la colección de Items es de ButtonPanelItems, no de Buttons. Así,
        'esta variable contendrá la verdadera colección de Buttons.
        Private buttonList As New List(Of Button)
    
        'Metodo que se ejecuta al cambiar la colección.
        Private Sub ItemCollectionChanged() Handles pItems.Changed
    
            'Limpio la colección de Buttons.
            Me.Controls.Clear()
            buttonList.Clear()
    
            'Creo y administro los nuevos Buttons que se agregarán al control
            'partiendo de las propiedades definidas en los items representantes
            '(ButtonPanelItems). Otras propiedades se asignan directamente al
            'momento de incluirlos (Width, Top).
            Dim bTop As Integer = 0
            For i As Integer = 0 To Me.Items.Count - 1
                Dim item As ButtonPanelItem = Me.Items.Item(i)
                Dim but As New Button
                Me.Controls.Add(but)
                buttonList.Add(but)
                With but
                    .Name = item.Name
                    .Text = item.Text
                    .Width = Me.Width - 1
                    .Top = bTop - 1
                    bTop += .Height
                End With
            Next
        End Sub
    
    End Class
     
     
    Y listo. Con esto obtengo precisamente lo que planteaba al inicio, exceptuando lo de la serialización, que no fue necesario:
     
    1) No aplica.
     
    2) El bloque Remove de la colección no se ejecuta al usar el editor de colecciones en tiempo-diseño, pero sí al compilar la aplicación. Igual no es problema, pues aunque los Buttons no se quitan de inmediato al remover los items en el editor de colecciones, sí se remueven al oprimir el botón OK de dicho editor.
     
    3) El problema de declaración en la clase Designer del formulario se solucionó al indicar y establecer el atributo DesignerSerializationVisibility a DesignerSerializationVisibility.Content.
     
    4) Al agregar los Buttons desde el código usando la colección personalizada, estos por defecto quedan inaccesibles desde fuera del ButtonPanel. Solo pueden modificarse a través de la propiedad Items, o cualquier otra que haga alusión a la colección privada buttonList.
     
    Espero de verdad que el tema sirva para otros que como yo, hayan estado buscando cómo hacer esto. Adjunto además un enlace para que bajen el proyecto en el que estuve trabajando. En él agregué otras cosas como la inclusión de un evento en ButtonPanel que da salida al evento Click de los Buttons, la implementación del mismo en un formulario contenedor, y propiedades que cambian, a su vez, algunas propiedades de todos los Buttons a través de buttonList. Recomiendo que prueben agregar items en tiempo de diseño, así como en tiempo de ejecución.
     
     
    Cualquier duda avisen. ;)

    Aquel que pregunta es un tonto por cinco minutos, pero el que no pregunta permanece tonto por siempre
    Proverbio chino
    martes, 13 de septiembre de 2011 17:38
  • Hola Jason,

    aunque veo que ya marcastes la pregunta como contestada, te comentare.

    no es necesario declara una colleccion indicando el tipo de objeto de la collecion, realmente el problema puede ser mala definicion de la collection.

    para agregar un control usando y este pueda ser seleccionado en tiempo de diseño luego de ser agregado por el editor de la coleccion lo mejor es sobre escribir el editor de collection "CollectionEditor" para sobre escribir la funcion CreateInstance() y usar la interfaz IDesignerHost para crear el nuevo control y luego agregarlo a la lista, tambien podras sobre escribir el metodo DestroyInstance() que se dispara el remover un control con el editor de la coleccion.

    y asi sucesivamente la mayoria de problemas es por la falta de conocimiento del tema...

    yo ya estoy por terminar un control que he creado, lo cree porque vi la necesidad de crearlo para usarlo en una aplicación que estoy desarrollando, en este caso publicare el codigo e tocare temas sobre la creación de controles personalizados que no son faciles de encontrar.

    en si el control esta en C# pero vere si me da tiempo de pasarlo a VB, aún no lo publico, lo estare publicando el fin de semana y te aseguro que te ayudara a entender mejor el diseñador del VS.

    Salu2,

    ah!!!!... revisare el tuyo para hacer las correcciones necesarias.


    Marvin E. Pineda

      ComboBoxMultiColumns

      NetBarControl

      TextEditor



    martes, 13 de septiembre de 2011 19:59
    Moderador
  • Que tal Marvin! Entiendo...

    Pues me parece perfecto poder aprender todos los detalles sobre el tema, pues de verdad ha sido un tanto complejo trabajar con esta propiedad tipo colección.

    Por cierto, no te he respondido más en el otro post referente a los grupos y los items; es porque descarté la implementación de ese sistema para manejar mis colecciones, en vista de lo complicado que es. Pero en cuanto lo necesite y lo revise, te comento. Igual quería mencionar, en base a una pregunta que me hiciste, que la forma en que los elementos de la colección persisten en la clase Designer es por el uso del atributo DesignerSerializationVisibility, como ya mencioné. Una vez indicado sobre la propiedad colección sus elementos quedan declarados.

    Saludos. Estaré esperando tu control.


    Aquel que pregunta es un tonto por cinco minutos, pero el que no pregunta permanece tonto por siempre
    Proverbio chino
    miércoles, 14 de septiembre de 2011 11:16
  • bueno, en uno de esos post hay un link a un projecto que hize para resolver tu problema, si descargas el projecto veras que hay implemento la coleccion para los controles ese te puede servir de ejemplo para que veas como funcionan las coleccción.

    con respecto a usar el atributo DesignerSerializationVisibility pues te comentare un dato curioso que pude ver en el projecto que te pase, el dato curioso que encontre para no usar CodeDom fue la propiedad "Name" en la clase, pude fijarme que al implementar esta propiedad el serializador del formulario persistio como variables las clases y yo no estaba colocando la propiedad "Name" poreso no persistia, me llamo la atención este comportamiento y lo probe varias veces sin la propiedad y con la propiedad y en efecto asi fue, al quitar la propiedad "Name" de la clase no persistia y al colocar la propiedad "Name" si la persistio. un punto interesante que pude detectar....

    pero bien, en el projecto en el anterior post usa las colecciones puedes revisar y comparar.

    Salu2,


    Marvin E. Pineda

      ComboBoxMultiColumns

      NetBarControl

      TextEditor




    miércoles, 14 de septiembre de 2011 16:37
    Moderador