ItemsControl上的WPF MVVM单选按钮

ItemsControl上的WPF MVVM单选按钮,wpf,mvvm,radio-button,ivalueconverter,Wpf,Mvvm,Radio Button,Ivalueconverter,我以前曾将枚举绑定到单选按钮,我基本上了解它的工作原理。我使用了这个问题的替代实现: 我想生成一个自定义类型的运行时枚举集,并将其作为一组单选按钮来表示,而不是枚举。我得到了一个视图,该视图针对一个运行时枚举集,该集具有一个绑定到ItemsSource和SelectedItem属性的ListView,因此我的ViewModel已正确连接。现在我正试图用单选按钮从列表视图切换到项目控件 以下是我所了解到的情况: <Window.Resources> <vm:Instanc

我以前曾将枚举绑定到单选按钮,我基本上了解它的工作原理。我使用了这个问题的替代实现:

我想生成一个自定义类型的运行时枚举集,并将其作为一组单选按钮来表示,而不是枚举。我得到了一个视图,该视图针对一个运行时枚举集,该集具有一个绑定到
ItemsSource
SelectedItem
属性的
ListView
,因此我的
ViewModel
已正确连接。现在我正试图用单选按钮从
列表视图切换到
项目控件

以下是我所了解到的情况:

<Window.Resources>
    <vm:InstanceToBooleanConverter x:Key="InstanceToBooleanConverter" />
</Window.Resources>

<!-- ... -->

<ItemsControl ItemsSource="{Binding ItemSelections}">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type vm:ISomeType}">
            <RadioButton Content="{Binding Name}"
                         IsChecked="{Binding Path=SelectedItem, Converter={StaticResource InstanceToBooleanConverter}, ConverterParameter={Binding}}"
                         Grid.Column="0" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
我现在遇到的问题是,我不知道如何将运行时值作为
ConverterParameter
发送。当我尝试(使用上面的代码)时,会出现以下错误:

无法在“Binding”类型的“ConverterParameter”属性上设置“Binding”。只能对DependencyObject的DependencyProperty设置“绑定”


有没有办法绑定到item实例,并将其传递给
IValueConverter

您已经很接近了。当一个转换器需要两个绑定时,您需要一个
MultiBinding
和一个
IMultiValueConverter
!语法有点冗长,但也不难理解

编辑:

这里有一个小代码让你开始

约束力:

<RadioButton Content="{Binding Name}"
        Grid.Column="0">
    <RadioButton.IsChecked>
        <MultiBinding Converter="{StaticResource EqualsConverter}">
            <Binding Path="SelectedItem"/>
            <Binding Path="Name"/>
        </MultiBinding>
    </RadioButton.IsChecked>
</RadioButton>
第二次编辑:

上述方法对于使用问题中链接的技术实现双向绑定没有用处,因为在转换回问题时,没有必要的信息可用

我认为正确的解决方案是直接的MVVM:对视图模型进行编码以匹配视图的需要。代码量非常小,不需要任何转换器或有趣的绑定或技巧

这里是XAML

<Grid>
    <ItemsControl ItemsSource="{Binding}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <RadioButton
                    GroupName="Value"
                    Content="{Binding Description}"
                    IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>
以及一些视图模型基础结构:

    public class CheckBoxValue : INotifyPropertyChanged
    {
        private string description;
        private bool isChecked;

        public string Description
        {
            get { return description; }
            set { description = value; OnPropertyChanged("Description"); }
        }
        public bool IsChecked
        {
            get { return isChecked; }
            set { isChecked = value; OnPropertyChanged("IsChecked"); }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class CheckBoxValueCollection : ObservableCollection<CheckBoxValue>
    {
        public CheckBoxValueCollection(IEnumerable<string> values)
        {
            foreach (var value in values)
                this.Add(new CheckBoxValue { Description = value });
            this[0].IsChecked = true;
        }

        public string SelectedItem
        {
            get { return this.First(item => item.IsChecked).Description; }
        }
    }
public类CheckBoxValue:INotifyPropertyChanged
{
私有字符串描述;
私人住宅被检查;
公共字符串描述
{
获取{返回说明;}
设置{description=value;OnPropertyChanged(“description”);}
}
公共场所被检查
{
获取{return isChecked;}
设置{isChecked=value;OnPropertyChanged(“isChecked”);}
}
公共事件属性更改事件处理程序属性更改;
受保护的无效OnPropertyChanged(字符串propertyName)
{
如果(PropertyChanged!=null)PropertyChanged(这是新的PropertyChangedEventArgs(propertyName));
}
}
公共类CheckBoxValueCollection:ObservableCollection
{
public CheckBoxValueCollection(IEnumerable值)
{
foreach(值中的var值)
Add(新的CheckBoxValue{Description=value});
此[0]。IsChecked=true;
}
公共字符串SelectedItem
{
获取{返回this.First(item=>item.IsChecked).Description;}
}
}

据我所知,使用
多绑定来实现这一点没有好方法,尽管您最初认为会有。由于无法绑定
ConverterParameter
,因此
ConvertBack
实现没有所需的信息

我所做的是创建一个单独的
EnumModel
类,仅用于将enum绑定到单选按钮。在
ItemsSource
属性上使用转换器,然后绑定到
EnumModel
EnumModel
只是一个使绑定成为可能的转发器对象。它保存枚举的一个可能值和对viewmodel的引用,因此可以将viewmodel上的属性转换为布尔值或从布尔值转换为布尔值

这是一个未经测试但通用的版本:

<ItemsControl ItemsSource="{Binding Converter={StaticResource theConverter} ConverterParameter="SomeEnumProperty"}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <RadioButton IsChecked="{Binding IsChecked}">
                <TextBlock Text="{Binding Name}" />
            </RadioButton>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

对于一个我知道有效的代码示例(但它仍然非常未完成-WIP!),您可以看到。这仅在我的库的上下文中有效,但它演示了基于
描述属性设置EnumModel的名称,这可能对您有用。

现在我了解了x:Shared(感谢您的支持),我放弃了我以前的回答,并说
多绑定毕竟是一种方法

XAML:

<StackPanel>
    <TextBlock Text="{Binding SelectedChoice}" />

    <ItemsControl ItemsSource="{Binding Choices}">
        <ItemsControl.Resources>
            <local:MyConverter x:Key="myConverter" x:Shared="false" />
        </ItemsControl.Resources>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <RadioButton>
                    <RadioButton.IsChecked>
                        <MultiBinding Converter="{StaticResource myConverter}" >
                            <Binding Path="DataContext.SelectedChoice" RelativeSource="{RelativeSource AncestorType=UserControl}" />
                            <Binding Path="DataContext" RelativeSource="{RelativeSource Mode=Self}" />
                        </MultiBinding>
                    </RadioButton.IsChecked>
                    <TextBlock Text="{Binding}" />
                </RadioButton>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</StackPanel>

事实证明,放弃使用
ItemsControl
而改为使用
ListBox
要简单得多

它的重量可能更重,但这主要是因为它为你做了繁重的工作。在
RadioButton.IsChecked
ListBoxItem.IsSelected
之间进行双向绑定非常容易。有了适用于
ListBoxItem
的适当控件模板,您可以轻松摆脱所有视觉选择

<ListBox ItemsSource="{Binding Properties}" SelectedItem="{Binding SelectedItem}">
    <ListBox.ItemContainerStyle>
        <!-- Style to get rid of the selection visual -->
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <ContentPresenter />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemTemplate>
        <DataTemplate DataType="{x:Type local:SomeClass}">
            <RadioButton Content="{Binding Name}" GroupName="Properties">
                <!-- Binding IsChecked to IsSelected requires no support code -->
                <RadioButton.IsChecked>
                    <Binding Path="IsSelected"
                             RelativeSource="{RelativeSource AncestorType=ListBoxItem}"
                             Mode="TwoWay" />
                </RadioButton.IsChecked>
            </RadioButton>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>


自从我发布这个问题以来,我一直在玩这个游戏<代码>多值转换器
似乎从一个值转换为两个值,反之亦然。如果我从
{theSelectedInstance,thisInstance}
转换为布尔值,这很容易。棘手的部分是如何将布尔值转换为实例。当我将
IsChecked
设置为true或手动检查它时,如何让实例设置
SelectedItem
?我想我最终还是必须绑定到转换器参数才能正常工作……这种方法注定要与动态枚举进行双向绑定,因为转换器参数不能使用数据绑定。您必须切换到
SelectedIndex
方法,然后单选按钮可以使用整数。这是一个不错的选择。我打开了另一个盒子
<ItemsControl ItemsSource="{Binding Converter={StaticResource theConverter} ConverterParameter="SomeEnumProperty"}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <RadioButton IsChecked="{Binding IsChecked}">
                <TextBlock Text="{Binding Name}" />
            </RadioButton>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
public class ToEnumModelsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var viewmodel = value;
        var prop = viewmodel.GetType().GetProperty(parameter as string);

        List<EnumModel> enumModels = new List<EnumModel>();

        foreach(var enumValue in Enum.GetValues(prop.PropertyType))
        {
            var enumModel = new EnumModel(enumValue, viewmodel, prop);
            enumModels.Add(enumModel);
        }

        return enumModels;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
public class EnumModel : INPC
{
    object enumValue;
    INotifyPropertyChanged viewmodel;
    PropertyInfo property;

    public EnumModel(object enumValue, object viewmodel, PropertyInfo property)
    {
        this.enumValue = enumValue;
        this.viewmodel = viewmodel as INotifyPropertyChanged;
        this.property = property;

        this.viewmodel.PropertyChanged += new PropertyChangedEventHandler(viewmodel_PropertyChanged);
    }

    void viewmodel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == property.Name)
        {
            OnPropertyChanged("IsChecked");
        }
    }

    public bool IsChecked
    {
        get
        {
            return property.GetValue(viewmodel, null).Equals(enumValue);
        }
        set
        {
            if (value)
            {
                property.SetValue(viewmodel, enumValue, null);
            }
        }
    }
}
<StackPanel>
    <TextBlock Text="{Binding SelectedChoice}" />

    <ItemsControl ItemsSource="{Binding Choices}">
        <ItemsControl.Resources>
            <local:MyConverter x:Key="myConverter" x:Shared="false" />
        </ItemsControl.Resources>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <RadioButton>
                    <RadioButton.IsChecked>
                        <MultiBinding Converter="{StaticResource myConverter}" >
                            <Binding Path="DataContext.SelectedChoice" RelativeSource="{RelativeSource AncestorType=UserControl}" />
                            <Binding Path="DataContext" RelativeSource="{RelativeSource Mode=Self}" />
                        </MultiBinding>
                    </RadioButton.IsChecked>
                    <TextBlock Text="{Binding}" />
                </RadioButton>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</StackPanel>
