Silverlight 4 - Am I using TransformToVisual() correctly? - silverlight

I have just started to play around with Silverlight and I decided to do a small app in Visual Studio 2010. I am trying to find the current position of a usercontrol in a Canvas. Here is the XAML layout:
<Grid x:Name="LayoutRoot" Background="#FF141313">
<Grid.RowDefinitions>
<RowDefinition Height="39"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Align Images" Cursor="Hand" Name="buttonAlignImages" Click="buttonAlignImages_Click" Margin="45,8,0,11" HorizontalAlignment="Left" Width="84" />
<Button HorizontalAlignment="Left" Width="33" Opacity="0.5" Background="{x:Null}" BorderThickness="1" FontFamily="Courier New" Content="Home" Cursor="Hand" Margin="8,8,0,11"/>
<Canvas x:Name="ImageContainer" Margin="8" Grid.Row="1" Background="Black"/>
</Grid>
My usercontrol is added to the "ImageContainer" Canvas. One of the buttons in XAML is called "buttonAlignImages". When the user clicks this I basically want the images to be aligned in a specific manner. Anyways to do this I want to first get the position of the usercontrol embedded in the "ImageContainer". So here is the code when the button is clicked:
private void buttonAlignImages_Click(object sender, RoutedEventArgs e)
{
double margin = 5.0;
Point top_left = new Point(margin, margin);
Point top_right = new Point(ActualWidth - margin, margin);
Point bottom_left = new Point(5.0, ActualHeight - margin);
Point bottom_right = new Point(ActualWidth - margin, ActualHeight - margin);
foreach (UIElement element in ImageContainer.Children)
{
Photo singlePhoto = element as Photo;
if (singlePhoto != null)
{
// get the transform for the current photo as applicable to basically this visual
GeneralTransform gt = singlePhoto.TransformToVisual(ImageContainer);
// get the position on the root visual by applying the transform to the singlePhoto
Point singlePhotoTopLeft = gt.Transform(new Point(0, 0));
// now translate the position of the singlePhoto
singlePhoto.Translate(singlePhotoTopLeft.X - top_left.X, singlePhotoTopLeft.Y - top_left.Y);
}
}
}
public void Translate(double deltaX, double deltaY)
{
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
}
The embedded photo usercontrol does move around but when I call gt.Transform(new Point(0,0)) it always gives me (0,0), so the resulting translation is only by 5 pixels. Why does this happen? Am I not using TransformToVisual() correctly?

It s been a while but if u cannot live on without the answer :) will u try
Point singlePhotoTopLeft = gt.Transform(new Point());
instead ?

Related

Rotate element in steps of 45 degrees, always forward

