Best way to bind My.Settings to a datagridview, so end user can modify? - vb.net

How can we databind a datagridview to some of the My.Settings (user scoped) properties so the user can edit the values? This must be to a datagridview. I know we can bind to My.Settings in a form with textboxes and so on, but in this case we just want it as list of editable strings in a datagridview.
Of course some My.Settings entries can have different datatypes which complicates matters but generally we're only working with strings and booleans.
Also, let's assume the user understands that he must enter the string "true" to set a boolean to true. No checkbox column needed.
Here's what we are using (and it works), just looking for a better, leaner way:
here's the class:
Public Class MySettingsMaint
...
then we have a bindinglist (this is probably yuk):
Private list As BindingList(Of BindingKeyValuePair)
here's what the datagridview binds to:
Public ReadOnly Property DataSource() As Object
Get
list = New BindingList(Of BindingKeyValuePair)
list.Add(New BindingKeyValuePair("PhoneExtension", My.Settings.PhoneExtension.ToString.ToLower))
list.Add(New BindingKeyValuePair("PhonePIN", My.Settings.PhonePIN.ToString.ToLower))
list.Add(New BindingKeyValuePair("PhoneEnabled", My.Settings.PhoneEnabled.ToString.ToLower))
Return From k In list Order By k.Key
Return list
End Get
End Property
...
Public Sub LoadGrid(ByVal grd As DataGridView) Implements
/some stuff here to create two DataGridViewColumns
/one bound to Key, the other bound to Setting
End Sub
more yuk here, to save back to My.Settings:
Public Sub Save() Implements IMaintainable.Save
My.Settings.PhoneExtension = (From x As BindingKeyValuePair In list Where x.Key = "PhoneExtension" Select x.Value.ToLower).ToList(0)
My.Settings.PhonePIN = (From x As BindingKeyValuePair In list Where x.Key = "PhonePIN" Select x.Value.ToLower).ToList(0)
My.Settings.PhoneEnabled = (From x As BindingKeyValuePair In list Where x.Key = "PhoneEnabled" Select x.Value.ToLower).ToList(0) = "true"
My.Settings.Save()
End Sub
the private class required by the above class:
Private Class BindingKeyValuePair
Sub New(ByVal k As String, ByVal v As String)
Key = k
Value = v
End Sub
Private _Key As String
Public Property Key() As String
/basic getsetter
End Property
Private _Value As String
Public Property Value() As String
/basic getsetter
End Property
End Class

You should use the PropertyGrid control? It will help with regard to supporting richer property types.

Related

Collection class of specific type containing basic features

