I have few textblocks in a grid which can be dragged. I want to restrict the user so that user can't drag a textblock outside the grid.
I've tried a few ways like getting the position of the grid so that somehow i can control but it didn't work as expected.
Thanks in advance.
This can be done pretty easily using a Canvas inside the Grid, calculating the coordinates of the TextBlock inside of the Canvas and then continuously checking whether the TextBlock is still within its bounds. When the TextBlock leaves its bounds, the Transform then reverts back to it's last known 'good' coordinates.
XAML
<Grid>
<Grid Name="GridBounds" Width="600" Height="600" Background="Aqua">
<Canvas>
<TextBlock Name="TextBlock1" Text="Drag Me" FontSize="40" ManipulationDelta="TextBlock1_ManipulationDelta" ManipulationMode="TranslateX, TranslateY" HorizontalAlignment="Stretch" Canvas.Left="216" Canvas.Top="234" VerticalAlignment="Stretch"/>
</Canvas>
</Grid>
</Grid>
Code Behind
CompositeTransform TextDrag = new CompositeTransform();
CompositeTransform savedTransform = new CompositeTransform();
public MainPage()
{
this.InitializeComponent();
TextBlock1.RenderTransform = TextDrag;
}
// Copy Transform X and Y if TextBlock is within bounds
private void CopyTransform(CompositeTransform orig, CompositeTransform copy)
{
copy.TranslateX = orig.TranslateX;
copy.TranslateY = orig.TranslateY;
}
private void TextBlock1_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
TextDrag.TranslateX += e.Delta.Translation.X;
TextDrag.TranslateY += e.Delta.Translation.Y;
CompositeTransform transform = TextBlock1.RenderTransform as CompositeTransform;
// Get current Top-Left coordinates of TextBlock
var TextTopLeft = TextBlock1.TransformToVisual(GridBounds).TransformPoint(new Point(0, 0));
// Get current Bottom-Right coordinates of TextBlock
var TextBottomRight = TextBlock1.TransformToVisual(GridBounds).TransformPoint(new Point(TextBlock1.ActualWidth, TextBlock1.ActualHeight));
// Get Top-Left grid coordinates
var GridTopLeft = (new Point(0, 0));
// Get Bottom-Right grid coordinates
var GridBottomRight = (new Point(GridBounds.ActualWidth, GridBounds.ActualHeight));
if (TextTopLeft.X < GridTopLeft.X || TextBottomRight.X > GridBottomRight.X)
{
// Out of bounds on X axis - Revert to copied X transform
TextDrag.TranslateX = savedTransform.TranslateX; ;
}
else if (TextTopLeft.Y < GridTopLeft.Y || TextBottomRight.Y > GridBottomRight.Y)
{
// Out of bounds on Y axis - Revert to copied Y transform
TextDrag.TranslateY = savedTransform.TranslateY;
}
else
{
// TextBlock is within bounds - Copy X and Y Transform
CopyTransform(transform, savedTransform);
}
}
Here it is in action.
I am implementing a user control that is a view for an arrangement of shapes, which can be moved around and zoomed with the mouse, altogether or individually. The user control contains a single "Root" canvas to which the shapes are added, and the render transform of which determines its position and size in the view.
What I would like to be able is to set the background of the canvas to a rectangle that encloses all its contained shapes. Is this possible ? Of course I can set the Width and Height properties but they seem to count from the origin only. But the shapes can as well be placed at negative coordinates relative to the canvas' origin so the background rectangle of the canvas would have to start at negative coordinates also.
Canvas cnv = new Canvas();
// show the canvas' dimensions
cnv.Background = Brushes.AliceBlue;
for (int i = -5; i < 5; i++)
{
for (int j = -5; j < 5; j++)
{
Rectangle newRectangle = new Rectangle();
cnv.Children.Add(newRectangle);
newRectangle.Width = 7;
newRectangle.Height = 7;
newRectangle.Fill = Brushes.Green;
Canvas.SetTop(newRectangle, j * 10);
Canvas.SetLeft(newRectangle, i * 10);
}
}
// these are the right bounding dimensions, but with the wrong origin
cnv.Width = 107;
cnv.Height = 107;
// nothing available like that?
// cnv.Left = -50;
// cnv.Top= -50;
Of course, if everything fails, I could just add another rectangle for the bounds.
The Canvas is transparent so why not stack it on top of a rectangle in a grid:
<Grid>
<Rectangle Fill="Lime" Stroke="Red" StrokeThickness="5" />
<Canvas />
</Grid>
Or if it realy has to look like a border:
<Border Background="Lime" BorderBrush="Red" BorderThickness="5">
<Canvas />
</Border>
The Source of the Image is bound to a URL which points to an image.
If the image at the URL is smaller then the MaxHeight and MaxWidth, the following code works great. The image size is exactly the same as the url and the window is sized properly.
If the image at the URL is larger then the MaxHeight and MaxWidth, only a portion of the image is displayed. The image does not get shrunk to fit into the window.
If I remove Stretch="None", the large picture then shrinks to fit into the MaxHeight and MaxWidth, looks great, but the small image gets expanded to consume all available space and looks like crap.
Here are two images I have been testing with:
http://imgur.com/iaBp2Fv,fiRrTJS#0
<Window x:Class="MyNamespace.Windows.PictureWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Profile Picture" ResizeMode="NoResize" UseLayoutRounding="True" SizeToContent="WidthAndHeight" MaxHeight="750" MaxWidth="750">
<Image Stretch="None" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding}" />
</Window>
What you could do is to to remove the Stretch="None", as you say, to make the large image shrink down. But, to avoid the small image to be scaled up, you just add this property:
StretchDirection="DownOnly"
This prevents small images from being scaled upwards, and allows large images to be scaled down. The Window resizes appropriately as well.
This is the code that I have tested in LinqPad. Just change showLarge to true and false to switch between the images.
bool showLarge = false;
var w = new Window();
w.ResizeMode = ResizeMode.NoResize;
w.UseLayoutRounding = true;
w.SizeToContent = SizeToContent.WidthAndHeight;
w.MaxHeight = 750;
w.MaxWidth = 750;
Image img = new Image();
img.HorizontalAlignment = HorizontalAlignment.Center;
img.VerticalAlignment = VerticalAlignment.Center;
img.StretchDirection = StretchDirection.DownOnly;
if(showLarge)
img.Source = new BitmapImage(new System.Uri(#"http://i.imgur.com/iaBp2Fv.jpg"));
else
img.Source = new BitmapImage(new System.Uri(#"http://i.imgur.com/fiRrTJS.jpg"));
w.Content = img;
w.ShowDialog();
In your code behind, create a property as
public Point ImageSize {get;set}
Get the image from the URL in the constructor/Initialize() and set ImageSize accordingly
Bind the height and width of your window to ImageSize.X and ImageSize.Y
Height="{Binding ImageSize.Y}" Width="{Binding ImageSize.Y}"
I have a canvas (ZoomableCanvas) which implements zooming and has child items. I'm trying to get the relative position of a child item when zooming in, in order to display extended information over it.
<Canvas x:Name="CanvasContainer" Width="1500" Height="780">
<ZoomableCanvas ApplyTransform="false" Loaded="ZoomableCanvas_Loaded" x:Name="ShareCanvas" Width="1500" Height="780" MinWidth="1500" MinHeight="780" Grid.Row="0" Grid.Column="0" />
</Canvas>
Code behind:
Canvas main = touched.FindVisualParent<Canvas>(); //Finds "CanvasContainer"
Point relativePoint = touched.TransformToAncestor(main).Transform(new Point(0, 0));
double canvasTop = Canvas.GetTop(touched);
But here canvasTop != relativePoint.Y before zooming?
Am I using this incorrectly. Doesn't it map to the parent visual and give a relative point?
The values you're comparing will only be equal if
There is no transform (RenderTranform or LayoutTransform) set on
touched, since these are taken into account in TransformToAncestor but
not in the Canvas.Left or Canvas.Top properties.
The ZoomableCanvas has no offset relative to the outer canvas. This
is because Canvas.GetTop(touched) returns the top offset relative
to the ZoomableCanvas (assumed that touched is a child of the
ZoomableCanvas), whereas relativePoint is relative to the outer
Canvas.
To illustrate the first issue, just put an element with a RenderTransform into a Canvas:
<Canvas x:Name="canvas">
<Label x:Name="child" Content="Hello" Canvas.Left="50" Canvas.Top="100">
<Label.RenderTransform>
<TranslateTransform X="50" Y="100"/>
</Label.RenderTransform>
</Label>
</Canvas>
If you'd call
var point = child.TransformToAncestor(canvas).Transform(new Point());
var left = Canvas.GetLeft(child);
var top = Canvas.GetTop(child);
it would return point as (100, 200), whereas left is 50 and top is 100.
I am trying to create some rotated text and save that image to a PNG file. The resulting PNG should be no larger than needed (or minimal padding). I have it working as long as there is no rotation, but as soon as I rotate the text, it is getting clipped off in the file. I am sure it has something to do with adjusting the either the CenterX and CenterY of the RotateTransform or creating a TranslateTransform, but I can't find anything on how to do it correctly and my trial-and-error testing has turned into trial-and-frustration.
My sample code is below. I looking for a solution that would work with an arbitrary angle and not just -45 degrees.
Finally, if someone knows how to meet these requirements, but say using an "old style" Graphics object instead of WPF tools, I am open to that solution to that too.
private static void CreateImageFile()
{
FormattedText ft;
Geometry textBox;
string fontName;
Typeface face;
DrawingVisual viz;
RotateTransform rt;
TranslateTransform tt;
Rect rect;
RenderTargetBitmap bmp;
PngBitmapEncoder encoder;
ft = CreateText("Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor", "Verdana", 12, false, false);
textBox = ft.BuildHighlightGeometry(new Point());
fontName = "Arial";
face = new Typeface(fontName);
// now create the visual we'll draw them to
viz = new DrawingVisual();
rt = new RotateTransform() { Angle = -45 };
rect = rt.TransformBounds(ft.BuildHighlightGeometry(new Point(0, 0)).Bounds);
using (DrawingContext dc = viz.RenderOpen())
{
dc.PushTransform(rt);
dc.DrawText(ft, new Point(0, 0));
dc.Pop();
}
bmp = new RenderTargetBitmap((int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(viz);
encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
encoder.Save(file);
}
private static FormattedText CreateText(string text, string typeface, double fontSize, bool bold, bool italic)
{
FontStyle fontStyle = FontStyles.Normal;
FontWeight fontWeight = FontWeights.Medium;
if (bold == true) fontWeight = FontWeights.Bold;
if (italic == true) fontStyle = FontStyles.Italic;
// Create the formatted text based on the properties set.
FormattedText formattedText = new FormattedText(
text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily(typeface),
fontStyle,
fontWeight,
FontStretches.Normal),
fontSize,
Brushes.Black, // This brush does not matter since we use the geometry of the text.
null,
TextFormattingMode.Display
);
return formattedText;
}
Update
Based upon some of the suggestions below, I decided to try a different tack and experiment in the GUI. I created a window like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="160" Width="160" Loaded="Window_Loaded">
<Grid>
<Canvas Name="WorkCanvas" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-45"/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>This is a test</TextBlock>
</Canvas>
</Grid>
</Window>
As you can see, it uses both the RotateTransform and the suggested RenderTransformOrigin, and the result is like the image below. And, as you can see, the text does not go through the middle of the Canvas. And that seems to be my entire problem. How to rotate the text and get it correctly centered.
Update 2
I decided to try a Grid instead of a Canvas this time and I can now get the text properly centered in the grid, but since I can't use a FormattedText object, I can't seem to measure the actual bounding box. All measurements of the TextBlock or the Grid come back as if it was not rotated at all (looking at ActualWidth, ActualHeight, and DesiredSize). If I can't get the rotated bounding box size, I can't save the PNG without it getting clipped.
Oh, and I tried rotating the text in an unrotated grid and rotating the grid itself, both give the same results when trying to determine the dimensions.
You could try to wrap your text in an element that has a rendertransformOrigin. Make you changes to that element. Try a canvas or a grid.
I think what you are missing is that you need to set RenderTransformOrigin to 0.5,0.5 so that your rotation transformation is around the center of you image and not the upper left-hand edge.
Update
In response to your update above. The problem is using canvas. If you remove your transform altogether, you'll see your TextBlock isn't centered to start with. It actually is rotating around it's center, it's just that the center isn't the center of the canvas. Try this:
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock TextAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="-45"/>
</TextBlock.RenderTransform>
This is a test
</TextBlock>
</Grid>
After much poking around and with help from Matt and kbo4sho88, I finally found the correct way of doing it. In addition to the help from the other posters, I finally found that I need to call TransformToVisual and TransformBounds to get the bounding box that I need for the correct file size. But, before that, I had to call Measure and Arrange since these objects are not shown on a screen.
Phew!
private static void CreateImageFile()
{
Grid workGrid;
TextBlock workTextBlock;
RenderTargetBitmap bitmap;
PngBitmapEncoder encoder;
Rect textBlockBounds;
GeneralTransform transform;
workGrid = new Grid()
{
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center
};
workTextBlock = new TextBlock()
{
Text = "Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor",
FontFamily = new FontFamily("Verdana"),
FontSize = 36,
TextAlignment = TextAlignment.Center,
RenderTransformOrigin = new Point(0.5, 0.5),
LayoutTransform = new RotateTransform(-45)
};
workGrid.Children.Add(workTextBlock);
/*
* We now must measure and arrange the controls we just created to fill in the details (like
* ActualWidth and ActualHeight before we call TransformToVisual() and TransformBounds()
*/
workGrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
workGrid.Arrange(new Rect(0, 0, workGrid.DesiredSize.Width, workGrid.DesiredSize.Height));
transform = workTextBlock.TransformToVisual(workGrid);
textBlockBounds = transform.TransformBounds(new Rect(0, 0, workTextBlock.ActualWidth, workTextBlock.ActualHeight));
/*
* Now, create the bitmap that will be used to save the image. We will make the image the
* height and width we need at 96DPI and 32-bit RGBA (so the background will be transparent).
*/
bitmap = new RenderTargetBitmap((int)textBlockBounds.Width, (int)textBlockBounds.Height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(workGrid);
encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
encoder.Save(file);
}
Of course this is just a sample (but working) method and the final one will be parameterized.
Final part of the puzzle was found at WPF: Getting new coordinates after a Rotation
The way I generally handle custom rendering is using my own FrameworkElement and overriding the OnRender method. So, as an example:
using System;
using System.Windows; // For the FrameworkElement baseclass
using System.Windows.Media; // To get the drawing context
public class CustomThing : FrameworkElement
{
// Constructor
CustomThing()
{
}
// Custom render code called whenever control is invalidated
protected override OnRender(DrawingContext context)
{
if (!this.IsVisible || this.ActualHeight <= 0 || this.ActualWidth <= 0)
{
return; // Don't do anything if this thing isn't renderable
}
Typeface tf = new Typeface(this.FontFamily, FontStyles.Normal, this.FontWeight, FontStretches.Normal);
FormattedText fText = new FormattedText(this.Text, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, tf, this.FontSize, this.Foreground);
// You could have accessors so that the various properties such as Fonts, etc. are
// Properties of the class, and using DependencyProperties, they can be set so they
// automatically cause an invalidation when they change.
double txWidth = fText.Width;
double txHeight = fText.Height;
// This measures the text
double w = this.ActualWidth;
double h = this.ActualHeight;
double w2 = w / 2.0;
double h2 = h / 2.0;
// Get the center point for the rotation
// In this case, the center of the control
Transform trans = new RotateTransform(-90.0, w2, h2);
// The transform is for counter-clockwise 90 degrees, centered
// in the center of the control.
context.PushTransform(trans);
// All drawing operations will be performed with the
// transformation applied
Point txPos = new Point(w2 - (txWidth / 2.0), h2 - (txHeight / 2.0));
// The render origin for the text is the upper left
// hand corner of the bounding box. This uses the same origin for
// the rotation, then shifts it by half the dimensions of the text.
context.DrawText(fText, txPos);
context.Pop();
// The pop method is only needed if you need to continue work
// with the drawing context and don't want the transform
// operating on future rendering.
}
}
There are other details, such as if you want the control to be mouse interactive (include a using System.Windows.Input), then you'll want to start the render by painting the entire control region a background color. The main thing is you don't need to measure the shape of the rotated text, only the unrotated text. If you want to position the final text in any other place besides the center of the control, just make sure your rotation center is also the same reference point as your text offset.
The other thing I typically do is to use Dependency Properties so that all the styling can be done in XAML. This way, the custom object can be previewed in the designer while changing the properties dynamically, a typical entry for an item would look like so:
public static readonly RoutedEvent TextChangedEvent = EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<string>), typeof(CustomThing));
public event RoutedPropertyChangedEventHandler<string> TextChanged
{
add { AddHandler(TextChangedEvent, value); }
remove { RemoveHandler(TextChangedEvent, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CustomThing), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnTextChanged)));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CustomThing cntrl = (CustomThing)obj;
RoutedPropertyChangedEventArgs<string> e = new RoutedPropertyChangedEventArgs<string>((string)args.OldValue, (string)args.NewValue, TextChangedEvent);
cntrl.OnTextChanged(e);
}
protected virtual void OnTextChanged(RoutedPropertyChangedEventArgs<string> e)
{
RaiseEvent(e);
}