Let's say I want to animate rotate an UI element 45 degrees clockwise each time I press a button.
I have defined 8 visual states setting different RotateTransform values between them. So, whenever I press the button I move to the next visual state:
VisualStateManager.GoToElementState(MyElement, "Position2", True)
etc.
The problem is that when moving from the VisualState 8 to VisualState 1, the element rotates backward. Seems logical, since it is moving from 315º to 0º.
The question is, how could achieve to goal of always moving forward when pressing the button?
A basic example with animation could look like this:
<Grid>
<Rectangle Fill="Black" Width="100" Height="100" RenderTransformOrigin=".5,.5">
<Rectangle.RenderTransform>
<RotateTransform x:Name="rotation"/>
</Rectangle.RenderTransform>
</Rectangle>
<Button Content="Rotate" VerticalAlignment="Bottom" Click="Button_Click"/>
</Grid>
with this Button Click handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
const double rotSpeed = 180; // °/s, i.e. 45° in 0.25 seconds
var newAngle = Math.Floor(rotation.Angle / 45 + 1) * 45; // integer multiple of 45°
var duration = TimeSpan.FromSeconds((newAngle - rotation.Angle) / rotSpeed);
rotation.BeginAnimation(
RotateTransform.AngleProperty, new DoubleAnimation(newAngle, duration));
}
I wrote a simple example for you in C#; VB should be very similar.
In your Main Window put a Rectangle and a Button. Then, set the RenderTransformOrigin for the Rectangle like in the Xaml sample.
In your code behind, declare your RotateTransform and attach it to the Rectangle as shown below. Everytime the Button is pressed, the Angle property of the RotateTransform is increased of 45.
#EDIT:
I like #Clemens formula to increase angle (it avoid overflows). I edited my answer.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="myRect" RenderTransformOrigin=".5,.5" Grid.Row="0" Width="100" Height="100" Fill="LightBlue" >
</Rectangle>
<Button Grid.Row="1" Width="100" Height="50" Margin="10" Click="Button_Click">Rotate</Button>
</Grid>
Window Code-Behind:
public partial class MainWindow : Window
{
private TransformGroup _transformGroup;
private RotateTransform _rotateTrsf;
public MainWindow()
{
InitializeComponent();
_transformGroup = new TransformGroup();
_rotateTrsf = new RotateTransform();
_transformGroup.Children.Add(_rotateTrsf);
SetupAnimation();
myRect.RenderTransform = _transformGroup;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_rotateAnimation.To = Math.Floor(_rotateTrsf.Angle / 45 + 1) * 45;
_rotateTrsf.BeginAnimation(RotateTransform.AngleProperty, _rotateAnimation);
_rotateAnimation.From = _rotateAnimation.To;
}
private void SetupAnimation()
{
_rotateAnimation = new DoubleAnimation();
_rotateAnimation.From = 0.0;
_rotateAnimation.To = 45;
_rotateAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.3));
}
}

Finding a UIElement's true position when within a PivotItem

