How do I edit a listview subitem in report view?

How do I edit a listview subitem in report view?

The problem is that I have created a listview in report view mode that shows all sub items....

Here is the table create code:

        ListView1.Columns.Add("Item Number", 100, HorizontalAlignment.Left)
        ListView1.Columns.Add("Description", 170, HorizontalAlignment.Left)
        ListView1.Columns.Add("Quantity", 62, HorizontalAlignment.Right)
        ListView1.Columns.Add("Price", 72, HorizontalAlignment.Right)
        ListView1.Columns.Add("Extended", 80, HorizontalAlignment.Right)
        ListView1.Columns.Add("Tax", 36, HorizontalAlignment.Center)

Add Code to add in view detail
                    Dim LItem As New ListViewItem
                    LItem.Text = ("Col1")
                    LItem.SubItems.Add("Col2")
                    LItem.SubItems.Add("Col3")
                    LItem.SubItems.Add("Col4")
                    LItem.SubItems.Add("Col5")
                    LItem.SubItems.Add("Col6")

                    LItem.ImageIndex = 0
                    ListView1.Items.Add(LItem)
                   
Code to display New View
                ListView1.GridLines = True
                ListView1.View = View.Details

(Problem 1)    Only the primary item seems to accept a click, How do I tell which row/col my mouse has clicked on?
(Problem 2)    From what I have seen so far, it looks like I need to put a textbox/combobox over the row/col to 'fake' input into the grid...  If so, integrate (problem 1) to put a textbox over the row/col to create input ability.

Thanks,
John
LVL 3
jdraggiAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

jdraggiAuthor Commented:
Considering converting to a datagrid... any thoughts here?

--john
entrapnetCommented:
SanclerCommented:
The advantage of a datagrid over a listview is that the former is specifically designed to display data which can be EDITED whereas the latter is principally designed just for DISPLAY.  This is not to say that you cannot use either for the other principal purpose but, if you do that, you will have more coding to do.

For the particular cases in point here (a) identifying which "cell" has been clicked and (b) allowing editing of that "cell", the datagrid wins hands down.  If it's just text (string or number) that may be entered that can be done directly into the cell.  If you want to offer a choice - listbox or combobox - that can be integrated into the datagrid's column rather than having to fake it over a listview's sub-item.

As with all controls, it has its own properties, methods and events to master, but the help file topics are better on datagrid than on many other controls.

I would personally go down the DataGrid route.

Roger
Learn Ruby Fundamentals

This course will introduce you to Ruby, as well as teach you about classes, methods, variables, data structures, loops, enumerable methods, and finishing touches.

jdraggiAuthor Commented:
I agree with you both ... and acutally found that the listview worked really well for the stuff I was doing up to this point...  now I need to go back to the datagrid and have the following issues...

In the listview I was able to set the column size with this method:
listview1.Columns.Add("Item Number", 100, HorizontalAlignment.Left)

How do I convert that?

So far I have the data showing in the datagrid and will post additional questions in a little bit... I was going to review the link above for ideas from, entrapnet

Code to this point:
        Dim dt As New DataTable
        dt.Columns.Add("Item Number", GetType(String))
        dt.Columns.Add("Description", GetType(String))
        dt.Columns.Add("Quantity", GetType(Decimal))
        dt.Columns.Add("Price", GetType(Decimal))
        dt.Columns.Add("Extended", GetType(Decimal))
        dt.Columns.Add("Tax", GetType(String))

row(0) = myRead("Item_Number")
row(1) = myRead("Description")
... etc
dt.Rows.Add(row)
DataGrid1.DataSource = dt



Thanks,
John



jdraggiAuthor Commented:
ok, more problems...

How do I know what was just changed?

In my last method I used an update procedure like so:

Dim x As Integer = Listview1.FocusedItem.Index
Track_Sale_Qty(x) = TextBox4.Text
Listview1.FocusedItem.SubItems.Item(2).Text = Track_Sale_Qty(x)
Listview1.FocusedItem.SubItems.Item(4).Text = FormatCurrency(DataGrid1.FocusedItem.SubItems.Item(2).Text * Listview1.FocusedItem.SubItems.Item(3).Text, 2)

