Why is a Line shape on a Canvas drifting away while dragging? - wpf

I see a strange behaviour dragging in Line shape over a Canvas that I cannot explain.
The Line shape is in a Thumb DataTemplate like so:
<DataTemplate DataType="{x:Type vm:MyLine}">
<Thumb DragDelta="Thumb_DragDelta">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Line Fill="LightBlue" StrokeThickness="2" Y1="{Binding Y1}" Y2="{Binding Y2}" X1="{Binding X1}" X2="{Binding X2}" Stroke="LightBlue" x:Name="Line"/>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</DataTemplate>
The Canvas is ItemsPanelTemplate of a ListBox. In the ListBox.ItemContainerStyle there is no Canvas.Top or Canvas.Left Binding or Setter. Also no Binding for Width and Height.
The Line on the Canvs is dragging but it drifts away from the mouse position. Fairly wild and quick.
In code behind the dragging has the effect:
line.X1 += e.HorizontalChange; line.X2 += e.HorizontalChange;
line.Y1 += e.VerticalChange; line.Y2 += e.VerticalChange;
The setters for X..Y raise PropertyChanged otherwise the Line wouldn't move.
What should I do for a proper dragging behaviour for Line and why?
Binding Mode=OneWay makes no difference.

It turns out that the Line shape behaves somewhat different from the Ellipse shape when added to a Thumb. The Thumb fires in its DragDelta event the total displacment of the mouse w.r.t. the position where the left button was pressed when the dragging started.
For an ellipse this is ok, because internally, the EllipseGeometry of the Ellipse shape is stretched to fill Rectangle that is positioned on the Canvas with Canvas.Left, Canvas.Top, Width and Height. Apparantly the total displacement is the correct value to animate the dragging of the ellipse on the canvas.
For a Line the correct value to animate the dragging of the ellipse on the canvas is the difference of the displacement between two consecutive DragDelta events. A Line is drawn on the basis of X1, X2, Y1, Y2 properties of an internal LineGeometry. The Rectangle of its GeometryBounds are ignored (although the line is clipped beyond this border). The Line's Canvas.Left=Canvas.Top can then be set to 0 and Width and Height equal to the ActualHeight and ActualWidth of the Canvas.
One could also create an own Ellipse shape with a Center and Radius property. Such a shape then would behave like the Line shape.
What I did is create a ThumbShape as a base class, copying code from Thumb.cs of MS. Therfore I also had to copy the corresponding EventArgs classes and EventHandler delegates. I added to the DragDelta eventarguments a relative mouse change so that I can choose whether to use the relative offset or the total offset of the dragging.

I had the same problem and after this answer "For a Line the correct value to animate the dragging of the ellipse on the canvas is the difference of the displacement between two consecutive DragDelta events." I developed this solution, worked for me, hope this helps someone
public class Connector : Thumb
{
private double _oldX = 0;
private double _oldY = 0;
public Connector()
{
DragDelta += Connector_DragDelta;
DragCompleted += Connector_DragCompleted;
}
private void Connector_DragCompleted(object sender, DragCompletedEventArgs e)
{
_oldX = 0;
_oldY = 0;
}
private void Connector_DragDelta(object sender, DragDeltaEventArgs e)
{
Thumb thumb = e.Source as Thumb;
DiagramConnectorViewModel viewModel = (DiagramConnectorViewModel)thumb.DataContext;
double leftOffSet = _oldX - e.HorizontalChange;
_oldX = e.HorizontalChange;
viewModel.X1 -= leftOffSet;
viewModel.X2 -= leftOffSet;
double topOffSet = _oldY - e.VerticalChange;
_oldY = e.VerticalChange;
viewModel.Y1 -= topOffSet;
viewModel.Y2 -= topOffSet;
}

Related

System.Windows.Controls.Ribbon has a top margin (border, space) of around 22px on the top, why and how to remove it properly?

This is the problem (unecessary margin showed by the red arrow):
This is the actual XAML of it:
<Ribbon DockPanel.Dock="Top">
This is the patch (which appears to me as working but a hack instead of a real solution):
<Ribbon DockPanel.Dock="Top" Margin="0, -22, 0, 0">
With the patch (more a hack than anything else to me):
Why there is a margin (border/space) at the top of the Ribbon and how to remove that margin properly without a hack (Margin -22 is a hack to me)?
Solution applied (Ed Bayiates solution):
<Ribbon DockPanel.Dock="Top" x:Name="MyRibbon" SizeChanged="RibbonSizeChanged">
private void RibbonSizeChanged(object sender, SizeChangedEventArgs e)
{
ContentPresenter titlePanel = MyRibbon.Template.FindName("PART_TitleHost", MyRibbon) as ContentPresenter;
if (titlePanel != null)
{
double titleHeight = titlePanel.ActualHeight;
MyRibbon.Margin = new Thickness(MyRibbon.Margin.Left, -titleHeight, MyRibbon.Margin.Right, MyRibbon.Margin.Bottom);
}
}
I think the area in question collapses into the Window titlebar if you host in a RibbonWindow instead of a standard Window.
If you can't do that, there are three items that take the same 22 pixel space in that area. One is PART_TitleHost. The second is a DockPanel with no Name attribute and the third is a Border with no Name attribute. Unless you re-template the whole Ribbon I don't think you can easily get rid of these. However, you could make your hack a bit less hacky if you set the y-margin to the exact size of this area. In the codebehind you can get the titlebar's actual height and reset the margin of the ribbonbar:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ContentPresenter titlePanel = Ribbon.Template.FindName("PART_TitleHost", Ribbon) as ContentPresenter;
if (titlePanel != null)
{
double titleHeight = titlePanel.ActualHeight;
Ribbon.Margin = new Thickness(Ribbon.Margin.Left, -titleHeight, Ribbon.Margin.Right, Ribbon.Margin.Bottom);
}
}
Image without that code:
Image with that code:

How to exactly center rendered text inside a Border

I have a Border with a Content of TextBlock that I want to be perfectly centered both horizontally and vertically. No matter what I try it never looks centered. What am I missing?
Using the code below the top of the text is 19px below the border, the bottom of the text is 5px above the border. It's also off center left or right depending on the Text value which I assume is related to the font.
The solution should work for varying text (1-31) with any font.
Code
<Grid Width="50" Height="50">
<Border BorderThickness="1" BorderBrush="Black">
<TextBlock Text="13" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="50"/>
</Border>
</Grid>
Result
Well then, challenge accepted ;-) This solution is based on the following idea:
Fit the TextBlock inside the border and make sure the entire text is rendered, even if not visible.
Render the text into a bitmap.
Detect the glyphs (i.e. characters) inside the bitmap to get the pixel-exact position.
Update the UI layout so the text is centered inside the border.
If possible, allow simple, generic usage.
1. TextBlock inside border / fully rendered
This is simple once you realize that the entire content of a ScrollViewer is rendered, so here is my UserControl XAML:
<UserControl x:Class="WpfApplication4.CenteredText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ScrollViewer x:Name="scroll"
IsHitTestVisible="False"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Hidden" />
</Grid>
</UserControl>
With the code behind as:
public partial class CenteredText : UserControl
{
public CenteredText()
{
InitializeComponent();
}
public static readonly DependencyProperty ElementProperty = DependencyProperty
.Register("Element", typeof(FrameworkElement), typeof(CenteredText),
new PropertyMetadata(OnElementChanged));
private static void OnElementChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var elem = e.NewValue as FrameworkElement;
var ct = d as CenteredText;
if(elem != null)
{
elem.Loaded += ct.Content_Loaded;
ct.scroll.Content = elem;
}
}
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
void Content_Loaded(object sender, RoutedEventArgs e) /*...*/
}
This control is basically a ContentControlwhich allows to handle the Loaded event of the content generically. There may be a simpler way to do this, I'm not sure.
2. Render to Bitmap
This one is simple. In the Content_Loaded() method:
void Content_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement elem = sender as FrameworkElement;
int w = (int)elem.ActualWidth;
int h = (int)elem.ActualHeight;
var rtb = new RenderTargetBitmap(w, h, 96, 96, PixelFormats.Pbgra32);
rtb.Render(elem);
/* glyph detection ... */
}
3. Detect the glyphs
This is surprisingly easy since a TextBlock is rendered with fully transparent background by default and we are only interested in bounding rectangle. This is done in a separate method:
bool TryFindGlyphs(BitmapSource src, out Rect rc)
{
int left = int.MaxValue;
int toRight = -1;
int top = int.MaxValue;
int toBottom = -1;
int w = src.PixelWidth;
int h = src.PixelHeight;
uint[] buf = new uint[w * h];
src.CopyPixels(buf, w * sizeof(uint), 0);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
// background is assumed to be fully transparent, i.e. 0x00000000 in Pbgra
if (buf[x + y * w] != 0)
{
if (x < left) left = x;
if (x > toRight) toRight = x;
if (y < top) top = y;
if (y > toBottom) toBottom = y;
}
}
}
rc = new Rect(left, top, toRight - left, toBottom - top);
return (toRight > left) && (toBottom > top);
}
The above method tries to find the leftmost, rightmost, topmost and bottommost pixel which is not transparent and returns the results as a Rect in the output parameter.
4. Update Layout
This is done later in the Content_Loaded method:
void Content_Loaded(object sender, RoutedEventArgs e)
{
/* render to bitmap ... */
Rect rc;
if (TryFindGlyphs(rtb, out rc))
{
if (rc.Height > this.scroll.ActualHeight || rc.Width > this.scroll.ActualWidth)
{
return; // todo: error handling
}
double desiredV = rc.Top - 0.5 * (this.scroll.ActualHeight - rc.Height);
double desiredH = rc.Left - 0.5 * (this.scroll.ActualWidth - rc.Width);
if (desiredV > 0)
{
this.scroll.ScrollToVerticalOffset(desiredV);
}
else
{
elem.Margin = new Thickness(elem.Margin.Left, elem.Margin.Top - desiredV,
elem.Margin.Right, elem.Margin.Bottom);
}
if (desiredH > 0)
{
this.scroll.ScrollToHorizontalOffset(desiredH);
}
else
{
elem.Margin = new Thickness(elem.Margin.Left - desiredH, elem.Margin.Top,
elem.Margin.Right, elem.Margin.Bottom);
}
}
}
This UI is updated using the following strategy:
Compute the desired offset between the border and the glyph rectangle in both directions
If the desired offset is positive, it means that the text needs to move up (or left in the horizontal case) so we can scroll down (right) by the desired offset.
If the desired offset is negative, it means that the text needs to move down (or right in the horizontal case). This cannot be done by scrolling since the TextBlock is top-left-aligned (by default) and the ScrollViewer is still at the initial (top/left) position. There is a simple solution though: Add the desired offset to the Margin of the TextBlock.
5. Simple Usage
The CenteredText control is used as follows:
<Border BorderBrush="Black" BorderThickness="1" Width="150" Height="150">
<local:CenteredText>
<local:CenteredText.Element>
<TextBlock Text="31" FontSize="150" />
</local:CenteredText.Element>
</local:CenteredText>
</Border>
Results
For border size 150x150 and FontSize 150:
For border size 150x150 and FontSize 50:
For border size 50x50 and FontSize 50:
Note: There is a 1-pixel error where the space to the left of the text is 1 pixel thicker or thinner than the space to the right. Same with the top / bottom spacing. This happens if the border has an even width and the rendered text an odd width (no sub-pixel perfectness is provided, sorry)
Conclusion
The presented solution should work up to a 1-pixel error with any Font, FontSize and Text and is simple to use.
And if you haven't noticed yet, very limited assumptions were made about the FrameworkElement which is used with the Elem property of the CenteredText control. So this should also work with any element which has transparent background and needs (near-)perfect centering.
What you are talking about is related to the specific font (and characters within that font) that you are using. Different fonts will have different baselines, heights and other attributes. In order to combat that, just use Padding on the Border or Margin on the TextBlock to make it fit where you want it:
<Grid Width="50" Height="50">
<Border BorderThickness="1" BorderBrush="Black">
<TextBlock Text="13" VerticalAlignment="Center" HorizontalAlignment="Center"
FontSize="50" Margin="0,0,3,14" />
</Border>
</Grid>
Note: You can also use the TextBlock.TextAlignment Property to make adjustments to the horizontal alignment of text content.
I'd add this as a comment but I haven't got enough reputation :P
It looks off center because the height and width you have specified for the grid (50x50) is too small to house a font size of 50. Either increase the size to 100x100 or lower the font size to something smaller.
To demonstrate that they will be perfectly aligned in the center by doing this - view this code in visual studio somewhere. You will see the numbers of these textblocks overlap perfectly.
<Grid Height="100" Width="100">
<Border BorderThickness="1" BorderBrush="Black" >
<TextBlock Text="13" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="50"/>
</Border>
<Border BorderThickness="1" BorderBrush="Black" >
<TextBlock Text="31" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="50"/>
</Border>
</Grid>
I hope this helps you out :)

How to position FormattedText with ItemsControl on Canvas (MVVM)

Using a MVVM pattern with ItemsControl for the XAML, I am trying to exactly position the topleft ink point of a FormattedText string at exactly the topleft corner of a specified rectangle. (The rectangle is given by the InkAnalyzer.Location.GetBounds()).
For reasons I can't figure out (or find on Google), the FormattedText string is always positioned down and to the right of the topleft corner of my rectangle, the distance seems proportional to the size of the font used for printing.
How can I achieve positioning of the FormattedText to hit the topleft corner of my rectangle?
Any help is greatly appreciated.
XAML
<ItemsControl
ItemsSource="{Binding Transcription}"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
Background="Transparent"
Width="{x:Static h:Constants.widthCanvas}"
Height="{x:Static h:Constants.heightCanvas}"
/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
C#
private ObservableCollection<StringViewModel> transcription = new ObservableCollection<StringViewModel>();
public ObservableCollection<StringViewModel> Transcription
{
get
{
return transcription;
}
}
public class StringViewModel : FrameworkElement, INotifyPropertyChanged
{
public StringViewModel(
Point topleft,
string text,
double fontsize,
SolidColorBrush brush,
double linewidth,
double columnheight,
Func<FormattedText,FormattedText> f)
{
this.text = text;
this.FontSize = fontsize * (72.0/96.0);
this.color = brush;
this.origin = topleft;
this.origin_X = topleft.X;
this.origin_Y = topleft.Y;
this.maxtextwidth = linewidth * (72.0/96.0);
this.maxtextheight = columnheight ;
this.f = f;
}
protected override void OnRender(DrawingContext dc)
{
FormattedText ft = new FormattedText(
text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily("Segoe Script"), FontStyles.Italic, FontWeights.Normal, FontStretches.Normal),
FontSize,
color);
ft.TextAlignment = TextAlignment.Left;
ft.MaxTextWidth = maxtextwidth;
ft.MaxTextHeight = maxtextheight;
dc.DrawText(ft, origin);
}
I've noticed that the Segoe family of fonts tends to have pretty generous spacing in the metrics. A quick test of Segoe Script at 16px (12pt at 72dpi) shows around 5px padding between the top of the taller glyphs and the bounding box.
In the example above, each chunk of text is positioned at the top-left of the pink border. You can see the extra vertical space in the first line, which I suspect reproduces what you're seeing in your own code.
Setting the LineHeight to the font size eliminates the extra space, but clips some of the taller glyphs. My experimentation shows that a LineHeight of 1.25 * FontSize tends to be ideal for this particular font, as shown in the third line. It also maintains adequate spacing to display multi-line text.
As an aside, you may want to embed the font in your application and reference it by its resource name, as opposed to simply specifying the font name and hoping your user has it installed (be sure to check the licensing details, though).

