Silverlight - polygon scaling and clipping - silverlight

I have a Canvas, to which I've added several thousand polygons.
I would like to be able to zoom in (which I'm doing via a ScaleTransform.
However I've been trying to use a Canvas.Clip as well to only draw a portion of the Canvas, but as soon as the ScaleTransform values are changed, the clipping stops working...
<Canvas Grid.Row="1" Margin="10" x:Name="cnvMain" Background="Transparent" >
<Canvas.Clip>
<RectangleGeometry x:Name="CanvasClip" Rect="0, 0, 300, 300"/>
</Canvas.Clip>
<Canvas.RenderTransform>
<ScaleTransform x:Name="CanvasScaleTransform" ScaleX="1" ScaleY="1"></ScaleTransform>
</Canvas.RenderTransform>
</Canvas>
And in my codebehind,
private void slScale_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
CanvasScaleTransform.ScaleX = slScale.Value;
CanvasScaleTransform.ScaleY = slScale.Value;
}
Am I doing anything obviously wrong?

The ScaleTransform (as all other transformations) is applied AFTER every other rendering. That means, first the cliprect is applied, then the scale transform. A solution would be to do the clipping one level higher, by putting another canvas around this one.

Put a border around your canvas and attach the clip region to the border rather than the canvas.

Related

WPF - show an image and set different clickable areas on it

the case is this one:
I have an image representing a schema, let's say a cross like the following
I need to include the image in a WPF UserControl and let the user click on each of the branches (red, green or blue...) and, according to the branch selected, do something different.
What would be the best way to solve this?
I tried with canvas but I don't find a way to trace correctly the background image with shapes (also because the real image is not so simple as the sample cross here)
thanks for any suggestion
It depends on the comlexity of shapes but it is not difficult to find the shape where mouse up event is fired by VisualTreeHelper.HitTest method.
Let's say there is a Canvas which has two Rectangles inside.
<Canvas Background="Transparent"
PreviewMouseUp="Canvas_PreviewMouseUp">
<Rectangle x:Name="Rect1" Canvas.Left="20" Canvas.Top="20"
Width="20" Height="20" Fill="Red"/>
<Rectangle x:Name="Rect2" Canvas.Left="80" Canvas.Top="80"
Width="20" Height="20" Fill="Green"/>
</Canvas>
Catching its PreviewMouseUp event, you can tell the Rectangle where that event is fired.
private void Canvas_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition((IInputElement)sender);
var result = VisualTreeHelper.HitTest((Visual)sender, position);
if (result.VisualHit is Rectangle rect)
{
Debug.WriteLine($"Hit {rect.Name}");
}
}

What would cause WPF DragDelta values to become suddenly cumulative with each call?

