WPF rectangle - round just top corners - wpf

How can I have just the top corners rounded for a WPF rectangle?
I created a border and set the CornerRadius property and inside the border I've added my rectangle, but it doesn't work, the rectangle is not rounded.
<Border BorderThickness="1" CornerRadius="50,50,0,0" BorderBrush="Black">
<Rectangle Fill="#FF5A9AE0" Stretch="UniformToFill" ClipToBounds="True"/>
</Border>

The problem you've got is that the rectangle is "overflowing" the rounded corners of your border.
A rectangle can't have individually rounded corners, so if you just put the background colour on the border and remove the rectangle:
<Border BorderThickness="1" Grid.Row="0" Grid.ColumnSpan="2"
CornerRadius="50,50,0,0" BorderBrush="Black" Background="#FF5A9AE0">
</Border>
You'll get your desired effect.

Set the RadiusX and RadiusY properties on the rectangle, this will give it rounded corners

Good example how its possible to do OnRender with DrawingContext:
/// <summary>
/// Draws a rounded rectangle with four individual corner radius
/// </summary>
public static void DrawRoundedRectangle(this DrawingContext dc, Brush brush,
Pen pen, Rect rect, CornerRadius cornerRadius)
{
var geometry = new StreamGeometry();
using (var context = geometry.Open())
{
bool isStroked = pen != null;
const bool isSmoothJoin = true;
context.BeginFigure(rect.TopLeft + new Vector(0, cornerRadius.TopLeft), brush != null, true);
context.ArcTo(new Point(rect.TopLeft.X + cornerRadius.TopLeft, rect.TopLeft.Y),
new Size(cornerRadius.TopLeft, cornerRadius.TopLeft),
90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
context.LineTo(rect.TopRight - new Vector(cornerRadius.TopRight, 0), isStroked, isSmoothJoin);
context.ArcTo(new Point(rect.TopRight.X, rect.TopRight.Y + cornerRadius.TopRight),
new Size(cornerRadius.TopRight, cornerRadius.TopRight),
90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
context.LineTo(rect.BottomRight - new Vector(0, cornerRadius.BottomRight), isStroked, isSmoothJoin);
context.ArcTo(new Point(rect.BottomRight.X - cornerRadius.BottomRight, rect.BottomRight.Y),
new Size(cornerRadius.BottomRight, cornerRadius.BottomRight),
90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
context.LineTo(rect.BottomLeft + new Vector(cornerRadius.BottomLeft, 0), isStroked, isSmoothJoin);
context.ArcTo(new Point(rect.BottomLeft.X, rect.BottomLeft.Y - cornerRadius.BottomLeft),
new Size(cornerRadius.BottomLeft, cornerRadius.BottomLeft),
90, false, SweepDirection.Clockwise, isStroked, isSmoothJoin);
context.Close();
}
dc.DrawGeometry(brush, pen, geometry);
}
Information from:
http://wpftutorial.net/DrawRoundedRectangle.html

This one will work even with Rectangle (or anything else) inside it:
<Border>
<Border.OpacityMask>
<VisualBrush>
<VisualBrush.Visual>
<Border CornerRadius="5" Height="100" Width="100" Background="White"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
// put your rounded content here
</Border>
You will have to play with Height and Width if you do not have exact size of content.

Related

WPF Popup positioning and drawn issues

I have a UserControl in which I create an Style that is applied later to the ContentControl of a Popup (below is all defined in the UserControl):
<Style x:Key="ttPopupContent" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<!-- BOTTOM Popup -->
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" Grid.Row="1" />
<StackPanel Grid.Row="1">
<TextBlock Text="My popup title for bottom popup" TextWrapping="Wrap"/>
<TextBlock Text="My popup body content for bottom popup" TextWrapping="Wrap" />
</StackPanel>
<Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left"
Margin="{TemplateBinding Tag}" Width="20" Grid.Row="0" Data="M 0,21 L 10,0 20,21" />
<ContentPresenter Margin="8" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Placement, ElementName=myPopup}" Value="Top">
<!-- TOP Popup -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid x:Name="Grid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" Grid.Row="0" />
<StackPanel Grid.Row="0">
<TextBlock Text="My popup title for top popup"
TextWrapping="Wrap"/>
<TextBlock Text="My popup body content for top popup"
TextWrapping="Wrap" />
</StackPanel>
<Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left"
Margin="{TemplateBinding Tag}" Width="20" Grid.Row="1" Data="M 0,0 L 10,20 20,0" />
<ContentPresenter Margin="8" Grid.Row="0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- this label is placed at leftmost of the screen -->
<TextBlock x:Name="txtBlck1" Text="ShowInfo" />
<Popup x:Name="myPopup1" AllowsTransparency="True" Opened="Popup_Opened1"
PlacementTarget="txtBlck1" Placement="Bottom">
<ContentControl Style="{StaticResource ttPopupContent}"/>
</Popup>
<!-- this label is placed at rightmost of the screen -->
<TextBlock x:Name="txtBlck2" Text="AnotherLabel" />
<Popup x:Name="myPopup2" AllowsTransparency="True" Opened="Popup_Opened2"
PlacementTarget="txtBlck2" Placement="Bottom">
<ContentControl Style="{StaticResource ttPopupContent}"/>
</Popup>
Sometimes it does not put the arrow pointing correctly to the label at which popup is bound. For example, if I hover on the mouse on label "AnotherLabel" it is drawn as follows (this label is at the rightmost of the screen):
as you can see the arrow is not placed in the right place. However, I have another label "ShowInfo" that is placed at the leftmost of the screen, then it works:
So I am trying to adjust the arrow horizontal alignment to point correctly to the label by doing this in code-behind (xaml.cs):
private void Popup_Opened1(object sender, EventArgs e)
{
UIElement target = myPopup1.PlacementTarget;
Point adjust = target.TranslatePoint(new Point(8, 0), popup);
if (adjust.Y > 0)
{
popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
myPopup1.Tag = new Thickness(adjust.X, -1.5, 0, -1.5);
}
private void Popup_Opened2(object sender, EventArgs e)
{
UIElement target = myPopup2.PlacementTarget;
Point adjust = target.TranslatePoint(new Point(8, 0), popup);
if (adjust.Y > 0)
{
popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
}
myPopup2.Tag = new Thickness(adjust.X, -1.5, 0, -1.5);
}
What I am trying to do in code-behind is put the arrow in the correct place by adjusting it horizontally as explained here (in that case is a tooltip, in my case is a Popup).
I have two problems:
Adjusting horizontally the arrow to point correctly to the label.
The arrow is not drawn correctly, it appears a black line under. See below image:
You should extend Popup to conveniently implement the desired behavior.
I suggest to internally set the Popup.Placement to PlacementMode.Custom to enable custom positioning.
This gives you easy access to the positioning context in order to detect when the placement target is positioned right on the screen (in other words the Popup is right aligned with the parent Window.
The arrow box and the tool tip content should be hosted by a custom control.
To draw the arrow box properly you should create and draw geometry for example by overriding UIElement.OnRender of the custom control.
For simplicity, the following example only supports bottom right placement. You can follow the simple pattern to extend the features to support additional positioning (read comments in code):
public class MyToolTip : Popup
{
internal enum Side
{
None = 0,
Left,
Top,
Right,
Bottom
}
private MyToolTipContent ToolTipContent { get; }
static MyToolTip()
{
PlacementTargetProperty.OverrideMetadata(typeof(MyToolTip), new FrameworkPropertyMetadata(default(object), OnPlacementTargetChanged));
PlacementProperty.OverrideMetadata(typeof(MyToolTip), new FrameworkPropertyMetadata(null, CoercePlacement));
}
public MyToolTip()
{
this.ToolTipContent = new MyToolTipContent();
this.Child = this.ToolTipContent;
// Enable custom placement to get context related placement info
// like size and positions.
this.CustomPopupPlacementCallback = CalculatePosition;
this.Placement = PlacementMode.Custom;
this.AllowsTransparency = true;
}
private static void OnPlacementTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as MyToolTip).OnPlacementTargetChanged(e.OldValue as UIElement, e.NewValue as UIElement);
private static object CoercePlacement(DependencyObject d, object baseValue)
{
// Backup/delegate original PlacementMode as we need to override it
// in order to enable custom placement (and to keep it enabled by force).
(d as MyToolTip).ToolTipContent.Placement = (PlacementMode)baseValue;
// Enforce custom placement (invokation of the Popup.CustomPopupPlacementCallback)
return PlacementMode.Custom;
}
// Show Popup on mouse hover over the placement target
protected virtual void OnPlacementTargetChanged(UIElement oldTarget, UIElement newTarget)
{
if (oldTarget is not null)
{
newTarget.MouseEnter -= OnTargetMouseEnter;
newTarget.MouseLeave -= OnTargetMouseLeave;
}
if (newTarget is not null)
{
newTarget.MouseEnter += OnTargetMouseEnter;
newTarget.MouseLeave += OnTargetMouseLeave;
}
}
private void OnTargetMouseLeave(object sender, MouseEventArgs e) => this.IsOpen = false;
private void OnTargetMouseEnter(object sender, MouseEventArgs e) => this.IsOpen = true;
private CustomPopupPlacement[] CalculatePosition(Size popupSize, Size targetSize, Point offset)
{
Side clippingSide = GetParentWindowClippingSide(targetSize);
// TODO::Handle clipping of remaining sides (top and bottom. Left side will always fit).
switch (clippingSide)
{
// Popup will clip right Window bounds => shift Popup to the left to compensate
// (i.e. right align with parent Window).
case Side.Right:
AlignRightSide(popupSize, ref offset, targetSize);
break;
}
// TODO::Handle remaining modes
switch (this.ToolTipContent.Placement)
{
case PlacementMode.Bottom:
offset.Offset(0, targetSize.Height);
break;
case PlacementMode.Top:
break;
}
// Enforce OnRender to update the visual with the latest instructions
this.ToolTipContent.InvalidateVisual();
return new[] { new CustomPopupPlacement(offset, PopupPrimaryAxis.None) };
}
private void AlignRightSide(Size popupSize, ref Point offset, Size targetSize)
{
offset = new Point(targetSize.Width, 0);
offset.Offset(-popupSize.Width, 0);
this.ToolTipContent.HorizontalToolTipArrowAlignment = HorizontalAlignment.Right;
}
private Side GetParentWindowClippingSide(Size targetSize)
{
Window parentWindow = Window.GetWindow(this.PlacementTarget);
var parentWindowBounds = new Rect(new Point(), parentWindow.RenderSize);
Point targetPositionInParentWindow = this.PlacementTarget.TranslatePoint(new Point(), parentWindow);
var targetBounds = new Rect(targetPositionInParentWindow, targetSize);
/* Check for clipping */
bool isTargetRightSideInParentWindowBounds = parentWindowBounds.Contains(targetBounds.TopRight);
if (!isTargetRightSideInParentWindowBounds)
{
return Side.Right;
}
// TODO::Check if Popup clips top or bottom Window bounds following the above pattern. If it does, switch to Bottom/Top placement
// to make it fit the parent's bounds by changing the OriginalPlacementMode accordingly.
// If you don't want to overwrite the original value introduce a dedicated property
// that you really depend on (for example OriginalPlacementModeInternal).
// Popup completely fits the parent using the desired placement.
return Side.None;
}
}
MyToolTipContent.cs
The custom control to make up the actual content of the Popup, responsible for drawing the visual geometries.
internal class MyToolTipContent : Control
{
public PlacementMode Placement
{
get => (PlacementMode)GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
public static readonly DependencyProperty PlacementProperty = DependencyProperty.Register(
"Placement",
typeof(PlacementMode),
typeof(MyToolTipContent),
new PropertyMetadata(default));
private const double ArrowHeight = 12;
private const double ArrowWidth = 12;
internal HorizontalAlignment HorizontalToolTipArrowAlignment { get; set; }
static MyToolTipContent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyToolTipContent), new FrameworkPropertyMetadata(typeof(MyToolTipContent)));
}
protected override void OnRender(DrawingContext drawingContext)
{
SolidColorBrush fillBrush = Brushes.Transparent;
var strokePen = new Pen(Brushes.Black, 2);
// Make room for the stroke
this.Margin = new Thickness(strokePen.Thickness);
var toolTipGeometry = new PathGeometry();
/* Create geometry depending on tool tip position.
*
* TODO::Draw different geometries based on different placement.
*
*/
if (this.HorizontalToolTipArrowAlignment == HorizontalAlignment.Right
&& this.Placement == PlacementMode.Bottom)
{
// "Push" content down to make room for the arrow
this.Padding = new Thickness(0, ArrowHeight, 0, 0);
var arrowBoxLines = new PolyLineSegment(new[]
{
new Point(this.RenderSize.Width - ArrowWidth, ArrowHeight),
new Point(this.RenderSize.Width - ArrowWidth / 2, 0),
new Point(this.RenderSize.Width, ArrowHeight),
new Point(this.RenderSize.Width, this.RenderSize.Height),
new Point(0, this.RenderSize.Height),
}, true);
var arrowBoxPath = new PathFigure(
new Point(0, ArrowHeight),
new[] { arrowBoxLines },
true);
toolTipGeometry.Figures.Add(arrowBoxPath);
}
drawingContext.DrawGeometry(fillBrush, strokePen, toolTipGeometry);
base.OnRender(drawingContext);
}
}
Generic.xaml
<Style TargetType="{x:Type local:MyToolTipContent}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Border Padding="{TemplateBinding Padding}">
<StackPanel>
<TextBlock Text="My popup title for bottom popup"
TextWrapping="Wrap" />
<TextBlock Text="My popup body content for bottom popup"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

How to know if an item is visible to user?

please look at this:
<Grid>
<ScrollViewer>
<Grid Background="Red" Width="50" Height="50" VerticalAlignment="Top" Margin="0,-50,0,0"/>
</ScrollViewer>
</Grid>
here the red grid is not visible because of its margin. but when user pulls down, it will be visible on the screen.
How can I know when it is visible? thanks.
(It's a WP8 app, if that matters)
This method might come handy for you.
private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
if (!element.IsVisible)
return false;
Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}

Canvas overlay image outside

I have a image inside a canvas. When a UserControl loaded, image move up.
<Canvas x:Name="cnvMain" Width="300" VerticalAlignment="Center" Height="200" SnapsToDevicePixels="True">
<Image x:Name="Image1" Width="200" Stretch="None" Canvas.Bottom="0" Source="ImageGallery/Desert.jpg" ></Image>
</Canvas>
I used DoubleAnimation.
DoubleAnimation _Animation;
private Storyboard _StoryBoard;
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
_Animation = new DoubleAnimation();
_Animation.From = -Image1.ActualHeight;
_Animation.To = cnvMain.ActualHeight;
_Animation.RepeatBehavior = RepeatBehavior.Forever;
_Animation.Duration = new Duration(TimeSpan.Parse("0:0:10"));
_Animation.FillBehavior = FillBehavior.Stop;
Storyboard.SetTarget(_Animation, Image1);
Storyboard.SetTargetProperty(_Animation, new PropertyPath(Canvas.BottomProperty));
_StoryBoard = new Storyboard();
_StoryBoard.Children.Add(_Animation);
_StoryBoard.Begin();
}
This code work well. My problem is the canvas did not overlay around of image like a frame (Image size is bigger of canvas and I want area of image inside canvas viewed). When I change Canvas to Grid it overlay outside of image but the animation did not work.
Try and use ClipToBounds="True" on your Canvas:

WPF Rub out top image to reveal image underneath

As the title suggests, using WPF I want to have a scenario where I have a bitmap image with another bitmap overlayed on top of it and, using the mouse, "paint" over the top bitmap so that it reveals the bitmap underneath. Is this just as simple as painting with a transparent brush? I have tried this to no avail but maybe I am missing something.
You can use a VisualBrush to create an OpacityMask. Here is a code example of a dark green rectangle being replaced by a light green one. Using this approach the two rectangles could be any elements including images.
<Grid MouseMove="Grid_MouseMove">
<Rectangle Fill="DarkGreen"/>
<Rectangle Fill="LightGreen">
<Rectangle.OpacityMask>
<VisualBrush Stretch="None" AlignmentX="Left" AlignmentY="Top">
<VisualBrush.Visual>
<Path Name="path" Stroke="Black" StrokeThickness="10"/>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
and here is the code-behind:
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var point = e.GetPosition(sender as Grid);
if (lastPoint != nullPoint)
AddSegment(lastPoint, point);
lastPoint = point;
}
else
{
lastPoint = nullPoint;
}
}
private void AddSegment(Point point1, Point point2)
{
if (segments.Count == 0 || segments[segments.Count - 1].Point != point1)
segments.Add(new LineSegment(point1, false));
segments.Add(new LineSegment(point2, true));
var figures = new PathFigureCollection();
figures.Add(new PathFigure(new Point(), segments, false));
var geometry = new PathGeometry();
geometry.Figures = figures;
path.Data = geometry;
}
List<LineSegment> segments = new List<LineSegment>();
private static readonly Point nullPoint = new Point(-1, -1);
Point lastPoint = nullPoint;

