Find the applied ScaleTransform on a Control or UIElement?
I have a control sitting somewhere in my Window. At the root of this Window is a grid named MainGrid. I have a ScaleTransform
applied on MainGrid's LayoutTransform that I use to zoom in on all the contents of my Window when the window's size grows. However, one of my controls usesBitmapCache
on their canvases to optimize drawing performance. BitmapCache does not take into consideration any ScaleTransform
s that might be applied to the control, so if I'm zoomed in, the control appears blurry.
BitmapCache does have a RenderAtScale property I can use to increase the scale of the cached image. However, the problem I have is that I don't know of an elegant way to find out what that Scale value needs to be. For now, I have a property on my contr开发者_如何转开发ol so that my Window can pass its scale value to the control. However, I would like it if I didn't need to rely on some external source passing in a scale value.
Is there any way a Control can get a summary of all the ScaleTransforms applied to it?
You can use the PresentationSource
's RootVisual
to calculate the total transform of an element. The general pattern to find the scale applied between a parent and a child is
Visual child = /* some visual */ this;
Visual parent = PresentationSource.FromVisual(child).RootVisual;
// calculate the transform needed to go from the parent to the child coordinate spaces
var childTransform = parent.TransformToDescendant(child);
// use the transform to scale a 1x1 rectangle
// use Inverse as the transform is from Parent coordinates to child, we want
// child to parent
var unitRect = new Rectangle(0, 0, 1, 1);
var transformedUnit = childTransform.Inverse.TransformBounds(unitRect);
// transformedUnit.Width and transformedUnit.Height are now the scale factors (X and Y)
Debug.Assert(transformedUnit.Width == 0.25 && transformedUnit.Height == 0.25);
As Elad Katz said, I don't think there's any direct way to find out if a ScaleTransform
is applied to any parent without going through them.
However, if you know that the ScaleTransform
is applied to the MainGrid, then you can bind RenderAtScale
to the ScaleX
or ScaleY
of the MainGrid's ScaleTransform
. Maybe this is what you're already doing but I throw it in as a tip anyway
Probably the easiest way to reference the ScaleTransform
in the Binding is by naming it
<Grid Name="MainGrid">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="MainGridScaleTransform" .../>
</TransformGroup>
</Grid.LayoutTransform>
The Binding should then look similar to this
<BitmapCache RenderAtScale="{Binding ElementName=MainGridScaleTransform,
Path=ScaleX}"/>
Otherwise, you can get the ScaleTransform
in the Path with TransformGroup
<BitmapCache RenderAtScale="{Binding ElementName=MainGrid,
Path=(LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)}"/>
Or without TransformGroup
<BitmapCache RenderAtScale="{Binding ElementName=MainGrid,
Path=(LayoutTransform).(ScaleTransform.ScaleX)}"/>
Based on Elad Katz's recommendations, I have created some code to scan up the VisualTree to attempt to total any uniform ScaleTransform
s. If any one has some optimizations for this algorithms or can think of a few things I may not be considering, let me know. Again, my goal is to get a RenderAtScale that is actually apropriate given the currently applied ScaleTransform
s. OnRenderSizeChanged
seems to be an OK place to do this, as it happens AFTER all the layout stuff has run. But, perhaps there is a better place to trigger GetTotalTransformScale()?
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
double totalTransformScale = GetTotalTransformScale();
BitmapCache bitmapCache = (BitmapCache)MyCachedCanvas.CacheMode;
if (bitmapCache.RenderAtScale != totalTransformScale)
bitmapCache.RenderAtScale = totalTransformScale;
}
private double GetTotalTransformScale()
{
double totalTransform = 1.0d;
DependencyObject currentVisualTreeElement = this;
do
{
Visual visual = currentVisualTreeElement as Visual;
if (visual != null)
{
Transform transform = VisualTreeHelper.GetTransform(visual);
// This condition is a way of determining if it
// was a uniform scale transform. Is there some better way?
if ((transform != null) &&
(transform.Value.M12 == 0) &&
(transform.Value.M21 == 0) &&
(transform.Value.OffsetX == 0) &&
(transform.Value.OffsetY == 0) &&
(transform.Value.M11 == transform.Value.M22))
{
totalTransform *= transform.Value.M11;
}
}
currentVisualTreeElement = VisualTreeHelper.GetParent(currentVisualTreeElement);
}
while (currentVisualTreeElement != null);
return totalTransform;
}
You can always recursively go through all of the Control's parents and sum their ScaleTransform
.
I don't think you can do it any other way.
I know this is an old question, but I had a similar problem myself: I needed to make one type of control to ignore the ScaleTransform
on the top level.
UIElement
has a RenderTransform
property which is basically the ScaleTransform
effecting it.
精彩评论