0

我刚刚遇到了 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根本没有被调用。

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

4

1 回答 1

1

由于正常的触发器触发,绑定不会被重新评估。

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

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

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

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

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

于 2013-04-30T19:25:35.543 回答