I am trying to implement the listview object in my application but, for some reason my listview is not showing text on any of the items. The items are added, and showing the small icon.
The result I would like (screenshot www).
My current result
Below is the code that I use to generate the listview. The listview is added on the winform using the designer.
Public Class OccurrenceControl
' Local variable
Private _occurrence As Inventor.ComponentOccurrence
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
' Create a new image list
Dim imageList As ImageList = New ImageList()
imageList.ImageSize = New Drawing.Size(32, 32)
imageList.Images.Add(My.Resources.MateConstraint)
imageList.Images.Add(My.Resources.AngleConstraint)
imageList.Images.Add(My.Resources.TangentConstraint)
imageList.Images.Add(My.Resources.InsertConstraint)
' Set the listview small images list
lvConstraints.SmallImageList = imageList
' Make the list scrollable
lvConstraints.Scrollable = True
' Set the listview view type
lvConstraints.View = View.List
End Sub
Public Sub ShowInfo(ByVal Occurrence As Inventor.ComponentOccurrence)
' Populate the local variable with the passed occurrence
_occurrence = Occurrence
' Clear all listed constraints
lvConstraints.Items.Clear()
' Set the grounded checkbox value
cbGrounded.Checked = Occurrence.Grounded
' Loop all constraints.
For Each oConstraint As Inventor.AssemblyConstraint In Occurrence.Constraints
' Create a new listview item
Dim oListItem As New ListViewItem
' Give the listview item a name
oListItem.Name = oConstraint.Name
' Add a image based on the constraint type.
If oConstraint.Type = Inventor.ObjectTypeEnum.kFlushConstraintObject Or Inventor.ObjectTypeEnum.kMateConstraintObject Then
oListItem.ImageIndex = 0
ElseIf oConstraint.Type = Inventor.ObjectTypeEnum.kAngleConstraintObject Then
oListItem.ImageIndex = 1
ElseIf oConstraint.Type = Inventor.ObjectTypeEnum.kTangentConstraintObject Then
oListItem.ImageIndex = 2
ElseIf oConstraint.Type = Inventor.ObjectTypeEnum.kInsertConstraintObject Then
oListItem.ImageIndex = 3
End If
' Add the new listview item to the listview
lvConstraints.Items.Add(oListItem)
Next
End Sub
End Class
You're not setting the Text of the ListViewItem anywhere so it's no surprise that no text is being displayed.
Related
I have this userform
The form contains three comboboxes along with "add new" command button
Once user click add new button it will add another row of comboboes with a clear button.
The clear button will remove that particular row of comboboxes only. add button will add a new line below this line
each row is an element i.e A,B,C... The insert button will write conditions e.g. A OR B OR C
inserted text gathered from comboboxes would be
{#(ProductA === "P25" || ProductB === "P26" || ProductC === "P27")}
I am trying to have a button that can dynamically add new line of comboboxes with a clear button. also clear button that will only remove that line of comboboxes. I can user the code as discussed here. But don't know how I can handle the position and clear button.
An interesting mini-project, so here is my "solution" (obviously still far from being fully-functional but has the basic add/remove row capability.
The userform I tested with had only a single command button btnAddRow (for adding new rows).
Class DataRow:
Option Explicit
Private WithEvents Combo1 As MSForms.ComboBox
Private WithEvents Combo2 As MSForms.ComboBox
Private WithEvents ClearButton As MSForms.CommandButton
Dim controlContainer As Object
Public id As Long
Private Sub ClearButton_Click()
'calls a method on the parent form to remove the row
CallByName controlContainer, "RemoveRow", VbMethod, id
End Sub
'create and set up a few properties the controls for a row
Public Sub Create(container As Object, rowId As Long)
Set Combo1 = container.Controls.Add("Forms.ComboBox.1", "C1_" & rowId)
Combo1.Left = 20
Combo1.Width = 60
Combo1.List = Array("Item1", "Item2")
Set Combo2 = container.Controls.Add("Forms.ComboBox.1", "C2_" & rowId)
Combo2.Left = 90
Combo2.Width = 60
Combo2.List = Array("Red", "Blue")
Set ClearButton = container.Controls.Add("Forms.commandbutton.1", "BC_" & rowId)
ClearButton.Left = 160
ClearButton.Width = 50
ClearButton.Height = 20
ClearButton.Caption = "Clear"
Set controlContainer = container 'keep a reference to the place where the controls are created
id = rowId 'identifies this row
End Sub
'set the vertical position on the form for the row
Public Sub Position(rowIndex)
Dim pTop
pTop = 15 + (25 * (rowIndex - 1))
Combo1.Top = pTop
Combo2.Top = pTop
ClearButton.Top = pTop
End Sub
'remove the row - called from the parent form
Public Sub Remove()
With controlContainer.Controls
.Remove Combo1.Name
.Remove Combo2.Name
.Remove ClearButton.Name
End With
End Sub
'move the "add row" button to this row
Public Sub TakeAdd(btn As MSForms.CommandButton)
btn.Left = ClearButton.Left + ClearButton.Width + 10
btn.Top = ClearButton.Top
End Sub
'is this row removable? (last remaining row can't be removed)
Public Sub CanRemove(bRemovable As Boolean)
ClearButton.Enabled = bRemovable
End Sub
The code for the userform:
Option Explicit
Dim id As Long 'sequence number for assigning unique id to each row
Dim col As New Collection 'holds instances of the class `DataRow`
Private Sub btnAddRow_Click()
AddRow 'add a new row
End Sub
Private Sub UserForm_Activate()
AddRow 'add starting row
End Sub
'add a row
Sub AddRow()
Dim obj As New DataRow
id = id + 1
obj.Create Me, id 'creates the controls
col.Add obj 'add to the collection
obj.Position col.Count 'sets row index on form
obj.TakeAdd Me.btnAddRow 'position the "add row" button
checkRemove 'check if remove buttons are enabled
End Sub
'remove the specified row
Sub RemoveRow(rowId As Long)
Dim i As Long
i = 1
Do While i <= col.Count
If col(i).id = rowId Then 'is this the row to remove?
col(i).Remove 'removes the row from the form
col.Remove (i) 'removes the class instance from the collection
Else
col(i).Position i '(re)position this row
i = i + 1
End If
Loop
col(col.Count).TakeAdd Me.btnAddRow '(re)position the "add row" button
checkRemove 'check if remove buttons are enabled
End Sub
'see if the "remove" buttons should be enabled
Sub checkRemove()
Dim obj
For Each obj In col
obj.CanRemove (col.Count > 1) 'enabled if >1 row on form
Next obj
End Sub
I am trying to resize/ re-position controls based on the size of the form. This is the class i am using :
Public Class Resizer
'----------------------------------------------------------
' ControlInfo
' Structure of original state of all processed controls
'----------------------------------------------------------
Private Structure ControlInfo
Public name As String
Public parentName As String
Public leftOffsetPercent As Double
Public topOffsetPercent As Double
Public heightPercent As Double
Public originalHeight As Integer
Public originalWidth As Integer
Public widthPercent As Double
Public originalFontSize As Single
End Structure
'-------------------------------------------------------------------------
' ctrlDict
' Dictionary of (control name, control info) for all processed controls
'-------------------------------------------------------------------------
Private ctrlDict As Dictionary(Of String, ControlInfo) = New Dictionary(Of String, ControlInfo)
'----------------------------------------------------------------------------------------
' FindAllControls
' Recursive function to process all controls contained in the initially passed
' control container and store it in the Control dictionary
'----------------------------------------------------------------------------------------
Public Sub FindAllControls(thisCtrl As Control)
'-- If the current control has a parent, store all original relative position
'-- and size information in the dictionary.
'-- Recursively call FindAllControls for each control contained in the
'-- current Control
For Each ctl As Control In thisCtrl.Controls
Try
If Not IsNothing(ctl.Parent) Then
Dim parentHeight = ctl.Parent.Height
Dim parentWidth = ctl.Parent.Width
Dim c As New ControlInfo
c.name = ctl.Name
c.parentName = ctl.Parent.Name
c.topOffsetPercent = Convert.ToDouble(ctl.Top) / Convert.ToDouble(parentHeight)
c.leftOffsetPercent = Convert.ToDouble(ctl.Left) / Convert.ToDouble(parentWidth)
c.heightPercent = Convert.ToDouble(ctl.Height) / Convert.ToDouble(parentHeight)
c.widthPercent = Convert.ToDouble(ctl.Width) / Convert.ToDouble(parentWidth)
c.originalFontSize = ctl.Font.Size
c.originalHeight = ctl.Height
c.originalWidth = ctl.Width
ctrlDict.Add(c.name, c)
End If
Catch ex As Exception
Debug.Print(ex.Message)
End Try
If ctl.Controls.Count > 0 Then
FindAllControls(ctl)
End If
Next '-- For Each
End Sub
'----------------------------------------------------------------------------------------
' ResizeAllControls
' Recursive function to resize and reposition all controls contained in the Control
' dictionary
'----------------------------------------------------------------------------------------
Public Sub ResizeAllControls(thisCtrl As Control, Optional wform As Form = Nothing)
Dim fontRatioW As Single
Dim fontRatioH As Single
Dim fontRatio As Single
Dim f As Font
If IsNothing(wform) = False Then wform.Opacity = 0
'-- Resize and reposition all controls in the passed control
For Each ctl As Control In thisCtrl.Controls
Try
If Not IsNothing(ctl.Parent) Then
Dim parentHeight = ctl.Parent.Height
Dim parentWidth = ctl.Parent.Width
Dim c As New ControlInfo
Dim ret As Boolean = False
Try
'-- Get the current control's info from the control info dictionary
ret = ctrlDict.TryGetValue(ctl.Name, c)
'-- If found, adjust the current control based on control relative
'-- size and position information stored in the dictionary
If (ret) Then
'-- Size
ctl.Width = Int(parentWidth * c.widthPercent)
ctl.Height = Int(parentHeight * c.heightPercent)
'-- Position
ctl.Top = Int(parentHeight * c.topOffsetPercent)
ctl.Left = Int(parentWidth * c.leftOffsetPercent)
'-- Font
f = ctl.Font
fontRatioW = ctl.Width / c.originalWidth
fontRatioH = ctl.Height / c.originalHeight
fontRatio = (fontRatioW +
fontRatioH) / 2 '-- average change in control Height and Width
ctl.Font = New Font(f.FontFamily,
c.originalFontSize * fontRatio, f.Style)
End If
Catch
End Try
End If
Catch ex As Exception
End Try
'-- Recursive call for controls contained in the current control
If ctl.Controls.Count > 0 Then
ResizeAllControls(ctl)
End If
Next '-- For Each
If IsNothing(wform) = False Then wform.Opacity = 1
End Sub
End Class
The problem with this code is that
1) It flickers a lot while resizing the controls or moving them around.
2) Some labels and buttons are moved around to random positions on the form and,
3) the size of the background image is not responsive to the size of the form (Not much knowledge on how to execute this.)
Any sort of help is appreciated.
Instead of manually doing this, look into properties on the form such as "Dock", "Anchor".
Examples:
Setting dock to top (or bottom) will allow a module to always cover
the entire top (or bottom) of the form, keeping the height the same.
Setting dock to left (or right) will allow a module to always cover the entire left (or right) of the form, keeping the height the
same.
Setting the anchor property to all of Top, Left, Right, Down will allow the control to expand as the form grows, but keep it's x/y top-left position.
Setting the anchor property to Top, Left, Right will allow the control to expand to the right as the form grows, but keep it's x/y top-left position
Setting the anchor property to Bottom, Right will allow the control to stay in a fixed position relative to the bottom corner of the form.
I've got part of my code that creates a tab in a tabcontrol, and then fills it with a datagridview which contains a couple columns that are DataGridViewComboBoxColumn.
It looks like this:
Private Sub NewTabPage()
Dim TabPageCount As Integer = RacerOrderTAB.TabPages.Count
RacerOrderTAB.TabPages.Add(TeamNames(TabPageCount)) 'teamnames() is an array of team names
Dim CurrentTabPage = RacerOrderTAB.TabPages(TabPageCount)
Dim GridToAdd As New DataGridView
GridToAdd.Size = CurrentTabPage.Size
GridToAdd.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
GridToAdd.Location = New Point(CurrentTabPage.Location.X, CurrentTabPage.Location.Y)
GridToAdd.Columns.Add("ShiftCOL", "Shift Name")
GridToAdd.Name = "grid_" & CurrentTabPage.Text
For y As Integer = 1 To ShiftSetup.racerspershift 'add extra column for each racer in shift
Dim cmb As New DataGridViewComboBoxColumn
cmb.HeaderText = "Racer" & y
cmb.Name = "Racer_" & y
cmb.MaxDropDownItems = AmountOfRacers
cmb.DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton
GridToAdd.Columns.Add(cmb)
Next
RacerOrderTAB.TabPages(TabPageCount).Controls.Add(GridToAdd)
End Sub
But I've been having difficulty in adding an eventhandler for the comboboxes. What I want to happen is that when a combobox is clicked and opened, I populate it with the items I want.
I managed to vaguely get it working by adding:
AddHandler GridToAdd.EditingControlShowing, AddressOf <sub name>
but then have been unable to figure out which combobox was clicked, and how to populate it. It's also been requiring like four clicks before the drop list will appear. I'm only slightly very confused.
Thanks for any advice; these DataGridViewComboBoxColumns [deep breath] have been confusing me a lot!
It may be a little hacky but it should do what you are asking… hopefully. I created two List(Of String) variables. AllRacers contains all the racers… i.e. all the names we want to appear in a combo box such that there is no other combo box on that row that has an item selected. These names are what all combo boxes on all rows would initially contain in the selectable items list.
The other List(Of String) UsedRacers contains a list of all the “comboboxes” on the current row that have selected items. Each time a cell value is changed and it is one of the “combobox” column cells, then UsedRacers is cleared/updated to reflect the added/changed selected item on the current row.
When a “comboBox” cell value is changed, SetUsedRacersForRow is called…
Private Sub SetUsedRacersForRow(rowIndex As Int16)
UsedRacers.Clear()
Dim curValue = ""
For i = 1 To racersPerShift
If (Not (dgvRacers.Rows(rowIndex).Cells(i).Value Is Nothing)) Then
curValue = dgvRacers.Rows(rowIndex).Cells(i).Value.ToString()
If (Not (String.IsNullOrEmpty(curValue))) Then
UsedRacers.Add(curValue)
End If
End If
Next
End Sub
The code above loops through all the “combobox” cells in the given row and if a “combobox” cell has something selected, the selected value is added to the UsedRacers list.
Now that the selected items for all the “comboboxes” in that row are in the UsedRacers list, we can now loop through each “combobox” cell in that row and set the proper list of names. To help, a method is created that returns a DataGridViewComboBoxCell such that the names in the current UsedRacers list will NOT be in the DataGridViewComboBoxCell’s list of selectable names.
The only issue here would be with cells that currently have an item selected. Each “combobox” cell with an item selected will uniquely need to have its selected item in its list of items. To remedy this, a check is needed to see if the “combobox” cell contains a value. If the “combobox” cell DOES contain a selected value, then this value is also contained in the UsedRacers list. Since THIS cell is the cell that is in the UseRacers list… then we need to ADD this value to this cells items list. Otherwise, we would not be able to display the unique selection.
To keep the UsedRacers list consistent, we need to add this item directly to the individual “combobox” cell and not remove or alter the UsedRacers list as this will be used for the other “combobox” cells. In other words… whatever value is selected in a combo box, we need to make sure it is one of the items in the “combobox’s” list of selectable items. I hope that makes sense.
This can all be done in the DataGridViews CellChanged event.
Private Sub dgvRacers_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgvRacers.CellValueChanged
If (e.ColumnIndex >= 1 And e.ColumnIndex <= racersPerShift) Then
SetUsedRacersForRow(e.RowIndex)
For i = 1 To racersPerShift
Dim newCell As DataGridViewComboBoxCell = GetCurrentComboBoxCell()
If (Not (dgvRacers.Rows(e.RowIndex).Cells(i).Value Is Nothing)) Then
Dim curValue = dgvRacers.Rows(e.RowIndex).Cells(i).Value.ToString()
newCell.Items.Add(curValue)
newCell.Value = curValue
End If
dgvRacers.Rows(e.RowIndex).Cells(i) = newCell
Next
End If
End Sub
In the code above, a method GetCurrentComboBoxCell (below) returns a DataGridViewComboBoxCell such that the items in the combo boxes list of items does not contain any items that are in the UsedRacers list. Because of this, a check is needed (above) to see if the cell already contains a value. NOTE: the DataGridViewComboBoxCell returned will always contain a “blank” empty item. This is necessary to allow the user to “De-Select” any currently selected value and then make the “De-Selected” item available to the other combo box cells.
Public Function GetCurrentComboBoxCell() As DataGridViewComboBoxCell
Dim newComboCell = New DataGridViewComboBoxCell()
newComboCell.DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton
newComboCell.FlatStyle = FlatStyle.Flat
newComboCell.Items.Add("")
For Each curRacer In AllRacers
If (Not UsedRacers.Contains(curRacer)) Then
newComboCell.Items.Add(curRacer)
End If
Next
Return newComboCell
End Function
Finally, putting all this together…
Dim racersInShift = 3
Dim AllRacers As List(Of String) = New List(Of String) From {"John", "Bobby", "Trent", "Josh", "Chapman", "Henry", "George", "Marvin"}
'Dim racersPerShift As Int16 = AllRacers.Count '<-- should be MAX value
Dim racersPerShift As Int16 = 4
Dim UsedRacers = New List(Of String)
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BuildGrid()
End Sub
Private Sub BuildGrid()
dgvRacers.Size = New Size(800, 200)
dgvRacers.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
'dgvRacers.Location = New Point(50, 200)
dgvRacers.Columns.Add("ShiftCOL", "Shift Name")
dgvRacers.Name = "RacersDGV"
dgvRacers.EditMode = DataGridViewEditMode.EditOnEnter
dgvRacers.AllowUserToAddRows = False
AddRacerColumns()
AddRacerRows()
End Sub
Private Sub AddRacerColumns()
Dim newColumn As DataGridViewComboBoxColumn
For i As Integer = 1 To racersPerShift
newColumn = GetNewComboBoxColumn("Racer" & i, "Racer " & i)
dgvRacers.Columns.Add(newColumn)
Next
End Sub
Private Sub AddRacerRows()
For i As Integer = 1 To racersInShift
Dim row As New DataGridViewRow
dgvRacers.Rows.Add(row)
Next
End Sub
Private Sub dgvRacers_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs)
‘See code above
End Sub
Private Sub SetUsedRacersForRow(rowIndex As Int16)
‘See code above
End Sub
Public Function GetCurrentComboBoxCell() As DataGridViewComboBoxCell
‘See code above
End Function
‘Lastly a method to set a whole `DataGridviewComboBoxColumn` which is used to initialize all the combo box columns
Public Function GetNewComboBoxColumn(colName As String, colHeader As String) As DataGridViewComboBoxColumn
Dim newComboCol = New DataGridViewComboBoxColumn()
newComboCol.DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton
newComboCol.FlatStyle = FlatStyle.Flat
newComboCol.Items.Add("")
newComboCol.HeaderText = colHeader
newComboCol.Name = colName
For Each curRacer In AllRacers
newComboCol.Items.Add(curRacer)
Next
Return newComboCol
End Function
I hope this helps, I am guessing there is an easier way to do this.
I am new in vb.net, so not much familiar with all VB functions. While working on 1 Windows Application, while taking order we need to save product and its specification like weight, height, width, color, length, material etc.
since every product can have different specification so its is not possible to determine and provide for fields in database.
So, I decided to provide textboxes so user can enter name & value while entering product details. 1 textbox for name & other textbox for value.
like this
Textbox1 = "WEIGHT" TextBox2 = "10" '(Value of Weight)
Textbox3 = "WIDTH" TextBox4 = "5" '(value of Width)
Textbox4 = "LENGTH" TextBox5 = "5" '(Value of Length)
(All these textboxes are dynamically created in Groupbox "GBox1")
Instead of saving product specification in separate column. I want to save these names & values as String e.g. "WEIGHT=10;WIDTH=5;LENGTH=5" in SQL Database(TEXT OR VARCHAR field). Because we dnt want any calculations or search etc. on this. just customer requirements to book order & save in Database for future records.
Then again While calling or editing Product SPLIT the string as separate fields, String Before = Separate & string after = separate, then display all names & their corresponding values in Textboxes (as Displayed while adding) so user can edit and after changes again save as single string value.
After search I found SPLIT & JOIN functions for this purpose.
need some help in using these functions in Loop to merge string from textboxes
for each loop to read all textboxes in Groupbox
Dim ItemList As New ArrayList()
Dim PrDetails As String
For Each Ctrl As Control In GBox1.Controls
If TypeOf Ctrl Is TextBox Then
ItemList.Add(CType(Ctrl, TextBox).Text)
End If
Next
PrDetails = String.Join()
How to perform join on these array list? and again SPLIT this pattern while retrieving from Database.
Also need suggestions regarding this approach or any other way to implement. Thanks.
For this solution to work, you will need to accept the fact that there needs to be some amount of standardization to your solution. TextBox1 and TextBox3 won't work as names, so I used txtName1 and txtValue1 etc. To test this solution, make a new form, put GroupBox1, paste the code in the class and run.
Private nameTextBoxName = "txtName" ' name textbox prefix
Private valueTextBoxName = "txtValue" ' value textbox prefix
Private paramSeparator = ";" ' between sets of parameters
Private nameAndValueSeparator = "=" ' between name and value
Private xOffset = 10 ' for horizontal spacing inside the groupbox
Private yOffset = 20 ' for vertical spacing inside the groupbox
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' your example data
Dim testString = "WEIGHT=10;WIDTH=5;LENGTH=5"
' set the textboxes
setTextBoxes(testString)
' get the string from the textboxes
Dim result = getTextBoxesString()
End Sub
' call to get a string with all the data from the textboxes
Private Function getTextBoxesString() As String
Dim stringToDatabase = ""
Dim textBoxes = GroupBox1.Controls.OfType(Of Control).
Where(Function(co As Control) TypeOf co Is TextBox).
Select(Of TextBox)(Function(co As Control) CType(co, TextBox))
Dim nameTextBoxes = textBoxes.Where(Function(co As Control) co.Name.Contains(nameTextBoxName))
Dim valueTextBoxes = textBoxes.Where(Function(co As Control) co.Name.Contains(valueTextBoxName))
stringToDatabase = nameTextBoxes.Select(Of String)(
Function(nameTextBox As TextBox)
Dim valueTextBox = valueTextBoxes.
Where(Function(vtb As TextBox) vtb.Name = nameTextBox.Name.Replace(nameTextBoxName, valueTextBoxName)).
First()
Return nameTextBox.Text & nameAndValueSeparator & valueTextBox.Text
End Function).Aggregate(Function(oldValue, newValue) oldValue & paramSeparator & newValue)
Return stringToDatabase
End Function
' call to set the textboxes inside the groupbox based on the data string
Private Sub setTextBoxes(textBoxesString As String)
Dim params = textBoxesString.Split(paramSeparator)
Dim index = 1
GroupBox1.Controls.Clear()
For Each param In params
Dim nameAndValue = param.Split(nameAndValueSeparator)
Dim nameTextBox As New TextBox With
{.Name = nameTextBoxName & index.ToString(),
.Text = nameAndValue(0),
.Location = New Point(xOffset, (index - 1) * .Height + yOffset)}
Dim valueTextBox As New TextBox With
{.Name = valueTextBoxName & index.ToString(),
.Text = nameAndValue(1),
.Location = New Point(.Width + xOffset, (index - 1) * .Height + yOffset)}
GroupBox1.Controls.Add(nameTextBox)
GroupBox1.Controls.Add(valueTextBox)
index += 1
Next
End Sub
i am trying to draw sort arrows on list view column header along with the default visual styles
so far i have got this
Private Sub List_DrawColumnHeader(sender As Object, e As DrawListViewColumnHeaderEventArgs) Handles List.DrawColumnHeader
e.DrawDefault = True
If e.ColumnIndex = selectedIndex Then
e.Graphics.DrawImage(ImageList1.Images(1), CType(e.Bounds.Left + e.Bounds.Width / 2, Single) - 5, -2)
End If
End Sub
but the visual style is drawn over the arrow somehow
so i figured i could try this :
Private Sub List_DrawColumnHeader(sender As Object, e As DrawListViewColumnHeaderEventArgs) Handles List.DrawColumnHeader
e.DrawDefault = True
If lastDrawn.ColumnIndex = selectedIndex Then
e.Graphics.DrawImage(ImageList1.Images(1), CType(lastDrawn.Bounds.Left + lastDrawn.Bounds.Width / 2, Single) - 5, -2)
End If
lastDrawn=e
End Sub
and it draws the arrow when the next corresponding column is being drawn
but with this i cant get it to draw for the last column
Screenshots:
In order to use the .NET build in solution for showing a custom icon for a list view column header you need to:
create an ImageList
add three images (up / down arrow and empty) to it
bind the image list to the ListView control
bind to the ColumnClick event of the ListView control
when sorting the columns set the ImageKey property of the currently sorted column depending on the sorting direction
This example class (a simple form) shows how to set the images correctly not using custom drawing for the ListView header columns.
It does not implement any sort! (how to implement a ListViewSorter is shown in this MSDN article)
You need to implement a custom ListView-Sorter class and retrieve the image or the image key from it, after a column is sorted.
Public Class SimpleForm
Inherits Form
Private sortItems = New ImageList()
Dim lv As ListView = New ListView()
Dim so = System.Windows.Forms.SortOrder.Ascending
Public Sub New()
' create columns, items and ListView
Dim columns = New List(Of ColumnHeader)
Dim c1 = New ColumnHeader()
c1.Name = "c1"
c1.Text = "Name"
Dim c2 = New ColumnHeader()
c2.Name = "c2"
c2.Text = "Type"
columns.Add(c1)
columns.Add(c2)
Dim items = New List(Of ListViewItem)
Dim i1 = New ListViewItem("Terminator")
i1.SubItems.Add("T1000")
Dim i2 = New ListViewItem("Terminator")
i2.SubItems.Add("T10")
Dim i3 = New ListViewItem("J.C.")
i3.SubItems.Add("Human")
items.Add(i1)
items.Add(i2)
items.Add(i3)
' init and bind column click
lv.Columns.AddRange(columns.ToArray())
lv.Items.AddRange(items.ToArray())
lv.SmallImageList = sortItems
lv.View = View.Details
lv.Dock = DockStyle.Fill
Controls.Add(lv)
AddHandler lv.ColumnClick, AddressOf clickEventHandler
' init images list
sortItems.TransparentColor = System.Drawing.Color.Transparent
sortItems.Images.Add("up", Image.FromFile("d:\temp\32\arrow_up.gif"))
sortItems.Images.Add("down", Image.FromFile("d:\temp\32\arrow_down.gif"))
sortItems.Images.Add("empty", Image.FromFile("d:\temp\32\check.gif"))
End Sub
Private Sub clickEventHandler(ByVal o As Object, ByVal e As ColumnClickEventArgs)
' Implement a custom ListViewItemSorter and fetch the icon from it!
' Set the ListViewItemSorter property to a new ListViewItemComparer
' object. Setting this property immediately sorts the
' ListView using the ListViewItemComparer object.
' THIS CODE SHOWS ONLY HOW TO SET THE SORT ICON!
For i As Integer = 0 To lv.Columns.Count - 1
If (i = e.Column) Then
Select Case (so)
Case System.Windows.Forms.SortOrder.Ascending
lv.Columns(i).ImageKey = "up"
so = System.Windows.Forms.SortOrder.Descending
Case System.Windows.Forms.SortOrder.Descending
lv.Columns(i).ImageKey = "down"
so = System.Windows.Forms.SortOrder.Ascending
Case Else
lv.Columns(i).ImageKey = "empty"
so = System.Windows.Forms.SortOrder.None
End Select
Else
lv.Columns(i).ImageKey = "empty"
End If
Next i
End Sub
End Class
The output looks like this: