nkasco
nkasco

Reputation: 335

ListBox Alternating Shading PowerShell

I seem to be stuck trying to add a bit of style to my form. I have a ListBox and I want to add alternate shading to every other row. Is this even possible? I tried looking at the $ListBox.Items property and below that I don't see anything for background options. Any ideas?

$ListBox = New-Object System.Windows.Forms.ListBox
$ListBox.Size = '325,95'
$ListBox.Location = '345,25'
$ListBox.Items.Add("Checking...") > $null

Upvotes: 0

Views: 1659

Answers (2)

boeprox
boeprox

Reputation: 1868

By the looks of your code, you aren't using XAML, but I wanted to add this anyways as an alternative approach.

You can set this by setting up a style trigger by writing XAML as your front end code for the UI and specifying the setter properties within the trigger. Then within your ListBox control, you can specify the name of the style that you created on the ItemContanerStyle property and specify an AlertnationCount of 2 so it highlights each row with the colors you specified.

My example below shows how it works as you add text to the list box.

#Build the GUI
[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" 
    Width = "313" Height = "800" ShowInTaskbar = "True" Background = "lightgray"> 
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <StackPanel >
            <StackPanel.Resources>
                <Style x:Key="AlternatingRowStyle" TargetType="{x:Type Control}" >
                    <Setter Property="Background" Value="LightBlue"/>
                    <Setter Property="Foreground" Value="Black"/>
                    <Style.Triggers>
                        <Trigger Property="ItemsControl.AlternationIndex" Value="1">                            
                            <Setter Property="Background" Value="White"/>
                            <Setter Property="Foreground" Value="Black"/>                                
                        </Trigger>                            
                    </Style.Triggers>
                </Style>     
            </StackPanel.Resources>
            <TextBox  IsReadOnly="True" TextWrapping="Wrap">
                Type something and click Add
            </TextBox>
            <TextBox x:Name = "inputbox"/>
            <Button x:Name="button1" Content="Add"/>
            <Button x:Name="button2" Content="Remove"/>
            <Expander IsExpanded="True">
                <ListBox x:Name="listbox" SelectionMode="Extended" AlternationCount="2"                 
                ItemContainerStyle="{StaticResource AlternatingRowStyle}"/>
            </Expander >
        </StackPanel>
    </ScrollViewer >
</Window>
"@

$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
 
#region Connect to Controls    
Write-Verbose "Connecting to controls"
$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
    New-Variable -Name $_.Name -Value $Window.FindName($_.Name) -Force
}
#endregion Connect to Controls

$Window.Add_SourceInitialized({
    #Have to have something initially in the collection
    $Script:observableCollection = New-Object System.Collections.ObjectModel.ObservableCollection[string]
    $listbox.ItemsSource = $observableCollection
    $inputbox.Focus()
})
 
#Events
$button1.Add_Click({
     $observableCollection.Add($inputbox.text)
     $inputbox.Clear()
})
$button2.Add_Click({
    ForEach ($item in @($listbox.SelectedItems)) {
        $observableCollection.Remove($item)
    }
}) 
$Window.ShowDialog() | Out-Null

Upvotes: 0

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174815

The only way to do this in with the ListBox control in Windows Forms is by hijacking the actual drawing of each row.

First, change the DrawMode property of the ListBox:

$ListBox.DrawMode = [System.Windows.Forms.DrawMode]::OwnerDrawFixed

This will allow us to override graphic rendering of the items via the DrawItem event.

Now all we need is to define the function that will draw the items. I found this great example in C# on doing alternate row colors without affecting selected items.

Luckily, C# is easily ported to PowerShell:

$ListBox.add_DrawItem({

    param([object]$s, [System.Windows.Forms.DrawItemEventArgs]$e)

    if ($e.Index -gt -1)
    {
        Write-Host "Drawing item at index $($e.Index)"

        <# If the item is selected set the background color to SystemColors.Highlight 
         or else set the color to either WhiteSmoke or White depending if the item index is even or odd #>
        $color = if(($e.State -band [System.Windows.Forms.DrawItemState]::Selected) -eq [System.Windows.Forms.DrawItemState]::Selected){ 
            [System.Drawing.SystemColors]::Highlight
        }else{
            if($e.Index % 2 -eq 0){
                [System.Drawing.Color]::WhiteSmoke
            }else{
                [System.Drawing.Color]::White
            }
        }

        # Background item brush
        $backgroundBrush = New-Object System.Drawing.SolidBrush $color
        # Text color brush
        $textBrush = New-Object System.Drawing.SolidBrush $e.ForeColor

        # Draw the background
        $e.Graphics.FillRectangle($backgroundBrush, $e.Bounds)
        # Draw the text
        $e.Graphics.DrawString($s.Items[$e.Index], $e.Font, $textBrush, $e.Bounds.Left, $e.Bounds.Top, [System.Drawing.StringFormat]::GenericDefault)

        # Clean up
        $backgroundBrush.Dispose()
        $textBrush.Dispose()
    }
    $e.DrawFocusRectangle()
})

Et voila:

Alternate row colors in ListBox

Upvotes: 1

Related Questions