I have UserControl wrapping a zoomable canvas with shapes that the user can drag.
Recently I tried to make changes to follow the advice I got in this question; Instead applying one transform to the the whole canvas with shapes, I tried to apply it individually to each shape's Geometry. The idea was to eliminate zooming effects on font sizes and line thicknesses.
Unfortunately, although I have most of it working (shapes show up where they should) I seem to have broken the dragging of individual shapes across the canvas. I cannot explain why.
The symptom is that the DragDelta event handler is reporting cumulative values for each HorizontalChange and VerticalChange. So my shapes now zoom off the edge of the screen with even small movements. They use to not be that way.
(Setting the "Handled" boolean on the event args has no effect)
I've posted some code below. While I am grateful to anyone who bothers to read through it, I'm actually more interested in the general concept of how someone can make DragDelta report cumulative values at all. The documentation states that they're only supposed to be the values since the last call to DragDelta.
I cannot be the first person to make this mistake. Is this a common problem with a common cause?
Anyway if you are still reading, here's the detail. The shapes are represented by an interface, IShape.
public interface IShape
{
Geometry RelativeGeometry { get; } // Geometry relative to shape origin
Geometry AbsoluteGeometry { get; } // Geometry relative to canvas origin
}
The IShape objects are drawn in an ItemsControl on a Canvas inside my UserControl
<Canvas x:Name="Scene">
<ItemsControl x:Name="ShapesLayer" ItemsSource="{Binding Shapes}"
ItemTemplate={StaticResource ShapeTemplate}"/>
</Canvas>
This is the ItemTemplate the items control uses to draw an IShape
<DataTemplate x:Key="ShapeTemplate" DataType="{x:Type gci:IShape}">
<Canvas>
<!-- Visible shape (not selectable) -->
<Path Data="{Binding AbsoluteGeometry, {StaticResource TransformGeometry}, ConverterParameter={StaticResource Trans}}"
IsHitTestVisible="False"/>
<!-- Invisible but selectable thumb control for dragging shape -->
<Thumb">
<Thumb.Style>
<EventSetter Event="DragDelta" Handler="Shape_DragDelta"/>
</Thumb.Style>
<Thumb.Template>
<ControlTemplate TargetType="{x:Type Thumb}">
<Path Fill="Transparent" Stroke="Transparent">
<Path.Data>
<RectangleGeometry Rect="{Binding AbsoluteGeometry.Bounds}"
Transform="{StaticResource Trans}"/>
</Path.Data>
</Path>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
</DataTemplate>
This is the DragDelta handler for the shape thumb control in code-behind
private void Shape_DragDelta(object sender, DragDeltaEventArgs e)
{
if (!(sender is FrameworkElement fe) || !(fe.DataContext is IShape shape))
return;
// Move each selected shape by the delta.
var v = new Vector {X = e.HorizontalChange, Y = e.VerticalChange};
foreach (var s in Shapes.Where(shp => shp.IsSelected))
{
if (s.Offset(v) && !(_movedShapes.Contains(s)))
_movedShapes.Add(s);
}
e.Handled = true;
}
Finally, this is the transform, declared in my control's resources and used by all shapes. It builds off of custom Scale and OffsetX/OffsetY dependency properties in the control. My control sets those values in code-behind with mouse/touch handlers. They are propagated to this resource and thus to the shapes.
<TransformGroup x:Key="Trans">
<!-- "Root" is the name of the control. It exposes custom dependency
properties "ZoomScale, "OffsetX" and "OffsetY" which apply to the entire
control and therefore are not changed when a shape is dragged -->
<ScaleTransform CenterX="0" CenterY="0"
ScaleX="{Binding ElementName=Root, Path=ZoomScale}"
ScaleY="{Binding ElementName=Root, Path=ZoomScale}"
/>
<TranslateTransform X="{Binding ElementName=Root, Path=OffsetX}"
Y="{Binding ElementName=Root, Path=OffsetY}"/>
</TransformGroup>
<!-- Value Converter that applies a transform to a Geometry -->
<gcc:TransformGeometryConverter x:Key="TransformGeometry"/>

How to adjust a canvas that child with position (-1,-1) is displayed completely

NEW INFORMATION!!!
Meanwhile i have found a solution but there is a new problem.
The solution is to set the margin of the canvas in code-behind to a new object of type Thickness with top and left 1 or 2.
But the canvas is lying on a tabcontrol.
When i switch between tabs or make a mousedown on the canvas the margin is lost.
I'm working with VS2015 on a WPF-application and have a very curious problem.
I got in one of my WPF windows a canvas as parent for some child elements.
One of these elements is a rectangle which shall show the user the size of a DIN-A4 page.
It is added in code-behind to the children collection of the canvas.
Normally i would place that rectangle at position (0,0).
But because of some problems i have to trick and set the position to (-1,-1) like that:
public static System.Windows.Shapes.Rectangle GetRectangle(double top, double left, double width, double height)
{
var rectangle = new System.Windows.Shapes.Rectangle();
System.Windows.Controls.Canvas.SetLeft(rectangle, -1);
System.Windows.Controls.Canvas.SetTop(rectangle, -1);
rectangle.Width = Math.Round(GetSizeInPoint(width)) + 2;
rectangle.Height = Math.Round(GetSizeInPoint(height));
rectangle.StrokeThickness = 1;
rectangle.Stroke = System.Windows.Media.Brushes.Red;
return rectangle;
}
But the result of it is that just a small part of the top and left border of the rectangle can be seen.
Do i have a chance to "move" the canvas so that the rectangle is displayed completely?
Hereby another important problematic point is that the Grid named "grdProtocolDesigner" can be serialized to XML and saved in the database.
So a complete restructuring would be a big problem.
Here the relevant part of my XAML including the canvas:
<ContentPresenter x:Name="protocolContainer"
Grid.Row="1"
Grid.Column="0">
<ContentPresenter.Content>
<Grid x:Name="grdProtocolDesigner"
Grid.Row="1"
Grid.Column="0">
<ScrollViewer>
<!--Important! The background of this panel has to be "Transparent" so that drag'n'drop-events can work.
Otherwise the events are not fired.-->
<Canvas x:Name="protocolDesignerPanel"
AllowDrop="True"
Visibility="{Binding DesignerPanelsVisibility}"
Width="2200" Height="4000"
MouseEnter="designerpanel_MouseEnter"
MouseLeave="designerpanel_MouseLeave"
MouseDown="designerpanel_MouseDown"
MouseMove="designerpanel_MouseMove"
MouseUp="designerPanel_MouseUp">
<Canvas.RenderTransform>
<ScaleTransform x:Name="scaleProtocolLayout"/>
</Canvas.RenderTransform>
<Canvas.Background>
<ImageBrush ImageSource="../../pictures/CanvasBackground.png"
TileMode="Tile"
Stretch="None"
Viewport="0, 0, 10, 10"
ViewportUnits="Absolute" />
</Canvas.Background>
</Canvas>
</ScrollViewer>
</Grid>
</ContentPresenter.Content>
</ContentPresenter>

