How to freeze merged columns in data grid view when scrolling vertically? - vb.net

I have a data grid view where I need the columns to be frozen or fixed when scrolling vertically.
I have a data grid view control in vb.net windows application which displays the data in a parent-child hierarchy(as shown below). The first column displays the parent data and the second column displays all its child data. The child data in the second column can be as much as 100 rows or even more. So when scrolling down through the grid, the value in the first column does not remain there as it is while the values in the second column(i.e. the child data) scrolls down. So if the user wants to check to which parent, the current child info belongs to, then again he will have to scroll up to the starting of the column to find the name of the parent. I want the values in the first column to be displayed or frozen till it reaches the end of the list of its child values in the grid or at least till the next row where the next parent data starts. I have suggested the client to go with a tree view but they are not agreeing and need it in a data grid view itself. Is there anyway to achieve this in a data grid view?
Thanks in advance.

You can't freeze a row (in runtime, on dgv scrolling) with index greater than zero because all those before are frozen and at that point you can't scroll your datagridview.
If I understood correctly what you want I wrote this class quickly (probably should be optimized). Usage is simple.
1 - First create your own datagridview.
2 - then add your columns and rows (IMPORTANT: Put a “X” in the Tag in each row is a Parent or is considered as title for other rows as you seen in TestPopulate method) .
3 - Call the class I made by passing the datagridview (you created first) as a parameter. At this point this control takes its size, placement and REPLACE YOUR DATAGRIDVIEW .
Private Class CustomDgv
Inherits Panel
Dim WithEvents TopDgv As DataGridView = New DataGridView
Dim WithEvents DownDgv As DataGridView = New DataGridView
Dim Cols As Integer
' This variable is in case you have more rows as "headrow"
' In TestPopulate you can see how to get those
Dim listOfOwnerRows As List(Of Integer) = New List(Of Integer)
Dim currentTopRow As Integer = -1
Protected Overloads Property Height As Integer
Get
Return MyBase.Height
End Get
Set(value As Integer)
MyBase.Height = value
TopDgv.Height = TopDgv.RowTemplate.Height - 1
DownDgv.Height = value - TopDgv.Height - 1
End Set
End Property
Protected Overloads Property Width As Integer
Get
Return MyBase.Width
End Get
Set(value As Integer)
MyBase.Width = value
TopDgv.Width = value - 1
DownDgv.Width = value - 1
End Set
End Property
Sub New(dgvOriginal As DataGridView)
DownDgv = dgvOriginal
Dim parentCtrl As Control = dgvOriginal.Parent
parentCtrl.Controls.Remove(dgvOriginal)
parentCtrl.Controls.Add(Me)
Me.Location = DownDgv.Location
Me.Size = DownDgv.Size
Me.BorderStyle = DownDgv.BorderStyle
TopDgv.Width = Width - 2 - SystemInformation.VerticalScrollBarWidth
TopDgv.Height = TopDgv.RowTemplate.Height
TopDgv.ScrollBars = ScrollBars.None
TopDgv.ColumnHeadersVisible = False
TopDgv.BorderStyle = BorderStyle.None
DownDgv.ColumnHeadersVisible = False
DownDgv.BorderStyle = BorderStyle.None
TopDgv.Left = 0
DownDgv.Left = 0
DownDgv.Width = Width - 2
DownDgv.Height = Height - 2
For Each Col As DataGridViewColumn In DownDgv.Columns
Dim cIndex As Integer = TopDgv.Columns.Add(Col.Clone)
If Col.Frozen Then
TopDgv.Columns(cIndex).Frozen = True
End If
Cols += 1
Next
DownDgv.Top = 0
Me.Controls.Add(TopDgv)
Me.Controls.Add(DownDgv)
If DownDgv.Rows.Count > 0 Then
listOfOwnerRows = (From R As DataGridViewRow In DownDgv.Rows
Where R.Tag = "X"
Select R.Index).ToList
If listOfOwnerRows.Count > 0 Then
SetFrosenRow(listOfOwnerRows(0))
End If
End If
End Sub
Protected Sub SetFrosenRow(index As Integer)
If DownDgv.Rows.Count > index Then
TopDgv.Rows.Clear()
TopDgv.Rows.Add()
Dim currentRIndex As Integer = DownDgv.FirstDisplayedScrollingRowIndex
'If you want onlly the base row
For i As Integer = 0 To Cols - 1
TopDgv.Rows(0).Cells(i).Value = DownDgv.Rows(index).Cells(i).Value
Next
'Or else get the diplayed on top row
TopDgv.Rows(0).DefaultCellStyle = New DataGridViewCellStyle With {
.BackColor = Color.Bisque
}
currentTopRow = index
End If
End Sub
Protected Sub SetChildValuesInTopRow(index As Integer)
For i As Integer = 1 To Cols - 1
TopDgv.Rows(0).Cells(i).Value = DownDgv.Rows(index).Cells(i).Value
Next
End Sub
Private Sub DownDgv_Scroll(sender As Object, e As ScrollEventArgs) Handles DownDgv.Scroll
Try
If e.ScrollOrientation = ScrollOrientation.VerticalScroll Then
Dim topR As Integer = DownDgv.FirstDisplayedScrollingRowIndex
'If you want in top row the current value that is in the top uncomment this
SetChildValuesInTopRow(topR)
If listOfOwnerRows.Count > 0 Then
Dim rToSetAsOwner As Integer = listOfOwnerRows(listOfOwnerRows.Count - 1)
For i As Integer = listOfOwnerRows.Count - 1 To 0 Step -1
If listOfOwnerRows(i) <= topR Then
rToSetAsOwner = listOfOwnerRows(i)
Exit For
End If
Next
If rToSetAsOwner <> currentTopRow Then
SetFrosenRow(rToSetAsOwner)
End If
Console.WriteLine("rToSetAsOwner: " & rToSetAsOwner)
End If
Else
TopDgv.HorizontalScrollingOffset = DownDgv.HorizontalScrollingOffset
End If
Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
End Class
Usage:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Try
' first populate you grid putting a tag in each row which is a header/parent/title for other rows
TestPopulate()
Dim customControl As Control = New CustomDgv(DataGridView1)
Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
Sub TestPopulate()
For i As Integer = 0 To 100
DataGridView1.Rows.Add()
If i = 0 Then
DataGridView1.Rows.Item(0).Cells(0).Value = "Owner 0"
DataGridView1.Rows(0).Tag = "X"
End If
If i = 50 Then
DataGridView1.Rows.Item(50).Cells(0).Value = "Owner 50"
DataGridView1.Rows(50).Tag = "X"
End If
If i = 70 Then
DataGridView1.Rows.Item(70).Cells(0).Value = "Owner 70"
DataGridView1.Rows(70).Tag = "X"
End If
DataGridView1.Rows.Item(i).Cells(1).Value = "child_" & i.ToString & "_1"
DataGridView1.Rows.Item(i).Cells(2).Value = "child_" & i.ToString & "_2"
Next
End Sub
I hope I have been helpful