class Viewmodel : INPC
{
    public Viewmodel()
    {
        Choices = new List<string>() { "one", "two", "three" };
        SelectedChoice = Choices[0];
    }

    public List<string> Choices { get; set; }

    string selectedChoice;
    public string SelectedChoice
    {
        get { return selectedChoice; }
        set
        {
            if (selectedChoice != value)
            {
                selectedChoice = value;
                OnPropertyChanged("SelectedChoice");
            }
        }
    }
}
public class MyConverter : IMultiValueConverter
{
    object selectedValue;
    object myValue;

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        selectedValue = values[0];
        myValue = values[1];

        return selectedValue == myValue;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        if ((bool)value)
        {
            return new object[] { myValue, Binding.DoNothing };
        }
        else
        {
            return new object[] { Binding.DoNothing, Binding.DoNothing };
        }

    }
}
<ListBox ItemsSource="{Binding Properties}" SelectedItem="{Binding SelectedItem}">
    <ListBox.ItemContainerStyle>
        <!-- Style to get rid of the selection visual -->
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <ContentPresenter />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
    <ListBox.ItemTemplate>
        <DataTemplate DataType="{x:Type local:SomeClass}">
            <RadioButton Content="{Binding Name}" GroupName="Properties">
                <!-- Binding IsChecked to IsSelected requires no support code -->
                <RadioButton.IsChecked>
                    <Binding Path="IsSelected"
                             RelativeSource="{RelativeSource AncestorType=ListBoxItem}"
                             Mode="TwoWay" />
                </RadioButton.IsChecked>
            </RadioButton>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>