WPF - Effects of "Center" vertical alignment on negative margins - c#

Attempting to animate a control by modifying its margins, I realized that I might not fully understand the effects of the different alignment options on negative margins. To better explain my question, I created an example containing two TextBlock controls each surrounded by a Border.
As shown below, I attempt to give the first TextBlock _TextBlock1 (blue) - which has a vertical alignment of Top - a top margin of -20 so that its bottom edge will sit immediately on top of its border _Border1. This produces the desired result. I then attempt to achieve the same effect on TextBlock _TextBlock2 (orange), which is identical to _TextBlock1 except for its vertical alignment of Center. Since this TextBlock is centered vertically, I apply a top margin of -40, which from my understanding should produce the same result (20 pixels to move its top edge to the top edge of the border _Border2 and another 20 pixels to bring it fully above the border).
As shown on the image below - taken from the designer view in Visual Studio - I seem to be missing something about how the margins affect the placement of these controls given their vertical alignment type. Could somebody explain to me how I should be interpreting the interaction between margins and alignment types (as related to this example)? Also, how can I modify the margins on _TextBlock2 to produce the same results as _TextBlock1?
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Test"
Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="60"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border
x:Name="_Border1"
Grid.Row="1"
Grid.Column="1"
BorderBrush="Black"
BorderThickness="1">
<TextBlock
Margin="0 -20 0 0"
x:Name="_TextBlock1"
Background="DodgerBlue"
VerticalAlignment="Top"
Height="20"/>
</Border>
<Border
x:Name="_Border2"
Grid.Row="1"
Grid.Column="2"
BorderBrush="Black"
BorderThickness="1">
<TextBlock
Margin="0 -40 0 0"
x:Name="_TextBlock2"
Background="Orange"
VerticalAlignment="Center"
Height="20"/>
</Border>
</Grid>
</Window>

Margins do not ‘move’ an element. Margins effectively grow or shrink the size of an element’s layout rectangle, which is provided by its parent. Alignment controls how an element positions itself within its layout rectangle.
Initially, the orange block has its parent’s entire area available for positioning, so its layout rectangle starts with a height of 60. Normally, adding (positive) margins shrinks the portion of the layout rectangle available to position an element. But a top margin of -40 effectively grows the orange block’s layout rectangle so it has a height of 60 - (-40) = 100. Let’s define the top-left corner of our effective layout rectangle as being (0, 0). Relative to this, the parent border’s top-left corner is (0, 40).
The orange block has a height of 20, and it has 100 units of vertical space in which to center itself. (100 - 20) / 2 = 40, so the block gets 40 units of vertical space above it and below it. This puts the orange block’s top-left corner at (0, 40), right along with its parent.

You can modify your _TextBlock2 as below to obtain required behaviour:
<TextBlock
Margin="0 -80 0 0"
x:Name="_TextBlock2"
Background="Orange"
VerticalAlignment="Center"
Height="20"/>
To have a better understanding regarding margins and padding go here.

Related

ActualHeight of Grid not correctly inheriting value from the StackPanel it references

I have a StackPanel with a Grid inside of it. I want that Grid to always be the same size as the StackPanel, so I have the code setup as such:
<StackPanel Name="ContentPanel" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FF2B2B2B" Margin="0,0,0,0">
<Grid HorizontalAlignment="Stretch" Margin="0, 0, 0, 0" VerticalAlignment="Top" Height="{Binding ActualHeight, ElementName=ContentPanel}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Content Goes Here! -->
</Grid>
</StackPanel>
However, my Grid doesn't seem to be respecting the height. When I resize the Window with the Resize Handle, the grid expands correctly, but doesn't shrink correctly and retains the maximum dimensions. This means anything who's position is referenced via VerticalAlignment=Bottom is liable to 'disappear' from the screen if you resize it to be smaller.
To debug, I gave my StackPanel a set Height of 500 -- Looking at the design preview, I see this:
StackPanel Dimensions:
https://imgur.com/a/Q30Zb
Grid Dimensions:
https://imgur.com/a/oGV4a
Even there, The Grid attempts to fill the space of the entire window, not just it's Parent StackPanel, which it should derive it's height from!
How do I force the Grid to inherent the height of the StackPanel? Alternatively, how do I always force the Grid to consume the entire StackPanel, even if a user resizes the Window?
I've tried changing VerticalAlignment values on all these items, and deleted all my content inside the grid, thinking it might be causing problems, but to no avail!
The stack panel will adjust its height to match the height of its contents. Once the grid has reached a certain size (e.g. 800), the stack panel will not force the grid to become smaller.
One easy option to get what you want would be to put the StackPanel into a parent grid, and then bind the child grid to the parent grid's height.

