web-dev-qa-db-fra.com

WPF - MVVM - ComboBox SelectedItem

J'ai ViewModel (implémenté INotifyPropertyChanged) en arrière-plan et la classe Category qui n'a qu'une seule propriété de type string. My ComboBox SelectedItem est lié à une instance d'une catégorie. Lorsque je modifie la valeur de l'instance, SelectedItem n'est pas mis à jour et Combobox n'est pas modifié.

EDIT: code

Boîte combo:

<ComboBox x:Name="categoryComboBox" Grid.Column="1"  Grid.Row="3" Grid.ColumnSpan="2" 
          Margin="10" ItemsSource="{Binding Categories}"
          DisplayMemberPath="Name" SelectedValue="{Binding NodeCategory, Mode=TwoWay}"/>

Propriété:

private Category _NodeCategory;
public Category NodeCategory
{
    get
    {
        return _NodeCategory;
    }
    set
    {
        _NodeCategory = value;
        OnPropertyChanged("NodeCategory");
    }
}

[Serializable]
public class Category : INotifyPropertyChanged
{
    private string _Name;
    [XmlAttribute("Name")]
    public string Name
    {
        get
        {
            return _Name;
        }
        set
        {
            _Name = value;
            OnPropertyChanged("Name");
        }
    }

    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    [field:NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;
}

et ce que j'essaye c'est: quand je mets

NodeCategory = some_list_of_other_objects.Category;

pour que cet élément soit sélectionné dans Combobox avec le DisplayMemberPath approprié

17
Bip

La catégorie que vous définissez dans cette ligne -

NodeCategory = some_list_of_other_objects.Category;

et un présent dans votre collection Catégories (ItemsSource="{Binding Categories}") doit faire référence au même objet. Si ce n'est pas le cas, SelectedItem ne fonctionnera pas.

Solution 1 -

Vous pouvez également essayer d'utiliser SelectedValuePath comme ceci -

<ComboBox x:Name="categoryComboBox" 
          ItemsSource="{Binding Categories}"
          DisplayMemberPath="Name" 
          SelectedValuePath="Name" 
          SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />

et dans le code, vous pouvez faire quelque chose comme ça -

private string _NodeCategory;
public string NodeCategory
{
    get
    {
        return _NodeCategory;
    }
    set
    {
        _NodeCategory = value;
        OnPropertyChanged("NodeCategory");
    }
}

et définir l'élément sélectionné comme ceci -

NodeCategory = some_list_of_other_objects.Category.Name;

et utiliser la valeur sélectionnée comme ceci -

Category selectedCategory = 
   some_list_of_other_objects.FirstOrDefault(cat=> cat.Name == NodeCategory);

ou

Category selectedCategory = 
   Categories.FirstOrDefault(cat=> cat.Name == NodeCategory);

Solution 2 -

Une autre solution possible peut être -

NodeCategory = 
  Categories.FirstOrDefault(cat=> cat.Name == some_list_of_other_objects.Category.Name);

de cette façon, votre propriété NodeCategory aura la référence d'un objet dans la collection Categories et SelectedItem fonctionnera.

20
akjoshi

Votre XAML a besoin de quelques modifications, mais je pense que le vrai problème est avec le code que vous avez publié et qui, je pense, ne raconte pas toute l'histoire. Pour commencer, votre zone de liste déroulante ItemSource est liée à une propriété appelée Catégories, mais vous n'indiquez pas comment cette propriété est codée ni comment votre propriété NodeCategory est initialement synchronisée avec l'élément.

Essayez d'utiliser le code suivant et vous verrez que l'élément sélectionné est synchronisé lorsque l'utilisateur modifie la valeur dans la zone de liste déroulante.

XAML

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <ComboBox x:Name="categoryComboBox"
              Grid.Column="1"
              Grid.Row="3"
              Grid.ColumnSpan="2"
              Margin="10"
              ItemsSource="{Binding Categories}"
              DisplayMemberPath="Name"
              SelectedItem="{Binding NodeCategory}" />
    <Label Content="{Binding NodeCategory.Name}" />
</StackPanel>

Code-behind

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private ObservableCollection<Category> _categories = new ObservableCollection<Category>
    {
        new Category { Name = "Squares"},
        new Category { Name = "Triangles"},
        new Category { Name = "Circles"},
    };

    public MainWindow()
    {
        InitializeComponent();
        NodeCategory = _categories.First();
        this.DataContext = this;
    }

    public IEnumerable<Category> Categories
    {
        get { return _categories; }
    }

    private Category _NodeCategory;
    public Category NodeCategory
    {
        get
        {
            return _NodeCategory;
        }
        set
        {
            _NodeCategory = value;
            OnPropertyChanged("NodeCategory");
        }
    }

    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

[Serializable]
public class Category : INotifyPropertyChanged
{
    private string _Name;
    [XmlAttribute("Name")]
    public string Name
    {
        get
        {
            return _Name;
        }
        set
        {
            _Name = value;
            OnPropertyChanged("Name");
        }
    }

    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    [field: NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;
}
5
Benjamin Gale

De mon petit exemple:

Remarque: cela ne définit qu'une chaîne (ou une catégorie d'une autre liste), mais les bases doivent être les mêmes ici:

Fondamentalement, cela se fait:

private void button1_Click(object sender, RoutedEventArgs e)
{
    (this.DataContext as ComboBoxSampleViewModel).SelectCategory("Categorie 4");
}

Voici mon XAML:

<Grid>
    <ComboBox Height="23" HorizontalAlignment="Left" Margin="76,59,0,0"   
              Name="comboBox1" VerticalAlignment="Top" Width="120" 
              ItemsSource="{Binding List.Categories}" 
              DisplayMemberPath="Name" 
              SelectedValue="{Binding NodeCategory, Mode=TwoWay}" />
    <Button Content="Button" Height="27" HorizontalAlignment="Left" 
            Margin="76,110,0,0" Name="button1" VerticalAlignment="Top" 
            Width="120" Click="button1_Click" />
</Grid>

et dans le ViewModel de la fenêtre

class ComboBoxSampleViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    public CategoryList List { get; set; }

    public ComboBoxSampleViewModel()
    {
        this.List = new CategoryList();
        NodeCategory = List.Selected;
    }

    private ComboBoxSampleItemViewModel nodeCategory;
    public ComboBoxSampleItemViewModel NodeCategory
    {
        get
        {
            return nodeCategory;
        }
        set
        {
            nodeCategory = value;
            NotifyPropertyChanged("NodeCategory");
        }
    }

    internal void SelectCategory(string p)
    {
        this.List.SelectByName(p);
        this.NodeCategory = this.List.Selected;
    }
}

Avec l'aide de cette petite classe:

public class CategoryList
{
    public ObservableCollection<ComboBoxSampleItemViewModel> Categories { get; set; }
    public ComboBoxSampleItemViewModel Selected { get; set; }
    public CategoryList()
    {
        Categories = new ObservableCollection<ComboBoxSampleItemViewModel>();

        var cat1 = new ComboBoxSampleItemViewModel() { Name = "Categorie 1" };
        var cat2 = new ComboBoxSampleItemViewModel() { Name = "Categorie 2" };
        var cat3 = new ComboBoxSampleItemViewModel() { Name = "Categorie 3" };
        var cat4 = new ComboBoxSampleItemViewModel() { Name = "Categorie 4" };

        Categories.Add(cat1);
        Categories.Add(cat2);
        Categories.Add(cat3);
        Categories.Add(cat4);

        this.Selected = cat3;
    }

    internal void SelectByName(string p)
    {
        this.Selected = this.Categories.Where(s => s.Name.Equals(p)).FirstOrDefault();
    }
}

Et cet article ViewModel

public class ComboBoxSampleItemViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    private string name;

    public string Name 
    { 
        get
        {
            return name;
        }
        set
        {
            name = value;
            NotifyPropertyChanged("Name");
        }
    }
}
1
Mare Infinitus

Si Combobox est lié à la classe d'objets du modèle d'affichage, alors que le SelectionBoxItem de l'objet expéditeur (dans le SelectionChanged dans le code derrière) n'a pas ce type, cela signifie qu'il est toujours en cours de chargement.

ComboBox combo = sender as ComboBox;
if (combo.SelectionBoxItem.GetType() == typeof(BindedClass))
{
            // Not loading
}
0
stéphane Boutinet