c# binding - Enlazando un ComboBox de WPF a una lista personalizada




mvvm datacontext (5)

Para vincular los datos a ComboBox

List<ComboData> ListData = new List<ComboData>();
ListData.Add(new ComboData { Id = "1", Value = "One" });
ListData.Add(new ComboData { Id = "2", Value = "Two" });
ListData.Add(new ComboData { Id = "3", Value = "Three" });
ListData.Add(new ComboData { Id = "4", Value = "Four" });
ListData.Add(new ComboData { Id = "5", Value = "Five" });

cbotest.ItemsSource = ListData;
cbotest.DisplayMemberPath = "Value";
cbotest.SelectedValuePath = "Id";

cbotest.SelectedValue = "2";

ComboData se ve así:

public class ComboData
{ 
  public int Id { get; set; } 
  public string Value { get; set; } 
}

Tengo un ComboBox que no parece actualizar el SelectedItem / SelectedValue.

ComboBox ItemsSource está vinculado a una propiedad en una clase ViewModel que enumera un grupo de entradas de la libreta de direcciones RAS como CollectionView. Luego he vinculado (en momentos distintos) el SelectedItem o SelectedValue a otra propiedad del ViewModel. He agregado un MessageBox en el comando guardar para depurar los valores establecidos por el enlace de datos, pero el enlace SelectedItem / SelectedValue no se está configurando.

La clase ViewModel se ve así:

public ConnectionViewModel
{
    private readonly CollectionView _phonebookEntries;
    private string _phonebookeEntry;

    public CollectionView PhonebookEntries
    {
        get { return _phonebookEntries; }
    }

    public string PhonebookEntry
    {
        get { return _phonebookEntry; }
        set
        {
            if (_phonebookEntry == value) return;
            _phonebookEntry = value;
            OnPropertyChanged("PhonebookEntry");
        }
    }
}

La colección _phonebookEntries se está inicializando en el constructor desde un objeto comercial. El ComboBox XAML se ve más o menos así:

<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
    DisplayMemberPath="Name"
    SelectedValuePath="Name"
    SelectedValue="{Binding Path=PhonebookEntry}" />

Solo estoy interesado en el valor de cadena real que se muestra en el ComboBox, no en otras propiedades del objeto, ya que este es el valor que necesito pasar a RAS cuando quiero hacer la conexión VPN, por lo tanto DisplayMemberPath y SelectedValuePath son ambos el nombre propiedad de ConnectionViewModel. El ComboBox está en un DataTemplate aplicado a un ItemsControl en una ventana cuyo DataContext se ha configurado en una instancia de ViewModel.

El ComboBox muestra la lista de elementos correctamente, y puedo seleccionar uno en la interfaz de usuario sin ningún problema. Sin embargo, cuando visualizo el cuadro de mensaje desde el comando, la propiedad PhonebookEntry aún tiene el valor inicial, no el valor seleccionado de ComboBox. Otras instancias de TextBox se están actualizando correctamente y se muestran en MessageBox.

¿Qué me estoy perdiendo con Databinding ComboBox? He realizado muchas búsquedas y parece que no puedo encontrar nada que esté haciendo mal.

Este es el comportamiento que estoy viendo, sin embargo, no está funcionando por alguna razón en mi contexto particular.

Tengo un MainWindowViewModel que tiene una CollectionView de ConnectionViewModels. En el código subyacente del archivo MainWindowView.xaml, configuro el DataContext al MainWindowViewModel. MainWindowView.xaml tiene un ItemsControl vinculado a la colección de ConnectionViewModels. Tengo un DataTemplate que contiene el ComboBox y algunos otros cuadros de texto. Los TextBoxes están ligados directamente a las propiedades del ConnectionViewModel usando Text="{Binding Path=ConnectionName}" .

public class ConnectionViewModel : ViewModelBase
{
    public string Name { get; set; }
    public string Password { get; set; }
}

public class MainWindowViewModel : ViewModelBase
{
    // List<ConnectionViewModel>...
    public CollectionView Connections { get; set; }
}

El código XAML detrás de:

public partial class Window1
{
    public Window1()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }
}

Entonces XAML:

<DataTemplate x:Key="listTemplate">
    <Grid>
        <ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
            DisplayMemberPath="Name"
            SelectedValuePath="Name"
            SelectedValue="{Binding Path=PhonebookEntry}" />
        <TextBox Text="{Binding Path=Password}" />
    </Grid>
</DataTemplate>

<ItemsControl ItemsSource="{Binding Path=Connections}"
    ItemTemplate="{StaticResource listTemplate}" />

Los cuadros de texto se enlazan correctamente y los datos se mueven entre ellos y el modelo de vista sin problemas. Solo el ComboBox no funciona.

Tiene razón en su suposición con respecto a la clase PhonebookEntry.

La suposición que estoy asumiendo es que el DataContext utilizado por mi DataTemplate se establece automáticamente a través de la jerarquía de enlace, de modo que no tengo que establecerlo explícitamente para cada elemento en ItemsControl . Eso me parece un poco tonto.

Aquí hay una implementación de prueba que demuestra el problema, basado en el ejemplo anterior.

XAML:

<Window x:Class="WpfApplication7.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <DataTemplate x:Key="itemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding Path=Name}" Width="50" />
                <ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
                    DisplayMemberPath="Name"
                    SelectedValuePath="Name"
                    SelectedValue="{Binding Path=PhonebookEntry}"
                    Width="200"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding Path=Connections}"
            ItemTemplate="{StaticResource itemTemplate}" />
    </Grid>
</Window>

El código detrás :

namespace WpfApplication7
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }
    }

    public class PhoneBookEntry
    {
        public string Name { get; set; }
        public PhoneBookEntry(string name)
        {
            Name = name;
        }
    }

    public class ConnectionViewModel : INotifyPropertyChanged
    {

        private string _name;

        public ConnectionViewModel(string name)
        {
            _name = name;
            IList<PhoneBookEntry> list = new List<PhoneBookEntry>
                                             {
                                                 new PhoneBookEntry("test"),
                                                 new PhoneBookEntry("test2")
                                             };
            _phonebookEntries = new CollectionView(list);
        }
        private readonly CollectionView _phonebookEntries;
        private string _phonebookEntry;

        public CollectionView PhonebookEntries
        {
            get { return _phonebookEntries; }
        }

        public string PhonebookEntry
        {
            get { return _phonebookEntry; }
            set
            {
                if (_phonebookEntry == value) return;
                _phonebookEntry = value;
                OnPropertyChanged("PhonebookEntry");
            }
        }

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name == value) return;
                _name = value;
                OnPropertyChanged("Name");
            }
        }
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class MainWindowViewModel
    {
        private readonly CollectionView _connections;

        public MainWindowViewModel()
        {
            IList<ConnectionViewModel> connections = new List<ConnectionViewModel>
                                                          {
                                                              new ConnectionViewModel("First"),
                                                              new ConnectionViewModel("Second"),
                                                              new ConnectionViewModel("Third")
                                                          };
            _connections = new CollectionView(connections);
        }

        public CollectionView Connections
        {
            get { return _connections; }
        }
    }
}

Si ejecuta ese ejemplo, obtendrá el comportamiento del que estoy hablando. TextBox actualiza bien su enlace cuando lo edita, pero el ComboBox no lo hace. Muy confuso ya que realmente lo único que he hecho es introducir un ViewModel padre.

Actualmente estoy trabajando con la impresión de que un elemento vinculado al hijo de un DataContext tiene ese hijo como su DataContext. No puedo encontrar ninguna documentación que aclare esto de una forma u otra.

Es decir,

Ventana -> DataContext = MainWindowViewModel
..Items -> Vinculado a DataContext.Páginas de teléfonos favoritos
.... Artículo -> DataContext = PhonebookEntry (implícitamente asociado)

No sé si eso explica mi suposición mejor (?).

Para confirmar mi suposición, cambie el enlace del TextBox para que sea

<TextBox Text="{Binding Mode=OneWay}" Width="50" />

Y esto mostrará que la raíz de enlace de TextBox (que estoy comparando con el DataContext) es la instancia de ConnectionViewModel.


Tuve lo que al principio parecía ser un problema idéntico, pero resultó ser debido a un problema de compatibilidad con NHibernate / WPF. El problema fue causado por la forma en que WPF verifica la igualdad de objetos. Logré que mis cosas funcionaran al usar la propiedad ID de objeto en las propiedades SelectedValue y SelectedValuePath.

