ผูก WPF ComboBox ไปยังรายการที่กำหนดเอง


183

ฉันมี ComboBox ที่ดูเหมือนจะไม่อัพเดทค่า SelectedItem / SelectedValue

ComboBox ItemsSource ถูกผูกไว้กับคุณสมบัติบนคลาส ViewModel ที่แสดงรายการสมุดโทรศัพท์ RAS จำนวนมากเป็น CollectionView จากนั้นฉันได้ผูก (ในเวลาที่แยกต่างหาก) ทั้งคุณสมบัติSelectedItemหรือSelectedValueไปยังคุณสมบัติอื่นของ ViewModel ฉันได้เพิ่ม MessageBox ลงในคำสั่งบันทึกเพื่อตรวจแก้จุดบกพร่องค่าที่กำหนดโดย databinding แต่ไม่มีการตั้งค่า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}" />

ฉันสนใจเฉพาะค่าสตริงจริงที่แสดงใน ComboBox ไม่ใช่คุณสมบัติอื่น ๆ ของวัตถุเนื่องจากเป็นค่าที่ฉันต้องส่งผ่านไปยัง RAS เมื่อฉันต้องการทำการเชื่อมต่อ VPN ด้วยเหตุนี้DisplayMemberPathและSelectedValuePathเป็นทั้งชื่อคุณสมบัติของ ConnectionViewModel ComboBox อยู่ในDataTemplateการนำไปใช้ItemsControlกับหน้าต่างที่ DataContext ได้รับการตั้งค่าเป็นอินสแตนซ์ ViewModel

ComboBox แสดงรายการของรายการอย่างถูกต้องและฉันสามารถเลือกหนึ่งรายการใน UI โดยไม่มีปัญหา อย่างไรก็ตามเมื่อฉันแสดงกล่องข้อความจากคำสั่งคุณสมบัติ PhonebookEntry ยังคงมีค่าเริ่มต้นอยู่ในนั้นไม่ใช่ค่าที่เลือกจาก ComboBox อินสแตนซ์ของกล่องข้อความอื่นกำลังปรับปรุงและแสดงใน MessageBox

ฉันขาดอะไรบ้างกับการบันทึกข้อมูล ComboBox? ฉันทำการค้นหาจำนวนมากและดูเหมือนจะไม่พบสิ่งใดที่ฉันทำผิด


นี่คือพฤติกรรมที่ฉันเห็น แต่มันไม่ทำงานด้วยเหตุผลบางอย่างในบริบทเฉพาะของฉัน

ฉันมี MainWindowViewModel ซึ่งมีCollectionViewConnectionViewModels อยู่ ในโค้ดไฟล์ MainWindowView.xaml ฉันตั้งค่า DataContext เป็น MainWindowViewModel MainWindowView.xaml มีข้อItemsControlผูกมัดกับคอลเลกชันของ ConnectionViewModels ฉันมี DataTemplate ที่เก็บ ComboBox และ TextBox อื่น ๆ TextBoxes จะผูกพันโดยตรงกับคุณสมบัติของ 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}" />

กล่องข้อความทั้งหมดผูกอย่างถูกต้องและข้อมูลย้ายระหว่างพวกเขาและ ViewModel โดยไม่มีปัญหา มันเป็นเพียง ComboBox ที่ไม่ทำงาน

คุณถูกต้องในสมมุติฐานของคุณเกี่ยวกับคลาส PhonebookEntry

สมมติฐานที่ผมทำก็คือว่า DataContext ใช้โดย DataTemplate 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>

behind รหัส :

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

หากคุณเรียกใช้ตัวอย่างนั้นคุณจะได้รับพฤติกรรมที่ฉันพูดถึง กล่องข้อความปรับปรุงการรวมที่ดีเมื่อคุณแก้ไข แต่ ComboBox ไม่ การดูสับสนมากเพราะสิ่งเดียวที่ฉันทำคือแนะนำ ViewModel ของผู้ปกครอง

ขณะนี้ฉันกำลังทำงานภายใต้การแสดงผลที่รายการที่ผูกกับชายด์ของ DataContext มีชายด์นั้นเป็น DataContext ฉันไม่พบเอกสารใด ๆ ที่ล้างสิ่งนี้ไม่ทางใดก็ทางหนึ่ง

กล่าวคือ

หน้าต่าง -> DataContext = MainWindowViewModel
..Items -> ที่ถูกผูกไว้กับ DataContext.PhonebookEntries
.... รายการ -> DataContext = PhonebookEntry (เชื่อมโยงโดยนัย)

ฉันไม่รู้ว่าจะอธิบายสมมติฐานของฉันได้ดีกว่านี้ไหม (?)


เพื่อยืนยันสมมติฐานของฉันเปลี่ยนการเชื่อมโยงกล่องข้อความให้เป็น

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

และสิ่งนี้จะแสดงการผูกกล่องข้อความ (ซึ่งฉันกำลังเปรียบเทียบกับ DataContext) คืออินสแตนซ์ ConnectionViewModel

คำตอบ:


189

คุณตั้งค่า DisplayMemberPath และ SelectedValuePath เป็น "ชื่อ" ดังนั้นฉันคิดว่าคุณมี PhoneBookEntry คลาสที่มีชื่อคุณสมบัติสาธารณะ

