I'm using the code below to draw text over a rectangle in a WPF canvas but it seems to stretch/squash the text and sometimes the back colour does not fill the entire box.
I'm looking for a way to make sure the box is always filled and the text is clear. Probably some form of dynamic font sizing?
Thanks.
Rectangle elip = new Rectangle();
elip.Height = 6;
elip.Width = 6;
Brush brush = new SolidColorBrush(Color.FromRgb(n.Value.R,
n.Value.G, n.Value.B));
Label TB = new Label();
TB.HorizontalAlignment = HorizontalAlignment.Stretch;
TB.Margin = new Thickness(0, 0, 0, 0);
TB.Background = brush;
TB.FontSize = 12;
TB.HorizontalContentAlignment = HorizontalAlignment.Center;
TB.Content = n.Value.Stations[0].TrackId;
BitmapCacheBrush bcb = new BitmapCacheBrush(TB);
elip.Fill = bcb;
elip.Stroke = Brushes.Black;
elip.StrokeThickness = 0.5;
elip.MouseDown += ElipOnMouseDown;
Canvas.SetTop(elip, n.Value.Y - elip.Width / 2);
Canvas.SetLeft(elip, n.Value.X - elip.Height / 2);
cMain.Children.Add(elip);
You can stretch the text using a viewbox.
I recommend using a usercontrol to encapsulate all your markup for things like this. You would have to have many thousands of these things before the overhead of a usercontrol vs building rectangles and whatnot would be significant.
I built this usercontrol:
Height="18" Width="24">
<Border BorderBrush="Black"
BorderThickness="1"
Background="Magenta"
>
<Viewbox Stretch="Uniform">
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=UserControl}}"
TextAlignment="Center"
Margin="2"
/>
</Viewbox>
</Border>
</UserControl>
Might not be precisely what you want in terms of width, height or whatever.
You could instantiate one of these, set it's Tag, canvas top and left and add it to the canvas.
Here's the equivalent markup I used to prove it:
Title="MainWindow" >
<Grid>
<Canvas>
<local:StationView Canvas.Left="20"
Canvas.Top="100"
Tag="16B"/>
</Canvas>
</Grid>
</Window>
Related
I'm using a rectangle drawn on an adorner to mark a region of interest on an image. The issue is that if I resize the window, the rectangle doesn't change size.
I'm new to WPF, so I've done a bunch of research, googling what I can with multiple different search terms. I actually just learned adorners that way, and I've gotten this far on that, but I've hit a wall on how to finish this last piece. I know that my problem is based in the size of the rectangle, but I don't know what to capture/look for to adjust it, since wpf resizes the actual image object on window resize, so there's no scale factor to look at.
Here's the XAML for the application I'm testing things in.
<Window x:Class="TestingAdorners.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestingAdorners"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="Btn" Source="nyan.png" Stretch="Uniform"/>
</AdornerDecorator>
</Grid>
</Window>
The adorner class:
class RoiAdorner : Adorner
{
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Brushes.Green, 5);
drawingContext.DrawRectangle(null, pen, rectangle);
}
}
And the Xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AdornerLayer.GetAdornerLayer(Btn).Add(new RoiAdorner(Btn));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
The desired result is that the rectangle scales with the image object so that it always covers the same region of the image. The problem is I don't know how to capture a scale factor to scale it up and down as the window resizes.
Update: After thinking through Frenchy's suggestion I realized the answer is simply: "Normalize your coordinates"
you just adapt your render method like this:
class RoiAdorner : Adorner
{
public double factorX = 0d;
public double factorY = 0d;
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
if (factorY == 0)
factorY = rectangle.Height / AdornedElement.DesiredSize.Height;
if (factorX == 0)
factorX = rectangle.Width / AdornedElement.DesiredSize.Width;
var r = new Rect(new Size(AdornedElement.DesiredSize.Width * factorX, AdornedElement.DesiredSize.Height * factorY));
//Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
drawingContext.DrawRectangle(null, new Pen(Brushes.Red, 5), r);
}
this.AdornedElement.DesiredSize gives you the size of image.
The approach I would use is to render the picture and rectangle to the same thing. Then that one thing is stretched, scaled or whatever.
One way to do this would be to use a DrawingImage. Drawing methods are extremely efficient if rather low level.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="img" Stretch="Uniform">
<Image.Source>
<DrawingImage PresentationOptions:Freeze="True">
<DrawingImage.Drawing>
<DrawingGroup>
<ImageDrawing Rect="0,0,595,446" ImageSource="DSC00025.jpg"/>
<GeometryDrawing Brush="Green">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</AdornerDecorator>
</Grid>
Another is with a visualbrush. Controls inherit from visual - this is somewhat higher level coding.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Rectangle Name="rec">
<Rectangle.Fill>
<VisualBrush Stretch="Uniform">
<VisualBrush.Visual>
<Grid Height="446" Width="595">
<Image Source="DSC00025.jpg" Stretch="Fill"/>
<Rectangle Height="30" Width="100" Fill="Green"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</AdornerDecorator>
</Grid>
Note that both of these are quick and dirty illustrations to give you the idea. The image I picked at random off my hard drive is sized 446 * 595. You could calculate sizes or bind or stretch as suits your requirement best.
I am creating shapes at run time on canvas in my app and except all the shapes, ellipse is going out of canvas. How do I restrict it to canvas? All other shapes are contained in canvas because of the control points at their vertices. How do I keep a check as to not let ellipse go out of canvas without clipping. I have used ClipToBounds and it doesn't meet my needs.
Also, an alternate solution is if I can add a controlpoint at the left side of ellipse of radiusX property. I can't add a controlpoint to left side of radiusX on ellipse. If you could help me with either of that?
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusYProperty, 1, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
//radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
//radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
//shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
Here is a quick example of what i would do. It could be improved on and the code is mainly written to be easy to read and follow. It also does not handle the possibility to if the shape's size is bigger than the Canvas (not sure if that is a use case in your project).
For the example I used the "Loaded" event on the Canvas, to reset the position before drawing. You would want this check before you draw the Ellipse object.
private void TestCanvas_Loaded(object sender, RoutedEventArgs e)
{
//canvas = 450 x 800
Ellipse test_ellipse = new Ellipse();
test_ellipse.Width = 100;
test_ellipse.Height = 100;
test_ellipse.Fill = Brushes.Red;
Canvas.SetLeft(test_ellipse, 700);
Canvas.SetTop(test_ellipse, -500);
Reset_Ellipse_Bounds(TestCanvas, ref test_ellipse);
TestCanvas.Children.Add(test_ellipse);
}
private void Reset_Ellipse_Bounds(Canvas myCanvas, ref Ellipse myEllipse)
{
var left = Canvas.GetLeft(myEllipse);
var top = Canvas.GetTop(myEllipse);
//handle too far right
if (left + myEllipse.Width > myCanvas.ActualWidth)
Canvas.SetLeft(myEllipse, myCanvas.ActualWidth - myEllipse.Width);
//handle too far left
if(left < 0)
Canvas.SetLeft(myEllipse, 0);
//handle too far up
if (top < 0)
Canvas.SetTop(myEllipse, 0);
//handle too far down
if (top + myEllipse.Height > myCanvas.ActualHeight)
Canvas.SetTop(myEllipse, myCanvas.ActualHeight - myEllipse.Height);
}
For Completeness the XAML:
<Grid>
<Canvas x:Name="TestCanvas" Loaded="TestCanvas_Loaded" />
</Grid>
The idea is to check the bounding box against the Canvas edges. There are ways to improve this, but i figured the simplest solution is easier to follow.
Within each if statement you could add more logic or a method to do further processing, but this should answer the general question of knowing if it is outside the parent.
Just set ClipToBounds="true" to its father control, it avoids the canvas to be drawn outside of it.
In my case I set it to Grid as followed :
<Grid x:Name="MainGrid" Background="WhiteSmoke" ClipToBounds="true">
<Canvas Margin="10" Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform2"/>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Grid>
I have encounteres a very weird issue with drawing shapes on a canvas in WPF.
<DockPanel Grid.Row="3">
<Canvas Name="BottomCanvas" Margin="15" Background="Yellow">
<Canvas Name="TransparentCanvas" Background="Transparent"
MouseDown="TransparentCanvas_MouseDown"
MouseUp="TransparentCanvas_MouseUp"
MouseMove="TransparentCanvas_MouseMove"
Width="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Canvas}},Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Canvas}},Path=ActualHeight}">
</Canvas>
</Canvas>
</DockPanel>
Above there is my canvas defined.
Below there is a simple example of drawing an elipse; I have no idea why this has no effect.
Ellipse myEllipse = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(0, 0, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.Width = myEllipse.Height = 100;
Canvas.SetTop(myEllipse, 15);
Canvas.SetLeft(myEllipse, 15); ;
bottomCanvas.Children.Add(myEllipse);
Can anyone see what is wrong?
EDIT:
I'm sorry. As you can see in XAML canvas is named 'BottomCanvas' and in code I've used bottomCanvas which is a property added to a MainWindow I don't know what for. That was the deal.
You mean the Ellipse is not visible? It's just because of the transparent fill of SolidColorBrush. The Alpha value of the Color should not be zero, which means invisible. If so, just change the Alpha value of the Color:
mySolidColorBrush.Color = Color.FromArgb(255, 0, 255, 0);
I am trying to make more deep black shadow effect to make readable text , infront white background .
I am using this code
c# void method of loaded
var compositor = ElementCompositionPreview.GetElementVisual(this.grid).Compositor;
var brush = compositor.CreateColorBrush(Colors.Black);
var spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size = this.grid.RenderSize.ToVector2();
var dropshadow = compositor.CreateDropShadow();
dropshadow.Mask = txtBlock.GetAlphaMask();
dropshadow.Color = brush.Color;
dropshadow.BlurRadius = 9.5f;
dropshadow.Opacity = 1.9f;
spriteVisual.Shadow = dropshadow;
ElementCompositionPreview.SetElementChildVisual(this.grid, spriteVisual);
xaml
<Grid Background="White">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid Loaded="grid_Loaded"
x:Name="grid" />
<TextBlock IsTextSelectionEnabled="True"
x:Name="txtBlock"
Text="Drop Shadow"
Foreground="White" Style="{StaticResource BodyTextBlockStyle}"
HorizontalAlignment="Center"
VerticalAlignment="Center" ></TextBlock>
</Grid>
</Grid>
Result
[![Result image][1]][1]
My Problem it is not enough black color shadow effect or opacity to watch the text .
Result
http://i.imgur.com/d6FglQT.png
I am trying to make that black effect more deep,
My Target to make shadow effect like this
Target
http://i.imgur.com/bOiRnCS.png
I know how to add a control to the canvas/grid/layout- simply by calling canvas.Childern.Add(). However, when I want to embed something inside a textblock, I can't seem to find the method for it. A textblock doesn't contain a Add method or anything, so I'm at a bit of a lost.
The XAML I'm trying to turn into C# is:
<TextBlock x:Name="textBlock">
<Line X1="0" Y1="0" X2="100" Y2="0" Stroke="Black" StrokeThickness="4" x:Name="line1"/>
<TextBlock Text="Hello there!" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="innerTextBlock" />
<Line X1="0" Y1="0" X2="100" Y2="0" Stroke="Black" StrokeThickness="4" x:Name="line2"/>
</TextBlock>
EDIT: I think the best way to do it (besides the answer) is to simply create a WPF User control and reference that.
You have to use inlines property (as stated before) so to reproduce your xaml it is enough to do the following (where LayoutRoot is the name of your parent control):
var t = new TextBlock();
t.Inlines.Add(new Line { X1 = 0, Y1 = 0, X2 = 100, Y2 = 0, Stroke = new SolidColorBrush(Colors.Black), StrokeThickness = 4.0 });
t.Inlines.Add("Hello there!");
t.Inlines.Add(new Line { X1 = 0, Y1 = 0, X2 = 100, Y2 = 0, Stroke = new SolidColorBrush(Colors.Black),StrokeThickness = 4.0});
LayoutRoot.Children.Add(t);
I Believe if you have multiple lines you must use the Inlines property which is a collection that contains a list of inline elements. You can't directly add text to it, you must add it to an Inline object - such as a Run.