locked
WPF ListBox 'navigation on keypress' when itemsource is an ObservableCollection Class RRS feed

  • Question

  • Alrighty, go easy on me, I am pretty new to WPF and also to this forum. I should say before I start, the entire project is complete and functional, save this feature.

    The goal is to be able to replicate the 'keyboard navigation behaviour' of 'Forms Listbox' in WPF (system.windows.controls.listbox).

    In WPF, I found had to bind an ItemSource of type ObservableCollection<string> to the listbox, because its contents change, and WPF does not allow items bound directly to the listbox control to be modified (once bound). Therefore I am updating the Observable collection when needed and this all works fine. What I am getting confused about is how to bind the ItemsControlClass to the observable collection. The ItemsControlClass has a settable boolean istextsearchenabled, which 'seems' to needed to be set against the ObservableCollection to allow is to be searched, but here I am woolly! 

    It seems this post solves a pretty similar problem to the one i am trying to solve, again i am not clear on this.

    Here is some test code to show the issue, once loaded you will observe that it isn't possible to navigate the listbox from the keyboard. This is the goal. Up, down and by typing the string names.

    #region Create Some Test Data
    
    $rootdse = Get-ADRootDSE
    
    [hashtable]$objectAttributes = @{}
    
    $ObjectClasses = ("User","Group")
    
    for ($i=0; $i -le $ObjectClasses.count -1; $i++)
    {
        $t = $ObjectClasses[$i]
        [void]$objectAttributes.add($t,$(Get-ADObject -SearchBase ($rootdse).SchemaNamingContext -Filter {name -like $t} -Properties MayContain,SystemMayContain | Select-Object @{n="Attributes";e={$_.maycontain + $_.systemmaycontain}} | Select-Object -ExpandProperty Attributes))
    }
    
    #endregion
    
    #region WPF xaml
    
    Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration
    
    [xml]$xaml = '<Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            Title="Test" Height="400" Width="250" Background="WhiteSmoke"  WindowStyle="ThreeDBorderWindow">
        <Grid Margin="0,0,0,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid Margin="10,10,0,0">
                <ComboBox Name="ComboBox" Padding="0" HorizontalAlignment="Left" Height="20" Margin="0,0,0,0" VerticalAlignment="Top" Width="100"/>
                <ListBox Name="ListBox" Padding="0" FontSize="9.5" HorizontalAlignment="Left" Width="210" Height="300" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Margin="0,40,0,0" VerticalAlignment="Top" IsTextSearchEnabled="True" SelectionMode="Single"/>
            </Grid>
        </Grid>
    </Window>'
    
    #endregion
    
    # Generate Powershell Window.
    $TestWindow = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml))
    $TestWindow.ResizeMode="NoResize"
    $xaml.SelectNodes("//*[@Name]") |% { Set-Variable -Name ($_.Name) -Value $TestWindow.FindName($_.Name) }
    
    # Create Dependent Items
    $Sorting = New-Object System.ComponentModel.SortDescription("","Ascending")
    $ListBoxItems = New-Object System.Collections.ObjectModel.ObservableCollection[string]
    
    # Handler for combobox Selection Changed.
    $handler_ComboBox_SelectionChanged =
    {
        $ListBoxItems.clear()
        $ref = $ComboBox.SelectedItem
        $objectAttributes[$ref] |% { $ListBoxItems.add($_) }
        $ListBox.Items.SortDescriptions.Add($Sorting)
    }
    
    # Update Control functionality.
    $ComboBox.ItemsSource = $ObjectClasses
    $ComboBox.add_SelectionChanged($handler_ComboBox_SelectionChanged)
    $ListBox.ItemsSource = $ListBoxItems
    
    # Add Functional attributes.
    $TestWindow.Add_Closing({[System.Windows.Forms.Application]::Exit(); Stop-Process $pid})
    $TestWindow.show()
    [void] $TestWindow.Activate()
    
    $app = New-Object System.Windows.Forms.ApplicationContext
    [void] [System.Windows.Forms.Application]::Run($app)

    Thanks for any solutions.

    Wednesday, January 30, 2019 12:32 PM

