VB.Net "mask" combo box text as another text - mysql

So I have a combobox which contains table names from a MySql database they are automatically listed using show tables query upon form load.
Is there anyway to show something else in the combobox but the text value still being the original table name?

It isn't impossible. Here is a trivial example:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' This would be whatever you are pulling from the database. For the purpose of this example, it is just mock data.
Dim dataFromDatabase As IEnumerable(Of String) = {"1st table from db", "2nd table from db", "etc."}
' What you actually want to display in the combobox. It should be in the same order as above and have the same number of items. Items must be unique.
Dim valuesToDisplayInComboBox As IEnumerable(Of String) = {"1st item", "2nd item", "3rd item"}
' This is what ties the two together. You would probably want this to be larger in scope than this example.
Dim dataCollection As New Dictionary(Of String, String)
For i As Integer = 0 To dataFromDatabase.Count - 1 Step 1
dataCollection.Add(valuesToDisplayInComboBox(i), dataFromDatabase(i))
Next
ComboBox1.DataSource = valuesToDisplayInComboBox
End Sub
End Class
Now you have a Dictionary that links the 2 together, so whenever the user selects something in the combobox, you would go to the Dictionary and look up the corresponding table name.

Class Element
Public ItemName as String = ""
Public ItemData As Object = Nothing
' Object allows it to be reusable beyond this use
Public Sub New(iName as String, iData As Object)
ItemName = iName
ItemData = iData
End Sub
Public overrides Function ToString As String
Return ItemName
End sub
End Class
....
For each s as string in listoftablesfromdatabase
' dont know how you are getting your list,
' but here is one way to alias them
Dim El as Element
Select Case s
Case "tbl_event_birthdays_september"
El = New Element("September Birthdays", s)
case ...
case ...
End Select
ComboBox1.Items.Add(el)
Next s
The class will automatically use the friendly name you gave it. To get the real selected item name:
realName = ComboBox1.SelectedItem.ItemData.Tostring
might not need the .ToString This is not a lot different than Douglas Barbin's idea, it still associates 2 strings, it just doesnt use a dictionary. Alternatively, you could store the Elements in a List(of Element) or Dictionary and bind it to the datasource as Douglas showed.
If the user comes back to the Combo over and over, then do use a List or Dictionary, but not temporary - build it once and use it over and over.

Related

Search content of a JSON file with output in a DataGridView