Related

DataGridView live search data

I am trying to add a search function to a DataGridView in vb.net using this code
For i As Integer = 0 To ContactsList.RowCount - 1
For j As Integer = 0 To ContactsList.ColumnCount - 1
If ContactsList.Rows(i).Cells(j).Value.ToString.ToLower.Trim = ContactsListSearch.Text.ToLower.Trim Then
MsgBox("Item found " + i.ToString)
ContactsList.Rows(i).Visible = True
Else
ContactsList.Rows(i).Visible = False
End If
Next
Next
I'm seeing the MsgBox show when the value matches, but the rows are not showing, it just hides all rows
Another possible option is to load data into a DataTable, create a BindingSource, set the BindingSource DataSource property to the DataTable. Set the DataGridView DataSource property to the BindingSource.
The following example works against a column in the DataTable. If the user clears the TextBox the filter is removed while if there is text filter with trim.
Private Sub SearchButton_Click(sender As Object, e As EventArgs) _
Handles SearchButton.Click
If String.IsNullOrWhiteSpace(SearchTextBox.Text) Then
bindingSource.Filter = ""
Else
Dim currentRowCount = bindingSource.Count
bindingSource.Filter = $"TRIM(LastName) = '{SearchTextBox.Text}'"
MessageBox.Show($"Before: {currentRowCount} Now: {bindingSource.Count}")
End If
End Sub
Edit If the column name might be variable consider loading a ComboBox with column names then adjust code as follows.
bindingSource.Filter = $"TRIM({FilterComboBox.Text}) = '{SearchTextBox.Text}'"
In most cases working against a data source is better than working against cells values as per the above recommendation.
I added 2 Exits in the IF statement when the value matches as it's searching each column as well, so it was causing them to be hidden as it loops columns too
For i As Integer = 0 To ContactsList.RowCount - 1
For j As Integer = 0 To ContactsList.ColumnCount - 1
If ContactsList.Rows(i).Cells(j).Value.ToString.ToLower.Trim = ContactsListSearch.Text.ToLower.Trim Then
MsgBox("Item found " + i.ToString)
ContactsList.Rows(i).Visible = True
Else
ContactsList.Rows(i).Visible = False
End If
Next
Next

