Подтвердить что ты не робот

WPF MVVM Радио кнопки на ItemsControl

Я связал перечисления с переключателями раньше, и я обычно понимаю, как это работает. Я использовал альтернативную реализацию из этого вопроса: Как связать RadioButtons с перечислением?

Вместо перечислений, я хотел бы создать набор времени выполнения, определенный пользователем, и представить их как набор радиокнопок. Я получил представление, работающее против набора с включенным временем выполнения с 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?

4b9b3361

Ответ 1

Оказывается, гораздо проще отказаться от использования 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>

Ответ 2

Насколько я знаю, нет хорошего способа сделать это с помощью MultiBinding, хотя изначально вы думаете, что это будет. Поскольку вы не можете привязать ConverterParameter, ваша реализация ConvertBack не имеет необходимой информации.

То, что я сделал, создано отдельным классом EnumModel исключительно для того, чтобы связать перечислимое число с переключателями. Используйте преобразователь в свойстве 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>

Конвертер:

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, который может быть вам полезен.

Ответ 3

Ты так близко. Если вам нужны две привязки для одного конвертера, вам нужны 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: скопируйте модель представления в соответствии с потребностями представления. Объем кода довольно мал и устраняет необходимость в каких-либо конвертерах или смешных привязках или трюках.

Вот XAML;

<Grid>
    <ItemsControl ItemsSource="{Binding}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <RadioButton
                    GroupName="Value"
                    Content="{Binding Description}"
                    IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

и код-код для моделирования модели представления:

        DataContext = new CheckBoxValueCollection(new[] { "Foo", "Bar", "Baz" });

и некоторой инфраструктуры представлений:

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

Ответ 4

Теперь, когда я знаю об x: Shared (спасибо вашему другому вопросу), я отказываюсь от своего предыдущего ответа и говорю, что 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>

Модель просмотра:

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

    }
}