MeasureOverride and ArrangeOverride are not called - c#

I'm using ZoomControl from WPFExtensions. Source code is here.
I've added adorner with OnRender method:
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawEllipse(
new SolidColorBrush(Colors.CornflowerBlue),
null,
new Point(1292, 100),
100, 100);
}
using ZoomControl xaml:
<Controls:ZoomControl x:Name="zoomControl1" Background="AliceBlue">
<Canvas Width="{Binding Path=SModel.Width}" Height="{Binding Path=SModel.Height}">
<Image HorizontalAlignment="Center"
Name="image1" Stretch="Fill"
VerticalAlignment="Center" Source="/Z;component/Images/00000006.jpg"
Margin="0,0,0,0" Canvas.Left="1"
Width="{Binding Path=SModel.Width}" Height="{Binding Path=SModel.Height}">
<Image.LayoutTransform>
<RotateTransform Angle="{Binding Path=SModel.Angle}" />
</Image.LayoutTransform>
</Image>
</Canvas>
</Controls:ZoomControl>
Applying adorner:
public Window1()
{
InitializeComponent();
image1.Loaded += loaded;
}
private void loaded(object sender, RoutedEventArgs e)
{
var adorner = new SplitAdorner(image1);
AdornerLayer.GetAdornerLayer(image1).Add(adorner);
}
When I drag Image control, my adorner is not moving.
I've tried overriding ArrangeOverride and MeasureOverride:
protected override Size MeasureOverride(Size constraint)
{
Debug.WriteLine("measureoverride");
InvalidateVisual();
return base.MeasureOverride(constraint);
}
protected override Size ArrangeOverride(Size finalSize)
{
Debug.WriteLine("arrangeoverride");
InvalidateVisual();
return base.ArrangeOverride(finalSize);
}
But no effect. There is not anything in Output window and adorner is not moving.
When I'm zooming - everything is OK though. Adorner changes it's position accordingly to Image control changes.
The problem is in my code or in ZoomControl?
Example app is here.
SOLUTION:
I had to put my canvas into AdornerDecorator:
<Controls:ZoomControl x:Name="zoomControl1" Background="AliceBlue">
<AdornerDecorator>
<Canvas Width="{Binding Path=SModel.Width}" Height="{Binding Path=SModel.Height}">
<Image HorizontalAlignment="Center"
Name="image1" Stretch="Fill"
VerticalAlignment="Center" Source="/Z;component/Images/00000006.jpg"
Margin="0,0,0,0" Canvas.Left="1"
Width="{Binding Path=SModel.Width}" Height="{Binding Path=SModel.Height}">
<Image.LayoutTransform>
<RotateTransform Angle="{Binding Path=SModel.Angle}" />
</Image.LayoutTransform>
</Image>
</Canvas>
</AdornerDecorator>
</Controls:ZoomControl>
Description is here.

Ok, i looked into your example app and checked it with snoop. So it seems that the transformation is somehow applied to your adorner aswell, but the visual is not correctly updated. Panning works in fact, its just that your adorner is not properly invalidated.
One quick idea is to give your adorner dependency property for zoom, translate x, translate y, and set the property flag "AffectsRender" and bind them from the zoom control to your canvas. Now everytime your zoom control modifies one of these properties, the adorners properties are updated via binding and directly invalidated via the "AffectsRender" flag.
[First answer, but not valid]
Are you applying the transform of the zoom control to your adorner? Because an adorner is not placed on the control, in fact it is registered in the next AdornerDecorator, found upwards beginning on the provided control. My guess is that the only decorator(or layer) found is the default one on the window. Apply the transform of the control to your adorner and it should work. Or you could place an AdornerDecorator inside your Zoomcontrol, but to be honest i'm not sure if this has the desired effect.

Related

Is it possible to control Composition XAML ElementVisual Clipping?

I am using UWP and working with the Composition API to programmatically scale child text visuals that are nested in a typical XAML hierarchy. The textblocks in our app are contained in things like borders and a number of those borders are items contained in a GridView.
In many of the scenarios I am experiencing clipping of the associated text visual as it scales to be larger than some of the XAML containers that host the elements and I would like the visual to not get clipped as it scales to be larger than its parent.
Here is a barebone example that demonstrates some of the problems I am seeing…
My test app starts as a blank UWP app and the root grid of my page contains the following Gridview:
<GridView >
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" CornerRadius="5" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 1" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 2" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border PointerPressed="Border_PointerPressed" Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 3"/>
</Grid>
</Border>
</GridViewItem>
</GridView>
The codebehind file contains the following additional using statements, a variable declaration, variable initialization in page constructor and this event handler:
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml.Hosting;
Compositor compositor;
public MainPage()
{
this.InitializeComponent();
compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
}
private void Border_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var content = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(sender as FrameworkElement, 0), 0);
var visual = ElementCompositionPreview.GetElementVisual(content as FrameworkElement);
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.InsertKeyFrame(0f, new Vector3(1.0f, 1.0f, 0.0f));
animation.InsertKeyFrame(0.5f, new Vector3(3.0f, 3.0f, 0.0f));
animation.InsertKeyFrame(1f, new Vector3(1.0f, 1.0f, 0.0f));
animation.Duration = TimeSpan.FromMilliseconds(5000);
visual.StartAnimation(nameof(visual.Scale), animation);
}
When you run the app and click on each of the strings you should initially notice that the first string behaves differently than the other two string.
The first string gets cropped at the Border's bounding box whereas the other two strings do not.
Also note that the other two strings appear to scale past the bounds of last item and out into the page, but that turns out to probably be due to the gridview autosizing to fill the page.
The difference between the first string and the other two is that the border has a corner radius property set on it. We use cornerradius setting in our application, so it would be nice to know if there is a way to override or control this behavior so that it doesn't clip the visual as it scales.
The other behavior that is causing us problems is that at the GridView bounds is another boundary that the visual is clipping at as it scales. If you set any property (like HorizontalAlignment="Center") on the Gridview that causes it to size itself to only be as big as it needs to be, then the visual gets cropped at the controls boundaries.
Is there anything within the Compositional API that allows me to prevent or influence this clipping behavior?

