Связывание данных WPF - что мне не хватает? - PullRequest
1 голос
/ 23 мая 2011

Я пытаюсь понять концепцию привязки данных WPF на простом примере, но, похоже, я не совсем понял смысл всего этого.

Примером является один из каскадных выпадающих списков; XAML выглядит следующим образом:

<Window x:Class="CascadingDropDown.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="496" Width="949" Loaded="Window_Loaded">
    <Grid>
        <ComboBox Name="comboBox1" ItemsSource="{Binding}" DisplayMemberPath="Key" SelectionChanged="comboBox1_SelectionChanged" />
        <ComboBox Name="comboBox2" ItemsSource="{Binding}" DisplayMemberPath="Name" />
    </Grid>
</Window>

Это код формы:

public partial class MainWindow : Window
{
    private ObservableCollection<ItemA> m_lstItemAContext = new ObservableCollection<ItemA>();
    private ObservableCollection<ItemB> m_lstItemBContext = new ObservableCollection<ItemB>();
    private IEnumerable<ItemB> m_lstAllItemB = null;

    public MainWindow()
    {
        InitializeComponent();

        this.comboBox1.DataContext = m_lstItemAContext;
        this.comboBox2.DataContext = m_lstItemBContext;
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        var lstItemA = new List<ItemA>() { new ItemA("aaa"), new ItemA("bbb"), new ItemA("ccc") };
        var lstItemB = new List<ItemB>() { new ItemB("aaa", "a11"), new ItemB("aaa", "a22"), new ItemB("bbb", "b11"), new ItemB("bbb", "b22") };

        initPicklists(lstItemA, lstItemB);
    }

    private void initPicklists(IEnumerable<ItemA> lstItemA, IEnumerable<ItemB> lstItemB)
    {
        this.m_lstAllItemB = lstItemB;

        this.m_lstItemAContext.Clear();
        lstItemA.ToList().ForEach(a => this.m_lstItemAContext.Add(a));
    }

    #region Control event handlers

    private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ComboBox ddlSender = (ComboBox)sender;
        ItemA itemaSelected = (ItemA)ddlSender.SelectedItem;

        var lstNewItemB = this.m_lstAllItemB.Where(b => b.KeyA == itemaSelected.Key);

        this.m_lstItemBContext.Clear();
        lstNewItemB.ToList().ForEach(b => this.m_lstItemBContext.Add(b));
    }

    private void comboBox2_?(object sender, ?EventArgs e)
    {
        // disable ComboBox  if empty
    }
    #endregion Control event handlers
}

А это мои классы данных:

class ItemA
{
    public string Key { get; set; }

    public ItemA(string sKey)
    {
        this.Key = sKey;
    }
}

class ItemB
{
    public string KeyA { get; set; }

    public string Name { get; set; }

    public ItemB(string sKeyA, string sName)
    {
        this.KeyA = sKeyA;
        this.Name = sName;
    }
}

Таким образом, всякий раз, когда элемент выбирается в comboBox1, соответствующие элементы должны отображаться в comboBox2. Это работает с текущим кодом, хотя я не уверен, идеален ли мой способ повторного заполнения соответствующей коллекции ObservableCollection.

Чего я не смог добиться, так это на самом деле реагировать на изменения в базовой коллекции comboBox2, например, для деактивации элемента управления, когда список пуст (т.е. когда в comboBox1 выбран «ccc»).

Конечно, я могу использовать обработчик события для события CollectionChanged ObservableCollection, и это будет работать в этом примере, но в более сложном сценарии, где DataContext ComboBox может измениться на совершенно другой объект (и, возможно, назад), это означало бы двойную зависимость - мне всегда приходилось бы переключать не только DataContext, но и обработчики событий назад и вперед. Мне это не кажется правильным, но я, вероятно, просто нахожусь на совершенно неверном пути по этому поводу.

По сути, я ищу событие, запускающее элемент управления, а не базовый список; не ObservableCollection, объявляющая «мое содержимое изменилось», а ComboBox, говорящий мне «что-то счастливое для моих предметов».

Что мне нужно сделать, или где я должен исправить свое восприятие всей концепции?

Ответы [ 3 ]

2 голосов
/ 23 мая 2011

По сути, я ищу событие, запускающее элемент управления, а не базовый список;не ObservableCollection, объявляющая «мое содержимое изменилось», а ComboBox, говорящий мне «что-то более удачное для моих предметов»

, если вы хотите использовать шаблон MVVM, тогда я бы сказал НЕТ.не элемент управления должен предоставлять информацию, но ваша модель представления должна.

Выполнение ObservableCollection является хорошим шагом на первый взгляд.в вашем случае я хотел бы создать только один список с ItemA и добавить новое свойство List типа ItemB к ItemA.

class ItemA
{
public string Key { get; set; }

public ItemA(string sKey)
{
    this.Key = sKey;
}

public IEnumerable<ItemB> ListItemsB { get; set;}

}

я предполагаю, что ItemA является родителем?

class ItemB
{

public string Name { get; set; }

public ItemB(string sName)
{
    this.Name = sName;
}
}

у вас есть коллекция ItemA, и каждый ItemA имеет свой собственный список зависящих от ItemB.

