Search code examples
vb.netmvvmdata-bindingdataset

Problem loading combobox from dataset VB.NET MvvM


Attempting to load up a combobox from database using a dataset; items do not show up in the combobox. Attempting to do this without writing any codebehind.

The Model:

Imports System.Data.OleDb

Public Class ChainModel
    Public Property ChainId As String
    Public Property ChainType As String

    Public Sub New(chainId As String, chainType As String)
        Me.ChainId = chainId
        Me.ChainType = chainType
    End Sub
End Class

The Dataset population:

Public Class DBCollection
    Public Shared dsChains As DataSet

    Public Shared Sub FillDataSet()
        Try
            dsChains = New DataSet()
            Dim adpt As OleDbDataAdapter = New OleDbDataAdapter With {
                .SelectCommand = New OleDbCommand("SELECT * FROM Chain ORDER BY Name, Manufacturer;", DbConn)
            }
            adpt.Fill(dsChains, Tbl)
            adpt.Dispose()
        Catch ex As Autodesk.AutoCAD.Runtime.Exception
            MsgBox("Unable to populate dataset! " & vbCrLf & ex.Message.ToString)
        End Try
    End Sub
End Class

The ViewModel:

Public Class TTStraightViewModel
    Implements INotifyPropertyChanged
    Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler Implements 
    INotifyPropertyChanged.PropertyChanged
    Dim _chainTypes As ObservableCollection(Of ChainModel) = New ObservableCollection(Of ChainModel)()

    Public Sub New()

    End Sub

    Public Property ChainTypes As ObservableCollection(Of ChainModel)
        Get
            If _chainTypes.Count = 0 Then DBLoadChains()
            Return _chainTypes
        End Get
        Set(value As ObservableCollection(Of ChainModel))
            _chainTypes = value
            NotifyPropertyChanged("ChainTypes")
        End Set
    End Property

    Private Sub DBLoadChains()
        For Each row As DataRow In DBCollection.dsChains.Tables("ChainTable").Rows
            Dim display As String = row("Name").ToString
            Dim value As String = row("id").ToString
            If display = String.Empty Then display = value
            _chainTypes.Add(New ChainModel(value, display))
        Next
    End Sub
End Class

The XAML:

<Window x:Class="TtStraightView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace: LayoutTools" ResizeMode="NoResize" Width="Auto" 
xmlns:p="clr-namespace:LayoutTools.My" 
Title="Table Top Conveyor Straight" SizeToContent="WidthAndHeight" Height="Auto">

    <Window.DataContext>
        <local:TTStraightViewModel />
    </Window.DataContext>

    <Window.Resources>
        <p:MySettings x:Key="MySettings" />
    </Window.Resources>

    <StackPanel>
        <ComboBox Margin="0,3,0,3" Grid.Column="2" Grid.Row="1" Width="230" SelectedValuePath="ChainId" DisplayMemberPath="ChainType" ItemsSource="{Binding ChainTypes}"/>    
    </StackPanel>
</Window>

DBCollection.FillDataSet() is called during program startup.

Been grappling with this for days, research, previous code and all, and still can't figure out why the combobox doesn't show any items when I run the code. Any ideas?


Solution

  • Finally resolved this sticky situation of mine, and, for anyone working in VB.NET who might be remotely interested in how to populate a combobox with database data the MvvM way (without any codebehind), here's how I accomplished it.

    1) Create a class to emulate the properties of the database table as required:

    Public Class ChainModel
        Public Property ChainId As Integer
    
        Public Property ChainType As String
    
        Public Sub New(chainId As Integer, chainType As String)
            Me.ChainId = chainId
            Me.ChainType = chainType
        End Sub
    End Class
    

    2) Create a database class to populate the dataset to be used (be sure to have your database connection set up before this step):

    Public Class DBCollection
        Public Shared dsChains As DataSet
        Public Shared Sub FillDataSet()
            dsChains = New DataSet()
            Dim adpt As OleDbDataAdapter = New OleDbDataAdapter With {
                .SelectCommand = New OleDbCommand("SELECT id,Name FROM Chain ORDER BY Name, Manufacturer;", Cat.DbConn)
                }
            adpt.Fill(dsChains,"Chain")
            adpt.Dispose()
        End Sub
    End Class
    

    3) Set up the MvvM model class such that the dataset is populated in constructor, and the observable collection property of the model is populated directly from dataset:

    Public Class TTStraightModel
        Dim _chainType As String = My.Settings.TTStraightChainType
        Dim _chainTypeList As ObservableCollection(Of ChainModel)
    
        Public Sub New()
            DBCollection.FillDataSet()
        End Sub
    
        Public Property ChainType As String
            Get
                Return _chainType
            End Get
            Set(value As String)
                _chainType = value
                My.Settings.TTStraightChainType = _chainType
            End Set
        End Property
    
        Public Property ChainTypeList As ObservableCollection(Of ChainModel)
            Get
                _chainTypeList = New ObservableCollection(Of ChainModel)()
                For Each row As DataRow In DBCollection.dsChains.Tables("Chain").Rows
                    Dim display As String = row("Name").ToString
                    Dim value As String = row("id").ToString
                    If display = String.Empty Then display = value
                    _chainTypeList.Add(New ChainModel(value, display))
                Next
                Return _chainTypeList
            End Get
            Set(value As ObservableCollection(Of ChainModel))
                _chainTypeList = value
            End Set
        End Property
    End Class
    

    4) Set up the MvvM view-model such that it reads the model's observable collection property into an observable collection property of its own:

    Public Class TTStraightViewModel
        Implements INotifyPropertyChanged
        Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
        Public Event PropertyChanged As PropertyChangedEventHandler Implements 
    INotifyPropertyChanged.PropertyChanged
    
        ReadOnly _ttStraightModel As New TTStraightModel()
    
        Public Sub New()
        End Sub
    
        Public Property ChainType As String
            Get
                Return _ttStraightModel.ChainType
            End Get
            Set(value As String)
                _ttStraightModel.ChainType = value
                NotifyPropertyChanged("ChainType")
            End Set
        End Property
    
        Public Property ChainTypeList As ObservableCollection(Of ChainModel)
            Get
                Return _ttStraightModel.ChainTypeList
            End Get
            Set(value As ObservableCollection(Of ChainModel))
                _ttStraightModel.ChainTypeList = value
                NotifyPropertyChanged("ChainTypeList")
            End Set
        End Property
    End Class
    

    5) Finally, set up the MvvM XAML view such that the combobox binds directly to the view model's exposed property (after having first set up the view model as data context for the view):

    <Window x:Class="TtStraightView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CatScriptLayoutTools" ResizeMode="NoResize" Width="Auto" 
        xmlns:p="clr-namespace:CatScriptLayoutTools.My" 
        Title="Table Top Conveyor Straight" SizeToContent="WidthAndHeight" Height="Auto">
    
        <Window.DataContext>
            <local:TTStraightViewModel />
        </Window.DataContext>
    
        <Window.Resources>
            <p:MySettings x:Key="MySettings" />
        </Window.Resources>
    
        <StackPanel>
            <ComboBox Margin="0,3,0,3" Width="230" SelectedValuePath="ChainId" DisplayMemberPath="ChainType" ItemsSource="{Binding ChainTypeList}" SelectedItem="{Binding ChainType}"/>    
    </StackPanel>
    </Window>
    

    The SelectedValuePath="ChainId" DisplayMemberPath="ChainType" ensure that the proper id/value combination is bound to the combobox.