Is it possible to control Composition XAML ElementVisual Clipping? - c#

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?

Related

Is the a MVVM WPF way to implement DrawingVisual based on an ObservableCollection

My Application displays a lot of lines and polygons/paths on a canvas.
My ViewModel holds a series of ObservableCollections that represent different items to be drawn.
The issue I have is the application is very slow to zoom and pan.
Zoom and pan is all taken care of using an IvalueConverter and converts from world coordinate system to the canvas coordinate system.
For this to work, I have to NotifyPropertyChange all objects visible on the screen to force them to be redrawn with the latest pan and zoom values.
It works very well for a few hundred lines, but for thousands it’s very slow. And if you zoom out so all objects are visible therefore subject to NotifyPropertyChange it’s almost unusable with over 10,000 lines.
I’m not using the polygons built-in features in any way as all handling, selection moving etc is taken care of in the viewmodel.
I therefore want to try and use DrawingVisual instead of Shapes as I understand they have much lower overheads but I can’t find any good MVVM examples of how to use them.
Examples I have seen show them being built in codebehind which isn’t how I think I should be using them.
Examples as follows:
//new DrawingVisual
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);
drawingContext.Close();
//creating a Host as follows:
public MyVisualHost()
{
_children = new VisualCollection(this);
_children.Add(CreateDrawingVisualRectangle());
_children.Add(CreateDrawingVisualText());
_children.Add(CreateDrawingVisualEllipses());
this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp);
}
Unless there is a WPF way to bind to my observable collection using DrawingVisual I would need to create and delete drawings objects in my models. Every-time a model is updated it would then have to update my drawingVisual. But then I’m creating View items in model which can’t be the correct way.
Can anybody advise how I should go about implementing DrawingVisual instead of Shapes in my MVVM application?
Here is an extract of the code I am currently using that uses Shapes
XAML
<ItemsControl x:Name="Catchments">
<ItemsControl.Resources>
<CollectionViewSource x:Key="CatchmentPolygons" Source="{Binding Path=NetworkMain.Catchments}"></CollectionViewSource>
<DataTemplate DataType="{x:Type cad:Catchment}">
<Polygon
Stroke="{Binding IsSelected, Mode=OneWay, Converter={StaticResource ObjectColour}, ConverterParameter=Catchment}"
StrokeThickness="1"
Visibility="{Binding Visible, Mode=OneWay, TargetNullValue='Hidden'}"
Points="{Binding Points, Mode=OneWay, Converter={StaticResource CollectionPointConverter}}">
<Polygon.Fill>
<SolidColorBrush
Color="{Binding IsSelected, Mode=OneWay, Converter={StaticResource ObjectColour}, ConverterParameter=Catchment}"
Opacity=".25"
>
</SolidColorBrush>
</Polygon.Fill>
</Polygon>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource CatchmentPolygons}}"></CollectionContainer>
</CompositeCollection>
</ItemsControl.ItemsSource>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
ClipToBounds="true">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
My Observable Collections in my ViewModel:
public ObservableCollection<Conduit> Conduits { get; set; } = new();
public ObservableCollection<Node> Nodes { get; set; } = new();
public ObservableCollection<Catchment> Catchments { get; set; } = new();
EDIT
Screenshots of the Canvas with a zoomed in and zoomed out view:
Zoomed out:
The images have scaled a little bit but in reality, line thicknesses, node sizes and arrows remain constant as you zoom in and out. Only the projected coordinates of nodes change.
Zoomed in:
You could add a dependency property to your visual host and bind this one to the source property of the view model.
Then the visual host can create a DrawingVisual per item in the source collection

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.

Scrolling problems when using ScaleTransform in Panorama/Pivot/RadSlideView item

