2010-04-14 11 views
0

J'ai un ListBox avec radiobutton sur la ligne horizontale. Le nombre de radiobuttons est facultatif. Le texte de chaque radiobutton est tiré d'une liste du modèle. Le radiobutton sélectionné sera déterminé par la propriété SelectedOption. Si aucun n'est sélectionné, il doit être défini sur -1. Le problème est que je souhaite qu'en plus des choix que le modèle fournit, je veux aussi qu'il y ait un choix "Ne sait pas" qui place SelectedOption à -1. Comment écrire le XAML pour ma ListBox pour obtenir ceci?Ajouter une ligne supplémentaire dans un ListBox avec XAML

Je voudrais aussi "Ne sait pas" pour avoir une autre couleur de fond et une marge.

Modèle:

  • IEnumerable<String> Descriptions - Texte descriptif pour les options disponibles, à l'exception de "Je ne sais pas"
  • Int SelectedOption - Index de la description sélectionnée. -1 Si "Ne sait pas" est sélectionné

Exemple:

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

() est un RadioButton
() Don’t know ont une autre couleur de fond

Répondre

3

Ce fut un projet intéressant qui exigeait un peu de piratage de temps en temps. Mais j'ai réussi la plupart du temps avec l'aide de multi-bindings et quelques convertisseurs de valeur. Cet exemple couvre toutes les fonctionnalités que vous avez demandées et a été encapsulé dans un seul Window pour faciliter la démonstration. Tout d'abord, commençons par le XAML de la fenêtre, où la plupart de la magie se produit:

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

L'astuce ici est que le premier est lié ListBox aux modèles de haut niveau. Chaque modèle ItemTemplate crée un autre ItemsControl intégré, que nous utilisons pour afficher les descriptions des éléments. C'est ainsi que nous pouvons soutenir un nombre dynamique de descriptions (cela fonctionne pour n'importe quel nombre).

Ensuite, nous allons vérifier le code-behind pour cette fenêtre:

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

Tout cela devrait être très clair. La classe LineModel représente le modèle que vous avez décrit dans votre question. Ainsi, il comporte une collection de descriptions de chaînes ainsi qu'une propriété SelectedOption, qui a été transformée en DependencyProperty pour les notifications de changement automatique.

Ensuite, le code pour les deux convertisseurs de valeur:

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

Le convertisseur de correspondance d'index est simple- très compare seulement deux indices et renvoie true ou false. Le convertisseur conteneur vers index est un peu plus complexe et repose sur quelques méthodes ItemContainerGenerator.

Maintenant, le résultat final, 100% lié aux données:

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

Les boutons radio sont générés à la volée et vérifier chaque résultat bouton radio dans la propriété SelectedOption être mis à jour sur votre modèle .

Questions connexes