Writing my first project in WPF and cannot wrap my mind around fallowing problem.
I have a DataGrid that uses ItemSource from DataSet Table (local DB in XML) User must be able to add columns to DataSet/DataGrid and set column DataTemplate, like text, image, date, ...
So I must use single DataTemplate for multiple columns, and change binding path based on column name, like:
<DataTemplate x:Key="ImageColumnTemplate">
<Grid>
<Image Source="{Binding Path=CURRENT_COLUMN_NAME Converter={StaticResource ImageReader}}" />
<TextBox Text="{Binding Path=CURRENT_COLUMN_NAME}"/>
</Grid>
</DataTemplate>
I understand that this approach is not correct, but I failed to find solution that:
-Is not XAML serialization / cloning based - does not work because loses parent references.
-Able to write value to row unlike "Path=." using inherited DataGridBoundColumn instead of DataGridTemplateColumn.
DataGridTextColumn does this is some way, and it works:
Dim fGridCol = New DataGridTextColumn() With {.Header = fColumn.ColumnName}
fGridCol.Binding = New Binding(fColumn.ColumnName) With {.Mode = BindingMode.TwoWay}
But DataGridTemplateColumn has no bind, and DataGridBoundColumn does not write value if inherited.
How can you do make this work?
EDIT
Allow me to put my question in different context:
The best I have got so far:
<Window x:Class="MainWindow"
...
<Window.Resources>
<local:CellStringReader x:Key="StringReader" />
<local:CellImageReader x:Key="ImageReader" />
<Style x:Key="TextBlockToggle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=IsEditing}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="TextBoxToggle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell}, Path=IsEditing}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="ImageColumnTemplate">
<Grid Focusable="True">
<Grid HorizontalAlignment="Left" Background="Transparent">
<Button PreviewMouseDown="SelectImageFile" >
<Image x:Name="ImageTemplateImage" Height="20" Width="20"
Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, UpdateSourceTrigger=PropertyChanged , Converter={StaticResource ImageReader}}"/>
</Button>
</Grid>
<TextBlock x:Name="ImageTemplateTextBlock" Margin="25,0,0,0"
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, UpdateSourceTrigger=PropertyChanged , Converter={StaticResource StringReader}}"/>
<TextBox x:Name="ImageTemplateTextBox" Margin="23,0,0,0" BorderThickness="0" Style="{StaticResource TextBoxToggle}"
Text="{Binding Mode=TwoWay, Path=., RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource StringReader}}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
...
<DataGrid x:Name="LocalGrid" Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.RowValidationRules>
<local:RowDataValidationRule/>
</DataGrid.RowValidationRules>
</DataGrid>
...
</Grid>
</Window>
And
Class MainWindow
Protected Overrides Sub OnInitialized(e As EventArgs)
LocalGrid.ItemsSource = Base.Tables("Local").DefaultView
CreateColumns()
End Sub
Private WithEvents Base As New Base
Private WithEvents LocalTable As DataView = Base.Tables("Local").DefaultView
Private Sub CreateColumns()
Dim LocalTable = Base.Tables("Local")
Dim TypesTable = Base.Tables("ColumnTypes")
For Each fColumn As DataColumn In LocalTable.Columns
Dim ColumnType As String = (From fRow As DataRowView In TypesTable.DefaultView Where fRow.Item("Name") = String.Format("Local." & fColumn.ColumnName) Select fRow.Item("Template") Take 1).FirstOrDefault()
If ColumnType = "Image" Then 'THIS IS IMAGE COLUMN
Dim ImageColumn As New DataGridTemplateColumn With {.Header = fColumn.ColumnName}
ImageColumn.CellTemplate = Me.FindResource("ImageColumnTemplate")
ImageColumn.CellEditingTemplate = Me.FindResource("ImageColumnTemplate")
LocalGrid.Columns.Add(ImageColumn)
Else 'THIS IS REGILAR COLUMN
Dim fGridCol = New DataGridTextColumn() With {.Header = fColumn.ColumnName}
fGridCol.Binding = New Binding(fColumn.ColumnName) With {.Mode = BindingMode.TwoWay, .UpdateSourceTrigger = UpdateSourceTrigger.LostFocus}
LocalGrid.Columns.Add(fGridCol)
End If
Next
End Sub
Private Sub SelectImageFile(ByVal sender As Object, ByVal e As RoutedEventArgs)
'This creates OpenFileDialog on button click
End Sub
End Class
Public Class CellStringReader : Implements IValueConverter
Private EditingCell As DataGridCell
Public Overridable Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
Dim Cell As DataGridCell = value
Dim Row As DataRowView = Cell.DataContext
Dim Column As DataGridColumn = Cell.Column
If Cell.IsEditing Then
EditingCell = Cell
Else
EditingCell = Nothing
End If
Return Row.Item(Column.Header)
End Function
Public Overridable Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
If EditingCell Is Nothing Then 'This is not callded, ever.
Throw New Exception("No cell editing")
End If
Return EditingCell
End Function
End Class
Public Class CellImageReader : Inherits CellStringReader
Public Overrides Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object
value = MyBase.Convert(value, targetType, parameter, culture)
If IsDBNull(value) OrElse String.IsNullOrWhiteSpace(value) Then
Return Nothing
ElseIf IO.File.Exists(value) Then
Return New BitmapImage(New Uri(value))
End If
End Function
Public Overrides Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object
Throw New NotSupportedException
End Function
End Class
The problem is, that editing TextBox in generated Image column does not call CellStringReader.ConvertBack() and does not write changed value of underlying DataRow.
I understand that this is because "Path=." in TextBox Binding, but I don't know any alternatives.
Parsing XAML in string breaks Button PreviewMouseDown, because of missing context, and it does not write value anyway.
My question is how to make TextBox write new value in DataRow.?
Hope it makes more seance now & sorry for long post.