Next Question...
If the user deletes a row, how do I know which row was deleted?

Lastly, once they are all done with the edit, how do I take the resulting information out?  IE/ read row/col?

Thanks... very new to this datagrid,
John
entrapnetCommented:
you can use itemtemplate to work for the alignment if you wan... and it will do even greater stuff compare to listview
jdraggiAuthor Commented:
OK, need to solve the last questions:

1. How do I know what was just changed?
2. If the user deletes a row, how do I know which row was deleted?
3. how do I take the resulting information out?  IE/ read row/col?


ok, I was able to add column styles with this code, so that's done...

        Dim tbl As New DataGridTableStyle

        Dim grdColStyle1 As New DataGridTextBoxColumn
        With grdColStyle1
            .MappingName = "Item Number"
            .HeaderText = "Item Number"
            .Width = 80
        End With

        Dim grdColStyle2 As New DataGridTextBoxColumn
        With grdColStyle2
            .MappingName = "Description"
            .HeaderText = "Description"
            .Width = 175
        End With

        Dim grdColStyle3 As New DataGridTextBoxColumn
        With grdColStyle3
            .MappingName = "Quantity"
            .HeaderText = "Quantity"
            .Width = 70
        End With

        Dim grdColStyle4 As New DataGridTextBoxColumn
        With grdColStyle4
            .MappingName = "Price"
            .HeaderText = "Price"
            .Width = 70
        End With

        Dim grdColStyle5 As New DataGridTextBoxColumn
        With grdColStyle5
            .MappingName = "Extended"
            .HeaderText = "Extended"
            .Width = 70
        End With

        Dim grdColStyle6 As New DataGridTextBoxColumn
        With grdColStyle6
            .MappingName = "Tax"
            .HeaderText = "Tax"
            .Width = 40
        End With

        tbl.GridColumnStyles.AddRange(New DataGridColumnStyle() {grdColStyle1, grdColStyle2, grdColStyle3, grdColStyle4, grdColStyle5, grdColStyle6})
        DataGrid1.TableStyles.Add(tbl)
SanclerCommented:
The thing to remember when using a datagrid is that its purpose is to display, and allow editing of, data which really exists somewhere else.  Although changes made in the datagrid will (unless you program to the contrary, e.g. by disallowing particular entries in particular cells) be displayed in the datagrid they will not alter the underlying data until the program instructs them to do so.

The "somewhere else" is a datasource - usually a datatable or dataview - and the link between that and the datagrid is achieved by "binding".  If any change is made in the datagrid - a cell's contents are edited, or a row is added, or a row is deleted - the binding mechanism keeps a record of those changes and allows them to be inspected before they are implemented in the datasource.

Here's a small sample app (adapted from the Help file) to illustrate

