UI Automation - Retrieving data from a SysListView32 (or any Listview) - vb.net

I'm trying to retrieve data from a SysListView32 object with the code below but it's returning an empty string.
The elements that I need to retrieve are those highlighted in red, as well as the others contained in the other ControlType.ListItem elements, according to the Inspector's print.
Can some one check what's wrong with my code?
Msgbox("Position the mouse cursor on the screen and press ENTER.")
Dim pt As POINTAPI
GetCursorPos(pt)
Dim hwnd As IntPtr = WindowFromPoint(pt)
Dim hwnd As IntPtr = 67202
Dim el As AutomationElement = AutomationElement.FromHandle(hwnd)
Dim walker As TreeWalker = TreeWalker.ContentViewWalker
Dim i As Integer = 0
Dim child As AutomationElement = walker.GetFirstChild(el)
While child IsNot Nothing
'
Dim k As Integer = 0
Dim child2 As AutomationElement = walker.GetFirstChild(child)
While child2 IsNot Nothing
Console.WriteLine(child2.Current.ToString)
child2 = walker.GetNextSibling(child2)
End While
child = walker.GetNextSibling(child)
End While

The SysListView32 may not provide the information requested if its current view state is not LV_VIEW_DETAILS, so we should temporarily (if the current view state is different), use the MultipleViewPattern of its AutomationElement to verify the view state and change it if necessary using the MultipleViewPattern.SetCurrentView() method.
The SetCurrentView() method uses the same values of the Win32 Control.
Then use the AutomationElement FindAll() method of the to find all child elements of type ControlType.DataItem or ControlType.ListItem (using an OrCondition).
For each of them, get all child items of type ControlType.Edit and ControlType.Text (using another OrCondition).
The position of each Item in the list is retrieved using the Item's GridItemPattern, to access the Item's Row property.
Finally, we restore the previous View State if we had to change it.
The code in the example fills a Dictionary(Of Integer, ListViewItem) (named sysListViewItems here), containing all the Items extracted from the SysListView32.
The Integer Key represents the position of an Item in the original ListView
The ListViewItem is a .Net object generated from the array of values (as strings) extracted from each item.
If you don't need ListViewItem objects, you can just store the List(Of String), represented by the itemsText object, instead of creating a ListViewItem here:
sysListViewItems.Add(gridPattern.Current.Row, New ListViewItem(itemsText.ToArray())).
The Handle of a SysListView32 can also be acquired enumerating the children of its Top Level Window by ClassName.
AutomationElement.RootElement provides the current Desktop Element:
Dim parentWindow = AutomationElement.RootElement.FindFirst(
TreeScope.Children, New PropertyCondition(AutomationElement.NameProperty, "[Window Caption]"))
Dim sysListView32 = parentWindow.FindAll(
TreeScope.Subtree, New PropertyCondition(AutomationElement.ClassNameProperty, "SysListView32"))
If more than one SysListView32 is found, filter by Header content, direct parent ControlType or ClassName or anything else that allows to single it out.
UI Automation requires a reference to the UIAutomationClient and UIAutomationTypes assemblies.
Imports System.Windows.Automation
' Find the ListView Handle as described or the ListView UI Element directly
Dim sysListViewHandle = [GetSysListView32Handle()]
Dim sysListViewElement = AutomationElement.FromHandle(sysListViewHandle)
If sysListViewElement Is Nothing Then Return
Dim sysListViewItems = New Dictionary(Of Integer, ListViewItem)()
Dim mulView As MultipleViewPattern = Nothing
Dim pattern As Object = Nothing
Dim currentView As Integer = -1
If sysListViewElement.TryGetCurrentPattern(MultipleViewPattern.Pattern, pattern) Then
mulView = DirectCast(pattern, MultipleViewPattern)
currentView = mulView.Current.CurrentView
If currentView <> ListViewWState.LV_VIEW_DETAILS Then
mulView.SetCurrentView(ListViewWState.LV_VIEW_DETAILS)
End If
End If
Dim childItemsCondition = New OrCondition(
New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataItem),
New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem))
Dim childItems = sysListViewElement.FindAll(TreeScope.Children, childItemsCondition)
If childItems.Count = 0 Then Return
Dim subItemsCondition = New OrCondition(
New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit),
New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Text))
For Each item As AutomationElement In childItems
Dim itemsText = New List(Of String)()
Dim subItems = item.FindAll(TreeScope.Children, subItemsCondition)
For Each subItem As AutomationElement In subItems
itemsText.Add(subItem.Current.Name)
Next
Dim gridPattern = DirectCast(subItems(0).GetCurrentPattern(GridItemPattern.Pattern), GridItemPattern)
sysListViewItems.Add(gridPattern.Current.Row, New ListViewItem(itemsText.ToArray()))
Next
If mulView IsNot Nothing Then
mulView.SetCurrentView(currentView)
End If
Friend Enum ListViewWState
LV_VIEW_ICON = &H0
LV_VIEW_DETAILS = &H1
LV_VIEW_SMALLICON = &H2
LV_VIEW_LIST = &H3
LV_VIEW_TILE = &H4
End Enum

