【问题标题】:Regular databinding seems to disable triggers常规数据绑定似乎禁用触发器
【发布时间】:2013-04-30 14:20:47
【问题描述】:

我刚刚遇到了 WPF 的另一个问题。

我有一组自定义控件(复合控件;由网格内的边框等组成)。

我通过触发器和绑定来控制他们的Background 颜色。我希望它们在鼠标悬停时变得更暗(通过触发器和自定义IValueConverter 实现),而且在选择(即单击)时更改颜色。后者由普通的Setter 完成。

<Grid Width="150" Height="50" Margin="5">
    <Border CornerRadius="3" BorderBrush="Black" BorderThickness="0.5" >
        <Border.Resources>
            <local:BackgroundConverter x:Key="ColorConverter"/>
        </Border.Resources>
        <Border.Style>
            <Style TargetType="Border">                    
                <Style.Triggers>
                    <Trigger Property="Grid.IsMouseOver" Value="True">
                        <Setter Property="Background" Value="{Binding Path=MyStatus, Converter={StaticResource ColorConverter}}"/>
                    </Trigger>
                    <Trigger Property="Grid.IsMouseOver" Value="False">
                        <Setter Property="Background" Value="{Binding Path=MyStatus, Converter={StaticResource ColorConverter}}"/>
                    </Trigger>
                </Style.Triggers>
                <Setter Property="Background" Value="{Binding Path=MyStatus, Converter={StaticResource ColorConverter}}"/>
            </Style>
        </Border.Style>
        <Grid>                
            <Grid.RowDefinitions>
                <RowDefinition Height="0.6*"/>
                <RowDefinition Height="0.5*"/>
            </Grid.RowDefinitions>                                
            <TextBlock Grid.Row="0" FontSize="14" TextAlignment="Center" VerticalAlignment="Center" FontWeight="Bold">                    
                <Label Foreground="{Binding Path=TextColor}" Content="{Binding Path=ID}"/>
            </TextBlock>
            <TextBlock Grid.Row="1" FontSize="9" TextAlignment="Center" VerticalAlignment="Top" Margin="0" Padding="0">
                <Label Content="{Binding Path=StockName}"/>
            </TextBlock>
        </Grid>
    </Border>
</Grid>

鼠标悬停效果正常工作,直到我单击其中一个控件。触发器在该点停止工作(除了尚未单击的控件)。

我有点困惑。如何在不禁用触发器的情况下使用绑定?

如有必要,我会提供更多详细信息。


@雷切尔

您可以稍后发布您的转换器代码吗?我不明白如何 IsMouseOver 属性被传递给转换器,所以它是 可能是在更改时未更新的静态值。而且因为 触发时该值不会改变,它可能不会打扰 重新评估价值。你可能会更好地使用 IMutliValueConverter 并将其传递给 IsMouseOver 和 MyStatus,所以它 每当这两个值中的任何一个发生变化时都会重新评估

我没有使用IMultiValueConverter。为此,我创建了自己的“复合”对象,其中包括IsMouseOver。这是必要的,因为背景颜色应该是根据我自己的数据(无论是选择项目还是映射项目)以及鼠标悬停(无论背景颜色如何,鼠标悬停时都应该稍微变暗)计算出来的.

转换器代码:

public class BackgroundConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Debug.WriteLine("BackgroundConverter.Convert()");
        if (!(value is StockViewBackgroundStatus))
        {
            throw new ArgumentException("value");
        }
        var casted = (StockViewBackgroundStatus)value;
        if (casted.IsNone)
        {
            if (casted.IsMouseOver)
            {
                return new SolidColorBrush(Colors.Gray);
            }
            else
            {
                return CreateLinearGradient(Colors.Gray, false);
            }
        }
        switch (casted.Status)
        {
            case StockItem.Status.Mapped:
                {
                    return CreateLinearGradient(Color.FromRgb(83, 165, 18), casted.IsMouseOver);
                }
            case StockItem.Status.MappedElsewhere:
                {
                    return CreateLinearGradient(Color.FromRgb(104, 189, 36), casted.IsMouseOver);
                }
            case StockItem.Status.NotMapped:
                {
                    return CreateLinearGradient(Colors.LightGray, casted.IsMouseOver);
                }
            default:
                {
                    throw new NotImplementedException(casted.Status.ToString());
                }
        }            
    }

    private static LinearGradientBrush CreateLinearGradient(Color initial, bool darker)
    {
        var darkened = darker ? 0.1 : 0;
        return new LinearGradientBrush(
            Lighten(initial, 1.05 - darkened),
            Lighten(initial, 0.95 - darkened), 
            90);
    }

    private static Color Lighten(Color initial, double factor)
    {
        Func<double, double> trunc = (value) => (Math.Max(0, Math.Min(255, value)));
        var resulting = Color.FromRgb(
            System.Convert.ToByte(trunc(initial.R * factor)),
            System.Convert.ToByte(trunc(initial.G * factor)),
            System.Convert.ToByte(trunc(initial.B * factor)));
        return resulting;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Debug.WriteLine("BackgroundConverter.ConvertBack()");
        return value;
    }
}

