[C#] WPF ComboBox를 사용자 지정 목록에 바인딩

SelectedItem / SelectedValue를 업데이트하지 않는 ComboBox가 있습니다.

ComboBox ItemsSource는 RAS 전화 번호부 항목을 CollectionView로 나열하는 ViewModel 클래스의 속성에 바인딩됩니다. 그런 다음 ViewModel 의 SelectedItem또는 SelectedValue다른 속성에 (별도로) 바인딩했습니다 . 데이터 바인딩으로 설정된 값을 디버깅하기 위해 MessageBox를 save 명령에 추가했지만 SelectedItem/ SelectedValue바인딩이 설정되지 않았습니다.

ViewModel 클래스는 다음과 같습니다.

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

_phonebookEntries 콜렉션이 비즈니스 오브젝트의 생성자에서 초기화되고 있습니다. ComboBox XAML은 다음과 같습니다.

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

I 만 실제 문자열 값에 관심이 콤보 상자에 표시되지이 같은 개체의 다른 특성은 내가 따라서, VPN 연결을 할 때이 RAS에 걸쳐 통과하는 데 필요한 값 DisplayMemberPathSelectedValuePath의 Name 속성 모두는 ConnectionViewModel. ComboBox는 DataContext가 ViewModel 인스턴스로 설정된 Window에 DataTemplate적용됩니다 ItemsControl.

ComboBox는 항목 목록을 올바르게 표시하며 UI에서 아무 문제없이 선택할 수 있습니다. 그러나 명령에서 메시지 상자를 표시 할 때 PhonebookEntry 속성에는 ComboBox에서 선택한 값이 아니라 여전히 초기 값이 있습니다. 다른 TextBox 인스턴스가 정상적으로 업데이트되어 MessageBox에 표시됩니다.

ComboBox의 데이터 바인딩에서 무엇을 놓치고 있습니까? 나는 많은 검색을 해왔고 내가 잘못하고있는 것을 찾지 못하는 것 같습니다.


이것은 내가보고있는 동작이지만 특정 상황에서 어떤 이유로 작동하지 않습니다.

CollectionViewConnectionViewModels 가있는 MainWindowViewModel이 있습니다 . MainWindowView.xaml 파일 코드 숨김에서 DataContext를 MainWindowViewModel로 설정했습니다. MainWindowView.xaml은 ItemsControlConnectionViewModels 컬렉션에 바인딩됩니다. ComboBox와 다른 TextBox를 포함하는 DataTemplate이 있습니다. TextBox는을 사용하여 ConnectionViewModel의 속성에 직접 바인딩됩니다 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; }
}

XAML 코드 숨김 :

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

그런 다음 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}" />

TextBox는 모두 올바르게 바인딩되며 데이터는 문제없이 ViewModel과 ViewModel간에 이동합니다. 작동하지 않는 것은 ComboBox뿐입니다.

PhonebookEntry 클래스에 대한 가정이 정확합니다.

내가 만들고있는 가정은 내 DataTemplate에서 사용하는 DataContext가 바인딩 계층을 통해 자동으로 설정되므로의 각 항목에 대해 명시 적으로 설정할 필요가 없다는 것 ItemsControl입니다. 그것은 나에게 약간 어리석은 것처럼 보일 것입니다.


다음은 위의 예를 기반으로 문제를 보여주는 테스트 구현입니다.

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>

코드 숨김 :

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

이 예제를 실행하면 내가 말하는 동작이 나타납니다. TextBox는 편집 할 때 바인딩이 제대로 업데이트되지만 ComboBox는 바인딩을 업데이트하지 않습니다. 내가 한 유일한 것으로 보는 것이 매우 혼란 스럽다면 부모 ViewModel을 소개하는 것입니다.

현재 DataContext의 자식에 바인딩 된 항목에 해당 자식이 DataContext라는 인상을 받고 있습니다. 이 방법을 정리하는 문서를 찾을 수 없습니다.

즉,

Window-> DataContext = MainWindowViewModel
..Items- > DataContext.PhonebookEntries에 바인딩 됨
…. Item- > DataContext = PhonebookEntry (내재적으로 연결됨 )

그게 내 가정을 더 잘 설명하는지 모르겠습니다 (?).


내 가정을 확인하려면 TextBox의 바인딩을 다음과 같이 변경하십시오.

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

그리고 이것은 TextBox 바인딩 루트 (DataContext와 비교하고 있음)가 ConnectionViewModel 인스턴스임을 보여줍니다.



답변

DisplayMemberPath 및 SelectedValuePath를 “Name”으로 설정 했으므로 공용 속성 Name을 가진 PhoneBookEntry 클래스가 있다고 가정합니다.

DataContext를 ConnectionViewModel 오브젝트로 설정 했습니까?

코드를 복사하고 약간 수정했는데 정상적으로 작동하는 것 같습니다. 뷰 모델 PhoneBookEnty 특성을 설정하고 콤보 박스에서 선택한 항목을 변경할 수 있으며 콤보 박스에서 선택한 항목을 변경할 수 있으며 뷰 모델 PhoneBookEntry 특성이 올바르게 설정되었습니다.

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

그리고 여기 내 코드 숨김이 있습니다.

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

편집 : Geoffs 두 번째 예가 작동하지 않는 것 같습니다. 내가하면 형 ReadOnlyCollection 될하기 위해 ConnectionViewModel에 PhonebookEntries 속성을 변경 , 콤보 상자의 SelectedValue 속성의 결합 양방향 잘 작동합니다.

CollectionView에 문제가 있습니까? 출력 콘솔에서 경고가 나타났습니다.

System.Windows.Data 경고 : 50 : CollectionView를 직접 사용하는 것은 완전히 지원되지 않습니다. 일부 비효율적이지만 기본 기능은 작동하지만 고급 기능에는 알려진 버그가 발생할 수 있습니다. 이러한 문제를 피하려면 파생 클래스 사용을 고려하십시오.

Edit2 (.NET 4.5) : DropDownList의 내용은 DisplayMemberPath가 아닌 ToString ()을 기반으로 할 수있는 반면 DisplayMemberPath는 선택 및 표시된 항목의 멤버 만 지정합니다.


답변

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는 다음과 같습니다.

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


답변

처음에는 동일한 문제인 것처럼 보였지만 NHibernate / WPF 호환성 문제 때문인 것으로 나타났습니다. WPF에서 개체가 같은지 확인하는 방식으로 인해 문제가 발생했습니다. SelectedValue 및 SelectedValuePath 속성에서 개체 ID 속성을 사용하여 물건을 작동시킬 수있었습니다.

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

자세한 내용은 Chester의 블로그 게시물 인 WPF ComboBox-SelectedItem, SelectedValue 및 NHibernate가있는 SelectedValuePath 를 참조하십시오.


답변

SelectedItem이 업데이트되지 않은 비슷한 문제가 있습니다.

내 문제는 선택한 항목이 목록에 포함 된 항목과 동일한 인스턴스가 아니라는 것입니다. 따라서 MyCustomObject에서 Equals () 메서드를 재정의하고 두 인스턴스의 ID를 비교하여 ComboBox에 동일한 개체라는 것을 알려주었습니다.

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


답변