如何在 UWP 中动态更改项目模板?

How to dynamically change itemtemplate in UWP?

在 WPF 中,您可以通过绑定动态地使用样式和模板修改控件。我看到如何在 UWP 中直接在控件中执行此操作,但我想应用一个模板,该模板将根据绑定自行更改。

一个例子就是按钮。在这个项目中,我有一个按钮可以打开和关闭一盏灯。该项目已创建且 运行 在 WPF 中,但需要转换为 UWP。在 WPF 版本中,我们为按钮提供了一个 LightStyle,根据它是什么类型的光,我们更改模板以针对该光进行外观和执行。 (例如:我们可以改变一些灯的颜色,一些灯的暗淡度,一些灯只是打开和关闭;但我们对它们都使用相同的 LightStyle。非常通用,动态,非常有用。)

如何在 UWP 中执行此操作?我搜索了一分钟,想在继续挖掘的同时停下来检查一下。请记住,这个项目是纯 MVVM,没有使用任何代码。我不介意解释背后的代码,只要它不是唯一的方法。

提前致谢:)

这是我正在使用的适用于我给定情况的答案。基本上,您必须使用 VisualStateTrigger 并通过代码手动创建触发器。您可以使用各种触发器,其中许多是内置的,但对于这种情况,我不得不,或者至少我认为我不得不手动编写一个。

这是触发代码。

public class StringComparisonTrigger : StateTriggerBase
{
    private const string NotEqual = "NotEqual";
    private const string Equal = "Equal";

    public string DataValue
    {
        get { return (string)GetValue(DataValueProperty); }
        set { SetValue(DataValueProperty, value); }
    }

    public static readonly DependencyProperty DataValueProperty =
        DependencyProperty.Register(nameof(DataValue), typeof(string), typeof(StringComparisonTrigger), new PropertyMetadata(Equal, (s, e) =>
        {
            var stringComparisonTrigger = s as StringComparisonTrigger;
            TriggerStateCheck(stringComparisonTrigger, stringComparisonTrigger.TriggerValue, (string)e.NewValue);
        }));


    public string TriggerValue
    {
        get { return (string)GetValue(TriggerValueProperty); }
        set { SetValue(TriggerValueProperty, value); }
    }

    public static readonly DependencyProperty TriggerValueProperty =
        DependencyProperty.Register(nameof(TriggerValue), typeof(string), typeof(StringComparisonTrigger), new PropertyMetadata(NotEqual, (s, e) =>
        {
            var stringComparisonTrigger = s as StringComparisonTrigger;
            TriggerStateCheck(stringComparisonTrigger, stringComparisonTrigger.DataValue, (string)e.NewValue);
        }));


    private static void TriggerStateCheck(StringComparisonTrigger elementTypeTrigger, string dataValue, string triggerValue)
        => elementTypeTrigger.SetActive(dataValue == triggerValue);
}

这是因为继承自 StateTriggerBase 可以在 VisualStateTriggers 组中使用,我将在下面 post。我不知道的是,您编写的任何依赖项 属性 都可以在 XAML 中使用,并且触发器中没有接口或任何东西可以使其工作。触发触发器的唯一代码行是 'SetActive(bool value)' ,您必须在希望状态更改时调用它。通过在 XAML 中创建依赖属性和绑定,您可以在 属性 更改时触发 SetActive,从而修改视觉状态。

DataTemplate 在下面。

<DataTemplate x:Key="LightsButtonTemplate">

    <UserControl>
        <StackPanel Name="panel">

            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup>
                    <VisualState>
                        <VisualState.StateTriggers>
                            <DataTriggers:StringComparisonTrigger DataValue="{Binding Type}"
                                                                  TriggerValue="READ" />
                        </VisualState.StateTriggers>
                        <VisualState.Setters>
                            <Setter Target="panel.(UIElement.Background)"
                                    Value="Red" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

            <TextBlock Text="{Binding Type}" />
            <TextBlock Text="{Binding LightStateViewModel.On}" />

        </StackPanel>
    </UserControl>

</DataTemplate>

最后,您可以在任何地方使用 DataTemplate,但我在绑定到 LightViewModel 列表的 ItemsControl 中使用它。

    <ScrollViewer Grid.Row="1">
        <ItemsControl ItemsSource="{Binding LightViewModels}"
                      ItemTemplate="{StaticResource LightsButtonTemplate}" />
    </ScrollViewer>

显然这不是我想要的灯按钮模板设计,但这是我为理解和实现动态模板所做的全部工作。希望这对来自 WPF 的其他人有所帮助。

从 StateTriggerBase 派生的自定义触发器 class 可以按照您希望的方式执行和绑定,您需要做的就是在您希望更新该触发器时调用 SetActive(true) 或 SetActive(false)。当它为真时,使用该触发器的 VisualState 将处于活动状态。

Here is a sample I've made - XAML:

<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Orientation="Horizontal">
    <StackPanel.Resources>
        <local:MySelector x:Key="MySelector">
            <local:MySelector.GreenTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}" Foreground="Green"/>
                </DataTemplate>
            </local:MySelector.GreenTemplate>
            <local:MySelector.RedTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}" Foreground="Red"/>
                </DataTemplate>
            </local:MySelector.RedTemplate>
        </local:MySelector>
    </StackPanel.Resources>

    <ListView x:Name="ListOfItems" Width="100" ItemTemplateSelector="{StaticResource MySelector}"/>
    <StackPanel>
        <ToggleSwitch OnContent="GREEN" OffContent="RED" Margin="10" IsOn="{x:Bind IsSwitched, Mode=TwoWay}"/>
        <Button Content="Add item" Click="AddClick" Margin="10"/>
    </StackPanel>
</StackPanel>

以及后面的代码:

public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    private bool isSwitched = false;
    public bool IsSwitched
    {
        get { return isSwitched; }
        set { isSwitched = value; RaiseProperty(nameof(IsSwitched)); }
    }

    public MainPage() { this.InitializeComponent(); }

    private void AddClick(object sender, RoutedEventArgs e)
    {
        ListOfItems.Items.Add(new ItemClass { Type = isSwitched ? ItemType.Greed : ItemType.Red, Text = "NEW ITEM" });
    }
}

public enum ItemType { Red, Greed };

public class ItemClass
{
    public ItemType Type { get; set; }
    public string Text { get; set; }
}

public class MySelector : DataTemplateSelector
{
    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        switch ((item as ItemClass).Type)
        {
            case ItemType.Greed:
                return GreenTemplate;
            case ItemType.Red:
            default:
                return RedTemplate;
        }
    }

    public DataTemplate GreenTemplate { get; set; }
    public DataTemplate RedTemplate { get; set; }
}

一般情况下,您的选择器可以选择各种开关,这取决于您的需要。在上面的示例中,我根据项目的 属性、here is a good example 如何切换项目类型来切换模板。