如何在 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 如何切换项目类型来切换模板。
在 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 如何切换项目类型来切换模板。