繁体   English   中英

C#WPF - 如何组合datatrigger和触发器?

[英]C# WPF - How to Combine datatrigger and trigger?

我不知道是否需要结合DataTrigger和Trigger,如果有更好的方法请告诉我。

我的目标是,创建一个菜单(带图标),图标会在遇到悬停或选定的事件时发生变化。

这是一个枚举定义所有菜单类型:

public enum PageTypes:byte
{
    NotSet = 0,
    HomePage = 1,
    ShopPage = 2,
    AboutPage = 3
}

然后我创建了一个MenuItemModel来表示每个菜单项:

public class MenuItemModel : INotifyPropertyChanged
{
    private PageTypes _menuItemType = PageTypes.NotSet;
    public PageTypes MenuItemType { get { return _menuItemType; } set { if (value != _menuItemType) { _menuItemType = value; RaisePropertyChanged(() => MenuItemType); } } }

    private bool _isSelected = false;
    public bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; RaisePropertyChanged(() => IsSelected); } } }
}

好的,然后我开始创建UI。

<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
    <Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
        <Image>
            <Image.Style>
                <Style TargetType="Image">
                    <Setter Property="Source" Value="/Image/Home_normal.png"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
                            <Setter Property="Source" Value="/Image/Shop_normal.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
                            <Setter Property="Source" Value="/Image/About_normal.png"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>
    </Button>
</DataTemplate>

到目前为止一切都很简单,但是当我尝试制作mouseOver和Selected效果时,问题就来了。

例如,如果鼠标超过home_normal.png,则应更改为home_hover.png,如果IsSelected属性为TRUE,则应忽略hover触发器,然后使用home_selected.png。 但是有3个图像,我怎么知道应该改变什么图像?

<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
    <Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
        <Image>
            <Image.Style>
                <Style TargetType="Image">
                    <Setter Property="Source" Value="/Image/Home_normal.png"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
                            <Setter Property="Source" Value="/Image/Shop_normal.png"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
                            <Setter Property="Source" Value="/Image/About_normal.png"/>
                        </DataTrigger>

                        <!-- MY PLAN -->
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Source" Value="?_hover.png"/>
                        </Trigger>
                        <DataTrigger Binding="{Binding IsSelected}" Value="True">
                            <Setter Property="Source" Value="?_selected.png"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>
    </Button>
</DataTemplate>

如果您可以在“我的计划”评论中看到问号,那就是我的问题:我应该在“价值”字段中做什么?

您可以像这样使用MultiDataTrigger 但是你应该为所有类型的页面添加相同的3个触发器。 请注意,下面的触发器会覆盖下面,条件的作用类似于逻辑AND。

<p:Style.Triggers xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
        <Setter Property="Source" Value="/Image/Shop_normal.png"/>
    </DataTrigger>
    <MultiDataTrigger>
        <MultiDataTrigger.Conditions>
            <Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
            <Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsMouseOver}" Value="true" />
        </MultiDataTrigger.Conditions>
        <Setter Property="Source" Value="/Image/Shop_MouseOver.png" />
    </MultiDataTrigger>
    <MultiDataTrigger>
        <MultiDataTrigger.Conditions>
            <Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
            <Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="true" />
        </MultiDataTrigger.Conditions>
        <Setter Property="Source" Value="/Image/Shop_IsSelected.png" />
    </MultiDataTrigger>
</p:Style.Triggers>

在我看来,你已经收到并接受的答案是一个很好的答案。 它完全基于XAML,这似乎是您的场景中的主要目标,它应该可以很好地工作。 也就是说,仅XAML解决方案相当冗长,涉及大量冗余代码。 在上面的场景中已经看到了这种情况,您有两种按钮类型,每种类型都有三种可能的状态。 当你添加按钮类型和状态时,它只会变得更糟。

如果您愿意做一些代码隐藏,我认为您可以实现相同的效果,但冗余更少。

具体来说,如果使用<MultiBinding> ,则可以将相关属性绑定到可用于查找正确图像源的集合。 为了让我完成这个,我需要创建一些小容器类型来存储查找数据,当然还有IMultiValueConverter实现来使用它们:

容器类型:

[ContentProperty("Elements")]
class BitmapImageArray
{
    private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();

    public List<ButtonImageStates> Elements
    {
        get { return _elements; }
    }
}

