简体   繁体   English

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

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

I don't know if need to combine DataTrigger & Trigger, if there's better way please tell me. 我不知道是否需要结合DataTrigger和Trigger,如果有更好的方法请告诉我。

My goal is, to create a menu(with icons), icons will change while meet hover or selected event. 我的目标是,创建一个菜单(带图标),图标会在遇到悬停或选定的事件时发生变化。

Here's an enum define all menu types: 这是一个枚举定义所有菜单类型:

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

Then I created a MenuItemModel represent each menu item: 然后我创建了一个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); } } }
}

Ok, then I begin to create UI. 好的,然后我开始创建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>

till now everything is very easy, but when I try to make mouseOver and Selected effect, problem comes. 到目前为止一切都很简单,但是当我尝试制作mouseOver和Selected效果时,问题就来了。

for example, if mouse over home_normal.png, it should change to home_hover.png, if IsSelected property is TRUE, image should be ignore hover trigger then use home_selected.png. 例如,如果鼠标超过home_normal.png,则应更改为home_hover.png,如果IsSelected属性为TRUE,则应忽略hover触发器,然后使用home_selected.png。 But there's 3 image, how do I know what image should change? 但是有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>

If you can see the question mark in "MY PLAN" comment, that would be my question: what should I do in the Value field? 如果您可以在“我的计划”评论中看到问号,那就是我的问题:我应该在“价值”字段中做什么?

You can use MultiDataTrigger like this. 您可以像这样使用MultiDataTrigger But you should add same 3 triggers for all types of pages. 但是你应该为所有类型的页面添加相同的3个触发器。 Note that next trigger overrides below and conditions works like logical AND. 请注意,下面的触发器会覆盖下面,条件的作用类似于逻辑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>

In my opinion, the answer you've already received and accepted is a good one. 在我看来,你已经收到并接受的答案是一个很好的答案。 It's entirely XAML-based, which seems to be a primary goal in your scenario, and it should work very well. 它完全基于XAML,这似乎是您的场景中的主要目标,它应该可以很好地工作。 That said, the XAML-only solution is fairly verbose and involves a lot of redundant code. 也就是说,仅XAML解决方案相当冗长,涉及大量冗余代码。 This is already seen in the scenario above where you have two buttons types, each with three possible states. 在上面的场景中已经看到了这种情况,您有两种按钮类型,每种类型都有三种可能的状态。 And it will only get worse as you add button types and states. 当你添加按钮类型和状态时,它只会变得更糟。

If you are willing to do a little code-behind, I think you can accomplish the same effect but with a lot less redundancy. 如果您愿意做一些代码隐藏,我认为您可以实现相同的效果,但冗余更少。

Specifically, if you use <MultiBinding> , you can bind the relevant properties to a collection that can be used to look up the correct image source. 具体来说,如果使用<MultiBinding> ,则可以将相关属性绑定到可用于查找正确图像源的集合。 In order for me to accomplish this, I needed to create a couple of small container types to store the lookup data, and of course the IMultiValueConverter implementation to use them: 为了让我完成这个,我需要创建一些小容器类型来存储查找数据,当然还有IMultiValueConverter实现来使用它们:

Container types: 容器类型:

[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; }
}

Converter: 转换器:

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();
    }
}

In your example, using the above might look something like this: 在您的示例中,使用上面的内容可能如下所示:

<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>

The converter takes, as input, bindings to the properties that affect the visual state of the button. 转换器将输入绑定到影响按钮可视状态的属性。 The first bound value is simply the type of the button; 第一个绑定值只是按钮的类型; this is used to look up the correct array of button states for the button. 这用于查找按钮的正确按钮状态数组。 The remaining bound values (you can have arbitrarily many in this approach) are flags that are searched; 其余的绑定值(在这种方法中你可以任意多个)是搜索的标志; the images are stored in the same order as the flags, with one additional "default" image at the end (ie if no flags are set, the default image is returned). 图像以与标志相同的顺序存储,最后有一个附加的“默认”图像(即如果没有设置标志,则返回默认图像)。

