WPF - Hidden Window Shows In Screenshot - wpf

My simple goal is to take a screenshot.
My application has a main window that has multiple purposes and one of them is taking a screenshot.
What I do is hide the window by using MainWindow.Hide(); and then showing a second window that handles the screenshot taking process.
this.Hide();
SnapshotManager snapshotMgr = new SnapshotManager();
snapshotMgr.Closed += SnapshotMgrClosed;
snapshotMgr.Show();
The second window is basically this:
<Grid>
<Image Name="MainImage" Stretch="None" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</Image>
<Border BorderBrush="Transparent" BorderThickness="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Border.Background>
<SolidColorBrush Color="Black" Opacity="0.4"/>
</Border.Background>
</Border>
</Grid>
with the properties:
AllowsTransparency="True"
Background="Transparent"
WindowStyle="None"
When the user clicks a button to take the screenshot I hide the main window and show this one.
Then I save the screen to the image in my second window like so:
Bitmap snapshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Graphics graphics = Graphics.FromImage(snapshot);
graphics.CopyFromScreen(new System.Drawing.Point(0,0), new System.Drawing.Point(0,0), Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
//-----------------------------------
// Save the screen to MainImage
//-----------------------------------
MemoryStream ms = new MemoryStream();
snapshot.Save(ms, ImageFormat.Bmp);
ms.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
MainImage.Source = bi;
The problem is that for some reason my main window shows up in the screenshot.
The MainWindow.Hide() probably doesn't work like I think it does.
How can I take a screenshot with the main window?
Thank you!
Edit:
The following code works but uses Sleep(200) which I really would like to avoid.
private void OnTakeSnapshot(object sender, RoutedEventArgs e)
{
this.LayoutUpdated += RunSnapshotMgr;
this.Hide();
}
private void RunSnapshotMgr(object sender, EventArgs e)
{
if (IsVisible == true) return;
Thread.Sleep(200);
SnapshotManager snapshotMgr = new SnapshotManager();
snapshotMgr.Closed += SnapshotMgrClosed;
snapshotMgr.Show();
this.LayoutUpdated -= RunSnapshotMgr;
}

Related

SizeToContent with a Listbox

I'm using SizeToContent="WidthAndHeight" so that I can size a window to the size of the currently displayed controls. This works fine, however, I'm running into an issue when displaying and adding items to a listbox. When adding a great number of items to the listbox this will cause the window to extend below the edge of the monitor. I notice that without the SizeToContent="WidthAndHeight" I can easily get the Listbox to limit itself and use a scrollbar. However, this has the negative of displaying all the extra unused space that the listbox with take up before I have made it visible. I have added a small sample solution below to display this behaviour.
NOTE window does not need to be resizable. It just needs to be efficient on space and I would rather not use absolute values if possible.
Explaination of desired behaviour; user only sees the button, window has no unused space around the button. When the button is clicked the Listbox is now visible but has not extended beyond the screen (i.e. a scroll bar has been added) and the button beneath is visible.
...ResizeMode="CanMinimize" SizeToContent="WidthAndHeight">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Content="hello" Click="Button_Click"/>
<ListView Name="lbx" Grid.Row="1" Visibility="Collapsed">
</ListView>
<Button Name="btn" Content="hello" Grid.Row="2" Visibility="Collapsed"/>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
btn.Visibility = Visibility.Visible;
lbx.Visibility = Visibility.Visible;
//Have tried various combinations of settings in code at runtime
this.SizeToContent = SizeToContent.Width;
this.Height = Double.NaN;
for (int i = 0; i < 1000; i++)
{
lbx.Items.Add("Num:" + i);
}
}
I've also tried this with a dockpanel instead of a Grid;
<Button Content="hello" Click="Button_Click" DockPanel.Dock="Top"/>
<Button Name="btn" Content="hello" DockPanel.Dock="Bottom" Visibility="Collapsed"/>
<ListView Name="lbx" Visibility="Collapsed">
</ListView>
Try swapping SizeToContent="WidthAndHeight" with SizeToContent="Width", the window starts up showing all the empty space that will later be used by the collapsed controls.
You could set the MaxHeight of the window based on the actual height of the ListBox and the screen, e.g.:
private void Button_Click(object sender, RoutedEventArgs e)
{
btn.Visibility = Visibility.Visible;
lbx.Visibility = Visibility.Visible;
for (int i = 0; i < 5; i++)
{
lbx.Items.Add("Num:" + i);
}
Dispatcher.Invoke(() =>
{
MaxHeight = Math.Min(SystemParameters.WorkArea.Height - Top, grid.ActualHeight);
}, System.Windows.Threading.DispatcherPriority.Background);
}
This seems like a good option. One drawback is that with a small number of items it still introduces a scrollbar even when the listbox items should be able to fit.
Introduce an offset then:
private void Button_Click(object sender, RoutedEventArgs e)
{
btn.Visibility = Visibility.Visible;
lbx.Visibility = Visibility.Visible;
for (int i = 0; i < 5; i++)
{
lbx.Items.Add("Num:" + i);
}
grid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
grid.Arrange(new Rect(new Size(grid.DesiredSize.Width, grid.DesiredSize.Height)));
Height = Math.Min(SystemParameters.WorkArea.Height - Top, grid.ActualHeight + 40);
}