Change value of row to image based on cell value

Hi everyone i am doing a windows form which is created in visual studio 2005, wherein it displays data in datagridview. I have a column "colImg" that will display 1 and 0. But i need to display a red image when the value of cell of colImg is 0 and green image when the value is 1. I have a code but the problem is it only displays image which is green but i have a values which is 0. Is there any problem on my code?
Private Sub grdView_CellFormatting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles grdView.CellFormatting
If grdView.Columns(e.ColumnIndex).Name.Equals("colImg") Then
Dim value As Integer
If TypeOf e.Value Is Integer Then
value = DirectCast(e.Value, Integer)
e.Value = My.Resources.Resources.NotYet
Else
For i As Integer = 0 To grdView.RowCount
If value = 0 Then
e.Value = My.Resources.Resources.Red
Else
e.Value = My.Resources.Resources.Green
End If
Next
End If
End If
There are several solutions for your question, I will provide one of them.
You need two columns in your DataGrid.
One is to hold the raw data (0 or 1); in my example I called it colValue.
The other is to hold only the image (red or green); named colImg.
colValue is not shown in the grid :
'Set colValue invisible which is first column in my example
DataGridView1.Columns(0).Visible = False
Use the CellValueChanged event to set the image of the colImg cell:
If e.ColumnIndex = 0 AndAlso Not isInit Then 'Value column has changed and we are not in Form Initializing
Dim valueCell = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex)
Dim imgCell = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex + 1) 'Whatever index your colImg is
If Integer.Parse(valueCell.Value.ToString()) = 1 Then
imgCell.Value = My.Resources.Green
Else
imgCell.Value = My.Resources.Red
End If
End If
To avoid that the event code breaks when the Form and thus the DataGridView is being initilized I created a local variable isInit and set it before and after the initialization:
Public Class Form1
Private isInit As Boolean
Public Sub New()
isInit = True
InitializeComponent()
isInit = False
...
End Sub
...
End Class
Sample Data:
DataGridView1.Rows(0).Cells(0).Value = 1
DataGridView1.Rows(1).Cells(0).Value = 0
DataGridView1.Rows(2).Cells(0).Value = 0
DataGridView1.Rows(3).Cells(0).Value = 1
Outcome:

How to search in listview

