MVVM:将单选按钮绑定到视图模型?

Dav*_*man 61 c# wpf binding mvvm radio-button

编辑:问题已在.NET 4.0中修复.

我一直在尝试使用IsChecked按钮将一组单选按钮绑定到视图模型.在审查其他帖子后,该IsChecked属性似乎根本不起作用.我已经整理了一个简短的演示,可以重现这个问题,我在下面列出了这个问题.

这是我的问题:使用MVVM绑定单选按钮是否有直接可靠的方法?谢谢.

附加信息:IsChecked物业不起作用有两个原因:

  1. 选择按钮后,组中其他按钮的IsChecked属性不会设置为false.

  2. 选择按钮后,第一次选择按钮后,其自身的IsChecked属性不会被设置.我猜测绑定在第一次点击时被WPF破坏了.

演示项目:以下是重现问题的简单演示的代码和标记.创建一个WPF项目并使用以下内容替换Window1.xaml中的标记:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" Loaded="Window_Loaded">
    <StackPanel>
        <RadioButton Content="Button A" IsChecked="{Binding Path=ButtonAIsChecked, Mode=TwoWay}" />
        <RadioButton Content="Button B" IsChecked="{Binding Path=ButtonBIsChecked, Mode=TwoWay}" />
    </StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

使用以下代码(hack)替换Window1.xaml.cs中的代码,该代码设置视图模型:

using System.Windows;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = new Window1ViewModel();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在将以下代码添加到项目中Window1ViewModel.cs:

using System.Windows;

namespace WpfApplication1
{
    public class Window1ViewModel
    {
        private bool p_ButtonAIsChecked;

        /// <summary>
        /// Summary
        /// </summary>
        public bool ButtonAIsChecked
        {
            get { return p_ButtonAIsChecked; }
            set
            {
                p_ButtonAIsChecked = value;
                MessageBox.Show(string.Format("Button A is checked: {0}", value));
            }
        }

        private bool p_ButtonBIsChecked;

        /// <summary>
        /// Summary
        /// </summary>
        public bool ButtonBIsChecked
        {
            get { return p_ButtonBIsChecked; }
            set
            {
                p_ButtonBIsChecked = value;
                MessageBox.Show(string.Format("Button B is checked: {0}", value));
            }
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

要重现该问题,请运行该应用程序并单击按钮A.将出现一个消息框,表示按钮A的IsChecked属性已设置为true.现在选择Button B.将出现另一个消息框,表示Button B的IsChecked属性已设置为true,但没有消息框指示Button A的IsChecked属性已设置为false - 该属性尚未更改.

现在再次单击按钮A. 该按钮将在窗口中选中,但不会出现任何消​​息框 - 该IsChecked属性尚未更改.最后,再次点击Button B - 结果相同.IsChecked首次单击按钮后,任何按钮都不会更新该属性.

Joh*_*wen 53

如果你从Jason的建议开始,那么问题就会变成一个单一的绑定选择,从列表中可以很好地转换为a ListBox.在这一点上,将样式应用于ListBox控件以使其显示为RadioButton列表是微不足道的.

<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <RadioButton Content="{TemplateBinding Content}"
                                     IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>
Run Code Online (Sandbox Code Playgroud)


Ran*_*ngy 19

看起来他们修复了绑定到IsChecked.NET 4中的属性.在VS2008中破解的项目在VS2010中工作.

  • 确认!该问题中的演示项目按预期在.NET 4中运行. (2认同)

Dav*_*man 10

对于任何研究这个问题的人来说,这是我最终实施的解决方案.它以John Bowen的答案为基础,我选择这个答案作为问题的最佳解决方案.

首先,我为透明列表框创建了一个样式,其中包含单选按钮作为项目.然后,我创建了按钮进入列表框 - 我的按钮是固定的,而不是作为数据读入应用程序,所以我将它们硬编码到标记中.

我使用ListButtons视图模型中调用的枚举来表示列表框中的按钮,并使用每个按钮的Tag属性来传递用于该按钮的枚举值的字符串值.该ListBox.SelectedValuePath属性允许我将属性指定Tag为所选值的源,我使用该SelectedValue属性绑定到视图模型.我以为我需要一个值转换器来转换字符串和它的枚举值,但WPF的内置转换器处理转换没有问题.

这是Window1.xaml的完整标记:

<Window x:Class="RadioButtonMvvmDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <!-- Resources -->
    <Window.Resources>
        <Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="{x:Type ListBoxItem}" >
                        <Setter Property="Margin" Value="5" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                    <Border BorderThickness="0" Background="Transparent">
                                        <RadioButton 
                                            Focusable="False"
                                            IsHitTestVisible="False"
                                            IsChecked="{TemplateBinding IsSelected}">
                                            <ContentPresenter />
                                        </RadioButton>
                                    </Border>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Setter.Value>
            </Setter>
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBox}">
                        <Border BorderThickness="0" Padding="0" BorderBrush="Transparent" Background="Transparent" Name="Bd" SnapsToDevicePixels="True">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <!-- Layout -->
    <Grid>
        <!-- Note that we use SelectedValue, instead of SelectedItem. This allows us 
        to specify the property to take the value from, using SelectedValuePath. -->

        <ListBox Style="{StaticResource RadioButtonList}" SelectedValuePath="Tag" SelectedValue="{Binding Path=SelectedButton}">
            <ListBoxItem Tag="ButtonA">Button A</ListBoxItem>
            <ListBoxItem Tag="ButtonB">Button B</ListBoxItem>
        </ListBox>
    </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

视图模型有一个属性SelectedButton,它使用ListButtons枚举来显示选择了哪个按钮.该属性调用我用于视图模型的基类中的PropertyChanged事件,这会引发事件:

namespace RadioButtonMvvmDemo
{
    public enum ListButtons {ButtonA, ButtonB}

    public class Window1ViewModel : ViewModelBase
    {
        private ListButtons p_SelectedButton;

        public Window1ViewModel()
        {
            SelectedButton = ListButtons.ButtonB;
        }

        /// <summary>
        /// The button selected by the user.
        /// </summary>
        public ListButtons SelectedButton
        {
            get { return p_SelectedButton; }

            set
            {
                p_SelectedButton = value;
                base.RaisePropertyChangedEvent("SelectedButton");
            }
        }

    }
} 
Run Code Online (Sandbox Code Playgroud)

在我的生产应用程序中,SelectedButtonsetter将调用一个服务类方法,该方法将在选择按钮时执行所需的操作.

要完成,这里是基类:

using System.ComponentModel;

namespace RadioButtonMvvmDemo
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Protected Methods

        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The name of the changed property.</param>
        protected void RaisePropertyChangedEvent(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
                PropertyChanged(this, e);
            }
        }

        #endregion
    }
}
Run Code Online (Sandbox Code Playgroud)

希望有所帮助!