Wednesday, April 3, 2013

Silverlight listbox item selection binding with dynamic border setting

The requirement needs the listboxitem with a dynamic border by a binding property. Once I implemented this by adding a border tag over the ContentPresenter, it automatically overwrote the default selection border. That means when you click the item from listbox, there's nothing happening with the border. It took me a few of days to fight on the dynamic borders of Silverlight listbox items. Finally it comes up with the VSM (VisualStateManager) solution as following code. One special thing that happening on my case is if you click once to select the item, the first item would be always auto selected, you'd have to click other item or other area once again to dime the first item border selection. The fix will be commenting the Storeboard of Focused VisualState. Here shows you the entire code.

(Notice: The 2 converters by binding Tag were my custom function to give the BorderBrush a custom color by listbox sourceitem property, it will need to implement IValueConverter as a reference class as the bottom code)

Xaml:

<ListBox x:Name="SKUImageListBox" Height="250" Width="600" SelectionMode="Single" HorizontalContentAlignment="Left" TabIndex="1"

HorizontalAlignment="Left" VerticalAlignment="Top" Margin="3,2,115,0" DataContext="{Binding}">

<ListBox.ItemContainerStyle>

<Style TargetType="ListBoxItem">

<Setter Property="Padding" Value="3" />

<Setter Property="HorizontalContentAlignment" Value="Left" />

<Setter Property="VerticalContentAlignment" Value="Top" />

<Setter Property="Background" Value="Transparent" />

<Setter Property="BorderThickness" Value="1"/>

<Setter Property="TabNavigation" Value="Local" />

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="ListBoxItem">

<Grid Background="{TemplateBinding Background}">

<vsm:VisualStateManager.VisualStateGroups>

<vsm:VisualStateGroup x:Name="CommonStates">

<vsm:VisualState x:Name="Normal" >

<Storyboard>

<DoubleAnimation Storyboard.TargetName="BorderSquare" Storyboard.TargetProperty="Opacity" Duration="0" To=".50"/>

</Storyboard>

</vsm:VisualState>

<vsm:VisualState x:Name="MouseOver">

<Storyboard>

<DoubleAnimation Storyboard.TargetName="fillColor" Storyboard.TargetProperty="Opacity" Duration="0" To=".35"/>

</Storyboard>

</vsm:VisualState>

<vsm:VisualState x:Name="Disabled">

<Storyboard>

<DoubleAnimation Storyboard.TargetName="ContentPresenterImg" Storyboard.TargetProperty="Opacity" Duration="0" To=".55" />

</Storyboard>

</vsm:VisualState>

</vsm:VisualStateGroup>

<vsm:VisualStateGroup x:Name="SelectionStates">

<vsm:VisualState x:Name="Unselected" />

<vsm:VisualState x:Name="Selected">

<Storyboard>

<DoubleAnimation Storyboard.TargetName="fillColor2" Storyboard.TargetProperty="Opacity" Duration="0" To=".75"/>

</Storyboard>

</vsm:VisualState>

</vsm:VisualStateGroup>

<vsm:VisualStateGroup x:Name="FocusStates">

<vsm:VisualState x:Name="Focused">

<!--<Storyboard>

<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Visibility" Duration="0">

<DiscreteObjectKeyFrame KeyTime="0">

<DiscreteObjectKeyFrame.Value>

<Visibility>Visible</Visibility>

</DiscreteObjectKeyFrame.Value>

</DiscreteObjectKeyFrame>

</ObjectAnimationUsingKeyFrames>

</Storyboard>-->

</vsm:VisualState>

<vsm:VisualState x:Name="Unfocused"/>

</vsm:VisualStateGroup>

</vsm:VisualStateManager.VisualStateGroups>

<Rectangle x:Name="fillColor" Opacity="0" Fill="#FFBADDE9" IsHitTestVisible="False" RadiusX="1" RadiusY="1" />

<Rectangle x:Name="fillColor2" Opacity="0" Fill="#FFBADDE9" IsHitTestVisible="False" RadiusX="1" RadiusY="1" />

<Border x:Name="BorderSquare" Opacity="0" IsHitTestVisible="False" BorderBrush="{Binding Tag,Converter={StaticResource SquareImageBorderBrushConverter}}" BorderThickness="{Binding Tag,Converter={StaticResource SquareImageBorderThicknessConverter}}"/>

<ContentPresenter x:Name="ContentPresenterImg" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}"/>

<Rectangle x:Name="FocusVisualElement" Stroke="#FF6DBDD1" StrokeThickness="1" Visibility="Collapsed" RadiusX="1" RadiusY="1" />

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</ListBox.ItemContainerStyle>

<ListBox.ItemsPanel>

<ItemsPanelTemplate>

<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20"/>

</ItemsPanelTemplate>

</ListBox.ItemsPanel>

<ListBox.ItemTemplate>

<DataTemplate>

<StackPanel Orientation="Horizontal">

<StackPanel Orientation="Vertical">

<Image Source="{Binding Path=Source}">

</Image>

</StackPanel>

</StackPanel>

</DataTemplate>

</ListBox.ItemTemplate>

</ListBox>


Converter:


Namespace Converters
    Public Class SquareBorderBrushConverter
        Implements IValueConverter

        Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
            Dim returnValue As String = String.Empty
            Dim img As New SkuOpsSvcRef.SKUImage
            Try
                If value IsNot Nothing Then
                    img = CType(value, SkuOpsSvcRef.SKUImage)
                    If Not img.IsSquare And Not img.IsNew Then
                        returnValue = "Red"
                    Else
                        returnValue = "Transparent"
                    End If
                Else
                    returnValue = String.Empty
                End If
            Catch
                returnValue = String.Empty
            End Try
            Return returnValue
        End Function

        Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
            Return Nothing
        End Function
    End Class

End Namespace


Namespace Converters
    Public Class SquareBorderThicknessConverter
        Implements IValueConverter

        Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
            Dim returnValue As String = String.Empty
            Dim img As New SkuOpsSvcRef.SKUImage
            Try
                If value IsNot Nothing Then
                    img = CType(value, SkuOpsSvcRef.SKUImage)
                    If Not img.IsSquare Then
                        returnValue = "4"
                    Else
                        returnValue = "3"
                    End If
                Else
                    returnValue = String.Empty
                End If
            Catch
                returnValue = String.Empty
            End Try
            Return returnValue
        End Function

        Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
            Return Nothing
        End Function
    End Class

End Namespace