Irregular behavior - XAML / UWP Community Toolkit Scale animation

Problem : I am using UWP Community Toolkit Scale animation and it works as expected for most of the images in the GridView, but for some the image goes out of bounds . (Please see the image below)
I have detected that the issue happens when the image width is more than 2x (2 times) the height of the image. That is when the image is very wide.
Code
I am using a user control as data template
Xaml :
<!-- Grid View -->
<GridView x:Name="gridView" SelectionChanged="gridView_SelectionChanged">
<GridView.ItemTemplate>
<DataTemplate>
<local:GridViewMenu/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- GridViewMenu User Control markup -->
<Grid>
<StackPanel>
<Image Source="{Binding webformatURL}" Stretch="UniformToFill" PointerEntered="image_PointerEntered" PointerExited="image_PointerExited"/>
</StackPanel>
</Grid>
C# Code :
private void image_PointerEntered(object sender, PointerRoutedEventArgs e)
{
Image img = sender as Image;
img.Scale(centerX: (float)(grid.ActualWidth / 2),
centerY: 100,
scaleX: 1.2f,
scaleY: 1.2f,
duration: 500, delay: 0).StartAsync();
}
private void image_PointerExited(object sender, PointerRoutedEventArgs e)
{
Image img = sender as Image;
img.Scale(centerX: (float)(grid.ActualWidth / 2),
centerY: 100,
scaleX: 1f,
scaleY: 1f,
duration: 500, delay: 0).StartAsync();
}
Result (Top left image is not scaling as expected, that is, it is going out of bounds)
How can I solve this issue ?
The scale animation of UWP Community Toolkit package actually use the CompositeTransform class for scaling. According to the description of Transforms and layout section:
Because layout comes first, you'll sometimes get unexpected results if you transform elements that are in a Grid cell or similar layout container that allocates space during layout. The transformed element may appear truncated or obscured because it's trying to draw into an area that didn't calculate the post-transform dimensions when dividing space within its parent container.
So that the parts overflow the bound that being truncated are unexpected. In another words, the image goes out is the transform expected. The current way you are using to meet your requirements is not reliable. If you change width-height ratio of GridViewMenu to 1.0 , you may find more images that width-height ratio larger than 1.0 will go out.
For a solution inside GridView, you could consider to use the ScrollViewer to zoom in the image instead, which can ensure the image is limited in a fixed area. For example:
<Grid x:Name="grid">
<ScrollViewer
x:Name="currentscroll"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<Image
x:Name="myImage"
Width="300"
Height="180"
PointerEntered="image_PointerEntered"
PointerExited="image_PointerExited"
Source="{Binding webformatURL}"
Stretch="UniformToFill">
</Image>
</ScrollViewer>
</Grid>
Code behind:
private void image_PointerEntered(object sender, PointerRoutedEventArgs e)
{
currentscroll.ChangeView(0, 0, 1.2f );
}
private void image_PointerExited(object sender, PointerRoutedEventArgs e)
{
currentscroll.ChangeView(0, 0, 1.0f);
}
You can try to use clipping:
<Grid>
<StackPanel>
<Image Source="{Binding webformatURL}" Stretch="UniformToFill"
PointerEntered="image_PointerEntered"
PointerExited="image_PointerExited">
<Image.Clip>
<RectangleGeometry Rect="0,0,300,150" />
</Image.Clip>
</Image>
</StackPanel>
</Grid>
300 and 150 would be the width and height of the grid item.
Otherwise it looks like a bug in the UWP Community Toolkit, it would be best to report it as an issue on GitHub.

How can I get the new position and size of a UIElement after performing a RenderTransform (Scale & Translate)?

