2

I'm writing an application. I'd like to have a tutorial mode where the screen of the application darkens and single features of the application are allowed to shine through. On my actual app, I have many datagrids and listboxes so I thought the easiest way to accomplish this might be to overlay the entire screen with a semi-transparent Panel and then somehow use the opacity mask to see through the mask in certain areas to highlight them in my application while the tutorial explains what they do. The only problem is, I can't get the opacity mask to work with a visualbrush and picking out specific objects like a Listbox. Below is an example program I wrote to demonstrate simply what I am trying to do.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <TextBlock Text="Two different text listboxes"/>

    <ListBox Grid.Row="1" Name="myListBox1" Grid.Column="0" VerticalAlignment="Top">
        <ListBoxItem Content="Item 1" Margin="3" Background="Tan"/>
        <ListBoxItem Content="Item 2" Margin="3" Background="Aqua"/>
        <ListBoxItem Content="Item 3" Margin="3" Background="Gold"/>
    </ListBox>

    <ListBox Grid.Row="1" Name="myListBox2" Grid.Column="1" VerticalAlignment="Top">
        <ListBoxItem Content="Item A" Margin="3" Background="Magenta"/>
        <ListBoxItem Content="Item B" Margin="3" Background="Chartreuse"/>
        <ListBoxItem Content="Item C" Margin="3" Background="Chocolate"/>
        <ListBoxItem Content="Item D" Margin="3" Background="Pink"/>
    </ListBox>


    <Button Grid.Row="2" Height="40" Margin="5" Content="Click me" Grid.ColumnSpan="2"/>

    <DockPanel Grid.RowSpan="3" Background="#55000000" Grid.ColumnSpan="2">
        <DockPanel.OpacityMask>
            <VisualBrush Visual="{Binding ElementName=myListBox1}"/>
        </DockPanel.OpacityMask>
    </DockPanel>
</Grid>

Can anyone give me any tips on how to simply accomplish this mask?

Jon Dosmann
  • 667
  • 7
  • 20
  • Obviously in the long term I will need to be able to modify which control is using the mask, but for now, I'd be happy just to know how to get the mask working. – Jon Dosmann Mar 05 '15 at 20:38
  • I'll show you a nifty trick using just a couple Rectangles and Clip when I get home later. – Chris W. Mar 05 '15 at 21:28
  • 2
    I think the easiest way to achieve this is to add `Panel.ZIndex="1"` to `myListBox1` – Szabolcs Dézsi Mar 05 '15 at 21:31
  • Oh hey @SzabolcsDézsi has a pretty good idea there too with the setting of the highest z-index put on the object you want to pop over your semi-transparent background. +1 :) – Chris W. Mar 05 '15 at 21:40
  • Yeah, that might actually work... I think I could get that to work in my larger application as well. I'll have to see. Thanks. – Jon Dosmann Mar 05 '15 at 21:43
  • Yea my way involved just having rectangles act as the clip outlines of a background, but I think I like the z-index approach more. – Chris W. Mar 05 '15 at 21:44
  • The problem I'm running into with trying to use Panel.ZIndex on my more complex program is that once you have a listbox nested inside another grid or panel, you can't raise it all the way to the top panel, but only to the top of it's parent panel. – Jon Dosmann Mar 06 '15 at 14:47
  • Oh yea, that would cause an issue, if you want I can still show you my way? However that does remind me why I did it my way before so I feel less ignorant for not thinking of the other suggested way lol. Anyway remember to put @theirname to respond to people or they dont get notified you did so :) – Chris W. Mar 06 '15 at 15:49
  • @ChrisW. I would be interested in hearing your method as well. I couldn't get the Panel.ZIndex to work with my more complex app. – Jon Dosmann Mar 06 '15 at 18:36

1 Answers1

1

So here's an example of how I've done this in the past. I was going to go the extra step and throw together a Storyboard animation to sequence through the property change of Clip on the one object to show you how it can work in your tutorial scenario (which it did quite nicely on the last project I did this on.) except it's Friday and I'm running late leaving the office already. :)

PS: Forgot to mention that originally I just put named rectangles collapsed over top of each of the controls I wanted to show off with a -5 margin. Once their visibility was toggled to visible and they gave back Rectangle.RenderedGeometry you can grab the Rect with a binding for your geometry with just xaml.

Or... If you don't need it dynamic, and you don't mind x layers over your outermost parent. You could always load it up in Blend -> Put Rectangle on top most z-index so it covers everything with an opacity, draw a Rectangle over your highlight area -> Select both -> [From top file menu] Select Object -> Select Path -> Select "Make Compound Path" and, voila you have a shape you can just toggle visibility on and cycle each through a storyboard.

Let me know if you have any questions or if you want me to show you to utilize the concept more, you can manually change Box1, Box2, Box3, etc on the StaticResource on "PresenterForeground" to see the concept in action though.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="350">
    <Window.Resources>
        <!-- These guys are for example, you could change the StaticResource on the Clip of the Rectangle
             below to reflect the changes here with a property change in a storyboard, or from a trigger, whatever -->
        <Geometry x:Key="Box1">M0,0 L280,0 L280,280 L0,280 z M10,10 L130,10 L130,130 L10,130 z</Geometry>
        <Geometry x:Key="Box2">M0,0 L280,0 L280,280 L0,280 z M150,10 L270,10 L270,130 L150,130 z</Geometry>
        <Geometry x:Key="Box3">M0,0 L280,0 L280,280 L0,280 z M10,150 L130,150 L130,270 L10,270 z</Geometry>
        <Geometry x:Key="Box4">M0,0 L280,0 L280,280 L0,280 z M150,150 L270,150 L270,270 L150,270 z</Geometry>
    </Window.Resources>

        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.Resources>
                <Style TargetType="Rectangle">
                    <Setter Property="Width" Value="100"/>
                    <Setter Property="Height" Value="100"/>
                    <Setter Property="Margin" Value="20"/>
                </Style>
            </Grid.Resources>

            <Rectangle Fill="Red"/>
            <Rectangle Grid.Column="1" Fill="Blue"/>
            <Rectangle Grid.Row="1" Fill="Green"/>
            <Rectangle Grid.Row="1" Grid.Column="1" Fill="Orange"/>

            <!-- This guy is our main foreground to cut visibility to everything else -->
            <Rectangle Name="PresenterForeground" Grid.ColumnSpan="2" Grid.RowSpan="2"
                Fill="#77000000" 
                Height="Auto"
                Width="Auto" 
                Margin="0"
                Clip="{StaticResource Box1}"/>

        </Grid>

</Window>

Hope this helps and have a great weekend, cheers!

Chris W.
  • 22,835
  • 3
  • 60
  • 94
  • I really like your solution. Since my windows re-size I'm going to need to rectangle to resize with the controls that they are presenting. I tried to get this to work by binding to a geometry in the same space as the object I want to highlight but it didn't work. Here is basically what I tried: at the object to highlight and at the bottom of the window. What have I done wrong? – Jon Dosmann Mar 08 '15 at 22:03
  • 1
    @JonD Hmm, take the Fill off of the first one since you're just grabbing the Rect, if anything you would probably want it to be Fill="{x:Null}" so that hittestvisibility will pass through that area, I've got a pretty busy week this week but I'll see if I can't get on here a little later to give a little more detailed example, so long as I don't have to re-write everything lol, I wish that proj didn't have an NDA or I'd try to hunt down source and just share it lol, would make it a lot easier. – Chris W. Mar 09 '15 at 15:40