<ComboBox Name="CategoryList"
          DisplayMemberPath="CategoryName"
          SelectedItem="{Binding Path=CategoryParent}"
          SelectedValue="{Binding Path=CategoryParent.ID}"
          SelectedValuePath="ID">

Consulte la publicación del blog de Chester, The WPF ComboBox - SelectedItem, SelectedValue y SelectedValuePath con NHibernate , para obtener más información.


Tuve un problema similar en el que SelectedItem nunca se actualizó.

Mi problema era que el elemento seleccionado no era la misma instancia que el elemento contenido en la lista. Así que simplemente tuve que anular el método Equals () en MyCustomObject y comparar los ID de esas dos instancias para decirle al ComboBox que es el mismo objeto.

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}

Estableces DisplayMemberPath y SelectedValuePath en "Nombre", por lo que supongo que tienes una clase PhoneBookEntry con un Nombre de propiedad pública.

¿Ha establecido el DataContext en su objeto ConnectionViewModel?

Copié tu código e hice algunas modificaciones menores, y parece funcionar bien. Puedo establecer la propiedad viewmodels PhoneBookEnty y el elemento seleccionado en el cuadro combinado cambia, y puedo cambiar el elemento seleccionado en el cuadro combinado y la propiedad PhoneBookEntry de los modelos de vista está configurada correctamente.

Aquí está mi contenido XAML:

<Window x:Class="WpfApplication6.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
<Grid>
    <StackPanel>
        <Button Click="Button_Click">asdf</Button>
        <ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
                  DisplayMemberPath="Name"
                  SelectedValuePath="Name"
                  SelectedValue="{Binding Path=PhonebookEntry}" />
    </StackPanel>
</Grid>
</Window>

Y aquí está mi código detrás:

namespace WpfApplication6
{

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            ConnectionViewModel vm = new ConnectionViewModel();
            DataContext = vm;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ((ConnectionViewModel)DataContext).PhonebookEntry = "test";
        }
    }

    public class PhoneBookEntry
    {
        public string Name { get; set; }

        public PhoneBookEntry(string name)
        {
            Name = name;
        }

        public override string ToString()
        {
            return Name;
        }
    }

    public class ConnectionViewModel : INotifyPropertyChanged
    {
        public ConnectionViewModel()
        {
            IList<PhoneBookEntry> list = new List<PhoneBookEntry>();
            list.Add(new PhoneBookEntry("test"));
            list.Add(new PhoneBookEntry("test2"));
            _phonebookEntries = new CollectionView(list);
        }

        private readonly CollectionView _phonebookEntries;
        private string _phonebookEntry;

        public CollectionView PhonebookEntries
        {
            get { return _phonebookEntries; }
        }

        public string PhonebookEntry
        {
            get { return _phonebookEntry; }
            set
            {
                if (_phonebookEntry == value) return;
                _phonebookEntry = value;
                OnPropertyChanged("PhonebookEntry");
            }
        }

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

Editar: El segundo ejemplo de Geoffs no parece funcionar, lo cual me parece un poco extraño. Si cambio la propiedad PhonebookEntries en ConnectionViewModel para que sea del tipo ReadOnlyCollection , el enlace TwoWay de la propiedad SelectedValue en el cuadro combinado funciona bien.

Tal vez hay un problema con el CollectionView? Noté una advertencia en la consola de salida:

System.Windows.Data Warning: 50: El uso de CollectionView directamente no es totalmente compatible. Las funciones básicas funcionan, aunque con algunas ineficiencias, pero las funciones avanzadas pueden encontrar errores conocidos. Considere usar una clase derivada para evitar estos problemas.

Edit2 (.NET 4.5): el contenido de DropDownList se puede basar en ToString () y no en DisplayMemberPath, mientras que DisplayMemberPath especifica el miembro solo para el elemento seleccionado y mostrado.


Puede obtener el TreeViewItem para un elemento dado en un árbol usando ItemContainerGenerator y una vez que lo tenga, ItemContainerGenerator en una posición para establecer el filtro.





c# wpf data-binding mvvm combobox