Programming

WPF ComboBox를 사용자 지정 목록에 바인딩

procodes 2020. 5. 23. 23:17
반응형

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

Edit: Geoffs second example does not seem to work, which seems a bit odd to me. If I change the PhonebookEntries property on the ConnectionViewModel to be of type ReadOnlyCollection, the TwoWay binding of the SelectedValue property on the combobox works fine.

Maybe there is an issue with the CollectionView? I noticed a warning in the output console:

System.Windows.Data Warning: 50 : Using CollectionView directly is not fully supported. The basic features work, although with some inefficiencies, but advanced features may encounter known bugs. Consider using a derived class to avoid these problems.

Edit2 (.NET 4.5): The content of the DropDownList can be based on ToString() and not of DisplayMemberPath, while DisplayMemberPath specifies the member for the selected and displayed item only.


To bind the data to 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 looks like:

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

I had what at first seemed to be an identical problem, but it turned out to be due to an NHibernate/WPF compatibility issue. The problem was caused by the way WPF checks for object equality. I was able to get my stuff to work by using the object ID property in the SelectedValue and SelectedValuePath properties.

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

See the blog post from Chester, The WPF ComboBox - SelectedItem, SelectedValue, and SelectedValuePath with NHibernate, for details.


I had a similar issue where the SelectedItem never got updated.

My problem was that the selected item was not the same instance as the item contained in the list. So I simply had to override the Equals() method in my MyCustomObject and compare the IDs of those two instances to tell the ComboBox that it's the same object.

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

참고URL : https://stackoverflow.com/questions/561166/binding-a-wpf-combobox-to-a-custom-list

반응형