Every time i use some class e.g Artikel as follows:
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
End Class
For such classes i would like to have collection class. The features i would like to have is like:
--> Add (passing Artikel object)
--> Remove (passing Artikel object)
--> Sort entire collection (based on Position property desc/asc)
--> Compare two Artikels (pass by Artikels and tell by which property has to be compared)
--> Check whether two artikels equals
--> Every added artikel has to be marked by Key (so maybe dictionary)? <key><Artikel>
--> Remove Artikel (passing by Key index)
Could somone from you there tell me or even better provide example of collection class pass those requirments?
EDIT: Startup:
Artikel's collection:
Option Strict On
Public Class Articles
Public Property collection As Dictionary(Of Integer, Artikel)
Sub New()
'Initiate new collection
collection = New Dictionary(Of Integer, Artikel)
End Sub
'Add new Artikel to collection
Public Function AddToCollection(ByVal artikel As Artikel) As Boolean
collection.Add(artikel)
Return True
End Function
'Remove specific Artikel
Public Sub RemoveFromCollectionByArtikel(artikel As Artikel)
If Not IsNothing(collection) Then
collection.Remove(artikel)
End If
End Sub
'Get collection
Public Function GetCollection() As Dictionary(Of Integer, Artikel)
Return collection
End Function
'Sort collection by property position
Public Sub SortByPosition()
collection.Sort()
End Sub
'Remove specific sending keys and then reorder them
Public Sub RemoveAllMarkedAsDeleted(keys As List(Of Integer))
'-- Check whther anything has been marked as deleted
If keys.Count > 0 Then
For Each row In keys
collection.Remove(row)
Next
ReorderKeys()
End If
'Reorder all Artikels in collection
Private Sub ReorderKeys()
Dim newCollection As New Dictionary(Of Integer, Artikel)
Dim index As Integer = 0
For Each collitem In collection
newCollection.Add(index, collitem.Value)
index += 1
Next
collection.Clear()
collection = newCollection
End Sub
End Class
Artikel class (additionally i implemented IComparable to be able to sort)
Option Strict On
Public Class Artikel
Implements IComparable(Of Artikel)
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
Public Function CompareTo(pother As Artikel) As Integer Implements IComparable(Of Artikel).CompareTo 'we can sort because of this
Return String.Compare(Me.Position, pother.Position)
End Function
Public Shared Function FindPredicate(ByVal partikel As Artikel) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel.ID = partikel2.ID
End Function
Public Shared Function FindPredicateByUserId(ByVal partikel As String) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel = partikel2.ID
End Function
End Class
Parts of it look good, but I would ultimately do it a bit differently. First, consider overloads on the item class to make them easier to create and default initialization:
Public Class Article
Property ID As Integer = -1
Property Key As String = ""
Property Name As String = ""
Property Position As Integer = -1
Property PubDate As DateTime = DateTime.Minimum
Public Sub New()
End Sub
' whatever minimum data a new item requires
Public Sub New(k As String, n As String)
Key = k
Name = n
End Sub
' full initialization:
Public Sub New(k As String, n As String, pos As Int32,
pubDt As DateTime)
...
End Sub
End Class
I added some properties for variety, and I suspect "Nummer" might be the "Key" mentioned in the OP, but whatever it is, I would add it to the Article class as that name, if it has some importance.
You might need a simple ctor for serialization (???). Some of these will find and use a Private parameterless constructor, but your code will be forced to use one of the overloads in order to provide some minimum level of data when a new one is created.
You probably do not need IComparable. That is typically for more complex comparisons, such as multiple or complex properties. An example is a carton or box:
If (width = Other.Width) AndAlso (height = Other.Height) Then
Return 0
ElseIf (width = Other.Height) AndAlso (height = Other.Width) Then
Return 0
End If
Plus more gyrations to work out which is "less" than the other. One reason you dont need it, is because If Art1.Postion > Art2.Postion is trivial. The other reason in your case, is because a Dictionary cannot be sorted.
Rather than a Dictionary, an internal List would work better for some of the things you describe but still allow you to have it act like a Dictionary to the extent you need it to. For this, I might build it using ICollection<T>:
Public Class ArticleCollection
Implements ICollection(Of Article)
Pressing Enter after that line will add all the required methods including:
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
Public Sub Clear() Implements ICollection(Of Article).Clear
Public Function Contains(item As Article) As Boolean Implements ICollection(Of Article).Contains
Public ReadOnly Property Count As Integer Implements ICollection(Of Article).Count
Public Function Remove(item As Article) As Boolean Implements ICollection(Of Article).Remove
It remains completely up to you how these are implemented. It also doesn't rule out adding methods such as RemoveAt(int32) or RemoveByKey(string) depending on what you need/how it will be used. One of the benefits to ICollection(Of T) is that it includes IEnumerable which will allow use for each loops (once you write the Enumerator): For Each art In Articles
To emulate a dictionary to allow only one item with a specific property value:
Public Class ArticleCollection
Implements ICollection(Of Article)
Private mcol As List(Of Article)
...
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
' check for existing key
If KeyExists(item.Key) = False Then
mcol.Add(item)
End If
End Sub
You can also overload them:
' overload to match Article ctor overload
Public Sub Add(key As String, name As String)
If KeyExists(key) = False Then
' let collection create the new item
' with the minimum required info
mcol.Add(New Article(key, name))
End If
End Sub
If you add an Item Property, you can index the collection ( Articles(3) ):
Property Item(ndx As Int32) As Article
Get
If ndx > 0 AndAlso ndx < mcol.Count Then
Return mcol(ndx)
Else
Return Nothing
End If
End Get
Set(value As Article)
If ndx > 0 AndAlso ndx < mcol.Count Then
mcol(ndx) = value
End If
End Set
End Property
' overload for item by key:
Public Property Item(key As String) As Article
An Add method and an Item Property will be important if the collection will display in the standard NET CollectionEditor.
There are several ways to implement sorting. The easiest is to use linq in the code which uses your collection:
Articles = New ArticleCollection
' add Article items
Dim ArticlesByDate = Articles.OrderBy(Function(s) s.PubDate).ToList()
Where PubDate is one of the Article properties I added. The other way to handle sorting is by the collection class returning a new collection (but it is so simple to do, there is little need for it):
Friend Function GetSortedList(bSortAsc As Boolean) As List(Of Article)
If bSortAsc Then
Return mcol.OrderBy(Function(q) q.PubDate).
ThenBy(Function(j) j.Position).ToList()
Else
Return mcol.OrderByDescending(Function(q) q.PubDate).
ThenByDescending(Function(j) j.Position).ToList()
End If
End Function
Whether it implements ICollection(Of T), inherits from ICollection(Of T) or does work off a Dictionary depends entirely on what this is, how it is used and whatever rules and restrictions there are (including if it will be serialized and how). These are not things we know.
MSDN has an article on Guidelines for Collections which is excellent.
Create your class
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Create another class which holds a private list and add sub routines to it
Public Class ArtikelList
Private _List as new list (of Artikel)
Public sub remove(Key as integer)
Dim obj as Artikel = nothing
for each x as Artikel in _List
if x.ID = Key then
obj = x
exit for
end if
Next
if not isnothing(obj) then
_List.remove(obj)
end if
End sub
Sub Add(obj as Artikel)
Dim alreadyDeclared as boolean = falsse
for each x as Artikel in _List
if x.ID = obj.id then
alreadyDeclared = true
exit for
end if
Next
if not AlreadyDeclared then
_List.add(obj)
Else
'Somehow inform the user of the duplication if need be.
end if
End sub
End Class
Then use your list class.
dim L as new ArtikelList
L.add(new Artikel(1280, "AFKforever!", "Prof.FluffyButton", 96))
L.remove(1280)
I only added one sub routine as an example. I hope it helps but feel free to ask for more example routines.
This can also be done by creating a class which inherits from the list class, exposing all list class functionality but by using this method you are forced to create every subroutine that will be used. This way you only use routines that you created exclusively for the purpose Artikel objects handling.
Check if two Artikels are equal
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Public Overrides Overloads Function Equals(obj As Object) As Boolean
If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then
Return False
else
dim _obj as artikel = obj
if Me.ID = _obj.ID then
Return true
else Return False
End If
End Function
End Class
Use it like:
If x.equals(y) then
'they have the same ID
end if

