用于数据绑定计算的延迟触发器
delayed trigger for calculation with data binding
我对 WPF 还很陌生,目前正在学习数据绑定的概念。
我的简化 XAML 代码。除了我的问题(下面),它工作正常 - 通过 GUI 快速和肮脏地放置对象,一旦工作将被清理:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
</Grid>
<Grid Grid.Row="1">
<GroupBox Header="Change Type:" Height="95" Width="100" VerticalAlignment="Top" Margin="270,4,422,0" >
<StackPanel>
<RadioButton x:Name="RbtAdd" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Add" Foreground="Green"/>
</WrapPanel>
</RadioButton>
<RadioButton x:Name="RbtPull" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Pull" Foreground="Blue"/>
</WrapPanel>
</RadioButton>
<RadioButton x:Name="RbtModify" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Modify" Foreground="DarkGray"/>
</WrapPanel>
</RadioButton>
</StackPanel>
</GroupBox>
<TextBlock x:Name="txtCurStock" HorizontalAlignment="Left" Margin="330,181,0,0" TextWrapping="Wrap" Text="{Binding Path=CurrentStock}" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" TextAlignment="Center"/>
<Label Content="Current stock:" HorizontalAlignment="Left" Margin="289,156,0,0" VerticalAlignment="Top"/>
<Label x:Name ="lblOperation" Content="Stock to Pull:" HorizontalAlignment="Left" Margin="507,156,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtEntry" HorizontalAlignment="Left" Height="32" Margin="489,181,0,0" TextWrapping="Wrap" TextAlignment="Center" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="120" FontSize="20" FontWeight="Bold" TextChanged="TxtEntry_TextChanged"/>
<Label Content="New Stock" HorizontalAlignment="Right" Margin="714,156,0,0" VerticalAlignment="Top" Width="68"/>
<TextBlock Text="{Binding Path=NewStock}" HorizontalAlignment="Right" Margin="0,186,10,0" TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" Width="68"/>
<TextBox x:Name="txtComment" HorizontalAlignment="Left" Height="86" Margin="289,233,0,0" TextWrapping="Wrap" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="493"/>
<Label Content="Comment:" HorizontalAlignment="Left" Margin="289,207,0,0" VerticalAlignment="Top"/>
<TextBlock x:Name ="txtModindicator" HorizontalAlignment="Left" Margin="433,181,0,0" TextWrapping="Wrap" Text="-" FontSize="20" FontWeight="Bold" VerticalAlignment="Top"/>
<TextBlock x:Name ="txtResindicator" HorizontalAlignment="Left" Margin="663,182,0,0" TextWrapping="Wrap" Text="=" FontSize="20" FontWeight="Bold" VerticalAlignment="Top"/>
</Grid>
</Grid>
现在缩短的 C# 代码:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SomeWPF
{
/// <summary>
/// Interaction logic for ModifyWindow.xaml
/// </summary>
public partial class MainWindow : INotifyPropertyChanged
{
public enum Mymode
{
add,
pull,
modify
}
public Mymode mode;
public MainWindow()
{
DataContext = this;
InitializeComponent();
CurrentStock = 5;
RbtPull.IsChecked = true;
ModEntry = 1;
}
private void ModeRadio_Checked(object sender, RoutedEventArgs e)
{
if (sender != null)
{
if (sender.Equals(RbtAdd))
{
mode = Mymode.add;
txtModindicator.Text = "+";
txtComment.Text = "Add";
lblOperation.Content = "Stock to Add:";
}
else if (sender.Equals(RbtPull))
{
mode = Mymode.pull;
txtModindicator.Text = "-";
txtComment.Text = "Pull";
lblOperation.Content = "Stock to Pull:";
}
else
{
mode = Mymode.modify;
txtModindicator.Text = "~";
lblOperation.Content = "Corrected Quantity:";
txtComment.Text = "Mod";
}
TxtEntry_TextChanged(sender, null);
}
}
private void TxtEntry_TextChanged(object sender, TextChangedEventArgs e)
{
if (mode == Mymode.add)
{
NewStock = CurrentStock + ModEntry;
}
else if (mode == Mymode.pull)
{
NewStock = CurrentStock - ModEntry;
}
else
{
NewStock = ModEntry;
}
}
#region Binding Stuff
private int _newstock;
public int NewStock
{
get
{
return _newstock;
}
set
{
if (_newstock != value)
{
_newstock = value;
OnPropertyChanged();
}
}
}
private int _modentry;
public int ModEntry
{
get
{
return _modentry;
}
set
{
if (_modentry != value)
{
_modentry = value;
OnPropertyChanged();
}
}
}
private int _currentstock;
public int CurrentStock
{
get
{
return _currentstock;
}
set
{
if (_currentstock != value)
{
_currentstock = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
所以这个 window 是一个用于库存存储的小程序中的弹出窗口,供用户输入库存的移动。
到目前为止一切都很好,我现在想做非常简单的计算部分。使用 "old" winforms c# 你只需获取值并更新结果 "manually" 的文本 属性 但当然我们 (I) 想学习新东西并使用数据绑定进行操作.
代码也做了计算,但触发器不是我想要的。
假设当前库存为 5
当 window 加载时,模式设置为 Pull (RbtPull) 并且用户条目(绑定到 ModEntry)通过代码设置为 1。因此 NewStock 应该是 4 才能正确显示。 (是的)
此外,注释字段(目前用于调试)显示 ModEntry 值 1。
到目前为止一切顺利。
现在我在 Stock to Pull 字段中输入 3,但没有任何反应。 (我希望它做出反应 "realtime")。新的股票仍然显示为4,评论仍然显示为1。
当我离开该字段(并单击评论字段)时 - 检测到 属性 更改并且评论字段也显示 3 (=ModEntry) - 所以它不是 "realtime" 但仅在该字段是时触发失去焦点,但这也是可以接受的。
真正的问题是:新的Stock留4个不计算。
现在,当我再次输入 Stock to Pull 字段并将值更改为 5 时,New Stock 字段更新为 2(所以我在 5-3=2 之前输入的值)
再次用 5 覆盖该字段会将新 Stock 更改为 0。
所以总是 "one step behind".
根据我的发现,我有一个想法,我需要某种绑定转换器而不是我的计算方法,但我真的找不到合适的东西,而且我对数据绑定还不够熟悉。已经直接在绑定变量代码中尝试了一些东西,但 none 有效。如果有人能在正确的方向上暗示我,我将非常感激。 (不需要银板解决方案,只需要知道搜索方式(例如,如果我使用的那种绑定完全有意义,或者是否有我必须添加的东西等)。
非常感谢!
PS:当然,如果有人愿意提供镀银解决方案,我将不胜感激。 :) - 抱歉英语不好,没有母语人士。
@nosale 的第二个 link(见评论)提供了问题的答案。
将 XAML 字段 txtEntry 和结果字段设置为 UpdateSourceTrigger=PropertyChanged 解决了这个问题。
所以正确的块现在看起来像这样,没有更改 c# 代码:
<Label Content="Current stock:" HorizontalAlignment="Left" Margin="289,156,0,0" VerticalAlignment="Top"/>
<Label x:Name ="lblOperation" Content="Stock to Pull:" HorizontalAlignment="Left" Margin="507,156,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtEntry" HorizontalAlignment="Left" Height="32" Margin="489,181,0,0" TextWrapping="Wrap" TextAlignment="Center" Text="{Binding Path=ModEntry, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120" FontSize="20" FontWeight="Bold" TextChanged="TxtEntry_TextChanged"/>
<Label Content="New Stock" HorizontalAlignment="Right" Margin="714,156,0,0" VerticalAlignment="Top" Width="68"/>
<TextBlock Text="{Binding Path=NewStock, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Right" Margin="0,186,10,0" TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" Width="68"/>
<TextBox x:Name="txtComment" HorizontalAlignment="Left" Height="86" Margin="289,233,0,0" TextWrapping="Wrap" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="493"/>
<Label Content="Comment:" HorizontalAlignment="Left" Margin="289,207,0,0" VerticalAlignment="Top"/>
原因是,文本框有默认的 UpdateSourceTrigger=LostFocus 而不是 PropertyChanged 以防止用户输入错别字时进行更新。
新学到的东西:WPF 很酷,可以自动处理不合理的值,如 null 或字符串,并将字段标记为红色! :)
再次感谢 links!
我对 WPF 还很陌生,目前正在学习数据绑定的概念。
我的简化 XAML 代码。除了我的问题(下面),它工作正常 - 通过 GUI 快速和肮脏地放置对象,一旦工作将被清理:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
</Grid>
<Grid Grid.Row="1">
<GroupBox Header="Change Type:" Height="95" Width="100" VerticalAlignment="Top" Margin="270,4,422,0" >
<StackPanel>
<RadioButton x:Name="RbtAdd" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Add" Foreground="Green"/>
</WrapPanel>
</RadioButton>
<RadioButton x:Name="RbtPull" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Pull" Foreground="Blue"/>
</WrapPanel>
</RadioButton>
<RadioButton x:Name="RbtModify" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Modify" Foreground="DarkGray"/>
</WrapPanel>
</RadioButton>
</StackPanel>
</GroupBox>
<TextBlock x:Name="txtCurStock" HorizontalAlignment="Left" Margin="330,181,0,0" TextWrapping="Wrap" Text="{Binding Path=CurrentStock}" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" TextAlignment="Center"/>
<Label Content="Current stock:" HorizontalAlignment="Left" Margin="289,156,0,0" VerticalAlignment="Top"/>
<Label x:Name ="lblOperation" Content="Stock to Pull:" HorizontalAlignment="Left" Margin="507,156,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtEntry" HorizontalAlignment="Left" Height="32" Margin="489,181,0,0" TextWrapping="Wrap" TextAlignment="Center" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="120" FontSize="20" FontWeight="Bold" TextChanged="TxtEntry_TextChanged"/>
<Label Content="New Stock" HorizontalAlignment="Right" Margin="714,156,0,0" VerticalAlignment="Top" Width="68"/>
<TextBlock Text="{Binding Path=NewStock}" HorizontalAlignment="Right" Margin="0,186,10,0" TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" Width="68"/>
<TextBox x:Name="txtComment" HorizontalAlignment="Left" Height="86" Margin="289,233,0,0" TextWrapping="Wrap" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="493"/>
<Label Content="Comment:" HorizontalAlignment="Left" Margin="289,207,0,0" VerticalAlignment="Top"/>
<TextBlock x:Name ="txtModindicator" HorizontalAlignment="Left" Margin="433,181,0,0" TextWrapping="Wrap" Text="-" FontSize="20" FontWeight="Bold" VerticalAlignment="Top"/>
<TextBlock x:Name ="txtResindicator" HorizontalAlignment="Left" Margin="663,182,0,0" TextWrapping="Wrap" Text="=" FontSize="20" FontWeight="Bold" VerticalAlignment="Top"/>
</Grid>
</Grid>
现在缩短的 C# 代码:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SomeWPF
{
/// <summary>
/// Interaction logic for ModifyWindow.xaml
/// </summary>
public partial class MainWindow : INotifyPropertyChanged
{
public enum Mymode
{
add,
pull,
modify
}
public Mymode mode;
public MainWindow()
{
DataContext = this;
InitializeComponent();
CurrentStock = 5;
RbtPull.IsChecked = true;
ModEntry = 1;
}
private void ModeRadio_Checked(object sender, RoutedEventArgs e)
{
if (sender != null)
{
if (sender.Equals(RbtAdd))
{
mode = Mymode.add;
txtModindicator.Text = "+";
txtComment.Text = "Add";
lblOperation.Content = "Stock to Add:";
}
else if (sender.Equals(RbtPull))
{
mode = Mymode.pull;
txtModindicator.Text = "-";
txtComment.Text = "Pull";
lblOperation.Content = "Stock to Pull:";
}
else
{
mode = Mymode.modify;
txtModindicator.Text = "~";
lblOperation.Content = "Corrected Quantity:";
txtComment.Text = "Mod";
}
TxtEntry_TextChanged(sender, null);
}
}
private void TxtEntry_TextChanged(object sender, TextChangedEventArgs e)
{
if (mode == Mymode.add)
{
NewStock = CurrentStock + ModEntry;
}
else if (mode == Mymode.pull)
{
NewStock = CurrentStock - ModEntry;
}
else
{
NewStock = ModEntry;
}
}
#region Binding Stuff
private int _newstock;
public int NewStock
{
get
{
return _newstock;
}
set
{
if (_newstock != value)
{
_newstock = value;
OnPropertyChanged();
}
}
}
private int _modentry;
public int ModEntry
{
get
{
return _modentry;
}
set
{
if (_modentry != value)
{
_modentry = value;
OnPropertyChanged();
}
}
}
private int _currentstock;
public int CurrentStock
{
get
{
return _currentstock;
}
set
{
if (_currentstock != value)
{
_currentstock = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
所以这个 window 是一个用于库存存储的小程序中的弹出窗口,供用户输入库存的移动。 到目前为止一切都很好,我现在想做非常简单的计算部分。使用 "old" winforms c# 你只需获取值并更新结果 "manually" 的文本 属性 但当然我们 (I) 想学习新东西并使用数据绑定进行操作. 代码也做了计算,但触发器不是我想要的。
假设当前库存为 5 当 window 加载时,模式设置为 Pull (RbtPull) 并且用户条目(绑定到 ModEntry)通过代码设置为 1。因此 NewStock 应该是 4 才能正确显示。 (是的) 此外,注释字段(目前用于调试)显示 ModEntry 值 1。 到目前为止一切顺利。
现在我在 Stock to Pull 字段中输入 3,但没有任何反应。 (我希望它做出反应 "realtime")。新的股票仍然显示为4,评论仍然显示为1。 当我离开该字段(并单击评论字段)时 - 检测到 属性 更改并且评论字段也显示 3 (=ModEntry) - 所以它不是 "realtime" 但仅在该字段是时触发失去焦点,但这也是可以接受的。
真正的问题是:新的Stock留4个不计算。 现在,当我再次输入 Stock to Pull 字段并将值更改为 5 时,New Stock 字段更新为 2(所以我在 5-3=2 之前输入的值) 再次用 5 覆盖该字段会将新 Stock 更改为 0。 所以总是 "one step behind".
根据我的发现,我有一个想法,我需要某种绑定转换器而不是我的计算方法,但我真的找不到合适的东西,而且我对数据绑定还不够熟悉。已经直接在绑定变量代码中尝试了一些东西,但 none 有效。如果有人能在正确的方向上暗示我,我将非常感激。 (不需要银板解决方案,只需要知道搜索方式(例如,如果我使用的那种绑定完全有意义,或者是否有我必须添加的东西等)。
非常感谢!
PS:当然,如果有人愿意提供镀银解决方案,我将不胜感激。 :) - 抱歉英语不好,没有母语人士。
@nosale 的第二个 link(见评论)提供了问题的答案。 将 XAML 字段 txtEntry 和结果字段设置为 UpdateSourceTrigger=PropertyChanged 解决了这个问题。
所以正确的块现在看起来像这样,没有更改 c# 代码:
<Label Content="Current stock:" HorizontalAlignment="Left" Margin="289,156,0,0" VerticalAlignment="Top"/>
<Label x:Name ="lblOperation" Content="Stock to Pull:" HorizontalAlignment="Left" Margin="507,156,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtEntry" HorizontalAlignment="Left" Height="32" Margin="489,181,0,0" TextWrapping="Wrap" TextAlignment="Center" Text="{Binding Path=ModEntry, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120" FontSize="20" FontWeight="Bold" TextChanged="TxtEntry_TextChanged"/>
<Label Content="New Stock" HorizontalAlignment="Right" Margin="714,156,0,0" VerticalAlignment="Top" Width="68"/>
<TextBlock Text="{Binding Path=NewStock, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Right" Margin="0,186,10,0" TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" Width="68"/>
<TextBox x:Name="txtComment" HorizontalAlignment="Left" Height="86" Margin="289,233,0,0" TextWrapping="Wrap" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="493"/>
<Label Content="Comment:" HorizontalAlignment="Left" Margin="289,207,0,0" VerticalAlignment="Top"/>
原因是,文本框有默认的 UpdateSourceTrigger=LostFocus 而不是 PropertyChanged 以防止用户输入错别字时进行更新。
新学到的东西:WPF 很酷,可以自动处理不合理的值,如 null 或字符串,并将字段标记为红色! :)
再次感谢 links!