problem with ContainerVisual.Transform - wpf

in my custom control i have a ContainerVisual object and a DrawingVisual under it.
I override ArrangeOverride and calculate the rectangle that i want to draw in based on the given size and the control's padding.
after that i set my ContainerVisual object's transform to the upper left corner of the rectangle so that the methods that render the drawing would not have to take account of the rectangle and assume that the drawing origin is at point 0,0.
this does not work, and the drawing is displaced. if instead i set transform of the DrawingVisual object it works and the rectangle is displayed the way it is supposed to be.
i thought that if i set transform on the container, it will automatically be applied to the visuals under it. is that so?
thanks for any help
EDIT: Updated the source code to show complete code.
class MyControl : Control
{
private readonly ContainerVisual container = new ContainerVisual();
private readonly DrawingVisual drawing = new DrawingVisual();
private Rect rect;
private void RenderDrawing()
{
using (var c = drawing.RenderOpen())
{
var p = new Pen(new SolidColorBrush(Colors.Black), 1);
c.DrawRectangle(null, p, new Rect(0, 0, rect.Width, rect.Height));
}
}
protected override Size ArrangeOverride(Size s)
{
var h = Math.Max(0, s.Height - Padding.Top - Padding.Bottom);
var w = Math.Max(0, s.Width - Padding.Left - Padding.Right);
var r = new Rect(Padding.Left, Padding.Top, w, h);
if (rect != r)
{
rect = r;
container.Clip = new RectangleGeometry(rect);
container.Transform = new TranslateTransform(rect.Left, rect.Top);
// replace the line above with the following line to make it work
// drawing.Transform = new TranslateTransform(rect.Left, rect.Top);
RenderDrawing();
}
return s;
}
protected override Visual GetVisualChild(int index)
{
return container;
}
protected override Size MeasureOverride(Size s)
{
return new Size();
}
protected override int VisualChildrenCount
{
get { return 1; }
}
public MyControl()
{
container.Children.Add(drawing);
AddVisualChild(container);
}
}
<Window x:Class="MyApp.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:c="clr-namespace:MyApp"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<c:MyControl Padding="20" />
</Grid>
</Window>

Explanation of strange clipping behavior
Now that you have posted your full source code I was finally able to see what you were seeing. Your problem isn't in the transform at all: It is in the clip!
If you comment out the container.Clip assignment statement, you get identical results no matter whether you put the transform on container or drawing
If you uncommented container.Clip assignment statement, the clipping region is perfectly centered on when the drawing is transformed, but when the container is transformed the clipping area is offset, so that only the lower and right lines of the rectangle were visible (and not all of those)
The reason this occurs is that the geometry specified for container.Clip is part of the container, so it is affected by container.Transform but not drawing.Transform:
This can be better understood by looking at the upper-left corners of the container, drawing, rectangle, and clip area relative to the upper-left corner of the window:
When you set the transform on the drawing:
Container is at (0,0) relative to window (null transform)
Clip area is at (20,20) relative to window (null transform + RectangleGeometry)
Drawing is at (20,20) relative to window (null transform + TranslateTransform)
Rectangle is at (20,20) relative to window (null transform + TranslateTransform + 0,0)
When you set the transform on the container:
Container is at (20,20) relative to window (TranslateTransform)
Clip area is at (40,40) relative to window (TranslateTransform + RectangleGeometry)
Drawing is at (20,20) relative to window (TranslateTransform + null transform)
Rectangle is at (20,20) relative to window (TranslateTransform + null transform + 0,0)
So your problem isn't that the transform isn't happening: It is that the transform is moving the clip area too, so the clip area no longer coincides with the rectangle and you can only see two sides of the rectangle.
Answer given for original code (retained because it has some useful explanation)
In fact, the code you posted never uses "container" so all you will see is a blank screen.
In your actual code you are using "container" incorrectly, preventing the events from occurring in the correct sequence to cause its Transform to be picked up and passed to the MIL layer.
Remember that when a Visual has a Transform set, it is not the visual itself but that Visual's visual parent that actually handles that transform. For example, if you render a page to XPS using ReachFramework or do hit testing, the Transform on the outermost Visual is ignored.
Your understanding is correct: If your visual tree is built following all the rules, it doesn't matter whether your transform is on your "container" or your "drawing".
Since you are using Control anyway, I'm curious why you don't just let the normal UIElement-based layout system handle your layout needs.
First update (retained for the same reason)
Thanks for the code correction. It is as I suspected: You are building your visual tree incorrectly. If you are using AddVisualChild you also must also override GetVisualChild and VisuaChildrenCount. This is because Visual does not store a list of children: It is up to the subclass (your class) to do this. What is happening is:
When you call AddVisualChild the container's transform is null so that is what is passed down to MILCore.
Later when you change the container's transform, it uses its parent pointer (that was set in AddVisualChild) to signal that its transform data must be refreshed. This update requires part of the visual tree to be scanned using GetVisualChild and VisualChildrenCount.
Since you didn't implement these methods this part of the update fails.
You say you are "new to WPF." Are you aware that you are playing with some of WPF's most low-level and esoteric features, ones that would never be used in a most ordinary WPF applications? It is equivalent to starting to learn programming using machine language. Normally you would use templates with Path, Rectangle, etc for this purpose. Sometimes you might go lower level and use a DrawingBrush with a DrawingGroup containing GeometryDrawings, etc. But you would almost never go all the way down to DrawingVisual and RenderOpen! The only time you would do that is when you have huge drawings consisting of millions of individual items and so you want to bypass all the layout and structure overhead of the higher layers for absolute maximum performance.
Manipulating the visual tree yourself (AddVisualChild, etc) is also an advanced feature. I always recommend people new to WPF stick with UIElement and above for the first few months, using Control with templates. I recommend they use Path and other shape subclasses for their drawings, and use VisualBrushes when advanced drawing effects are needed.
Hope this helps.

