hola, me gustaría saber si se puede introducir ítems en un Listbox de modo que se vean verticales
del modo normal sería
12345678
45664555
78999999
Pero yo quiero hacerlo así
1 4 7
2 5 8
3 6 9
4 6 9
5 4 9
6 5 9
7 5 9
8 5 9
Si hago esto por ejemplo...
Dim Num() As String = {"1", "2", "3", "4"}
Dim strLinea As String = Nothing
strLinea = String.Join(Environment.NewLine, Num).Trim
ListBox1.Items.Add(strLinea)
Label1.Text= strLinea
el Label1 muestra correctamente:
1
2
3
4
Mientras que un Listbox o un ListView lo mostraría así
1234
El ListBox aunque lo mostrara como el label pondría uno debajo del otro. Con un Listview podría ponerlo en cada columna, pero también lo pone horizontal.
Lo he hecho con un FlowLayoutPanel1 Panel, y añado Labels de forma automática pero no es lo que busco. Además que me ocupa mucho código y es tedioso de hacer.
¿Alguna idea?
Gracias
Acebo de darme cuenta que además con Panel, no puedo hacer multiselección
Hola.
El problema con un ListBox y un ListView es que automaticamente acomodan los caracteres de salto de linea para la vista en horizontal, para representarlo como una sola linea. Me parece que la única solución sería heredar la class ListBox o ListView con OwnerDraw para dibujar manualmente los rectángulos de los items y el contenido de texto, se puede hacer (al menos con un ListView), pero es una solución tediosa que requeriría tiempo y esfuerzo.
Con un ListBox, para hacerlo vertical puedes pasarle un array, esto significa que si tenemos el string "12345678", cada caracter "{1, 2, 3, 4, 5, 6, 7, 8}" será seleccionado por individual, no se si eso te parecerá bien.
(http://i.imgur.com/lc1B5eQ.png)
With ListBox1
.MultiColumn = True
.IntegralHeight = False
.Font = New Font(.Font.FontFamily, 14.0F)
.ColumnWidth = CInt(Math.Ceiling(.Font.Size))
.Size = New Size((.ColumnWidth * 4), 200)
End With
Dim arr1 As String() = {"1", "2", "3", "4", "5", "6", "7", "8"}
Dim arr2 As String() = {"4", "5", "6", "6", "4", "5", "5", "5"}
Dim arr3 As String() = {"7", "8", "9", "9", "9", "9", "9", "9"}
ListBox1.Items.AddRange(arr1)
ListBox1.Items.AddRange(arr2)
ListBox1.Items.AddRange(arr3)
Con un ListView, puedes utilizar el modo de vista LargeIcon, sin embargo, para que se muestre en vertical debemos activar la propiedad LabelWrap, y esto nos da un resultado visual poco agradable sobre los items que no están seleccionados, puesto que no podemos redimensionar el tamaño de los rectángulos de cada item a menos que heredemos el control:
(http://i.imgur.com/PcilPTI.png)
With ListView1
.Font = New Font(.Font.FontFamily, 12.25F)
.View = View.LargeIcon
.LabelWrap = True
End With
Dim str1 As String = String.Join(ControlChars.Lf, {"1", "2", "3", "4", "5", "6", "7", "8"})
Dim str2 As String = String.Join(ControlChars.Lf, {"4", "5", "6", "6", "4", "5", "5", "5"})
Dim str3 As String = String.Join(ControlChars.Lf, {"7", "8", "9", "9", "9", "9", "9", "9"})
Dim item1 As New ListViewItem(str1)
Dim item2 As New ListViewItem(str2)
Dim item3 As New ListViewItem(str3)
ListView1.Items.AddRange({item1, item2, item3})
Con un DataGridView ocurre exactamente lo mismo por defecto, los caracteres de salto linea se acomodan para la vista en horizontal (las lineas en blanco no se eliminan del item, tampoco en un Listview, simplemente el control representa el texto en las filas sin las lineas en blanco), sin embargo, el DataGridView es un control mucho más personalizable que un ListView, así que podemos adaptarlo a nuestras necesidades de vista de filas en vertical y el resultado quedará bastante bien:
(http://i.imgur.com/PDiSyDn.png)
With DataGridView1
.Columns.Add("Column1", "")
.Columns.Add("Column2", "")
.Columns.Add("Column3", "")
.Font = New Font(.Font.FontFamily, 12.25F)
.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells
.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
.DefaultCellStyle.WrapMode = DataGridViewTriState.True
End With
Dim str1 As String = String.Join(ControlChars.Lf, {"1", "2", "3", "4", "5", "6", "7", "8"})
Dim str2 As String = String.Join(ControlChars.Lf, {"4", "5", "6", "6", "4", "5", "5", "5"})
Dim str3 As String = String.Join(ControlChars.Lf, {"7", "8", "9", "9", "9", "9", "9", "9"})
DataGridView1.Rows.Add({str1, str2, str3})
En resumen, tu mejor opción es recurrir al control DataGridView, o bien heredar la class ListView, activar el OwnerDraw para dibujar manualmente el contenido del control y trastear con el StringFormat, StringAlignment, y los Bounds para intentar conseguir el resultado de vista en vertical (que no estoy muy seguro de si se podrá, depende de los miembros que sean accesibles]).
Saludos!
Gracias @Elektro
Hay que ver como te curras las respuestas, me sabe mal y todo.
el DataGridView parece una buena opción, lo probaré a ver. No se me había ocurrido probar con él.
La verdad es que no entiendo como no se puede. Como aquí en la primera columna:
(http://i35.tinypic.com/1zno510.jpg)
Lo intenté con API, con SendMessage, y probando conseguí cosas interesantes pero no eso.
Cita de: okik en 12 Diciembre 2016, 01:13 AM
La verdad es que no entiendo como no se puede. Como aquí en la primera columna:
(http://i35.tinypic.com/1zno510.jpg)
Lo intenté con API, con SendMessage, y probando conseguí cosas interesantes pero no eso.
Poder se puede, como ya dije, pero es que lo que muestras en esa imagen, no es un
ListView por defecto, en todo cado puede ser un
DataGridView modificado, o un
ListView que ha sido modificado, es decir, un
user-control personalizado. Puedes hacer lo mismo que en esa imagen, como ya dije, heredando la class
ListView.
Aquí tienes un ejemplo base:
- http://pastebin.com/sp1CfSJc
- http://foro.elhacker.net/net/listview_con_progress_downloader-t418867.0.html;msg1956380#msg1956380
No necesitas
en ningún momento recurrir a las funciones de la API de Windows, eso déjalo para circunstancias en donde no puedas controlarlo de otra forma más directa; al heredar la class
ListView, obtienes todo el control necesario, haciendo uso de los miembros heredados, para personalizar la manera en que se renderiza el control (añadir barras de progreso, hacer el texto multi-linea, cambiar los colores por defecto, etc), simplemente aprende a hacerlo:
- Owner Drawing in Windows Forms Controls (https://msdn.microsoft.com/en-us/library/yyfab68k%28v=vs.110%29.aspx)
Creating a Windows Form User Control - MSDN - Microsoft (https://msdn.microsoft.com/en-us/library/aa302342.aspx)
Pero ya te digo, lo veo una pérdida de tiempo, cuando puedes usar un
DataGridView y darle una apariencia similar a un
ListView (suponiendo que eso sea lo que te frena).
Saludos!
Esos saltos de línea que ves en la primera columna los genera automáticamente el DataGridView cuando le colocas a la columna DefaultCellStyle->WrapMode = true
Solo existen en la visualización; si agrandases la columna, se recolocaría.
Gracias
El DataGridView va perfecto. No se me ocurrió utilizarlo porque lo usaba para base de datos, modificar tablas .
Public Class Form1
Dim DataGridView1 As New DataGridView
Sub New()
' Llamada necesaria para el diseñador.
InitializeComponent()
' Agregue cualquier inicialización después de la llamada a InitializeComponent().
Me.Controls.Add(Me.DataGridView1)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
With DataGridView1
.DefaultCellStyle.WrapMode = DataGridViewTriState.True
.RowHeadersVisible = False
.ColumnHeadersVisible = False
.ReadOnly = True
.RowCount = 1
.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells
' .AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells
.MultiSelect = True
.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText 'Permite copiar el texto
.RowsDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter 'coloca los valores en el centro
.Size = New Size(New Point(400, 235))
End With
'Configuración de las cabeceras de las columna
With DataGridView1.ColumnHeadersDefaultCellStyle
.Font = New Font("Lucida Console Unicode", FontStyle.Bold)
.Alignment = DataGridViewContentAlignment.MiddleCenter 'coloca el texto en el centro
End With
Dim nvars As Integer = 20
DataGridView1.ColumnCount = nvars
Task.Factory.StartNew(Sub()
For I As Integer = 0 To nvars - 1
Dim Integ As Integer = I
Me.Invoke(DirectCast(Sub()
DataGridView1.Columns(Integ).Width = 30
DataGridView1.Item(Integ, 0).Value = Serie1()
System.Threading.Thread.Sleep(50)
End Sub, MethodInvoker))
Next
End Sub)
End Sub
Public Function Serie1() As String
Dim col(16) As String
Dim Rand As New Random
For index As Integer = 1 To col.Count - 1
col(index) = CStr(Rand.Next(0, 16))
Next
Return CStr(String.Join(Environment.NewLine, col).Trim)
End Function
End Class
(https://s25.postimg.org/6h24ettzj/datagridview_Wrap_Mode.png)
Lo malo es que no parece que pueda evitar que las columnas no sean redimensionables y al mismo tiempo en la creación de la columna establecer el ancho de la misma.
Sí, puedes evitar que sean redimensionables, una a una. Tienes que ir a editar columnas y ahí ves todas sus propiedades.
También tiene el DataGridView unos campos que son los "default", que afectan a todas las columnas, filas y celdas que no tengan un "valor explícito".
Todo lo puedes hacer, échale un ojo a las propiedades detenidamente.
Cita de: ivancea96 en 12 Diciembre 2016, 22:58 PM
Sí, puedes evitar que sean redimensionables, una a una. Tienes que ir a editar columnas y ahí ves todas sus propiedades.
También tiene el DataGridView unos campos que son los "default", que afectan a todas las columnas, filas y celdas que no tengan un "valor explícito".
Todo lo puedes hacer, échale un ojo a las propiedades detenidamente.
claro que puedo evitar que sea redimensionable si ajusto establezco:
DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells
Pero entonces ya no puedo establecer el ancho, aunque lo haga lo ignora.
Vale, la solución es usar
Padding junto con
DisplayedCells
DataGridView1.DefaultCellStyle.Padding = New Padding(5, 2, 5, 2)
DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells
Donde:
Padding(X(izquierdo), Y(Arriba), X(derecho), Y(abajo))
Public Class Form1
Dim DataGridView1 As New DataGridView
Sub New()
' Llamada necesaria para el diseñador.
InitializeComponent()
' Agregue cualquier inicialización después de la llamada a InitializeComponent().
Me.Controls.Add(Me.DataGridView1)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
With DataGridView1
.DefaultCellStyle.WrapMode = DataGridViewTriState.True
.RowHeadersVisible = False
.ColumnHeadersVisible = False
.ReadOnly = True
.RowCount = 1
.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells
.DefaultCellStyle.Padding = New Padding(5, 2, 5, 2)
.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells
.MultiSelect = True
.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText 'Permite copiar el texto
.RowsDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter 'coloca los valores en el centro
.Size = New Size(New Point(400, 240))
End With
Dim nvars As Integer = 20
DataGridView1.ColumnCount = nvars
Task.Factory.StartNew(Sub()
For I As Integer = 0 To nvars - 1
Dim Integ As Integer = I
Me.Invoke(DirectCast(Sub()
DataGridView1.Item(Integ, 0).Value = Serie1()
System.Threading.Thread.Sleep(50)
End Sub, MethodInvoker))
Next
End Sub)
End Sub
Public Function Serie1() As String
Dim col(16) As String
Dim Rand As New Random
For index As Integer = 1 To col.Count - 1
col(index) = CStr(Rand.Next(0, 16).ToString("00"))
Next
Return CStr(String.Join(Environment.NewLine, col).TrimStart)
End Function
End Class
(https://s25.postimg.org/4drp75u6n/datagridview_Wrap_Mode2.png)
Bueno, pues GRACIAS a los dos. Lo doy por solucionado ;-)
Para evitar usar padding y tener un ancho fijo, le colocas el Width, le colocas Resizable a False y AutoSize a None.
Conviene que no interactues con el control para construir las columnas y filas, en su lugar puedes definir un DataTable y construir la tabla allí, y entonces usar la propiedad DataSource del DataGridView. Recuerda, cuanta menos interacción directa exista por parte de tu código con los controles, mejor.
He extendido mucho (realmente mucho) el siguiente ejemplo, pero bueno, así es como lo haría yo:
Primeramente, definimos un type para almacenar y/o generar secuencias numéricas aleatorias. Aparte de servirnos para la representación epxlícita de este tipo de secuencias numéricas, otra diferencia (o ventaja) entre una colección List(Of Integer()) y esto, es que podremos reutilizarlo para muchos otros propósitos en el futuro:
Public Class RandomSequence
Protected Shared ReadOnly rand As New Random()
Public ReadOnly Property Value As Integer()
Get
Return Me.valueB
End Get
End Property
Protected valueB As Integer()
Public ReadOnly Property Value(ByVal format As String) As String
Get
Return Me.ToString(New IntegerSequenceFormatter(), format)
End Get
End Property
Public ReadOnly Property Length As Integer
Get
Return Me.lengthB
End Get
End Property
Protected lengthB As Integer
Private Sub New()
End Sub
Protected Friend Sub New(ByVal value As Integer())
Me.valueB = value
Me.lengthB = value.Length
End Sub
Protected Friend Sub New(ByVal length As Integer)
Me.valueB = Me.GenerateSequence(length).ToArray()
Me.lengthB = length
End Sub
Protected Friend Sub New(ByVal length As Integer, ByVal minValue As Integer, ByVal maxValue As Integer)
Me.valueB = Me.GenerateSequenceInternal(length, minValue, maxValue).ToArray()
Me.lengthB = length
End Sub
Public Overridable Function GenerateSequence(ByVal length As Integer) As Integer()
Me.valueB = Me.GenerateSequenceInternal(length, minValue:=0, maxValue:=Integer.MaxValue).ToArray()
Return Me.valueB
End Function
Public Overridable Function GenerateSequence(ByVal length As Integer, ByVal minValue As Integer, ByVal maxValue As Integer) As Integer()
Me.valueB = Me.GenerateSequenceInternal(length, minValue, maxValue).ToArray()
Return Me.valueB
End Function
Protected Iterator Function GenerateSequenceInternal(ByVal length As Integer, ByVal minValue As Integer, ByVal maxValue As Integer) As IEnumerable(Of Integer)
For i As Integer = 0 To (length - 1)
Yield rand.Next(minValue, maxValue)
Next i
End Function
Public Overrides Function Equals(ByVal obj As Object) As Boolean
If (TypeOf obj IsNot RandomSequence) Then
Return False
Else
Return String.Join(" ", Me.valueB).Equals(String.Join(" ", DirectCast(obj, RandomSequence).valueB))
End If
End Function
Public Overridable Shadows Function ToString(ByVal formatProvider As IFormatProvider, ByVal format As String) As String
If (TypeOf formatProvider Is ICustomFormatter) Then
Return DirectCast(formatProvider, ICustomFormatter).Format(format, Me.valueB, formatProvider)
ElseIf (formatProvider IsNot Nothing) Then
Return formatProvider.ToString()
Else
Return Me.valueB.ToString()
End If
End Function
<EditorBrowsable(EditorBrowsableState.Never)>
Public Overridable Shadows Function GetHashCode() As Integer
Return MyBase.GetHashCode()
End Function
<EditorBrowsable(EditorBrowsableState.Never)>
Public Overridable Shadows Function [GetType]() As Type
Return MyBase.GetType()
End Function
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shared Shadows Function ReferenceEquals(ByVal objA As Object, ByVal objB As Object) As Boolean
Return Object.ReferenceEquals(objA, objB)
End Function
End Class
Ejemplo de uso:
Dim seq1 As New RandomSequence(length:=3) ' minValue:=0, maxValue:=Integer.MaxValue
Dim seq2 As New RandomSequence(length:=3, minValue:=0, maxValue:=100)
Dim seq3 As New RandomSequence({1, 2, 3})
Console.WriteLine(seq1.Length)
Console.WriteLine(seq1.Equals(seq2))
Console.WriteLine(seq1.Value.ToString())
Console.WriteLine(seq1.Value("Horizontal")) ' o "H"
Console.WriteLine(seq1.Value("Vertical")) ' o "V"
Seguidamente, definimos un segunto Type donde implementaremos las interfaces IFormatProvider y ICustomFormatter, y así desarrollaremos el algoritmo de formateo para representar en texto horizontal o vertical una secuencia numérica. La gran ventaja de desarrollar nuestro propio proveedor de formato es que se puede adaptar para utilizarlo de mil formas distintas, en cientos de situaciones diferentes.
Public Class IntegerSequenceFormatter : Implements IFormatProvider, ICustomFormatter
Public Function GetFormat(formatType As Type) As Object Implements IFormatProvider.GetFormat
If formatType Is GetType(ICustomFormatter) Then
Return Me
Else
Return Nothing
End If
End Function
Public Function Format(fmt As String, arg As Object, formatProvider As IFormatProvider) As String Implements ICustomFormatter.Format
If Not Me.Equals(formatProvider) Then
Return Nothing
End If
If String.IsNullOrEmpty(fmt) Then
Dim result As Integer() = TryCast(arg, Integer())
If (result Is Nothing) Then
Throw New ArgumentException("Value is not of type Integer()", paramName:="arg")
Exit Function
End If
End If
If String.IsNullOrEmpty(fmt) Then
Throw New ArgumentNullException(paramName:="fmt")
Exit Function
End If
Select Case fmt.ToLower()
Case "h", "horizontal"
Dim seq As Integer() = DirectCast(arg, Integer())
Dim maxLength As Integer = CStr(seq.Max()).Length
Dim sb As New StringBuilder(capacity:=seq.Length * maxLength)
For Each item As Integer In seq
sb.Append(item.ToString().PadLeft(maxLength, "0"c))
sb.Append(" "c)
Next
Return sb.ToString.TrimEnd(" "c)
Case "v", "vertical"
Dim seq As Integer() = DirectCast(arg, Integer())
Dim maxLength As Integer = CStr(seq.Max()).Length
Dim sb As New StringBuilder(capacity:=seq.Length * maxLength)
For Each item As Integer In seq
sb.AppendLine(item.ToString().PadLeft(maxLength, "0"c))
Next
Return sb.ToString()
Case Else
Throw New FormatException(String.Format("'{0}' cannot be used to format {1}.", fmt, arg.ToString()))
End Select
End Function
End Class
Ejemplo de uso:
Dim arr As Integer() = {1, 2, 3, 4, 5}
Dim strHorz As String = String.Format(New IntegerSequenceFormatter, "{0:H}", arr)
Dim strVert As String = String.Format(New IntegerSequenceFormatter, "{0:V}", arr)
Console.WriteLine(strHorz)
Console.WriteLine(strVert)
Nota:
En el el type RandomSequence no necesitaremos utilizarlo pasando tantos argumentos, lo acortaremos a RandomSequence.Value("H") y RandomSequence.Value("V").
Por último, solo nos queda hacer uso de todo esto para construir la tabla y representarla en el control DataGridView:
Public Class Form1 : Inherits Form
Dim seqList As New List(Of RandomSequence)
Dim seqTable As New DataTable("RandomSequenceTable")
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim seqCount As Integer = 20 ' Amount of random sequences to generate.
' Build random sequences list. This is optional, just to have a short reference to a generic collection in source-code.
For i As Integer = 0 To (seqCount - 1)
seqList.Add(New RandomSequence(length:=16, minValue:=0, maxValue:=16))
Next
' Build table from list.
For x As Integer = 0 To (seqList.Count - 1)
seqTable.Columns.Add(New DataColumn)
seqTable.Columns(x).DataType = GetType(RandomSequence)
Next
seqTable.Rows.Add.ItemArray = seqList.ToArray()
' To build a data-table of vertical strings representation:
' seqTable.Rows.Add.ItemArray = (From seq As RandomSequence In seqList Select seq.Value("V")).ToArray()
seqTable.AcceptChanges()
' Build grid.
DataGridView1.SuspendLayout()
With DataGridView1
.AllowUserToAddRows = False
.AllowUserToResizeColumns = False
.AutoGenerateColumns = True
.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells
.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells
.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText
.ColumnHeadersVisible = False
.DefaultCellStyle.Padding = New Padding(5, 2, 5, 2)
.DefaultCellStyle.WrapMode = DataGridViewTriState.True
.MultiSelect = True
.ReadOnly = True
.RowHeadersVisible = False
.RowsDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
.Size = New Size(New Point(400, 240))
End With
DataGridView1.DataSource = seqTable
DataGridView1.ResumeLayout()
End Sub
Private Sub DataGridView1_CellFormatting(ByVal sender As Object, ByVal e As DataGridViewCellFormattingEventArgs) _
Handles DataGridView1.CellFormatting
If (e.Value IsNot Nothing) Then
Dim item As RandomSequence = TryCast(e.Value, RandomSequence)
If (item IsNot Nothing) Then
e.Value = item.Value("Vertical")
End If
End If
End Sub
End Class
Nótese que el contenido de las celdas se formatean controlando el evento DataGridView1.CellFormatting, esto es algo ilustrativo y opcional, se puede contruir la data-table con las celdas ya formateadas para no tener que hacerlo después controlando ese evento, pero veo de mayor utilidad tener un data-table de RandomSequence, que de Strings multilinea...
Resultado de ejecución:
(http://i.imgur.com/e4Do5Qb.png)
Saludos!
Cita de: Eleкtro en 13 Diciembre 2016, 14:47 PM
Conviene que no interactues con el control para construir las columnas y filas, en su lugar puedes definir un DataTable y construir la tabla allí, y entonces usar la propiedad DataSource del DataGridView. Recuerda, cuanta menos interacción directa exista por parte de tu código con los controles, mejor.
Fantástica idea, tampoco se me había ocurrido, muchas gracias. Ahora se muestra todo directamente. Lo utilizaré a partir de ahora incluso con un listbox o un listview, aunque sea una lista, para evitar el proceso de llenado poco a poco del control. es mejor con datasource