如何使用条件更新 ItemsControl 显示的所有项目?
How to update all items shown by ItemsControl using a condition?
在一个 UWP 项目中,我有一个 UI,它有一个绑定到一组团队对象的 ItemsControl。有一个单独的 GameController 对象,它有一个 CurrentTeam 属性,随着游戏的进行而改变。我希望能够在 CurrentTeam 团队的 ItemTemplate 中有一个视觉提示。例如,当前团队的名称带有下划线。 Team 对象没有对 GameController 的引用。
一种方法是在每个团队上放置一个标志,比如 IsCurrentTeam 并绑定到 ItemTemplate 中的那个。我不是特别喜欢这种方法,因为这意味着当 CurrentTeam 发生变化时,我必须绕过除当前团队之外的所有团队,以更新他们的标志。
在 WPF 中,我认为可能有一个使用 ObjectDataProvider 的解决方案,因为它提供了绑定到方法的能力,但由于我在 UWP 中,此选项不可用。
有人知道更好的方法吗?
在 WPF 中,我的首选选项是 DataTrigger,当项目的 DataContext {Binding}
等于父级 CurrentTeam
的内容时设置下划线 属性(使用元素名称参考那个)。
由于 UWP 不支持触发器,您可以像 中那样使用 DataTriggerBehaviour
。
好的,我准备了一个示例来说明这是如何实现的。为了解决 UWP 中的限制,它使用了一些技术,例如 'data context anchoring' 和附加属性。
这是我的支持 classes,我认为它们与您的有点相似:
public class GameControllerViewModel : INotifyPropertyChanged
{
private Team _currentTeam;
public event PropertyChangedEventHandler PropertyChanged;
public GameControllerViewModel(IEnumerable<Team> teams)
{
Teams = teams;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Team CurrentTeam
{
get { return _currentTeam; }
set
{
if (value != _currentTeam)
{
_currentTeam = value;
OnPropertyChanged();
}
}
}
public IEnumerable<Team> Teams { get; private set; }
}
public class Team
{
public string Name { get; set; }
}
页面后面的代码:
public sealed partial class GamesPage : Page
{
public GamesPage()
{
this.InitializeComponent();
this.DataContext = new GameControllerViewModel(
new[]
{
new Team { Name = "Team A" },
new Team { Name = "Team B" },
new Team { Name = "Team C" },
new Team { Name = "Team D" }
}
);
}
}
如您所见,页面的构造函数实例化了一个有四个团队的GameControllerViewModel,并将其设置为页面的数据上下文。
页面XAML如下:
<Page
x:Class="UniversalScratchApp.GamesPage" x:Name="View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UniversalScratchApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:BoolToFontWeightConverter x:Key="BoolToFontWeightConverter"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Current Team:" Margin="4" VerticalAlignment="Center"/>
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Teams}" SelectedItem="{Binding CurrentTeam, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="4">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ItemsControl Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{Binding Teams}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" local:TeamProperties.CurrentTeam="{Binding ElementName=View, Path=DataContext.CurrentTeam}" local:TeamProperties.Team="{Binding}" FontWeight="{Binding Path=(local:TeamProperties.IsCurrentTeam), RelativeSource={RelativeSource Mode=Self}, Mode=OneWay, Converter={StaticResource BoolToFontWeightConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Page>
在 ItemsControl 的 DataTemplate 中,您可以看到我绑定了三个自定义附加属性; TeamProperties.CurrentTeam
、TeamProperties.Team
和 TeamProperties.IsCurrentTeam
。附加属性定义如下 class:
[Bindable]
public static class TeamProperties
{
private static void TeamPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
Team team = GetTeam(sender);
Team currentTeam = GetCurrentTeam(sender);
if (team != null && currentTeam != null)
{
SetIsCurrentTeam(sender, team.Equals(currentTeam));
}
}
public static readonly DependencyProperty CurrentTeamProperty = DependencyProperty.RegisterAttached("CurrentTeam", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetCurrentTeam(DependencyObject obj)
{
return (Team)obj.GetValue(CurrentTeamProperty);
}
public static void SetCurrentTeam(DependencyObject obj, Team value)
{
obj.SetValue(CurrentTeamProperty, value);
}
public static readonly DependencyProperty TeamProperty = DependencyProperty.RegisterAttached("Team", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetTeam(DependencyObject obj)
{
return (Team)obj.GetValue(TeamProperty);
}
public static void SetTeam(DependencyObject obj, Team value)
{
obj.SetValue(TeamProperty, value);
}
public static readonly DependencyProperty IsCurrentTeamProperty = DependencyProperty.RegisterAttached("IsCurrentTeam", typeof(bool), typeof(TeamProperties), new PropertyMetadata(false));
public static bool GetIsCurrentTeam(DependencyObject obj)
{
return (bool)obj.GetValue(IsCurrentTeamProperty);
}
public static void SetIsCurrentTeam(DependencyObject obj, bool value)
{
obj.SetValue(IsCurrentTeamProperty, value);
}
}
解释一下,CurrentTeam
和 Team
属性是通过绑定在依赖对象(文本块)上设置的。虽然 Team
属性 可以使用当前数据上下文,但 CurrentTeam
属性 必须绑定到 'outer' DataContext。它通过在页面上指定一个 x:Name="View"
并将其用于 'anchor' 数据上下文来实现这一点,这样它就可以通过使用绑定的 ElementName=View
部分的绑定来访问。
因此,只要这些属性中的任何一个发生变化,IsCurrentTeam
属性 就会通过 TeamPropertiesChanged
回调设置在同一个依赖对象上。 IsCurrentTeam
属性 然后使用此处显示的 BoolToFontWeightConverter 绑定到 FontWeight 属性(因为它比下划线更容易):
public class BoolToFontWeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool)
{
return ((bool)value) ? FontWeights.ExtraBold : FontWeights.Normal;
}
else
{
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
现在,当在顶部组合框(代表您用于更改团队的任何机制)中选择一个团队时,ItemsControl
中的相应团队将以粗体显示。
对我来说效果很好。希望对你有帮助。
在一个 UWP 项目中,我有一个 UI,它有一个绑定到一组团队对象的 ItemsControl。有一个单独的 GameController 对象,它有一个 CurrentTeam 属性,随着游戏的进行而改变。我希望能够在 CurrentTeam 团队的 ItemTemplate 中有一个视觉提示。例如,当前团队的名称带有下划线。 Team 对象没有对 GameController 的引用。
一种方法是在每个团队上放置一个标志,比如 IsCurrentTeam 并绑定到 ItemTemplate 中的那个。我不是特别喜欢这种方法,因为这意味着当 CurrentTeam 发生变化时,我必须绕过除当前团队之外的所有团队,以更新他们的标志。
在 WPF 中,我认为可能有一个使用 ObjectDataProvider 的解决方案,因为它提供了绑定到方法的能力,但由于我在 UWP 中,此选项不可用。
有人知道更好的方法吗?
在 WPF 中,我的首选选项是 DataTrigger,当项目的 DataContext {Binding}
等于父级 CurrentTeam
的内容时设置下划线 属性(使用元素名称参考那个)。
由于 UWP 不支持触发器,您可以像 DataTriggerBehaviour
。
好的,我准备了一个示例来说明这是如何实现的。为了解决 UWP 中的限制,它使用了一些技术,例如 'data context anchoring' 和附加属性。
这是我的支持 classes,我认为它们与您的有点相似:
public class GameControllerViewModel : INotifyPropertyChanged
{
private Team _currentTeam;
public event PropertyChangedEventHandler PropertyChanged;
public GameControllerViewModel(IEnumerable<Team> teams)
{
Teams = teams;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Team CurrentTeam
{
get { return _currentTeam; }
set
{
if (value != _currentTeam)
{
_currentTeam = value;
OnPropertyChanged();
}
}
}
public IEnumerable<Team> Teams { get; private set; }
}
public class Team
{
public string Name { get; set; }
}
页面后面的代码:
public sealed partial class GamesPage : Page
{
public GamesPage()
{
this.InitializeComponent();
this.DataContext = new GameControllerViewModel(
new[]
{
new Team { Name = "Team A" },
new Team { Name = "Team B" },
new Team { Name = "Team C" },
new Team { Name = "Team D" }
}
);
}
}
如您所见,页面的构造函数实例化了一个有四个团队的GameControllerViewModel,并将其设置为页面的数据上下文。
页面XAML如下:
<Page
x:Class="UniversalScratchApp.GamesPage" x:Name="View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UniversalScratchApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:BoolToFontWeightConverter x:Key="BoolToFontWeightConverter"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Current Team:" Margin="4" VerticalAlignment="Center"/>
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Teams}" SelectedItem="{Binding CurrentTeam, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="4">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ItemsControl Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{Binding Teams}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" local:TeamProperties.CurrentTeam="{Binding ElementName=View, Path=DataContext.CurrentTeam}" local:TeamProperties.Team="{Binding}" FontWeight="{Binding Path=(local:TeamProperties.IsCurrentTeam), RelativeSource={RelativeSource Mode=Self}, Mode=OneWay, Converter={StaticResource BoolToFontWeightConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Page>
在 ItemsControl 的 DataTemplate 中,您可以看到我绑定了三个自定义附加属性; TeamProperties.CurrentTeam
、TeamProperties.Team
和 TeamProperties.IsCurrentTeam
。附加属性定义如下 class:
[Bindable]
public static class TeamProperties
{
private static void TeamPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
Team team = GetTeam(sender);
Team currentTeam = GetCurrentTeam(sender);
if (team != null && currentTeam != null)
{
SetIsCurrentTeam(sender, team.Equals(currentTeam));
}
}
public static readonly DependencyProperty CurrentTeamProperty = DependencyProperty.RegisterAttached("CurrentTeam", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetCurrentTeam(DependencyObject obj)
{
return (Team)obj.GetValue(CurrentTeamProperty);
}
public static void SetCurrentTeam(DependencyObject obj, Team value)
{
obj.SetValue(CurrentTeamProperty, value);
}
public static readonly DependencyProperty TeamProperty = DependencyProperty.RegisterAttached("Team", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetTeam(DependencyObject obj)
{
return (Team)obj.GetValue(TeamProperty);
}
public static void SetTeam(DependencyObject obj, Team value)
{
obj.SetValue(TeamProperty, value);
}
public static readonly DependencyProperty IsCurrentTeamProperty = DependencyProperty.RegisterAttached("IsCurrentTeam", typeof(bool), typeof(TeamProperties), new PropertyMetadata(false));
public static bool GetIsCurrentTeam(DependencyObject obj)
{
return (bool)obj.GetValue(IsCurrentTeamProperty);
}
public static void SetIsCurrentTeam(DependencyObject obj, bool value)
{
obj.SetValue(IsCurrentTeamProperty, value);
}
}
解释一下,CurrentTeam
和 Team
属性是通过绑定在依赖对象(文本块)上设置的。虽然 Team
属性 可以使用当前数据上下文,但 CurrentTeam
属性 必须绑定到 'outer' DataContext。它通过在页面上指定一个 x:Name="View"
并将其用于 'anchor' 数据上下文来实现这一点,这样它就可以通过使用绑定的 ElementName=View
部分的绑定来访问。
因此,只要这些属性中的任何一个发生变化,IsCurrentTeam
属性 就会通过 TeamPropertiesChanged
回调设置在同一个依赖对象上。 IsCurrentTeam
属性 然后使用此处显示的 BoolToFontWeightConverter 绑定到 FontWeight 属性(因为它比下划线更容易):
public class BoolToFontWeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool)
{
return ((bool)value) ? FontWeights.ExtraBold : FontWeights.Normal;
}
else
{
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
现在,当在顶部组合框(代表您用于更改团队的任何机制)中选择一个团队时,ItemsControl
中的相应团队将以粗体显示。
对我来说效果很好。希望对你有帮助。