Search code examples
c#wpfz-indexitemscontrol.net-core-3.1

Lock zIndex of a ContentPresenter Upon Mouse Enter or Drag Complete


I need to lock the Z-order of a canvas/content control after it is dragged by a Thumb.

In the below image, the "Joe Smith" pops above the others the other two ellipses while the the mouse over is active. Once the drag stops and the mouse moves out, it drops back to its value.

I am unable to find a solution within the design I have shown below to keep it locked above the others.

Joe moves in front, only to be dropped back behind Jenny.


Minimal Reproducible Example

I have created a code gist of all code that contains the xaml, the thumb class and the people class. All one has to do is create a .Net Core WPF app and drop in the snippets of code and set name spacing in Xaml as needed.


Design There is an ItemsControl which has DataTemplate defined with a Canvas. Each content in the ItemsControl has ContentControl which has a Thumb as applied by a style.

Another style trigger of the mouse entering the ContentPresenter has the content temporarily pushed in zIndex above the others by setting the content's internal Grid's zIndex to 1.

How to stick that zIndex?

Xaml

<ItemsControl Margin="10" ItemsSource="{Binding Source={StaticResource People}}">

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Rows="1" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Canvas>
                <ContentControl  Width="100" Height="100" >
                    <Grid>
                        <Ellipse Fill="Silver">
                            <Ellipse.Effect>
                                <DropShadowEffect Color="Black" Direction="320"  ShadowDepth="6"  Opacity="0.5"/>
                            </Ellipse.Effect>
                        </Ellipse>
                        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                            <TextBlock Margin="3,3,3,0" Text="{Binding Path=First}"/>
                            <TextBlock Margin="3,0,3,7" Text="{Binding Path=Last}"/>
                        </StackPanel>
                    </Grid>
                </ContentControl>
            </Canvas>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="{x:Type ContentPresenter}">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Grid.ZIndex" Value="1"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

See the Gist for all supporting classes and styles to reproduce


Actual Design

The ultimate goal is to have a panel of images of a set size, when the user grabs the thumb the image it will move forward and lock above the others. I say this in-case there is another way to do that which could provide an answer above the minimal example design.


Solution

  • In my ItemsControl I changed the ItemsPanelTemplate to be a Canvas such as

    <ItemsPanelTemplate>
        <Canvas x:Name="MainCanvas" />
    </ItemsPanelTemplate>
    

    Which when looking at the Visual Tree where the user was clicking the ContentControl, it had a parent of a Canvas that had a ContentPresenter with that top level Canvas such as (see named MainCanvas):

    Visual tree

    I changed the ContentControl to have a MouseEnter event :

    <ContentControl  Width="100" Height="100" MouseEnter="EnterMouse">
       <Grid>
           <Ellipse Fill="Silver">
    

    In that method I needed to find the named "MainCanvas" Canvas and enumerate all of its children the ContentPresenters and extract the max ZIndex and then set my ContentControl (shown in blue above) to that ZIndex value plus 1.

    Here is the code behind where extract the necessary the parents:

    private void EnterMouse(object sender, MouseEventArgs e)
    {
        if (sender is ContentControl cc)
        {
            var cpParent = FindParent<ContentPresenter>(cc);
            var p2 = FindParent<Canvas>(cpParent);
    
            var max = p2.Children.Cast<UIElement>().Max(control => Panel.GetZIndex(control));
            Panel.SetZIndex(cpParent, max + 1);
        }
    }
    
    private T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        DependencyObject immediateParent = VisualTreeHelper.GetParent(child);
    
        T parent = immediateParent as T;
    
        return parent ?? FindParent<T>(immediateParent);
    }