How to make ellipse circle and resizable keeping its shape?

I have got a Grid, and I want to make a templet a button look like a circle in one of the cells.
I've managed to make it placing it in Canvas but my Circle doesn't resize with the grid. When I used Vertical and Horizontal Alignment Stretch it doesn't work ether.
The question is: How to make a circle in XAML, in one of the cells of the Grid to be resizable keeping it circle shape while I'm resize the window?
If I have understood you, this code works fine:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Ellipse HorizontalAlignment="Stretch" VerticalAlignment="Top"
Height="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"></Ellipse>
</Grid>
The eclipse mantains the shape and it is resize depending on the grid.

Why does a too small container cause a child to clip to its own bounds?

For alignment purposes, I have a control which renders a button outside of it's own bounds. This seems to work perfectly in most cases, but it produces undesired output in cases where the size of the control plus its margins exceeds the outline of its container.
Below is an example where the red grid represents the control and the blue rectangle is the button. The blue rectangle renders correctly when the outer grid is sufficiently big (Width="300"), but not when the outer grid is too small to contain the red grid and its margin. The blue rectangle will then get clipped to the bounds for the red grid.
This seems to be a bug. I would expect the blue rectangle to be clipped according to the bounds of the outer grid and the right margin of the red grid, but being clipped to the bounds of the red rectangle makes no sense to me. What is the best approach for working around this? I could set the right margin of the red grid to a sufficiently large negative number and set ClipToBounds="True" on the outer grid, but I was hoping that there would be a better solution.
<Grid Width="300">
<Grid Margin="50" Background="Red" Width="200" HorizontalAlignment="Left">
<Rectangle Fill="Blue" Margin="-20" HorizontalAlignment="Left" Width="50" Height="50" />
</Grid>
</Grid>
<Grid Width="299">
<Grid Margin="50" Background="Red" Width="200" HorizontalAlignment="Left">
<Rectangle Fill="Blue" Margin="-20" HorizontalAlignment="Left" Width="50" Height="50" />
</Grid>
</Grid>

XAML/WPF zoom to a specific area on a canvas

