WPF单选按钮更改未更新UI MVVM
我有两个单选按钮在使用MVVM的UI中用作单选按钮列表。第一次加载用户控件时,将选择其中一个单选按钮,相关控件将显示在UI。。。现在,当我更改单选按钮时,UI不会得到更新 下面是XAML示例:WPF单选按钮更改未更新UI MVVM,wpf,mvvm,Wpf,Mvvm,我有两个单选按钮在使用MVVM的UI中用作单选按钮列表。第一次加载用户控件时,将选择其中一个单选按钮,相关控件将显示在UI。。。现在,当我更改单选按钮时,UI不会得到更新 下面是XAML示例: <Label Grid.Column="0" Grid.Row="3" Content="Exchange Details:" Margin="3" VerticalContentAlignment="Center" Style="{StaticResource NormalLabelStyle}"
<Label Grid.Column="0" Grid.Row="3" Content="Exchange Details:" Margin="3" VerticalContentAlignment="Center" Style="{StaticResource NormalLabelStyle}"></Label>
<Grid Grid.Column="1" Grid.Row="3" Width="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<RadioButton GroupName="rdoExchange" Content="Basic" IsChecked="{Binding Path=ExchangeDetailsBasic}" Grid.Column="0" VerticalContentAlignment="Center" VerticalAlignment="Center"></RadioButton>
<RadioButton GroupName="rdoExchange" Content="Advanced" IsChecked="{Binding Path=ExchangeDetailsAdvanced}" Grid.Column="2" VerticalContentAlignment="Center" VerticalAlignment="Center"></RadioButton
</Grid>
<Label Grid.Column="3" Grid.Row="0" Content="Number of Mailbox Profiles:" VerticalContentAlignment="Center" Style="{StaticResource NormalLabelStyle}" Visibility="{Binding Path=IsAdvanced}" ></Label>
<telerik:RadNumericUpDown Grid.Column="4" Grid.Row="0" Margin="3" Value="{Binding Path=NumberofMailboxProfiles}" IsInteger="True" Minimum="1" Maximum="4" HorizontalAlignment="Left" Visibility="{Binding Path=IsAdvanced}">< /telerik:RadNumericUpDown>
private enum ExchangeDetails{
Basic,
Advanced
}
private bool isBasicMode = true;
public bool ExchangeDetailsBasic {
get {
return this.isBasicMode;
}
set {
if (value) {
this.applicationSpecificRequirements[ExchangeDetailsKey] = ExchangeDetails.Basic.ToString();
if (!this.isBasicMode) {
this.CheckBasicOrAdvancedSelecteAndDisplayView();
}
}
}
}
public bool ExchangeDetailsAdvanced {
get {
return !this.isBasicMode;
}
set {
if (value) {
this.applicationSpecificRequirements[ExchangeDetailsKey] = ExchangeDetails.Advanced.ToString();
this.CheckBasicOrAdvancedSelecteAndDisplayView();
}
}
}
public Visibility IsAdvanced { get; private set; }
private void CheckBasicOrAdvancedSelecteAndDisplayView() {
this.isBasicMode = this.applicationSpecificRequirements.ContainsKey(ExchangeDetailsKey) ? (this.applicationSpecificRequirements[ExchangeDetailsKey].Equals(ExchangeDetails.Basic.ToString()) ? true : false) : true;
this.IsAdvanced = this.isBasicMode ? Visibility.Collapsed : Visibility.Visible;
}
如果在视图模型中实现INotifyPropertyChanged,并在XAML中设置Binding Mode=TwoWay,则可以让绑定为您处理其余部分 下面是使用您的一些代码的示例:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<RadioButton GroupName="rdoExchange" Content="Basic"
IsChecked="{Binding Path=ExchangeDetailsBasic, Mode=TwoWay}"
Grid.Column="0"
VerticalContentAlignment="Center"
VerticalAlignment="Center"/>
<RadioButton GroupName="rdoExchange" Content="Advanced"
IsChecked="{Binding Path=ExchangeDetailsAdvanced, Mode=TwoWay}"
Grid.Column="1"
VerticalContentAlignment="Center"
VerticalAlignment="Center"/>
<Label Grid.Column="0" Grid.Row="1" Grid.RowSpan="2"
Content="Number of Mailbox Profiles:"
VerticalContentAlignment="Center"
Visibility="{Binding Path=IsAdvanced, Mode=TwoWay}" />
</Grid>
下面是实现INotifyPropertyChanged的基类
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
我猜您缺少视图模型类的INotifyPropertyChanged的实现。如果您使用了双向数据绑定,并且在选择更改时引发property changed事件,那么一切都会正常工作@Zamboni用代码示例解释了这一点。单选按钮、组和绑定不能混合使用。令人惊讶的是,这是设计的 有三种方法可以更改UI中绑定控件的值。一个是用户可以自己用鼠标点击或按键来完成。第二,代码可以更改数据源的值,绑定将更新UI中的值 第三种方法是在代码中显式设置值。如果执行此操作,则刚刚设置的控件上的绑定将被禁用 这有点违反直觉。您希望新值被推送到数据源。设计假设是,如果您希望在数据源中更改值,那么您应该在数据源中更改它,并且您的代码正在操纵UI,因为您不希望再绑定它。这为您提供了一种手动重写绑定的简单方法—只需在代码中设置控件的值—这不会迫使您查找
绑定
对象并显式地操作它。这是有一定道理的。我想是吧
但它会给单选按钮带来问题。因为分组的单选按钮会在代码中更改彼此的值。如果一个组中有三个单选按钮,其中一个被选中,则单选按钮将查找组中的其他按钮并取消选中它们。如果查看Reflector中的代码,可以看到这一点
所以发生的事情正是您所观察到的:您单击单选按钮,绑定被禁用
下面是你对此所做的——这实际上是相当有意义的。不要使用组。可以使用单选按钮,但仅限于其视觉样式。忽略它们的分组功能
相反,实现使绑定布尔属性在视图模型中相互排斥的逻辑,例如:
public bool Option1
{
set
{
_Option1 = value;
if (value)
{
Option2 = false;
Option3 = false;
}
OnPropertyChanged("Option1");
}
}
如果你仔细想想,这种逻辑无论如何都不应该出现在你的视野中。因为这是逻辑,这就是视图模型的用途。因此,虽然这有点痛苦,但你可以用这样的想法来安慰自己:从架构上来说,这是正确的做法。Robert Rossney的答案很好,但我仍然认为单选按钮应该像单选按钮一样,让VM处理更重要的逻辑 这是我的解决方案:一个附加属性,用于切换同一组中所有按钮的IsChecked属性。在我的机器上工作:-)
使用系统;
使用System.Collections.Generic;
使用System.Windows;
使用System.Windows.Controls;
命名空间Elca.MvvmHelpers{
公共类RadioButtonHelper:DependencyObject{
私有静态只读字典s_group2ButtonsMap=new Dictionary();
私有静态只读列表s_knownButtons=新列表();
RadioButtonched上的私有静态无效(对象发送器,路由目标e){
RadioButton rb=(RadioButton)发送器;
取消选中其他按钮组(rb);
}
公共静态布尔?已检查(单选按钮d){
返回(bool?)d.GetValue(IsCheckedProperty);
}
公共静态无效设置已检查(单选按钮d,布尔值){
d、 设置值(IsCheckedProperty,value);
}
公共静态只读从属属性是检查属性=
DependencyProperty.RegisterAttached(“已检查”),
类型(bool?),
类型(RadioButtonHelper),
新建FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.Journal|
默认情况下,FrameworkPropertyMetadataOptions.BindsTwoWay,
IsCheckedChanged);
公共静态无效已检查更改(DependencyObject d、DependencyPropertyChangedEventArgs e){
var rb=d作为单选按钮;
if(rb==null){
抛出新异常(“IsChecked attached属性仅适用于FrameworkElement类型”);
}
记忆按钮(rb);
if((bool)e.NewValue){
rb.IsChecked=true;//这将触发OnRadioButtonChecked=>同一组中的其他按钮将被取消选中
}
}
专用静态无效记忆按钮(RadioButton rb){
var groupName=GetGroupName(rb);
//如果此按钮未知,请根据其组名将其添加到右侧列表中
if(s_knownButtons.Contains(rb)){
返回;
}
s_knownButtons.Add(rb);
列出现有按钮;
如果(!s_group2ButtonsMap.TryGetValue(groupName,out existingButtons)){
//未知组
s_group2ButtonsMap[groupName]=新列表{rb};
注册按钮事件(rb);
}否则{
如果(!existingButtons.Contains(rb)){
现有按钮。添加(rb);
注册按钮事件(rb);
public bool Option1
{
set
{
_Option1 = value;
if (value)
{
Option2 = false;
Option3 = false;
}
OnPropertyChanged("Option1");
}
}
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace Elca.MvvmHelpers {
public class RadioButtonHelper : DependencyObject {
private static readonly Dictionary<string, List<RadioButton>> s_group2ButtonsMap = new Dictionary<string, List<RadioButton>>();
private static readonly List<RadioButton> s_knownButtons = new List<RadioButton>();
private static void OnRadioButtonChecked(object sender, RoutedEventArgs e) {
RadioButton rb = (RadioButton)sender;
UncheckOtherButtonsInGroup(rb);
}
public static bool? GetIsChecked(RadioButton d) {
return (bool?) d.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(RadioButton d, bool? value) {
d.SetValue(IsCheckedProperty, value);
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked",
typeof(bool?),
typeof(RadioButtonHelper),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCheckedChanged));
public static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var rb = d as RadioButton;
if (rb == null) {
throw new Exception("IsChecked attached property only works on a FrameworkElement type");
}
RememberRadioButton(rb);
if ((bool) e.NewValue) {
rb.IsChecked = true; // this triggers OnRadioButtonChecked => other buttons in the same group will be unchecked
}
}
private static void RememberRadioButton(RadioButton rb) {
var groupName = GetGroupName(rb);
// if this button is unknown, add it to the right list, based on its group name
if (s_knownButtons.Contains(rb)) {
return;
}
s_knownButtons.Add(rb);
List<RadioButton> existingButtons;
if (! s_group2ButtonsMap.TryGetValue(groupName, out existingButtons)) {
// unknown group
s_group2ButtonsMap[groupName] = new List<RadioButton> {rb};
RegisterButtonEvents(rb);
} else {
if (! existingButtons.Contains(rb)) {
existingButtons.Add(rb);
RegisterButtonEvents(rb);
}
}
}
private static void RegisterButtonEvents(RadioButton rb) {
rb.Unloaded += OnButtonUnloaded;
rb.Checked += OnRadioButtonChecked;
}
private static void OnButtonUnloaded(object sender, RoutedEventArgs e) {
RadioButton rb = (RadioButton) sender;
ForgetRadioButton(rb);
}
private static void ForgetRadioButton(RadioButton rb) {
List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];
existingButtons.Remove(rb);
s_knownButtons.Remove(rb);
UnregisterButtonEvents(rb);
}
private static void UnregisterButtonEvents(RadioButton rb) {
rb.Unloaded -= OnButtonUnloaded;
rb.Checked -= OnRadioButtonChecked;
}
private static void UncheckOtherButtonsInGroup(RadioButton rb) {
List<RadioButton> existingButtons = s_group2ButtonsMap[GetGroupName(rb)];
foreach (RadioButton other in existingButtons) {
if (other != rb) {
SetIsChecked(other, false);
}
}
SetIsChecked(rb, true);
}
private static string GetGroupName(RadioButton elt) {
string groupName = elt.GroupName;
if (String.IsNullOrEmpty(groupName)) {
groupName = "none"; // any value will do
}
return groupName;
}
}
}
<RadioButton MvvmHelpers:RadioButtonHelper.IsChecked="{Binding IsExplicitFileSelected, Mode=TwoWay}">
...
</RadioButton>