Search code examples
c#xamluwpwin-universal-app

Creating a control that covers the whole area except a particular area


I want to create a control that covers the whole area except a particular area. This particular area will be in an ellipse/rectangle shape. Something like the below concept

enter image description here

My idea is to use paths. Below is my code for the path.

<Path Name="ShowcasePath" StrokeThickness="1" IsHitTestVisible="True">
    <Path.Data>
        <GeometryGroup>
            <EllipseGeometry RadiusX="100" RadiusY="50"/>
            <RectangleGeometry/>
        </GeometryGroup>
    </Path.Data>
</Path>

Centre of the EllipseGeometry and Rect of the RectangleGeometry will be set in code behind.

This method has two problems

  1. The fill colour is semi-transparent even with no alpha value. The final colour will be transparent white. So it's not a real problem.
  2. The item inside the ellipse should be fully functional and items outside the ellipse should be non-functional.

enter image description here

Any idea for solving the above issues?

or Any different idea for this control?


Solution

  • I think you were already pretty close with your example, at least I can't find anything wrong with it. If you try the following code, you will see it does exactly what you described:

    <Grid>
        <Button Content="Clickable" HorizontalAlignment="Left" 
          Margin="113,90,0,0" VerticalAlignment="Top" Width="75"/>
        <Button Content="Non-Clickable" HorizontalAlignment="Left" 
          Margin="272,90,0,0" VerticalAlignment="Top" Width="75"/>
        <Path Name="ShowcasePath" StrokeThickness="1" Fill="#88ff0000" IsHitTestVisible="True">
            <Path.Data>
                <GeometryGroup FillRule="EvenOdd">
                    <EllipseGeometry Center="150,100" RadiusX="100" RadiusY="50"/>
                    <RectangleGeometry Rect="0,0,500,500"/>
                </GeometryGroup>
            </Path.Data>
        </Path>
    </Grid>
    

    enter image description here

    Keep in mind however, that users could still change the focused element with Tab and activate elements with Space or Return, even if you set IsHitTestVisible to false.

    The "focus the ellipse on an element" functionality can easily be taken care of like this:

    public void FocusOnElement(FrameworkElement element)
    {
        Point center = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
        Point newPosition = element.TranslatePoint(center, ShowcasePath);
        ellipseGeometry.Center = newPosition;
    }
    

    The enable/disable all other elements functionality would best be taken care of in your viewmodel, or if you don't have one, kind of like this:

    private void DiableAllButThisAndItsParents(FrameworkElement thisElement)
    {
        List<FrameworkElement> hierarchy = FindParents(thisElement).ToList();
    
        foreach (FrameworkElement element in hierarchy)
        {
            element.IsEnabled = true;
    
            if (ReferenceEquals(element, thisElement)) continue;
    
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
            {
                var child = VisualTreeHelper.GetChild(element, i);
                if (!(child is FrameworkElement childElement)) continue;
    
                childElement.IsEnabled = hierarchy.Contains(childElement);
            }
        }
    }
    
    private IEnumerable<FrameworkElement> FindParents(FrameworkElement element)
    {
        DependencyObject current = element;
    
        while (current != null)
        {
            if (current is FrameworkElement)
                yield return (FrameworkElement) current;
            current = VisualTreeHelper.GetParent(current);
        }
    }
    

    Put that all together and it should look something like this:

    enter image description here