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.
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
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
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í:
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...
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.
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.
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
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
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...
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:
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)
...
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:
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
...
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:
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:
<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
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
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
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
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:
- http://codebetter.com/patricksmacchia/2008/01/05/rambling-on-the-sealed-keyword/
- https://stackoverflow.com/a/911781/1248295
- https://stackoverflow.com/a/2044453/1248295
Y las guías de diseño que recomienda
Microsoft sobre el sellado de clases:
- https://docs.microsoft.com/es-es/dotnet/standard/design-guidelines/sealing
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.
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
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
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.