Query about hit testing

I am new to WPF. I want to have several different shapes in a canvas, then show a different context menu depending on which shape user right-clicks in. I am presently trying with just one shape, a rectangle inside a canvas. What came to mind was that in the handler for right click I should do a hit test for the rectangle. If it comes back true, I would show the context menu for this shape. But it does not work. How can I detect that the right click is inside the rectangle and not elsewhere on the canvas?
XAML :
<Canvas Grid.Column ="2" Name="canvas" Background="Transparent" RightButtonDown="show_context_menu" >
<Rectangle Name="myrectangle" Width="1000" Height="500" Fill="LightSteelBlue" Stroke="Black" StrokeThickness="4" Canvas.Left="10" Canvas.Top="100"/>
</Canvas>
C# :
private void show_context_menu(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((UIElement)sender);
HitTestResult result = VisualTreeHelper.HitTest(myrectangle, pt);
if (result != null)
{
ContextMenu cm = cmCanvas as ContextMenu;
cm.PlacementTarget = sender as Canvas;
cm.IsOpen = true;
}
}
Issue is you are calculating point relative to canvas and passing visual as rectangle.
Calculate point relative to myrectangle and pass it in HitTest method:
Point pt = e.GetPosition(myrectangle);
HitTestResult result = VisualTreeHelper.HitTest(myrectangle, pt);
if (result != null)
{
.......
}

Silverlight 5: Terrible Performance Issues with DropShadowEffect

