Nodes with variables - vb.net

I'm not a experience vb.net programmer so I'm struggling with some code related to treeview node implementation:
My goal is to implement a hierarchical and editable structure related to a "Bill of Materials" (e.g. a house has a foundation, walls, roof,.. and walls has stones, plaster,....)
The number of levels of this hierarchical structure is (per definition) unknown.
I'm able to populate a treeview(1) with SQL data to multiple but a fixed number of levels.
The code I've implemented:
TreeView1.Nodes.Add(var01, var01).Nodes.Add(var02, var02) (example 2 levels)
where var01 and var02 the names within the structure are (e.g. House-Wall). The Node structure is build using a "for - next loop"
Officious by adding ".Nodes.Add(varXX, varXX)" I'm able to extend the level of the structure.
My goal however is to implement "the adding of .Nodes.Add(varXX, varXX)" through a loop make the number of hierarchical levels flexible.
I tried to convert Treeview1.Nodes... to a string and build a (overall) string through a loop. Then I tried to convert this string to the treeview control. This principle doesn't unfortunately work.
Any advice would be appreciated.

Try something like this:
Public Function GetBOMTree() As TreeNode
Dim BOMTable As DataTable
' Assuming your hierarchy is small enough to do a full table load into BOMTable, put your SQL statements here
Dim BOMDictionary = BOMTable.Rows.Cast(Of DataRow) _ ' Assumed to be a data table
.Where(Function(F) F.Item("ParentKey").GetType IsNot GetType(DBNull)) _
.GroupBy(Function(F) CInt(F.Item("ParentKey"))) _ ' Assuming integer keys
.ToDictionary(Function(F) F.Key)
Dim Root = BOMTable.Rows.Cast(Of DataRow) _
.Where(Function(F) F.Item("ParentKey").GetType Is GetType(DBNull)) _
.FirstOrDefault
If Root IsNot Nothing Then
Dim GetTree As Func(Of DataRow, TreeNode) = ' The "as Func" is necessary to allow recursion
Function(D As DataRow) As TreeNode
Dim Result As New TreeNode
Dim Key = CInt(D.Item("PrimaryKey"))
Result.Tag = Key
Result.Text = CStr(D.Item("Description"))
If BOMDictionary.ContainsKey(Key) Then
Dim Children = BOMDictionary.Item(Key)
For Each Child In Children.OrderBy(function(F) cstr(f.item("Description")))
Result.Nodes.Add(GetTree(Child))
Next
End If
Return Result
End Function
Return GetTree(Root)
Else
Return Nothing
End If
End Function
The way this works is using a little recursion, and some LINQ to objects to get an initial dictionary. I've made the assumption that your hierarchy is in you table and not an external table, but if it's external, you can modify what you see here to make it work. I've assumed integer keys. Code is trivial to modify if you are using GUID's, just cast appropriately.

Related

Adding new child-Node to specific parent-Node