The Form has a text field and a DataGridView. It is necessary, without displaying the entire contents of the JSON file in the DataGridView, to search the content of the JSON file and display the search result in the DataGridView.
You need to search by the UserName tag. You need to start typing either the first or last name in the text field and in the DataGridView display the result of the found.
How to read text file in DataGridView I know:
Imports System.IO
Imports Newtonsoft.Json
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim result = JsonConvert.DeserializeObject(Of List(Of Users))(File.ReadAllText("D:\Users.json"))
DataGridView1.DataSource = result
End Sub
Public Class Users
Public Property ID() As Integer
Public Property UserName() As String
Public Property Login() As String
Public Property Title() As String
Public Property Dep() As String
Public Property Mail() As String
Public Property Phone() As String
End Class
End Class
I also know how to do a file search. Only for some reason the result displays - the first element found:
Dim json As String = File.ReadAllText("D:\Users.json")
Dim objectList = JsonConvert.DeserializeObject(Of List(Of Users))(json)
Dim foundItem = objectList.Where(Function(underscore) underscore.UserName.Contains("Tom")).FirstOrDefault()
If foundItem IsNot Nothing Then
MessageBox.Show(foundItem.UserName)
Else
MsgBox("none")
End If
And the actual contents of the json file itself:
[
{
"id":"1",
"UserName":"Fred Smith",
"Login":"f.smith",
"Title":"engineer",
"Dep":"IT infrastcomcture",
"Mail":"f.smith#domain.com",
"Phone":"111",
},
{
"id":"2",
"UserName":"Ben Taylor",
"Login":"b.taylor",
"Title":"programmer",
"Dep":"IT infrastcomcture",
"Mail":"b.taylor#domain.com",
"Phone":"100",
},
{
"id":"3",
"UserName":"Steve Harris",
"Login":"s.harris",
"Title":"System Administrator",
"Dep":"IT infrastcomcture",
"Mail":"s.harris#domain.com",
"Phone":"263",
},
{
"id":"4",
"UserName":"Tom Walker",
"Login":"t.walker",
"Title":"engineer",
"Dep":"IT infrastcomcture",
"Mail":"t.walker#domain.com",
"Phone":"263",
},
{
"id":"5",
"UserName":"Tom Davis",
"Login":"t.davis",
"Title":"engineer",
"Dep":"IT infrastcomcture",
"Mail":"t.davis#domain.com",
"Phone":"200",
},
{
"id":"6",
"UserName":"Ben Walker",
"Login":"b.walker",
"Title":"System Administrator",
"Dep":"IT infrastcomcture",
"Mail":"b.walker#domain.com",
"Phone":"167",
},
]
A few things to note:
The JSON presented here represent an array of objects which all have the same properties. It can be considered an array of records or Rows.
You need to deserialize this JSON, present the result in a DataGridView and allow the user to filter and probably sort the data.
You're currently deserializing this JSON to simple collection a class objects, which is perfectly fine. It may become a little more complex if you want to filter and sort this collection, since a simple List<T> doesn't support it by itself. Nor does a BindingList.
You should implement the IBindingListView interface in a class that handles the List of objects and most probably also the INotifyPropertyChanged interface in the base class (your current Users class).
Or use an ORM / Mini-ORM instead.
There's an already built (and tested) Type that already implements all these features, the DataTable class.
Since, as mentioned, your JSON IS actually a Table (an array of records), deserializing it to a DataTable is quite straightforward. It's just:
Dim dt = JsonConvert.DeserializeObject(Of DataTable)(json)
The DataTable class already allows filtering, setting its DefaultView.RowFilter property and sorting, setting its DefaultView.Sort property.
Nonetheless, I suggest to use a BindingSource as mediator between the DataTable and the UI.
This tool is quite useful, since it provides common methods to filter and sort a source of data, provided that the source of data actually has these capabilities.
Using a BindingSource, you always use the same methods, no matter what the source of data is.
It also generates some useful events, as the ListChanged, AddingNew, CurrentChanged events and more.
The ListChanged event also provides arguments that specify the type of change.
With a BindingSource, to serialize back to JSON, if the the data has changed:
[BindingSource].EndEdit()
Dim json = JsonConvert.SerializeObject([BindingSource].DataSource, Formatting.Indented)
In the sample code, these objects are used (see the visual example):
UsersSource: the BindingSource object
tBoxFilterUser: a TextBox Control, filters the data using the UserName Property
tBoxFilterTitle: a TextBox Control, filters the data using the Title Property
btnRemoveFilter: a Button Control used to remove the current filters
dgv: a DataGridView Control
Public Class SomeForm
Private UsersSource As New BindingSource()
' Current filters
Private UserNameFilter As String = "UserName LIKE '%%'"
Private UserTitleFilter As String = "Title LIKE '%%'"
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
Dim json = File.ReadAllText("D:\Users.json")
Dim dt = JsonConvert.DeserializeObject(Of DataTable)(json)
dt.AcceptChanges()
UsersSource.DataSource = dt
dgv.DataSource = UsersSource
End Sub
Private Sub tBoxFilterUser_TextChanged(sender As Object, e As EventArgs) Handles tBoxFilterUser.TextChanged
Dim tbox = DirectCast(sender, TextBox)
UserNameFilter = $"UserName LIKE '%{tbox.Text}%'"
UsersSource.Filter = $"{UserNameFilter} AND {UserTitleFilter}"
End Sub
Private Sub tBoxFilterTitle_TextChanged(sender As Object, e As EventArgs) Handles tBoxFilterTitle.TextChanged
Dim tbox = DirectCast(sender, TextBox)
UserTitleFilter = $"Title LIKE '%{tbox.Text}%'"
UsersSource.Filter = $"{UserNameFilter} AND {UserTitleFilter}"
End Sub
Private Sub btnRemoveFilter_Click(sender As Object, e As EventArgs) Handles btnRemoveFilter.Click
tBoxFilterUser.Clear()
tBoxFilterTitle.Clear()
UsersSource.RemoveFilter()
End Sub
End Class
This is how it works:

Creating readonly copy of a collection in access VBA

I am new to Access VBA and I am stuck in what I think a "Language Limitation". I have a collection of Items and I want to copy some of its items in a new collection depending on the condition and then work on that new collection. But the problem is that if I change or remove anything from that new collection, it gets changed in the previous collection also. But I dont want that to happen as it would be again used as it is in next iteration.
The code which I have used to make the new collection is:
Private Function ReturnSubCollection(TotalCollection As Collection, workIDs As String) As Collection
Dim collWorkIDs As Collection
Dim itemCount As Integer
Dim obj As Object
For itemCount = 1 To TotalCollection.count
If InStr(1, workIDs, TotalCollection.item(itemCount).Work_ID) > 0 Then
Set obj = TotalCollection.item(itemCount)
If collWorkIDs Is Nothing Then Set collWorkIDs = New Collection
collWorkIDs.Add obj
End If
Next
Set ReturnSubCollection = collWorkIDs
End Function
This is a limitation of VB. The elegant solution is to create a "memento" class in your item object as mentioned in this great answer.
A simple work around might be this:
Suppose your item class starts with three values Work_ID, Work_Name, Work_Date. Modify your code as follows:
With TotalCollection.item(itemCount)
If InStr(1, workIDs, .Work_ID) > 0 Then
Set obj = New itemClass
obj.Work_ID = .Work_ID
obj.Work_Name = .Work_Name
obj.Work_Date = .Work_Date
'And so on, for any additional fields.
If collWorkIDs Is Nothing Then Set collWorkIDs = New Collection
collWorkIDs.Add obj
End If
End With
Crude, certainly. Effective, hopefully.

LINQ to SQL datagridview query returns length, not value

I've got a Windows Form with a button and a datagridview. The project includes a working database connection and LINQ to SQL class. I'm trying to bind the datagridview to the LINQ to SQL.
In a code module I've got this:
Public Function DataGridList() As BindingSource
Dim NewBindingSource As New BindingSource()
Dim db As New DataClasses1DataContext()
NewBindingSource.DataSource = _
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select Block.gr6_school Distinct
Return NewBindingSource
End Function
And this button_click code in the form:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
DataGridView1.DataSource = DataGridList()
End Sub
When I click the button I get the length of the school names in the datagridview, with a column header of "length."
If I just run this very similar code in the button_click instead, the school names appear correctly in the immediate window:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim db As New DataClasses1DataContext()
Dim TestQuery =
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select Block.gr6_school Distinct
For Each block In TestQuery
Debug.Print(block)
Next
End Sub
Give this a try:
Public Function DataGridList() As BindingSource
Dim NewBindingSource As New BindingSource()
Dim db As New DataClasses1DataContext()
NewBindingSource.DataSource = _
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select New With { Key .Value = Block.gr6_school } Distinct
Return NewBindingSource
End Function
This should give it a property that the DataGridView can pick up on. The New With... creates an anonymous object with a Property named Value. The DataGridView works on this type of object by enumerating the public properties and rendering them as columns. If you had needed more than one value, you could have added additional items inside the curly braces in the same way separated by commas. See Anonymous Types (Visual Basic) for more information.
You might try adding a .ToString to the Select:
From Block In db.BLOCK_ASSIGNMENTs
Where Block.gr912_school = "Franklin"
Select Block.gr6_school.ToString Distinct
I believe that Debug.Print does an implicit conversion to .ToString when it prints to the immediate window. However, a datagrid cell treats everything as an object and displays the default property for that object.
Turns out, of course, this has been addressed often, including on SO, and here. The route I chose was to use an intermediate DataTable:
Public Function DataGridList() As DataTable
Dim NewDataTable As New DataTable
Dim db As New DataClasses1DataContext()
Dim i As Int32
Dim qry =
From Block In db.BLOCK_ASSIGNMENTs.AsEnumerable
Where Block.gr912_school = "Franklin"
Select Block.gr6_school Distinct
NewDataTable.Columns.Add("School")
For i = 0 To qry.Count - 1
NewDataTable.Rows.Add(qry(i))
Next
Return NewDataTable
End Function
This seems pretty slow the first time it runs, so I may try something else in the future, but it allows me to feed the grid via LINQ, which is what I want to work with.
(I would like to use the qry's CopyToDataTable property, but it's only available if the query returns a DataTableRows collection, or some such, and my hacking around didn't reveal how to do that.)

grab linq to sql record by primarykey without knowing its type

how can i grab a record (and eventually delete it) using linq2sql without knowing the type at compile time?
so far i've got
Sub Delete(ByVal RecordType As String, ByVal ID As Integer)
Dim dummy = Activator.CreateInstance(MyAssembly, RecordType).Unwrap
Dim tbl = GetTable(dummy.GetType)
tbl.DeleteOnSubmit(dummy)
End Sub
but of course the dummy is not an actual record, its just a dummy
i don't want to use direct sql (or executecommand) as there's business logic going on at deletion in the datacontext partial class
can this be done somehow?
thank you very much!
EDIT
in response to striplinwarior, i edited my code to:
Sub Delete(ByVal RecordType As ObjectType, ByVal ID As Integer)
Dim dummy = Activator.CreateInstance(ObjectType.Account.GetType.Assembly.FullName, RecordType.ToString).Unwrap
SetObjProperty(dummy, PrimaryKeyField(RecordType), ID)
Dim tbl = GetTable(dummy.GetType)
tbl.Attach(dummy)
tbl.DeleteOnSubmit(dummy)
SubmitChanges()
End Sub
this does fire off the deletion code correclty, but also seems to try to add the record first to the db, as i get a sqlexception that some "not null" fields are empty, which i guess is true about the dummy record, as the only thing this has is the primarykey, else is all empty. so i tried the other code u posted (something i anyways always wanted to have) and that works excellent!
hers my current code:
Function LoadRecord(ByVal RecordType As String, ByVal RecordID As Integer) As Object
Dim dummy = Activator.CreateInstance(AssemblyName, RecordType).Unwrap
Dim rowType = dummy.GetType
Dim eParam = Expression.Parameter(rowType, "e")
Dim idm = rowType.GetProperty(PrimaryKeyField(RecordType))
Dim lambda = Expression.Lambda(Expression.Equal(Expression.MakeMemberAccess(eParam, idm), Expression.Constant(RecordID)), eParam)
Dim firstMethod = GetType(Queryable).GetMethods().[Single](Function(m) m.Name = "Single" AndAlso m.GetParameters().Count() = 2).MakeGenericMethod(rowType)
Dim tbl = GetTable(rowType)
Dim obj = firstMethod.Invoke(Nothing, New Object() {tbl, lambda})
Return obj
End Function
Sub Delete(ByVal RecordType As String, ByVal RecordID As Integer)
Dim obj = LoadRecord(RecordType, RecordID)
Dim tbl = GetTable(obj.GetType)
tbl.DeleteOnSubmit(obj)
SubmitChanges()
End Sub
Thank You
The only way I can think of is to use the model information from your database mapping to figure out which member represents the primary key:
Dim primaryKey = (From t In db.Mapping.GetTables() _
Where t.RowType.Type = tableType _
Let keyMember = (From dm In t.RowType.DataMembers where dm.IsPrimaryKey).FirstOrDefault() _
Select keyMember.Member.Name).First()
(I'm using LinqPad here: I assume typical LINQ to SQL models have this mapping information available.)
Then use reflection to set the value of that key member on the dummy item you've created. After that, you need to attach the dummy to the table before trying to delete it, passing false as a second parameter to tell LINQ to SQL that you don't actually want to update the object using its current values, but that it should track changes from here on.
tbl.Attach(dummy, false)
tbl.DeleteOnSubmit(dummy)
db.SubmitChanges()
Does that make sense?
Edit
When you're only deleting an object, you don't necessarily have to get the record from the database. If you set the ID value of the object and then attach it to the context (as shown above), LINQ to SQL will treat it as if it were retrieved from the database. At that point, calling DeleteOnSubmit should tell the context to construct a DELETE statement in SQL based on that object's primary key value.
However, if you need to retrieve the object for some purpose other than deletion, you'll need to construct an expression to represent the query for that object. So, for example, if you were writing the query manually, you would say something like:
Dim obj = tbl.First(Function(e) e.Id = ID)
So to dynamically build the lambda expression inside the parentheses, you might do something like this:
Dim eParam = Expression.Parameter(rowType, "e")
Dim lambda = Expression.Lambda(Expression.Equal(Expression.MakeMemberAccess(eParam, idMember), Expression.Constant(ID)), eParam)
Then you would need to use reflection to invoke the generic First method:
Dim firstMethod = GetType(Queryable).GetMethods().[Single](Function(m) m.Name = "Single" AndAlso m.GetParameters().Count() = 2).MakeGenericMethod(rowType)
Dim obj = firstMethod.Invoke(Nothing, New Object() {tbl, lambda})

Referencing global variable - MS Access

Im trying to pass the name of a global variable to a sub routine and would like to know how to reference it. For example I could do the below with a control:
Private Sub passCtrlName(ctlName as String)
Me.Controls(ctlName) = "Whatever"
End Sub
Edit:
For Example
Public imGlobVar As String
Public Sub passGlobVar(frm as Form, ctlName as String, globVar as String)
frm.Controls(ctlName) = globVar
End sub
And call it as
Private Sub imaButton_Click()
imGlobVar = "something"
Call passGlobVar(Me , txtBox1, imGlobVar)
End sub
2nd Edit:
It seems that I could most definitely be barking up the wrong tree here, so I will explain what I'm trying to achieve.
I have a form that has textboxes for the users (risk) address, with a checkbox at the top that lets the user select that this address is the same as the 'contact' details already on the system, and the textboxes are locked.
Populating the textboxes is fine and works. What I use the global variables for is to improve usability (albeit slightly).
The user can add new details, and if they hit the checkbox 'make same as contact' the details that they have entered are stored in the global variables, one for each control.
If the user has made a mistake by hitting the checkbox, they haven't lost these value, and by unchecking the box the entered values are returned.
I hoped to create a sub routine where I could pass the name of the global variable and control and calling this routine, as opposed to writing it out for each control.
I have a feeling that I could be using the wrong technique to achieve my goals. But in answer to my original question, it appears that you can not pass global variables to sub routines in the manner that I wished.
You do not need to pass global variables, you can simply refer to them by name. Note that global variables are reset if an unhandled error occurs.
In http://msdn.microsoft.com/en-us/library/dd897495(office.12).aspx you will find a section on Scope and Lifetime of Variables and Constants.
In a module:
Option Explicit
Public glbVarName As String
Const MyConstant=123
Sub InitVar
glbVarName="Something"
End Sub
Any other module, includeing a form's class module:
Sub SomeSub
MsgBox glbVarName
SomeVar=2+MyConstant
End Sub
If you're asking if you can dynamically reference global variables using a string containing the variable name the answer is no. You could use a single global array and pass the index, which would allow you to dynamically reference an element of the array.
[Edit]
In response to the clarification in the question: You could just save the value of each control to its Tag property when the user checks the checkbox. Then, if the user unchecks the checkbox, you can just loop over your controls and assign the value from the Tag back to the Value of the control.
You could store the values from your controls in a Dictionary object, using the control names as the dictionary keys. Then you can retrieve the value for each control based on the control's name.
Option Compare Database
Option Explicit
Const cstrMyControls As String = "Text0,Text2,Text4,Text6"
Dim objDict As Object
Private Sub chkToggle_Click()
If Me.chkToggle = True Then
Call SaveValues
Else
Call RestoreValues
End If
End Sub
Private Sub SaveValues()
Dim varControls As Variant
Dim i As Long
Set objDict = Nothing 'discard previous saved values '
Set objDict = CreateObject("Scripting.Dictionary")
varControls = Split(cstrMyControls, ",")
For i = 0 To UBound(varControls)
objDict.Add varControls(i), Me.Controls(varControls(i)).Value
Next i
End Sub
Private Sub RestoreValues()
Dim varControls As Variant
Dim i As Long
If objDict Is Nothing Then
'MsgBox "No values to restore." '
Else
varControls = objDict.keys()
For i = 0 To UBound(varControls)
Me.Controls(varControls(i)).Value = objDict(varControls(i))
Next i
End If
End Sub
I use additional field in table - name cancel - of course boolean - when i'm not sure if contents of fields will be valid I set it true. If this field will be true by the end - then I clean up (it may be all record or some fileds of course). Very easy.