返回 null 时如何递归 select AdornedElement 的父级

How to recursively select the parent of AdornedElement when returning null

好的,我之前收到的答案 非常有效。然而,在已知问题中,它提到了以下内容:

Also, this won't work inside UserControls right away, because AdornerLayer.GetAdornerLayer(AdornedElement) will return null inside UserControls. This could be easily fixed by looking for the AdornerLayer of the PARENT of the UserControl (or the parent of the parent, recursively). There are functions around to do so.

所以,我大部分时间都在使用代码,但是当我尝试在 tabcontrol.Instead 范围内的元素上使用它时,我遇到了一个问题,该元素具有所需的效果,模糊效果仅适用在 TabItem 中,而不是整个 window。此外,tabItem 的内容似乎作为某种视觉画笔打印了几次。这是一个例子。自定义装饰器包裹在包含 2 个文本块的堆栈面板周围,一个包含 "GP: ",另一个包含数字。这是在选项卡项目中应用此功能时的前后对比图:

那么,我该如何纠正这个问题?

我将 post 我的代码片段放在这里,因为自回答以来我对它们进行了轻微修改(尽管没有 "broken" 这个)

这里是 XAML,装饰器:

<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>

装饰师:

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;
    }

}

最后是装饰者:

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)));
    }
}

AdornerLayer.GetAdornerLayer 检索给定元素 (parent) 上方的装饰层。因此,这不一定会在 UserControl 中 return null。只要 UserControl 之上存在 AdornerLayer,它就会 return。 Window 默认情况下会创建一个 AdornerLayer,但仅在加载后才会创建。 事实上,我用一个简单的方法测试了你的代码

<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>

我无法重现问题。模糊应用于 window

内的所有内容

因此在您的情况下,必须有一个 parent 的 UserControl 创建了一个 AdornerLayer。我猜是 TabControl 或 TabItem。您可以使用Snoop查看。

不过不用担心,您可以创建自己的 AdornerLayer。只需将要模糊的元素放在 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>

然后修改每次调用AdornerLayer.GetAdornerLayer。不要传递原始元素,而是传递要模糊的容器。在我的示例中,它要么是网格,要么是 AdornerDecorator 本身。

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

上面的代码使用 Window.GetWindow 并访问它的内容(第一个 child)。但是你可以很容易地在TipFocusAdorner/Decorator中创建一个属性来指定要传递给AdornerLayer.GetAdornerLayer

的元素