class ButtonImageStates
{
    public string Key { get; set; }
    public BitmapImage[] StateImages { get; set; }
}

转换器:

class OrderedFlagConverter : IMultiValueConverter
{
    public object Convert(object[] values,
        Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        BitmapImageArray imageData = (BitmapImageArray)parameter;
        string type = (string)values[0];

        foreach (ButtonImageStates buttonStates in imageData.Elements)
        {
            if (buttonStates.Key == type)
            {
                int index = 1;

                while (index < values.Length)
                {
                    if ((bool)values[index])
                    {
                        break;
                    }

                    index++;
                }

                return buttonStates.StateImages[index - 1];
            }
        }

        return DependencyProperty.UnsetValue;
    }

    public object[] ConvertBack(object value,
        Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

在您的示例中,使用上面的内容可能如下所示:

<DataTemplate x:Key="MenuTemplate">
  <Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
    <Image>
      <Image.Source>
        <MultiBinding>
          <MultiBinding.Converter>
            <l:OrderedFlagConverter/>
          </MultiBinding.Converter>
          <MultiBinding.ConverterParameter>
            <l:BitmapImageArray>
              <l:ButtonImageStates Key="ShopPage">
                <l:ButtonImageStates.StateImages>
                  <x:Array Type="{x:Type BitmapImage}">
                    <BitmapImage UriSource="/Image/Shop_selected.png"/>
                    <BitmapImage UriSource="/Image/Shop_hover.png"/>
                    <BitmapImage UriSource="/Image/Shop_normal.png"/>
                  </x:Array>
                </l:ButtonImageStates.StateImages>
              </l:ButtonImageStates>
              <l:ButtonImageStates Key="AboutPage">
                <l:ButtonImageStates.StateImages>
                  <x:Array Type="{x:Type BitmapImage}">
                    <BitmapImage UriSource="/Image/About_selected.png"/>
                    <BitmapImage UriSource="/Image/About_hover.png"/>
                    <BitmapImage UriSource="/Image/About_normal.png"/>
                  </x:Array>
                </l:ButtonImageStates.StateImages>
              </l:ButtonImageStates>
            </l:BitmapImageArray>
          </MultiBinding.ConverterParameter>
          <Binding Path="ButtonType"/>
          <Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
          <Binding Path="IsSelected"/>
        </MultiBinding>
      </Image.Source>
    </Image>
  </Button>
</DataTemplate>

转换器将输入绑定到影响按钮可视状态的属性。 第一个绑定值只是按钮的类型; 这用于查找按钮的正确按钮状态数组。 其余的绑定值(在这种方法中你可以任意多个)是搜索的标志; 图像以与标志相同的顺序存储,最后有一个附加的“默认”图像(即如果没有设置标志,则返回默认图像)。

这样,添加新的按钮类型只涉及添加一个新的ButtonImageStates对象,为该按钮类型指定正确的键,添加新的按钮状态只涉及向每个按钮类型的列表添加一行:与图像对应的BitmapImage引用对于该按钮类型的状态。

这样做可以大大减少必须添加的代码量,因为需要新的按钮类型和状态:只需要在XAML中提及一次给定的按钮类型,同样每个触发属性只提一次。 仅XAML方法将需要大量重复的样板,并且实际的图像文件引用将分散在样式声明中。


这是一个基本技术的简单演示。 缺乏一个好的MCVE ,我不想浪费时间重新创建代码的一部分,这些代码对于演示来说并非严格必要:

  • 我只打算创建四个状态图像,当然只编写代码来处理四种可能的状态:两种不同的按钮类型。
  • 我也没有把它放在一个菜单中; 我只是使用普通的ItemsControl来显示按钮。
  • 当然,视图模型是一个退化类; 我没有打扰财产变更通知,因为这里不需要它。 如果您包含该示例仍然有效。

以下是示例中使用的图像(我是程序员,而不是艺术家......我认为甚至不打扰图像内容,因为这也不是严格要求演示基本技术,但我认为我可以处理四个基本图像:) ):

red_normal.png
red_hover.png
green_normal.png
green_hover.png

这些将添加到项目的“Resources”文件夹中, Build Action设置为Resource

XAML:

<Window x:Class="TestSO34193266MultiTriggerBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:TestSO34193266MultiTriggerBinding"
        Title="MainWindow" Height="350" Width="525">