In this way, adding new button types involves only adding a new ButtonImageStates object, specifying the correct key for that button type, and adding new button states involves only adding a single line to each button type's list: the BitmapImage reference that corresponds to the image for that state for that button type. 这样,添加新的按钮类型只涉及添加一个新的ButtonImageStates对象,为该按钮类型指定正确的键,添加新的按钮状态只涉及向每个按钮类型的列表添加一行:与图像对应的BitmapImage引用对于该按钮类型的状态。

Doing it this way drastically cuts down on the amount of code one has to add as new button types and states are needed: a given button type need be mentioned in the XAML only once, and likewise each triggering property is mentioned only once. 这样做可以大大减少必须添加的代码量,因为需要新的按钮类型和状态:只需要在XAML中提及一次给定的按钮类型,同样每个触发属性只提一次。 A XAML-only approach will require a lot of duplicated boilerplate, and the actual image file references will be scattered throughout the style declaration. 仅XAML方法将需要大量重复的样板,并且实际的图像文件引用将分散在样式声明中。


Here is a simple demo of the basic technique. 这是一个基本技术的简单演示。 Lacking a good MCVE to start with, I didn't want to waste time re-creating parts of the code that weren't strictly necessary for the purposes of a demonstration: 缺乏一个好的MCVE ,我不想浪费时间重新创建代码的一部分,这些代码对于演示来说并非严格必要:

  • I only bothered to create four state images, and of course only wrote code to deal with four possible states: two each for two different button types. 我只打算创建四个状态图像,当然只编写代码来处理四种可能的状态:两种不同的按钮类型。
  • I also didn't bother with putting this in a menu; 我也没有把它放在一个菜单中; I'm just using a plain ItemsControl to present the buttons. 我只是使用普通的ItemsControl来显示按钮。
  • Naturally, the view model is a degenerate class; 当然,视图模型是一个退化类; I didn't bother with property-change notification, since it's not needed here. 我没有打扰财产变更通知,因为这里不需要它。 The example still works if you include that though. 如果您包含该示例仍然有效。

Here are the images used in the example (I'm a programmer, not an artist…I considered not even bothering with image content, since that's also not strictly required to demonstrate the basic technique, but figured I could handle four basic images :) ): 以下是示例中使用的图像(我是程序员,而不是艺术家......我认为甚至不打扰图像内容,因为这也不是严格要求演示基本技术,但我认为我可以处理四个基本图像:) ):

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

These are added to the project in a "Resources" folder, with the Build Action set to Resource . 这些将添加到项目的“Resources”文件夹中, Build Action设置为Resource

XAML: 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#: 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; }
}

One minor note: for some reason I get in the XAML editor the following error message on the <Window> element declaration: 一个小注:由于某种原因,我在XAML编辑器中的<Window>元素声明中得到以下错误消息:

Collection property 'TestSO34193266MultiTriggerBinding.ButtonImageStates'.'StateImages' is null. 集合属性'TestSO34193266MultiTriggerBinding.ButtonImageStates'。'StateImages'为空。

I've clearly failed to jump through some hoop the XAML editor wants me to clear with respect to the declaration and/or implementation of ButtonImageStates , but what that is I don't know. 我显然没有跳过XAML编辑器要求我清楚关于ButtonImageStates的声明和/或实现的一些ButtonImageStates ,但是我不知道这是什么。 The code compiles and runs just fine, so I haven't bothered to try to figure that part out. 代码编译并运行得很好,所以我没有费心去试图找出那部分。 It may well be the case that there's a better way to represent the map of button state images, but this way works and other than the spurious error seems fine to me. 情况很可能是有一种更好的方式来表示按钮状态图像的映射,但这种方式起作用,除了虚假错误对我来说似乎很好。

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

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