9
votes

I have a class that populates a ListView by passing a list of objects. The class uses reflection to see the properties of each object in order to generate the ListView. How could I change the background color of a row in the ListView.

This page does exactly what I am looking for. The only problem is that my ListView is bound to the list of objects. In other words each item of the ListView is an object that is bound instead of a ListViewItem. I am assuming that is the reason why I cannot cast some item in the ListView to a ListViewItem. For example when I do this:

ListViewItem someItem = (ListViewItem)listView1.Items[0];

I get an InvalidcastException because if I where to physically add the objects to the ListView like:

listview.items.add(someObject) then this will work, but because I am binding the list to the ListView that line does not work. I think that is the reason why I am not able to cast. The reason why I want to cast it is becasue a ListViewItem has a Background property.

EDIT

I am able to do that with the first 12 objects I have tried the folowing:

for (int i = 0; i < listView1.Items.Count; i++)
{
    var lvitem = listView1.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem;
    lvitem.Foreground = Brushes.Green;                
}

and I get this error:

first try

and I also have tried this:

foreach (Tiro t in listView1.Items)
{
    var lvitem = listView1.ItemContainerGenerator.ContainerFromItem(t) as ListViewItem;
    if (t.numero == 0 || t.numero == 37)
    {
        //lvitem.Background = Brushes.Green;
        lvitem.Foreground = Brushes.Green;
    }
    else if (t.numero % 2 == 0)
    {
        //lvitem.Background = Brushes.Red;
        lvitem.Foreground = Brushes.Red;
    }
    else
    {
        //lvitem.Background = Brushes.Gray;
        lvitem.Foreground = Brushes.Black;
    }

}

and I get the same error:

enter image description here

I don't understand why lvitem is null after the 12 iteration?

It only works with the items that are being displayed....

6

6 Answers

10
votes

You need to introduce ViewModels instead of shredding the WPF UI. e.g. I could create one as follows

public class ItemVM : INotifyPropertyChanged // if you want runtime changes to be reflected in the UI
{
  public string Text {... raise property change in setter }
  public Color BackgroundColor {... ditto... }
}

Next create a list of such objects as a property in your DataContext so that your ListView can bind to it.

// e.g. MainWindow
    public IEnumerable<ItemVM> Items { get; set; }

Now all you need to do is bind your ListView to this collection and wire up the DataContext of the UI properly

       <ListView x:Name="MyListView" ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}">
                    <TextBlock.Background>
                        <SolidColorBrush Color="{Binding BackgroundColor}"/>
                    </TextBlock.Background>
                    </TextBlock>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Button Click="Button_Click" Content="Go PaleGreen"/>

Now changing the background color is easy. Just set the property of the corresponding ItemVM object to the Color you want. e.g. to set all items to go PaleGreen

private void Button_Click(object sender, RoutedEventArgs e)
    {
        foreach (var item in Items)
            item.BackgroundColor = Colors.PaleGreen;
    }
4
votes

You could use the ItemContainerGenerator, e.g:

var lvitem = listView.ItemContainerGenerator.ContainerFromItem(item) as ListViewItem;
var lvitem = listView.ItemContainerGenerator.ContainerFromIndex(0) as ListViewItem;

However by default the ListView is virtualizing, this means ListViewItems are created on the fly as needed (only if the item is actually visible in the list), so the above methods will not return containers for items which are currently not visible.

This being the case it usually is preferable to define a binding on the Background property via a Setter in the ItemContainerStyle.

3
votes

When using the ItemContainerGenerator then be aware that the containers are generated asynchronously. The generator exposes a status changed event you could listen to:

listView.ItemContainerGenerator.StatusChanged += new EventHandler(ContainerStatusChanged);     

private void ContainerStatusChanged(object sender, EventArgs e)  
{  
    if (listView.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)  
    {  
        foreach (Tiro t in listView1.Items)
        {
            ...
        }
    }  
}

Not sure if that will create any weird drawing effects (flickering) or not.

Another option instead of building the listview items in code is to you use data templates. You might have to add a few properties to your view model for display purposes though.

1
votes

Assuming the items in your ListBox are of type Foo, and in the ListBox you will display Foo.ItemInfo for each Foo item, and finally let's say there's a property called Status that determines how you want each Foo.ItemInfo to be displayed in the ListBox with respect to the background, foreground, font style, and tooltip text. Based on these requirements, add the following in your XAML:

<ListBox FontFamily="Courier New"
         HorizontalAlignment="Left"
      ...
      ...other ListBox attributes...
      ...
    <ListBox.Resources>
        <local:BGConverter x:Key="BackgroundConverter"/>
        <local:FGConverter x:Key="ForegroundConverter"/>
        <local:FSConverter x:Key="FontStyleConverter"/>
        <local:TTConverter x:Key="ToolTipConverter"/>
    </ListBox.Resources>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ItemInfo}"
                Background="{Binding Converter={StaticResource BackgroundConverter}}"
                FontStyle="{Binding Converter={StaticResource FontStyleConverter}}"
                Foreground="{Binding Converter={StaticResource ForegroundConverter}}"
                ToolTip="{Binding Converter={StaticResource ToolTipConverter}}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Next, add the following into your MainWindow.xaml.cs (or whatever you've named the XAML's accompanying file) in C#:

public class BGConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Foo foo = (Foo)value;
        string bgColor = "Gray";

        switch(foo.Status)
        {
            case 0: 
                bgColor = "White";
                break;

            case 1: 
                bgColor = "Cyan";
                break;

            case 2: 
                bgColor = "Yellow";
                break;
        }

        return bgColor;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class FSConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Foo foo = (Foo)value;
        string fStyle = "Normal";

        switch(foo.Status)
        {
            case 0:
                fStyle = "Normal";
                break;

            case 1: 
                fStyle = "Oblique";
                break;

            case 2: 
                fStyle = "Italic";
                break;
        }

        return fStyle;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}


public class FGConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Foo foo = (Foo)value;
        string fgColor = "Black";

        switch(foo.Status)
        {
            case 0: 
                fgColor = "Blue";
                break;

            case 1: 
                fgColor = "Brown";
                break;

            case 2: 
                fgColor = "DarkBlue";
                break;
        }

        return fgColor;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class TTipConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Foo foo = (Foo)value;
        string ttText = "No tool tips for this item.";

        switch(foo.Status)
        {
            case 0: 
                ttText = "The item has not been processed";
                break;

            case 1: 
                ttText = "The item has been processed but not saved";
                break;

            case 2: 
                ttText = "The item has been processed and saved";
                break;
        }

        return ttText ;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

This is one way I've found that works...there's no doubt many other ways, and your mileage may vary...

In any event, HTH

1
votes

After some googling i found out my own solution I am using Listview.ItemsSource and as source i use List Then i can set background of specify ListViewItem in List, and just refresh listview.

XAML:

 <ListView x:Name="listView" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.Row="1">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="IP"  DisplayMemberBinding="{Binding IP}" Width="Auto"/>
                        <GridViewColumn Header="PING" DisplayMemberBinding="{Binding Ping}" Width="Auto"/>
                        <GridViewColumn Header="Host Name" DisplayMemberBinding="{Binding DNS}" Width="Auto"/>
                        <GridViewColumn Header="Mac" DisplayMemberBinding="{Binding MAC}" Width="Auto"/>
                        <GridViewColumn Header="Výrobce" DisplayMemberBinding="{Binding Manufacturer}" Width="Auto"/>
                    </GridView>
                </ListView.View>
            </ListView>

Fill ListView with Items with Gray Background:

    List<ListViewItem> ITEMS = new List<ListViewItem>();
    private void button_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 1; i < 20; i++)
        {
            ListViewItem OneItem = new ListViewItem();
            OneItem.Background = Brushes.LightGray;
            OneItem.Content = new Device() { IP = "1.1.1.1", Ping = "30ms", DNS = "XYZ", MAC = "2F:3C:5F:41:F9", Manufacturer = "Intel" };
            ITEMS.Add(OneItem);
            listView.ItemsSource = ITEMS;
        }
        listView.Items.Refresh();
    }
    public class Device
    {
        public string IP { get; set; }
        public string Ping { get; set; }
        public string DNS { get; set; }
        public string MAC { get; set; }
        public string Manufacturer { get; set; }
    }

Create Method for Row Change Color:

    private void ChangeRowColor(int RowIndex,SolidColorBrush NewBackground)
    {
        ITEMS[RowIndex].Background = NewBackground;
        listView.Items.Refresh();
    }

And use it:

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        ChangeRowColor(4, Brushes.Green);
    }
0
votes
  List<ListViewItem> ITEMS = new List<ListViewItem>();
private void loadListView(ListView lv)
{
    int numberOfRows = 20;
    string[] student_number, first_name, last_name, middle_name, extension, course, year, section;
      //    ...... Assign values to the arrays above...
    for (int h = 0; h <= numberOfRows - 1; h++)
        {
            ListViewItem OneItem = new ListViewItem();
            OneItem.Background = course[h] == "Grade" ? Brushes.Red : Brushes.Transparent; //Decide the color of the Row
            OneItem.Content = new Student
            {
                Student_Number = student_number[h],
                Course = course[h],
                Section = section[h],
                Year = year[h],
                FullName = first_name[h] + " " + middle_name[h] + ". " + last_name[h] + " " + extension[h]
            };
            ITEMS.Add(OneItem);
            lv.ItemsSource = ITEMS;
        }
        lv.Items.Refresh();
}
public class Student
{
    public string Student_Number { get; set; }
    public string FullName { get; set; }
    public string Course { get; set; }
    public string Section { get; set; }
    public string Year { get; set; }
}

output screenshot