Search code examples
xamlwindows-8winrt-xamlwindows-8.1winrt-xaml-toolkit

Change the Foreground color of a TextBlock inside a ListView's DataTemplate when the item is selected


I'm building a Windows Store app with C#/XAML.

I have a simple ListView bound to an ItemsSource. There's a DataTemplate which defines the structure of each item and that has a ContentControl and a TextBlock in it.

I wish to change the Foreground colour of the TextBlock when the item is selected. Does anyone know how I can do this?

<ListView Grid.Column="1" 
              ItemsSource="{Binding Categories}" 
              ItemContainerStyle="{StaticResource CategoryListViewItemStyle}"
              Background="{StaticResource DeepRedBrush}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <ContentControl Content="{Binding Id, Converter={StaticResource Cat2Icon}}" HorizontalAlignment="Left" VerticalAlignment="Center" Width="110" Foreground="#FF29BCD6"/>
                    <TextBlock x:Name="catName" HorizontalAlignment="Left" Margin="0" TextWrapping="Wrap" Text="{Binding Name}" Grid.Column="1" VerticalAlignment="Center" FontSize="18.667" 
                               Foreground="White"/>

                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

At the moment it's set to "White", so all I need is some binding expression that will change the Foreground property depending on the selected state of the item in the listview.


Solution

  • This does what you are asking for.

    Using this XAML

    <Grid x:Name="LayoutRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView x:Name="MyListView" ItemsSource="{Binding Items}" SelectionMode="Single" SelectedItem="{Binding Selected, Mode=TwoWay}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Height="100" Width="300">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <Ellipse x:Name="ellipse">
                            <Ellipse.Fill>
                                <SolidColorBrush Color="{Binding Color}" />
                            </Ellipse.Fill>
                        </Ellipse>
                        <TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10" Text="{Binding Title}" Style="{StaticResource HeaderTextBlockStyle}" />
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
    

    And this code behind:

    public class MyModel : BindableBase
    {
        string _Title = default(string);
        public string Title { get { return _Title; } set { SetProperty(ref _Title, value); } }
    
        Color _Color = Colors.White;
        public Color Color { get { return _Color; } set { SetProperty(ref _Color, value); } }
    }
    
    public class MyViewModel : BindableBase
    {
        public MyViewModel()
        {
            var items = Enumerable.Range(1, 10)
                .Select(x => new MyModel { Title = "Title " + x.ToString() });
            foreach (var item in items)
                this.Items.Add(item);
        }
    
        MyModel _Selected = default(MyModel);
        public MyModel Selected
        {
            get { return _Selected; }
            set
            {
                if (this.Selected != null)
                    this.Selected.Color = Colors.White;
                SetProperty(ref _Selected, value);
                value.Color = Colors.Red;
            }
        }
    
        ObservableCollection<MyModel> _Items = new ObservableCollection<MyModel>();
        public ObservableCollection<MyModel> Items { get { return _Items; } }
    }
    
    public abstract class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
        {
            if (!object.Equals(storage, value))
            {
                storage = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    It will update your data template for you.

    I want to make this quick point: updating the content of your list through the ViewModel is the easiest and most light-weight approach. In this case, I am updating the color which is bound to the ellipse. However, if this were a complex set of changes, I might just set a style instead. Another option is to hide and show an entire set of controls in the template. You cannot, however, change the data template because it will not be re-rendered until the grid re-draws, and that's not what you want to do.

    Just like changing the Ellipse color, you could change the TextBlock Foreground like you asked in your question. Either way, this gets you what you want in the most elegant way.

    Best of luck!