Add rows to class bound datagridview

Hi I'm trying to bind a list of objects to a datagridview
Binding a existing list(Of is working but I'm trying to add or remove a object from, my dgv isn't updating.
Public Class Form1
Dim lt As New List(Of Test)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
lt.Add(New Test("Mac", 2200))
lt.Add(New Test("PC", 1100))
dgv.DataSource = lt
lt.Add(New Test("Android", 3300)) 'This line won't appear in the dgv
End Sub
End Class
Public Class Test
Public Sub New(ByVal name As String, ByVal cost As String)
_name = name
_cost = cost
End Sub
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _cost As String
Public Property Cost() As String
Get
Return _cost
End Get
Set(ByVal value As String)
_cost = value
End Set
End Property
End Class
How can I add or remove or change a value from the dgv to the list and inverse?
NoiseBe
Change this line:
Dim lt As New List(Of Test)
To:
Imports System.ComponentModel
...
Private lt As New BindingList(Of Test)
When the collection contents will change, you should use a BindingList(Of T). This collection has events associated with it which make it aware of changes to the list contents.
If in addition to the list contents, the list items will change (like Test.Name), you should also implement INotifyPropertyChanged on the class itself.
Another way to do it:
dgv.DataSource = Nothing
lt.Add(New Test("Android", 3300))
dgv.DataSource = lt
This "resets" the DataSource so that the new contents will show up. However, it means that several other things get reset like selected items; if you are binding to a List/Combo control, you will also have to reset the ValueMember and DisplayMember properties as well.

VB.NET Forms ListBox doesn't display DataSource

I'm trying to link my ListBox to an ObservableCollection.
Here's my class for defining mods:
Public Class TroveMod
Private m_FileName As String
Private m_Enabled As Boolean
Public Property FileName() As String
Get
Return m_FileName
End Get
Set(value As String)
m_FileName = value
End Set
End Property
Public Property Enabled() As Boolean
Get
Return m_Enabled
End Get
Set(value As Boolean)
m_Enabled = value
End Set
End Property
Public ReadOnly Property ModName()
Get
Return Path.GetFileNameWithoutExtension(FileName)
End Get
End Property
End Class
And this is the actual Property ModList:
Public Property ModList() As ObservableCollection(Of TroveMod)
Get
Return m_ModList
End Get
Set(value As ObservableCollection(Of TroveMod))
m_ModList = value
End Set
End Property
I add items using:
Private Sub AddMod(file__1 As String, enabled As Boolean)
If File.Exists(file__1) Then
ModList.Add(New TroveMod() With { _
.FileName = file__1, _
.Enabled = enabled _
})
End If
End Sub
Everytime I want to add something to this Collection using AddMod, it won't show off in my listbox :/ I added an ModListBindingSource to the ListBox and set the DisplayMember and ValueMember to ModName but it still won't work. I also got a status label, which says, that it successful added the mods to the collection but it simply won't show them in the ListBox. Did I miss something?
An ObservableCollection is not quite what you want - this would allow your code to see changes to the collection via events. For instance, if there were several actors adding items to the collection and you wanted to know that. In this case, since 'local' code is adding items, it is not needed.
It is also not clear how the collection is mapped to the ListBox as the DataSource. Try this:
Public Class Form...
Private myList As New BindingList(Of TroveMod)
Sub Form_Load(....
theListBox.DataSource = myList
theListBox.DisplayMember = "ModName"
Now, as you add things to the BindingList they will appear in theListBox. If an item changes though (e.g. the Name), that change will not show up without a little more work, but as these are file names that seems unlikely.
The form property is not needed unless an external class also needs access to the collection/BindingList; in that case, you probably do not want need a setter.

Keep two-way binding when whole object has changed

I have a class:
Public Class TestClass
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnNotifyChanged(ByVal pName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(pName))
End Sub
Private _One As Integer
Private _Two As Integer
Public Sub New(ByVal One As Integer, ByVal Two As Integer)
_One = One
_Two = Two
End Sub
Public Property One() As Integer
Get
Return _One
End Get
Set(ByVal value As Integer)
_One = value
OnNotifyChanged("One")
End Set
End Property
Public Property Two() As Integer
Get
Return _Two
End Get
Set(ByVal value As Integer)
_Two = value
OnNotifyChanged("Two")
End Set
End Property
End Class
I can create an instance and bind two TextBoxes to the object:
Dim MyObject As New TestClass(1, 2)
TextBoxOne.DataBindings.Add("Text", MyObject, "One")
TextBoxTwo.DataBindings.Add("Text", MyObject, "Two")
Now I can change the TextBoxes or the object:
MyObject.Two = 3
..the object and TextBoxes are updated in two ways.
Now I want to update the whole object:
MyObject = New TestClass(3, 4)
...but this doesn't update the TextBoxes.
What am I doing wrong?
Your text boxes hold a reference to the first instance of the object you've created.
Now you're creating a second instance, supposedly in order to replace the existing instance, but the text boxes are unaware of the change.
You needs to either:
Pass the new instance to the text boxes (directly, as you assigned the first instance, or indirectly by having some "Model" object that both boxes are bound to).
Update the existing instance instead of replacing it with a new one (you can simply assign values to the fields, or create some "AssignFrom (other)" method, etc.)
Get yourself some other - more orderly - way of notifiying the controls that their underlying data source has changed / should be changed.

Create a dropdown list of valid property values for a custom control

I've created a custom user control that has several properties. One specifies which database I want the control to access. I want to be able to present the user of the control a drop down from which he can select which database the control will interact with.
How do I get the dropdown to work? I can get default values, but have yet to figure out how to get the selectable list.
Any help is apprectiated.
Thanks.
Marshall
You just need to attach your own TypeConverter to your property. You will override the GetStandardValuesSupported and GetStandardValues methods (maybe GetStandardValuesExclusive too) to return the list of databases you want to show.
If you are new to the PropertyGrid and the TypeConverter, here is a document.
It turns out to be simpler than I thought.
I had an enumeration set up for the property, but was having trouble using it for the property type. Said it was unaccessable outside of the class.
Then I had a 'duh' moment and changed the enumeration from Friend to Public, and then I was able to use the enumeration as the property type. As a result the values from the enumeration are listed in a dropdown when I look at the values for that property of the controls.
Thanks to all that answered.
Marshall
I'm a little confused about your problem.
If your user control contains a DropDownList control, just inititalize somewhere in the user control.
The easiest way is in the Codebehind for the usercontrol, just do DropDownList.Items.Add() or whatever the syntax is for adding an item.
Here is the template I use to create a custom datasource for my comboboxes:
Private Class Listing
Private _List As New ArrayList
Public Sub Add(ByVal ItemNumber As Integer, ByVal ItemName As String)
_List.Add(New dataItem(ItemNumber, ItemName))
End Sub
Public ReadOnly Property List() As ArrayList
Get
Return _List
End Get
End Property
End Class
Private Class dataItem
Private _ItemNumber As Integer
Private _ItemName As String
Public Sub New(ByVal intItemNumber As Integer, ByVal strItemName As String)
Me._ItemNumber = intItemNumber
Me._ItemName = strItemName
End Sub
Public ReadOnly Property ItemName() As String
Get
Return _ItemName
End Get
End Property
Public ReadOnly Property ItemNumber() As Integer
Get
Return _ItemNumber
End Get
End Property
Public ReadOnly Property DisplayValue() As String
Get
Return CStr(Me._ItemNumber).Trim & " - " & _ItemName.Trim
End Get
End Property
Public Overrides Function ToString() As String
Return CStr(Me._ItemNumber).Trim & " - " & _ItemName.Trim
End Function
End Class
And this is how I load it:
ListBindSource = New Listing
Me.BindingSource.MoveFirst()
For Each Row As DataRowView In Me.BindingSource.List
Dim strName As String = String.Empty
Dim intPos As Integer = Me.BindingSource.Find("Number", Row("Number"))
If intPos > -1 Then
Me.BindingSource.Position = intPos
strName = Me.BindingSource.Current("Name")
End If
ListBindSource.Add(Row("Number"), strName)
Next
cboNumber.DataSource = ListBindSource.POList
cboNumber.DisplayMember = "DisplayValue"
cboNumber.ValueMember = "Number"
AddHandler cboNumber.SelectedIndexChanged, AddressOf _
cboNumber_SelectedIndexChanged
Hope this helps. One thing to keep in mind is that if cboNumber has a handler already assigned to the SelectedIndexchanged event, you will encounter problems. So don't create a default event.