首先,对不起我的英语。我正在做的一个项目遇到了问题,但我无法指出问题出在哪里。
如果有人能帮助我或给予我一个建议来解决这个问题,我将非常感激。
我可以创建一个最小复制案例来更好地解释我的问题:
让我们创建一个名为Foo的类:
public class Foo
{
public Foo(byte _id, string _name)
{
ID = _id;
Name = _name;
}
public byte ID { get; set; }
public string Name { get; set; }
public bool Option1 { get; set; }
public bool Option2 { get; set; }
public bool Option3 { get; set; }
}
然后在主窗口中,让我们创建一个名为“Foos”的“ObservableCollection”,并使用一些值填充它:
public partial class MainWindow : Window
{
public ObservableCollection<Foo> Foos { get; set; } = new ObservableCollection<Foo>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
Foos.Add(new Foo(1, "foo 1") { Option1 = true });
Foos.Add(new Foo(2, "foo 2") { Option2 = true });
Foos.Add(new Foo(3, "foo 3") { Option3 = true });
}
}
在UI中,我现在有一个绑定到Foos的数据网格,显示一些元素(Name & ID)。
在右侧,我有一些RadioButton链接到数据网格的“SelectedItem”,显示选项1,2&3:
<StackPanel Orientation="Horizontal">
<DataGrid x:Name="DtgSource" ItemsSource="{Binding Foos}"
AutoGenerateColumns="False"
CanUserAddRows="False"
Width="100"
SelectionChanged="DtgSource_SelectionChanged"
SelectedValuePath="Name">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Name}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Vertical" >
<RadioButton GroupName="Options" IsChecked="{Binding ElementName=DtgSource,
Path=SelectedItem.Option1}" Content="Option 1"/>
<RadioButton GroupName="Options" IsChecked="{Binding ElementName=DtgSource,
Path=SelectedItem.Option2}" Content="Option 2"/>
<RadioButton GroupName="Options" IsChecked="{Binding ElementName=DtgSource,
Path=SelectedItem.Option3}" Content="Option 3"/>
</StackPanel>
</StackPanel>
问题是,有时,当我在数据网格中选择一个对象时,即使我从未单击任何单选按钮,选项也会更改:我有这样的行为:
- 选择Foo 1:单选按钮状态:Op 1:真|Op 2:假|Op 3:假
- 选择Foo 2:单选按钮状态:Op 1:假|Op 2:真|Op 3:假
- 选择Foo 3:单选按钮状态:Op 1:假|Op 2:假|Op 3:真
- 选择Foo 1:单选按钮状态:Op 1:真|Op 2:假|Op 3:假
- 选择Foo 3:单选按钮状态:Op 1:假|Op 2:假|Op 3:否
我是否错过了什么,或者是否有更好的实现来避免这种行为?在我正在做的项目中,除了radiobutton,我还有其他控件,我只在RadioButton上遇到这个问题。
我尝试了其他控件的选择面板,如列表视图和列表框。相同的行为
我还尝试以不同的方式实现Foo,以获得更多的信息来调试,并捕捉选项被修改的时刻:
SelectionChanged方法:
private void DtgSource_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var d = sender as DataGrid;
Foo selitem = d.SelectedItem as Foo;
Console.WriteLine($"SelectionChanged to {d.SelectedValue}| options -> {selitem.Option1}|{selitem.Option2}|{selitem.Option3}");
}
用于调试的选项实现:
public class Foo
{
private bool option1;
public bool Option1 { get => option1; set { if (option1 != value) { option1 = value; Console.WriteLine($"{Name}|Option1 set to {value}"); } } }
//...
如前所述,控制台中的输出如下:
SelectionChanged to foo 1| options -> True|False|False
SelectionChanged to foo 2| options -> False|True|False
SelectionChanged to foo 3| options -> False|False|True
foo 3|Option3 set to False
SelectionChanged to foo 1| options -> True|False|False
SelectionChanged to foo 3| options -> False|False|False
通过在任何选项的'set'上插入一个断点并查看调用堆栈,我看到调用已经由'external code' -> UI完成
/!\在问这个问题之前,我发现:wpf RadioButton Binding Issue这似乎是相关的。但公认的答案是避免使用'GroupName'属性。这是我尝试过的事情,可悲的是,行为完全相同。
正如我所说,任何帮助都将非常感激,以了解这里正在发生的事情:)
1条答案
按热度按时间hec6srdp1#
TL; DR-
RadioButton
组在将IsChecked
双向绑定到单个bool
属性并且绑定源更改时会出现问题。这里的根本问题是,当
SelectedItem
更改时,每个RadioButton
的相应IsChecked
属性一次只更新一个。然而,在分组模式下,RadioButton
被编程为当任何一个按钮变为true时立即将组中的其他按钮更新为false,这当然是有意义的。不幸的副作用是,当您更改选择时,有一段时间按钮并不都指向同一个对象,但WPF仍然将按钮视为一组,因此错误对象的
OptionX
属性被设置为false。以下是发生的步骤:
初始状态-按钮未绑定。在网格中选择Foo1。
Button1.IsChecked = Foo1.Option1 (true)
。其他按钮仍然未绑定,没有进一步操作。Button2.IsChecked = Foo1.Option2 (false)
。没有进一步的操作。Button3.IsChecked = Foo1.Option3 (false)
。没有进一步的操作。在网格中选择Foo2。
Button1.IsChecked = Foo2.Option1 (false)
。没有进一步的操作。Button2.IsChecked = Foo2.Option2 (true)
。然而,这也会导致:Foo2.Option1.IsChecked = Button1.IsChecked = false
和**Foo1.Option3.IsChecked = Button3.IsChecked = false
**。后者不是错别字;这是一个不匹配,因为Button3仍然绑定到Foo1,尽管它是无害的,因为Foo1.Option3已经是false。Button3.IsChecked = Foo2.Option3 (false)
.在网格中选择Foo1。
Button1.IsChecked = Foo1.Option1 (true)
。这也导致:* *Foo2.Option2.IsChecked = Button2.IsChecked = false
和Foo2.Option3.IsChecked = Button3.IsChecked = false
。另一个不匹配。它对Foo2.Option3是无害的,因为它无论如何都是false,但它错误地强制Foo2.Option2设置为false,因为Button2还没有将其绑定源切换回Foo1,因此它错误地输出Foo2**.Option2而不是Foo1.Option2。Button2.IsChecked = Foo1.Option2 (false)
.Button3.IsChecked = Foo1.Option3 (false)
.在网格中选择Foo2。
Button1.IsChecked = Foo2.Option1 (false)
.Button2.IsChecked = Foo2.Option2 (false)
.Button3.IsChecked = Foo2.Option3 (false)
。因此,您可以看到问题是,绑定源每次为每个
RadioButton
更改一个,而不是原子地更改。当任何绑定更改导致RadioButton
被选中时,组中的其他按钮会立即受到影响,即使它们自己的绑定源尚未更新。因此,如果绑定源可能更改,您就不能安全地将IsChecked
与分组的RadioButton
绑定到单个bool
支持属性。这是否可以被称为WPF中的bug,我将留给其他人来发表意见,但至少这种行为是可以解释的。如果你想坚持使用单独的
bool
支持属性,并且不想处理枚举,一个解决方案是取消RadioButton
的分组,并在视图模型端处理独占性,如下所示:(Don不要省略属性更改通知的额外代码,否则当一个按钮更改时,UI不会更新其他按钮)。
在XAML中:
在这里,您需要指定不同的组名来覆盖默认的分组行为。
无枚举解决方法的一个缺点是,它不能很好地扩展,如果您每次都不正确地输出正确的属性,可能会导致细微的错误。
如果你愿意使用枚举,那么下面评论中的Rufo爵士的例子很棒,值得作为自己的答案。另一个很好的选择(实际上是我的首选)是这个问题的公认答案:
How do you work around the binding problems with radio button groups
这两个属性绑定可以工作,而单个
bool
属性绑定不能工作的原因有点微妙,但最终它们都可以防止在RadioButton
由于处于互斥组中而被UI取消选中时更改backing属性。