WPF ComboBox को एक कस्टम सूची में बांधना


183

मेरे पास एक ComboBox है जो SelectItem / SelectValue को अपडेट नहीं करता है।

ComboBox ItemSource एक ViewModel क्लास की एक प्रॉपर्टी से जुड़ा है, जो एक संग्रह दृश्य के रूप में RAS फोनबुक प्रविष्टियों के एक समूह को सूचीबद्ध करता है। तब मैं (अलग-अलग समय पर) दोनों SelectedItemया SelectedValueViewModel की अन्य संपत्ति के लिए बाध्य हूं । मैंने डेटाबाइंडिंग द्वारा निर्धारित मानों को डीबग करने के लिए सेव कमांड में एक मैसेजबॉक्स जोड़ा है, लेकिन 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}" />

मुझे केवल कॉम्बो बॉक्स में प्रदर्शित वास्तविक स्ट्रिंग मूल्य में दिलचस्पी है, न कि वस्तु के किसी अन्य गुण के रूप में, यह वह मूल्य है जो मुझे आरएएस में पास करने की आवश्यकता है जब मैं वीपीएन कनेक्शन बनाना चाहता हूं, इसलिए DisplayMemberPathऔर SelectedValuePathदोनों के नाम की संपत्ति है ConnectionViewModel। ComboBox एक विंडो पर एक पर DataTemplateलागू होता है, जिसका ItemsControlDataContext ViewModel उदाहरण के लिए सेट किया गया है।

ComboBox वस्तुओं की सूची को सही ढंग से प्रदर्शित करता है, और मैं UI में किसी भी समस्या का चयन कर सकता हूं। हालाँकि जब मैं संदेश बॉक्स को कमांड से प्रदर्शित करता हूं, तो PhonebookEntry संपत्ति में अभी भी प्रारंभिक मूल्य है, कॉम्बो बॉक्स से चयनित मूल्य नहीं है। अन्य टेक्स्टबॉक्स इंस्टेंस मैसेजबॉक्स में ठीक और प्रदर्शित कर रहे हैं।

क्या मैं कॉम्बोबॉक्स को डेटाबाइंडिंग के साथ याद कर रहा हूं? मैंने बहुत खोज की है और ऐसा कुछ भी नहीं पा सकता हूँ जो मैं गलत कर रहा हूँ।


यह वह व्यवहार है जिसे मैं देख रहा हूं, हालांकि यह मेरे विशेष संदर्भ में किसी कारण से काम नहीं कर रहा है।

मेरे पास एक MainWindowViewModel है जिसमें CollectionViewConnectionViewModels है। MainWindowView.xaml फ़ाइल कोड-पीछे में, मैं MainWindowViewModel में DataContext सेट करता हूं। MainWindowView.xaml के पास ItemsControlConnectionViewModels के संग्रह के लिए एक बाध्यता है। मेरे पास एक DataTemplate है जो कॉम्बो बॉक्स के साथ-साथ कुछ अन्य टेक्स्टबॉक्स भी रखता है। 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}" />

TextBoxes सभी सही ढंग से बाँधते हैं, और डेटा उनके और 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; }
        }
    }
}

यदि आप उस उदाहरण को चलाते हैं, तो आपको वह व्यवहार मिलेगा जिसकी मैं बात कर रहा हूं। जब आप इसे संपादित करते हैं, तो टेक्स्टबॉक्स इसके बाइंडिंग फाइन को अपडेट करता है, लेकिन कॉम्बो बॉक्स नहीं। बहुत भ्रमित करने वाली बात यह है कि वास्तव में केवल एक चीज जो मैंने की है, वह है माता-पिता ViewModel।

मैं वर्तमान में इस धारणा के तहत श्रम कर रहा हूं कि किसी DataContext के बच्चे के लिए बाध्य वस्तु उस बच्चे को अपने DataContext के रूप में रखती है। मुझे ऐसा कोई भी दस्तावेज़ नहीं मिला है जो इसे एक या दूसरे तरीके से साफ़ करता हो।

अर्थात,

विंडो ->
डेटा कॉन्टेक्स्ट = मेनविंडो व्यूमॉडल ..इतिमाएँ -> डेटाऑन्टेक्स्ट.फोनबुकइनट्रीज के लिए बाध्य
....

मुझे नहीं पता कि क्या मेरी धारणा को कोई बेहतर (?) बताता है।


मेरी धारणा की पुष्टि करने के लिए, टेक्स्टबॉक्स के बाइंडिंग को बदल दें

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

और यह TextBox बाइंडिंग रूट दिखाएगा (जो मैं DataContext से तुलना कर रहा हूं) ConnectionViewModel उदाहरण है।

जवाबों:


189

आपने DisplayMemberPath और SelectValuePath को "नाम" पर सेट किया है, इसलिए मैं मानता हूं कि आपके पास एक सार्वजनिक संपत्ति के नाम के साथ PhoneBookEntry है।