pixel selection for PanZoomImage

I am new to WPF and am trying to write a clickable zoom-pan image control. I already have a zoom-pan image which seems to work:
<Border Name="border" ClipToBounds="True">
<Canvas>
<Image Name ="image">
Source="{Binding Path=Source}"
MouseLeftButtonDown="image_MouseLeftButtonDown"
MouseLeftButtonUp="image_MouseLeftButtonUp"
MouseMove="image_MouseMove"
MouseWheel="image_MouseWheel">
</Image>
</Canvas>
</Border>
For the mouse and wheel events I used this post: http://www.codeproject.com/Articles/168176/Zooming-and-panning-in-WPF-with-fixed-focus
I am writing the clickable control by inheriting from ZoomPanImage and adding an event for LeftMouseUp.
public class ClickableImage : PanZoomImage
{
public event Action<Point> Click;
//...
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
// ... all sorts of checks to distinguish click from mouse move
if (Click != null)
{
Click(ControlToImage(mouseUpCoordinates));
}
}
protected Point ControlToImage(Point controlPixel)
{
//this is where i am stuck...
}
}
My problem is that I can't seem to calculate the correct image coordinates given the control coordinates. I need to take into account that the image can be zoomed and panned and that the window itself can be resized.
I tried using the rendering transform. When I zoom and pan the image I update the transform. And when I try to convert control coordinates to image coordinates I use the inverse transform:
Point imagePixel = image.RenderTransform.Inverse.Transform(controlPixel);
But this didn't work. One of the problems is that the Transform starts as Identity while in fact the image is stretched uniformly to the control's size.
Thanks,
Dina
Here's how I solved it. As Clemens suggested, I set the image stretch mode to none.
<Image Name="image" RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="None"
Source="{Binding Path=Source}"
MouseLeftButtonDown="image_MouseLeftButtonDown"
MouseLeftButtonUp="image_MouseLeftButtonUp"
MouseMove="image_MouseMove"
MouseWheel="image_MouseWheel"
Loaded="image_Loaded">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Fit to window" Click="FitToWindow_MenuItem_Click"></MenuItem>
</ContextMenu>
</Image.ContextMenu>
</Image>
This means that when the image is loaded into the window, you can only see part of it - depending on the window size. This is bad, but what's important is that the transform is identity and you can now manually set it such that the image is fully shown in the window.
private void FitViewToWindow()
{
if (Source == null)
throw new InvalidOperationException("Source not set");
BitmapSource bitmapSource = Source as BitmapSource;
if (bitmapSource == null)
throw new InvalidOperationException("Unsupported Image Source Type");
if (border.ActualWidth <= 0 || border.ActualHeight <= 0)
return;
double scaleX = border.ActualWidth / bitmapSource.PixelWidth;
double scaleY = border.ActualHeight / bitmapSource.PixelHeight;
double scale = Math.Min(scaleX, scaleY);
Matrix m = Matrix.Identity;
m.ScaleAtPrepend(scale, scale, 0, 0);
double centerX = (border.ActualWidth - bitmapSource.PixelWidth * scale) / 2;
double centerY = (border.ActualHeight - bitmapSource.PixelHeight * scale) / 2;
m.Translate(centerX, centerY);
image.RenderTransform = new MatrixTransform(m);
}
This function should be called upon loading the image and upon changing the source of the image. As for resizing the window - as long as you keep track of the transform, you will be able to convert coordinate systems correctly. For example, here's what I do for window resize:
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
//center the image in the new size
if (sizeInfo.PreviousSize.Width <= 0 || sizeInfo.PreviousSize.Height <= 0)
return;
Matrix m = image.RenderTransform.Value;
double offsetX = (sizeInfo.NewSize.Width - sizeInfo.PreviousSize.Width) / 2;
double offsetY = (sizeInfo.NewSize.Height - sizeInfo.PreviousSize.Height) / 2;
m.Translate(offsetX, offsetY);
image.RenderTransform = new MatrixTransform(m);
}