I am trying to add a new child node into specific parent node.
The thing is that I can't find a property I could use to specify which parent Node I want to use.
Only I can use is:
TreeView1.SelectedNode.Nodes.Add(newNode)
But I don't want to use SelectedNode.
What I need should look like this:
TreeView1.ParentNode(Me.ds_Tables.Table.Rows(a).Item(0)).Nodes.Add(newNode)
edit:
So, I wrote ParentNode just to make it clear that that's a node where I will add a new node to.
Relationship between nodes and datatable is that I am using table column result to give a name to Node.
The thing is that my table looks like (id, code, name, parentId) parentId is the id column from that table. So, when parentId is filled (not Null) that means that that result is a part of another result from that table. (I hope that's clear for you, if it's not I'll try to explain in different way).
So, basically I have to find all results who has parentId filled and find which result own it and put that name into that "parent Node".
Option 1
It seems you read all the data at once at some point and then construct the tree. If that is the case, you can construct the TreeNodes fully before adding them to the TreeView:
Dim items As New List(Of Item)()
Dim map As New Dictionary(Of Integer, TreeNode)()
' first, create all TreeNode objects
For Each item As var In items
Dim node As New TreeNode()
' set node values
map.Add(item.Id, node)
Next
' second, construct the relations
For Each item As var In items
Dim node = map(item.Id)
If item.ParendID.HasValue Then
map(item.ParentID).Nodes.Add(node)
Else ' no parent = root node
TreeView.Nodes.Add(node)
End If
Next
Option 2
If your tree is dynamic, you can still keep a global dictionary indicating which Id links to which TreeNode:
Private map As New Dictionary(Of Integer, TreeNode)();
Option 3
Use the Tag property and write an Extension Method for TreeNodeCollection:
<System.Runtime.CompilerServices.Extension> _
Public Shared Function Find(nodes As TreeNodeCollection, item As Object) As TreeNode
For Each node As var In nodes
If node.Tag IsNot Nothing AndAlso node.Tag.Equals(item) Then
Return node
End If
Next
Return Nothing ' or throw an exception
End Function
and then use
TreeView.Nodes.Find(parentID).AddNodes(...)

In vb.net, I can't get my DirectoryInfo function to work properly

I'm trying to perform file deletion but only on files that don't exist in a list.
Example:
Dim FilesToKeep As List(Of String) = MyFunctionThatPopulatesTheList
The FilesToKeep list consists of the filenames.
Here is where I'm having trouble, as these clause functions throw me off big-time.
Dim filesToDelete
filesToDelete = New DirectoryInfo(FilePath) _
.GetFiles("*", SearchOption.AllDirectories) _
.Where(Function(f) Not f.Attributes.HasFlag(FileAttributes.Hidden)) _
.Where(Function(f) Not FilesToKeep.ToString.Contains(f.Name)) _
.[Select](Function(f) New FileCollectionForDelete(f)).ToArray()
Two things I'm trying to do if you look at the bottom two lines of the DirectoryInfo function. I only want the files that do not exist in the FilesToKeep list. The second, is just a helper where I'm storing the information about the file.
But as it stands, filesToDelete returns every single file.
Thank you for your help.
=========== EDIT =============
After comments, I gave it another shot, but curious if anyone can offer opinion on stability of this function.
First, I created another variable called FilesToKeep2
Dim FilesToKeep2 As String = String.Join(",", FilesTOKeep.ToArray())
And my function I left how it was, as it isn't comparing the entire path, note the (f.Name).
So right now this seems to be working properly, but worried about gotcha's later on.
Would this function be as solid as iterating through each one individually?
The problem is this expression:
Function(f) Not FilesToKeep.ToString.Contains(f.Name)
The type of the FilesToKeep object is a List(Of String). Calling ToString on a List(Of String) returns the name of the type. Just remove that part of the expression and you'll be fine:
Function(f) Not FilesToKeep.Contains(f.Name)
Also, I think you're overthinking things with the final .Select(). Skip that (and the .ToArray() call) entirely.
Final code:
Dim FilesToKeep As List(Of String) = MyFunctionThatPopulatesTheList()
Dim filesToDelete = (New DirectoryInfo(FilePath)).GetFiles("*", SearchOption.AllDirectories).
Where(Function(f) Not f.Attributes.HasFlag(FileAttributes.Hidden)).
Where(Function(f) Not FilesToKeep.Contains(f.Name))
For Each fileName As String In filesToDelete
File.Delete(fileName)
Next
Regarding your edit: you're probably fine with that edit. However, you should know that commas are legal in file names, and therefore it's possible to create files that should be deleted, but will still match your string. For best results here, at least use a delimiter character that's not legal in file names.

Building a dynamic LINQ query

I have a listbox which users can select from a list if Towns, I want to be able to build a LINQ query based on the selected items in the list e.g.
Dim ddlTowns As ListBox = CType(Filter_Accommodation1.FindControl("ddlTowns"), ListBox)
If Not ddlTowns Is Nothing Then
For Each Item In ddlTowns.Items
If Item.Selected Then
'// Build query
End If
Next
End If
I have researched LinqKit as it appears to be able to do what I need but I cannot after hours of trying make any headway. I cannot find anything in VB which translates in anything meaningful or usable.
Just had a Eureka moment and rather than using predicate I came up with this...
Private Function Filter_Accommomdation_QueryBuilder() As IEnumerable
Dim ddlTowns As ListBox = CType(Filter_Accommodation1.FindControl("ddlTowns"), ListBox)
Dim myList As New List(Of String)
If Not ddlTowns Is Nothing Then
For Each Item In ddlTowns.Items
If Item.Selected Then
myList.Add(Item.value)
End If
Next
End If
Dim Filter_Query = _
From c In InitialQuery _
Where myList.ToArray.Contains(c.MyData.element("townvillage").value) _
Select c
Return Filter_Query
End Function
As a note I'm using c.MyData as the nature of InitialQuery demands a number of structured fields (the query is reused from various tables which by poor design aren't very consistant).
Check out this question - contains some useful VB examples for you: Using PredicateBuilder with VB.NET

