I would like to build a pyramid with Frame elements in Xamarin.Forms. I would like to use a Grid layout, but it is difficult since the frames on the next row will overlap. What is the best layout to use in this situation? StackLayout, AbsoluteLayout or something else?
I would approach this using AbsoluteLayout and would generate the pyramid in the code-behind. It will save you a lot of typing and will be even faster.
The rough code for this would be:
private void BuildPyramid()
{
int cellSize = 40;
int height = 8;
for (int row = 0; row < height - 1; row++) //one less to have two cells on top row
{
//add "empty" space equal to a multiple of half-size of a cell
int startX = row * cellSize / 2;
var numberOfColumns = height - row; //each row has one less cell
for (int column = 0; column < numberOfColumns; column++)
{
var x = column * cellSize + startX;
var y = (height - row - 1) * cellSize; //y-axis decreases going down
var frame = new Frame()
{
WidthRequest = cellSize,
HeightRequest = cellSize,
Margin = new Thickness(2),
BorderColor = Color.CornflowerBlue
};
AbsLayout.Children.Add(frame);
AbsoluteLayout.SetLayoutBounds(frame, new Rectangle(x, y, cellSize, cellSize));
}
}
}
The result:
As a side note, if you are looking for a XAML-only approach - the Grid approach is also possible, but you have to do a trick here - you must add twice as many columns as you have cells in the widest row, to use half-width of a cell for layout and also utilize Grid.ColumnSpan to make the Frames always span 2 columns at once:
<Grid HorizontalOptions="Center" ColumnSpacing="0" RowSpacing="0">
<Grid.Resources>
<Style TargetType="Frame">
<Setter Property="BorderColor" Value="CornflowerBlue" />
<Setter Property="Margin" Value="2" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="15" />
<ColumnDefinition Width="15" />
</Grid.ColumnDefinitions>
<!-- top row -->
<Frame Grid.Column="1" Grid.ColumnSpan="2" />
<Frame Grid.Column="3" Grid.ColumnSpan="2" />
<!-- bottom row -->
<Frame Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" />
<Frame Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" />
<Frame Grid.Row="1" Grid.Column="4" Grid.ColumnSpan="2" />
</Grid>
Yields:
Related
I am trying to set the HeightRequest of a Rectangle to 10, but the height instead fills the entire height of the Grid. What am I doing wrong or is this a bug in Xamarin Forms. I am using Xamarin.Forms 5.0.0.2012.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<shapes:Rectangle Grid.Row="0" Grid.Column="0"
Fill="Green"
StrokeThickness="0"
WidthRequest="50"
HeightRequest="10"/>
<Label Grid.Row="0" Grid.Column="1"
Style="{StaticResource Subtitle1TextStyle}"
Text="Green area: ideally, your average lines would stay within your target range." />
<shapes:Rectangle Grid.Row="1" Grid.Column="0"
Fill="Black"
StrokeThickness="0"
WidthRequest="50"
HeightRequest="10"/>
<Label Grid.Row="1" Grid.Column="1"
Style="{StaticResource Subtitle1TextStyle}"
Text="Black line: The black line has a lot of text on this line. The Rectangle height keeps growing even though the height request has been set to 10." />
</Grid>
Because you are putting the Label and the black rectangle on the same Row (which have it Height set to Auto), it means it will take the maximum Height of the elements in that Row which in your case is the Label, thus the rectangle will get the same Height as the Label even that you have specified it HeightRequestto be 10.
To overcome this create another Row and span the Label over Row 2 and 3, also notice the new simplified syntax for ColumnDefinition and RowDefinition:
<Grid RowDefinitions="auto,auto,auto" ColumnDefinitions="auto,*">
<Rectangle Grid.Row="0" Grid.Column="0"
Fill="Green"
StrokeThickness="0"
WidthRequest="50"
HeightRequest="10"/>
<Label Grid.Row="0" Grid.Column="1"
Text="Green area: ideally, your average lines would stay within your target range."/>
<Rectangle Grid.Row="1" Grid.Column="0"
Fill="Black"
VerticalOptions="Fill"
StrokeThickness="0"
WidthRequest="50"
HeightRequest="10"/>
<Label Grid.Row="1" Grid.Column="1"
Grid.RowSpan="2"
TextColor="Black"
Text="Black line: The black line has a lot of text on this line. The Rectangle height keeps growing even though the height request has been set to 10."/>
</Grid>
I want to align 12 TextBlocks in a circle like this
InOrder to achieve this I have tried something like this
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
<RowDefinition Height="24"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="9" Grid.Column="0" Grid.Row="3"></TextBlock>
<TextBlock Text="10" Grid.Column="1" Grid.Row="2"></TextBlock>
<TextBlock Text="11" Grid.Column="2" Grid.Row="1"></TextBlock>
<TextBlock Text="12" Grid.Column="3" Grid.Row="0"></TextBlock>
<TextBlock Text="1" Grid.Column="4" Grid.Row="1"></TextBlock>
<TextBlock Text="2" Grid.Column="5" Grid.Row="2"></TextBlock>
<TextBlock Text="3" Grid.Column="6" Grid.Row="3"></TextBlock>
<TextBlock Text="4" Grid.Column="5" Grid.Row="4"></TextBlock>
<TextBlock Text="5" Grid.Column="4" Grid.Row="5"></TextBlock>
<TextBlock Text="6" Grid.Column="3" Grid.Row="6"></TextBlock>
</Grid>
But this doesn't give me a good circle, is there any other way to achieve label arranged in a circle?
If you want to create a good circle, the grid is not a suitable solution. Using a coordinate system in canvas might be a way to create a good circle. Let's say we need a clock whose size is 200*200. So the center point (x0,y0) is (100,100). As we know, the angle between the two adjacent numbers is 30. Then the center point of the TextBlock for 2 o'clock- (x2, y2) can be calculated.
x2 = x0 + r * cos(Math.PI * angle/ 180.0) and y2 = y0 - r * sin(Math.PI * angle/ 180.0)
It's the same for other TextBlocks.
Updated
I made a simple sample for this.
Xaml:
<Canvas Background="Gray" Width="300" Height="300" >
<TextBlock Text="2" x:Name="textblock2" ></TextBlock>
<TextBlock Text="1" x:Name="textblock1" ></TextBlock>
<TextBlock Width="20" Height="20" Text="12" Canvas.Top="40" Canvas.Left="140"></TextBlock>
<TextBlock Width="20" Height="20" Text="9" Canvas.Top="140" Canvas.Left="40"></TextBlock>
<TextBlock Width="20" Height="20" Text="3" Canvas.Top="140" Canvas.Left="240" ></TextBlock>
<TextBlock Width="20" Height="20" Text="6" Canvas.Top="240" Canvas.Left="140" ></TextBlock>
</Canvas>
Code behind:
int r = 100;
//1 o'clock
// need to minus 10 to get Canvas.top and Canvas.lelf value because x1 y1 represent the center of the textblock not the center of the textblock
double x1 = 150 + r * Math.Cos(Math.PI * 60 / 180.0) - 10;
double y1 = 150 - r * Math.Sin(Math.PI * 60 / 180.0) - 10;
textblock1.SetValue(Canvas.LeftProperty, x1);
textblock1.SetValue(Canvas.TopProperty, y1);
//2 o'clock
double x2 = 150 + r * Math.Cos(Math.PI * 30 / 180.0)-10;
double y2 = 150 - r * Math.Sin(Math.PI * 30 / 180.0)-10;
textblock2.SetValue(Canvas.LeftProperty, x2);
textblock2.SetValue(Canvas.TopProperty, y2);
What it looks like:
I have the following XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="11" />
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<ListView ... Grid.Column="2"/>
<controls:GridSplitter
Grid.Column="1"
Width="11"
ResizeBehavior="BasedOnAlignment"
ResizeDirection="Auto"
Background="Gray"
Foreground="White"
FontSize="13">
<controls:GridSplitter.Element>
<Grid>
<TextBlock HorizontalAlignment="Center"
IsHitTestVisible="False"
VerticalAlignment="Center"
Text=""
Foreground="Black"
FontFamily="Segoe MDL2 Assets">
</TextBlock>
</Grid>
</controls:GridSplitter.Element>
</controls:GridSplitter>
<Canvas Canvas.ZIndex="1">
<ContentControl MaxWidth="750" Content="{Binding CAV, Mode=TwoWay}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />
</Canvas>
</Grid>
Without the Canvas wrapped around the ContentControl in the first column, the children of the ContentControl properly stay within the content control and I can make it wider to see more of the children horizontally.
When I add the Canvas and set the ZIndex, the children of the content control spill out over the gridsplitter and the ListView without respecting the width of the contentcontrol.
The effect I'm trying to get it to allow expanding the width of the content control with the grid splitter and having that content control expand "over" the listview (instead of reducing the width of the listview).
What am I missing? I'm confused as to why the width of the contentcontrol isn't being respected suddenly just because I wrap it in a Canvas. Or should I not be using Canvas to get the "overlay" effect I want?
Canvas doesn't stretch its children. It only gives them the space they need. In fact, you don't have to wrap your ContentControl inside a Canvas to set the Canvas.ZIndex; it is an attached property that can be attached to the ContentControl directly. If you want a panel that allows its children/child to stretch, try a Border(single child only) or a Grid.
However, setting the ZIndex here seems to be unnecessary to me. Since the GridSplitter simply resizes the width of the columns, there won't be any overlay happening here.
If you want the overlay effect, you need to create another Grid with the same column layout and place your ListView there, something like the following -
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="11" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Grid x:Name="ListView" Background="LightGreen" Grid.Column="2" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="11" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<controls:GridSplitter Grid.Column="1" Background="LightBlue">
</controls:GridSplitter>
<Grid x:Name="ContentControl" Background="LightPink" Opacity="0.5" />
</Grid>
</Grid>
As an addendum Canvas tells the layout tree it uses no space. It doesn't care about the size of it's children, despite however many children it might have. For reference, here's the Canvas' MeasureOverride:
protected override Size MeasureOverride(Size constraint)
{
Size childConstraint = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
foreach (UIElement child in InternalChildren)
{
if (child == null) { continue; }
child.Measure(childConstraint);
}
return new Size();
}
Which effectively says "I'm not taking up any space and my children can use whatever space they want".
I took the following tutorial as a good start point to animation and also explored the toolkit Offset option but found it somehow complex to accomplish what I want, unless proven otherwise.
Goal:
Upon clicking a matrix button, move the object from its starting position to the position of the clicked button, then return the object back to its initial position.
Challenge: The main challenge might be in determining the exact position of each matrix element (Button); the element's position in regard to the Grid is pretty clear, for example Grid.Row="1" Grid.Column="2", but in regard to Canvas and animation in general...no idea!
According to the code below, how to move the EllipseFigure from square 6 to square 1?
<Canvas>
<Canvas.Resources>
<Storyboard x:Name="myStoryboard">
<!-- Animate the center point of the ellipse. -->
<PointAnimation EnableDependentAnimation="True" Storyboard.TargetProperty="Center"
Storyboard.TargetName="EllipseFigure"
Duration="0:0:5"
From="1,2"
To="0,0"
RepeatBehavior="Forever" />
</Storyboard>
</Canvas.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="Yellow" />
<Setter Property="BorderThickness" Value="5" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Yellow" />
<Setter Property="BorderThickness" Value="5" />
</Style>
</Grid.Resources>
<Button Grid.Row="0" Grid.Column="0" Content="1" />
<Button Grid.Row="0" Grid.Column="1" Content="2" />
<Button Grid.Row="0" Grid.Column="2" Content="3" />
<Button Grid.Row="1" Grid.Column="0" Content="4" />
<Button Grid.Row="1" Grid.Column="1" Content="5" />
<Button Grid.Row="1" Grid.Column="2" Content="6" />
<Path Grid.Row="1" Grid.Column="2" Fill="Blue" PointerPressed="ButtonClick">
<Path.Data>
<!-- Describe an ellipse. -->
<EllipseGeometry x:Name="EllipseFigure"
Center="20,20" RadiusX="15" RadiusY="15" />
</Path.Data>
</Path>
</Grid>
</Canvas>
CodeBehind:
private void ButtonClick(object sender, RoutedEventArgs e)
{
myStoryboard.Begin();
}
Using the Storyboard approach you have, you can accomplish this by getting the position of the button and the ball and set the To property of the animation. You can also return the ball by setting AutoReverse to true
<Storyboard x:Name="myStoryboard">
<PointAnimation Storyboard.TargetProperty="Center"
Storyboard.TargetName="EllipseFigure"
Duration="0:0:1"
AutoReverse="True"
EnableDependentAnimation="True"/>
</Storyboard>
Make sure you subscribe to the Click event of the buttons and not the PointerPressed of the ball.
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
// Get the position (top left) of the Button
GeneralTransform transform = button.TransformToVisual(this);
Point buttonPosition = transform.TransformPoint(new Point(0, 0));
// Get the center of the button
Point buttonCenter = new Point(buttonPosition.X + (button.ActualWidth / 2), buttonPosition.Y + (button.ActualHeight / 2));
// Get the position of the ball
transform = Ball.TransformToVisual(this);
Point ballPosition = transform.TransformPoint(new Point(0, 0));
// Get the center of the ball
Point ballCenter = new Point(ballPosition.X + (EllipseFigure.Center.X), ballPosition.Y + (EllipseFigure.Center.Y));
// The animation acts like it's at 0,0. calculate position to go to
Point to = new Point(buttonCenter.X - ballCenter.X, buttonCenter.Y - ballCenter.Y);
var storyBoard = (Storyboard)Root.Resources["myStoryboard"];
var animation = (PointAnimation)storyBoard.Children[0];
animation.To = to;
storyBoard.Begin();
}
Note that I give your Path element an x:Name of "Ball"
I have a dynamically expanding Grid inside another Grid which is separated onto 4 rows and 4 columns.
My main content grid spans 4 rows and 2 columns and dynamically loads different Views, and sometimes expands in height. My problem is, the other rows are also expanded although they have a fixed height, which makes the layout appear weird.
I want to keep all the rows' heights to 36, but just expand the lowest row so all content of my grid is shown.
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="70" />
<ColumnDefinition Width="110" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="36" />
<RowDefinition Height="36" />
<RowDefinition Height="36" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontSize="14"
Content="X">
</Button>
<Grid x:Name="MainPaymentContentRoot"
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="1"
Grid.ColumnSpan="2"
Margin="20,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollMode="Auto">
</Grid>
<TextBlock Text="MyField" Grid.Row="0" Grid.Column="3" Margin="10,0,0,0" VerticalAlignment="Center" />
<Button Grid.Row="1" Grid.Column="3" Margin="10,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Content="AnotherButton">
</Button>
</Grid>
This is for a Win 8 Development.
Currently, your RowDefinition is set to Auto. This means that the row will calculate the content size, and adjust it's height accordingly.
You need to change it to Height="*".
<RowDefinition Height="*" />
This will force the RowDefinition to expand to the height of the parent. In other words, it will take up all available space.