I am performing scaling, translation and rotation on an image using RenderTransform. Transformations are not done using mouse events, rather, the user will click a button and then the image will scale/translate/rotate on a fixed value.
My problem is I want to determine the new position/size of the image each time a scaling, translation or rotation is performed. So I added Changed events on the code-behind. The question is how do you get the new position/size?
Please take a look at what I've done so far:
XAML:
<Border x:Name="mainImageBorderCtrl" ClipToBounds="True">
<Grid x:Name="imageGridCtrl">
<Grid.RenderTransform>
<TranslateTransform Changed="TranslateTransform_Changed"/>
</Grid.RenderTransform>
<Image x:Name="mainImageCtrl" RenderTransformOrigin="0.5, 0.5" Source="{Binding Image}">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform Changed="ScaleTransform_Changed"/>
<RotateTransform />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
Please note that I separated TranslateTransform from ScaleTransform and RotateTransform so that the orientation won't be affected.
The following are the 2 changed events. In here, I expect to get the new position/size every time a scale or a translate has occurred. But the bounds values do not change.
Code-behind
private Rect bounds;
private void TranslateTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
private void ScaleTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
EDIT: My goal here is to restrict the translation(panning) of the Image inside the Border control, that's why I need to get the bounds of the Image after each transformation, whatever the transformation is, so that I can check whether the bounds of the Image exceeds the Border.
TranslateTransform is generating correct bounds. However, for ScaleTransform you are getting bounds on control imageGridCtrl but the transform is added as child of control mainImageCtrl.
So you should use mainImageCtrl in place of imageGridCtrl to get correct bounds:
bounds = mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize));
The transform change events fire too early. You should hook up to LayoutUpdated event of the image control. Then try:
mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize))

Two Image Layers and OpacityMask

I'm trying to crop a circle from one image, and put it on top another image in WPF.
The Circle's center changes according to the mouse movements, and needs to be bounded dynamically.
I tried to position two images on top of each other, and use a third image that I draw in real time as an opacity mask.
Could you please provide short code to solve this problem efficiently ?
The code below describes what you can do with an OpacityMask. It's a little counterintuitive, because we expect a XAML rendering to layer elements bottom-to-top.
However, in this case you want your "background" image to layer on top of the foreground, because the OpacityMask will serve to display only that portion of the foreground described by the position and size of the VisualBrush, rendering the rest transparent. It's given as follows:
<Grid x:Name="MainGrid" MouseMove="Grid_MouseMove">
<Rectangle Fill="Red" ></Rectangle>
<Rectangle Fill="Green">
<Rectangle.OpacityMask>
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Ellipse Width="40" Height="40" StrokeThickness="1" Fill="Black" />
</VisualBrush.Visual>
<VisualBrush.RelativeTransform>
<TransformGroup>
<TranslateTransform x:Name="OpacityFilterTransform" X="1" Y="1"/>
</TransformGroup>
</VisualBrush.RelativeTransform>
</VisualBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
Then, this event handler code computes the position of the ellipse and applies it to the OpacityFilter's TranslateTransform object, giving you control over the position of the image.
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this);
var height = MainGrid.ActualHeight;
var width = MainGrid.ActualWidth;
// with the position values, interpolate a TranslateTransform for the opacity mask
var transX = position.X / width;
var transY = position.Y / height;
OpacityFilterTransform.X = transX - 0.5;
OpacityFilterTransform.Y = transY - 0.5;
}
This solution should work for any descendant of Visual you care to layer.

Horizontal image scaling with ScaleTransform in WPF

I have an image in a WPF window with the default Stretch setting, Uniform, and am making an attempt to make it fill the screen horizontally. I do not wish to use a different Stretch setting as this is supposed to a learning experience. The image dimensions being loaded are 420x800. This is the XAML for the window..
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Red" Height="1200" Width="840">
<Image Name="Image" Source="{Binding SourceUri}">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="Scale" />
<TranslateTransform x:Name="Translate" />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Window>
On the code-behind, I am attempting to calculate the scaling to zoom the image to fill the horizontal screen and I am using translate transform the move it to the center of the screen. The following bit of code is obviously wrong...
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApplication1 {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = this;
double ImageWidth = 420;
Scale.ScaleX = Width / ImageWidth;
Translate.X = -(ImageWidth / 2);
}
public string SourceUri {
get {
return #"C:\Users\Roel\Desktop\test.png";
}
}
}
}
I am attempting to understand how stretching and transformations are working together but I am having difficulty with this. I would appreciate all insights, even references to detailed explanations, as I have trouble finding any informational source explaining clearly and concisely how the transformations are applied.
You would usually just do this:
<Image Name="Image" Source="{Binding SourceUri}" Stretch="Fill"/>
In case you really need to calculate the stretch transformation manually, you would only need a ScaleTransform, no TranslateTransform, and you would put that into the LayoutTransform of the Image control. Moreover, the Image control would have to be placed into a Grid, which provides the size of the Windows's "client area". You can't calculate anything based on the Window's Width (or ActualWidth) as that includes the width of the Window's borders.
<Grid SizeChanged="Grid_SizeChanged">
<Image Name="image" Source="{Binding SourceUri}">
<Image.LayoutTransform>
<ScaleTransform x:Name="scale"/>
</Image.LayoutTransform>
</Image>
</Grid>
In the Grid's SizeChanged handler you would calculate the scaling as shown below.
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
scale.ScaleX = e.NewSize.Width / image.Source.Width;
scale.ScaleY = e.NewSize.Height / image.Source.Height;
}

Categories

Resources