问题
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.
回答1:
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!
回答2:
You can simply handle the SelectionChanged
event on the ListView
and change the Foreground
of the previously selected item and the newly selected item by either changing a view model value on SelectedItem
that is bound to your Foreground
.
You can also find the TextBlock
using ListView.ItemContainerGenerator.ContainerFromItem(ListView.SelectedItem)
+ VisualTreeHelper
as in Jerry Nixon's blog post and changing the Foreground directly, though that technique has a problem if your ListView
is virtualized (which it is by default), since if you scroll away from the selected item and back - the item view with the changed Foreground
might be recycled and used for another item in your collection.
Another option is to bind the Foreground
to the IsSelected
property of the parent ListViewItem
which you can do in many ways as well. You could for example put your entire DataTemplate
in a UserControl
and bind the Foreground
to the Parent
of that control. The problem is I think Parent
is not a dependency property and I see no ParentChanged
event on FrameworkElement
(base class for UserControl
that defines the Parent
property), so it might be tough to go this route. Another way to bind these is to define an attached dependency property or behavior that would set up that binding for you, but that is complicated (though I have already created one you could use here).
Finally you could modify your ListView.ItemContainerStyle
and change the SelectedBackground
value. If that works - it would be the ideal solution.
<Style TargetType="ListViewItem">
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="TabNavigation" Value="Local"/>
<Setter Property="IsHoldingEnabled" Value="True"/>
<Setter Property="Margin" Value="0,0,18,2"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter CheckHintBrush="{ThemeResource ListViewItemCheckHintThemeBrush}" CheckBrush="{ThemeResource ListViewItemCheckThemeBrush}" ContentMargin="4" ContentTransitions="{TemplateBinding ContentTransitions}" CheckSelectingBrush="{ThemeResource ListViewItemCheckSelectingThemeBrush}" DragForeground="{ThemeResource ListViewItemDragForegroundThemeBrush}" DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}" DragBackground="{ThemeResource ListViewItemDragBackgroundThemeBrush}" DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}" FocusBorderBrush="{ThemeResource ListViewItemFocusBorderThemeBrush}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" PointerOverBackgroundMargin="1" PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" PointerOverBackground="{ThemeResource ListViewItemPointerOverBackgroundThemeBrush}" ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" SelectedPointerOverBorderBrush="{ThemeResource ListViewItemSelectedPointerOverBorderThemeBrush}" SelectionCheckMarkVisualEnabled="True" SelectedForeground="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" SelectedPointerOverBackground="{ThemeResource ListViewItemSelectedPointerOverBackgroundThemeBrush}" SelectedBorderThickness="{ThemeResource ListViewItemCompactSelectedBorderThemeThickness}" SelectedBackground="{ThemeResource ListViewItemSelectedBackgroundThemeBrush}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
来源:https://stackoverflow.com/questions/20379234/change-the-foreground-color-of-a-textblock-inside-a-listviews-datatemplate-when