the problem is with the container.Clip. it should be
container.Clip = new RectangleGeometry(new Rect(0, 0, w, h));

Related

Pinch-Zoom: Get Touch Position relative to Object

I want to make a Touch-Viewer where I want to zoom into the point between my finger.
I have a viewbox with content-presenter inside a grid:
<Grid IsManipulationEnabled="True">
<Viewbox x:Name="PART_Viewbox">
/// Content
</Viewbox>
</Grid>
Zoom in with the scrollwheel of my mouse into the point where the mouse is, i solved within the following methods:
var position = e.GetPosition(PART_Viewbox);
matrix.ScaleAtPrepend(scale, scale, position.X, position.Y);
The method e.GetPosition ist only available in MouseEventsArgs and not in ManipulationDeltaEventArgs.
So how can i get the relative position in touch-mode?
When i take the e.ManipulationOrigin from ManipulationDeltaEventArgs and the grid is bigger then the Viewbox it translates the content of the viewbox somewhere else while zooming in or out.
The most examples showing zooming into the middle of the content (image). Thats not what I want.
The class FrameworkElement derives from UIElement. There is a method TranslatePoint which translates a point relative to a element.
In ManipulationDeltaEventArgs is a Property ManipulationOrigin which represents the point between the two fingers in a pinch zoom. An other important property is the ManipulationContainer that contains the element on that the manipulation is executed.
So in my case i can do following:
// Typecast ManipulationContainer to FrameworkElement and get point around the finger
Point position =((FrameworkElement)e.ManipulationContainer)
.TranslatePoint(e.ManipulationOrigin, _Viewbox);
// Center the new point in account to previous manipulations
position = matrix.Transform(position);
// do the transformation
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, position.X, position.Y)
I found this hint in the sourcecode of https://multitouchtransformbehavior.codeplex.com/

Resize Custom Control after Rotation Transformation

I've got a custom control that draws a graph. A new requirement has arrived, and one part of a solution is rotating the graph. There are other changes inside the control, but these are relatively simple and won't affect this issue.
I can rotate the control as follows:
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
InitStandard();
drawXaxis();
drawYaxis();
drawZAxis();
Titles();
Generate();
drawGridLines();
if (UsedAxisType == AxisType.FloorPlot)
{
gdRootGrid.RenderTransformOrigin = new Point(.5, .5);
gdRootGrid.RenderTransform = new RotateTransform(90);
}
}
This rotates with no problem - I now need to change the width & height of the control to match the new dimensions created with the rotate. I can't do that here as setting the width & height of the control in this event causes a rendering loop.
How do I do this properly?
Try
gdRootGrid.LayoutTransform = new RotateTransform(90);
instead.

Custom line drawing in WPF

