Clipping behaviour on custom BorderBrush - wpf

I have a simple rounded border with a solid brush:
<Border BorderThickness="2,0,2,0" CornerRadius="10,10,10,10" BorderBrush="Green"/>
Looks as I expect. Now, I want the border to have a dashed stroke:
<Border BorderThickness="2,0,2,0" CornerRadius="10,10,10,10">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle StrokeThickness="1" Stroke="Green" StrokeDashArray="1 2"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"
StrokeDashCap="Square"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
</Border>
That somehow clips the border in a weird way I can't understand. I thought it would be the way the brush is painted, but I can't make out any pattern. There should be no clipping with the inner content of the border (the red thing you can partly see) as it looks doesn't clip in that way in any other case.
If I set the StrokeThickness a bit higher, I looks more acceptable:
<Border BorderThickness="2,0,2,0" CornerRadius="10,10,10,10">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle StrokeThickness="4" Stroke="Green" StrokeDashArray="1 2"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"
StrokeDashCap="Square"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
</Border>
Can someone explain why that looks so strange? The appearance also changes if I set a different width or height for the rectangle - also in a way I don't understand. The position of the strokes and gaps changes, but not in a way I can make sense of it.

It's because you're doing it via a brush. What you see would actually be expected.
#Clemens actually helped me with an explanation to an issue I had some time ago by being kind enough to show some Doc snippets.
A Brush "paints" or "fills" an area with its output. Different brushes have different types of output. Some brushes paint an area with a solid color, others with a gradient, pattern, image, or drawing.
•VisualBrush: Paints an area with a Visual object. A VisualBrush enables you to duplicate content from one portion of your application into another area; it's very useful for creating reflection effects and magnifying portions of the screen.
So the are being "painted" is determined by the area it's allowed, in this case from the parent panel providing the area within it's BorderBrush so we see a predictable pattern provided by the parent.
Hope this helps, cheers.

Related

Set Size of Rectangle to image size

i'm using this, to show icons that can change their color
<Rectangle Width="100"
Height="100"
Fill="{DynamicResource WhiteBrush">
<Rectangle.OpacityMask>
<ImageBrush Stretch="Uniform" ImageSource={x:Static ImageResources.MyImage}"/>
</Rectangle.OpacityMask>
</Rectangle>
The thing is, that not every Image looks good in 100x100 and if I do not fill these values, nothing is shown. What I wanted to achieve is, that the rectangle automatically takes the needed size of the Image, same as if I would use
<Image Source="{x:Static ImageResources.MyImage}"/>
Any Idea how to achieve this? I already tried to give the ImageBrush a name and reference the size from the rectangle, this doesn't work.
As a workaround I placed an Image at the same position with Visibility=Hidden and get the actualHeight and actualWidth for the Rectangle from there, but that's not an acceptable solution. Any hints welcome.
You could bind the Rectangle's Width and Height:
<Rectangle Fill="{DynamicResource WhiteBrush"
Width="{Binding Width, Source={x:Static ImageResources.MyImage}}"
Height="{Binding Height, Source={x:Static ImageResources.MyImage}}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{x:Static ImageResources.MyImage}"/>
</Rectangle.OpacityMask>
</Rectangle>

WPF - Size rectangle by its ImageBrush opacity mask

I have a rectangle which has an ImageBrush opacity mask (basically a coloured icon).
This is my current code:
<Rectangle Fill="Black">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Path/To/Icon.png"/>
</Rectangle.OpacityMask>
</Rectangle>
This produces no result without setting a fixed width and height for the rectangle. Is it possible to size the rectangle according to the ImageBrush image size?
Thanks for your help. Sorry if this is a silly question, I am still learning WPF.
Edit: To clarify, what I am trying to achieve is for the rectangle to behave same as this:
<Image Source="/Path/To/Icon.png"/>
It should be sized according to the dimensions of the image source file.
Stretch="None" keep image size. Rectangle with binding ActualWidth and ActualHeight resize from ImageBrush size. Result is same if we don't use both. On both case rectangle resize to true ImageBrush size. Hope I understand your problem.
<Rectangle Fill="Black" Width="{Binding ActualWidth, ElementName=image}" Height="{Binding ActualHeight, ElementName=image}">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="/Path/To/Icon.png" x:Name="image" Stretch="None">
</ImageBrush>
</Rectangle.OpacityMask>
</Rectangle>
<Rectangle Width="{Binding ActualWidth, ElementName=image}" Height="{Binding ActualHeight, ElementName=image}">
<Rectangle.Fill>
<ImageBrush ImageSource="C:\Users\neeraj\Pictures\Img1.jpg" x:Name="image" Stretch="None">
</ImageBrush>
</Rectangle.Fill>
</Rectangle>
Try this snippet. I am not sure why you wanted the fill black. If idea was to have a Black surrounding, use Background="Black" on the outer Grid.

Canvas is clipped when size is too big

I am using a canvas with the an ImageBrush to dispaly an image. I am setting the size of the canvas to the original size of the image so I can get the coordinates when I move the mouse etc.
The problem is that when I put the canvas in a control (Grid for example) with a smaller size the Canvas is clipped.
<Grid>
<Canvas Width="{Binding ImageWidth}" Height="{Binding ImageHeight}" >
<Canvas.Background>
<ImageBrush ImageSource="{Binding Image, Converter={StaticResource imgConverter}}"/>
</Canvas.Background>
</Canvas>
</Grid>
Is there a way to keep the canvas size without being clipped?
I've been meaning to dig deeper in to the source to work out where the clipping occurs for a while now, but never get around to doing it. I've been using a not-so-nice trick of inserting a Canvas into the visual tree when this happens as a workaround.
There are a number of controls that clip the child visuals; Grid, StackPanel, etc. As I mentioned the usual quick fix is to use a Canvas after the container that causes the clip.
In your snippet are there more containers higher up the visual tree?
If the depth was actually something like this:-
<StackPanel>
<Grid>
<Canvas Width="{Binding ImageWidth}" Height="{Binding ImageHeight}" >
<Canvas.Background>
<ImageBrush ImageSource="{Binding Image, Converter={StaticResource imgConverter}}"/>
</Canvas.Background>
</Canvas>
</Grid>
</StackPanel>
Then this might cause clipping. If you insert another Canvas further up the visual tree then this clipping is removed.
<StackPanel>
<Canvas>
<Grid>
<Canvas Width="{Binding ImageWidth}" Height="{Binding ImageHeight}" >
<Canvas.Background>
<ImageBrush ImageSource="{Binding Image, Converter={StaticResource imgConverter}}"/>
</Canvas.Background>
</Canvas>
</Grid>
</Canvas>
</StackPanel>
This workaround can then become problematic if it alters other layout needs for other controls.

Aero white glow in WPF?

How do I add the Aero white glow behind a control in WPF?
I mean the glow like in a window's caption bar in Vista/7.
This unrelated question seems to have the answer...
Outer bevel effect on text in WPF
I just needed a rectangle behind my text block...
<Rectangle Height="{Binding ElementName=textBlock1, Path=ActualHeight}" HorizontalAlignment="{Binding ElementName=textBlock1, Path=HorizontalAlignment}" Margin="{Binding ElementName=textBlock1, Path=Margin}" VerticalAlignment="{Binding ElementName=textBlock1, Path=VerticalAlignment}" Width="{Binding ElementName=textBlock1, Path=ActualWidth}" Fill="White" Opacity="0.5">
<Rectangle.Stroke>
<SolidColorBrush />
</Rectangle.Stroke>
<Rectangle.Effect>
<BlurEffect Radius="10" KernelType="Gaussian" />
</Rectangle.Effect>
</Rectangle>
This is gonna do the trick for any control when you replace all the "textBlock"s in the code!
And it looks exactly the same as the aero glow!
Does this help? The effect can be quite subtle but it does look alright.
I tried messing about with Pixel Shaders in WPF using Shazzam but it was a lot of work to achieve the same effect or better. They use a lot of "under the hood" stuff (such as double-pass shaders) of which the techniques aren't available to the WPF api.

WPF: Resizing a circle, keeping the center point instead of TopLeft?

