Using VisualBrush as OpacityMask - c#

I want to set OpacityMask to a control, but I need to build that mask dynamically.
Here is how it should look:
The width and height of the whole (red) rectangle is dynamic, based on width and height of parent control. But I need to place two small rectangles (static width and height) in top left and top right corner, as shown on image. So how can I make this happen?
I tried this code, but it doesn't work: (nothing is displayed at all)
<Border BorderBrush="#80FFFFFF" BorderThickness="1" CornerRadius="5">
<Border.OpacityMask>
<VisualBrush>
<VisualBrush.Visual>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Height="2">
<Border Background="Transparent" Width="12" VerticalAlignment="Stretch" HorizontalAlignment="Left" />
<Border Background="Black" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
<Border Background="Transparent" Width="12" VerticalAlignment="Stretch" HorizontalAlignment="Right" />
</StackPanel>
<Border Background="Black" />
</DockPanel>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
</Border>
Is it even valid to use VisualBrush this way (as a OpacityMask)?

If I understand your question correctly you want those Black squares in you image to be transparent?
Update: Uploaded sample project here: http://www.mediafire.com/?5tfkd1cxwfq0rct
I think the problem is that the Panel inside the VisualBrush won't stretch. You could get the desired effect by Binding the Width and Height of whatever Panel you use to the ActualWidth and ActualHeight of the Border
<Border Name="border" BorderBrush="Red" BorderThickness="1" CornerRadius="5">
<Border.OpacityMask>
<VisualBrush>
<VisualBrush.Visual>
<Grid Width="{Binding ElementName=border, Path=ActualWidth}"
Height="{Binding ElementName=border, Path=ActualHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Fill="Transparent" Grid.Column="0"/>
<Rectangle Fill="Black" Grid.Column="1"/>
<Rectangle Fill="Transparent" Grid.Column="2"/>
<Rectangle Fill="Black" Grid.Row="1" Grid.ColumnSpan="3"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
<Grid>
<TextBlock Text="Testing OpacityMask with a rather long string................." Grid.ZIndex="3"/>
<Rectangle Fill="Green"/>
</Grid>
</Border>
Update Again
The DropShadowEffect for the Decorator Child of the Border seems to push the OpacityMask for the Border both Verticaly and Horizontaly. And what's even worse is that it seems to stack, so in your example when you have three DropShadowEffects for three nested Decorators, the sum of the BlurRadius is 45 (20+15+10) so the OpacityMask is pushed by a value of 45 (at least it looks like this is whats going on, but it's a little hard to tell..). You could try to compensate for this by increasing the ColumnDefinition Widths and RowDefinition Heights but I think it'll be hard to find a dynamic solution.
A better approach to your problem may be to use Border.Clip but that doesn't come easy either.
Point1: 0, 2
Point2: 12, 2
Point3: 12, 0
Point4: Width of Border - 12, 0
Point5: Width of Border - 12, 2
Point5: Width of Border, 2
Point6: Width of Border, Height of Border
Point7: 0, Height of Border
Update 3
Came up with a better solution that doesn't require so many Bindings. Create a custom class that derives from Border and override GetLayoutClip. This works both in Designer and Runtime. To increase flexibility of ClippedBorder you could introduce some Dependency Properties to use instead of the hardcoded 2 and 12. New sample app here: http://www.mediafire.com/?9i13rrqpbmzdbvs
public class ClippedBorder : Border
{
protected override Geometry GetLayoutClip(Size layoutSlotSize)
{
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures = new PathFigureCollection();
//Point1: 0, 2
PathFigure pathFigure = new PathFigure();
pathFigure.StartPoint = new Point(0, 2);
//Point2: 12, 2
LineSegment lineSegment1 = new LineSegment();
lineSegment1.Point = new Point(12, 2);
//Point3: 12, 0
LineSegment lineSegment2 = new LineSegment();
lineSegment2.Point = new Point(12, 0);
//Point4: Width of Border - 12, 0
LineSegment lineSegment3 = new LineSegment();
lineSegment3.Point = new Point(this.ActualWidth-12, 0);
//Point5: Width of Border - 12, 2
LineSegment lineSegment4 = new LineSegment();
lineSegment4.Point = new Point(this.ActualWidth-12, 2);
//Point5: Width of Border, 2
LineSegment lineSegment5 = new LineSegment();
lineSegment5.Point = new Point(this.ActualWidth, 2);
//Point6: Width of Border, Height of Border
LineSegment lineSegment6 = new LineSegment();
lineSegment6.Point = new Point(this.ActualWidth, this.ActualHeight);
//Point7: 0, Height of Border
LineSegment lineSegment7 = new LineSegment();
lineSegment7.Point = new Point(0, this.ActualHeight);
pathFigure.Segments.Add(lineSegment1);
pathFigure.Segments.Add(lineSegment2);
pathFigure.Segments.Add(lineSegment3);
pathFigure.Segments.Add(lineSegment4);
pathFigure.Segments.Add(lineSegment5);
pathFigure.Segments.Add(lineSegment6);
pathFigure.Segments.Add(lineSegment7);
pathGeometry.Figures.Add(pathFigure);
return pathGeometry;
}
}

