Obtener indice control seleccionado (Matriz 2 dimensiones) VB2013

Iniciado por Tazmania40, 17 Enero 2019, 13:12 PM

0 Miembros y 1 Visitante están viendo este tema.

Tazmania40

Buenas estoy realizando un programa y me veo atascado en un evento por código, de momento no encuentro nada pero sigo buscando, a ver si alguno me da la solución antes.

Pongo el siguiente ejemplo y es lo mismo que deseo que funcione con las matrices de 2 dimensiones en Visual Basic 2013.

Código (vbnet) [Seleccionar]

Private Lista(4) As PictureBox

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
       For A = 0 To 4
           Lista(A) = New PictureBox                       ' Creamos una nueva instancia
           Lista(A).Size = New Size(32, 32)                ' Tamaño PictureBox
           Lista(A).BorderStyle = 1                        ' Dibuja borde
           Lista(A).Location = New Point(A * 32 + A, 50)   ' Posicionamos PictureBox
           Me.Controls.Add(Lista(A))                       ' Añadimos al Formulario
           AddHandler Lista(A).Click, AddressOf Evento1    ' Agregamos evento Click
       Next A      
End Sub

Private Sub Evento1(sender As Object, e As EventArgs)
       Dim Index As Integer = Array.IndexOf(Lista, sender) ' Obtenemos el índice seleccionado
       MsgBox("Has pulsado " & Index + 1 & " recuadro.")
End Sub


El código de arriba muestra 5 controles de tipo PictureBox como un array, puesto que en NET no se
podía realizar mediante diseño, al hacer click en cualquiera de ellos obtenemos el índice del
que hemos pulsado y así podemos utilizar las propiedades de cada uno de ellos.

Siguiendo con el ejemplo, me gustaría lo mismo para una matriz de 2 dimensiones que la dibujo
perfectamente, pero cuando asigno el evento Click no se que emplear para obtener el índice
puesto que Array.IndexOf es para matrices unidimensionales ¿Como puedo obtener el índice de
una matriz de 2 dimensiones, (0,0) (0,1) (0,2).. (1,0)..etc, al hacer click sobre cada recuadro
PictureBox ? Muchas GRACIAS y saludos

Código (vbnet) [Seleccionar]

Private Matriz(2, 3) As PictureBox                      ' Matriz de 3x4 elementos
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load      
       
       ' Dibujamos matriz bidimensional de 3 filas y 4 columnas
       For F = 0 To 2                                         ' Recorre las Filas
           For C = 0 To 3                                     ' Recorre las Columnas
               Matriz(F, C) = New PictureBox
               Matriz(F, C).Size = New Size(32, 32)
               Matriz(F, C).BorderStyle = 1
               Matriz(F, C).Location = New Point(C * 32 + C, F * 32 + F + 100)
               Me.Controls.Add(Matriz(F, C))
               AddHandler Matriz(F, C).Click, AddressOf Evento2
           Next C
       Next F
   End Sub

   Private Sub Evento2(sender As Object, e As EventArgs)
       ' Fallo porque Array.IndexOf  es para matrices de 1 sola dimensión
       Dim Index As Integer = Array.IndexOf(Matriz, sender)
       '...
   End Sub


Eleкtro

#1
Cita de: Tazmania40 en 17 Enero 2019, 13:12 PMal hacer click en cualquiera de ellos obtenemos el índice del
que hemos pulsado y así podemos utilizar las propiedades de cada uno de ellos.

No entiendo tu planteamiento. Si el objetivo realmente es obtener un índice con el que poder obtener acceso al control y usar sus propiedades, entonces la obtención del índice carece de sentido puesto que el objeto sender es una referencia de dicho control, pudiendo hacer algo así:

Código (vbnet) [Seleccionar]
Private Sub Evento1(sender As Object, e As EventArgs)
   Dim pcb As PictureBox = DirectCast(sender, PictureBox)
   ' Hacer aquí lo que quieras con este PictureBox...
End Sub


