创建一个覆盖除特定区域以外的整个区域的控件
Creating a control that covers the whole area except a particular area
我想创建一个覆盖除特定区域以外的整个区域的控件。该特定区域将呈 ellipse/rectangle 形状。类似于下面的概念
我的想法是使用路径。下面是我的路径代码。
<Path Name="ShowcasePath" StrokeThickness="1" IsHitTestVisible="True">
<Path.Data>
<GeometryGroup>
<EllipseGeometry RadiusX="100" RadiusY="50"/>
<RectangleGeometry/>
</GeometryGroup>
</Path.Data>
</Path>
EllipseGeometry
的Centre
和RectangleGeometry
的Rect
将在后面的代码中设置。
这个方法有两个问题
- 即使没有 alpha 值,填充颜色也是半透明的。最终颜色将是透明的白色。所以这不是一个真正的问题。
- 椭圆内的项目应该功能齐全,椭圆外的项目应该没有功能。
有解决上述问题的办法吗?
或者这个控件有什么不同的想法吗?
您无需尝试获取 "Click-through" 元素,只需将元素(在您的情况下为切换按钮)置于最前面(基本上位于其他所有元素之上,以便可以点击)。
您可以通过以下任一方式执行此操作:
- 更改元素的 ZIndexProperty 使其位于最前面。
YourElement.SetValue(Canvas.ZIndexProperty, index);
删除元素并再次添加到父网格(因此它基本上是最后添加的元素)
parentGrid.Children.Remove(YourElement);
parentGrid.Children.Add(YourElement);
parentGrid.UpdateLayout();
用户与控件成功交互后,您可以将其切换回之前的状态。希望这对您有所帮助。
我认为您的示例已经非常接近了,至少我找不到任何问题。如果您尝试以下代码,您会发现它完全符合您的描述:
<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>
但是请记住,用户仍然可以使用 Tab 更改焦点元素并使用 Space 或激活元素Return,即使您将 IsHitTestVisible
设置为 false
。
"focus the ellipse on an element" 功能可以像这样轻松处理:
public void FocusOnElement(FrameworkElement element)
{
Point center = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
Point newPosition = element.TranslatePoint(center, ShowcasePath);
ellipseGeometry.Center = newPosition;
}
enable/disable 所有其他元素的功能最好在您的视图模型中处理,或者如果您没有,有点像这样:
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);
}
}
把它们放在一起,它应该看起来像这样:
我想创建一个覆盖除特定区域以外的整个区域的控件。该特定区域将呈 ellipse/rectangle 形状。类似于下面的概念
我的想法是使用路径。下面是我的路径代码。
<Path Name="ShowcasePath" StrokeThickness="1" IsHitTestVisible="True">
<Path.Data>
<GeometryGroup>
<EllipseGeometry RadiusX="100" RadiusY="50"/>
<RectangleGeometry/>
</GeometryGroup>
</Path.Data>
</Path>
EllipseGeometry
的Centre
和RectangleGeometry
的Rect
将在后面的代码中设置。
这个方法有两个问题
- 即使没有 alpha 值,填充颜色也是半透明的。最终颜色将是透明的白色。所以这不是一个真正的问题。
- 椭圆内的项目应该功能齐全,椭圆外的项目应该没有功能。
有解决上述问题的办法吗?
或者这个控件有什么不同的想法吗?
您无需尝试获取 "Click-through" 元素,只需将元素(在您的情况下为切换按钮)置于最前面(基本上位于其他所有元素之上,以便可以点击)。 您可以通过以下任一方式执行此操作:
- 更改元素的 ZIndexProperty 使其位于最前面。
YourElement.SetValue(Canvas.ZIndexProperty, index);
删除元素并再次添加到父网格(因此它基本上是最后添加的元素)
parentGrid.Children.Remove(YourElement); parentGrid.Children.Add(YourElement); parentGrid.UpdateLayout();
用户与控件成功交互后,您可以将其切换回之前的状态。希望这对您有所帮助。
我认为您的示例已经非常接近了,至少我找不到任何问题。如果您尝试以下代码,您会发现它完全符合您的描述:
<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>
但是请记住,用户仍然可以使用 Tab 更改焦点元素并使用 Space 或激活元素Return,即使您将 IsHitTestVisible
设置为 false
。
"focus the ellipse on an element" 功能可以像这样轻松处理:
public void FocusOnElement(FrameworkElement element)
{
Point center = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
Point newPosition = element.TranslatePoint(center, ShowcasePath);
ellipseGeometry.Center = newPosition;
}
enable/disable 所有其他元素的功能最好在您的视图模型中处理,或者如果您没有,有点像这样:
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);
}
}
把它们放在一起,它应该看起来像这样: