【问题标题】:Add an extra row in a ListBox with XAML使用 XAML 在 ListBox 中添加额外的行
【发布时间】:2010-04-14 18:23:32
【问题描述】:

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

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

型号:

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

示例:

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

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

【问题讨论】:

    标签: wpf xaml wpf-controls binding


    【解决方案1】:

    这是一个有趣的项目,需要不时进行一些黑客攻击。但我主要在多重绑定和几个值转换器的帮助下管理它。此示例涵盖了您请求的所有功能,并且已封装到单个 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;
        }
    }
    

    索引匹配转换器非常简单——它只是比较两个索引并返回真或假。容器到索引的转换器有点复杂,并且依赖于几个ItemContainerGenerator 方法。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-18
      • 1970-01-01
      • 2014-12-28
      • 1970-01-01
      • 2021-06-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多