Para todo lo demás, una forma simple de almacenar datos adicionales (que en este caso ayuden a la identificación del control dentro de un array) sería usar la propiedad Control.Tag que hereda la clase PictureBox, ahí puedes especificar el índice y dimensión del Array o lo que tú quieras al momento de crear la instancia de dicho control en los búcles esos que tienes...

Código (vbnet) [Seleccionar]
For A = 0 To 4
    ...
   Lista(A).Tag = A ' Índice.
    ...
Next A


Pero como ya digo esto en principio no es necesario, puesto que en el controlador de eventos "Evento1" ya tienes acceso al control mediante el objeto sender.

Saludos.








Tazmania40

En primer lugar muchas gracias Elektro como siempre. Efectivamente el objeto sender, me fije ayer más tarde podía asignar las propiedades en el momento de hacer clic y que ese PictureBox por ejemplo cambiara de color.

Código (vbnet) [Seleccionar]

Private Sub Evento2(sender As Object, e As EventArgs)       
        sender.BackColor = Color.Blue
        '.......
    End Sub


Aunque lo que requiero realmente es obtener el indice como en array de una dimensión. Si hay alguna forma de pasarle esas 2 coordenadas al evento o que las recoja y ponerlo en una variable matriz de 2 dimensiones
Código (vbnet) [Seleccionar]

Dim Index As Integer = Array.IndexOf(Lista, sender)
' Esto mismo con una variable matriz de mismo tipo que la principal y donde la clase Array pueda
' obtener los 2 indices al igual que hace con 1 solo. Aunque sus métodos solo dicen que es para
' 1 dimensión. Si hay algo parecido a esta clase y obtenerlo fácilmente.


Como bien dices la propiedad Tag podemos guardar esas coordenadas, pero lamentablemente la utilizo para guardar valores. Otra posibilidad es agregar otra propiedad personal al control PictureBox (Como Tag, aunque claro serían otras 2 más Matriz(F, C) guardar F y C) , pero sería muy engorroso y alargaría mucho el código, además de que no se.

No se si en los parámetros del evento click pueden recoger esas 2 coordenadas, tal y como hacemos cuando creamos un procedimiento o función, que también me valdría.

Gracias y saludos

Eleкtro

#3
Cita de: Tazmania40 en 18 Enero 2019, 10:10 AM
Como bien dices la propiedad Tag podemos guardar esas coordenadas, pero lamentablemente la utilizo para guardar valores. Otra posibilidad es agregar otra propiedad personal al control PictureBox (Como Tag, aunque claro serían otras 2 más Matriz(F, C) guardar F y C) , pero sería muy engorroso y alargaría mucho el código, además de que no se.

La propiedad Tag es de tipo Object, lo que permite asignarle cualquier tipo de objeto, y esto quiere decir que puedes diseñar una clase como la siguiente, en la que almacenarías los datos que tu quisieras, y luego asignar una instancia de esa clase a al propiedad Tag...

Código (vbnet) [Seleccionar]
Friend NotInheritable Class TestClass

   Public Property TestProperty1 As Object
   Public Property TestProperty2 As Object

   Public Sub New()
   End Sub

End Class


No hace falta mencionar que en esa clase añadirías la cantidad de propiedades que realmente necesites (donde asignarías los índices del array multidimensional, entre otras cosas que desees), y del tipo que realmente sean, no necesariamente del tipo Object. Y entonces podrías hacer:

Código (vbnet) [Seleccionar]

For A = 0 To 4
   ...
   Dim obj As New TestClass()
   obj.TestProperty1 = A ' Indice.
   obj.TestProperty2 = asignar otra cosa distinta...
   Lista(A).Tag = obj
   ...
Next A

(o lo mismo pero en el búcle del array bidimensional)

...
Código (vbnet) [Seleccionar]
Private Sub Evento1(sender As Object, e As EventArgs)
   Dim pcb As PictureBox = DirectCast(sender, PictureBox)
   Dim obj As TestClass = DirectCast(pcb.Tag, TestClass)
   ...
End Sub


¿Eso no te sirve para las intenciones que tengas?. Esto sería más rápido que iterar los elementos de un array para hallar el control en cuestión.