Wavy underlines in a FlowDocument

In WPF, is there an easy way to add wavy underlines (like spelling errors in Word) to FlowDocument elements? There's the Underline class, but there seems to be no way to style it.
You can create the wavy effect using the following changes to Robert Macne's solution
Add a visual brush to the Grid.Resources section:
<VisualBrush x:Key="WavyBrush" Viewbox="0,0,3,2" ViewboxUnits="Absolute" Viewport="0,0.8,6,4" ViewportUnits="Absolute" TileMode="Tile">
<VisualBrush.Visual>
<Path Data="M 0,1 C 1,0 2,2 3,1" Stroke="Red" StrokeThickness="0.2" StrokeEndLineCap="Square" StrokeStartLineCap="Square" />
</VisualBrush.Visual>
</VisualBrush>
And change the pen to:
<Pen Brush="{StaticResource WavyBrush}" Thickness="6" />
A red underline is simple enough:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid.Resources>
<FlowDocument x:Key="doc">
<Paragraph>
<Run Text="This text is underlined in red.">
<Run.TextDecorations>
<TextDecoration Location="Underline">
<TextDecoration.Pen>
<Pen Brush="Red" Thickness="1" DashStyle="{x:Static DashStyles.Dot}"/>
</TextDecoration.Pen>
</TextDecoration>
</Run.TextDecorations>
</Run>
</Paragraph>
</FlowDocument>
</Grid.Resources>
<FlowDocumentReader Document="{StaticResource doc}"/>
</Grid>
A wavy red underline would be a bit more involved, but I think you could create a VisualBrush with a wavy red thing in it, and set that as the Brush of the Pen that you specify for the underlining TextDecoration. Edit: see bstoney's post for this.
Here is #bstoney's solution implemented in code.
Pen path_pen = new Pen(new SolidColorBrush(Colors.Red), 0.2);
path_pen.EndLineCap = PenLineCap.Square;
path_pen.StartLineCap = PenLineCap.Square;
Point path_start = new Point(0, 1);
BezierSegment path_segment = new BezierSegment(new Point(1, 0), new Point(2, 2), new Point(3, 1), true);
PathFigure path_figure = new PathFigure(path_start, new PathSegment[] { path_segment }, false);
PathGeometry path_geometry = new PathGeometry(new PathFigure[] { path_figure });
DrawingBrush squiggly_brush = new DrawingBrush();
squiggly_brush.Viewport = new Rect(0, 0, 6, 4);
squiggly_brush.ViewportUnits = BrushMappingMode.Absolute;
squiggly_brush.TileMode = TileMode.Tile;
squiggly_brush.Drawing = new GeometryDrawing(null, path_pen, path_geometry);
TextDecoration squiggly = new TextDecoration();
squiggly.Pen = new Pen(squiggly_brush, 6);
text_box.TextDecorations.Add(squiggly);
I know this is an old question but I prefer this brush. It's a little angular but very clean.
<VisualBrush x:Key="WavyBrush">
<VisualBrush.Visual>
<Path Data="M 0,2 L 2,0 4,2 6,0 8,2 10,0 12,2" Stroke="Red"/>
</VisualBrush.Visual>
</VisualBrush>
Another way can be : Creating a drawing group with two lines(kind of inverted V) and passing that drawing group as content for drawing brush and having Tile as mode so that it can repeat. But here peaks will be pointed as we are drawing two lines, one starts from others end.
Sample will look like :
// Configuring brush.
DrawingGroup dg = new DrawingGroup();
using (DrawingContext dc = dg.Open())
{
Pen pen = new Pen(Brushes.Red, 1);
dc.DrawLine(pen, new System.Windows.Point(0.0, 0.0), new System.Windows.Point(3.0, 3.0));
dc.DrawLine(pen, new System.Windows.Point(3.0, 3.0), new System.Windows.Point(5.0, 0.0));
}
public DrawingBrush brush = new DrawingBrush(dg);
brush.TileMode = TileMode.Tile;
brush.Viewport = new Rect(0, 0, 4, 4);
brush.ViewportUnits = BrushMappingMode.Absolute;
// Drawing line on VisualCollection
private VisualCollection visualCollection = new VisualCollection(this);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext drawingContext = dv.RenderOpen())
{
drawingContext.DrawLine(new Pen(brush, 2), new Point(50, 100), new Point(200, 100));
base.OnRender(drawingContext);
}
this.visualCollection.Add(dv);

Resources