1
votes

One line summary of my situation: when I select a detail item, the master is still unselected, so I get wrong detail item.

Assume that two ViewModel MasterList and DetailList are ObservableCollection, and try to make Explorer-like GUI. Left panel has master-detail listboxes, and right panel will show one of the selected detail item. there is no need to show master information in right panel.

On my left panel, ListBox controls are coded as below.

<ListBox x:Name="listBoxMaster" ItemsSource="{Binding Path=MasterList}" SelectionMode="Extended"
            IsSynchronizedWithCurrentItem="True">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <ListBox x:Name="listBoxDetail" ItemsSource="{Binding Path=DetailList}" IsSynchronizedWithCurrentItem="True" />
        </DataTemplate>
</ListBox.ItemTemplate>

Note that I set IsSynchronizedWithCurrentItem="True" to both ListBox controls.

and in right panel, the selected detail item information will be displayed with the Binding as below. Let's simplify the detail item class called DetailItemClass has Name and Number properties.

Trial 1

<WrapPanel Name="wrapPanel1" DataContext="{Binding ElementName=listBoxMaster, Path=SelectedItem}">
    <StackPanel DataContext="{Binding Path="DetailList/">
        <TextBox Text="{Binding Path=Name}" />
        <TextBox Text="{Binding Path=Number}" />
    </StackPanel>
</WrapPanel>

The weired GUI problem is happend. if we have.

  • Master1 has detail 1,2,3.
  • Master2 has detail 4,5,6.

When I select M1-D2, it works. after that, when I select detail M2-D4, it works. BUT, after that, when I select M1-D2 again, it does NOT works! the pre-selected item may not update its master to be selected. Moreover, sometimes M2-D5 selection gives M1-D2 in right panel. weired.

Trial 2

<WrapPanel Name="wrapPanel1" DataContext="{Binding Path=MasterList/DetailList/}">
    <StackPanel>
        <TextBox Text="{Binding Path=Name}" />
        <TextBox Text="{Binding Path=Number}" />
    </StackPanel>
</WrapPanel>

Just not works. AlThough I set IsSynchronizedWithCurrentItem to true, but failed. I don't know why.

Trial 3

I just get rid of DataContext XAML bindings and use event trigger at code-behind as below.

    private void listBoxMaster_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count == 1)
        {
            if (e.AddedItems[0] is DetailItemClass)
            {
                var element = (DetailItemClass)e.AddedItems[0];
                wrapPanel1.DataContext = element;
            }
        }
    }

It works very well, but it does not use XAML Binding.

Could you teach me proper binding expression?

2
Why do you use ListBox of ListBoxes? It isn't cool and won't work. There is the TreeView control for such situations.vortexwolf
Finally I solved with TreeView control.Youngjae

2 Answers

0
votes

Did you try:

<WrapPanel Name="wrapPanel1" DataContext="{Binding ElementName=listBoxMaster, Path=SelectedItem}">
    <StackPanel>
        <TextBox Text="{Binding Path=DetailList.Name}" />
        <TextBox Text="{Binding Path=DetailList.Number}" />
    </StackPanel>
</WrapPanel>

or

<WrapPanel Name="wrapPanel1">
    <StackPanel>
        <TextBox Text="{Binding ElementName=listBoxMaster, Path=SelectedItem.DetailList.Name}" />
        <TextBox Text="{Binding ElementName=listBoxMaster, Path=SelectedItem.DetailList.Number}" />
    </StackPanel>
</WrapPanel>

These are to ensure whether the DataContext is actually cascading.

0
votes

You shouldn't use multiple listboxes because there will be many issues related to the multiple selection. For example, you can select the first listbox, but the item from the second.

multiple list boxes fail

Replace the outer list box by the ItemsControl class so that it doesn't receive the selection and isn't focusable.

Also add the single property to the root model which contains the single selected item for all lists. I've reproduced your hierarchy by using very simple models and in this example this property is called SelectedDetailItem:

public class MasterListViewModel : INotifyPropertyChanged
{
    public ObservableCollection<DetailListViewModel> MasterList { get; set; }

    private ItemViewModel _selectedDetailItem;

    public ItemViewModel SelectedDetailItem
    {
        get { return _selectedDetailItem; }
        set
        {
            _selectedDetailItem = value;
            OnPropertyChanged("SelectedDetailItem");
        }
    }
}

public class DetailListViewModel
{
    public ObservableCollection<ItemViewModel> DetailList { get; set; }
}

public class ItemViewModel
{
    public string Title { get; set; }
}

And bind this property to both controls:

<ItemsControl x:Name="listBoxMaster" ItemsSource="{Binding Path=MasterList}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ListBox x:Name="listBoxDetail" ItemsSource="{Binding Path=DetailList}" DisplayMemberPath="Title" 
                        SelectedItem="{Binding DataContext.SelectedDetailItem, Mode=TwoWay, ElementName=listBoxMaster}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

<WrapPanel Name="wrapPanel1" DataContext="{Binding SelectedDetailItem}" Grid.Column="1">
    <TextBox Text="{Binding Path=Title}" />
</WrapPanel>