Environment Info:
Windows 7 64bit SP1
6GB Memory
Intel(R) Core(TM) i5-2400S CPU # 2.50GHz (4 cores)
Silverlight 5
I have very terrible performance issues with DropShadowEffect in Silerlight.
I created a minimized demo for it:
http://www.peterlee.com.cn/public/ShadowEffectTestDemo/ShadowEffectTestTestPage.html
Click the button Zoom to try to zoom the canvas (with a Star shape with DropShadowEffect). You will find that when the Zoom = 64, your CPU and memory are very crazy.
But, if you remove the DropShadowEffect by clicking the Remove Effect button, and then zoom it, everything is okay.
However, if we use a TextBlock with DropShadowEffect, everything is fine. You can try it by clicking the "Use TextBlock` button.
I don't know what Silverlight is dealing with the TextBlock and my customized Star shape for the DropShadowEffect.
Please help me out. Thanks.
UPDATE:
According to the replies:
I also tried it in Release mode (now the sample demo link was built in Release mode);
I also added GPA acceleration suggested by ChrisF:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="EnableGPUAcceleration" value="true" />
<param name="source" value="ShadowEffectTest.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="5.0.61118.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
</a>
</object>
Here is the UPDATED code
MainPage.xmal:
<UserControl x:Class="ShadowEffectTest.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button x:Name="btnZoom" Width="75" Height="23" Content="Zoom"
Grid.Column="0" Grid.Row="0"
Click="btnZoom_Click"/>
<Button x:Name="btnRemoveEffect" Width="100" Height="23" Content="Remove Effect"
Grid.Row="0" Grid.Column="1"
Click="btnRemoveEffect_Click"/>
<Button x:Name="btnUseTextBlock" Width="120" Height="23" Content="Use TextBlock"
Grid.Row="1" Grid.Column="0"
Click="btnUseTextBlock_Click" />
<Canvas x:Name="mainCanvas" Width="200" Height="150" Background="LightBlue"
Grid.Column="1" Grid.Row="1" />
</Grid>
</UserControl>
MainPage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Windows.Media.Effects;
namespace ShadowEffectTest
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
// These are the points for a Star shape
List<Point> points = new List<Point>()
{
new Point(0, 36.3634469292486),
new Point(-12.3606797688474, 43.2188191057189),
new Point(-24.7213595376948, 50.0741912821893),
new Point(-37.0820393065422, 56.9295634586596),
new Point(-34.4313595448175, 42.9654855127096),
new Point(-31.7806797830927, 29.0014075667595),
new Point(-29.130000021368, 15.0373296208095),
new Point(-39.4200000080385, 5.31010468057062),
new Point(-49.709999994709, -4.41712025966827),
new Point(-59.9999999813794, -14.1443451999072),
new Point(-46.0011100186002, -15.919247828416),
new Point(-32.002220055821, -17.694150456925),
new Point(-18.0033300930418, -19.4690530854338),
new Point(-12.0022200620278, -32.3361808961241),
new Point(-6.00111003101392, -45.2033087068145),
new Point(0, -58.0704365175048),
new Point(6.00111003101392, -45.2033087068145),
new Point(12.0022200620278, -32.3361808961241),
new Point(18.0033300930418, -19.4690530854338),
new Point(32.002220055821, -17.694150456925),
new Point(46.0011100186002, -15.919247828416),
new Point(59.9999999813794, -14.1443451999072),
new Point(49.709999994709, -4.41712025966827),
new Point(39.4200000080385, 5.31010468057062),
new Point(29.130000021368, 15.0373296208095),
new Point(31.7806797830927, 29.0014075667595),
new Point(34.4313595448175, 42.9654855127096),
new Point(37.0820393065422, 56.9295634586596),
new Point(24.7213595376948, 50.0741912821893),
new Point(12.3606797688474, 43.2188191057189),
new Point(0, 36.3634469292486)
};
uie = RenderBezier(points);
// This the evil for the performance issues (crazy memory and CPU)
uie.Effect = ShadowEffect;
uie.CacheMode = new BitmapCache();
uie.SetValue(Canvas.LeftProperty, 25.0);
uie.SetValue(Canvas.TopProperty, 25.0);
mainCanvas.Children.Add(uie);
}
private UIElement uie = null;
public Path RenderBezier(List<Point> ctrlPoints, List<List<Point>> innersCtrlPoints = null)
{
// Step 0: Merge ctrlPoints lists
List<List<Point>> ctrlPointsLists;
if (innersCtrlPoints == null)
{
ctrlPointsLists = new List<List<Point>>(1);
ctrlPointsLists.Add(ctrlPoints);
}
else
{
ctrlPointsLists = new List<List<Point>>(1 + innersCtrlPoints.Count);
ctrlPointsLists.Add(ctrlPoints);
foreach (List<Point> list in innersCtrlPoints)
ctrlPointsLists.Add(list);
}
PathGeometry pg = new PathGeometry();
foreach (List<Point> list in ctrlPointsLists)
{
// Step 0: check (Debug.Assert)
Debug.Assert(list.Count % 3 == 1,
"public Path RenderBezier(IList<Point> ctrlPoints): number of control points is not 3n+1.");
int n = (list.Count - 1) / 3; // Number of BezierSegments
Debug.Assert(n > 0,
"public Path RenderBezier(IList<Point> ctrlPoints): at least one Bezier segment required.");
// Step 1: Add BezierSegments to PathFigure
PathFigure pf = new PathFigure();
pf.StartPoint = list[0];
for (int i = 0; i < n; ++i)
pf.Segments.Add(GetBezierSegment(
list[3 * i + 1],
list[3 * i + 2],
list[3 * i + 3]
));
// Step 2: Add PathFigures to PathGeometry
pg.Figures.Add(pf);
}
// Step 3: Add PathGemotry to GeometryGroup
GeometryGroup gg = new GeometryGroup();
gg.Children.Add(pg);
// Step 4: Set GeometryGroup as Path.Data
Path path = new Path();
path.Data = gg;
// Step 5: Set some Path properties
// if (ShowOutline)
{
path.Stroke = new SolidColorBrush(Colors.Black);
path.StrokeThickness = 1.0;
path.StrokeEndLineCap = PenLineCap.Round;
path.StrokeLineJoin = PenLineJoin.Round;
path.StrokeStartLineCap = PenLineCap.Round;
}
// Finally, return it
return path;
}
// This the evil for the performance issues (crazy memory and CPU)
private static DropShadowEffect ShadowEffect
{
get
{
return new DropShadowEffect()
{
Color = Colors.Blue,
BlurRadius = 5,
Direction = 0,
ShadowDepth = 0
};
}
}
private static BezierSegment GetBezierSegment(Point p1, Point p2, Point p3)
{
BezierSegment bs = new BezierSegment();
bs.Point1 = p1;
bs.Point2 = p2;
bs.Point3 = p3;
return bs;
}
public static readonly double[] ZoomingSteps = new double[]
{
1.0,
1.5,
2.0,
3.0,
4.0,
6.0,
8.0,
12.0,
16.0,
24.0,
32.0,
48.0,
64.0,
128.0
};
private int index = 0;
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
if (index >= ZoomingSteps.Length - 1)
return;
ScaleTransform st = new ScaleTransform();
st.ScaleX = st.ScaleY = ZoomingSteps[index++];
btnZoom.Content = ZoomingSteps[index].ToString();
mainCanvas.RenderTransform = st;
}
private void btnRemoveEffect_Click(object sender, RoutedEventArgs e)
{
index = 0;
btnZoom.Content = "Zoom";
uie.Effect = null;
mainCanvas.RenderTransform = new ScaleTransform();
}
// If I use TextBlock as the child UIElement, then everything is okay
// path = new TextBlock() { Text = "Text Block" };
private void btnUseTextBlock_Click(object sender, RoutedEventArgs e)
{
mainCanvas.Children.Remove(uie);
index = 0;
btnZoom.Content = "Zoom";
uie = new TextBlock() { Text = "Text Block" };
mainCanvas.Children.Add(uie);
uie.Effect = ShadowEffect;
uie.CacheMode = new BitmapCache();
mainCanvas.RenderTransform = new ScaleTransform();
}
}
}
I've tried running WinDbg against your application and dumpheap the application. here is the Result. Now dont worry, I dont understand half of it either. But I went a little deeper and there seem to be large amounts of arrays/lists where atleast 80% are null.
You might want to look at WinDbg yourself. Here is a small Tutorial
I'll try to look at it in the morning if I can and I hope I've helped you atleast in the right direction
Here's my trick: never use DropShadowEffect. Create a clone of whatever you want to create a drop shadow for, put it behind the main content, and use a BlurEffect on it. Much better performance, much better quality. I have no idea why.
I think the problem is here:
uie.CacheMode = new BitmapCache();
Typically, this tells silverlight to try and cache the bitmap in memory so that the computer does not need to re-render the element in your application.
Look here for the suggested tips on silverlight performance:
http://msdn.microsoft.com/en-us/library/cc189071(v=vs.95).aspx
The problem has to do with how the rendering pipeline of Silverlight works. The DropShadowEffect is just a pixel shader, and pixel shaders work in Silverlight by creating a buffered copy of the image they will be applied to and then allow you to change pixel properties of the real image using the values of the buffered copy. This buffered copy is the entire image and is not affected by clipping. Because zooming creates a very large pre-clipped image, the pixel shader must make a physical buffered copy of a very large pre-clipped image...this is why the performance is so bad when you zoom.
The only solution I have found for this is to disable the pixel shader effect while you are zooming (or panning the zoomed image), and add it back when the image is zoomed in (or panning is complete). This way you don't incur the performance penalty of the pixel shader being applied for every frame of zooming or panning.

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;
}

