I am using C#, Silverlight, Visual Studio for Windows Phone 7.
I would like to get the Clip of a UIElement that includes any transform that has been done on the UIElement.
This example gives me the Clip but does not include any transform from the UIElement:
// uie is a UIElement taken from a PhoneApplicationPage
Geometry myClip = uie.Clip;
I tried the following, but it ended up moving the UIElement:
var frame = Application.Current.RootVisual as PhoneApplicationFrame;
var page = frame.Content as PhoneApplicationPage;
GeneralTransform gt = uie.TransformToVisual(page);
uie.RenderTransform = gt as Transform;
Geometry myClip = uie.Clip;
I also tried to add an inverse transform at the end to undo any movement, but that seemed to make it worse:
uie.RenderTransform = gt.Inverse as Transform;
Please help me get the Clip with the original transform of the UIElement without messing with the UIElement itself.
Thanks in advance.
I figured out how to solve this without messing with the original UIElement. I needed to create a new Geometry object with the same data rather than using the original Geometry. Not only does this separate the new data from the old data, it also prevents an object from possibly getting assigned as a child to multiple objects.
Here is the resulting code I used:
var frame = Application.Current.RootVisual as PhoneApplicationFrame;
var page = frame.Content as PhoneApplicationPage;
GeneralTransform gt = uie.TransformToVisual(page);
var place = gt.Transform(new Point());
// declaring new variables to hold these values
Geometry geom;
Path path = new Path();
// here I checked for other restrictions on my UIElements before assigning the variables
geom = new RectangleGeometry();
(geom as RectangleGeometry).Rect = new Rect(place.X, place.Y, uie.RenderSize.Width, uie.RenderSize.Height);
path.Data = geom;
I mentioned that I check for some restrictions on my UIElements before assigning the variables. This is related to another question I posted about clips (here).
Related
I'm trying to create a method to draw a Line(path) between two UserControls. I found a post by someone that gave me a general pointer on how to do this, I implemented the code succesfully and started adapting it to my needs.
I am having a problem with accessing the user control:
Button b2 = new Button();
var transform2 = b2.TransformToVisual(b2.Parent as UIElement);
Works as it should, but my buttons get created dynamicaly through a method so I can't access them as "b2".
I tried the following:
var transfrom3 = canvas1.Children[0].TransformToVisual(canvas1.Children[0].Parent as UIElement);
but accessing it like that gives me an error on .Parent.
If have also tried:
var p1 = this.FindName(ps.ProcessID.ToString());
var p2 = this.FindName(ps.PreID.ToString());
////get geo data from both controls
var transform1 = p1.TransformToVisual(p1.Parent as UIElement);
var transform2 = p2.TransformToVisual(p2.Parent as UIElement);
Can anyone tell me how i can access these UserControls?
This line ought to work:-
var transfrom3 = canvas1.Children[0].TransformToVisual(canvas1.Children[0].Parent as FrameworkElement);
as you have discovered UIElement doesn't have Parent, its the FrameworkElement that adds the Parent property.
However it would be better to access the control via its name (I'll assume you are assigning a Name to the dynamically added controls).
This:-
var p1 = this.FindName(ps.ProcessID.ToString());
ought to work assuming this is UserControl and the FrameworkElement in question was given the matching name in code:-
canvas1.Children.Add(new Button() {Name = ps.ProcessID.ToString(), Content="Hello"});
Hmm I did not see that you changed UIElement to FrameworkElement, this setup works for me now:
var control1 = this.FindName(ps.ProcessID.ToString()) as FrameworkElement;
var control2 = this.FindName(ps.PreID.ToString()) as FrameworkElement;
var transform1 = control1.TransformToVisual(control1.Parent as UIElement);
var transform2 = control2.TransformToVisual(control2.Parent as UIElement);
If I add the control to a var as a FrameworkElement, then I can use the .Parent on the control again.
Thanks for your answer Anthony!
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));
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)?
I am creating a WPF application in .net 4.0.
Basically I want to have a map of the world (2d) where by I can place images on, depending on their country location. This map can be an image however, it would be hard to determine the co-ordinates for the location right?
Can someone provide me with the simplest way of doing this?
I would do this by placing the image/shape object in Canvas Panel (panel with coordinates system) also I would add a wpf toolkit zoomviewer to get nice panning and zooming of the map.
Then I would make a Dictionary where key is a name of the country and value is a Point where pictures should be placed.
There are many solutions to this, all depending on your design (and tastes).
In all cases, the thing to remember is that the placement of the items on the map will always be relative to the size of the map, and will need to be recalculated whenever the size of the background map is changed. This calculation will be depend on the map itself (i.e. is it a rectangle or round, is the latitude/longitude a fixed and equal grid, the layout of the map, etc.).
One possible way to set up the main window is something like this:
This assumes that there is a folder called "Resources" that contains an image called "world-map.jpg", with its Build Action set to Resource.
Then, in your code behind, you would have to have something that actually adds a new image to the map in the appropriate place.
Personally, I would probably create a class (perhaps a custom or user control) to hold the image information, including the latitude/longitude, an Image object, etc. The main window (or ViewModel) would have a collection of these objects, which would make it easier to put them in the right places when a resize occurred. For example:
public partial class Window1 : Window
{
ObservableCollection<MyMapImageClass> mapImages = new ObservableCollection<MyMapImageClass>();
public Window1()
{
InitializeComponent();
layout.SizeChanged += new SizeChangedEventHandler(layout_SizeChanged);
}
void layout_SizeChanged(object sender, SizeChangedEventArgs e)
{
foreach (MyMapImageClass mapImage in mapImages)
{
Point point = CalculateImagePosition(mapImage.latitude, mapImage.longitude);
mapImage.Image.SetValue(Canvas.LeftProperty, point.X);
mapImage.Image.SetValue(Canvas.TopProperty, point.Y);
}
}
Point CalculateImagePlacement(double latitude, double longitude)
{
Point point = new Point();
// Do the calculations appropriate to your map
point.X = LongCalculation(longitude);
point.Y = LatCalculation(latitude);
}
void PlaceImage(double latitude, double longitude)
{
Image img = new Image();
ImageSourceConverter converter = new ImageSourceConverter();
string path = "pack://application:,,,/Resources/SomeImage.png";
ImageSource source = (ImageSource)converter.ConvertFromString(path);
img.Source = source;
img.Width = 10d;
Point point = CalculateImagePlacement(latitude, longitude);
img.SetValue(Canvas.LeftProperty, point.X);
img.SetValue(Canvas.TopProperty, point.Y);
layout.Children.Add(img);
MyMapImageClass mapImage = new MyMapImageClass();
mapImage.Latitude = latitude;
mapImage.Longitude = longitude;
mapImage.Image = img;
mapImages.Add(mapImage);
}
}
Greetings!
I am currently working on a Silverlight project and I would like to animate a simple polygon shape (a trapezoid actually). Specifically, I woudld like to move two of the four points dynamically after some event happened. I need / want to resize and move one of the parallel sides to another position.
I admit I am rather new to Silverlight and have not found a source that could tell me wether it is even possible, not to mention how it can be done.
I have used animations before, so the general concept of storyboards and animations is not new to me. But how can I move the points of a polygon in an animation? Are there alternatives that have a similar optical effect (e.g. animating a path)?
Is there a PropertyPath I can use, similar to
P3AnimBack.SetValue(Storyboard.TargetPropertyProperty,
new PropertyPath("(Path.Data).
(PathGeometry.Figures)[0].(PathFigure.Segments)[0].
(BezierSegment.Point3)"));
, as found in a Point Animation in Silverlight 3 tutorial?
Thank you all in advance. :)
i don't know anything about Silverlight, or animations in .NET in general, but Charles Petzold did something similar:
Squaring the Circle (view animation in browser)
Animating Points and PointCollections in Silverlight (view in browser)
Animated Polyline Interpolations in Silverlight
As requested in a comment, I explain what I finally used to make my animation:
I follwed up on Animated Polyline Interpolations in Silverlight and more or less directly used this code - "stealing" the PointCollectionInterpolator.cs class.
Then I had my method to create the polygons I need and prepare animations:
private void CreatePolygon(TextBox txtbx, string prop, Color curcol)
{
PointCollectionInterpolator pci = new PointCollectionInterpolator();
pci.Points1 = new PointCollection() // Start Points
{
new Point(...),
new Point(...),
new Point(...),
new Point(...),
};
pci.Points2 = new PointCollection() // End Points
{
new Point(...),
new Point(...),
new Point(...),
new Point(...),
};
Polygon tmpply = new Polygon();
LayoutRoot.Children.Add(tmpply);
tmpply.Points = pci.InterpolatedPoints;
DoubleAnimation animpci = new DoubleAnimation();
animpci.Duration = someDuration;
animpci.From = 0.0;
animpci.To = 1.0;
Storyboard.SetTarget(animpci, pci);
Storyboard.SetTargetProperty(animpci, new PropertyPath("(Progress)"));
myStoryBoard.Children.Add(animpci);
}
And then in some random event handler, I start the animation. Additionally, so I can reuse the method, I moved the end points collection into the start points collection and update the interpolator with new end points. (Remember setting the progress to 0.0 ...) So each time the handler fires, the polygon seamlessly morphs into a new one.
private void SomeEventHandler(object sender, RoutedEventArgs e)
{
PointCollectionInterpolator polygonPCI =
this.referenceToPointCollectionInterpolator;
polygonPCI.Points1 = polygonPCI.Points2;
polygonPCI.Progress = 0.0;
polygonPCI.Points2 = getNewEndPoints();
myStoryBoard.Begin();
}
In retrospect, i would change the names from Points1 and Points2 to StartPoints and EndPoints resp.
Hope this helped. :)