Related

Template doesnt work in Content Control in C#

Here is my xaml
<Canvas x:Name="DesignArea">
<ContentControl
Name="DesignerItem"
Width="100"
Height="100"
Canvas.Top="100"
Canvas.Left="100"
Template="{StaticResource DesignerItemTemplate}">
<Ellipse Fill="Blue" IsHitTestVisible="False"/>
</ContentControl>
<ContentControl Width="130"
MinWidth="50"
Height="130"
MinHeight="50"
Canvas.Top="150"
Canvas.Left="150"
Template="{StaticResource DesignerItemTemplate}">
<Path Fill="Blue"
Data="M 0,5 5,0 10,5 5,10 Z"
Stretch="Fill"
IsHitTestVisible="False"/>
</ContentControl>
</Canvas>
As you can see that the content control use the template just fine with all of it functionality
But I want to do it through C# like this
ContentControl ct = new ContentControl();
ControlTemplate Temp;
Temp = (ControlTemplate)this.FindResource("DesignerItemTemplate");
ct.Template = Temp;
Ellipse ell = new Ellipse();
ell.Fill = new SolidColorBrush(Colors.Black);
ell.Width = 100;
ell.Height = 100;
ell.IsHitTestVisible = false;
ct.Content = ell;
DesignArea.Children.Add(ct);
The Black Ellipse in C# did show up the grid template like the xaml shape
But I can't move, drag drop, resize or rotate Like the content control in the canvas What happen?
Oh I see why
Turn out I need to set the properties of the Canvas too in order to make it work
Canvas.SetLeft(ct, 300);
Canvas.SetTop(ct, 300);
ct.Width = 100;
ct.Height = 100;

How to create sharp 1px lines un UWP? [duplicate]

I overriden Border control and in my overriden OnRender I do:
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
this.SnapsToDevicePixels = true;
this.VisualEdgeMode = EdgeMode.Aliased;
var myPen = new Pen(new SolidColorBrush(Colors.LightGray), 1);
dc.DrawLine(myPen, new Point(1, 1), new Point(1, RenderSize.Height - 1));
return;
Which give me this result:
Question:
Is anybody can tell me why my code draw a line that start at (0,1) while it is suppose to start at (1, 1) like written in code ?
My DPI are 96, 96.
For ref, this is my xaml:
<Window xmlns:MyControls="clr-namespace:MyControls;assembly=MyControls" x:Class="TestControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<MyControls:Border3D BorderThickness="3" BorderBrush="Aqua">
<Rectangle Width="74" Height="3" HorizontalAlignment="Left">
<Rectangle.Fill>
<SolidColorBrush Color="#40FF0000">
</SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
</MyControls:Border3D>
<Rectangle Grid.Row="1" Grid.Column="0" Width="80" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<Rectangle.Fill>
<SolidColorBrush Color="LightGray"></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
Keep in mind that (0, 0) is not the center of the upper left pixel. Instead it is the upper left corner of that pixel. In order to draw a line with a stroke thickness of 1 in the middle of the second pixel column (with index 1) you would have to draw from (1.5, 1) to (1.5, RenderSize.Height - 1):
dc.DrawLine(myPen, new Point(1.5, 1), new Point(1.5, RenderSize.Height - 1));
Setting SnapsToDevicePixels = true made your line snap to the left by half a pixel.
If you would use PenLineCap.Square for both the StartLineCap and EndLineCap properties of the line's Pen, you could draw from exactly one pixel center to the other:
var myPen = new Pen(Brushes.LightGray, 1);
myPen.StartLineCap = PenLineCap.Square;
myPen.EndLineCap = PenLineCap.Square;
dc.DrawLine(myPen, new Point(1.5, 1.5), new Point(1.5, RenderSize.Height - 1.5));