Imports System.Data
Public Class FormExample
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    Friend WithEvents btnShowChanges As System.Windows.Forms.Button
    Friend WithEvents btnAcceptChanges As System.Windows.Forms.Button
    Friend WithEvents myDataGrid As System.Windows.Forms.DataGrid
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.myDataGrid = New System.Windows.Forms.DataGrid
        Me.btnShowChanges = New System.Windows.Forms.Button
        Me.btnAcceptChanges = New System.Windows.Forms.Button
        CType(Me.myDataGrid, System.ComponentModel.ISupportInitialize).BeginInit()
        Me.SuspendLayout()
        '
        'myDataGrid
        '
        Me.myDataGrid.DataMember = ""
        Me.myDataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText
        Me.myDataGrid.Location = New System.Drawing.Point(8, 8)
        Me.myDataGrid.Name = "myDataGrid"
        Me.myDataGrid.Size = New System.Drawing.Size(448, 240)
        Me.myDataGrid.TabIndex = 0
        '
        'btnShowChanges
        '
        Me.btnShowChanges.Location = New System.Drawing.Point(48, 264)
        Me.btnShowChanges.Name = "btnShowChanges"
        Me.btnShowChanges.Size = New System.Drawing.Size(112, 24)
        Me.btnShowChanges.TabIndex = 1
        Me.btnShowChanges.Text = "Show Changes"
        '
        'btnAcceptChanges
        '
        Me.btnAcceptChanges.Location = New System.Drawing.Point(304, 264)
        Me.btnAcceptChanges.Name = "btnAcceptChanges"
        Me.btnAcceptChanges.Size = New System.Drawing.Size(104, 24)
        Me.btnAcceptChanges.TabIndex = 2
        Me.btnAcceptChanges.Text = "Accept Changes"
        '
        'FormExample
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(464, 294)
        Me.Controls.Add(Me.btnAcceptChanges)
        Me.Controls.Add(Me.btnShowChanges)
        Me.Controls.Add(Me.myDataGrid)
        Me.Name = "FormExample"
        Me.Text = "FormExample"
        CType(Me.myDataGrid, System.ComponentModel.ISupportInitialize).EndInit()
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private Function GetTheTable() As DataTable
        Dim dt As New DataTable("MyTable")
        Dim nCols As Integer = 4
        Dim nRows As Integer = 10
        Dim i As Integer
        For i = 0 To nCols - 1
            dt.Columns.Add(New DataColumn(String.Format("Col{0}", i)))
        Next i
        For i = 0 To nRows
            Dim dr As DataRow = dt.NewRow()
            Dim j As Integer
            For j = 0 To nCols - 1
                dr(j) = String.Format("row{0} col{1}", i, j)
            Next j
            dt.Rows.Add(dr)
        Next
        dt.AcceptChanges()
        Return dt
    End Function

    Private Sub PrintView(ByVal dv As DataView, ByVal label As String)
        Dim i As Integer
        Dim c As Integer
        For i = 0 To dv.Count - 1
            For c = 0 To dv.Table.Columns.Count - 1
                Console.Write(dv(i)(c) & " ")
            Next
        Next i
        Console.WriteLine("was " & label)
    End Sub

    Private Sub FormExample_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim dt As DataTable = Me.GetTheTable()
        myDataGrid.DataSource = dt
    End Sub

    Private Sub btnShowChanges_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnShowChanges.Click
        Dim myTable As DataTable = myDataGrid.DataSource
        Dim myView As New DataView(myTable)
        myView.RowStateFilter = DataViewRowState.ModifiedCurrent
        PrintView(myView, "Modified")
        myView.RowStateFilter = DataViewRowState.Added
        PrintView(myView, "Added")
        myView.RowStateFilter = DataViewRowState.Deleted
        PrintView(myView, "Deleted")
    End Sub

    Private Sub btnAcceptChanges_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAcceptChanges.Click
        Dim myTable As DataTable = myDataGrid.DataSource
        myTable.AcceptChanges()
    End Sub
End Class

It generates an example table and displays it in the datagrid.  If you make changes to the datagrid, and then click on the Show Changes button, it will print out a list of those changes.  When, and only when, you click the Accept Changes button will it implement the changes in the underlying table.  If you do that and then immediately click the Show Changes button, there will be none because the table and the datagrid are now "in synch" again.

There are other ways of answering your questions, but this is a common approach (although obviously with something other than just printing out the results being done).

Roger
jdraggiAuthor Commented:
From above, what you are saying is that all I can get is difference from what is already in the non-accepted/updated datatable before it is accepted?  Can I get a row/col that was changed other than the returned difference from it or does my entire method have to change based on this?

That doesn't seem right, I must be missing something.

--John
SanclerCommented:
John

No.  As I said, there are other ways.

If you want to react to things as they occur you can use .CurrentCellChanged, or .Click with HitTest.  Either will give you the Row and Column.

Roger
jdraggiAuthor Commented:
can you give me an example of
.CurrentCellChanged

jdraggiAuthor Commented:
ok, fustration is starting to set in...  ha

that gives you the spot that you click into or move the mouse button to but does not give you the row/cell that you just left...

At this point I am considering going back to a list view and just putting 6 of them side-by-side...  The biggest problem that I have right now is that I have to get this done today and this datagrid seemed like a good idea at first but is starting to get very complicated, very quick.

Any ideas on other methods?  Basically what I am trying to make is a point-of-sale screen where the user can change the quantity...  I have the items being added through another screen which is constructing a master GUID table which is related to a quantity table.

Everything else is done except this hang-up.  :)

