Boutons Radio WPF MVVM sur ItemsControl

j'ai déjà relié des énums à des boutons radio, et je comprends généralement comment ça marche. J'ai utilisé l'implémentation alternative de cette question: comment lier les RadioButtons à un enum?

au lieu des énumérations, j'aimerais générer un ensemble d'exécution-énuméré d'un type personnalisé et les présenter comme un ensemble de boutons radio. J'ai obtenu une vue travaillant contre un ensemble d'exécution-énuméré avec un ListView , liant à la ItemsSource et SelectedItem , donc mon ViewModel est branché correctement. Maintenant j'essaie de passer d'un ListView à un ItemsControl avec des boutons radio.

C'est aussi loin que j'ai obtenu:

<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 a la même application que EnumToBooleanConverter pour cette autre question. Cela semble juste, car il semble comme il invoque juste la Equals méthode:

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

le problème je suis Je n'arrive pas à trouver comment envoyer une valeur d'exécution comme ConverterParameter . Lorsque j'essaie (avec le code ci-dessus), j'obtiens cette erreur:

Une "Liaison" ne peut pas être réglé sur "ConverterParameter propriété de type "Contraignant". Un "Binding" ne peut être défini que sur une propriété Dependenced'un objet Dependencyob.

y a-t-il un moyen de lier l'instance de l'élément, et de la Passer au IValueConverter ?

21
demandé sur Community 2011-05-05 06:08:43

4 réponses

il s'avère qu'il est beaucoup plus simple d'abandonner en utilisant ItemsControl et à la place aller avec ListBox .

il peut être plus lourd, mais c'est surtout parce qu'il fait le gros du travail pour vous. Il est vraiment facile de faire une liaison bidirectionnelle entre RadioButton.IsChecked et ListBoxItem.IsSelected . Avec le modèle de contrôle approprié pour le ListBoxItem , vous pouvez facilement vous débarrasser de toute la sélection visuelle.

<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>
34
répondu Merlyn Morgan-Graham 2017-03-28 07:06:44

autant que je sache, il n'y a pas de bonne façon de le faire avec un MultiBinding , bien que vous pensiez d'abord qu'il y en aurait. Puisque vous ne pouvez pas lier le ConverterParameter , votre implémentation du ConvertBack n'a pas l'information dont elle a besoin.

ce que j'ai fait est créé une classe séparée EnumModel uniquement dans le but de lier un enum aux boutons radio. Utilisez un convertisseur sur la propriété ItemsSource et ensuite vous êtes lié à un EnumModel . Le EnumModel est juste un objet forwarder pour rendre la liaison possible. Il est titulaire d'une possible valeur de l'enum et une référence à ce dernier de sorte qu'il peut traduire une propriété du viewmodel et d'un booléen.

Voici une version non testée mais générique:

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

le convertisseur:

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

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

pour un exemple de code que je connais fonctionne (mais il est encore tout à fait mat - WIP!), vous pouvez voir http://code.google.com/p/pdx/source/browse/trunk/PDX/PDX/Toolkit/EnumControl.xaml.cs . Cela ne fonctionne que dans le contexte de ma bibliothèque, mais cela montre comment définir le nom du modèle D'énumération basé sur le DescriptionAttribute , qui pourrait vous être utile.

4
répondu default.kramer 2011-05-05 04:03:00

Vous êtes si proche. Lorsque vous avez besoin de deux fixations pour un convertisseur, vous avez besoin d'un MultiBinding et d'un IMultiValueConverter ! La syntaxe est un peu plus détaillé, mais pas plus difficile.

Edit:

voici un petit code pour vous commencer.

La reliure:

<RadioButton Content="{Binding Name}"
        Grid.Column="0">
    <RadioButton.IsChecked>
        <MultiBinding Converter="{StaticResource EqualsConverter}">
            <Binding Path="SelectedItem"/>
            <Binding Path="Name"/>
        </MultiBinding>
    </RadioButton.IsChecked>
</RadioButton>

et le convertisseur:

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

Deuxième Édition:

l'approche ci-dessus n'est pas utile pour mettre en oeuvre la liaison bidirectionnelle en utilisant la technique liée à la question parce que l'information nécessaire n'est pas disponible lors de la conversion en arrière.

la bonne solution que je crois est simple MVVM: code la vue-Modèle pour répondre aux besoins de la vue. La quantité de code est assez petite et évite d'avoir à utiliser des convertisseurs ou des fixations drôles ou des astuces.

voici le XAML;

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

et le code-behind pour simuler la vue-modèle:

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

et certains view-model infrastructure:

    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; }
        }
    }
2
répondu Rick Sladkey 2011-05-05 04:45:03

maintenant que je connais x: Shared (merci à votre autre question ), je renonce à ma réponse précédente et je dis qu'un MultiBinding est la voie à suivre après tout.

The 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>

Le 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");
            }
        }
    }
}

le convertisseur:

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

    }
}
1
répondu default.kramer 2017-05-23 12:17:26