How to recursively select the parent of an AdornedElement when returning null

OK, so the answer I got earlier here works really great. However, in known issues, he mentions the following:

Also, it won't work in UserControls right away, because it AdornerLayer.GetAdornerLayer(AdornedElement)

will return null

inside UserControls. This could be easily eliminated by looking for the AdornerLayer

Parent UserControl

(or the parent's parent, recursively). There are functions for this.

So, I'm using the code fine for the most part, however I ran into a problem when I try to use it on an element in a tabcontrol. Instead of having the desired effect, the blur effect is applied only to the TabItem, instead of the entire window. Also, the contents of the tabItem seem to print multiple times like a visual brush. Here's an example. The custom decorator is wrapped around a stacking panel containing 2 text blocks, one containing "GP:" and the other containing a number. Here are the before and after snapshots of what it looks like when applied on a tab element:

BEFORE

AFTER

So how would you fix this?

I'll post my code snippets here, as I modified them a bit after the answer (although not the way it was "broken")

Here's the XAML, with a decorator:

<models:TipFocusDecorator IsOpen="{Binding TutorialBoolGP}" TipHead="{Binding TutorialTitle}" TipText="{Binding TutorialDescription}" TipPos="{Binding TutorialPosition}">
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Background="Transparent">
        <TextBlock Text="GP: " />
        <TextBlock Text="{Binding PlayerGP, Converter={StaticResource IntToComma}}" />
    </StackPanel>
</models:TipFocusDecorator>

      

Decorator:

public class TipFocusDecorator : Decorator
{

    public bool IsOpen
    {
        get { return (bool)GetValue(IsOpenProperty); }
        set { SetValue(IsOpenProperty, value); }
    }
    // Using a DependencyProperty as the backing store for Open.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsOpenProperty =
        DependencyProperty.Register("IsOpen", typeof(bool), typeof(TipFocusDecorator),
        new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsOpenPropertyChanged));


    public string TipText
    {
        get { return (string)GetValue(TipTextProperty); }
        set { SetValue(TipTextProperty, value); }
    }
    // Using a DependencyProperty as the backing store for TipText.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TipTextProperty =
        DependencyProperty.Register("TipText", typeof(string), typeof(TipFocusDecorator), new UIPropertyMetadata(string.Empty));


    public string TipHead
    {
        get { return (string)GetValue(TipHeadProperty); }
        set { SetValue(TipHeadProperty, value); }
    }
    // Using a DependencyProperty as the backing store for TipText.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TipHeadProperty =
        DependencyProperty.Register("TipHead", typeof(string), typeof(TipFocusDecorator), new UIPropertyMetadata(string.Empty));


    public string TipPos
    {
        get { return (string)GetValue(TipPosProperty); }
        set { SetValue(TipPosProperty, value); }
    }
    // Using a DependencyProperty as the backing store for TipPos.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TipPosProperty =
        DependencyProperty.Register("TipPos", typeof(string), typeof(TipFocusDecorator), new UIPropertyMetadata(string.Empty));


    public bool HasBeenShown
    {
        get { return (bool)GetValue(HasBeenShownProperty); }
        set { SetValue(HasBeenShownProperty, value); }
    }

    // Using a DependencyProperty as the backing store for HasBeenShown.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty HasBeenShownProperty =
        DependencyProperty.Register("HasBeenShown", typeof(bool), typeof(TipFocusDecorator), new UIPropertyMetadata(false));

    private static void IsOpenPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var decorator = sender as TipFocusDecorator;

        if ((bool)e.NewValue)
        {
            if (!decorator.HasBeenShown)
                decorator.HasBeenShown = true;

            decorator.Open();
        }

        if (!(bool)e.NewValue)
        {
            decorator.Close();
        }
    }

    TipFocusAdorner adorner;

    protected void Open()
    {
        adorner = new TipFocusAdorner(this.Child);
        var adornerLayer = AdornerLayer.GetAdornerLayer(this.Child);
        adornerLayer.Add(adorner);
        TutorialTip tip = new TutorialTip(TipHead,TipText,TipPos);
        tip.Owner = Application.Current.MainWindow;
        double width = tip.Width;
        double height = tip.Height;
        Point position = this.Child.PointToScreen(new Point(0d, 0d));
        switch (TipPos)
        {
            case "Bottom":
                position.X += (this.Child.RenderSize.Width / 2) - (width / 2);
                position.Y += this.Child.RenderSize.Height + 10;
                break;
            case "Top":
                position.X += (this.Child.RenderSize.Width / 2) - (width / 2);
                position.Y += -height - 10;
                break;
            case "Left":
                position.X += -width - 10;
                position.Y += (this.Child.RenderSize.Height / 2) - (height / 2);
                break;
            case "Right":
                position.X += this.Child.RenderSize.Width + 10;
                position.Y += (this.Child.RenderSize.Height / 2) - (height / 2);
                break;
        }
        tip.Left = position.X;
        tip.Top = position.Y;
        tip.ShowDialog();
        //MessageBox.Show(TipText + position);  // Change for your custom tip Window
        IsOpen = false;
    }

    protected void Close()
    {
        var adornerLayer = AdornerLayer.GetAdornerLayer(this.Child);
        adornerLayer.Remove(adorner);
        adorner = null;
    }

}

      