How to obtain the scaled size of a WPF Visual element

I am rendering a WPF Visual (UserControl) to a bitmap but the problem is that the rendered image is the size of the UserControl before it is scaled/transformed. So let's say the UserControl was designed at 200x200 pixels. When I render to BMP I'm using the UserControl's ActualWidth and ActualHeightt which report 200 and 200 respectively. Problem is the UserControl is in a Canvas and is auto sized (set to scale/fill with the Window size) to something closer to 1200 x 1200 (it changes)
I've done some reading and searching and so far can't figure out how to determine the effective size, that is the size the control is being painted on screen.
I came across this question which sounded hopeful but the Transform returned does not contain scaling data. Well it does, but they are both 1.
Get element position after transform
Any suggestions on where to look for the render scaling would be great!
[UPDATE] As suggested, I'm including the relevant code:
public static Bitmap PngBitmap(this Visual visual)
{
// Get height and width
int width = (int)(double)visual.GetValue(
FrameworkElement.ActualWidthProperty);
int height = (int)(double)visual.GetValue(
FrameworkElement.ActualHeightProperty);
// Render
RenderTargetBitmap rtb =
new RenderTargetBitmap(
width,
height,
96,
96,
PixelFormats.Default);
rtb.Render(visual);
// Encode
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
System.IO.MemoryStream stream = new System.IO.MemoryStream();
encoder.Save(stream);
// Create Bitmap
Bitmap bmp = new Bitmap(stream);
stream.Close();
return bmp;
}
public static BitmapSource BitmapSource(this Visual visual)
{
Bitmap bmp = visual.PngBitmap();
IntPtr hBitmap = bmp.GetHbitmap();
BitmapSizeOptions sizeOptions = BitmapSizeOptions.FromEmptyOptions();
return Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
sizeOptions);
}
[Update #2] Added the XAML - The Grid element was removed because it was HUGE and from my reading of the XAML the Canvas containing the keyboard UserControl was NOT part of the Grid element.
<UserControl 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:PMD.HECAT.DashboardModule"
xmlns:PMD_HECAT_DashboardModule_VirtualKeyboard="clr-namespace:PMD.HECAT.DashboardModule.VirtualKeyboard"
xmlns:System_Windows_Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
xmlns:PMD_HECAT_DashboardModule_Input="clr-namespace:PMD.HECAT.DashboardModule.Input"
xmlns:control="clr-namespace:PMD.HECAT.DashboardModule.Controls"
x:Class="PMD.HECAT.DashboardModule.CandidateElectrodeView"
x:Name="UserControl"
mc:Ignorable="d"
d:DesignWidth="1024" d:DesignHeight="768" Width="640" Height="360">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Themes/DashboardStyles.xaml" />
<ResourceDictionary Source="../Themes/ImageButtons.xaml" />
<ResourceDictionary Source="CandidateViewResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView.KeyboardClose" SourceName="virtualKeyboardView">
<BeginStoryboard Storyboard="{StaticResource OnKeyboardClose1}"/>
</EventTrigger>
</UserControl.Triggers>
<Canvas Width="100" HorizontalAlignment="Left">
<PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView x:Name="virtualKeyboardView" Height="222" Width="550" RenderTransformOrigin="0.5,0.5" Opacity="0" Active="False">
<PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform X="40" Y="400"/>
</TransformGroup>
</PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView.RenderTransform>
</PMD_HECAT_DashboardModule_VirtualKeyboard:VirtualKeyboardView>
<Rectangle Stroke="White" Opacity="0.7" Fill="White" Height="370" Width="654.851" Canvas.Left="687" Canvas.Top="0" />
</Canvas>
</UserControl>
I know lots of time passed since the question was asked but it doesn't hurt to post some more info :)
I use this code to find size (scale) of visuals with applied render transform(s).
GeneralTransform t = transformedVisual.TransformToVisual(parentVisual);
Vector topLeft = (Vector) t.Transform(new Point(0,0));
Vector topRight = (Vector) t.Transform(new Point(originalWidthOfTransformedVisual, 0));
double renderedWidth = (topRight-topLeft).Length;
double widthScale = renderedWidth / originalWidthOfTransformedVisual;
Hi i had a similar problem if the visual wasn't displayed before to force this rendering
uiElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
uiElement.Arrange(new Rect(new Point(0, 0), uiElement.DesiredSize));
Size _size = uiElement.DesiredSize;
this works at least in my case to fore the UIElement to Render it Size
If there is a RenderTransform you will hardly be able to determine the size unless you you some serious calculations
In most other cases you will be able to get the size of the FrameworkElement by using the ActualHeight and ActualWidth properties. Still, these properties will give you the size of the bounding box which will be sufficient for the scenario you describe.
You say you use the Visual's width and height.
First, there are two problems.
A Visual, http://msdn.microsoft.com/en-us/library/system.windows.media.visual.aspx does not have any Properties for Height and Width.
Now, assuming you have a FrameworkElement, which does have a Height and Width property, are you using the Height and Width Properties, or the ActualHeight and ActualWidth properties?
If you are not, then those are the properties you are looking for.
If you are using them, or you are not using a FrameworkElement, then you should post some code.
If you could use animation to perform the Transform then animation provides Completed event. That should be a good place to extract new transformed size.
Based on #bor's answer, I simplified the code to allow a more common use:
private static Rect GetTransformedVisualBounds(Visual source, Visual target, Rect bounds)
{
return source.TransformToVisual(target).TransformBounds(bounds);
}
source is a Visual which can have transformations applied;
target is the Visual to which the coordinates will be converted to;
bounds is the non-transformed bounds of the source Visual;
The following is an example of use of the above method:
private void ExampleOfUse()
{
Border border = new Border()
{
Width = 100,
Height = 100
};
ContainerVisual container = new ContainerVisual();
container.Children.Add(border);
container.Transform = new ScaleTransform(2d, 2d);
Rect transformedBounds = GetTransformedVisualBounds(container, this, VisualTreeHelper.GetDescendantBounds(container));
// This should print a rectangle with a size of 200x200;
Debug.Print($"Transformed bounds: {transformedBounds}");
}
This is a very old question, but it was useful for me and I hope it is still useful for someone else as well.
public static Rect GetRelativePlacement(this UIElement element, UIElement relativeTo)
{
var absolutePos = element.PointToScreen(new Point(0, 0));
var posRelativeTo = relativeTo.PointToScreen(new Point(0, 0));
var topLeft = new Point(absolutePos.X - posRelativeTo.X, absolutePos.Y - posRelativeTo.Y);
var bottomRight = element.PointToScreen(new Point(element.RenderSize.Width, element.RenderSize.Height));
var bounds = Rect.Empty;
bounds.Union(topLeft);
bounds.Union(bottomRight);
return bounds;
}

Resources