Standard WPF setup...grid defined in XAML, datacontext set to an instance of a class that exposes the datatable as a property. Business logic is in the datatable class (validation, default values, event handlers).
<UserControl x:Class="AllowablesView"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
xmlns:toolkit="<a href="http://schemas.microsoft.com/wpf/2008/toolkit">http://schemas.microsoft.com/wpf/2008/toolkit</a>"
xmlns:local="clr-namespace:Parallax.AllowableBottles"
Name="TheView"
Height="300" Width="300">
<UserControl.Resources>
<local:AllowablesViewModel x:Key="AllowablesVM"/>
<BooleanToVisibilityConverter x:Key="VisibilityConverter"/>
<local:BooleanToOracleFlagConverter x:Key="FlagConverter"/>
</UserControl.Resources>
<Grid DataContext="{StaticResource AllowablesVM}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ToolBar Grid.Row="0" Grid.Column="0" Name="tb">
<Button Command="{Binding SaveCommand}">Save</Button>
<Button Command="{Binding RevertCommand}">Revert</Button>
<Button Name="btnRefresh">Refresh</Button>
<ProgressBar MinWidth="75" Visibility="{Binding IsWorking, Converter={StaticResource VisibilityConverter}}" IsIndeterminate="True"/>
</ToolBar>
<WrapPanel Grid.Row="0" Grid.Column="1">
<TextBlock VerticalAlignment="Center">Search:</TextBlock>
<TextBox Margin="2" Name="txtAnalysis" MinWidth="50" TabIndex="0"></TextBox>
<Button Margin="2" Command="{Binding FillCommand}" CommandParameter="{Binding ElementName=txtAnalysis, Path=Text, ValidatesOnExceptions=True}" IsDefault="True">_Go</Button>
</WrapPanel>
<toolkit:DataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Allowables, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"
AutoGenerateColumns="False" IsReadOnly="{Binding IsReadOnly}" Background="White"
FrozenColumnCount="2" AlternatingRowBackground="LightGreen" Name="dg" RowHeaderWidth="40">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTextColumn Binding="{Binding MASTER_ANALYSISNUMBER, Mode=OneWay, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"
IsReadOnly="True" Header="Analysis #"/>
<toolkit:DataGridComboBoxColumn TextBinding="{Binding BOTTLE_CODE, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"
Header="Bottle Code">
<toolkit:DataGridComboBoxColumn.ItemsSource>
<Binding Source="{StaticResource AllowablesVM}" Path="ValidBottles"/>
</toolkit:DataGridComboBoxColumn.ItemsSource>
</toolkit:DataGridComboBoxColumn>
<toolkit:DataGridCheckBoxColumn Binding="{Binding DEFAULT_FLAG, Converter={StaticResource FlagConverter}, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Header="Default"/>
<toolkit:DataGridTextColumn Binding="{Binding ANALYSIS_VOLUME, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Header="Vol. Needed"/>
<toolkit:DataGridComboBoxColumn TextBinding="{Binding UNITS, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, Mode=TwoWay}" Header="Units">
<toolkit:DataGridComboBoxColumn.ItemsSource>
<Binding Source="{StaticResource AllowablesVM}" Path="ValidBottleUnits"/>
</toolkit:DataGridComboBoxColumn.ItemsSource>
</toolkit:DataGridComboBoxColumn>
<toolkit:DataGridTextColumn Binding="{Binding ANALYSIS_HOLDINGTIME, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Header="Holding Time"/>
<toolkit:DataGridComboBoxColumn TextBinding="{Binding HOLDTIME_UNITS, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Header="Holdtime Units">
<toolkit:DataGridComboBoxColumn.ItemsSource>
<Binding Source="{StaticResource AllowablesVM}" Path="ValidHoldtimeUnits"/>
</toolkit:DataGridComboBoxColumn.ItemsSource>
</toolkit:DataGridComboBoxColumn>
<toolkit:DataGridCheckBoxColumn Binding="{Binding SEPARATEBOTTLE_FLAG, Converter={StaticResource FlagConverter}, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Header="Sep. Bot. Req."/>
<toolkit:DataGridComboBoxColumn TextBinding="{Binding BOTTLETRACKING_TEXT, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Header="Type">
<toolkit:DataGridComboBoxColumn.ItemsSource>
<Binding Source="{StaticResource AllowablesVM}" Path="ValidTracking"/>
</toolkit:DataGridComboBoxColumn.ItemsSource>
</toolkit:DataGridComboBoxColumn>
<toolkit:DataGridTextColumn Binding="{Binding BOTTLE_COUNT, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Header="Min. Count"/>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
</Grid>
</UserControl>
Imports System.Data
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Public Class AllowablesViewModel
Implements INotifyPropertyChanged
Private WithEvents mDataSet As AllowablesDataSet
Private mConnectionString As String
Public Property ConnectionString() As String
Get
Return mConnectionString
End Get
Set(ByVal value As String)
If value <> mConnectionString AndAlso value <> String.Empty Then
Me.DataSet = New AllowablesDataSet(value)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ValidBottles"))
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ValidTracking"))
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ValidHoldtimeUnits"))
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ValidBottleUnits"))
End If
End Set
End Property
Private Property DataSet() As AllowablesDataSet
Get
Return mDataSet
End Get
Set(ByVal value As AllowablesDataSet)
If value IsNot mDataSet Then
mDataSet = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("DataSet"))
End If
End Set
End Property
Private mFillCommand As RelayCommand
Public ReadOnly Property FillCommand() As ICommand
Get
If mFillCommand Is Nothing Then
mFillCommand = New RelayCommand(AddressOf FillDB, _
Function() mDataSet IsNot Nothing _
AndAlso mDataSet.HasConnection)
End If
Return mFillCommand
End Get
End Property
Public Sub FillDB(ByVal analysisNumber As String)
Dim t As New System.Threading.Thread(AddressOf mDataSet.Fill)
Me.IsWorking = True
t.Start(analysisNumber)
End Sub
Private Sub mDataSet_DataSetFilled(ByVal sender As Object, ByVal e As System.EventArgs) Handles mDataSet.DataSetFilled
Me.Allowables = New System.Data.DataView(mDataSet.ALLOWABLE_BOTTLESVIEW)
Me.IsWorking = False
End Sub
Private WithEvents mAllowables As System.Data.DataView
Public Property Allowables() As System.Data.DataView
Get
If mAllowables Is Nothing Then
mAllowables = mDataSet.ALLOWABLE_BOTTLESVIEW.DefaultView
End If
Return mAllowables
End Get
Private Set(ByVal value As System.Data.DataView)
If mAllowables IsNot value Then
mAllowables = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Allowables"))
End If
End Set
End Property
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private mSaveCommand As RelayCommand
Public ReadOnly Property SaveCommand() As ICommand
Get
If mSaveCommand Is Nothing Then
mSaveCommand = New RelayCommand(AddressOf SaveToDB, _
Function() _
Me.DataSet IsNot Nothing _
AndAlso Me.DataSet.HasChanges AndAlso Not Me.IsWorking)
End If
Return mSaveCommand
End Get
End Property
Private Sub SaveToDB()
Dim t As New System.Threading.Thread(AddressOf mDataSet.SaveToDB)
Me.IsWorking = True
t.Start()
End Sub
Private mIsWorking As Boolean
Public Property IsWorking() As Boolean
Get
Return mIsWorking
End Get
Set(ByVal value As Boolean)
If mIsWorking <> value Then
mIsWorking = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsLoading"))
End If
End Set
End Property
Private mRevertCommand As RelayCommand
Public ReadOnly Property RevertCommand() As ICommand
Get
If mRevertCommand Is Nothing Then
mRevertCommand = New RelayCommand(AddressOf RejectChanges, _
Function() _
Me.DataSet IsNot Nothing _
AndAlso Me.DataSet.HasChanges)
End If
Return mRevertCommand
End Get
End Property
Private Sub RejectChanges()
mDataSet.RejectChanges()
Me.Allowables = New System.Data.DataView(Me.DataSet.ALLOWABLE_BOTTLESVIEW)
End Sub
Private mIsReadOnly As Boolean
Public Property IsReadOnly() As Boolean
Get
Return mIsReadOnly
End Get
Set(ByVal value As Boolean)
If value <> mIsReadOnly Then
mIsReadOnly = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsReadOnly"))
End If
End Set
End Property
Private Sub mDataSet_NoRowsFound(ByVal sender As Object, ByVal e As System.EventArgs) Handles mDataSet.NoRowsFound
MessageBox.Show("There were no allowables found for that analysis.", "No Allowables Found", MessageBoxButton.OK, MessageBoxImage.Exclamation)
End Sub
Public ReadOnly Property ValidBottles() As List(Of String)
Get
If Me.DataSet IsNot Nothing Then
Return Me.DataSet.BottleCodes
Else
Return Nothing
End If
End Get
End Property
Public ReadOnly Property ValidTracking() As List(Of String)
Get
If Me.DataSet IsNot Nothing Then
Return Me.DataSet.BottleTrackingOptions
Else
Return Nothing
End If
End Get
End Property
Public ReadOnly Property ValidHoldtimeUnits() As List(Of String)
Get
If Me.DataSet IsNot Nothing Then
Return Me.DataSet.ValidHoldtimeUnits
Else
Return Nothing
End If
End Get
End Property
Public ReadOnly Property ValidBottleUnits() As List(Of String)
Get
If Me.DataSet IsNot Nothing Then
Return Me.DataSet.BottleUnits
Else
Return Nothing
End If
End Get
End Property
End Class
And then just a standard typed dataset with stuff happening in the ColumnChanging and ColumnChanged events of the lone DataTable class.