คุณได้ตั้งค่า DataContext เป็นวัตถุ ConnectionViewModel ของคุณแล้วหรือยัง

ฉันคัดลอกโค้ดของคุณและทำการแก้ไขเล็กน้อยและดูเหมือนว่าจะทำงานได้ดี ฉันสามารถตั้งค่าคุณสมบัติ PhoneBookEnty ของ Viewmodels และรายการที่เลือกใน Combobox และฉันสามารถเปลี่ยนรายการที่เลือกใน Combobox และมุมมองโมเดล 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 ตัวอย่างที่สองดูเหมือนจะไม่ทำงานซึ่งดูเหมือนจะแปลกสำหรับฉัน ถ้าฉันเปลี่ยนคุณสมบัติ PhonebookEntries บน ConnectionViewModel ให้เป็นประเภท ReadOnlyCollectionการโยง TwoWay ของคุณสมบัติ SelectedValue บน combobox จะทำงานได้ดี

อาจมีปัญหากับ CollectionView หรือไม่ ฉันสังเกตเห็นคำเตือนในคอนโซลเอาต์พุต:

ระบบคำเตือนข้อมูล: 50: การใช้ CollectionView โดยตรงไม่ได้รับการรองรับอย่างสมบูรณ์ คุณสมบัติพื้นฐานทำงานได้แม้ว่าจะมีความไร้ประสิทธิภาพ แต่คุณสมบัติขั้นสูงอาจพบข้อบกพร่องที่รู้จัก พิจารณาใช้คลาสที่ได้รับเพื่อหลีกเลี่ยงปัญหาเหล่านี้

Edit2 (.NET 4.5):เนื้อหาของ DropDownList สามารถเป็นไปตาม ToString () และไม่ใช่ DisplayMemberPath ในขณะที่ DisplayMemberPath ระบุสมาชิกสำหรับรายการที่เลือกและแสดงเท่านั้น


1
ผมสังเกตว่าข้อความเช่นกัน แต่ผมถือว่าสิ่งที่ถูกปกคลุมไปด้วยจะได้รับข้อมูลพื้นฐานที่มีผลผูกพัน ฉันเดาว่าไม่. :) ตอนนี้ฉันกำลังแสดงคุณสมบัติเป็น IList <T >และในคุณสมบัติ getter โดยใช้ _list.AsReadOnly () คล้ายกับที่คุณพูดถึง มันทำงานได้เหมือนที่ฉันคาดหวังว่าจะมีวิธีดั้งเดิม นอกจากนี้ฉันยังทราบว่าในขณะที่การเชื่อมโยงรายการแหล่งข้อมูลทำงานได้ดีฉันสามารถใช้คุณสมบัติปัจจุบันใน ViewModel เพื่อเข้าถึงรายการที่เลือกใน ComboBox ถึงกระนั้นมันก็ไม่ได้รู้สึกเป็นธรรมชาติที่มีผลผูกพันคุณสมบัติ ComboBoxes SelectedValue / SelectedItem
Geoff Bennett

3
ฉันสามารถยืนยันได้ว่าการเปลี่ยนแปลงคอลเลกชันซึ่งItemsSourceคุณสมบัติถูกผูกไว้กับคอลเลกชันแบบอ่านอย่างเดียวทำให้มันทำงานได้ ในกรณีของฉันฉันมีการเปลี่ยนแปลงได้จากการObservableCollection ReadOnlyObservableCollectionถั่ว. นี่คือ. NET 3.5 - ไม่แน่ใจว่าได้รับการแก้ไขใน 4.0
ChrisWue

74

เมื่อต้องการผูกข้อมูลกับ 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; } 
}

วิธีนี้ไม่ได้ผลสำหรับฉัน ItemsSource ทำงานได้ดี แต่เส้นทางของเส้นทางไม่ได้เปลี่ยนเส้นทางอย่างถูกต้องไปยังค่า ComboData
Coneone

3
IdและValueต้องเป็นคุณสมบัติไม่ใช่ฟิลด์คลาสเช่น:public class ComboData { public int Id { get; set; } public string Value { get; set; } }
Edgar

23

ฉันมีสิ่งที่ในตอนแรกดูเหมือนว่าจะเป็นปัญหาที่เหมือนกัน แต่มันกลับกลายเป็นเพราะปัญหาความเข้ากันได้ NHibernate / WPF ปัญหาเกิดจากวิธีที่ WPF ตรวจสอบความเท่าเทียมกันของวัตถุ ฉันสามารถทำให้สิ่งของทำงานโดยใช้คุณสมบัติ ID วัตถุในคุณสมบัติ SelectedValue และ SelectedValuePath

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

ดูโพสต์บล็อกจาก Chester, WPF ComboBox - SelectedItem, SelectedValue และ SelectedValuePath กับ NHibernateเพื่อดูรายละเอียด


1

ฉันมีปัญหาที่คล้ายกันซึ่ง SelectedItem ไม่เคยอัปเดต

ปัญหาของฉันคือว่ารายการที่เลือกไม่ใช่อินสแตนซ์เดียวกับรายการที่มีอยู่ในรายการ ดังนั้นฉันต้องแทนที่เมธอด Equals () ใน MyCustomObject ของฉันและเปรียบเทียบ IDs ของทั้งสองอินสแตนซ์เพื่อบอก ComboBox ว่าเป็นวัตถุเดียวกัน

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.