क्या आपने DataContext को अपने ConnectionViewModel ऑब्जेक्ट पर सेट किया है?

मैंने आपको कोड कॉपी किया और कुछ छोटे संशोधन किए, और यह ठीक काम करने लगता है। मैं कॉम्बोक्सॉक्स परिवर्तनों में Viewmodels 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;
    }
}

संपादित करें: ज्योफ दूसरा उदाहरण काम नहीं करता है, जो मुझे थोड़ा अजीब लगता है। अगर मैं PhoneBookEntries संपत्ति को ChangeViewModel पर ReadOnlyCollection के रूप में बदल देता हूं , तो कॉम्बोक्स पर SelectValue प्रॉपर्टी के टूव्यू बंधन ठीक काम करता है।

शायद संग्रह दृश्य के साथ कोई समस्या है? मैंने आउटपुट कंसोल में एक चेतावनी देखी:

System.Windows.Data चेतावनी: 50: सीधे संग्रह दृश्य का उपयोग करना पूरी तरह से समर्थित नहीं है। बुनियादी सुविधाएँ काम करती हैं, हालांकि कुछ अक्षमताओं के साथ, लेकिन उन्नत सुविधाएँ ज्ञात बगों का सामना कर सकती हैं। इन समस्याओं से बचने के लिए एक व्युत्पन्न वर्ग का उपयोग करने पर विचार करें।

Edit2 (.NET 4.5): DropDownList की सामग्री ToString () और DisplayMemberPath पर आधारित हो सकती है, जबकि DisplayMemberPath केवल चयनित और प्रदर्शित आइटम के लिए सदस्य को निर्दिष्ट करता है।


1
मैंने उस संदेश को भी नोटिस किया, लेकिन मैंने मान लिया कि जो कवर किया गया था वह बुनियादी डेटा बाइंडिंग होगा। मुझे नहीं लगता। :) अब मैं IList <T के रूप >में और संपत्ति गेट्टर में _list.AsReadOnly () के समान गुणों का खुलासा कर रहा हूं , जिस तरह से आपने उल्लेख किया है। यह काम कर रहा है क्योंकि मुझे उम्मीद है कि मूल विधि होगी। इसके अलावा, इसने मेरे दिमाग को पार कर दिया कि जब आइटम्स सोर्स बाध्यकारी ठीक काम कर रहा था, तो मैं कॉम्बोबॉक्स में चयनित आइटम तक पहुंचने के लिए ViewModel में वर्तमान संपत्ति का उपयोग कर सकता था। फिर भी, यह ComboBoxes SelectValue / SelectItem संपत्ति को बाइंड करने के रूप में स्वाभाविक रूप से महसूस नहीं करता है।
ज्योफ बेनेट

3
मैं पुष्टि कर सकता हूं कि संग्रह को बदलना, जिसके लिए ItemsSourceसंपत्ति बाध्य है, केवल पढ़ने के लिए संग्रह यह काम करता है। मेरे मामले में मैं से बदलना पड़ा ObservableCollectionकरने के लिए ReadOnlyObservableCollection। नट। यह .NET 3.5 है - निश्चित नहीं है अगर यह 4.0 में तय किया गया है
क्रिस

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

यह समाधान मेरे लिए काम नहीं करता है। आइटम्स स्रोत ठीक काम करता है, लेकिन पथ प्रॉपर्टीज़ कॉम्बोडेटा मानों को सही ढंग से पुनर्निर्देशित नहीं कर रहे हैं।
कोनोन

3
Idऔर गुणValue होने चाहिए , न कि क्लास फील्ड, जैसे:public class ComboData { public int Id { get; set; } public string Value { get; set; } }
एडगर

23

मुझे लगता है कि पहली बार में यह एक समान समस्या थी, लेकिन यह NHibernate / WPF संगतता समस्या के कारण निकला। यह समस्या जिस तरह से वस्तु समानता के लिए WPF जाँच की वजह से हुई थी। मैं SelectValue और SelectValuePath गुणों में ऑब्जेक्ट ID प्रॉपर्टी का उपयोग करके अपना सामान प्राप्त करने में सक्षम था।

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

विवरण के लिए Chester, The WPF ComboBox - SelectItem , SelectValue और NHibernate के साथ चयनितValuePath से ब्लॉग पोस्ट देखें।


1

मेरे पास एक ऐसा ही मुद्दा था जहां SelectItem कभी अद्यतन नहीं हुआ।

मेरी समस्या यह थी कि चयनित आइटम सूची में शामिल आइटम के समान नहीं था। इसलिए मुझे बस अपने MyCustomObject में बराबर () विधि को ओवरराइड करना था और कॉम्बो बॉक्स को बताने के लिए उन दो उदाहरणों की आईडी की तुलना करना था कि यह एक ही वस्तु है।

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.