I am trying to create a Loop that will read through the information on my ListView through the SubItem to find the text that matches the text in my Textbox whenever I hit the search button and Focuses the listbox onto the matched text. Below is what I have but it keeps telling me that the value of string cannot be converted. I am also pretty sure that my numbers wont loop correctly but I am not really sure how to cause them to loop endlessly till end of statement.
Dim T As String
T = Lines.Text
For r As Integer = 0 to -1
For C As Integer = 0 to -1
If List.Items(r).SubItems(C).Text = Lines.Text Then
List.FocusedItem = T
End If
Next
Next
End Sub
I don't understand your code, but I do understand the question. Below is example code to search all rows and columns of a listview. Search is case insensitive and supports a "find next match" scenario. If a match or partial match is found in any column the row is selected. TextBox1 gets the text to find. FindBtn starts a new search.
Private SrchParameter As String = ""
Private NxtStrtRow As Integer = 0
Private Sub FindBtn_Click(sender As Object, e As EventArgs) Handles FindBtn.Click
If Not String.IsNullOrWhiteSpace(TextBox1.Text) Then
SrchParameter = TextBox1.Text
NxtStrtRow = 0
SearchListView()
End If
End Sub
Private Sub ListView1_KeyDown(sender As Object, e As KeyEventArgs) Handles ListView1.KeyDown
If e.KeyCode = Keys.F3 Then
SearchListView()
End If
End Sub
Private Sub SearchListView()
' selects the row containing data matching the text parameter
' sets NxtStrtRow (a form level variable) value for a "find next match" scenario (press F3 key)
If ListView1.Items.Count > 0 Then
If SrchParameter <> "" Then
Dim thisRow As Integer = -1
For x As Integer = NxtStrtRow To ListView1.Items.Count - 1 ' each row
For y As Integer = 0 To ListView1.Columns.Count - 1 ' each column
If InStr(1, ListView1.Items(x).SubItems(y).Text.ToLower, SrchParameter.ToLower) > 0 Then
thisRow = x
NxtStrtRow = x + 1
Exit For
End If
Next
If thisRow > -1 Then Exit For
Next
If thisRow = -1 Then
MsgBox("Not found.")
NxtStrtRow = 0
TextBox1.SelectAll()
TextBox1.Select()
Else
' select the row, ensure its visible and set focus into the listview
ListView1.Items(thisRow).Selected = True
ListView1.Items(thisRow).EnsureVisible()
ListView1.Select()
End If
End If
End If
End Sub
Instead of looping like that through the ListView, try using a For Each instead:
searchstring as String = "test1b"
ListView1.SelectedIndices.Clear()
For Each lvi As ListViewItem In ListView1.Items
For Each lvisub As ListViewItem.ListViewSubItem In lvi.SubItems
If lvisub.Text = searchstring Then
ListView1.SelectedIndices.Add(lvi.Index)
Exit For
End If
Next
Next
ListView1.Focus()
This will select every item which has a text match in a subitem.
Don't put this code in a form load handler, it won't give the focus to the listview and the selected items won't show. Use a Button click handler instead.
This is the easiest way to search in listview and combobox controls in vb net
dim i as integer = cb_name.findstring(tb_name.text) 'findstring will return index
if i = -1 then
msgbox("Not found")
else
msgbox("Item found")
end if

For loop through function

I tried to draw 64 button(8 rows and 8 columns) to make a game, but for some reason my code does not work. I hope someone can help me with this.
Public Class Form1
Dim AMOUNTOF_COLUMNS As Integer = 8
Dim AMOUNTOF_ROWS As Integer = 8
Public Function setNew(row As Integer, column As Integer) As Button
Dim newButton As New Button()
newButton.Width = 40
Me.Controls.Add(newButton)
newButton.Left = 0
newButton.Top = 0
newButton.Height = newButton.Width
newButton.Text = ""
newButton.Tag = (column + (row * AMOUNTOF_COLUMNS))
AddHandler newButton.MouseDown, AddressOf Klik
Return newButton
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For value1 As Integer = 0 To AMOUNTOF_ROWS Step 1
For value2 As Integer = 0 To AMOUNTOF_COLUMNS Step 1
Me.setNew(value1, value2)
Next
Next
End Sub
If I execute this code, it only draws me one button.
You're drawing 64 buttons but they are all on top of each other. You will need to offset them correctly. Particularly these lines should be modified so that every button gets the correct position.
newButton.Left = 0
newButton.Top = 0
EDIT:
newButton.Tag = "Column x - row y"
Public Sub Klik(sender as Object, e As EventArgs)
Dim b as Button = sender
Dim value as String = b.Tag
If value = "Column 1 - row 1" Then
' Do action for col 1 row 1
Else If value = "Column 2 row 1" Then
' Do action for col 2 row 1
Else If ...
Else If ...
End If
End Sub
You need to modify you method to draw your buttons next to each other, and set their Left and Top properties accordingly:
Public Function setNew(row As Integer, column As Integer) As Button
Dim newButton As New Button()
newButton.Width = 40
newButton.Height = newButton.Width
Me.Controls.Add(newButton)
newButton.Left = column * newButton.Width ' you may need some offset here too
newButton.Top = row * newButton.Height ' you may need some offset here too
newButton.Text = ""
newButton.Tag = (column + (row * AMOUNTOF_COLUMNS))
AddHandler newButton.MouseDown, AddressOf Klik
Return newButton
End Function
Also, you need to draw one less column and row as you're currently creating 9 rows and columns
For value1 As Integer = 0 To AMOUNTOF_ROWS - 1 Step 1
For value2 As Integer = 0 To AMOUNTOF_COLUMNS -1 Step 1
Me.setNew(value1, value2)
Next
Next

Trouble with Timer_tick not stopping

I'm very new to programming and vb.net, trying to self teach more so as a hobby, as I have an idea for a program that I would find useful, but I am having trouble getting past this issue and I believe it is to do with the timer.
I have a form of size.(600,600) with one button of size.(450,150) that is set location(100,50) on the form. When clicked I want to move down it's own height, then add a new button in it's place. The code included below works as desired for the first two clicks, but on the third click the button keeps moving and the autoscroll bar extends. I initially thought it was the autoscroll function or the location property, but realised that as the button keeps moving, the timer hasn't stopped. I am aware that the code is probably very clunky in terms of achieving the outcome, and that there are a few lines/variables that are currently skipped over by the compiler (these are from older attempts to figure this out).
I have looked around and can't find the cause of my problem. Any help would be greatly appreciated. Apologies if the code block looks messy - first go.
Public Class frmOpenScreen
Dim intWButtons, intCreateButtonY, intCreateButtonX 'intTimerTick As Integer
Dim arrWNames() As String
Dim ctrlWButtons As Control
Dim blnAddingW As Boolean
Private Sub btnCreateW_Click(sender As System.Object, e As System.EventArgs) Handles btnCreateW.Click
'Creates new Button details including handler
Dim strWName, strWShort As String
Dim intCreateButtonY2 As Integer
Static intNumW As Integer
Dim B As New Button
strWName = InputBox("Please enter the name name of the button you are creating. Please ensure the spelling is correct.", "Create W")
If strWName = "" Then
MsgBox("Nothing Entered.")
Exit Sub
End If
strWShort = strWName.Replace(" ", "")
B.Text = strWName
B.Width = 400
B.Height = 150
B.Font = New System.Drawing.Font("Arial Narrow", 21.75)
B.AutoSizeMode = Windows.Forms.AutoSizeMode.GrowAndShrink
B.Anchor = AnchorStyles.Top
B.Margin = New Windows.Forms.Padding(0, 0, 0, 0)
'Updates Crucial Data (w name array, number of w buttons inc Create New)
If intNumW = 0 Then
ReDim arrWNames(0)
Else
intNumW = UBound(arrWNames) + 1
ReDim Preserve arrWNames(intNumW)
End If
arrWNames(intNumW) = strWShort
intNumW = intNumW + 1
intWButtons = WButtonCount(intWButtons) + 1
'updates form with new button and rearranges existing buttons
intCreateButtonY = btnCreateW.Location.Y
intCreateButtonX = btnCreateW.Location.X
‘intTimerTick = 0
tmrButtonMove.Enabled = True
‘Do While intTimerTick < 16
‘ 'blank to do nothing
‘Loop
'btnCreateW.Location = New Point(intCreateButtonX, intCreateButtonY + 150)
B.Location = New Point(intCreateButtonX, intCreateButtonY)
Me.Controls.Add(B)
B.Name = "btn" & strWShort
intCreateButtonY2 = btnCreateW.Location.Y
If intCreateButtonY2 > Me.Location.Y Then
Me.AutoScroll = False
Me.AutoScroll = True
Else
Me.AutoScroll = False
End If
'MsgBox(intCreateButtonY)
End Sub
Function WButtonCount(ByRef buttoncount As Integer) As Integer
buttoncount = intWButtons
If buttoncount = 0 Then
Return 1
End If
Return buttoncount
End Function
Public Sub tmrButtonMove_Tick(sender As System.Object, e As System.EventArgs) Handles tmrButtonMove.Tick
Dim intTimerTick As Integer
If intTimerTick > 14 Then
intTimerTick = 0
End If
If btnCreateW.Location.Y <= intCreateButtonY + 150 Then
btnCreateW.Top = btnCreateW.Top + 10
End If
intTimerTick += 1
If intTimerTick = 15 Then
tmrButtonMove.Enabled = False
End If
End Sub
End Class
So my current understanding is that the tick event handler should be increasing the timertick variable every time it fires, and that once it has hits 15 it should diable the timer and stop the button moving, but it is not doing so.
Thanks in advance.
IntTimerTick is initialized to 0 at the beginning of every Tick event. This won't happen if you declare it to be static:
Static Dim intTimerTick As Integer