views:

2163

answers:

4

I have a listview that is binded to a ThreadSafeObservableCollection. The background of each of these items is set to an enum that is run through a color converter, here's the code for these 2 settings.

<UserControl.Resources>
    <EncoderView:EncoderStatusToColorConverter x:Key="ColorConverter"/>
    <Style x:Key="ItemContStyle" TargetType="{x:Type ListViewItem}">
        <Setter Property="Background" Value="{Binding Converter={StaticResource ColorConverter}}" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
    </Style>
</UserControl.Resources>

what I want to have happen is that the background color of the listviewItem will change from Red - Yellow - Green based on an enum value. Which is updated based on business logic rules. This is currently working, but only for the initial display of the item. When I make a change to the "Status" property of the object that the listItemView is bound to, the background does not update. If I remove the object from the collection, change the status, and then add it to the collection again, the background IS updated.
I've tried making the object IPropertyNotify, and throwing the event on the status property changed setter, but that didn't work.

does anyone know if there is something special I have to do in order to get the background of a listview item to update. I'm also open for other ideas on how to solve this problem, thanks. here's the XAML for the ListView. EncoderService.Encoders is my ThreadSafeObservableCollection of Encoder objects.

        <ListView  AutomationProperties.AutomationId="FinishScreen"  
                   ItemsSource="{Binding Path=EncoderService.Encoders}" 
                   x:Name="DataListView" Grid.RowSpan="1" Grid.Row="1" Margin="5"
                   ItemContainerStyle="{StaticResource ItemContStyle}"
                   Background="Azure">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="MAC">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <ContentControl Content="{Binding Path=MAC}" ToolTip="{Binding Path=MAC}"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="IDF">...
+1  A: 

Have you tried using a DataTemplateSelector? I use it to change which template is used for a ListBoxItem, which is basically just changing the foreground color based on a property. I haven't tried the scenario you are doing where the data changes after the page is loaded, but I don't see why it wouldn't work.

I have this class:

public class UniqueWordDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item != null && item is WordScore)
        {
            WordScore word = item as WordScore;

            FrameworkElement elem = container as FrameworkElement;

            if (word.IsUnique)
                return
                    elem.FindResource("UniqueWordListTemplate") as DataTemplate;
            else
                return
                    elem.FindResource("WordListTemplate") as DataTemplate;
        }

        return null;
    }
}

In my control's resources I have this line:

    <UserControl.Resources>
    ...
    <my:UniqueWordDataTemplateSelector x:Key="myDataTemplateSelector"/>

I also have my two templates in here:

    <DataTemplate x:Key="WordListTemplate">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="210" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock Text="{Binding Path=Word}" Background="{x:Null}" Foreground="White"/>
            <TextBlock Text="{Binding Path=Score}" Background="{x:Null}" Foreground="White"/>
        </Grid>
    </DataTemplate>
    <DataTemplate x:Key="UniqueWordListTemplate">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="210" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock Text="{Binding Path=Word}" Background="{x:Null}" Foreground="Gold"/>
            <TextBlock Text="{Binding Path=Score}" Background="{x:Null}" Foreground="White"/>
        </Grid>
    </DataTemplate>

And then I use the following for my ListBox:

   <ListBox x:Name="PlayerList2" ItemTemplateSelector="{DynamicResource myDataTemplateSelector}" IsSynchronizedWithCurrentItem="True" Background="#FF252525"/>
Sailing Judo
A: 

I like this approach, and I'm going to try to persue it, but the only problem I see right off the bat is that I'm using the GridView, because I'm showing tabular data. I like the column headers. But I think I can find a way around that. I'll probably just put my own column headers in manually.

How does the ListItem get notified that it should update it's data template? does this happen each time it recieves a PropertyChanged event on any of its bounded items?

Joshua
If the ItemsSource is bound to an ObservableCollection it will automagically be notified when a new item is added to the collection. You will still need to implement INotifyPropertyChangedfor properties that the ListBoxItem is already displaying to be affected. Hope this helps...
Sailing Judo
A: 

I think I may be confused about what has to impliment INotifyPropertyChanged.

So I have a ObservableCollection of "Encoder" objects. The ObservableCollection is obviously observable, and when i add and remove things from it, it gets updated. but each "Encoder's" properties are not being updated. From what I understand that means that the "Encoder" class must implement INotifyPropertyChanged, and send the name of the property in the event. Is this correct thinking? because at the moment it isn't working. I want to make sure i have the theory correct before I post more code.

thanks

Joshua
+1  A: 

The issue is that the Binding to Background is taking the entire Encoder object, which doesn't change unless you remove and add it. Even if Encoder implements INotifyPropertyChanged, the Binding is still looking at the whole Encoder object and has no way of knowing which properties of Encoder are relevant to your EncoderStatusToColorConverter, so it won't update itself.

The solution in your case is to narrow the scope of the Binding on Background to just the property (or properties) relevant to EncoderStatusToColorConverter. Any properties in the Path of your Binding will be watched for updates. So, for example, if you only need Status:

<Style x:Key="ItemContStyle" TargetType="{x:Type ListViewItem}">
    <Setter Property="Background" Value="{Binding Path=Status, Converter={StaticResource ColorConverter}}" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>

In this case, if you implement INotifyPropertyChanged on Encoder and notify whenever Status changes, the Binding should update for you. This also means you'll need to update your EncoderStatusToColorConverter so it only takes a Status type.

Robert Macnee
this makes perfect sense, thank you Robert
Joshua