I'm trying to implement zoom-functionality in a RadSlideView ItemTemplate. I'm doing this by using a ViewportControl with a Canvas and then applying a RenderTransform (ScaleTransform) to a StackPanel in the Canvas. Similar to the SDK-sample found here.
The problem I have is that the ScaleTransform seems to be affecting the swipe-gesture used to change item in the SlideView/Panorama/Pivot control. E.g. if the ScaleTransform is set to 0.1 it seems like I only need to swipe 1/10th of the length to change item compared to using a ScaleTransform of 1.0.
I found that if I set IsHitTestVisible to false on the ItemTemplate the swiping works like I want. But this is not a solution since I sometimes need to be able to pan the content vertically while still being able to change item by swiping horizontally.
So my question is how can I solve this?
For reference the XAML looks like this:
<Controls:RadSlideView Name="SlideView" ItemsSource="{Binding Pages}" IsLoopingEnabled="False" SelectionChanged="RadSlideView_SelectionChanged" CacheMode="BitmapCache" ManipulationStarted="SlideView_ManipulationStarted" ManipulationCompleted="SlideView_ManipulationCompleted" ManipulationDelta="SlideView_ManipulationDelta">
<Controls:RadSlideView.ItemTemplate>
<DataTemplate>
<ViewportControl x:Name="SlideViewViewport" ViewportChanged="SlideViewViewport_ViewportChanged" Loaded="SlideViewViewport_Loaded">
<Canvas>
<StackPanel>
<Image Source="{Binding Image}" Stretch="Fill" Width="{Binding ElementName=SlideView, Path=DataContext.PageWidth}" Height="{Binding ElementName=SlideView, Path=DataContext.PageHeight}" CacheMode="BitmapCache"/>
<StackPanel.RenderTransform>
<ScaleTransform x:Name="xform"/>
</StackPanel.RenderTransform>
</StackPanel>
</Canvas>
</ViewportControl>
</DataTemplate>
</Controls:RadSlideView.ItemTemplate>
I have also looked at Teleriks RadPanAndZoom-control to avoid implementing my own zoom-functionality, but since I sometimes need to place two pictures side by side and zoom them as if they were one I don't think I can use it.
The problem is that ScaleTransformation scales your picture, but doesn't change it's height and width. Only if Height And Width are overflowing scrollviewer you can scroll the content

location of elements in a panel wpf

How to find the child elements positions in a stack panel.
<StackPanel Orientation="Horizontal">
<ToggleButton Width="20"
Height="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="Visible" />
<TextBlock Margin="5"
VerticalAlignment="Center"
FontSize="15"
Text="Selection Mode" />
</StackPanel>
How to find the X,Y position of Toggle button and Text block?
You could always use TranslatePoint to translate coordinates relative to one UIElement to coordinates relative to another UIElement:
var toggleButtonPosition = toggleButton.TranslatePoint(new Point(0, 0), stackPanel);
var textBlockPosition = textBlock.TranslatePoint(new Point(0, 0), stackPanel);
The above code translates the point (0, 0) relative to the respective control to coordinates relative to the containing StackPanel, and hence gives the position of each control inside the StackPanel.
Basically, the position of a control is determined by the control which holds it, the margin property, the alignement and such.
You could use this to determine the position of the child control.

Draw text on a shape in a wpf

Some of you maybe find this question dull but I am still not deeply accustomed to wpf drawing. I want to add formatted text on a Rectangle which moves around on a canvas and I have got a hint to override the UIElement.OnRender method. However I do not know if I should override the canvas class or the Shape class. In any correct case, to what refers the drawingContext parameter of the method as described in the example: http://msdn.microsoft.com/en-us/library/bb613560.aspx#FormattedText_Object ?
Is the text ultimately assigned to the shape or is it a visual temporary effect that cannot move along with the shape on the canvas?
Is there any further effective means of drawing text on a shape?
You can draw Text on top of a Rectangle by placing both controls in a parent container that allows controls to overlap, such as a Grid or a Canvas
<Grid>
<Rectangle Fill="Red" Stroke="Black"
HorizontalAlignement="Stretch" VerticalAlignment="Stretch" />
<Label Content="Test"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
You can then apply whatever formatting you want to the Label, the Rectangle, and you can move the group around by setting the positioning of the Grid
Rachel's answer is correct, although you can extend it a bit, have some UserControl defined as:
And in the codebehind define 1. Label:String DependencyProperty, Shape:UIElement DependencyProperty.
Handle the Shape's change event and call:
private void UpdateShape()
{
grdShapeContainer.Children.Clear();
if(this.Shape != null)
{
grdShapeContainer.Children.Add(this.Shape);
}
}
This way you will be able to make things dynamic.
Regards,
Artak
You might also want to look into ZIndex property which can be set on objects like Grid (<Rectangle Background="Black" Grid.ZIndex = 99 /> for instance would put it overtop other items) which useful for making things like "loading" screens.

Categories

Resources