<ComboBox x:Name="cbo_itemA" ItemsSource="{Binding ListItemA}" DisplayMemberPath="Key"/>
<ComboBox ItemsSource="{Binding ElementName=cbo_itemA, Path=SelectedItem.ListItemsB}"
          DisplayMemberPath="Name" />
2 голосов
/ 23 мая 2011

Вот более чистый (возможно, не очень оптимизированный) способ добиться этого, не затрагивая вашу бизнес-модель и используя ViewModel и XAML только, когда это возможно:

Просмотр модели:

public class WindowViewModel : INotifyPropertyChanged
{
    private ItemA selectedItem;


    private readonly ObservableCollection<ItemA> itemsA = new ObservableCollection<ItemA>();
    private readonly ObservableCollection<ItemB> itemsB = new ObservableCollection<ItemB>();
    private readonly List<ItemB> internalItemsBList = new List<ItemB>();


    public WindowViewModel()
    {
        itemsA = new ObservableCollection<ItemA> { new ItemA("aaa"), new ItemA("bbb"), new ItemA("ccc") };
        InvokePropertyChanged(new PropertyChangedEventArgs("ItemsA"));

        internalItemsBList = new List<ItemB> { new ItemB("aaa", "a11"), new ItemB("aaa", "a22"), new ItemB("bbb", "b11"), new ItemB("bbb", "b22") };

    }

    public ObservableCollection<ItemA> ItemsA
    {
        get { return itemsA; }
    }

    public ItemA SelectedItem
    {
        get { return selectedItem; }

        set
        {
            selectedItem = value;

            ItemsB.Clear();
            var tmp = internalItemsBList.Where(b => b.KeyA == selectedItem.Key);
            foreach (var itemB in tmp)
            {
                ItemsB.Add(itemB);
            }


            InvokePropertyChanged(new PropertyChangedEventArgs("SelectedItem"));
        }
    }

    public ObservableCollection<ItemB> ItemsB
    {
        get { return itemsB; }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void InvokePropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, e);
    }
}

Код сзади:

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

и XAML:

 <StackPanel>
    <ComboBox Name="comboBox1" ItemsSource="{Binding ItemsA}" DisplayMemberPath="Key" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"  />
    <ComboBox Name="comboBox2" ItemsSource="{Binding ItemsB}" DisplayMemberPath="Name">
        <ComboBox.Style>
            <Style TargetType="{x:Type ComboBox}">
                <Setter Property="IsEnabled" Value="true"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ItemsB.Count}" Value="0">
                        <Setter Property="IsEnabled" Value="false"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ComboBox.Style>
    </ComboBox>
</StackPanel>

Копирование-вставка это должно работать.

Несколько случайных мыслей:

1) в WPF старайтесь всегда использовать шаблон MVVM и никогда не помещать код в файлы кода для обработчиков событий.Для действий пользователя (например, нажатия кнопок) используйте шаблон Команды.Что касается других действий пользователя, для которых команды недоступны, подумайте как можно больше об «привязке»: вы можете многое сделать, поскольку можете перехватывать события из представления в установщиках свойств виртуальной машины (в вашем примере я использую SelectedItem свойство setter).

2) Используйте XAML столько, сколько сможете.Инфраструктура WPF обеспечивает очень мощную систему привязки и триггеров (в вашем примере для включения комбобокса не требуется никакой строки C #).

3) ObservableCollection сделаны доступными для модели представления представлению.через связывание.Они также предназначены для использования вместе с их событием CollectionChanged, которое вы можете обрабатывать в модели представления.Воспользуйтесь этим (в вашем примере я играю с коллекцией Observable на виртуальной машине, где это должно произойти, и любые изменения в коллекции отражаются в представлении через DataBinding).

Надеемся, это поможет!

0 голосов
/ 23 мая 2011

Вам нужна коллекция ключей?Если нет, то я бы предложил создать его динамически из элементов путем группировки через CollectionView:

private ObservableCollection<object> _Items = new ObservableCollection<object>()
{
    new { Key = "a", Name = "Item 1" },
    new { Key = "a", Name = "Item 2" },
    new { Key = "b", Name = "Item 3" },
    new { Key = "c", Name = "Item 4" },
};
public ObservableCollection<object> Items { get { return _Items; } }
<StackPanel>
    <StackPanel.Resources>
        <CollectionViewSource x:Key="ItemsSource" Source="{Binding Items}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Key"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </StackPanel.Resources>
    <StackPanel.Children>
        <ComboBox Name="keyCb" ItemsSource="{Binding Source={StaticResource ItemsSource}, Path=Groups}" DisplayMemberPath="Name"/>
        <ComboBox ItemsSource="{Binding ElementName=keyCb, Path=SelectedItem.Items}" DisplayMemberPath="Name"/>
    </StackPanel.Children>
</StackPanel>

Первый ComboBox показывает ключи, которые генерируются путем группировки поKey -свойство, второе связывается с подэлементами выбранного элемента в первом поле со списком, показывая Name элемента.

Также см. CollectionViewGroup ссылку вНа первом этапе я использую Name, во втором - Items.

Конечно, вы можете создавать эти группы ключей как вручную, так и вложив элементы в ключевой объект.

...