views:

57

answers:

3

Hi,

I'm rather a newbie with WPF and still playing with bindings and such, so this probably a basic question.

Consider the following class:

class DataHolder
{
    public List<int> Data;

    // Points to one of the items in Data. Thus can be between 0 and Data.Count
    public int Pointer;
}

I've set a ListBox's DataContext to be an instance of the class above and its ItemSource is the 'Data' of the instance.

Now I'd like to mark in the ListBox the color of the list item that holds Data[Pointer] to be gray color.

What should I use? I've tried doing it with DataTriggers but I am not sure how to compare two different values using them. I'd rather not use IValueConverter at this point, unless it is impossible to do otherwise.

EDIT: Feel free to convert the public data to properties in the mentioned class

Thanks!

+2  A: 

I've done things like this before by creating what I call a DataViewModel. This is basically a ViewModel for each item in the collection. I tried it quick with your example and it worked. I'll paste all my code below, should be able to pull it in and execute it if you wish.

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="525">

<Window.DataContext>
    <local:MainWindow_ViewModel/>
</Window.DataContext>
<StackPanel>
    <TextBox Text="{Binding Pointer, Mode=TwoWay}" Height="20" Width="100"/>
    <ListBox ItemsSource="{Binding MyDataHolder}" Height="20" Width="100">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Value}" Background="{Binding Color}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</StackPanel>

MainWindow_ViewModel.cs

public class MainWindow_ViewModel
{
    public MainWindow_ViewModel() 
    {
        data = new DataHolder();

        foreach (int value in data.Data)
        {
            myData.Add(new Data_DataViewModel() { Value = value });
        }

        this.Pointer = 4;
    }

    private DataHolder data;
    private List<Data_DataViewModel> myData = new List<Data_DataViewModel>();

    public List<Data_DataViewModel> MyDataHolder
    {
        get
        {
            return myData;
        }
    }

    public int Pointer
    {
        get { return this.data.Pointer; }
        set 
        {
            this.data.Pointer = value;
            foreach (Data_DataViewModel dvm in this.myData)
            {
                dvm.UpdateColor(this.data.Pointer);
            }
        }
    }
}

DataHolder.cs

public class DataHolder
{
    public List<int> Data
    {
        get { return data; }   
    }
    private List<int> data = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8 };

    public int Pointer = 3;
}

Data_DataViewModel.cs

public class Data_DataViewModel : INotifyPropertyChanged
{
    public int Value
    {
        get { return val; }
        set { val = value; } 
    }
    private int val;

    public Brush Color
    {
        get 
        {
            if (this.Value == pointer)
            {
                return Brushes.Gray;
            }
            else
            {
                return Brushes.Pink;
            }
        }
    }

    private int pointer;

    public void UpdateColor(int pointerValue)
    {
        this.pointer = pointerValue;
        OnPropertyChanged("Color");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
JSprang
It is an interesting solution, thanks. I imagined it with some complex Binding syntax that I am not aware of. However, it isn't clear from the example, are you initializing 'myData' with data from 'data'? If so, it is rather a shame that you need to store the same data twice.
VitalyB
Oh, ok, I missed the initial initialization. Makes sense now :) Thanks.
VitalyB
+1  A: 

Interesting question. My solution uses ItemTemplateSelector. In its SelectTemplate override, you can access the Panel (VirtualizingStackPanel) under which the ListItems are housed. The trick here is that as each ListItem is added to the ListBox, this override is called and we can use the ChildCount to determinate its index in the ListBox. This helps us in comparison and selecting the right template.

Main.xaml

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApplication1" mc:Ignorable="d" x:Class="WpfApplication1.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>       
        <DataTemplate
            x:Key="DataTemplateWithHighlight">
            <StackPanel>
                <TextBlock HorizontalAlignment="Stretch"
                    Text="{Binding Mode=OneWay}"
                    Background="Gray" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate
            x:Key="DataTemplateWithoutHighlight">
            <StackPanel>
                <TextBlock
                    HorizontalAlignment="Stretch"
                    Text="{Binding Mode=OneWay}"
                    Background="White" />
            </StackPanel>
        </DataTemplate>
        <local:ListBoxItemTemplateSelector
            x:Key="listBoxItemTemplateSelector"
            HighlightedItemTemplate="{StaticResource DataTemplateWithHighlight}"
            NonHighlightedItemTemplate="{StaticResource DataTemplateWithoutHighlight}" />

    </Window.Resources>
    <StackPanel Orientation="Vertical" d:LayoutOverrides="Height" HorizontalAlignment="Center" VerticalAlignment="Center">
        <ListBox
            x:Name="listBoxWithMeaninglessNumbers"
            Height="100"
            Width="100"
            ItemsSource="{Binding Data}"
            ItemTemplateSelector="{DynamicResource listBoxItemTemplateSelector}">          
        </ListBox>
    </StackPanel>
</Window>

DataItems Class

 public  class DataItems
    {
      public List<int> Data { get; set;}
      public int HighlightedIndex { get; set; }
    }

Some initial data to get you going

public static class DataStub
{
        public static DataItems TestDataItems
        {
            get
            {
                var dataItems = new DataItems();
                dataItems.Data = new List<int>(){1,5,9,6,8};
                dataItems.HighlightedIndex = 2;

                return dataItems;
            }
        }
}

MainWindow.Xaml.cs

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        listBoxWithMeaninglessNumbers.DataContext = DataStub.TestDataItems;
    }
}

TemplateSelector Class:

 public class ListBoxItemTemplateSelector:DataTemplateSelector
    {
       public DataTemplate NonHighlightedItemTemplate { get; set; }
       public DataTemplate HighlightedItemTemplate { get; set; }



         public override DataTemplate SelectTemplate(object item, DependencyObject container)
       {
             var listBoxItem = ((FrameworkElement) container).TemplatedParent as ListBoxItem;
             var panel = VisualTreeHelper.GetParent(listBoxItem) as Panel;
             var highlightedIndex = (panel.DataContext as DataItems).HighlightedIndex;
             var currentChildIndex = panel.Children.Count-1;

             return (highlightedIndex == currentChildIndex) ? HighlightedItemTemplate : NonHighlightedItemTemplate;
            }
    }
}

Hope this helps.

SKG
Interesting! Learned quite a bit from your example. Not sure if I am going with the way you described or the other comment but thanks!
VitalyB
One issue with your example, however, is that you can't change the currentChildIndex during runtime (without deleting and re-adding all the items). Any thoughts on how to overcome that? I realize it wasn't a precondition in the original question, but now I am curious.
VitalyB
A: 

Listbox also has a SelectedIndex property that you can bind to. Make the index a property on your ViewModel (Hint: the data property, also make sure the property fires a notify change event). Then edit the Selected State of the listbox item.

IMO far easier than the other proposals and uses the templating and binding more appropriately.

Ragepotato
I think you have misinterpreted the question. Maybe the OP can correct me. If you bind the SelectedIndex to a property, that would select that specific ListItem. OP never mentions about selecting an item, just changing the Background based on the Index/Pointer in the DataHolder
SKG
Exactly. Thus since the list is bound and he wants to color the item at the index then changing the selected state to make the item gray achieves his goal.
Ragepotato
You're assuming that `DataHolder.Pointer` contains the index of the currently selected value. That's not actually been specified.
Robert Rossney
Apparently I am confused. I took "Now I'd like to mark in the ListBox the color of the list item that holds Data[Pointer] to be gray color." to mean just that.
Ragepotato
Thanks for the answer, Ragepotato, but as SKG/Robert said, Pointer variable can be different from the selected value.
VitalyB