--John
SanclerCommented:
John

Sorry, I am out and about a lot and only check in to EE sporadically.  I imagine any comments I might now make will be too late.

In the datagrid you can check where you've been, rather than where you are now, by having static variables - say previousRow, previousCol - in the sub for the event you are using to monitor and setting those when you leave that sub.  Then next time you re-enter the sub you can read them to find where you were on the last visit to it.

As for alternatives, one idea might be a usercontrol with textboxes, listboxes, comboboxes, whatever side by side: replicating a "row".  You could then build up a "grid" by adding such controls, one under the other, as "rows" are needed.  Here's a link to a recent demo of that idea.

Roger
jdraggiAuthor Commented:

Thanks, I appreciate it... I had to put the peddle to the metal though so I just hammered out the code that I knew how to do...  It kinda sucked because I was not really sure how to get the box coordinates so I just constructed it from bits :)

ha.. anyway it's working although I would like to have optimized it a little...


This overlays the textbox:
    Private Sub ListView3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListView3.Click
        'QTY mod
        Dim x As Integer = (Math.Floor(ListView3.PointToClient(Control.MousePosition).Y / ListView3.GetItemRect(0).Height))
        If Not x <= Track_Sales_order Then
            Exit Sub
        End If

        TextBox5.Width = ListView3.GetItemRect(0).Width
        TextBox5.Height = ListView3.GetItemRect(0).Height
        TextBox5.Font = ListView3.Font
        listViewRow = ListView3.SelectedItems.Item(0).Index
        TextBox5.Text = Track_Sale_Qty(listViewRow)
        TextBox5.Location = New Point(272, (77 + ListView3.GetItemRect(0).Height * x))
        TextBox5.Visible = True
        TextBox5.SelectAll()
    End Sub

This monitors keypress:

    Private Sub TextBox5_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox5.KeyPress
        'declare the c as character
        Dim c As Char
        'taking currently pressed character in c
        c = e.KeyChar
        'checking if the character is digit or control character else will be rejected
        If Not (Char.IsDigit(c) Or Char.IsControl(c) Or c = ".") Then
            e.Handled = True
        End If
        If c = Microsoft.VisualBasic.vbCr Then
            TextBox5.Visible = False
            ListView3.Items.Item(listViewRow).Text = TextBox5.Text
            Track_Sale_Qty(listViewRow) = CDec(TextBox5.Text)

            ListView5.Items.Item(listViewRow).Text = Track_Sale_Qty(listViewRow) * CDec(ListView4.Items.Item(listViewRow).Text)
        End If
        Calc_Total()
    End Sub

Do you know any better way to get the coordinates than to use a floor method and move it down by a height factor as such:
Dim x As Integer =(Math.Floor(ListView3.PointToClient(Control.MousePosition).Y / ListView3.GetItemRect(0).Height))
TextBox5.Location = New Point(272, (77 + ListView3.GetItemRect(0).Height * x))
       
I would like to get away from having to hard code the distance factors as I did above.

Thanks,
john


SanclerCommented:
John

I haven't tried this for your purpose.  I've just downloaded the demo from

http://www.syncfusion.com/FAQ/WindowsForms/FAQ_c90c.aspx#q1006q