How can I dynamically select my Table at runtime with Dynamic LINQ

I'm developing an application to allow engineers to conduct simple single table/view queries against our databases by selecting Database, Table, Fields.
I get how to use the Dynamic LINQ Library Sample to provide for dynamically selecting the Select, Where and Order by Clauses at runtime but I'm at an impass on how to allot for table choice.
Is there a way to provide for dynamically selecting the "from" table at run time, and if how could you provide some concrete example or point me in the direction of someone who has?
Thank You ever so much.
EDIT
So Both of the answers seem to be saying the same general Idea. I'm going to try to convert the C# into VB and get it to work.
The first answer converts to
NotInheritable Class DataContextExtensions
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function GetTableByName(context As DataContext, tableName As String) As ITable
If context Is Nothing Then
Throw New ArgumentNullException("context")
End If
If tableName Is Nothing Then
Throw New ArgumentNullException("tableName")
End If
Return DirectCast(context.[GetType]().GetProperty(tableName).GetValue(context, Nothing), ITable)
End Function
End Class
but it's tossing me an Error stating that Extension methods can be defined only in modules.
But when I wrap it in module tags it still gives the same error.
So I got it to compile by wrapping it in Module Tags and stripping the class tags. Also I can pull the last line out of it and shove that directly into my base method which allows my to execute it but, it seems to be coming back empty. When I try to enumerate the results there are none. Not sure if this is my codes problem or the new codes issue, I'll test more.
Here's my conversion of the second Example, Now I'm off to try to see if I can get them to work. I'll be back with questions or results after some testing.
'get the table from a type (which corresponds to a table in your context)
Dim dataContextNamespace = "My.DataContext.Namespace"
Dim type = Type.[GetType](dataContextNamespace + tableName)
Dim table = dc.GetTable(type
'add where clauses from a list of them
For Each whereClause As String In whereClauses
table = table.Where(whereClause)
Next
'generate the select clause from a list of columns
Dim query = table.[Select]([String].Format("new({0})"), [String].Join(",", selectColumns))
Thanks for the help. BBL
See Get table-data from table-name in LINQ DataContext.
This is probably actually better done by using direct SQL statements. LINQ will just get in your way.
VB Conversion:
NotInheritable Class DataContextExtensions
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function GetTableByName(context As DataContext, tableName As String) As ITable
If context Is Nothing Then
Throw New ArgumentNullException("context")
End If
If tableName Is Nothing Then
Throw New ArgumentNullException("tableName")
End If
Return DirectCast(context.[GetType]().GetProperty(tableName).GetValue(context, Nothing), ITable)
End Function
End Class
Usage:
Dim myDataContext as New MyCustomDataContext
myDataContext.GetTableByName("ORDERS").Where("...")
You can use GetTable() to get the corresponding ITable of your data. Then coupled with using DLINQ, making it relatively easy.
This example uses the AdventureWorks database. My project has the context defined in the DatabaseTest assembly in the DatabaseTest.AdventureWorks namespace.
'' need my database and DLINQ extensions up top
Imports DatabaseTest.AdventureWorks
Imports System.Linq.Dynamic
'' sample inputs
Dim dc = New AdventureWorksDataContext()
Dim tableName = "Contact"
Dim whereClauses() = {"FirstName = ""John"" OR LastName = ""Smith"""}
Dim selectColumns() = {"FirstName", "LastName"}
'' get the table from a type (which corresponds to a table in your database)
Dim typeName = "DatabaseTest.AdventureWorks." & tableName & ", DatabaseTest"
Dim entityType = Type.GetType(typeName)
Dim table = dc.GetTable(entityType)
Dim query As IQueryable = table
'' add where clauses from a list of them
For Each whereClause As String In whereClauses
query = query.Where(whereClause)
Next
'' generate the select clause from a list of columns
query = query.Select(String.Format("new({0})", String.Join(",", selectColumns)))
In retrospect, using reflection might have been the easier way to get the table since you have the name already. But then the names might not have a 1-to-1 correspondence so you'll have to compensate for it.
Dim table As ITable = dc.GetType().GetProperty(tableName & "s").GetValue(dc, Nothing)

How do I update a single table of a DataSet using a TableAdapter, without hard-coding the table name?

This seems like a really basic thing that I'm doing, yet I'm tearing my hair out trying to make it work.
My situation is this: I have a project which contains a large number of lookup tables, and I have all of these lookup tables represented in a single typed DataSet, which contains TableAdapters for each lookup. I've designed an editor for these lookup tables, which should allow editing of one of these at a time. My front-end is written in VB and WinForms, the back-end is a SOAP web service; I can successfully pass the changes to the DataSet back to the web service, but can't find a way to use a TableAdapter to update the single table that has been changed.
What I'm trying to do is instantiate the appropriate TableAdapter for the updated DataTable by sending the name of the table back to the web service along with the DataSet, then referring to the TableAdapter with a dynamic name. The normal way to instantiate a TableAdapter is this:
Dim ta As New dsLookupsTableAdapters.tlkpMyTableTableAdapter
What I'd like to do is this, but of course it doesn't work:
strTableName = "tlkpMyTable"
Dim ta As New dsLookupsTableAdapters(strTableName & "TableAdapter")
Is there any way to achieve this, or am I taking the wrong approach altogether? My other alternative is to write separate code for each table, which I'd prefer to avoid!
You can use Activator to create an instance of your TableAdapter from its string name, just like you want:
object adapter = Activator.CreateInstance(Type.GetType("My.Namespace.MyDataSetTableAdapters." + myTable.Name + "TableAdapter"));
Then, because TableAdapters don't have a common interface, you should use reflection to call its Update method:
adapter.GetType().GetMethod("Update").Invoke(adapter, null);
http://msdn.microsoft.com/en-us/library/system.type.getmethod.aspx
This is from memory, but roughly close enough. You can also use GetProperty to get the connection property and set it as required.
Not sure I 100% understand, do you have a single DataTable in your DataSet, or one DataTable per lookup table?
Anyway, perhaps you could you this approach to filter by lookup table?
It's pretty easy to create types at runtime given the (string) type name.
Here's a self-contained VB class which illustrates one way to do it: use System.Activator.CreateInstance to create instances of types using a string representation of the type name. Then you can cast it to a DataAdapter base class and use it like any other DataAdapter.
Public Class dsLookupsTableAdapters
Public Function CreateInstance(ByVal strName As String) As Object
CreateInstance = Nothing
For Each a As System.Reflection.Assembly In System.AppDomain.CurrentDomain.GetAssemblies()
Try
Dim strAssemblyName As String() = a.FullName.Split(New Char() {","c})
Dim strNameTemp As String = strAssemblyName(0) & "." & strName
Dim instance As Object = System.Activator.CreateInstance(a.FullName, strNameTemp)
If instance IsNot Nothing Then
Dim handle As System.Runtime.Remoting.ObjectHandle
handle = CType(instance, System.Runtime.Remoting.ObjectHandle)
Dim o As Object = handle.Unwrap()
CreateInstance = o
Exit For
End If
Catch ex As System.Exception
Continue For ' ignore exception, means type isn't there
End Try
Next
End Function
Public Class tlkpMyTableTableAdapter
Inherits System.Data.Common.DataAdapter
End Class
Public Sub Test()
' define type name. note that, in this sample, tlkpMyTableTableAdapter is a nested
' class and dsLookupsTableAdapters is the containing class, hence the "+". If, however,
' dsLookupsTableAdapters is a namespace, replace the "+" with a "."
Dim typeName As String = "dsLookupsTableAdapters+tlkpMyTableTableAdapter"
Dim adapter As System.Data.Common.DataAdapter
Dim o As Object = CreateInstance(typeName)
adapter = CType(o, System.Data.Common.DataAdapter)
End Sub
End Class
If you are using VB.Net 2008, then use the tableadaptermanager (http://msdn.microsoft.com/en-us/library/bb384426.aspx). I think this would be much easier to code against :)
Wade