De todas formas, si realmente quieres encontrar el índice del control en un array multidimensional, en este ejemplo bidimensional, tan solo debes recorrer sus dimensiones, y lo puedes hacer de la siguiente manera:

Código (vbnet) [Seleccionar]
Public Shared Function IndicesOf(Of T)(ByVal [array] As T(,), ByVal value As T) As Integer()

   For i As Integer = [array].GetLowerBound(0) To [array].GetUpperBound(0)
       For j As Integer = [array].GetLowerBound(1) To [array].GetUpperBound(1)
           If [array](i, j)?.Equals(value) Then
               Return {i, j}
           End If
       Next j
   Next i

   Return Nothing

End Function

...
Código (vbnet) [Seleccionar]
Private Sub Evento1(sender As Object, e As EventArgs)
   Dim indices As Integer() = IndicesOf(Matriz, DirectCast(sender, PictureBox))
   Dim pcb As PictureBox = Matriz(indices(0), indices(1))
   ...
End Sub


Saludos.




EDITO:

Casi se me olvida mostrarte la alternativa que mencionaste de añadirle una segunda propiedad "Tag" a la clase PictureBox...

Es tan simple como heredar la clase PictureBox y declarar una propiedad personalizada:

Código (vbnet) [Seleccionar]
Public Class CustomPictureBox : Inherits PictureBox

   Public Property Tag2 As Object

End Class


pero ten en cuenta que esto es un "overkill" puesto que con un propiedad tag es más que suficiente al poderle asignar cualquier tipo de objeto, y este puede ser una clase o estructura que contenga múltiples valores como ya mostré en el ejemplo de arriba. Lo que intento decir es que el pensamiento de "2 siempre es mejor que 1" en realidad resulta algo innecesario por lo que acabo de mencionar, por que la solución óptima no sería crear múltiples propiedades similares a Tag, sino en su lugar crear un tipo pesonalizado donde almacenar múltiples valores, y asignar una instancia de ese tipo a la propiedad Tag.

Y por último, aquí tienes lo mismo pero con una personalización más sofisticada o cuidada para la manipulación de este PictureBox personalizado en el diseñador de forms:

Código (vbnet) [Seleccionar]
<DisplayName(NameOf(CustomPictureBox))>
<Description("A custom PictureBox control.")>
<DesignTimeVisible(True)>
<DesignerCategory(NameOf(UserControl))>
<ToolboxBitmap(GetType(PictureBox), "PictureBox.bmp")>
<ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Require)>
<ClassInterface(ClassInterfaceType.AutoDispatch)>
<ComVisible(True)>
<DefaultBindingProperty(NameOf(CustomPictureBox.Image))>
<DefaultProperty(NameOf(CustomPictureBox.Image))>
<DefaultEvent(NameOf(CustomPictureBox.Click))>
<Docking(DockingBehavior.Ask)>
<PermissionSet(SecurityAction.Demand, Name:="FullTrust")>
Public Class CustomPictureBox : Inherits PictureBox

   <DisplayName(NameOf(CustomPictureBox.Tag2))>
   <Description("Additional user-defined data associated with the object.")>
   <Category("Data")>
   <Browsable(True)>
   <Bindable(True)>
   <DefaultValue(DirectCast(Nothing, Object))>
   <Localizable(False)>
   <TypeConverter(GetType(StringConverter))>
   Public Property Tag2 As Object

End Class









Tazmania40

Muchas gracias Elektro eres un genio, haces que lo difícil parezca fácil y encima expones muchas maneras. Voy analizarlas y te voy escribiendo en este mismo post. No tengo mucho tiempo (tengo 3 niñas y dos son bebes) y me cuesta probar y analizar.

El primero de la clase no lo había pensado de esa manera. He realizado el código y funciona. Lo pongo el pequeño ejemplo

Código (vbnet) [Seleccionar]