and extracted this code

    <StructLayout(LayoutKind.Sequential)> _
    Structure RECT
        Public left As Integer
        Public top As Integer
        Public right As Integer
        Public bottom As Integer
    End Structure 'RECT

    <DllImport("user32.dll")> _
    Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByRef lParam As RECT) As Integer
    End Function


    Public Function GetListViewSubItem(ByVal listView1 As ListView, ByVal pt As Point) As Integer

        Const LVM_FIRST As Integer = &H1000
        Const LVM_GETSUBITEMRECT As Integer = LVM_FIRST + 56
        Const LVIR_BOUNDS As Integer = 0

        Dim myrect As RECT
        Dim lvitem As ListViewItem = listView1.GetItemAt(pt.X, pt.Y)
        If lvitem Is Nothing AndAlso listView1.SelectedItems.Count > 0 Then
            lvitem = listView1.SelectedItems(0)
        End If
        Dim intLVSubItemIndex As Integer = -1
        Dim LVSubItem As ListViewItem.ListViewSubItem = Nothing

        If Not (lvitem Is Nothing) Then
            Dim intSendMessage As Integer
            Dim i As Integer
            For i = 1 To lvitem.SubItems.Count - 1
                LVSubItem = lvitem.SubItems(i)
                myrect = New RECT()
                myrect.top = i
                myrect.left = LVIR_BOUNDS
                intSendMessage = SendMessage(listView1.Handle, LVM_GETSUBITEMRECT, lvitem.Index, myrect)
                If pt.X < myrect.left Then
                    LVSubItem = lvitem.SubItems(0)
                    intLVSubItemIndex = 0
                    Exit For
                ElseIf pt.X >= myrect.left And pt.X <= myrect.right Then
                    intLVSubItemIndex = i
                    Exit For
                Else
                    LVSubItem = Nothing
                End If
            Next i
        End If
        If LVSubItem Is Nothing OrElse lvitem Is Nothing Then
            intLVSubItemIndex = -1
        End If
        Return intLVSubItemIndex
    End Function 'GetListViewSubItem

It tests OK for its declared purpose, which is not quite yours, but I reckon it should be relatively easy to adapt it for you to get the precise sub-item rectangle you are aiming to cover with the floating textbox.

Roger

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
SanclerCommented:
PS

... and then all you'll have to do is adjust by your listview's top and left.
planoczCommented:
If you still want to use a datgrid...

Check this link on an app I was working on....

http://www.experts-exchange.com/Programming/Programming_Languages/Dot_Net/VB_DOT_NET/Q_21284443.html
jdraggiAuthor Commented:
Sancler, should have time to test tomorrow... Thanks for your post, that should do it!

Get back with you tomorrow,
John
jdraggiAuthor Commented:
OK, well... for now I'm happy...  here's the updated code.. the link you sent allowed me to change the .click item to a .mouse and use the e.X and e.Y return to allow a user to click anywhere inside of the listview col.  Thus this makes it more like a 'real' app and I'm happy...

The code will still need more work to allow handling of scrolling because they had too many items however that shouldn't be too hard now that this is working.  I'll also probably go back to the single listview but for now I'm using 6 listview objects side-by-side.

Here is the final code:

    Dim listViewRow As Integer
    Private Sub listView3_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles ListView3.MouseUp
        'QTY mod
        Dim x As Integer = (Math.Floor(e.Y / ListView3.GetItemRect(0).Height)) - 1
        If Not x < Track_Sales_order Then
            Exit Sub
        End If
        MsgBox(ListView3.GetItemRect(0).Height)
        TextBox5.Width = ListView3.GetItemRect(0).Width
        TextBox5.Height = ListView3.GetItemRect(0).Height
        TextBox5.Font = ListView3.Font
        listViewRow = x
        TextBox5.Text = Track_Sale_Qty(listViewRow)
        ' 19 = the current Y offset value
        TextBox5.Location = New Point(ListView3.Location.X, (ListView3.Location.Y + 19 + ListView3.GetItemRect(0).Height * x))
        TextBox5.Visible = True
        TextBox5.SelectAll()
    End Sub


This is the error handler for the textbox overlay:

    Private Sub TextBox5_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox5.KeyPress
        'declare the c as character
        Dim c As Char
        'taking currently pressed character in c
        c = e.KeyChar
        'checking if the character is digit or control character else will be rejected
        If Not (Char.IsDigit(c) Or Char.IsControl(c) Or c = ".") Then
            e.Handled = True
        End If
        If c = Microsoft.VisualBasic.vbCr Or c = Microsoft.VisualBasic.vbTab Then
            TextBox5.Visible = False
            ListView3.Items.Item(listViewRow).Text = TextBox5.Text
            Track_Sale_Qty(listViewRow) = CDec(TextBox5.Text)

            ListView5.Items.Item(listViewRow).Text = Track_Sale_Qty(listViewRow) * CDec(ListView4.Items.Item(listViewRow).Text)
        End If
        Calc_Total()
    End Sub

Thanks for all your help!
--John

It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic.NET

From novice to tech pro — start learning today.