I'd like to get the on-screen position and visibility of a TextBlock which is embedded in a PivotItem - e.g. the on-screen position and visibility of TB2 in:
<phone:Pivot>
<phone:PivotItem Header="first">
<TextBlock x:Name="TB1" Text="Hello 1"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="second" >
<TextBlock x:Name="TB2" Text="Hello 2"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="three" >
<TextBlock x:Name="TB3" Text="Hello 3"></TextBlock>
</phone:PivotItem>
</phone:Pivot>
For other Xaml controls I have achieved this using code like:
public static Rect Position(this FrameworkElement element)
{
if (element.Visibility == Visibility.Collapsed)
return Rect.Empty;
if (element.Opacity < 0.01)
return Rect.Empty;
// Obtain transform information based off root element
GeneralTransform gt = element.TransformToVisual(Application.Current.RootVisual);
// Find the four corners of the element
Point topLeft = gt.Transform(new Point(0, 0));
Point topRight = gt.Transform(new Point(element.RenderSize.Width, 0));
Point bottomLeft = gt.Transform(new Point(0, element.RenderSize.Height));
Point bottomRight = gt.Transform(new Point(element.RenderSize.Width, element.RenderSize.Height));
var left = Math.Min(Math.Min(Math.Min(topLeft.X, topRight.X), bottomLeft.X), bottomRight.X);
var top = Math.Min(Math.Min(Math.Min(topLeft.Y, topRight.Y), bottomLeft.Y), bottomRight.Y);
var position = new Rect(left, top, element.ActualWidth, element.ActualHeight);
return position;
}
However, for PivotItem children this calculation doesn't provide the expected answers when the PivotItem is off-screen. Instead it provides the answer of the position the item will take on-screen when the PivotItem is swiped back on-screen. During a transition, I can see that the position is correctly calculated using the above method - e.g. during the transition I will see the position correctly swipe in from the left/right of the screen.
I've looked through other properties like Visible and Opacity to see if there is some other mechanism being used here to hide the off-screen PivotItem or one of its Parent, GrandParent, etc - but all of those seem to yield Visible and Opacity == 1.0 results.
I've also tried iterating the VisualTree to see if any element with a solid background is masking the "off-screen" PivotItems - but I can't see any in the tree.
Is there some other property to consider here? How are the PivotItem contents hidden when they are "off-screen"? If I really have to, I know that I can use the SelectedIndex property of the Pivot to try to help with the PivotItem position calculations, but I'm hoping to avoid SelectedIndex if I can - I'd prefer to get the position using general Xaml and VisualTree methods if at all possible.
I looked into it, but I can't find how they are hiding the PivotItems. I guess the position is correct, but the item is just not drawn for unknown (to me) reasons. I'll look into it again, later, but for now I wanted to mention a few other potential issues I noticed.
Shouldn't you be using Math.Max for the bottom right corner? Reason being - ActualHeight and ActualWidth are not rotation-aware so to speak.
RenderSize.Width/Height and ActualWidth/Height are different. I'm not sure it's a good idea to use both of them for the calculation.
Don't you need to check everything in the visual tree above the element to see if it's actually visible?
Edit: I looked at Microsoft.Phone.dll's decompiled source (that's the dll with the Pivot control), and I couldn't find anything about how the PivotItems are hidden. There are some native method calls though.
I have made some Changes to Your code and its giving UIElement's true position
XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<phone:Pivot x:Name="MyPivot">
<phone:PivotItem Header="first" x:Name="first">
<TextBlock x:Name="TB1" FontSize="30" Text="Hello 1" Tap="TB1_Tap"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="second" x:Name="second">
<TextBlock x:Name="TB2" FontSize="50" Height="66" Width="155" Margin="20" Text="Hello 2" Tap="TB2_Tap"></TextBlock>
</phone:PivotItem>
<phone:PivotItem Header="three" x:Name="three">
<TextBlock x:Name="TB3" Text="Hello 3"></TextBlock>
</phone:PivotItem>
</phone:Pivot>
</Grid>
CS:
public partial class PivotPage1 : PhoneApplicationPage
{
public PivotPage1()
{
InitializeComponent();
}
private void TB1_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
Rect r = Pos.MyPosition(TB1,first);
string str = "Left" + r.Left.ToString() + "\nTop:" + r.Top.ToString() + "\nRight:" + r.Right.ToString() + "\nBottom:" + r.Bottom.ToString() + "\nHeight:" + r.Height.ToString() + "\nWidth" + r.Width.ToString();
MessageBox.Show(str);
}
private void TB2_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
Rect r = Pos.MyPosition(TB2,second);
string str = "Left" + r.Left.ToString() + "\nTop:" + r.Top.ToString() + "\nRight:" + r.Right.ToString() + "\nBottom:" + r.Bottom.ToString() + "\nHeight:" + r.Height.ToString() + "\nWidth" + r.Width.ToString();
MessageBox.Show(str);
}
}
public static class Pos
{
public static Rect MyPosition(FrameworkElement child,FrameworkElement parent)
{
if (child.Visibility == Visibility.Collapsed)
return Rect.Empty;
if (child.Opacity < 0.01)
return Rect.Empty;
// Obtain transform information based off root child
GeneralTransform gt = child.TransformToVisual(parent);
// Find the four corners of the child
Point topLeft = gt.Transform(new Point(0, 0));
Point topRight = gt.Transform(new Point(child.RenderSize.Width, 0));
Point bottomLeft = gt.Transform(new Point(0, child.RenderSize.Height));
Point bottomRight = gt.Transform(new Point(child.RenderSize.Width, child.RenderSize.Height));
var left = Math.Min(Math.Min(Math.Min(topLeft.X, topRight.X), bottomLeft.X), bottomRight.X);
var top = Math.Min(Math.Min(Math.Min(topLeft.Y, topRight.Y), bottomLeft.Y), bottomRight.Y);
var position = new Rect(left, top, child.ActualWidth, child.ActualHeight);
return position;
}
}

What's the right way to create a XAML Border with repeating elements in Silverlight?