ScaleTransform transforms non-linearly

I am using scale transform to allow a user to resize a control. What happens though is that when you start to move the mouse the control jumps to a new size, and then scales oddly. The further you move your mouse from the starting location the larger the increase in size becomes.
I expect its the way I calculate the scale to be applied. Here is the code:
private void ResizeGrip_MouseDown(object sender, MouseButtonEventArgs e)
{
ResizeHandle.CaptureMouse();
//Get the initial coordinate cursor location on the window
initBtmX = e.GetPosition(this).X;
bottomResize = true;
}
private void ResizeGrip_MouseUp(object sender, MouseButtonEventArgs e)
{
bottomResize = false;
ResizeHandle.ReleaseMouseCapture();
}
private void ResizeGrip_MouseMove(object sender, MouseEventArgs e)
{
if( bottomResize == true)
{
//Get the new Y coordinate cursor location
double newBtmX = e.GetPosition(this).X;
//Get the smallest change between the initial and new cursor location
double diffX = initBtmX - newBtmX;
// Let our rectangle capture the mouse
ResizeHandle.CaptureMouse();
double newWidth = e.GetPosition(this).X - diffX;
double scaler = newWidth / ResizeContainer.ActualWidth;
Console.WriteLine("newWidth: {0}, scalar: {1}", newWidth, scaler);
if (scaler < 0.75 || scaler > 3)
return;
ScaleTransform scale = new ScaleTransform(scaler, scaler);
ResizeContainer.LayoutTransform = scale;
}
}
Update: Now with XAML
<wtk:IToolDialog x:Name="VideoPlayer" ParentControl="{Binding ElementName=Stage}" DialogTitle="Video Player" Margin="90,5,0,0">
<Grid>
<Grid x:Name="ResizeContainer" ClipToBounds="True" Width="320" Height="240" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,1">
<!-- The video frame -->
<Image Stretch="Fill" Source="{Binding CurrentFrameImage}" x:Name="VideoImage" />
<Grid>
<pplcontrols:VideoGroundPlane Foreground="Black" GridSize="20" GroundPlane="{Binding GroundPlane}">
</pplcontrols:VideoGroundPlane>
</Grid>
<Grid x:Name="HitMask" IsHitTestVisible="False"/>
</Grid>
<ResizeGrip Cursor="SizeNWSE" x:Name="ResizeHandle" VerticalAlignment="Bottom" HorizontalAlignment="Right" Mouse.MouseDown="ResizeGrip_MouseDown" Mouse.MouseUp="ResizeGrip_MouseUp" Mouse.MouseMove="ResizeGrip_MouseMove"></ResizeGrip>
</Grid>
</wtk:IToolDialog>
Any reason why you are not using ResizeContainer.RenderTransfrom instead of ResizeContainer.LayoutTransform? I.e. use
ResizeContainer.LayoutTransform = scale;
If you want the scale to be linear I think you should use
double scaler = 1 - diff / ResizeContainer.ActualWidth;
EDIT:
There is a bug in the code that causes the scaled control to "jump" in size if you try to resize more than once. I suggest you do the following:
Add a RenderTransform to your ResizeContainer grid:
<Grid.RenderTransform>
<ScaleTransform x:Name="transform" ScaleX="1" ScaleY="1" />
</Grid.RenderTransform>
Change the code in your MouseMove event handler to the following:
if (bottomResize)
{
double newBtmX = e.GetPosition(this).X;
double scaler = -(initBtmX - newBtmX) / grid1.ActualWidth;
initBtmX = newBtmX;
transform.ScaleX += scaler;
transform.ScaleY += scaler;
}
This way you change the scale by whatever small amount the mouse has moved while dragging. All child controls within the grid should scale as well.

Resources