使用XAML在ListBox中添加额外的行

时间:2010-04-14 18:23:32

标签: wpf xaml wpf-controls binding

我在水平线上有一个带有radiobutton的ListBox。单选按钮的数量是可选的。每个单选按钮的文本取自模型列表。选择哪个单选按钮将由属性SelectedOption确定。如果没有,则选择它应设置为-1。 问题是我希望除了模型提供的选择之外,我还希望选择“不知道”将SelectedOption放到-1。如何为我的ListBox编写XAML来获取它?

我还希望“不知道”有另一种背景颜色和边距。

型号:

  • IEnumerable<String> Descriptions - 除了“不知道”
  • 之外,可用选项的说明性文字
  • Int SelectedOption - 所选说明的索引。 -1如果选择“不知道”

示例:

---------------------------------------------------------
| () Option1 () Option2 () Option3        () Don’t know |
---------------------------------------------------------

()是一个单选按钮 () Don’t know有另一种背景颜色

1 个答案:

答案 0 :(得分:3)

这是一个有趣的项目,需要不时进行一些黑客攻击。但我主要通过多绑定和几个值转换器来管理它。此示例涵盖您请求的每个功能,并且已封装到单个Window中以便于演示。首先,让我们从窗口的XAML开始,大部分魔法发生在那里:

<Window x:Class="TestWpfApplication.BoundRadioButtonListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:TestWpfApplication"
Title="BoundRadioButtonListBox" Height="200" Width="500"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
    <local:ItemContainerToIndexConverter x:Key="ItemContainerToIndexConverter"/>
    <local:IndexMatchToBoolConverter x:Key="IndexMatchToBoolConverter"/>
</Window.Resources>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <ListBox ItemsSource="{Binding Models}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <ItemsControl x:Name="DescriptionList" ItemsSource="{Binding Descriptions}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <RadioButton Content="{Binding}" Margin="5"
                                             Command="{Binding RelativeSource={RelativeSource FindAncestor,
                                             AncestorType={x:Type ItemsControl}}, Path=DataContext.CheckCommand}"
                                             CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"
                                             GroupName="{Binding RelativeSource={RelativeSource FindAncestor,
                                             AncestorType={x:Type ItemsControl}}, Path=DataContext.GroupName}">
                                    <RadioButton.Tag>
                                        <MultiBinding Converter="{StaticResource ItemContainerToIndexConverter}">
                                            <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}"
                                                     Mode="OneWay"/>
                                            <Binding RelativeSource="{RelativeSource Self}" 
                                                     Path="DataContext"/>
                                        </MultiBinding>
                                    </RadioButton.Tag>
                                    <RadioButton.IsChecked>
                                        <MultiBinding Converter="{StaticResource IndexMatchToBoolConverter}">
                                            <Binding RelativeSource="{RelativeSource Self}" 
                                                     Path="Tag"/>
                                            <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}"
                                                     Path="DataContext.SelectedOption"/>
                                        </MultiBinding>
                                    </RadioButton.IsChecked>
                                </RadioButton>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel Orientation="Horizontal"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>
                    <Border Background="LightGray" Margin="15,5">
                        <RadioButton Content="Don't Know"
                                     Command="{Binding CheckCommand}"
                                     GroupName="{Binding GroupName}">
                            <RadioButton.CommandParameter>
                                <sys:Int32>-1</sys:Int32>
                            </RadioButton.CommandParameter>
                        </RadioButton>
                    </Border>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    <StackPanel Grid.Row="1">
        <Label>The selected index for each line is shown here:</Label>
        <ItemsControl ItemsSource="{Binding Models}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding SelectedOption}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</Grid>

这里的诀窍是第一个ListBox绑定到顶级模型。每个模型ItemTemplate创建另一个嵌入式ItemsControl,我们用它来显示项目描述。这就是我们如何支持动态数量的描述(这适用于任何数字)。

接下来,让我们看看这个窗口的代码隐藏:

/// <summary>
/// Interaction logic for BoundRadioButtonListBox.xaml
/// </summary>
public partial class BoundRadioButtonListBox : Window
{
    public ObservableCollection<LineModel> Models
    {
        get;
        private set;
    }

    public BoundRadioButtonListBox()
    {
        Models = new ObservableCollection<LineModel>();

        List<string> descriptions = new List<string>()
        {
            "Option 1", "Option 2", "Option 3"
        };

        LineModel model = new LineModel(descriptions, 2);
        Models.Add(model);

        descriptions = new List<string>()
        {
            "Option A", "Option B", "Option C", "Option D"
        };

        model = new LineModel(descriptions, 1);
        Models.Add(model);

        InitializeComponent();
    }
}

public class LineModel : DependencyObject
{
    public IEnumerable<String> Descriptions
    {
        get;
        private set;
    }

    public static readonly DependencyProperty SelectedOptionProperty =
        DependencyProperty.Register("SelectedOption", typeof(int), typeof(LineModel));

    public int SelectedOption
    {
        get { return (int)GetValue(SelectedOptionProperty); }
        set { SetValue(SelectedOptionProperty, value); }
    }

    public ICommand CheckCommand
    {
        get;
        private set;
    }

    public string GroupName
    {
        get;
        private set;
    }

    private static int Index = 1;

    public LineModel(IEnumerable<String> descriptions, int selected)
    {
        GroupName = String.Format("Group{0}", Index++);
        Descriptions = descriptions;
        SelectedOption = selected;
        CheckCommand = new RelayCommand((index) => SelectedOption = ((int)index));
    }
}

所有这一切都应该非常明确。 LineModel类代表您在问题中描述的模型。因此,它具有一系列字符串描述以及SelectedOption属性,该属性已成为DependencyProperty以进行自动更改通知。

接下来,两个值转换器的代码:

public class ItemContainerToIndexConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length == 2 &&
            values[0] is ItemsControl &&
            values[1] is string)
        {
            ItemsControl control = values[0] as ItemsControl;
            ContentPresenter item = control.ItemContainerGenerator.ContainerFromItem(values[1]) as ContentPresenter;
            return control.ItemContainerGenerator.IndexFromContainer(item);
        }
        return -1;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return null;
    }
}

public class IndexMatchToBoolConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length == 2 && 
            values[0] is int && 
            values[1] is int)
        {
            return (int)values[0] == (int)values[1];
        }
        return false;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return null;
    }
}

索引匹配转换器非常简单 - 只比较两个索引并返回true或false。索引转换器的容器有点复杂,并且依赖于一些ItemContainerGenerator方法。

现在,完成的结果,100%数据绑定:

alt text http://img210.imageshack.us/img210/2156/boundradiobuttons.png

即时生成单选按钮,检查每个单选按钮会导致模型上的SelectedOption属性更新。