I'm trying to create a border with a blue background and repeating circles. As an example:
For the vertical portion, I'm using a vertical StackPanel in a Grid. A circle (overlapping a blue Rectangle) is declared in a ControlTemplate. To produce the repetition, I've copy-pasted a bunch of ContentControls, each of which points to my ControlTemplate.
For example:
<StackPanel
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="0"
Orientation="Vertical"
>
<ContentControl
attachedProperties:LightEllipseAttachedProperties.LightState="{Binding ElementName=PhoneApplicationPage, Path=GameController.Instance.Lights}"
Template="{StaticResource LightbulbTemplate}"
/>
**Repeat N times**
<ContentControl
attachedProperties:LightEllipseAttachedProperties.LightState="{Binding ElementName=PhoneApplicationPage, Path=GameController.Instance.Lights}"
Template="{StaticResource LightbulbTemplate}"
/>
</StackPanel>
<ControlTemplate
x:Key="LightbulbTemplate"
>
<Grid>
<VisualStateManager.VisualStateGroups>
</VisualStateManager.VisualStateGroups>
<Rectangle
Fill="#3300CC"
Height="15"
Width="15"
/>
<Ellipse
x:Name="LightEllipse"
Height="8"
Width="8"
>
<Ellipse.Fill>
<SolidColorBrush />
</Ellipse.Fill>
</Ellipse>
</Grid>
</ControlTemplate>
My question: Is there a better way to create a border of repeating elements using Silverlight? Perhaps Border has a Tiling capability so it will repeat the ControlTemplate itself, rather than me adding individual ContentControls?
If you want a simple method for building a shape like that, you could try using a Rectangle with a custom StrokeDashArray:
It was generated by this XAML code:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Height="200">
<Rectangle StrokeThickness="14" StrokeDashCap="Round" Stroke="#FF00B2E6" />
<Rectangle StrokeDashArray="0.1 1.3"
StrokeThickness="10" StrokeDashCap="Round" Margin=".9" >
<Rectangle.Stroke>
<SolidColorBrush Color="#BFFF0606"/>
</Rectangle.Stroke>
</Rectangle>
</Grid>
The XAML uses 2 rectangles. One as the "background" color and the other for the circles. By experimenting with the StrokeDashArray values, you can control the shape of the dash and the distance to the next dash. By using a Round dash cap, and a small dash size (.1), it generates a shape that looks nearly round. You can experiment with the location of the Rectangles, Margins etc., to control the final look.
The nice part about using this technique is that it's extremely an efficient operation on the Phone to draw the shape and it will automatically resize to content as needed.
I've a slightly different proposal (not only in XAML):
Make Background your Border. I've managed to do something like this:
It works quite good and automatically fits to UIElement size, thought may need some time (not much) to load (but can be prepared when you start your App and then reused). I've done it via WritableBitmap - just rendered that many elements (the adventage is that any elements may be used - stars, tringles, even other images) that I need:
private WriteableBitmap CreateBorderBrush(int width, int height)
{
Rectangle firstBrush = new Rectangle();
firstBrush.Width = 15;
firstBrush.Height = 15;
firstBrush.Fill = new SolidColorBrush(Colors.Blue);
Ellipse secondBrush = new Ellipse();
secondBrush.Width = 8;
secondBrush.Height = 8;
secondBrush.Fill = new SolidColorBrush(Colors.Orange);
int dimensionX = width - width % 15;
int dimensionY = height - height % 15;
WriteableBitmap bitmapToBrush = new WriteableBitmap(dimensionX, dimensionY);
for (int i = 0; i < width / 15; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = i * 15, Y = 0 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = i * 15 + 3, Y = 3 });
}
for (int i = 1; i < height / 15 - 1; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = 0, Y = i * 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = 3, Y = i * 15 + 3 });
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = dimensionX - 15, Y = i * 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = dimensionX - 15 + 3, Y = i * 15 + 3 });
}
for (int i = 0; i < width / 15; i++)
{
bitmapToBrush.Render(firstBrush, new TranslateTransform() { X = i * 15, Y = dimensionY - 15 });
bitmapToBrush.Render(secondBrush, new TranslateTransform() { X = i * 15 + 3, Y = dimensionY - 15 + 3 });
}
bitmapToBrush.Invalidate();
return bitmapToBrush;
}
In MainPage constructor I've used it like this:
this.Loaded += (s, e) =>
{
myGrid.Background = new ImageBrush() { ImageSource = CreateBorderBrush((int)myGrid.ActualWidth, (int)myGrid.ActualHeight) };
};
And the XAML code:
<Grid Name="myGrid" Grid.Row="0" Width="300" Height="200">
<Button x:Name="first" Content="Button" Width="150" Height="100"/>
</Grid>
I would suggest creating a separate UserControl. The number of circles can be a Dependencyproperty of the UserControl. You can then use a ItemsControl to repeat the circles.

Dynamically adding and moving ellipses using WPF

I'm using WPF and having trouble dynamically/programatically adding ellipses to my grid.
I'm dynamically allocating and placing ellipses inside myGrid. Trouble is the position on the ellipses don't change. I'm using Canvas.SetLeft and SetTop, but the ellipses still seem stuck.
Here is the code for dynamic allocation :
{
...
Ellipse el = new Ellipse();
RadialGradientBrush b = new RadialGradientBrush();
b.RadiusX = r * 10.0f;
b.RadiusY = r * 10.0f;
b.GradientOrigin = new Point(0.5f, 0.5f);
b.GradientOrigin = new Point(0.5f, 0.5f);
b.GradientStops.Add(new GradientStop(Colors.Green, 0.0));
b.GradientStops.Add(new GradientStop(Colors.Blue, 1.0));
el.Width = 5.0f + r * 20.0f;
el.Height = 5.0f + r * 20.0f;
el.Stroke = b;
SetEllipsePosition(el, p);
this.myGrid.Children.Add(el);
...
}
private void SetEllipsePosition(FrameworkElement ellipse, Point j)
{
Canvas.SetLeft(ellipse, j.X);
Canvas.SetTop(ellipse, j.Y);
}
<Grid Height="480" Name="myGrid" Width="640">
<GroupBox Header="Pattern" Height="117" HorizontalAlignment="Left" Margin="10,564,0,0" Name="groupBox1" VerticalAlignment="Top" Width="238"></GroupBox>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="33,30,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click_1" />
<Grid Margin="6,507,408,-121">
<RadioButton Content="Lines" Height="16" HorizontalAlignment="Left" Margin="15,18,0,0" Name="rbLines" VerticalAlignment="Top" GroupName="RenderStyles" />
<RadioButton Content="Circles" Height="16" HorizontalAlignment="Left" Margin="15,49,0,0" Name="rbCircles" VerticalAlignment="Top" GroupName="RenderStyles" />
</Grid>
</Grid>
The problem is that you are using a Grid but setting Canvas properties, you could add a Canvas into the Grid and draw you ellipses on the Canvas ( add them to the Canvases children), and then it would work.
Or you could use the Margin property of your ellipse to set it's position on the Grid
Canvas.Left and Canvas.Top are attached properties: you set them only if your UI element is going to be contained in a Canvas; and only when it is on a Canvas will those properties be used (by the Canvas layout manager). Same with attached properties from Grid (such as Grid.Column to tell the Grid parent in what column the UI elements "wants" to be), Panel (Panel.ZIndex to tell the Panel parent at what z index the UI element should be put), etc.

How to find the location of control on some upper level control?

How can i find the location of the 'SomeRectangle' ?
This Rectangle is the location and the size that i need to crop from the 'somepicture' that is actually the picture that appear in the background of the main grid.
<Rectangle x:Name="SomeRectangle" Height="50" Width="50" Stroke="Red" HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="5" MinHeight="5" />
GeneralTransform gt =
SomeRectangle.TransformToVisual(Application.Current.RootVisual as UIElement);
Point offset = gt.Transform(new Point(0, 0));
double controlTop = offset.Y;
double controlLeft = offset.X;
Source: Silverlight Forum

Resources