Related

Duplicating a TextBox in VB.Net

I was wondering how I would duplicate a TextBox, not just the text inside of it.
TextBox.copy copies the text selected inside the TextBox, which is not quite what I need.
I am using vb.net.
Please try this function :)
Private Function CopyControl(ByVal obj As Object, Optional ByVal locationX As Integer = 0, Optional ByVal locationY As Integer = 0) As Object
Dim objnew As Object = Activator.CreateInstance(obj.GetType()) 'Create new control
Dim oldprops As PropertyDescriptorCollection = TypeDescriptor.GetProperties(obj) 'Control properties
Dim newprops As PropertyDescriptorCollection = TypeDescriptor.GetProperties(objnew) 'New control properties
For i As Integer = 0 To oldprops.Count - 1 '
newprops(i).SetValue(objnew, oldprops(i).GetValue(obj)) 'New control properties = Old control properties
Next
objnew.location = New Point(locationX, locationY) 'Set location
Return objnew 'New control is copied
'Im sorry my english is bad, I hope you understand..
End Function
Me.Controls.Add(CopyControl(TextBox1))
I'm sorry for my bad english. I'm turkish :D

Get RepositoryItem (As Control) From Devexpress-GridviewCell

I'm using the devexpress Gridcontrol/Gridview which has 4 columns
Name: String
Description: String
Action: RepositoryItemLookUpEdit
Info: RepositoryItemHyperLinkEdit
Right now i want to write a function which updates the Action-column but only if the Value is contained in the datasource of the RepositoryItemLookUpEdit
So i started writing the code and this is how i far i got:
For i As Integer = 0 To GridViewDD.RowCount - 1
Dim j As Integer = i
Dim rItemlookup As RepositoryItemLookUpEdit = CType(GridViewDD.GetRow(i), DataRowView).Item("Actions")
If CType(rItemlookup.DataSource, List(Of String)).Contains(curraction) Then
// Do update of the datasource here (which works)
End If
Next
GridControlDD.RefreshDataSource()
My problem lies at the line:
Dim rItemlookup As RepositoryItemLookUpEdit = CType(GridViewDD.GetRow(i), DataRowView).Item("Actions")
Question:
How can i get the RepositoryItemLookUpEdit of a cell in devexpress (or its datasource)?
Note:
The datasource of my gridview (GridViewDD) is a List And my datasource of the RepositoryItemLookUpEdit in Action is always a List(Of String)
Note 2:
The contents of my datasource may vary from row to row
You can easily get your RepositoryItem from GridColumn.ColumnEdit property.
Here is example:
Dim rItemlookup As RepositoryItemLookUpEdit = GridViewDD.Columns("Action").ColumnEdit
'...
For i As Integer = 0 To GridViewDD.RowCount - 1
Dim j As Integer = i
If CType(rItemlookup.DataSource, List(Of String)).Contains(curraction) Then
'... Do update of the datasource here (which works)
End If
Next
GridControlDD.RefreshDataSource()
I've found it. thanks to 'many' hours of browsing the devexpress-forums. Using Gridviewinfo and GridDataRowInfo you can easily access the controls 'hidden' inside the grid
In my case the code looks as following
Dim gvInfo As GridViewInfo = GridViewDD.GetViewInfo()
Dim rInfo As GridDataRowInfo = gvInfo.RowsInfo.FindRow(i)
Dim rItemlookup As RepositoryItemLookUpEdit = rInfo.Cells(GridViewDragDrop.Columns.Item("Actions")).Editor
you can now use rItemlookup to change or access its properties.
I hope this may be of some use to anyone.

