使用转换器应用并绑定到其他对象的属性的样式在运行时未被覆盖



问题:

当窗口第一次加载时,样式被设置,然后我无法替换它。

XAML:

Style="{Binding Path=BoolProperty, Converter={StaticResource ButtonStyleConverter}}"

VM:

属性:

public bool BoolProperty => SomeOtherObject.YetAnotherObject.OtherBoolProperty;

在构造函数中:

SomeOtherObject.YetAnotherObject.PropertyChanged += (s, a)
=> PropertyChanged.Notify(this, nameof(BoolProperty));

转换器:

  • 转换器返回基于BoolPropertyStyle对象
  • 不包括代码,因为它按预期工作,请参阅解决方案

样式:

  • 已经证明样式是正确的,在这种情况下不会导致问题(请参阅解决方案(

注意:

  • Notify((只是一个扩展方法,可以方便地使用IPropertyChanged
  • 在根事件和所有通知、转换器调用等之后调用相同的Notify((时,样式会正确更新

我验证了以下内容:

  • 当导致更改的事件发生时,将正确调用PropertyChanged.Notify
  • 然后按预期调用BoolProperty的getter
  • 在那之后,转换器被调用,我验证了它是否返回了正确的样式
  • 在Live Property Explorer中检查样式时,很明显第一个样式仍然设置

我尝试过的:

  • .Notify(this, nameof(BoolProperty))更改为.NotifyAll(this)
  • 首先应用第二种样式(看看样式本身是否有问题(
  • 添加UpdateSourceTrigger=PropertyChanged}
  • 仅用BoolProperty替换Path=BoolProperty
  • 添加具有属性名称的ConverterParameter属性

解决方案/解决方案:

多亏了@EldHasp,我能够验证这实际上不是XAMl/Converter/Style问题,而是与Notify((调用的方式有关。我不知道为什么UI在所有调用/线程完成时都不更新,但我通过替换来修复它

SomeOtherObject.YetAnotherObject.PropertyChanged += (s, a)
=> PropertyChanged.Notify(this, nameof(BoolProperty));

带有:

this.Command_That_Also_Relies_On_OtherBoolProperty.CanExecuteChanged += (s, a) 
=> PropertyChanged.Notify(this, nameof(BoolProperty));

虽然是黑客攻击,但在我的情况下,这种变通方法是可以接受的,因为我没有时间进一步调查根本原因。为了完成,命令看起来如下:

public ICommand SomeCommand_That_Also_Relies_On_YetAnotherObject => new RelayCommand(
() =>  /* some code */ ,
() => SomeOtherObject.YetAnotherObject.OtherBoolProperty);

该命令还需要以下内容来刷新:

CommandManager.InvalidateRequerySuggested();

问题似乎是Notify((不是从主线程调用的。

实际解决方案:

从另一个线程修改对象属性时引发OnPropertyChanged

问题显然不在您展示的代码中。这里有一个最简单的例子,效果很好。

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace StyleBindingConverter
{
public class BooleanToStyleConverter : IValueConverter
{
public Style TrueStyle { get; set; }
public Style FalseStyle { get; set; }
private static readonly BooleanConverter boolConverter = new BooleanConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is bool boolean))
{
string str = value?.ToString();
if (string.IsNullOrWhiteSpace(str) ||
!boolConverter.IsValid(str))
return DependencyProperty.UnsetValue;
boolean = (bool)boolConverter.ConvertFromString(value.ToString());
}

return boolean
? TrueStyle
: FalseStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
namespace StyleBindingConverter
{
public class BooleanViewModel
{
public bool BoolProperty { get; set; }
}
}
<Window x:Class="StyleBindingConverter.TestStyleConverterWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StyleBindingConverter"
mc:Ignorable="d"
Title="TestStyleConverterWindow" Height="450" Width="400">
<Window.Resources>
<Style x:Key="Button.Style.True" TargetType="Button">
<Setter Property="Background" Value="Green"/>
</Style>
<Style x:Key="Button.Style.False" TargetType="Button">
<Setter Property="Background" Value="LightCoral"/>
</Style>
<local:BooleanToStyleConverter x:Key="BooleanToStyleConverter"
FalseStyle="{StaticResource Button.Style.False}"
TrueStyle="{StaticResource Button.Style.True}"/>
</Window.Resources>
<Window.DataContext>
<local:BooleanViewModel/>
</Window.DataContext>
<UniformGrid Columns="1">
<Button Content="Test Button" HorizontalAlignment="Center" VerticalAlignment="Center"
Padding="15 5"
Style="{Binding BoolProperty, Converter={StaticResource BooleanToStyleConverter}, Mode=OneWay}"/>
<CheckBox Content="Style Change" HorizontalAlignment="Center" VerticalAlignment="Center"
IsChecked="{Binding BoolProperty, Mode=TwoWay}"/>
</UniformGrid>
</Window>

异步对属性、通知和转换器的行为绝对没有影响。

以下是异步更改属性的示例。使用主题中INotifyPropertyChanged的基本实现:BaseInpc。

using Simplified;
using System.Timers;
namespace StyleBindingConverter
{
public class BooleanViewModelAsync : BaseInpc
{
private bool _boolProperty;
public bool BoolProperty { get => _boolProperty; private set => Set(ref _boolProperty, value); }
private readonly Timer timer = new Timer() { Interval = 1000};
public BooleanViewModelAsync()
{
timer.Elapsed += (s, e) => BoolProperty = !BoolProperty;
timer.Start();
}
}
}
<Window x:Class="StyleBindingConverter.TestStyleConverterAsyncWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StyleBindingConverter"
mc:Ignorable="d"
Title="TestStyleConverterAsyncWindow"
Height="450" Width="400">
<Window.Resources>
<Style x:Key="Button.Style.True" TargetType="Button">
<Setter Property="Background" Value="Green"/>
</Style>
<Style x:Key="Button.Style.False" TargetType="Button">
<Setter Property="Background" Value="LightCoral"/>
</Style>
<local:BooleanToStyleConverter x:Key="BooleanToStyleConverter"
FalseStyle="{StaticResource Button.Style.False}"
TrueStyle="{StaticResource Button.Style.True}"/>
</Window.Resources>
<Window.DataContext>
<local:BooleanViewModelAsync/>
</Window.DataContext>
<Grid>
<Button Content="Test Button" HorizontalAlignment="Center" VerticalAlignment="Center"
Padding="15 5"
Style="{Binding BoolProperty, Converter={StaticResource BooleanToStyleConverter}, Mode=OneWay}"/>
</Grid>
</Window>

最新更新