StockItem对象

public partial class StockItem : UserControl, INotifyPropertyChanged
{
    private bool _empty;
    public StockItem()
    {
        InitializeComponent();
        DataContext = this;
    }

    private string _id;
    public string ID
    {
        get
        {
            return _id;
        }
        set
        {
            _id = value;
            RaisePropertyChanged("ID");
        }

    }

    public Brush TextColor
    {
        get
        {                
            Color color = IsSelected ? Colors.White : Colors.Black;
            return new SolidColorBrush(color);
        }
    }

    private string _stockName;
    public string StockName
    {
        get
        {
            return _stockName;
        }
        set
        {
            _stockName = value;
            RaisePropertyChanged("StockName");
        }
    }

    StockViewBackgroundStatus _status;
    public StockViewBackgroundStatus MyStatus
    {
        get
        {
            return new StockViewBackgroundStatus()
            {
                IsMouseOver = this.IsMouseOver,
                IsNone = IsEmpty,
                Status = MappingStatus
            };
        }
        set
        {
            _status = value;
            Debug.WriteLine("in " + ID + "...");
            Debug.WriteLine("RaisePropertyChanged(\"IsMouseOver\")");
            Debug.WriteLine("RaisePropertyChanged(\"MyStatus\")");

            RaisePropertyChanged("IsMouseOver"); // added, but doesn't help
            RaisePropertyChanged("MyStatus");
        }
    }

    public bool IsEmpty
    {
        get
        {
            return _empty;
        }
    }

    public static StockItem EmptyStock
    {
        get
        {
            return new StockItem()
            {
                _empty = true,
                ID = "none",
                Name = String.Empty
            };
        }
    }

    internal EventHandler Selected
    {
        get;
        set;
    }

    private Status _mappingStatus;
    public Status MappingStatus
    {
        get
        {
            return _mappingStatus;
        }
        set
        {
            _mappingStatus = value;
            Debug.WriteLine("in " + ID + "...");
            Debug.WriteLine("RaisePropertyChanged(\"MappingStatus\")");
            Debug.WriteLine("RaisePropertyChanged(\"TextColor\")");
            RaisePropertyChanged("MappingStatus");
            RaisePropertyChanged("TextColor");

            MyStatus = new StockViewBackgroundStatus() { IsMouseOver = this.IsMouseOver, IsNone = _empty, Status = value };                

            if (value == Status.Mapped && Selected != null)
            {
                Selected(this, null);
            }
        }
    }

    public bool IsSelected
    {
        get
        {
            return MappingStatus == Status.Mapped;
        }
    }

    public enum Status
    {            
        Mapped,
        MappedElsewhere,
        NotMapped
    }