Can I create a property of a property?

So I recently grasped the concept of using classes in my Visual Basic programming, and I found it tremendously helpful. In my current project, I have several groups boxes of check boxes (each check box denotes a "Behavior") and in each group box, there is always one check box that has a textbox control instead of a label (to allow the user to specify an "Other" behavior). It is that user-generated label that is giving me trouble...
I created a class called "Behaviors" that basically does the following:
getChecked > This method gets each checked checkbox and adds it to
the BehaviorCollection for a given Form.
behaviorCollection > represents the collection of checked
checkboxes.
getOtherChecked > does the same as "getChecked" except with the
"Other Behavior" checkboxes.
otherBehaviorCollection > represents the collection of checked
"Other" checkboxes.
The issue is that for each checked "Other Behaviors" checkbox, I need to store the value of its corresponding textbox. I would like to set my getOtherChecked() method to do this, so that in the end, I would be able to something like this...
Dim myBoxes as new Behaviors
Dim cBox as Checkbox
Dim cBoxLabel as String
myBoxes.getOtherChecked(myUserForm) 'This would get each checked "Other Behaviors" checkbox object, and also somehow add another property to it called "LinkedTextboxLabel" that would be assigned the value of the corresponding textbox.
cBox = myBoxes.otherBehaviorCollection.item(0) 'Assign a checkbox from my "Other Behaviors" collection to a variable.
cBoxLabel = cBox.LinkedTextboxLabel 'Assign the user-inputted value of the linked textbox to a variable.
So basically how could/should I add a custom-property to a collection item or checkbox?
I thought about just adding the names of the controls to a temporary DataTable or SQL table, so that each row would have the name of a checkbox in one column and its corresponding textbox value in the next, but I am hoping there is a more commonly used and accepted method.
Thank you in advance!
You could add a property for the text associated with the "Other Behaviors" checkbox.
EDIT: You might be trying to generalize your data too far, because the "Other behaviors" is a special case and deserves separate consideration.
If you have a look at what the following code (in a new Windows Forms project) creates, it might give you ideas:
Public Class Form1
''' <summary>
''' A behaviour domain and its characteristics, with one user-defined entry.
''' </summary>
''' <remarks></remarks>
Public Class BehavioursSectionDescriptor
Property BehaviourTypeName As String
Property BehaviourNames As List(Of String)
Property CustomBehaviours As String
End Class
''' <summary>
''' Return a GroupBox containing CheckBoxes and one Checkbox with a TextBox adjacent to it.
''' </summary>
''' <param name="behaviourSet"></param>
''' <returns></returns>
''' <remarks></remarks>
Private Function GetBehaviourGroupPanel(behaviourSet As BehavioursSectionDescriptor) As GroupBox
Dim gb As New GroupBox
gb.Text = behaviourSet.BehaviourTypeName
Dim fixedBehaviourNames As List(Of String) = behaviourSet.BehaviourNames
Dim customBehavioursValue As String = behaviourSet.CustomBehaviours
Dim cbVertSeparation As Integer = 4
Dim gbPadding As Integer = 20
Dim cb As New CheckBox
Dim yLoc As Integer = gbPadding
For i = 0 To fixedBehaviourNames.Count - 1
cb = New CheckBox
cb.Location = New Point(gbPadding, yLoc)
cb.Text = fixedBehaviourNames(i)
' you can use the .Tag Object of a Control to store information
cb.Tag = behaviourSet.BehaviourTypeName & "-Cb-" & i.ToString()
gb.Controls.Add(cb)
yLoc += cb.Height + cbVertSeparation
Next
cb = New CheckBox
cb.Text = ""
cb.Location = New Point(gbPadding, yLoc)
cb.Tag = behaviourSet.BehaviourTypeName & "-Custom behaviours"
gb.Controls.Add(cb)
Dim tb As New TextBox
tb.Location = New Point(gbPadding + 18, yLoc)
tb.Width = 100
tb.Text = customBehavioursValue
gb.Controls.Add(tb)
' make sure the textbox appears in front of the checkbox's label area
tb.BringToFront()
gb.Size = New Size(160, yLoc + gbPadding * 2)
Return gb
End Function
Private Function GetTestData() As List(Of BehavioursSectionDescriptor)
Dim bsds = New List(Of BehavioursSectionDescriptor)
bsds.Add(New BehavioursSectionDescriptor With {.BehaviourTypeName = "In water", _
.BehaviourNames = New List(Of String) From {"Floats", "Spins"}, _
.CustomBehaviours = "Paddles"})
bsds.Add(New BehavioursSectionDescriptor With {.BehaviourTypeName = "Under light", _
.BehaviourNames = New List(Of String) From {"Shines", "Glows", "Reflects"}, _
.CustomBehaviours = "Iridesces"})
bsds.Add(New BehavioursSectionDescriptor With {.BehaviourTypeName = "Near food", _
.BehaviourNames = New List(Of String) From {"Sniffs", "Looks"}, _
.CustomBehaviours = ""})
Return bsds
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim bsds As List(Of BehavioursSectionDescriptor) = GetTestData()
Dim gbs As New List(Of GroupBox)
Dim xLoc As Integer = 20
Dim yLoc As Integer = 20
' make some GroupBoxes to present the data input fields
For i = 0 To bsds.Count - 1
Dim gb = GetBehaviourGroupPanel(bsds(i))
gb.Location = New Point(xLoc, yLoc)
gb.Dock = DockStyle.None
yLoc += gb.Height + 30
Me.Controls.Add(gb)
Next
' size the form to fit the content
Me.Size = New Size(240, yLoc + 40)
End Sub
End Class
I know it doesn't answer the question of adding a property to a property, but could you create a class for the Other checkbox and override it's capabilities? Then you could add checkboxes and OtherCheckBoxes to your generic collection? for instance, (by no means complete, but you should get the idea)
EDIT: Changed code to show Shadows
Public Class OptionalCheckbox : Inherits CheckBox
Private mOptionalText As String
Public Shadows Property Text() As String
Get
Return mOptionalText
End Get
Set(value As String)
mOptionalText = value
MyBase.Text = value
End Set
End Property
End Class
For each item, if you were to retrieve .Text, you would either get your textbox value or your checkbox label (if it was a normal checkbox)
And how to utilize in other parts of your code. Again, this is just more of an example. You would still need to work with the textbox that is assigned to the OtherCheckBox to get it to write the text to that, as well as read from that into the .Text property of the Class.
Dim newCheckBoxCollection As New Collection
Dim cBox As New CheckBox
cBox.Text = "Standard Value Here"
'other properties of the checkbox can be modified here
newCheckBoxCollection.Add(cBox)
Dim cOBox As New OptionalCheckbox
cOBox.Text = "Optional Text Here"
'other properties of the checkbox can be modified here
newCheckBoxCollection.Add(cOBox)
For Each cb As CheckBox In newCheckBoxCollection
Me.FlowLayoutPanel1.Controls.Add(cb)
Next
If you are trying to just save the data into something like a DataTable or SQL table the code would be a bit of an overkill. I suggest you use a stream reader/writer and try checking the values that way as the code would be a lot more simple.

how do i initialize my arraylist

I have a function that adds items to my arraylist. my problem is that it only holds one item at a time since it is reinitializing the array lit every time I click my button. what is the syntax in VB to only initialize the array if it has not been created yet?
Dim itemSelectAs New ArrayList()
Dim Quantities As New ArrayList()
Dim itemQtyOrdered As Integer
Public Sub ShtickDataList_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.ListViewCommandEventArgs) Handles ShtickDataList.ItemCommand
If e.CommandName = "addToCart" Then
Dim itemQuantity As DropDownList = e.Item.FindControl("QuantityDropDown")
itemQtyOrdered = itemQuantity.SelectedValue
ItemSelect.Add(e.CommandArgument)
Quantities.Add(itemQtyOrdered)
Session("itemInCart") = ItemSelect
Session("quantities") = Quantities
viewInvoice()
End If
End Sub
Protected Sub viewInvoice()
Dim itemSelected As ArrayList = DirectCast(Session("itemInCart"), ArrayList)
Dim QuantityofItem As ArrayList = DirectCast(Session("quantities"), ArrayList)
Dim conn As SqlConnection
Dim comm As SqlCommand
Dim reader As SqlDataReader
Dim purimConnection2 As String = ConfigurationManager.ConnectionStrings("Purim").ConnectionString
conn = New SqlConnection(purimConnection2)
comm = New SqlCommand("SELECT ProductName FROM Products WHERE ProductID = #ProductID", conn)
Dim i As Integer
For i = 0 To ItemSelect.Count - 1
comm.Parameters.Add("#ProductID", Data.SqlDbType.Int)
comm.Parameters("#ProductID").Value = (ItemSelected.Count - 1)
'Next
Try
conn.Open()
reader = comm.ExecuteReader()
ViewCartlink.Text = "View Cart: (" & ItemSelected.Count & ")"
Finally
conn.Close()
End Try
End Sub
First you need to dimension your array list.
Dim array_list as ArrayList()
Then you can instantiate one
array_list = new ArrayList
Or you can combine it into one step:
Dim array_list = new ArrayList()
After that you can add and remove elements from your array list with
array_list.add(obj)
and remove with
array_list.remove(obj)
It looks like your problem is related to accessing the members of an arraylist. New items are always added to the end of an arraylist. To access them directly, you will need their index. If you know the index of the item you want to access use
array_list(i)
If you don't you will need to iterate over the array. To do this you have two options. You can use "for each" or you can use a normal for loop and use array_list.count as your upper bound.
You're recreating your two session values every time you call your button click menu. You need to pull them out of the Session variable and put them in local variables and put them back into the session variable.
Your button method should be:
Public Sub ShtickDataList_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.ListViewCommandEventArgs) Handles ShtickDataList.ItemCommand
if isNothing(itemSelect) Then itemSelect = New ArrayList()
if isNothing(itemQtyOrdered) Then itemQtyOrdered= New ArrayList()
If e.CommandName = "addToCart" Then
Dim itemQuantity As DropDownList = e.Item.FindControl("QuantityDropDown")
itemQtyOrdered = itemQuantity.SelectedValue
ItemSelect.Add(e.CommandArgument)
Quantities.Add(itemQtyOrdered)
Session("itemInCart") = ItemSelect
Session("quantities") = Quantities
viewInvoice()
End If
End Sub
And change your Global calls to:
Dim itemSelect As ArrayList() = Session("itemInCart")
Dim Quantities As New ArrayList() = Session("quantities")
Define your array outside the button click event. (Form level)
Then in the button click event try this:
If myArrayList Is Nothing then
'initializes the array list only if that hasn't happened yet
myArrayList = new ArrayList
End If
'adds the item to the existing list without causing it to reintialize
myArrayList.add(item)
That way it is initialized if it hasn't been, but not if it already has. If it is initialized at the form level ie... its declared as new already then you can just add to it.
Basically make sure that you aren't calling New for the arrayList in the button click event.
Editing for web form:
You should probably check where you initialize your arrayList. Like in the Page_Load:
If Not Page.IsPostBack Then
myArrayList = New ArrayList
End If
MSDN Postback

Referencing an Unbound DataGridView Without Specifically Naming It?

I am using 3 unbound DataGridView controls to display certain information. To load the information into those DGVs, I am pulling the information from an encrypted file, decrypting it, parsing the information, then trying to fill the DGVs with that information. The loading from the file is called by the menu item click. Here is what I have so far:
Private Sub miCLoad_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles miCLoad.Click
Dim FilePath As String = "C:\FList\CList.clt"
Dim LoadFile As New SaveandLoad.SaveAndLoad
Dim FileRead As New Simple3Des("MyPassword")
Dim FileString As String = FileRead.ReadFile(FilePath)
With LoadFile
.WhichList = dgCourses
.FilePath = FilePath
.DecryptedString = FileRead.DecryptData(FileString)
.dgList = dgCourses
End With
Call LoadFile.LoadFile()
End Sub
Public Class SaveandLoad
Public Property WhichList As New DataGridView
Public Property FilePath As String
Public Property DecryptedString As String
Public Property EncryptedString As String
Public Property dgList As Control
Public Sub LoadFile()
Dim dgRow As DataGridViewRow
Dim dgCell As DataGridViewTextBoxCell
Dim Lines() As String = DecryptedString.Split(vbLf)
Dim LinesList As List(Of String) = Lines.ToList
LinesList.RemoveAt(Lines.Length - 1)
For Each Line As String In LinesList
Dim Fields() As String = Line.Split(",")
dgRow = New DataGridViewRow
For x = 0 To (WhichList.Columns.Count - 1) Step 1
dgCell = New DataGridViewTextBoxCell
dgCell.Value = Fields(x).ToString
dgRow.Cells.Add(dgCell)
Next
WhichList.Rows.Add(dgRow)
Next
Select Case WhichList.Name
Case "dgCourses"
frmFacultyList.dgCourses = WhichList
frmFacultyList.dgCourses.Refresh()
WhichList.Dispose()
Case "dgFList"
frmFacultyList.dgFList = WhichList
frmFacultyList.dgFList.Refresh()
WhichList.Dispose()
Case "dgSList"
frmFacultyList.dgSList = WhichList
frmFacultyList.dgSList.Refresh()
WhichList.Dispose()
End Select
MsgBox("List Successfully Loaded", vbOKOnly, "Load")
End Sub
I want to be able to reference (or fill) a DGV without using 'select case' or 'if-then' statements. This will be too inefficient once I start adding the many other DGVs, that will be added in the future. Therefore, the title is the main question. I am using VS Express 2010.
I don't know VB too much, however, I'll post my solution in C# (may be helpfull in some way....)
DataGridView myDGV;
foreach (var item in this.Controls)
{
if (item.GetType() == typeof(DataGridView))
{
if (((DataGridView)item).Name == WhichList.Name)
{
//Cannot assing to 'item' here, because it is a 'foreach iteration variable'
//However you can save the variable for later use.
myDGV = (DataGridView)item;
}
}
}
myDGV = WhichList;
// different approach
DataGridView myDGV = (DataGridView)this.Controls.Find(WhichList.Name, false).First();
myDGV = WhichList;
Here is what worked for me in VB.NET:
Dim FormControls As New frmFacultyList.ControlCollection(frmFacultyList)
For Each DGV As DataGridView In FormControls
If WhichList.Name = DGV.Name Then
DGV = WhichList
DGV.Refresh()
End If
Next
Make an instance of the control collection then search specifically for DGVs using For Each. Simple and efficient.