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
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
Any idea for solving the above issues?
or Any different idea for this control?
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>
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: