22

我之前已经将枚举绑定到单选按钮,并且我大致了解它是如何工作的。我使用了这个问题的替代实现:如何将 RadioButtons 绑定到枚举?

我想生成一个自定义类型的运行时枚举集,而不是枚举,并将它们呈现为一组单选按钮。我已经得到了一个视图,它与一个运行时枚举的集合一起工作ListView,绑定到ItemsSourceandSelectedItem属性,所以我ViewModel的连接正确。现在我正在尝试使用单选按钮从 a 切换ListView到 a 。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>

InstanceToBooleanConverterEnumToBooleanConverter具有与其他问题相同的实现。这似乎是正确的,因为它似乎只是调用了该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. 当我尝试(使用上面的代码)时,我收到此错误:

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

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

4

4 回答 4

42

事实证明,放弃 usingItemsControl而是使用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>
于 2011-05-06T01:05:46.667 回答
4

据我所知,MultiBinding虽然您最初认为会有,但没有用 a 来做到这一点的好方法。由于您无法绑定ConverterParameter,因此您的ConvertBack实现没有所需的信息。

我所做的EnumModel只是为了将枚举绑定到单选按钮而创建了一个单独的类。在属性上使用转换器ItemsSource,然后绑定到EnumModel. 这EnumModel只是一个使绑定成为可能的转发器对象。它保存枚举的一个可能值和对视图模型的引用,因此它可以将视图模型上的属性与布尔值相互转换。

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

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

对于我知道有效的代码示例(但它仍然很粗糙 - WIP!),您可以查看http://code.google.com/p/pdx/source/browse/trunk/PDX/PDX/Toolkit/EnumControl。 xml.cs。这仅在我的库的上下文中有效,但它演示了基于 设置 EnumModel 的名称DescriptionAttribute,这可能对您有用。

于 2011-05-05T03:54:47.093 回答
3

你是如此接近。当您需要一个转换器的两个绑定时,您需要 aMultiBinding和 a IMultiValueConverter!语法有点冗长,但并不难。

编辑:

这里有一些代码可以帮助您入门。

绑定:

<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; }
        }
    }
于 2011-05-05T02:22:44.567 回答
1

既然我知道了 x:Shared (感谢您的另一个问题),我放弃了我之前的回答并说 aMultiBinding毕竟是要走的路。

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

    }
}
于 2011-05-05T23:09:52.377 回答