计算字段在 UI 中编辑之前不会更新



我正在尝试以新手程序员的身份使用 XAML 和 C# 测试数据绑定。我有两个绑定到属性的滑块,我想使用滑块设置的属性的两个值的总和更新文本框。

我正在使用INotifyPropertyChanged并尝试更改我能找到的每个属性,但在编辑文本框之前我无法更新文本框,此时文本框将更新为正确的值。使用UpdateSourceTrigger=PropertyChanged仅在编辑文本框后立即更新文本框,而不是在选择另一个元素时更新文本框。我尝试编写一个单独的事件处理程序,该处理程序不使用[CallerNameMember]并使用指定的属性,但它似乎没有改变任何内容。

<Grid>
<Grid.RowDefinitions>
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
Text="{Binding BoundNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontSize="20"
FontWeight="Bold"
AllowDrop="False" />
<Slider Grid.Row="1"
Value="{Binding BoundNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="100"
Minimum="10"
IsSnapToTickEnabled="True"
TickFrequency="10" />
<TextBox Grid.Row="2"
Text="{Binding BoundNumber2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AllowDrop="False" />
<Slider Grid.Row="3"
Value="{Binding BoundNumber2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="100"
Minimum="10"
IsSnapToTickEnabled="True"
TickFrequency="10" />

<TextBox Grid.Row="4"
Name="MathBox"
Text="{Binding QuickMath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}">
</TextBox>
</Grid>

public partial class OrderScreen : INotifyPropertyChanged
{
public OrderScreen()
{
DataContext = this;
InitializeComponent();
}
private int quickMath;
public int QuickMath
{
get { return _boundNumber + _boundNumber2; }
set
{
if (value != quickMath)
{
quickMath = value;
OnPropertyChanged();
}
}
}
private int _boundNumber;
public int BoundNumber
{
get { return _boundNumber; }
set
{
if (_boundNumber != value)
{
_boundNumber = value;
// MathBox.Text = quickMath.ToString();
OnPropertyChanged();
}
}
}
private int _boundNumber2;
public int BoundNumber2
{
get { return _boundNumber2; }
set
{
if (_boundNumber2 != value)
{
_boundNumber2 = value;
MathBox.Text = quickMath.ToString();
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

我可以让它与注释掉的MathBox.Text = quickMath.ToString();一起工作,但我希望有更好的方法来使用数据绑定来做到这一点。期待中的感谢!

绑定机制订阅DataSource对象的PropertyChanged事件,因此无需将事件与INPC实现一起"初始化",但您可能已经注意到,当更改BoundNumberBoundNumber2时,QuickMath属性的PropertyChanged事件确实永远不会触发。

您可以通过不同的方式修复它,例如,为所有受影响的属性显式调用 OnPropertyChanged:

private int _boundNumber;
public int BoundNumber
{
get { return _boundNumber; }
set
{
if (_boundNumber != value)
{
_boundNumber = value;
OnPropertyChanged();
OnPropertyChanged(nameof(QuickMath));
}
}
}

请注意,这样您就可以QuickMath属性保持为只读。此方法在其他情况下非常有效,例如与时间相关的属性,例如,如果数据源属性根据记录的时间戳和当前时间设置字符串格式(如"2 分钟前编辑">),并且您调用PropertyChanged作为定时任务。

public int QuickMath => _boundNumber + _boundNumber2;

或者,您可以更新QuickMath以及修改BoundNumberBoundNumber2以触发OnPropertyChanged()QuickMathsetter 内部的调用:

private int _boundNumber2;
public int BoundNumber2
{
get { return _boundNumber2; }
set
{
if (_boundNumber2 != value)
{
_boundNumber2 = value;
OnPropertyChanged();
QuickMath = BoundNumber + BoundNumber2;
}
}
}

如果QuickMath中的逻辑不允许将其设置为只读属性,这是有意义的。在这种情况下,您必须相应地调整 getter,并在那里使用私有或受保护的 setter 以避免数据不一致和意外行为。

private int _quickMath;
public int QuickMath
{
get { return _quickMath; }
private set
{
if (value != _quickMath)
{
_quickMath = value;
OnPropertyChanged();
}
}
}

在这两种情况下,都不需要双向绑定到QuickMath

<TextBlock Grid.Row="4" Text="{Binding QuickMath, Mode=OneWay}"/>

附带说明一下,查看代码的其余部分,值得一提的是,绑定机制应将 UI 与数据隔离,其中 XAML 知道数据源对象属性(名称和类型),但不知道它的内部实现,而数据源对象根本不了解 XAML。所以

  1. 不应该像MathBox.Text一样从数据对象调用FrameworkElement
  2. 将数据对象类与页面或控件类完全分开被认为是一个好的设计。

希望这有帮助。

您尚未在任何地方初始化PropertyChanged事件,因此永远不会调用它。像这样声明和初始化它:

public event PropertyChangedEventHandler PropertyChanged = delegate { };

绑定到计算属性的文本框QuickMath应从中接收PropertyChanged事件,以便更新字段中的文本。 尽管您OrderScreen实现INotifyPropertyChanged接口,但它不会在更改QuickMath时引发事件,因为它的 setter(引发事件的位置)永远不会被调用。您可以修复它,例如,通过从独立属性资源库调用 QuickMath 资源库,如其他答案中的建议或委托给 DependenciesTracking lib:

public class OrderScreen : INotifyPropertyChanged
{
private readonly IDependenciesMap<OrderScreen> _dependenciesMap =
new DependenciesMap<OrderScreen>()
.AddDependency(i => i.QuickMath, i => i.BoundNumber + i.BoundNumber2, i => i.BoundNumber, i => i.BoundNumber2);
public OrderScreen() => _dependenciesMap.StartTracking(this);
private int _boundNumber2;
private int _boundNumber;
private int _quickMath;
public int QuickMath
{
get => _quickMath;
private set
{
if (value != _quickMath)
{
_quickMath = value;
OnPropertyChanged();
}
}
}
public int BoundNumber
{
get => _boundNumber;
set
{
if (_boundNumber != value)
{
_boundNumber = value;
OnPropertyChanged();
}
}
}
public int BoundNumber2
{
get => _boundNumber2;
set
{
if (_boundNumber2 != value)
{
_boundNumber2 = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class Tests_SO_56623403
{
[Test]
public void Test_SO_56623403()
{
var sut = new OrderScreen();
var raisedEventsCount = 0;
sut.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(OrderScreen.QuickMath))
++raisedEventsCount;
};
Assert.Multiple(() =>
{
Assert.That(sut.QuickMath, Is.EqualTo(0));
Assert.That(raisedEventsCount, Is.EqualTo(0));
});
sut.BoundNumber = 12;
Assert.Multiple(() =>
{
Assert.That(sut.QuickMath, Is.EqualTo(12));
Assert.That(raisedEventsCount, Is.EqualTo(1));
});
sut.BoundNumber2 = 40;
Assert.Multiple(() =>
{
Assert.That(sut.QuickMath, Is.EqualTo(52));
Assert.That(raisedEventsCount, Is.EqualTo(2));
});
}
}

相关内容

  • 没有找到相关文章

最新更新