  <Window.Resources>
    <l:OrderedFlagConverter x:Key="orderedFlagConverter1"/>

    <BitmapImage x:Key="bitmapRedNormal"
                 UriSource="pack://application:,,,/Resources/red_normal.png"/>
    <BitmapImage x:Key="bitmapRedHover"
                 UriSource="pack://application:,,,/Resources/red_hover.png"/>
    <BitmapImage x:Key="bitmapGreenNormal"
                 UriSource="pack://application:,,,/Resources/green_normal.png"/>
    <BitmapImage x:Key="bitmapGreenHover"
                 UriSource="pack://application:,,,/Resources/green_hover.png"/>

    <l:ViewModel x:Key="redViewModel" ButtonType="Red"/>
    <l:ViewModel x:Key="greenViewModel" ButtonType="Green"/>

    <x:Array x:Key="items" Type="{x:Type l:ViewModel}">
      <StaticResource ResourceKey="redViewModel"/>
      <StaticResource ResourceKey="greenViewModel"/>
    </x:Array>

    <x:Array x:Key="redButtonStates" Type="{x:Type BitmapImage}">
      <StaticResource ResourceKey="bitmapRedHover"/>
      <StaticResource ResourceKey="bitmapRedNormal"/>
    </x:Array>

    <x:Array x:Key="greenButtonStates" Type="{x:Type BitmapImage}">
      <StaticResource ResourceKey="bitmapGreenHover"/>
      <StaticResource ResourceKey="bitmapGreenNormal"/>
    </x:Array>

    <l:BitmapImageArray x:Key="allButtonStates">
      <l:ButtonImageStates Key="Red" StateImages="{StaticResource redButtonStates}"/>
      <l:ButtonImageStates Key="Green" StateImages="{StaticResource greenButtonStates}"/>
    </l:BitmapImageArray>

    <ItemsPanelTemplate x:Key="panelTemplate">
      <StackPanel IsItemsHost="True" Orientation="Horizontal"/>
    </ItemsPanelTemplate>

    <DataTemplate x:Key="template" DataType="l:ViewModel">
      <Button>
        <Image Stretch="None">
          <Image.Source>
            <MultiBinding Converter="{StaticResource orderedFlagConverter1}"
                          ConverterParameter="{StaticResource allButtonStates}">
              <Binding Path="ButtonType"/>
              <Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
            </MultiBinding>
          </Image.Source>
        </Image>
      </Button>
    </DataTemplate>

    <!-- explicit namespace only for the benefit of Stack Overflow formatting -->
    <p:Style TargetType="ItemsControl"
             xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
      <Setter Property="ItemsSource" Value="{StaticResource items}"/>
      <Setter Property="ItemsPanel" Value="{StaticResource panelTemplate}"/>
    </p:Style>
  </Window.Resources>

  <StackPanel>
    <ItemsControl ItemTemplate="{StaticResource template}"/>
  </StackPanel>
</Window>

C#:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

class ViewModel
{
    public string ButtonType { get; set; }
}

class OrderedFlagConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        BitmapImageArray imageData = (BitmapImageArray)parameter;
        string type = (string)values[0];

        foreach (ButtonImageStates buttonStates in imageData.Elements)
        {
            if (buttonStates.Key == type)
            {
                int index = 1;

                while (index < values.Length)
                {
                    if ((bool)values[index])
                    {
                        break;
                    }

                    index++;
                }

                return buttonStates.StateImages[index - 1];
            }
        }

        return DependencyProperty.UnsetValue;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

[ContentProperty("Elements")]
class BitmapImageArray
{
    private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();

    public List<ButtonImageStates> Elements
    {
        get { return _elements; }
    }
}

class ButtonImageStates
{
    public string Key { get; set; }
    public BitmapImage[] StateImages { get; set; }
}

一个小注:由于某种原因,我在XAML编辑器中的<Window>元素声明中得到以下错误消息:

集合属性'TestSO34193266MultiTriggerBinding.ButtonImageStates'。'StateImages'为空。

我显然没有跳过XAML编辑器要求我清楚关于ButtonImageStates的声明和/或实现的一些ButtonImageStates ,但是我不知道这是什么。 代码编译并运行得很好,所以我没有费心去试图找出那部分。 情况很可能是有一种更好的方式来表示按钮状态图像的映射,但这种方式起作用,除了虚假错误对我来说似乎很好。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM