WPF Master-Details представление со списком и Combobox с привязкой - PullRequest
3 голосов
/ 01 августа 2009

Я искал ответ на свой вопрос уже несколько дней, но не могу найти решение.

Проблема в том, что выпадающий список обновляет объект Test в классе User с помощью ранее выбранных пользователей.

т.е. вы выбираете user2, а user2 имеет test2, затем вы выбираете user5, у которого есть test5. Теперь, если вы снова выберете user2, он покажет, что у него есть test5.

Вот код. У меня есть два класса пользователей и тесты. И две ObservableCollections для каждого из них. Вот как я их настроил:

public class User
{
    public string Name { get; set; }
    public int test { get; set; }
    public test userTest { get; set; }
}

public class test
{
    public int ID { get; set; }
    public String Name { get; set; }
}

public class ListOfTests:ObservableCollection<test>
{
    public ListOfTests()
    {
        for (int i = 0; i < 4; i++)
        {
            test newTest = new test();
            newTest.ID = i;
            newTest.Name = "Test " + i;
            Add(newTest);
        }
    }
}

public class ListOfUsers: ObservableCollection<User>
{
    public ListOfUsers()
    {
        ListOfTests testlist = new ListOfTests();
        for (int i = 0; i < 10; i++)
        {
            User newUser = new User();
            newUser.Name = "User " + i;
            newUser.ID = i;
            newUser.userTest = testlist[i];
            Add(newUser);
        }
    }
}

И XAML:

<Window x:Class="ComboboxTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ComboboxTest"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="SP1">
    <StackPanel.Resources>
        <local:ListOfTests x:Key="ListOfTests" />
    </StackPanel.Resources>
    <ListBox ItemsSource="{Binding}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
    <TextBox Text="{Binding Path=Name}" Foreground="Black"  />
    <TextBox Text="{Binding Path=userTest}" />  
    <ComboBox SelectedItem="{Binding Path=userTest}" 
              SelectedValue="{Binding Path=userTest.ID}"
              ItemsSource="{Binding Source={StaticResource ListOfTests}}" 
              DisplayMemberPath="Name" 
              SelectedValuePath="ID"

              Foreground="Black" />
</StackPanel>

Теперь, если я изменю привязку на SelectedItem на "{Binding Path = userTest, Mode = OneWay}", тогда она работает, но я не могу изменить ее вручную.

Вот мысль о кикере ... Если я нацеливаюсь на .Net 4.0 (VS2010), то он работает нормально ...

Может кто-нибудь помочь мне найти решение этой проблемы?

Ответы [ 2 ]

5 голосов
/ 01 августа 2009

Если я понимаю ваш вопрос, похоже, что WPF не уведомляется при изменении значения свойства. Вы можете обойти это, реализовав интерфейс INotifyPropertyChanged . Например, класс User будет выглядеть примерно так:

public class User : INotifyPropertyChanged
{
    private string name = string.Empty;
    public string Name
    {
        get { return this.name; }
        set
        {
            this.name = value;
            this.OnPropertyChanged("Name");
        }
    }

    private int test = 0;
    public int Test
    {
        get { return this.test; }
        set
        {
            this.test = value;
            this.OnPropertyChanged("Test");
        }
    }

    private test userTest = null;
    public test UserTest
    {
        get { return this.userTest; }
        set
        {
            this.userTest = value;
            this.OnPropertyChanged("UserTest");
        }
    }

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

Вероятно, вы должны сделать то же самое для вашего test класса.

WPF будет следить за событием PropertyChanged и обновлять любые привязанные привязки по мере необходимости. Это должно привести к тому, что выбранный элемент в ComboBox вернется к тесту для user2.

Обновление : ОК, думаю, у меня все получилось. Я думаю, что вам не хватает части кода в том, что вы опубликовали (например, что такое DataContext для Window), но вот что я получил:

Я создал класс с именем ViewModel, для которого установлено DataContext основного Window. Вот его код:

class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        for(int i = 0; i < 4; i++)
        {
            this.tests.Add(new Test()
            {
                ID = i,
                Name = "Test " + i.ToString(),
            });
        }

        for(int i = 0; i < 4; i++)
        {
            this.users.Add(new User()
            {
                Name = "User " + i.ToString(),
                ID = i,
                UserTest = this.tests[i],
            });
        }
    }

    private ObservableCollection<User> users = new ObservableCollection<User>();
    public IEnumerable<User> Users
    {
        get { return this.users; }
    }

    private ObservableCollection<Test> tests = new ObservableCollection<Test>();
    public IEnumerable<Test> Tests
    {
        get { return this.tests; }
    }

    private User currentUser = null;
    public User CurrentUser
    {
        get { return this.currentUser; }
        set
        {
            this.currentUser = value;
            this.OnPropertyChanged("CurrentUser");
        }
    }

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

Я перенес создание двух списков в код. Одна вещь, которую я заметил в вашем примере, это то, что один экземпляр ListOfTests использовался как ItemsSource для ComboBox, тогда как другой экземпляр использовался для построения ListOfUsers. Я не уверен, было ли это частью проблемы или нет, но лучше всего иметь один список тестов.

XAML для основного Window выглядит следующим образом:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <ListBox ItemsSource="{Binding Path=Users}"
                 SelectedItem="{Binding Path=CurrentUser}"
                 DisplayMemberPath="Name"
                 IsSynchronizedWithCurrentItem="True">
        </ListBox>
        <TextBox Text="{Binding Path=CurrentUser.Name}" />
        <TextBox Text="{Binding Path=CurrentUser.UserTest.Name}" />
        <ComboBox ItemsSource="{Binding Path=Tests}"
                  SelectedItem="{Binding Path=CurrentUser.UserTest}"
                  DisplayMemberPath="Name" />
    </StackPanel>
</Window>

Ключом к тому, чтобы все заработало, является свойство CurrentUser. Он связан с ListBox.SelectedItem, а ComboBox.SelectedItem связан с CurrentUser.UserTest. Это изменит выбор в ComboBox для представления теста пользователя, выбранного в ListBox.

У меня все это работает с использованием Visual Studio 2008 SP1, так что, надеюсь, оно будет работать и для вас. Если у вас возникнут какие-либо проблемы с этим, дайте мне знать, и я посмотрю, что я могу сделать.

0 голосов
/ 06 августа 2009

Энди,

Вот более читабельная выдержка из кода, который у меня сейчас есть.

public class User : INotifyPropertyChanged
{
    private string name;
    public string Name 
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }

    private int iD;
    public int ID 
    {
        get
        {
            return iD;
        }
        set
        {
            iD = value;
            OnPropertyChanged("ID");
        }
    }

    private test userTest;
    public test UserTest 
    {
        get
        {
            return userTest;
        }
        set
        {
            userTest = value;
            OnPropertyChanged("UserTest");
        }
    }


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

Выглядит лучше, чем в комментариях.

С уважением Корн

...