I have a Overlay over my WPF Applicaiton, it shows some boarders as "context sensitive help". The boarders should now overrule the parent background and show the content behind (some kind of a view Port through the background).
The Controls look like this without Overlay:
With the Overlay Activated it looks like this:
The Overlay is a Usercontrol containing a ListBox of Items it should supply a boarder to. The ListBoxPanel is a Canvas and the ListBoxItems are the Boarders(Buttons) you can see, which are moved over the UIElements they should surround using a ItemContainerStyle.
The Overlay looks like this:
<ItemsControl ItemsSource="{Binding HelpItems}" KeyboardNavigation.TabNavigation="Cycle" IsTabStop="True"
helpers:FocusHelper.FocusOnLoad="True" FocusVisualStyle="{StaticResource EmptyFocusVisual}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Button Command="{Binding ShowPopupCommand}" Background="Transparent" BorderThickness="2">
<Button.Style>
<Style>
<Setter Property="Button.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
<Setter Property="FrameworkElement.Width" Value="{Binding Width}" />
<Setter Property="FrameworkElement.Height" Value="{Binding Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
I want the overlay to be transparent inside the boarder, while keeping the semitransparent dimming background on the list box, in other words the empty space of my ListBox should be Gray. Is there any easy way I can get the Light Blue border to show the Content behind the panel without the semi transparent background of my Overlay?
This is the target result:
I did as well try to create a Opacity filter but it is the wrong way around. And it does not seem there is a easy way to invert a opacity filter.
Console,
Ok we have "to make some holes in the ice".
So here is a custom control : OverlayWithGaps
that draws itself, with a given background that can be semi transparent.
OverlayWithGaps
has a Rect Collection that represents the gaps :
public ObservableCollection<Rect> Gaps
{
get { return (ObservableCollection<Rect>)GetValue(GapsProperty); }
set { SetValue(GapsProperty, value); }
}
private static FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
null
);
public static readonly DependencyProperty GapsProperty =
DependencyProperty.Register("Gaps", typeof(ObservableCollection<Rect>), typeof(OverlayWithGaps), fpm);
With AffectsRender
, if that dependency property changes redrawing will happen.
Here is the drawing function :
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
if (Gaps != null && Gaps.Count > 0)
{
Geometry newGeometry = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
foreach (var gap in Gaps)
// remove each rectangle of the global clipping rectangle :
// we make "a hole in the ice"
newGeometry = Geometry.Combine(newGeometry,
new RectangleGeometry(gap),
GeometryCombineMode.Exclude,
transform: null);
// When the geometry is finished, we make the hole
dc.PushClip(newGeometry);
}
dc.DrawRectangle(Background, null, new Rect(0, 0, ActualWidth, ActualHeight));
}
EDIT
3. Rectangles are provided from the ItemsControl ListViewItems
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// overlay is the OverlayWithGaps instance
// in the window
overlay.Gaps = new ObservableCollection<Rect>(
itemsControl1.FindAllVisualDescendants()
.OfType<Grid>()
.Select(grid => {
Point relativePoint = grid.TransformToAncestor(this)
.Transform(new Point(0, 0));
return new Rect(relativePoint.X,
relativePoint.Y,
grid.ActualWidth,
grid.ActualHeight
);
})
);
}
Note that I select Grids in the LINQ query, because they are in the DataTemplate.
But the Linq query could select nearly anything (by name, ...)
The FindAllVisualDescendants()
extension function can be found here :
Datagrid templatecolumn update source trigger explicit only updates first row
Best coding