Valiante
Valiante

Reputation: 1226

PowerShell WPF - Binding DataGrid ComboBox to Column In ItemsSource

I am trying to populate a ComboBox within a DataGrid with unique values from a given column, however I'm getting unexpected results in that it splits the value from that row into individual characters and populates each ComboBox with said characters.

Here's a simple example script of my issue;

$csv = "ID,Fruit,Owner`r`n"
$csv += "1,Apple,Andrew`r`n"
$csv += "2,Banana,Bill`r`n"
$csv += "3,Cherry,Charles`r`n"
$csv += "4,Date,Daniel`r`n"
$csv += "5,Elderberry,Ethan`r`n"

$data = ConvertFrom-Csv $csv

$inputXML = @"
<Window x:Name="DataGridComboTest" x:Class="DataGridComboTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DataGridComboTest" Width="640" Height="480" WindowStartupLocation="CenterScreen">
    <Grid>
        <DataGrid x:Name="DataGrid" Margin="10,10,10,10" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding ID}" Header="ID"/>
                <DataGridTextColumn Binding="{Binding Fruit}" Header="Fruit"/>
                <DataGridTextColumn Binding="{Binding Owner}" Header="Owner"/>

                <DataGridTemplateColumn Header="Owner Combo">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox
                                SelectedItem="{Binding Path=Owner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                ItemsSource="{Binding Owner}"
                                Text="{Binding Path=Owner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                            </ComboBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
"@

$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N'  -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML

$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Form=[Windows.Markup.XamlReader]::Load( $reader )
$xaml.SelectNodes("//*[@Name]") | %{ Set-Variable -Name "$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop }

$DataGrid.ItemsSource = $data

$Form.ShowDialog() | Out-Null

What I'd like to be able to do is select a different owner for each fruit from the existing owners in the table, however instead I'm given a choice of each letter in the adjacent owner's name;

Example screenshot

Upvotes: 1

Views: 2439

Answers (2)

mm8
mm8

Reputation: 169360

You should bind the ItemsSource to an IEnumerable<string> rather than a scalar string (which is an IEnumerable<char>).

Try something like this:

...
ItemsSource="{DynamicResource owners}"
...

$owners = $data | Select-Object -ExpandProperty Owner -Unique
$Form.Resources.Add("owners", $owners)

Upvotes: 1

Valiante
Valiante

Reputation: 1226

With mm8's help I was able to come up with a working script;

$csv = "ID,Fruit,Owner`r`n"
$csv += "1,Apple,Andrew`r`n"
$csv += "2,Banana,Bill`r`n"
$csv += "3,Cherry,Charles`r`n"
$csv += "4,Date,Daniel`r`n"
$csv += "5,Elderberry,Ethan`r`n"
$csv += "6,Fig,Bill`r`n"
$csv += "7,Grape,Daniel`r`n"

$data = ConvertFrom-Csv $csv

$inputXML = @"
<Window x:Name="DataGridComboTest" x:Class="DataGridComboTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DataGridComboTest" Width="640" Height="480" WindowStartupLocation="CenterScreen">
    <Grid>
        <DataGrid x:Name="DataGrid" Margin="10,10,10,10" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding ID}" Header="ID"/>
                <DataGridTextColumn Binding="{Binding Fruit}" Header="Fruit"/>
                <DataGridTextColumn Binding="{Binding Owner}" Header="Owner"/>

                <DataGridTemplateColumn Header="Owner Combo">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox
                                SelectedItem="{Binding Path=Owner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                ItemsSource="{DynamicResource owners}"
                                Text="{Binding Path=Owner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                            </ComboBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
"@

$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N'  -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML

$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Form=[Windows.Markup.XamlReader]::Load( $reader )
$xaml.SelectNodes("//*[@Name]") | %{ Set-Variable -Name "$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop }

$DataGrid.ItemsSource = $data

#$owners = [Linq.Enumerable]::ToArray($data | Select-Object -ExpandProperty Owner)
$owners = $data | Select-Object -ExpandProperty Owner -Unique
$Form.Resources.Add("owners", $owners)

$Form.ShowDialog() | Out-Null

The DynamicResource binding was key, but the [Linq.Enumerable] line from mm8's answer threw an error. However simplifying it to select unique owners from $data resolved this. I also added a couple of duplicate owners to the source data to more accurately simulate a real-world scenario, hence "-Unique".

DataGridCombo working example

Upvotes: 1

Related Questions