2010-08-13 12 views
5

Je veux lier une énumération qui possède des attributs flags à une zone de liste avec un modèle d'élément de zone de liste de contrôle dans le modèle mvvm? Comment puis-je faire ceci?Comment lier des drapeaux enums à ListBox dans MVVM

[Flags] 
public enum SportTypes 
{ 
    None = 0, 
    Baseball = 1, 
    Basketball = 2, 
    Football = 4, 
    Handball = 8, 
    Soccer = 16, 
    Volleyball = 32 
} 


<ListBox Name="checkboxList2" 
       ItemsSource="{Binding Sports}" 

       Margin="0,5" 
       SelectionMode="Multiple"> 
      <ListBox.ItemTemplate> 
       <DataTemplate> 
        <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter}}" 
        Content="{Binding Item}"/> 
       </DataTemplate> 

      </ListBox.ItemTemplate> 
+0

@HCL - La solution avec un convertisseur de valeur ne fonctionne pas car ConverterParameter n'est pas lisible. – mkus

Répondre

3

Je vois deux solutions: Une qui est entièrement dynamique, et une qui est statique. La solution dynamique est beaucoup de travail et IMO pas trivial. Celui statique devrait être facile:

Créer un panneau dans votre DataTemplate. Là, placez pour chaque Flag-Value un CheckBox. Ensuite, utilisez le ConverterParameter pour spécifier le drapeau, le convertisseur doit utiliser. Cela ressemblerait à quelque chose comme ceci:

<StackPanel>  
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Baseball}}" Content="Baseball"/> 
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Basketball}}" Content="Basketball"/> 
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Football}}" Content="Football"/> 
    <CheckBox IsChecked="{Binding ..../> 
</StackPanel/> 

Dans votre convertisseur il vous suffit de faire quelques comparaisons et-logiques et vous aurez ce que vous cherchez. Si vous êtes intéressé par la solution dynamique, faites un commentaire, je peux vous donner quelques idées par où commencer. Mais OMI cela ne sera vraiment pas trivial.

Informations Additonal

Si vous voulez avoir une liste au lieu du StackPanel, utilisez un ScrollViewer dans une bordure ou même un ListBox.

+0

Qu'écrivez-vous exactement dans la méthode ConvertBack pour la liaison bidirectionnelle? – TDaver

4

Vous ne pouvez pas lier directement la valeur directement, car le convertisseur ne peut pas générer la combinaison de drapeaux à partir d'un seul drapeau. Vous devez donc gérer une collection de drapeaux et créer la combinaison basée sur cette collection. La difficulté est que la propriété ListBox.SelectedItems est en lecture seule. Cependant, this blog post donne une solution de contournement agréable, en utilisant une propriété jointe.

Voici un exemple complet utilisant cette solution:

code-behind

[Flags] 
public enum MyEnum 
{ 
    Foo = 1, 
    Bar = 2, 
    Baz = 4 
} 

public partial class TestEnum : Window, INotifyPropertyChanged 
{ 
    public TestEnum() 
    { 
     InitializeComponent(); 
     _flags = (MyEnum[])Enum.GetValues(typeof(MyEnum)); 
     _selectedFlags = new ObservableCollection<MyEnum>(); 
     _selectedFlags.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_selectedFlags_CollectionChanged); 
     this.DataContext = this; 
    } 

    void _selectedFlags_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
     if (_selectedFlags.Count == 0) 
      Value = default(MyEnum); 
     else 
      Value = _selectedFlags.Aggregate((v, acc) => acc | v); 
    } 

    private MyEnum[] _flags; 
    public MyEnum[] Flags 
    { 
     get { return _flags; } 
     set 
     { 
      _flags = value; 
      OnPropertyChanged("Flags"); 
     } 
    } 

    private ObservableCollection<MyEnum> _selectedFlags; 
    public ObservableCollection<MyEnum> SelectedFlags 
    { 
     get { return _selectedFlags; } 
     set 
     { 
      _selectedFlags = value; 
      OnPropertyChanged("SelectedFlags"); 
     } 
    } 

    private MyEnum _value; 
    public MyEnum Value 
    { 
     get { return _value; } 
     set 
     { 
      _value = value; 
      OnPropertyChanged("Value"); 
      var currentFlags = _flags.Where(f => _value.HasFlag(f)); 
      var addedFlags = currentFlags.Except(_selectedFlags).ToArray(); 
      var removedFlags = _selectedFlags.Except(currentFlags).ToArray(); 
      foreach (var f in addedFlags) 
      { 
       _selectedFlags.Add(f); 
      } 
      foreach (var f in removedFlags) 
      { 
       _selectedFlags.Remove(f); 
      } 
     } 
    } 

    private DelegateCommand<MyEnum> _setValueCommand; 
    public ICommand SetValueCommand 
    { 
     get 
     { 
      if (_setValueCommand == null) 
      { 
       _setValueCommand = new DelegateCommand<MyEnum>(v => Value = v); 
      } 
      return _setValueCommand; 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

XAML

<Window x:Class="TestPad.TestEnum" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:my="clr-namespace:TestPad" 
     Title="TestEnum" Height="300" Width="300"> 
    <Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto" /> 
      <RowDefinition Height="*" /> 
      <RowDefinition Height="*" /> 
     </Grid.RowDefinitions> 
     <TextBlock Text="{Binding Value}" /> 
     <ListBox Grid.Row="1" 
       ItemsSource="{Binding Flags}" 
       SelectionMode="Extended" 
       my:MultiSelectorBehavior.SynchronizedSelectedItems="{Binding SelectedFlags}"> 
      <ListBox.ItemTemplate> 
       <DataTemplate> 
        <CheckBox Content="{Binding}" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}}" /> 
       </DataTemplate> 
      </ListBox.ItemTemplate> 
     </ListBox> 
     <ItemsControl Grid.Row="2" 
         ItemsSource="{Binding Flags}"> 
      <ItemsControl.ItemTemplate> 
       <DataTemplate> 
        <Button Command="{Binding DataContext.SetValueCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" 
          CommandParameter="{Binding}" 
          Content="{Binding}" /> 
       </DataTemplate> 
      </ItemsControl.ItemTemplate> 
     </ItemsControl> 
    </Grid> 
</Window> 

NOTE: dans un souci de simplicité, le ViewModel est le Classe de fenêtre Dans une vraie application MVVM, vous utiliseriez bien sûr une classe ViewModel distincte ...

0

Pour développer l'article de Chris, voici une explication plus complète de la façon dont vous pouvez le faire.

Ce scénario n'est pas idéal, car la propriété contenant l'énumération doit être un peu plus complexe que d'habitude, mais cela fonctionne.

Code Converter:

public class EnumFlagConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     var theEnum = value as Enum; 
     return theEnum.HasFlag(parameter as Enum); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     var theEnum = parameter as Enum; 
     return theEnum; 
    } 
} 

XAML Convertisseur:

<StackPanel> 
    <StackPanel.Resources> 
     <local:EnumFlagConverter x:Key="MyConverter" /> 
    </StackPanel.Resources> 
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Baseball}}" Content="Baseball"/> 
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Basketball}}" Content="Basketball"/> 
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Football}}" Content="Football"/> 
    <CheckBox IsChecked="{Binding ..../> 
</StackPanel> 

Ensuite, pour se lier à cela, je l'ai fait un peu de ruse pour obtenir le convertisseur fonctionne correctement:

private SportTypeEnum _TheSportType; 
public SportTypeEnum _TheSportType 
{ 
    get { return _TheSportType; } 
    set 
    { 
     if (_TheSportType.HasFlag(value)) 
      _TheSportType &= ~value; 
     else 
      _TheSportType |= value; 
     NotifyPropertyChanged(); 
    } 
} 

En raison de cette logique de réglage spéciale, vous voudrez probablement inclure une méthode comme celle-ci pour vous permettre de définir complètement la valeur du code:

private void ResetTheSportType() 
{ 
    _TheSportType = _TheSportType.None; 
    NotifyPropertyChanged(() => TheSportType); 
}