1

I am trying to do grouped items in a WPF ComboBox using XAML, and I found this that talks about using a list to drive the grouping. But the example Code Behind is of course not Powershell, and I am having a heck of a time groking how to build the list. Can someone point me at a good resource on CollectionViews in Powershell? I have zero experience in C/C++/C#, though I hope Powershell ends up being a gateway drug. ;) BTW, I did find this, that talks about doing the collection view in XAML, but the content of my collectionView is going to be dynamic, so I have to build it in code.

BTW, the ultimate goal is a combo box that looks like this...

Sets
   Arch
   MEP
   Viz

Packages
   RVT_2015
   RVT_2016
   MAX_2016

... where the actual sets and packages are dynamically pulled from some user customized XML, and the user can pick one set, or one package. Trying to do two lists and enforce one choice with a UX that isn't frustrating has been, less than effective. So I thought a single grouped combo box would address the problem nicely.

EDIT: Woot to progress!

$locations = @('Amsterdam', 'Berlin', 'London')
$sets = @('Arch', 'MEP', 'Viz')
$packages = @('RVT_2015', 'RVT_2016', 'MAX_2016')

[xml]$xaml = @"
<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen" ResizeMode="NoResize"
    Width = "300" Height = "200" ShowInTaskbar = "True" Background = "lightgray"> 
    <StackPanel>
        <Label  Content="Define your job:" VerticalAlignment="Center" HorizontalAlignment="Left"  />
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0" Content="Location" VerticalAlignment="Center" HorizontalAlignment="Left"  />
            <ComboBox Name="Location" Width="200"  
                Grid.Row="0" Grid.Column="1"
                VerticalAlignment="Center" HorizontalAlignment="Right"
                VerticalContentAlignment="Center"  
                Margin="0,0,0,0">             
            </ComboBox>

            <Label Grid.Row="1" Grid.Column="0" Content="Target" VerticalAlignment="Center" HorizontalAlignment="Left"  />
            <ComboBox Name="Target"  
                Grid.Row="1" Grid.Column="1"
                VerticalAlignment="Center" HorizontalAlignment="Right"
                VerticalContentAlignment="Center" 
                Width="200" Margin="0,0,0,0">
                <ComboBox.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name}"/>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </ComboBox.GroupStyle>
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}"/>
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </Grid>
    </StackPanel>
</Window>
"@

$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$window=[Windows.Markup.XamlReader]::Load($reader)

$data = @() 
foreach ($target in $sets) {
    $data += New-Object PSObject -prop @{Name="$target";Category="Sets"}
}
foreach ($target in $packages) {
    $data += New-Object PSObject -prop @{Name="$target";Category="Packages"}
}

$listView = [System.Windows.Data.ListCollectionView]$data
$listView.GroupDescriptions.Add((new-object System.Windows.Data.PropertyGroupDescription "Category"))

$location = $window.findname("Location")
$location.ItemsSource = $locations

$target = $window.findname("Target")
$target.ItemsSource = $listView


$window.ShowDialog() | out-null
Community
  • 1
  • 1
Gordon
  • 6,257
  • 6
  • 36
  • 89

1 Answers1

1

Based on the first example you have linked in your question, here is a simple implementation in PowerShell

Add-Type –assemblyName WindowsBase
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName PresentationFramework

[xml]$xaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <ComboBox Name="comboBox" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left"  
        Margin="10,20,0,0">
        <ComboBox.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Name}"/>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ComboBox.GroupStyle>
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>
</Window>
"@

$reader = (New-Object Xml.XmlNodeReader $xaml) 
$GUIWindow = [Windows.Markup.XamlReader]::Load( $reader ) 

$xaml.SelectNodes("//*[@Name]") | % {Set-Variable -Name ($_.Name) -Value $GUIWindow.FindName($_.Name)}

$data = @() 
$data += New-Object PSObject -prop @{Name="Arch";Category="Sets"}
$data += New-Object PSObject -prop @{Name="MEP";Category="Sets"}
$data += New-Object PSObject -prop @{Name="Viz";Category="Sets"}
$data += New-Object PSObject -prop @{Name="RVT_2015";Category="Packages"}
$data += New-Object PSObject -prop @{Name="RVT_2016";Category="Packages"}
$data += New-Object PSObject -prop @{Name="MAX_2016";Category="Packages"}

$lview = [System.Windows.Data.ListCollectionView]$data
$lview.GroupDescriptions.Add((new-object System.Windows.Data.PropertyGroupDescription "Category"))
$comboBox.ItemsSource = $lview


$GUIWindow.ShowDialog() | out-null

This is very basic, but you can build on that.

EDIT:

Original answer was using the following two lines:

$lview = [System.Windows.Data.ListCollectionView]::new($data)
$lview.GroupDescriptions.Add([System.Windows.Data.PropertyGroupDescription]::new("Category"))

They work in PS5, but will produce an error in all previous version of PowerShell. The workaround is to use Typecasting for ListCollectionView. This works in PS2.

$lview = [System.Windows.Data.ListCollectionView]$data
$lview.GroupDescriptions.Add((new-object System.Windows.Data.PropertyGroupDescription "Category"))

I have edited the code with this workaround.

Jan Chrbolka
  • 4,184
  • 2
  • 29
  • 38
  • Jan, thats exactly the kick start I needed! Thanks. One clarification, I am PS v2 only for the moment. I am not in a position to force my customers to upgrade, and most of my industry is clinging to Windows 7 like a baby sloth to it's mother. I ask about v2 because I am getting Method invocation failed because [System.Windows.Data.ListCollectionView] doesn't contain a method named 'new'. on the two $lview lines and was thinking I probably need .NET 4.5 for this, correct? – Gordon Jun 16 '15 at 14:38
  • Hmm, seems it isn't a .NET version problem, as I tried ([System.Windows.Data.ListCollectionView]) | Get-Member on a machine with .NET 4.5 installed, and I get the same errors. looks like I have some more Googling to do in the morning. – Gordon Jun 16 '15 at 20:59
  • @gordon Yep, I can reproduce that error on anything up to PS4 and .NET 4.5. Strange, since I can't find a reference to any PowerShell changes relating to WPF. Anyway, I have edited the answer with a workaround. – Jan Chrbolka Jun 17 '15 at 00:50
  • Jan, thanks so much. That has me making good progress. One last clarification. Based on the revised code up top, I have two combo boxes now, one that groups and one that doesn't. The ungrouped one starts in an indeterminate state, while the grouped one starts with the first item selected, but I don't see anything in the XML that defines start condition for either. I am guessing using GroupStyle changes default behavior and I need to Google for the XML that allows for indeterminate default with GroupStyle? Or am I headed down a deadened with that assumption? – Gordon Jun 17 '15 at 08:38
  • @Gordon Glad it worked. As for the Grouped ComboBox automatically selecting the first item, I thing this is caused by the collection view. Don't know how to prevent that, but you can just use `$target.SelectedItem = $null` to unselect it before showing the form. – Jan Chrbolka Jun 17 '15 at 23:09
  • Dang, that should have been obvious! Thanks again. In the end I might not need it, as I may have a need for the first option to be "All", and having that be the default would be fine. But good to know know how to do it in any case. Next step is methods to ensure Location is picked, since I don't think I am going to allow an All option there yet. – Gordon Jun 18 '15 at 17:29