Answers

  • You cannot muse "Run" in PowerShell. PowerShell forms (WinForms or WPF) only work correctly with "ShowDialog".  "App.Run" requires multithreading and PS only supports on main thread. Replace the last lines:

    $TestWindow.show()
    [void] $TestWindow.Activate()
    
    $app = New-Object System.Windows.Forms.ApplicationContext
    [void] [System.Windows.Forms.Application]::Run($app)

    With:

    $TestWindow.ShowDialog()

    and the listbox will work.  The other lines only work in a compiled WPF application.


    \_(ツ)_/

    • Marked as answer by Anthony Guyon Wednesday, January 30, 2019 3:50 PM
    Wednesday, January 30, 2019 3:39 PM

All replies

  • You cannot muse "Run" in PowerShell. PowerShell forms (WinForms or WPF) only work correctly with "ShowDialog".  "App.Run" requires multithreading and PS only supports on main thread. Replace the last lines:

    $TestWindow.show()
    [void] $TestWindow.Activate()
    
    $app = New-Object System.Windows.Forms.ApplicationContext
    [void] [System.Windows.Forms.Application]::Run($app)

    With:

    $TestWindow.ShowDialog()

    and the listbox will work.  The other lines only work in a compiled WPF application.


    \_(ツ)_/

    • Marked as answer by Anthony Guyon Wednesday, January 30, 2019 3:50 PM
    Wednesday, January 30, 2019 3:39 PM
  • Also remove the "Closing" code.  The script will terminate correctly without it when run as a script file.


    \_(ツ)_/

    Wednesday, January 30, 2019 3:43 PM
  • Thank you so much - perfectly accurate!

    Also fixed a child window not showing...

    Wednesday, January 30, 2019 3:51 PM
  • This is actually much easier that you are making it which is what happens when you try to convert arbitrary C# code.  PowerShell adds much functionality to all objects and allows us to just directly do things.  Also understanding how types are managed inNet and how they are "coerced" between types helps  us to know how we can just directly use a type when a specific type is specified.  Most related types can be "cast" and PowerShell does this for you.

    [xml]$xaml = '<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Title="Test" Height="400" Width="250" Background="WhiteSmoke" WindowStyle="ThreeDBorderWindow"> <Grid Margin="0,0,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid Margin="10,10,0,0"> <ComboBox Name="ComboBox" Padding="0" HorizontalAlignment="Left" Height="20" Margin="0,0,0,0" VerticalAlignment="Top" Width="100"/> <ListBox Name="ListBox" Padding="0" FontSize="9.5" HorizontalAlignment="Left" Width="210" Height="300" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Margin="0,40,0,0" VerticalAlignment="Top" IsTextSearchEnabled="True" SelectionMode="Single"/> </Grid> </Grid> </Window>' #endregion # get required data $adclasses = @{ User = Get-AdUser -Filter * | select -expand name Group = Get-AdGroup -Filter * | select -expand Name } # Generate Powershell Window Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration $reader = [System.Xml.XmlNodeReader]::New($xaml) [System.Windows.Window]$window = [Windows.Markup.XamlReader]::Load($reader) $window.WindowStyle = 'SingleBorderWindow' $window.WindowStartupLocation = 'CenterScreen' $window.ResizeMode = 'NoResize'

    #by adding the type we get auto-complete and intelllisense to work correctly [System.Windows.Controls.ComboBox]$comboBox = $window.FindName('ComboBox') [System.Windows.Controls.ListBox]$listBox = $window.FindName('ListBox') $listBox.Items.SortDescriptions.Add([System.ComponentModel.SortDescription]::New('', 'Ascending'))
    # Handler for combobox Selection Changed. $ComboBox.add_SelectionChanged({ $ListBox.ItemsSource = $null $listBox.ItemsSource = $adclasses[$comboBox.SelectedItem] }) $ComboBox.ItemsSource = $adclasses.Keys $window.ShowDialog()



    \_(ツ)_/



    • Edited by jrv Wednesday, January 30, 2019 4:52 PM
    Wednesday, January 30, 2019 4:46 PM