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>