    protected void RaisePropertyChanged(string property)
    {
        if (PropertyChanged == null)
        {
            return;
        }
        PropertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

封装集合的视图类(我实际上设置为DataContext

public class TargetStocks
{
    public ObservableCollection<StockItem> AllStocks
    {
        get;
        set;
    }

    public void Add(StockItem sv, EventHandler selected)
    {
        if (sv == null)
        {
            throw new ArgumentNullException("sv");
        }
        sv.MouseDown += sv_MouseDown;
        if (selected != null)
        {
            sv.Selected += selected;
        }
        if (AllStocks == null)
        {
            AllStocks = new ObservableCollection<StockItem>();
        }
        AllStocks.Add(sv);
    }

    public void AddRange(IEnumerable<StockItem> stocks, EventHandler selected)
    {
        foreach (var stock in stocks)
        {
            Add(stock, selected);
        }
    }

    void sv_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (!(sender is StockItem))
        {
            return;
        }
        var sv = (StockItem)sender;
        foreach (StockItem stock in AllStocks)
        {
            if (stock.MappingStatus == StockItem.Status.Mapped)
            {
                // this seems to kill the trigger
                stock.MappingStatus = StockItem.Status.NotMapped;
            }
            if (stock == sv && sv.MappingStatus != StockItem.Status.Mapped)
            {
                // as above
                stock.MappingStatus = StockItem.Status.Mapped;
            }
        }
    }
}

正如调试所显示的那样,在单击任何库存项目之前(或在更改其中任何一个 MappingStatus 之前)鼠标悬停效果在根本不触发转换器的情况下起作用。

Convert 根本没有被调用。

它在似乎禁用(或分离)触发器的MouseDown 事件处理程序中设置MappingStatus

【问题讨论】:

  • 您能否编辑您的问题以包含更改背景颜色的点击代码?另外,您的Click 事件是在绑定中设置源属性,还是设置边框的Background 属性?如果是第二个,Depdencey Property Precedence 规定在对象本身上设置的值优先于样式化或触发的值。
  • 正如@Rachel 所问 - 你的Click 在做什么?
  • 也许我遗漏了一些东西,但你的触发器不是都返回相同的值吗?它们都将Status 设置为{Binding Path=MyStatus, Converter={StaticResource ColorConverter}},这也与默认Setter 的值相同,因此无论鼠标是否在其上,您的背景颜色都将保持静态...
  • @Rachel @XAMeLi Click 正在重置所有子项的MyStatus (引发INotifyPropertyChanged.PropertyChanged 事件)(所有子项,因为当单击的项被选中时,其余项因此未被选中)
  • @Rachel - 转换器将IsMouseOver 属性考虑在内。多亏了它,它最初可以工作。问题是触发器被禁用(?),从那时起转换器不再使用(通过调试代码验证)。

标签: wpf xaml data-binding triggers


【解决方案1】:

正常触发器触发不会重新评估绑定。

您可以将属性更改为新的绑定对象,但是绑定本身仅在第一次作为触发器的结果使用时才被评估。

因此,当您的 IsMouseOver 属性更改时,Background 属性将从一个 Binding 对象更改为另一个 Binding 对象,但绑定本身不会被重新评估。

如果您在绑定值上发出 PropertyChange 通知,则绑定重新评估。

作为测试,将Debug 行或断点添加到您的转换器,并在绑定值上触发PropertyChanged 通知来测试它。当它收到更改通知并重新评估时,您会看到它被击中。

如果您希望在多个属性之一更改时评估绑定值,请使用IMultiValueConverter

【讨论】:

  • 假期回来;我相应地更新了我的问题。感谢您的帮助:(
  • @KonradMorawski 谢谢。根据您的代码,您绝对应该考虑切换到IMultiValueConverter,因为如果IsMouseOverMappingStatus 发生更改,或者MyStatus 值实际上没有更改为新值,则不会重新评估绑定.您可以创建一个IMultiValueConverter,它既接受来自 UI 的 IsMouseOver 属性,也接受来自数据的 MappingStatusIsSelected 属性。
  • 谢谢,我会试试的。但是为什么它(目前)只有在点击之前才能正常工作?
  • @KonradMorawski 它仅在第一次触发发生时评估绑定值,并缓存该结果。将鼠标移到存储 ColorA 并将其移出存储 ColorB,因此在不重新评估转换器的情况下来回移动时颜色会发生变化,但是当您在 MyStatus 中触发 MyStatus 上的 PropertyChange 通知时@事件时,两个绑定都会被重新评估,并且两个触发器的缓存值都会更新为IsMouseOver 颜色,因为在单击它时鼠标位于项目上方。
  • 感谢您的解释。我会去尝试IMultiValueConverter。已接受答案
猜你喜欢
  • 2011-12-14
  • 2018-02-02
  • 1970-01-01
  • 2010-10-29
  • 2018-05-14
  • 1970-01-01
  • 2010-11-02
  • 2010-10-18
  • 1970-01-01
相关资源
最近更新 更多