I'd like to resize a circle on my canvas with the help of a slider. This circle can be moved around on the canvas by some drag&drop stuff I did in code behind, so its position is not fixed.
I have bound the slider's value to an ellipse's height and width. Unfortunately, when I use the slider, the circle gets resized with its top left point (actually the top left point of the rectangle it's sitting in) staying the same during the operation.
I would like to resize it with its center point being constant during the operation. Is there an easy way to do this in XAML? BTW, I already tried ScaleTransform, but it didn't quite do what I wanted.
Thanks a bunch! :-)
Jan
<Canvas x:Name="MyCanvas">
<!-- this is needed for some adorner stuff I do in code behind -->
<AdornerDecorator Canvas.Left="10"
Canvas.Top="10">
<Ellipse x:Name="myEllipse"
Height="{Binding Path=Value, ElementName=mySlider}"
Width="{Binding Path=Value, ElementName=mySlider}"
Stroke="Aquamarine"
Fill="AliceBlue"
RenderTransformOrigin="0.5 0.5">
<Ellipse.RenderTransform>
<RotateTransform Angle="{Binding Path=Value, ElementName=myRotationSlider}" />
</Ellipse.RenderTransform>
</Ellipse>
</AdornerDecorator>
<Slider x:Name="mySlider"
Maximum="100"
Minimum="0"
Width="100"
Value="10"
Canvas.Left="150"
Canvas.Top="10" />
<Slider x:Name="myRotationSlider"
Maximum="360"
Minimum="0"
Width="100"
Value="0"
Canvas.Left="150"
Canvas.Top="50" />
</Canvas>
You can bind your Canvas.Left and Canvas.Top to your Height and Width via a ValueConverter.
Specifically (edit):
Create a property each for the Canvas.Left and Canvas.Top and bind to these.
Store the old values for Width and Heigth or the old slider value.
Whenever the slider is changed, get the incremental change "dx" by subtracting the stored value.
(Don't forget to update the stored value...)
Add dx to Width and Height property.
And, as Will said, add dx/2*-1 to Canvas.Left and Canvas.Top properties.
Does that make sense?
The problem is that you are using the SLIDER to adjust the width and height. Width and height are not calculated around RenderTransformOrigin; only RenderTransforms use that value.
Here's a corrected version (brb, kaxaml):
<Canvas x:Name="MyCanvas">
<!-- this is needed for some adorner stuff I do in code behind -->
<AdornerDecorator Canvas.Left="50" Canvas.Top="50">
<Ellipse
x:Name="myEllipse"
Width="10"
Height="10"
Fill="AliceBlue"
RenderTransformOrigin="0.5 0.5"
Stroke="Aquamarine">
<Ellipse.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Path=Value, ElementName=myRotationSlider}"/>
<ScaleTransform
CenterX=".5"
CenterY=".5"
ScaleX="{Binding Path=Value, ElementName=mySlider}"
ScaleY="{Binding Path=Value, ElementName=mySlider}"/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</AdornerDecorator>
<Slider
x:Name="mySlider"
Width="100"
Canvas.Left="150"
Canvas.Top="10"
Maximum="10"
Minimum="0"
SmallChange=".01"
Value="1"/>
<Slider
x:Name="myRotationSlider"
Width="100"
Canvas.Left="150"
Canvas.Top="50"
Maximum="360"
Minimum="0"
Value="0"/>
</Canvas>
Of course, this will probably not work for you. Why? Well, the ScaleTransform I used zooms not only the circle but also the border; as the circle gets bigger the border does as well. Hopefully you won't care about this.
Also, realize when combining transforms (scale then rotate in this case) that they are applied in order, and one may affect how another is done. In your case, you would not notice this. But if, say, you were doing a rotate and translate, the order would be relevant.
Ah, what was I thinking? Just stick the ellipse in a Grid (simplest solution but other containers would work). The grid automatically takes care of centering the ellipse as it is resized. No need for any value converters! Here's the code:
<Canvas x:Name="MyCanvas">
<!-- this is needed for some adorner stuff I do in code behind -->
<Grid Width="100" Height="100">
<AdornerDecorator>
<Ellipse
x:Name="myEllipse"
Width="{Binding Path=Value, ElementName=mySlider}"
Height="{Binding Path=Value, ElementName=mySlider}"
Fill="AliceBlue"
RenderTransformOrigin="0.5 0.5"
Stroke="Aquamarine">
<Ellipse.RenderTransform>
<RotateTransform Angle="{Binding Path=Value, ElementName=myRotationSlider}"/>
</Ellipse.RenderTransform>
</Ellipse>
</AdornerDecorator>
</Grid>
<Slider
x:Name="mySlider"
Width="100"
Canvas.Left="150"
Canvas.Top="10"
Maximum="100"
Minimum="0"
Value="10"/>
<Slider
x:Name="myRotationSlider"
Width="100"
Canvas.Left="150"
Canvas.Top="50"
Maximum="360"
Minimum="0"
Value="0"/>
</Canvas>
Since you're using a Canvas, the location an element has is the location. If you want the Top,Left position to change you need to do it yourself. If you were using another Panel type, like a Grid, you could change the alignment of your Ellipse to place it in the same relative location no matter what the size. You could get that effect by adding a Grid inside your AdornerDecorator and centering the Ellipse but you'd also need to set the AdornerDecorator or Grid to a fixed size because they won't stretch in a Canvas.
The best solution you could use would be a ScaleTransform applied to the RenderTransform property with a RenderTransformOrigin of 0.5,0.5. You said you had problems with ScaleTransform but not what the problem was.
Wrap your Ellipse in a Grid of the maximum size. As long as it is smaller, the Ellipse will be centered in the Grid:
<Grid
Canvas.Left="10"
Canvas.Top="10"
Width="100"
Height="100">
<AdornerDecorator>
<Ellipse x:Name="myEllipse"
Height="{Binding Path=Value, ElementName=mySlider}"
Width="{Binding Path=Value, ElementName=mySlider}"
Stroke="Aquamarine"
Fill="AliceBlue"
RenderTransformOrigin="0.5 0.5">
<Ellipse.RenderTransform>
<RotateTransform Angle="{Binding Path=Value, ElementName=myRotationSlider}" />
</Ellipse.RenderTransform>
</Ellipse>
</AdornerDecorator>
</Grid>
You may need to adjust your dragging logic to handle dragging the Grid instead of the Ellipse itself.
I've found a very easy way to do this in plain XAML: set Margin="-1000000". Read more here: Positioning an element inside the Canvas by its center (instead of the top left corner) using only XAML in WPF

Resources