I'm trying to manually draw a line in WPF by overriding the OnRender method of a control and calling the DrawLine method on the given DrawingContext. I read somewhere that this method call doesn't instantly draw the line, but I can't work out how to actually cause the line to appear.
I've tried using a combination of PathGeometry, LineSegments, Line and Polyline controls. I could draw what I wanted then, but offsets weren't quite right (i.e. when drawing a line, it was fine, when drawing a polyline, everything became incorrectly offset).
Any advice on this would be great.
EDIT
Pen Code
private static readonly Pen LinePen = new Pen(new SolidColorBrush(Colors.Green), 3.0d);
private static readonly Pen WayPointPen = new Pen(new SolidColorBrush(Colors.Gray), 3.0d);
Render Code
protected override void OnRender(DrawingContext drawingContext)
{
// Draw way points
this.DrawWayPoints(drawingContext);
if (mDrawing)
{
// Draw current line
this.DrawCurrentLine(drawingContext);
}
}
private void DrawCurrentLine(DrawingContext context)
{
if(mStartPoint.HasValue && mEndPoint.HasValue)
{
// Draw the line
context.DrawLine(LinePen, mStartPoint.Value, mEndPoint.Value);
}
}
private void DrawWayPoints(DrawingContext context)
{
if (mWayPoints.Count < 2)
{
return;
}
// Draw all points
for (int i = 0; i < mWayPoints.Count - 1; i++)
{
var start = mWayPoints[i];
var end = mWayPoints[i + 1];
// Draw the line
context.DrawLine(WayPointPen, start, end);
}
}
EDIT
Test Project: http://dl.dropbox.com/u/12763956/DrawingTest.zip
(Test Project written in Visual Studio 2010)
Usage:
- Left click within the raised area to add points to the list.
- Right click to end drawing and clear points.
Note: Custom drawn lines (in OnRender override) do not appear.
There are actually two issues here. The first is your Canvas's Background covers up anything you'd draw on your DrawingControl. So if you set the Canvas Background to Transparent, you can temporarily work around that issue.
The second issue is you need to call InvalidateVisual after you add a point to your collection to force it to redraw.
You would probably need to add another control that appears on top of the Canvas, and render the lines there. Or you'd need to render the Background yourself in the DrawingControl.OnRender method.

WPF: How to crop/clip a Drawing or a DrawingImage?

I have a function receiving a Drawing that I need to partially expose as a DrawingImage (i.e.: its position and size will be reduced/changed to fit in a target area).
How can I crop/clip a region of the original Drawing?
Or maybe it is easier to do that after the transformation to DrawingImage (how clip that DrawingImage)?
The solution was to encapsulate the original Drawing in a DrawingGroup and then apply a clipping geometry...
public DrawingGroup MyClippingFunc(Drawing OriginalDrawing, Rect ClippingArea)
{
var Group = new DrawingGroup();
Group.Children.Add(OriginalDrawing);
Group.ClipGeometry = new RectangleGeometry(ClippingArea);
return Group;
}
This is another way to do it, using the InkCanvas StrokeCollection class as an example.
using (DrawingContext drawingContext = drawingGroup.Open())
{
drawingContext.PushClip(new RectangleGeometry(yourRectangleObject));
Strokes.Draw(drawingContext);
drawingContext.Pop();
}
I am a little confused on what you are asking but maybe my answer to this similar question will help?
How can I use a PathGeometry as a mask for a BitmapSource (or any image data)?

How to clone Silverlight visual tree structure

I have the same problem as the question stated in "Printing in Silverlight 4".
To get around the problem, I have tried to scale transform root of my visual tree before printing.
void document_PrintPage(object sender, PrintPageEventArgs e)
{
var renderScale = 1.0D;
if (LayoutRoot.ActualWidth > e.PrintableArea.Width)
renderScale = e.PrintableArea.Width/LayoutRoot.ActualWidth;
var scaleTransform = new ScaleTransform();
scaleTransform.ScaleX *= renderScale;
scaleTransform.ScaleY *= renderScale;
e.PageVisual = LayoutRoot;
e.PageVisual.RenderTransform = scaleTransform;
}
Now above code correctly prints out with silverlight visuals fit on a piece of paper.
The problem now is that LayoutRoot itself is now scaled down on the screen.
The question is, is there a way for me to create a clone of LayoutRoot before applying scale transform?
My walk-around is to applying the scale tranformation again after printing but I'd like to know if there is a way to clone visual tree
My goodness, thanks for the question. I had the same problem but tried to fiddle about with setting the dimensions of a container (that is already in the visual tree) to the printable area, which does not work, as another layout pass seems to be required. ScaleTransform does work here however instantly.
I'm fine with the "work around" by just doing a myContainer.ClearValue(FrameworkElement.RenderTransformProperty) in the EndPrint event. Trying to clone the visual tree will yield a plethora of other issues (I have lazy loading content etc).
Check out this link for details on silverlight object clone.
also just another idea would using xamlreader/writer to read the xaml string and creating an in-memory copy of the visual tree work.
for ex
If your xaml has button called originalbutton, using the code below you will have a copy of the button in readerLoadButton
// Save the Button to a string.
string savedButton = XamlWriter.Save(originalButton);
// Load the button
StringReader stringReader = new StringReader(savedButton);
XmlReader xmlReader = XmlReader.Create(stringReader);
Button readerLoadButton = (Button)XamlReader.Load(xmlReader);

Resources