【问题标题】:WPF RadioButton Change Not Updating UI MVVMWPF RadioButton 更改未更新 UI MVVM
【发布时间】:2012-01-19 06:57:32
【问题描述】:

我有两个使用 MVVM 在 UI 中用作单选按钮列表的单选按钮。第一次加载用户控件时,选择了一个单选按钮,相关控件显示在 UI 中...现在当我更改单选按钮时,UI 没有更新。

以下是示例 XAML:

<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>

下面是我的 ViewModel 代码:

 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;
    }

【问题讨论】:

  • 修复了在项目中使用 Aspect 引用..
  • 如果您自己解决了,请分享您的解决方案。

标签: wpf mvvm


【解决方案1】:

单选按钮、组和绑定不能混用。令人惊讶的是,这是设计使然。

有三种方法可以在 UI 中更改绑定控件的值。一种是用户可以通过鼠标点击或按键自己完成。二是代码可以改变数据源的值,绑定会更新UI中的值。

第三种方法是在代码中显式设置值。如果这样做,您刚刚设置的控件上的绑定将被禁用。

这有点违反直觉。您希望新值被推送到数据源。设计假设是,如果您希望在数据源中更改值,您将在数据源中更改它,并且您的代码正在操纵 UI,因为您不想要它不再被束缚。这为您提供了一种手动覆盖绑定的简单方法 - 只需在代码中设置控件的值 - 这不会强迫您找到 Binding 对象并显式操作它。这有一定的意义。我猜。

但它会给单选按钮带来问题。因为分组的单选按钮会在代码中更改彼此的值。如果您在一个组中有三个单选按钮,并且其中一个被选中,则该单选按钮会找到该组中的其他按钮并取消选中它们。看看Reflector中的代码就知道了。

所以发生的事情正是您所观察到的:您单击单选按钮并禁用绑定。

这就是你要做的事情 - 这实际上很有意义。不要使用组。您可以使用单选按钮,但仅限于它们的视觉风格。忽略它们的分组功能。

相反,实现使绑定的布尔属性在您的视图模型中互斥的逻辑,例如:

public bool Option1
{
   set
   {
      _Option1 = value;
      if (value)
      {
         Option2 = false;
         Option3 = false;
      }
      OnPropertyChanged("Option1");
   }
}

如果你仔细想想,这个逻辑真的不应该出现在视图中。因为它是逻辑,这就是视图模型的用途。因此,虽然这有点痛苦,但你可以安慰自己,从架构上来说这是正确的做法。

【讨论】:

  • 我遇到了完全相同的问题,这解决了它。它应该被标记为答案。我不敢相信这样的事情在没有任何警告的情况下存在,你发现的唯一方法是你的绑定神秘地不起作用!啊!
  • 阅读您的答案后,我所做的只是为每个 RadioButton 创建不同的 GroupName,它就像魅力一样!感谢您的精彩解释。
【解决方案2】:

我猜您缺少视图模型类的 INotifyPropertyChanged 实现。如果您使用了两种方式的数据绑定,并且在选择更改时引发了属性更改事件,那么一切都应该可以正常工作。 @Zamboni 已经用代码示例进行了解释。

【讨论】:

  • 感谢您的回复.. 为 INotifyPropertyChanged 创建了一个方面,并且在加载控件时第一次工作.. 类似的事情在其他控件中也有效..
  • 不,正在发生的事情是绑定被禁用。请参阅我对这个令人难以置信的烦人问题的来之不易的解决方案的回答。
【解决方案3】:

如果您在视图模型中实现 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>

这是视图模型:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
  }

  private bool _isBasicMode = true;
  public bool ExchangeDetailsBasic
  {
    get
    {
      return this._isBasicMode;
    }

    set
    {
      this._isBasicMode = value;
      if (value)
      {
        ExchangeDetailsAdvanced = false;
        IsAdvanced = Visibility.Collapsed;
      }
      this.OnPropertyChanged("ExchangeDetailsBasic");
    }
  }

  private bool _isAdvancedMode = false;
  public bool ExchangeDetailsAdvanced
  {
    get
    {
      return this._isAdvancedMode;
    }

    set
    {
      _isAdvancedMode = value;
      if (value)
      {
        ExchangeDetailsBasic = false;
        IsAdvanced = Visibility.Visible;
      }
      this.OnPropertyChanged("ExchangeDetailsAdvanced");
    }
  }

  private Visibility _isAdvanced = Visibility.Collapsed;
  public Visibility IsAdvanced
  {
    get
    {
      return _isAdvanced;
    }
    set
    {
      _isAdvanced = value;
      this.OnPropertyChanged("IsAdvanced");
    }
  }
}

这是实现 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 创建了一个方面,并且在加载控件时第一次工作.. 类似的事情在其他控件中也有效..
  • 不,这不是导致问题的原因。
【解决方案4】:

Robert Rossney 的回答很好,但我仍然认为单选按钮的行为应该像单选按钮,让 VM 处理更重要的逻辑。

这是我的解决方案:一个附加属性,用于切换同一组中所有按钮的 IsChecked 属性。在我的机器上工作:-)

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>

VM 对每个单选按钮都有一个布尔属性。必须为每个此类属性分配一个值,以启动附加属性的侦听过程。

所有没有组名的按钮都被认为是同一个组的一部分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-20
    • 2014-11-23
    • 2021-08-02
    • 2023-03-08
    • 1970-01-01
    相关资源
    最近更新 更多