-1

I need to fit the VisualBrush used into the button to the entire window. The VisualBrush is linked to an Image that is stretched to the entire visualization, but in the visual that image starts to appear in the corner of the button.

        <Button x:Name="button" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Acquista ora- $23.99" FontSize="48" BorderBrush="{x:Null}">
            <Button.Background>
                <VisualBrush Visual="{Binding ElementName=img}" Stretch="None" AlignmentX="Center" AlignmentY="Center" ViewboxUnits="RelativeToBoundingBox" ViewportUnits="RelativeToBoundingBox" />
            </Button.Background>
        </Button>

How can I do? Thanks in advance.

BionicCode
  • 1
  • 4
  • 28
  • 44
Inkling00
  • 13
  • 5
  • Could you show and explain in more detail what the goal is? It is unclear to me what the result should be. – Emond Oct 14 '21 at 10:11
  • You are currently disallowing the brush to stretch: `Stretch="None"`. – BionicCode Oct 14 '21 at 10:42
  • I can use only the none value. My goal is to adapt the visualbrush to the entire window. – Inkling00 Oct 14 '21 at 11:19
  • But how do you expect it to stretch? You paint the background of a button that can't stretch. The brush can't stretch. How should this work out? Currently the brush size or area is limited by the button's size. You could apply the brush to the root element of your window e.g., Grid.Background instead. But then again, how can the image drawn by the brush fit the window (on shrink/grow) if you don't allow it to stretch. If you don't allow stretch then the drawn image will have the original size of the source visusal. – BionicCode Oct 14 '21 at 12:28
  • This question in connected to my other one. My main objective is to blur the background image only behind the button, and I thought I could do that like so. – Inkling00 Oct 14 '21 at 12:37
  • Did you look at this? https://github.com/ConfusedHorse/BlurryControls – Andy Oct 14 '21 at 18:06
  • Is there a way to do that without a library? – Inkling00 Oct 14 '21 at 18:20
  • Extract the code you want. You could grab a chunk of a picture using the technique here https://stackoverflow.com/questions/7295504/how-to-get-a-part-of-an-image-and-use-it-as-a-separate-image – Andy Oct 14 '21 at 18:28

1 Answers1

0

If want to blur the image behind the Button (or a transparent control in general) you have to follow a different approach.

You need the exact tile of the image in order to blur it using the BlurEffect.

In order to not blur the Button itself, you must add alayer beneath the button that has the BlurEffect applied.

The following example extends a ContentControl named BlurHost that renders the Content e.g., the Button, on top of a Border element that will actualy blur the background using a VisualBrush.
The brush itself has a tile defined that is located at the position of the BlurHost which hosts the Button (or any other transparent control).

The basic steps to implement a blurred background:

  1. Add the background image
  2. Create a blur layer beneath the element
  3. Get the bounds of the element e.g., the Button which is located relative to the parent of the Image (preferably the root container)
  4. Use the bounding rectangle to define the tile of the VisualBrush (the actual section of the image)
  5. Apply the brush on the blur layer

Usage example

MainWindow.xaml

<Window>
  <!-- Allow the root grid to stretch accross the Window -->
  <Grid>
    <Image x:Name="img" Source="/someImage.png"  />

    <!-- 
         Optionally override the default BlurEffect 
         by setting the BlurHost.BlurEffect property 
    -->
    <local:BlurHost BlurBackground="{Binding ElementName=img}" 
                    BlurOpacity="1"
                    HorizontalAlignment="Center" 
                    VerticalAlignment="Center">
      <Button Background="Transparent" 
              FontSize="48" 
              Content="Acquista ora- $23.99" />
    </local:BlurHost>
  </Grid>
</Window>

enter image description here

Implementation example

The implementation is simple. You have to add property changed handlers in order to make the control dynamic.

BlurHost.cs
The ContentControl serves as a container. The blurred background is visible at the transparent areas of the content.

public class BlurHost : ContentControl
{
  public Visual BlurBackground
  {
    get => (Visual)GetValue(BlurBackgroundProperty);
    set => SetValue(BlurBackgroundProperty, value);
  }

  public static readonly DependencyProperty BlurBackgroundProperty =
      DependencyProperty.Register(
        "BlurBackground",
        typeof(Visual),
        typeof(BlurHost),
        new PropertyMetadata(default(Visual), OnBlurBackgroundChanged));

  public double BlurOpacity
  {
    get => (double)GetValue(BlurOpacityProperty);
    set => SetValue(BlurOpacityProperty, value);
  }

  public static readonly DependencyProperty BlurOpacityProperty =
      DependencyProperty.Register(
        "BlurOpacity",
        typeof(double),
        typeof(BlurHost),
        new PropertyMetadata(1.0));

  public BlurEffect BlurEffect
  {
    get => (BlurEffect)GetValue(BlurEffectProperty);
    set => SetValue(BlurEffectProperty, value);
  }

  public static readonly DependencyProperty BlurEffectProperty =
      DependencyProperty.Register(
        "BlurEffect",
        typeof(BlurEffect),
        typeof(BlurHost),
        new PropertyMetadata(
          new BlurEffect() 
          { 
            Radius = 10, 
            KernelType = KernelType.Gaussian, 
            RenderingBias = RenderingBias.Performance 
          }));

