如何使用条件更新 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.CurrentTeamTeamProperties.TeamTeamProperties.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);
    }
}

解释一下,CurrentTeamTeam 属性是通过绑定在依赖对象(文本块)上设置的。虽然 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 中的相应团队将以粗体显示。

对我来说效果很好。希望对你有帮助。