Public Class Form1
    Private Matriz(2, 3) As PictureBox                          ' Matriz de 3x4 elementos

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        ' Dibujamos matriz bidimensional de 3 filas y 4 columnas
        For F = 0 To 2                                         ' Recorre las Filas
            For C = 0 To 3                                     ' Recorre las Columnas
                Matriz(F, C) = New PictureBox
                Matriz(F, C).Size = New Size(32, 32)
                Matriz(F, C).BorderStyle = 1
                Matriz(F, C).Location = New Point(C * 32 + C, F * 32 + F + 100)

                Dim obj As New TestClass()
                obj.Valor = 0
                obj.F = F
                obj.C = C
                Matriz(F, C).Tag = obj

                Me.Controls.Add(Matriz(F, C))
                AddHandler Matriz(F, C).Click, AddressOf Evento2
            Next C
        Next F
    End Sub

    Private Sub Evento2(sender As Object, e As EventArgs)
        Dim pcb As PictureBox = DirectCast(sender, PictureBox)
        Dim obj As TestClass = DirectCast(pcb.Tag, TestClass)
        pcb.BackColor = Color.Blue
        MsgBox("Fila y Columna (" & obj.F + 1 & "," & obj.C + 1 & ")")
    End Sub

    ''''' CLASE PARA ASIGNAR LAS PROPIEDADES
    Friend NotInheritable Class TestClass
        Public Property Valor As Integer
        Public Property F As Integer
        Public Property C As Integer

        Public Sub New() ' Constructor vacío
        End Sub
    End Class

End Class


En este código la pega que veo es en Tag yo tengo valores que al principio valen 0 y luego les asigno otros al pulsar en cada recuadro que provienen de la propiedad Tag de un Picture principal.
Aqui lo he llamado obj.Valor, podia solucionarlo a continuación del evento
Código (vbnet) [Seleccionar]

obj.Valor = PicPrincipal.Tag
Matriz(obj.F, obj.C).Tag = obj

Vamos enredándolo un poco porque siempre tienes que pasarle todas las propiedades, pero la solución es válida para los índices de la matriz. Una pregunta para las prácticas de buena programación

Friend NotInheritable : porque empleas que la clase sea Friend y este modificador (es la clase base según he leido). He probado con Public (funciona y sin modificador) que siempre realizo cuando creo mis programas, claro dentro de una clase externa. Me imagino que Friend es para que no se pueda acceder externamente, aunque ahi yo suelo emplear Private, siempre he tenido un lio con estas cosas que son sencillas.

A ver si mañana (aunque ya es, jeje) pruebo la función para mi código y así conservo la propiedad Tag con los valores y sería más fácil.

Gracias y saludos

Eleкtro

#5
Cita de: Tazmania40 en 19 Enero 2019, 00:11 AM
Friend NotInheritable : porque empleas que la clase sea Friend y este modificador (es la clase base según he leido). He probado con Public (funciona y sin modificador) que siempre realizo cuando creo mis programas, claro dentro de una clase externa. Me imagino que Friend es para que no se pueda acceder externamente, aunque ahi yo suelo emplear Private, siempre he tenido un lio con estas cosas que son sencillas.

Realmente no tiene mucha importancia. El modificador de visibilidad de clase Friend hace que la clase solamente sea accesible desde tu proyecto/executable. Simplemente consideré que era la visibilidad de clase más acertada para tu código, puesto que el modificador Public permite acceder a la clase desde cualquier otra clase y no creo que vayas a necesitar hacer eso.

El modificador de clase NotInheritable (sealed en C#) lo que hace es "sellar" la clase para que no se pueda heredar (usando el keyword Inherits como en la clase del CustomPictureBox), es decir, para que no se pueda usar como una clase base.
Este sellado resulta en una pequeña optimización, así que si no tienes necesidad de heredar una clase entonces no hay problema en sellarla usando NotInheritable, pero tampoco pasa nada por no hacerlo...

Aquí te dejo info acerca de las optimizaciones de usar NotInheritable:

Y las guías de diseño que recomienda Microsoft sobre el sellado de clases:

Ojo, esas recomendaciones de Microsoft parecen estar orientadas a escenarios profesionales (ej. el desarrollo de una API pública), ya que sugieren cosas como: "Sellar una clase porque no se puede pensar en un escenario de extensibilidad no es una buena razón" - pero evidentemente no puede existir un escenario de extensibilidad de tu clase "TestClass" puesto que en principio el código de esa clase sellada es solo para que lo uses tú en tu programa, así que aquí pasa a ser más que una buena razón para sellar la clase, y por ello no debes hacerle mucho caso a ciertas recomendaciones en ese sentido.

Saludos.








Tazmania40

Muchas gracias Elektro por todo, ya he probado la segunda opción y es la que me voy a quedar (dejo aqui el código) es justo lo que quería, por un lado con el sender obtengo las propiedades de la matriz del PictureBox donde he realizado click para cambiar y por otro lado el índice con tu función que era la pregunta principal, así conservo los datos de mi propiedad Tag que puedo modificar.

Tienes un dominio absoluto de los métodos de las clases, te envidio (sanamente) por tu facilidad sobre la programación en general, yo como ya comente en otros post programo por hobby para realizar pequeños jueguecitos principalmente con VB.NET y XNA, aunque ahora también aprendí algo de C# (traducciones  a vb.net) y GameMaker (v.1.4).

Gracias por la explicación de Friend, como suelo utilizar clases externas siempre pongo public. Siempre veo que sueles utilizar la forma correcta y de buenas prácticas para programar, aunque muchos no lo entendemos y dando consejos de que no utilizar. Miraré esa información a modo consulta. Sobre el tema de modificar un PictureBox o cualquier otro control, creo que es muy extenso y ese tercer método no utilizaré. Saludetes y que pases un buen finde

Código (vbnet) [Seleccionar]

Public Class Form1
    Private Matriz(2, 3) As PictureBox                          ' Matriz de 3x4 elementos

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        ' Dibujamos matriz bidimensional de 3 filas y 4 columnas
        For F = 0 To 2                                          ' Recorre las Filas
            For C = 0 To 3                                      ' Recorre las Columnas
                Matriz(F, C) = New PictureBox
                Matriz(F, C).Size = New Size(32, 32)
                Matriz(F, C).BorderStyle = 1
                Matriz(F, C).Location = New Point(C * 32 + C, F * 32 + F + 100)
                Matriz(F, C).Tag = 0
                Me.Controls.Add(Matriz(F, C))
                AddHandler Matriz(F, C).Click, AddressOf Evento2
            Next C
        Next F
    End Sub

    Private Sub Evento2(sender As Object, e As EventArgs)
        Dim indices As Integer() = IndicesOf(Matriz, DirectCast(sender, PictureBox))
        Dim pcb As PictureBox = DirectCast(sender, PictureBox)
        pcb.BackColor = Color.Blue
        MsgBox("Valor Fila: " & indices(0) + 1 & " y Columna: " & indices(1) + 1)
    End Sub

    ' Función obtener índice de una Matriz de 2 dimensiones
    Public Shared Function IndicesOf(Of T)(ByVal [array] As T(,), ByVal value As T) As Integer()

        For i As Integer = [array].GetLowerBound(0) To [array].GetUpperBound(0)
            For j As Integer = [array].GetLowerBound(1) To [array].GetUpperBound(1)
                If [array](i, j).Equals(value) Then
                    Return {i, j}
                End If
            Next j
        Next i
        Return Nothing

    End Function
End Class

Eleкtro

No quiero parecer pedante ni condescendiente ni pesado, pero no hay nada que envidiar, son cosas muy sencillitas. La manipulación de arrays (o jagged arrays) uni o multidimensionales es de las primeras cosas que se aprende a hacer si se estudia siguiendo unos pasos correctos de aprendizaje. Y el resto que he podido mostrarte como la herencia de clases o el uso de tipos genéricos pues son conceptos fundamentales de la programación orientada a objetos.

No te he mostrado nada que no pudieras llegar a hacer por ti mismo con el debido aprendizaje y la posterior práctica, pero como eres un tio de familia pues se entiende que no tengas tiempo o ganas suficiente.

En fin. Un placer ayudar a personas así (de agradecidas).

Saludos.