And finally, Adorner:

public class TipFocusAdorner : Adorner
{
    public TipFocusAdorner(UIElement adornedElement)
        : base(adornedElement)
    {
    }

    protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        var root = Window.GetWindow(this);
        var adornerLayer = AdornerLayer.GetAdornerLayer(AdornedElement);
        var presentationSource = PresentationSource.FromVisual(adornerLayer);
        Matrix transformToDevice = presentationSource.CompositionTarget.TransformToDevice;

        var sizeInPixels = transformToDevice.Transform((Vector)adornerLayer.RenderSize);
        RenderTargetBitmap rtb = new RenderTargetBitmap((int)(sizeInPixels.X), (int)(sizeInPixels.Y), 96, 96, PixelFormats.Default);

        var oldEffect = root.Effect;
        var oldVisibility = AdornedElement.Visibility;
        root.Effect = new BlurEffect();
        AdornedElement.SetCurrentValue(FrameworkElement.VisibilityProperty, Visibility.Hidden);
        rtb.Render(root);
        AdornedElement.SetCurrentValue(FrameworkElement.VisibilityProperty, oldVisibility);
        root.Effect = oldEffect;

        drawingContext.DrawImage(rtb, adornerLayer.TransformToVisual(AdornedElement).TransformBounds(new Rect(adornerLayer.RenderSize)));
        drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(22, 0, 0, 0)), null, adornerLayer.TransformToVisual(AdornedElement).TransformBounds(new Rect(adornerLayer.RenderSize)));
        drawingContext.DrawRectangle(new VisualBrush(AdornedElement) { AlignmentX = AlignmentX.Left, TileMode = TileMode.None, Stretch = Stretch.None },
            null,
            AdornedElement.RenderTransform.TransformBounds(new Rect(AdornedElement.RenderSize)));
    }
}

      

+3


source to share


1 answer


AdornerLayer.GetAdornerLayer Retrieves the adorner layer above (parent) of this element. So it is not necessarily true that this will return null inside the UserControl. As long as the AdornerLayer exists over the UserControl, it will return this. The default window creates an AdornerLayer, but only after it's loaded. Actually I checked your code using simple

<Grid x:Name="Container">
    <DockPanel>
        <TextBlock DockPanel.Dock="Top">Outside of Tab</TextBlock>
        <TabControl x:Name="TabControl">
            <TabItem Header="Here">
                <local:UserControlContainingTipFocus/>
            </TabItem>
        </TabControl>
    </DockPanel>
</Grid>

      

I could not reproduce the problem. The blur is applied to everything inside the window.

So, in your case, there should be a parent UserControl that the AdornerLayer created. I am assuming TabControl or TabItem. You can use Snoop to check.

But not to worry, you can create your own AdornerLayer. Just put the element you want to blur inside the AdornerDecorator.



<Window >
    <AdornerDecorator>
        <Grid x:Name="Container">
            <DockPanel>
                <TextBlock DockPanel.Dock="Top">Outside of adorner</TextBlock>
                    <TabControl x:Name="TabControl">
                        <TabItem Header="Here">
                            <local:TestControl></local:TestControl>
                        </TabItem>
                    </TabControl>
            </DockPanel>
        </Grid>
    </AdornerDecorator>
</Window>

      

Then change each call to AdornerLayer.GetAdornerLayer. Instead of passing in the original element, go through the container you want to blur. In my example, this is either Grid or AdornerDecorator.

var root = Window.GetWindow(this);
var blurContainer = (Visual) root.Content;
var adornerLayer = AdornerLayer.GetAdornerLayer(blurContainer);

      

The above code uses Window.GetWindow and accesses it (first child). But you can easily create a property in TipFocusAdorner / Decorator to specify which element to pass AdornerLayer.GetAdornerLayer

+5


source







All Articles