  private Border PART_BlurDecorator { get; set; }
  private VisualBrush BlurDecoratorBrush { get; set; }

  static BlurHost()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(BlurHost), new FrameworkPropertyMetadata(typeof(BlurHost)));
  }

  public BlurHost()
  {
    Loaded += OnLoaded;

    // TODO::Update Opacity of VisualBrush when property BlurOpacity changes
    this.BlurDecoratorBrush = new VisualBrush()
    {
      ViewboxUnits = BrushMappingMode.Absolute,
      Opacity = this.BlurOpacity
    };
  }

  private void DrawBlurredElementBackground()
  {
    if (!TryFindVisualRootContainer(this, out FrameworkElement rootContainer))
    {
      return;
    }

    // Get the section of the image where the BlurHost element is located
    Rect elementBounds = TransformToVisual(rootContainer)
      .TransformBounds(new Rect(this.RenderSize));

    // Use the section bounds to actually "cut out" the image tile 
    this.BlurDecoratorBrush.Viewbox = elementBounds;
  }

  private void OnLoaded(object sender, RoutedEventArgs e)
  {
    if (TryFindVisualRootContainer(this, out FrameworkElement rootContainer))
    {
      rootContainer.SizeChanged += OnRootContainerElementResized;
    }

    DrawBlurredElementBackground();
  }

  public override void OnApplyTemplate()
  {
    base.OnApplyTemplate();
    this.PART_BlurDecorator = GetTemplateChild("PART_BlurDecorator") as Border;
    this.PART_BlurDecorator.Effect = this.BlurEffect;
    this.PART_BlurDecorator.Background = this.BlurDecoratorBrush;
  }

  private static void OnBlurBackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    var this_ = d as BlurHost;
    this_.BlurDecoratorBrush.Visual = e.NewValue as Visual;
    this_.DrawBlurredElementBackground();
  }

  private void OnRootContainerElementResized(object sender, SizeChangedEventArgs e)
    => DrawBlurredElementBackground();

  private bool TryFindVisualRootContainer(DependencyObject child, out FrameworkElement rootContainerElement)
  {
    rootContainerElement = null;
    DependencyObject parent = VisualTreeHelper.GetParent(child);
    if (parent == null)
    {
      return false;
    }

    if (parent is not Window visualRoot)
    {
      return TryFindVisualRootContainer(parent, out rootContainerElement);
    }

    rootContainerElement = visualRoot.Content as FrameworkElement;
    return true;
  }
}

Generic.xaml
The default Style for the BlurHost. The Generic.xaml file is located in the Themes folder of the application (project).

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Net.Wpf">

  <Style TargetType="local:BlurHost">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:BlurHost">
          <Grid>

            <!-- Blur layer beneath the hosted element (ContentPresenter) -->
            <Border x:Name="PART_BlurDecorator"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"/>
            <ContentPresenter />
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thanks very much!!! Can it have a corner radius? – Inkling00 Oct 15 '21 at 10:05
  • The BlurHost? Simply set the CornerRadius on the PART_BlurDecorator in the Style above. Then disable the Border of the Button. and set a BorderBrush and BorderThickness on the BlurHost element. Alternatively modify the Border of the Button only. Ihave updated the defgault style so that setting BorderBrush and Thickness on the BlurHost is propagated to the PART_BlurDecorator border – BionicCode Oct 15 '21 at 10:59
  • I tried to use it but I get an error with "if (parent is not Window visualRoot)" – Inkling00 Oct 15 '21 at 18:12
  • What is the error? Do you use .NET 5? If not, replace `if (parent is not Window visualRoot)` with `if (!(parent is Window visualRoot))`. – BionicCode Oct 15 '21 at 18:23
  • Ok. Also, where do I add the style? – Inkling00 Oct 15 '21 at 18:26
  • As nI wrote, you main project has a Themes folder which contains a Generic.xaml file. This is where you add it. – BionicCode Oct 15 '21 at 18:31
  • Do I need to add that Generic.xaml file in the App.xaml file? And do I need to link the style with the BlurHost in the window? – Inkling00 Oct 15 '21 at 18:32
  • Thanks for the support!!! It works very well. – Inkling00 Oct 15 '21 at 18:39
  • So you have found the Generic.xaml file? In the BlurHost.cs file you find this line `DefaultStyleKeyProperty.OverrideMetadata(typeof(BlurHost), new FrameworkPropertyMetadata(typeof(BlurHost)));`. This instructs WPF to check the Generic.xaml file for the default style for the control. WPF will load it automatically unless you define another style somewhere to override this default style. Good job that you have made it work. – BionicCode Oct 15 '21 at 19:59
  • The control is very basic. You may come across situations where you have to improve the behavior. At least you have a good starting point and you get the basic principle of how to blur the background in a simple way. In the style you can see how the content is layered on top of the border (using the Z-Index behavior of the Grid). The Border actually applies the blur effect on its background which is drawn using the VisualBrush that captures the image. The control is just a way to make it – BionicCode Oct 15 '21 at 20:12