WPF shape different opacity for stroke and fill

This is a very basic question.
I want to be able to add a shape defining different opacity for fill and for stroke.
If I add this:
Ellipse e = new Ellipse();
e.Width = e.Height = 150;
e.Stroke = Brushes.Aqua;
e.Fill = Brushes.Chartreuse;
e.StrokeThickness = 20;
e.Opacity = .25;
plotCanvas.Children.Add(e);
I can only set 1 opacity. Instead I would like the fill to be 0.25 opaque and the stroke to be 1.0 opaque.
Thank you
Patrick
Setting the Opacity on the Ellipse will set the opacity for the entire control. What you want to do is create dedicated Brushes for Fill and Stroke, and control the opacity on the Brushes, i.e. :
SolidColorBrush strokeBrush = new SolidColorBrush(Colors.Aqua);
strokeBrush.Opacity = .25d;
Alternatively, you could control the Alpha channel of the brush:
SolidColorBrush strokeBrush = new SolidColorBrush(Color.FromArgb(/*a, r, g, b*/));
<Ellipse Stroke="Red" Width="200" Height="100" StrokeThickness="5">
<Ellipse.Fill>
<SolidColorBrush Color="Green" Opacity=".25"></SolidColorBrush>
</Ellipse.Fill>
</Ellipse>
Or in C# you can set the fill to a new SolidColorBrush with the desired opacity for the Opacity property.
You can't set the opacity twice for a single Shape object. Insteaf of setting the opacity twice you can add a Border to your Ellipse:
<Canvas x:Name="MyCanvas" Width="1000" Height="1000" Background="White">
<Border BorderBrush="Black" Opacity="1" BorderThickness="10" CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}">
<Ellipse Height="150" Width="150" Fill="Black" Opacity="0.25"></Ellipse>
</Border>
But since the Border is a rectangle which encloses the ellipse, you also need to set the cornerradius

Why my coordinate (1,1) start at (0, 1)?

I overriden Border control and in my overriden OnRender I do:
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
this.SnapsToDevicePixels = true;
this.VisualEdgeMode = EdgeMode.Aliased;
var myPen = new Pen(new SolidColorBrush(Colors.LightGray), 1);
dc.DrawLine(myPen, new Point(1, 1), new Point(1, RenderSize.Height - 1));
return;
Which give me this result:
Question:
Is anybody can tell me why my code draw a line that start at (0,1) while it is suppose to start at (1, 1) like written in code ?
My DPI are 96, 96.
For ref, this is my xaml:
<Window xmlns:MyControls="clr-namespace:MyControls;assembly=MyControls" x:Class="TestControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<MyControls:Border3D BorderThickness="3" BorderBrush="Aqua">
<Rectangle Width="74" Height="3" HorizontalAlignment="Left">
<Rectangle.Fill>
<SolidColorBrush Color="#40FF0000">
</SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
</MyControls:Border3D>
<Rectangle Grid.Row="1" Grid.Column="0" Width="80" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<Rectangle.Fill>
<SolidColorBrush Color="LightGray"></SolidColorBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
Keep in mind that (0, 0) is not the center of the upper left pixel. Instead it is the upper left corner of that pixel. In order to draw a line with a stroke thickness of 1 in the middle of the second pixel column (with index 1) you would have to draw from (1.5, 1) to (1.5, RenderSize.Height - 1):
dc.DrawLine(myPen, new Point(1.5, 1), new Point(1.5, RenderSize.Height - 1));
Setting SnapsToDevicePixels = true made your line snap to the left by half a pixel.
If you would use PenLineCap.Square for both the StartLineCap and EndLineCap properties of the line's Pen, you could draw from exactly one pixel center to the other:
var myPen = new Pen(Brushes.LightGray, 1);
myPen.StartLineCap = PenLineCap.Square;
myPen.EndLineCap = PenLineCap.Square;
dc.DrawLine(myPen, new Point(1.5, 1.5), new Point(1.5, RenderSize.Height - 1.5));

