【问题标题】:How to dynamically change itemtemplate in UWP?如何在 UWP 中动态更改项目模板?
【发布时间】:2018-05-10 04:59:48
【问题描述】:

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

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

你如何在 UWP 中做到这一点?我已经搜索了一分钟,并认为我会在继续挖掘的同时停下来检查一下。请记住,这个项目是纯 MVVM 并且没有使用任何代码。只要不是唯一的方法,我不介意解释背后的代码。

提前致谢:)

【问题讨论】:

  • 您是否考虑过使用DataTemplateSelector 来根据您的需要关联适当的项目模板?
  • 这有点尴尬,因为我认为自己很擅长 XAML,但实际上我从未使用过 DataTemplateSelector。在回复之前,我做了一些研究以了解它,但我不确定我是否完全了解它。我的 ViewModel 中有一个字符串。我想将模板应用于控件(例如按钮),并根据字符串值应用适当的模板。如果可以做到这一点,你介意用一个简单的例子赶上我吗?如果它有效,我可以标记为已回答并无论如何都会投票。

标签: c# xaml uwp uwp-xaml


【解决方案1】:

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 如何打开项目的类型。

【讨论】:

    【解决方案2】:

    这是我正在使用的适用于我给定情况的答案。基本上,您必须使用 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 组中使用,我将在下面发布。我不知道的是,您编写的任何依赖属性都可以在 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,但我在绑定到 LightViewModels 列表的 ItemsControl 中使用它。

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

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

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

    【讨论】:

      猜你喜欢
      • 2010-10-30
      • 2015-02-22
      • 2021-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多