Search code examples
c#wpfxamllistboxitemspaneltemplate

Two overlapping ListBoxes. Selection problem


I'm trying to display shapes in two separate Listboxes occupying the same space. I've set the Background to both Transparent and {x:Null} but the mouseclick is still getting captured by the topmost Listbox so I can't select any shapes from the underlying ListBox.

Here is some sample code reproducing the problem.

<Grid>
    <!-- ListBox 1 -->
    <ListBox Background="{x:Null}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas Background="{x:Null}"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Background="Transparent">
                    <Ellipse Width="100" Height="100" Stroke="Blue" StrokeThickness="10"/>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
        1
    </ListBox>

    <!-- ListBox 2 -->
    <ListBox Background="{x:Null}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas Background="{x:Null}"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Canvas.Left" Value="100"/>
                <Setter Property="Canvas.Top" Value="100"/>
            </Style>
        </ListBox.ItemContainerStyle>
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Ellipse Width="100" Height="100" Stroke="Blue" StrokeThickness="10"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
        1
    </ListBox>
</Grid>

This is how I solved this problem for now but I'm more than open for other suggestions :) I enabled hittesting on the Grid and disabled it for both ListBoxes. Then I did the hittesting in the event handler instead

<Grid MouseDown="Grid_MouseDown"
      IsHitTestVisible="True"
      Background="Transparent">

    <!-- ListBox 1 -->
    <ListBox Background="Transparent"
             IsHitTestVisible="True"
             ..>
    </ListBox>

    <!-- ListBox 2 -->
    <ListBox Background="Transparent"
             IsHitTestVisible="True"
             ..>
    </ListBox>
</Grid>

Event handler and hittesting

private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
    Grid grid = sender as Grid;
    Point ptCurrent = e.GetPosition(grid);
    VisualTreeHelper.HitTest(grid, null, new HitTestResultCallback(HitTestCallback), new PointHitTestParameters(ptCurrent));
}
public HitTestResultBehavior HitTestCallback(HitTestResult htrResult)
{
    ListBoxItem listBoxItem = GetVisualParent<ListBoxItem>(htrResult.VisualHit);
    if (listBoxItem != null)
    {
        listBoxItem.IsSelected = true;
        return HitTestResultBehavior.Stop;
    }
    return HitTestResultBehavior.Continue;
}
public T GetVisualParent<T>(object child) where T : Visual
{
    DependencyObject c = child as DependencyObject;
    while ((c != null) && !(c is T))
    {
        c = VisualTreeHelper.GetParent(c);
    }
    return c as T;
}

Solution

  • You can bind multiple sets of data to a single ListBox by using a CompositeCollection as the items source:

    <ListBox ...>
      <ListBox.ItemsSource>
        <CompositeCollection>
          <CollectionContainer Collection={Binding ...}" />
          <CollectionContainer Collection={Binding ...}" />
        </CompositeCollection>
      </ListBox.ItemsSource>
    </ListBox>
    

    You can then use data templates to control the appearance of different data types. You can either use implicit data templates or you can use an ItemTemplateSelector.

    For more information about using and binding to a CompositeCollection, see this resource: How do you bind a CollectionContainer to a collection in a view model?