Get scaling factor of an image when the image goes out of grid

I have a grid that contains an image.
<Grid Name="grid1">
<Image Name="image1" Stretch="None" Source="C:\Users\User\Desktop\image.jpg"/>
</Grid>
If the size of image was greater than the size of the grid, I want to scale it manually by render transform to fit the grid. I don't want to use Stretch="Fill" because I need the scale factor.
Is there any way to detect the situation that an UIElement goes out of view?
I need your help.
You could simply set the Stretch property to Uniform (or perhaps Fill) and calculate the scaling factor from the ActualWidth of the Image and the Width of the ImageSource, whenever you need it. The sample below does the calculation in a SizeChanged handler, but it could be anywhere else.
<Image Name="image1" Stretch="Uniform" Source="C:\Users\User\Desktop\image.jpg"
SizeChanged="ImageSizeChanged"/>
The calculation looks like this:
private void ImageSizeChanged(object sender, SizeChangedEventArgs e)
{
var scale = image1.ActualWidth / image1.Source.Width;
}
As Uniform is the default value of the Stretch property, you wouldn't have to set it at all.
I'm not sure why you would want to rescale your image manually when WPF can do it for you...
Instead of putting the image straight into the grid, use a Viewbox control:
<Grid Name="grid1">
<Viewbox>
<Image Name="image1" Stretch="None" Source="C:\Users\User\Desktop\image.jpg"/>
</Viewbox>
</Grid>
The Viewbox will automatically scale the picture to fit inside the grid...

WPF. How to show only part of big canvas?

Lets say I have a canvas defined to be 1000x1000 big. Is it possible to only show a 100x100 part of it in a Viewbox(or a rectangel)?
Any help is apreciated.....
If you work with Brushes, you might want to take a look at Viewbox and Viewport in WPF
Edit: I just realised that Viewbox and Viewport are used for Brushes
This is not really appropiate in your situation. I looked it up, and I think you will like the Clip property on UIElement.
Since Canvas is also a UIElement, you can use the Clip property to simulate a viewport on your Canvas..
Click here for some simple Geometry types
I think you would suffice with a RectangleGeometry
<Canvas>
<Canvas.Clip>
<RectangleGeometry Rect="50,50,25,25" />
</Canvas.Clip>
</Canvas>
Edit #2:
Hehe ok.. if you want your total Canvas displayed, only smaller, perheps you should take a look and LayoutTransform. Then use a ScaleTranform to resize your Canvas ;).
<Canvas>
<Canvas.LayoutTransform>
<ScaleTransform CenterX="0" CenterY="0" ScaleX="0.5" ScaleY="0.5" />
</Canvas.LayoutTransform>
</Canvas>
Tweak the parameters until you receive the desired effect ;)

Resources