How to rotate an image within a StackPanel or Grid in Silverlight

I have an image control sits inside a Grid control. I already have a button to enable zoom-in to this image. After zoom-in, the Horizontal/vertical scroll bars are displayed. And then I rotate the image contained grid, the image and the grid scroll bar are messed up. How should I incorporate both zoom-in and rotate for the image control? The following are the code that I am using in my project.
The image control zoom-in code I used (x is the image control):
if ((x as Image) != null) { x.Height = x.Height * 1.3; x.Width = x.Width * 1.3; }
The rotation code I used (x is the image control):
if ((x as Image) != null)
{
RotateTransform rotate = new RotateTransform(); rotate.Angle = rotateAngle;
rotate.CenterX = x.Width / 2;
rotate.CenterY = x.Height / 2;
x.RenderTransform = rotate;
};
The XAML is:
<ScrollViewer x:Name="scrollViewer" Height="480" Width="615"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ScrollViewer.Content>
<Grid x:Name="ImageGrid">
<StackPanel x:Name="ImageStackPanel">
<Image Source="..." VerticalAlignment="Center" Width="220" Height="170" ></Image>
</StackPanel>
</Grid>
</ScrollViewer.Content>
</ScrollViewer>
Does anybody have any existing code snippet that I can borrow to resolve this trick?
I think you need to use TransformGroup to use more than one transform at the time:
ScaleTransform myScaleTransform = new ScaleTransform();
myScaleTransform.ScaleY = 3;
RotateTransform myRotateTransform = new RotateTransform();
myRotateTransform.Angle = 45;
// Create a TransformGroup to contain the transforms
// and add the transforms to it.
TransformGroup myTransformGroup = new TransformGroup();
myTransformGroup.Children.Add(myScaleTransform);
myTransformGroup.Children.Add(myRotateTransform);
// Associate the transforms to the image.
x.RenderTransform = myTransformGroup;
This may work for your needs:
<Image x:Name="image" Source="myImageSource" Stretch="Uniform"
HorizontalAlignment="Center" VerticalAlignment="Center"
RenderTransformOrigin="0.5, 0.5">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="Rotate"/>
<ScaleTransform x:Name="Scale" />
</TransformGroup>
</Image.RenderTransform>
</Image>
code behind:
Rotate.Angle = 45;
Scale = 0.25;
You may be missing the LayoutTransformer from the Silverlight Toolkit, and the AnimationMediator from one of the Toolkit developers.
With the LayoutTransformer you can set its content to anything, not just images, and apply any transformation with it, and as opposed to the usual RenderTransform, it will affect layout and actual sizes.
I have a similar scenario and I use it like this:
<Grid>
<fs:AnimationMediator x:Name="RotateMediator" LayoutTransformer="{Binding ElementName=LayoutTransformer}" AnimationValue="{Binding Angle, ElementName=RotateTransform, Mode=TwoWay}" />
<fs:AnimationMediator x:Name="ScaleXMediator" LayoutTransformer="{Binding ElementName=LayoutTransformer}" AnimationValue="{Binding ScaleX, ElementName=ScaleTransform, Mode=TwoWay}" />
<fs:AnimationMediator x:Name="ScaleYMediator" LayoutTransformer="{Binding ElementName=LayoutTransformer}" AnimationValue="{Binding ScaleY, ElementName=ScaleTransform, Mode=TwoWay}" />
<tkt:LayoutTransformer x:Name="LayoutTransformer" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<tkt:LayoutTransformer.LayoutTransform>
<TransformGroup>
<RotateTransform x:Name="RotateTransform" />
<ScaleTransform x:Name="ScaleTransform" />
</TransformGroup>
</tkt:LayoutTransformer.LayoutTransform>
<Image x:Name="MyImage" Source="mysource.png" Width="600" Height="800" />
</tkt:LayoutTransformer>
</Grid>
Because of the lack of MultiBinding you'd probably additionally have to manually handle the input value (from Slider controls etc) changed events and then set the AnimationValues of RotateMediator etc accordingly.

Categories

Resources