Кнопки радио в MVVM в WPF на управления ItemsControl
я связывал перечисления с переключателями раньше, и я вообще понимаю, как это работает. Я использовал альтернативную реализацию из этого вопроса:как привязать Радиобуттоны к перечислению?
вместо перечислений я хотел бы создать перечисляемый во время выполнения набор пользовательского типа и представить их как набор переключателей. Я получил вид на выполнения-перечисленный набор с ListView
привязка к ItemsSource
и SelectedItem
свойства, поэтому мой ViewModel
подключен правильно. Теперь я пытаюсь переключиться с ListView
до ItemsControl
с переключателями.
вот насколько я понял:
<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>
InstanceToBooleanConverter
имеет ту же реализацию, что и EnumToBooleanConverter
из этого другой вопрос. Это кажется правильным, так как кажется, что он просто вызывает Equals
способ:
public class InstanceToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
проблема, которую я получаю сейчас, заключается в том, что я не могу понять, как отправить значение времени выполнения как ConverterParameter
. Когда я пытаюсь (с кодом выше), я получаю эту ошибку:
"привязка" не может быть установлена для свойства "ConverterParameter" типа "привязка". "Привязка" может быть установлена только на DependencyProperty DependencyObject.
есть ли способ привязаться к экземпляру элемента и передать его в IValueConverter
?
4 ответов
оказывается, гораздо проще отказаться от использования 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>
насколько я знаю, нет хорошего способа сделать это с MultiBinding
, хотя вы изначально думаете, что будет. Так как вы не можете связать ConverterParameter
ваш ConvertBack
реализация не имеет необходимой информации.
то, что я сделал, создано отдельным EnumModel
класс исключительно с целью привязки перечисления к переключателям. Используйте конвертер на ItemsSource
свойство, а затем вы привязываетесь к EnumModel
. The 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>
конвертер:
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();
}
}
EnumModel:
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);
}
}
}
}
для образца кода, который я знаю, работает (но он все еще довольно неполированный-WIP!), вы можете видеть http://code.google.com/p/pdx/source/browse/trunk/PDX/PDX/Toolkit/EnumControl.xaml.cs - ... Это работает только в контексте моей библиотеки, но демонстрирует установку имени EnumModel на основе DescriptionAttribute
, что может быть полезно для вас.
вы так близко. Когда вам нужны две привязки для одного конвертера, вам нужен MultiBinding
и IMultiValueConverter
! Синтаксис немного более подробный, но не более сложный.
Edit:
вот небольшой код, чтобы вы начали.
обязательные:
<RadioButton Content="{Binding Name}"
Grid.Column="0">
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource EqualsConverter}">
<Binding Path="SelectedItem"/>
<Binding Path="Name"/>
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
и конвертер:
public class EqualsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].Equals(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Второй Редактировать:
вышеуказанный подход не полезен для реализации двухсторонней привязки с использованием метода, связанного с вопросом, потому что необходимая информация недоступна при обратном преобразовании.
правильное решение, которое я считаю, является прямым MVVM: код view-model, чтобы соответствовать потребностям представления. Количество кода довольно мало и устраняет необходимость в каких-либо конвертерах или забавных привязках или трюки.
вот XAML;
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton
GroupName="Value"
Content="{Binding Description}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
и code-behind для имитации view-model:
DataContext = new CheckBoxValueCollection(new[] { "Foo", "Bar", "Baz" });
и некоторая инфраструктура view-model:
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; }
}
}
теперь, когда я знаю о x: Shared (благодаря вашему другой вопрос), Я отрекаюсь от своего предыдущего ответа и говорю, что a MultiBinding
- это путь, в конце концов.
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>
viewmodel:
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 };
}
}
}