So I have a WPF window with a grid which has 2 columns. In the first column I have a canvas which I am rendering an SVG image to. This canvas is re-sized to be the same size as the image (so there are no transforms going on) and it is assumed that the image is a lot smaller than the screen so there is no need for a scroll viewer - I'll call this the left canvas.
In the second column I have another canvas which is inside a Viewbox, the same SVG is also being rendered to this canvas and it is assumed that the SVG image size is larger than the Viewbox fixed size. The Viewbox re-sizes the canvas to fit inside it - although doesn't appear to apply any transforms to the canvas and doesn't change its width or height, some other magic goes on here - but fine, it works.
The idea is that the user can draw a rectangle on the left canvas, which will represent a zoom area, and then the right canvas will zoom in so that rectangle will fit to the Viewbox containing the canvas - by fit I mean without cropping or stretching/squashing any of the zoom area , so if it is a landscape zoom area in a portrait Viewbox, the sides of the zoom area will meet the sides of the Viewbox leaving space at the top and bottom which is fine.
I though this would be straight forward as there are no transforms applied to either of the canvases, and they both have the same width and height (even though some magic from the Viewbox is making the right one smaller). This is how I'm doing it at the moment:
Find the centre point:
centreX = Canvas.GetLeft(zoomAreaRect) + (zoomAreaRect.Width / 2);
centreY = Canvas.GetTop(zoomAreaRect) + (zoomAreaRect.Height / 2);
Find the scale amount:
double scale;
if(zoomAreaRect.Width > zoomAreaRect.Height)
{
scale = RightCanvas.Width / zoomAreaRect.Width;
}
else
{
scale = RightCanvas.Height / zoomAreaRect.Height;
}
Then use a scale transform using centreX and centreY as the centre of the transform, and scale for both scaleX and scaleY on the transform.
Now this clearly doesn't work because I need to somehow take into account the Viewbox size when working out the scale amount, I'm just not sure how to do this. Can anyone help please?
Update:
I have scrapped the Viewbox as this complicates things.. so the right canvas is just normal size too but is contained inside a border with fixed width and height. The aim is to zoom in on the zoom area until it fits to the border.
Here is the XAML for the right side:
<Border Name="ContainingBorder"
Grid.Column="1"
MaxWidth="295"
MaxHeight="487"
Height="487">
<Border.Clip>
<RectangleGeometry Rect="0.5, 0.5, 295, 487"/>
</Border.Clip>
<Canvas Name="RightCanvas"/>
</Border>
I have managed to zoom in the correct amount, it just doesn't zoom into the right centre. I just use the aspect ratio as the scale amount which appears to work:
double ratioX = ContainingBorder.AcctualWidth / zoomAreaRect.Width;
double ratioY = ContainingBorder.AcctualHeight / zoomAreaRect.Height;
double scale = ratioX < ratioY ? ratioX : ratioY;
Any ideas how I can figure out the centre x and y? The above centreX and centreY calculations don't appear to work properly.
I'm not sure if I fully understood your question. maybe pasting the xaml code would help.
Potentially you can work with the "margin" property of the canvas:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--clip to bounds for hide parts of the canvas-->
<Viewbox ClipToBounds="True">
<!--set margin negatively to zoom out of the viewbox size-->
<Canvas Margin="0,0,-100,-100" Width="200" Background="Red" Height="200">
<Rectangle Width="100" Height="100" Fill="Gray"/>
</Canvas>
</Viewbox>
</Grid>
Maybe you can solve your problem with a visual brush:
<Window x:Class="ZoomViewBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="700">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Viewbox Width="700" Height="300">
<Canvas x:Name="LeftCanvas" Width="600" Background="Red" Height="600">
<!--drawing Rectangle simulates your zoom area-->
<Rectangle x:Name="DrawingRectange" Width="100" Height="150" Fill="Green" Canvas.Left="200" Canvas.Top="300"></Rectangle>
<Rectangle Width="200" Height="200" Fill="Gray" Canvas.Left="250" Canvas.Top="350"/>
</Canvas>
</Viewbox>
<Viewbox Grid.Column="1">
<Rectangle Width="{Binding ElementName=DrawingRectange,Path=Width}" Height="{Binding ElementName=DrawingRectange,Path=Height}">
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=LeftCanvas}" Stretch="None" >
<VisualBrush.Viewbox>
<!-- X = DrawingRectangle.X / LeftCanvas.Width
Y = DrawingRectangle.X / LeftCanvas.Height
Width = DrawingRectangle.Width / LeftCanvas.Width
Height = DrawingRectangle.Height / LeftCanvas.Height-->
<Rect X="0.333" Y="0.5" Width="0.1666" Height="0.25"></Rect>
</VisualBrush.Viewbox>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</Viewbox>
</Grid>
Of coarse the Viewbox of the VisualBrush must be binded to the actual values from the DrawingRectangle (in code behind or via converters).

How to set a control's position according to another in windows 8 app?

I have a page like this:
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
here are contents
they are forever absolutely in the center of the screen
no matter of the resolution/size
</StackPanel>
</Grid>
Everything is working fine. But now I want to add a back button in the top-left corner.
And I don't want to split the grid into 2 columns and 2 rows like this:
the contents are no longer absolutely centered, even we can split the grid by percent, because the contents are sometimes very wide and sometimes very narrow.
I want to know how can I keep the contents horizontal/vertical aligned "Center", and then set the back button's position relatively to the content.
I would suggest using a grid layout with 3 columns to ensure the content is centered with the columns widths set to *,Auto, *. This will ensure the main content is always centered and not care about the size of the button. You can then use margins to set the seperation as required. This is a techinique I have used in SL, WPF & Metro.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button HorizontalAlignment="Right" VerticalAlignment="Top" Content="Do Something"/>
<ContentControl VerticalAlignment="Top" Content="My custom content"/>
</Grid>
slightly hacky ansewer
You might be able to achieve by positioning your stackpanel in the center, and then set a negative left margin the width of the button to shift everything left by the required amount...
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="-45,0,0,0">
<button with width and padding totalling 45px />
here are contents
they are forever absolutely in the center of the screen
no matter of the resolution/size
</StackPanel>
</Grid>

Categories