I am learning WPF and trying to dynamically draw a collection of polygons to a canvas.
I am configuring the polygons as follows:
mHexagon = new Polygon();
mHexagon.Width = Diameter * 2;
mHexagon.Height = Diameter * 2;
mHexagon.StrokeThickness = 1;
mHexagon.Stroke = Brushes.AliceBlue;
mHexagon.Points = new PointCollection(Vertices.Select(v => new Point(v.X, v.Y)));
Sample point collection:
Vertices
:128,64
:96,8.57437415779593
:32,8.57437415779592
:0,64
:32,119.425625842204
:96,119.425625842204
I am adding the polygon as follows (encapsulated in Hexagon class):
PlayerMapWindow.Children.Add(hex.Polygon);
And my XAML looks like:
<Border BorderThickness="1"
BorderBrush="DarkRed"
Background="Black"
Padding="2">
<Viewbox Name="PlayerMapViewbox"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Canvas Name="PlayerMapWindow"/>
</Viewbox>
</Border>
After adding the polygon (hex) to the canvas children, nothing is drawn.
Remove the ViewBox and your code works fine.
<Border BorderThickness="1"
BorderBrush="DarkRed"
Background="Black"
Padding="2">
<Canvas Name="PlayerMapWindow"/>
</Border>
Canvas has default height and width set to 0. If you want scaling feature of ViewBox, you have to constraint width and height of canvas.
<Border BorderThickness="1"
BorderBrush="DarkRed"
Background="Black"
Padding="2">
<Viewbox Name="PlayerMapViewbox"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Canvas Name="PlayerMapWindow" Width="300" Height="300"/>
</Viewbox>
</Border>
From MSDN:
Canvas is the only panel element that has no inherent layout
characteristics. A Canvas has default Height and Width properties of
zero, unless it is the child of an element that automatically sizes
its child elements. Child elements of a Canvas are never resized, they
are just positioned at their designated coordinates. This provides
flexibility for situations in which inherent sizing constraints or
alignment are not needed or wanted. For cases in which you want child
content to be automatically resized and aligned, it is usually best to
use a Grid element.
Related
Within my Grid I have the following StackPanel:
<StackPanel Grid.Row="6" Grid.Column="4" Margin="5, 5, 10, 5" VerticalAlignment="Stretch" Background="Yellow" Orientation="Vertical">
<Border Background="LightGray" BorderThickness="1" BorderBrush="Black" VerticalAlignment="Center">
<StackPanel Orientation="Vertical">
<Button BorderThickness="0" Background="Transparent" Margin="0 10" HorizontalAlignment="Center">
<Button.Content>
<Image Source="\Resources\Arrow2 Up.ico"/>
</Button.Content>
</Button>
<Button BorderThickness="0" Background="Transparent" Margin="0 10" HorizontalAlignment="Center">
<Button.Content>
<Image Source="\Resources\Arrow2 Down.ico"/>
</Button.Content>
</Button>
</StackPanel>
</Border>
</StackPanel>
I have made the background yellow, so we can see the size of the StackPanel. The whole thing looks as follows:
I want to have the button in the vertical center. I can achieve that by replacing the outer StackPanel with another Grid and we get the following.
It seems like StackPanels do not know their own actual height but Grids do? Why is that the case, or is there another reason for this behavior?
A StackPanel by definition stacks elements vertically from top to bottom or horizontally from left to right (or right to left with FlowDirection="RightToLeft") depending on its Orientation.
Arranges child elements into a single line that can be oriented horizontally or vertically.
That is all it does, it does not take into account proportional sizes based on the available space. A Grid on the other hand has columns and rows that can take advantage of star sizing to achieve exactly that.
Columns and rows that are defined within a Grid can take advantage of Star sizing to distribute remaining space proportionally. When Star is selected as the height or width of a row or column, that column or row receives a weighted proportion of the remaining available space.
In your case you may not even need a StackPanel. You could just place your Border directly inside the Grid and set its VerticalAlignment and HorizontalAligment properties accordingly e.g:
<Border Grid.Row="6" Grid.Column="4" Margin="5, 5, 10, 5" Background="LightGray" BorderThickness="1" BorderBrush="Black" VerticalAlignment="Center">
I want to add scroll view to my program and I tried ScrollView control but that don't take effect. It's my first time dealing with scrolls please help me :).
My xaml Code:
<DockPanel Grid.Row="1" Background="#FF695887">
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="795">
<Canvas Name="zemelapis" Height="Auto" Width="Auto">
<Image Name="pav_kelias" />
<Image Name="car1" />
</Canvas>
</ScrollViewer>
</DockPanel>
Because these 2 images is not fitting here I need a scroll for them. Maybe I should use ScrollBar?
My program example: https://gyazo.com/a4ba7e4d5004632e2229a87e686c4c09
, as you can see bottom image is not fitting in range of window.
You have specified Auto as Height and Width. This implies that the Canvas will fill the height available to it.
From the documentation:
Auto sizing behavior implies that the element will fill the height
available to it.
In this case the available height is the height of the ScrollViewer.
If you want the Canvas to be bigger, and hence the ScrollViewer to scroll, you should set a height on Canvas that is bigger than the height of ScrollViewer.
So, for example:
<DockPanel Grid.Row="1" Background="#FF695887">
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="795">
<Canvas Name="zemelapis" Height="1000" Width="Auto">
<Image Name="pav_kelias" />
<Image Name="car1" />
</Canvas>
</ScrollViewer>
</DockPanel>
If you want your ScrollViewer to work easily, use a Grid instead of a Canvas:
<DockPanel Background="#FF695887">
<ScrollViewer >
<Grid Name="zemelapis">
<Image Name="pav_kelias" Source="acteurs.png"/>
<Image Name="car1" Source="public.jpg"/>
</Grid>
</ScrollViewer>
</DockPanel>
As explain by Domysee, Canvas gives you total control of the layout. Grid however will automatically adjust its size depending on the content.
See http://www.wpf-tutorial.com/panels/introduction-to-wpf-panels/
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>
Basically I'm trying to do something whereby a WPF image is inside a WPF border, and periodically I rotate the image by changing the RotateTransform Angle property.
The problem is, when I rotate the image, the border doesn't rotate, or attempt to change to fit the new shape of the picture. I've tried setting it's Alignment properties to stretch, and even binding the height/width of the border to that of the image, but no luck. I suspect the problem is that, when I rotate the image, it doesn't actually change the height or width of the Image object, so of course the border doesn't know what to do.
Is there a better way to rotate the image that would allow the border to resize, or if not, how do I get the border to resize correctly, given that I'm changing the RotateTransform Angle.
Thanks!
You can use the LayoutTransform instead of RenderTransform for this. If you try changing the angle of rotation you'll see the border changes size to accommodate it. (Think this is what you're asking? If you actually want the border to rotate then you can just rotate that instead of the image)
<Window x:Class="rotate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Black" BorderThickness="1">
<Grid Background="Blue" Width="80" Height="80">
<Grid.LayoutTransform>
<RotateTransform Angle="10"/>
</Grid.LayoutTransform>
</Grid>
</Border>
</Grid>
</Window>
Use LayoutTransform instead of RenderTranform.
RenderTransform only does a visual transformation of the control and is applied after measuring and arranging Controls. Therefore it doesn't affect the size seen by other controls.
LayoutTransform really affects the layout of the object. It's applied before measuring and arranging control, so the other control see a change in the size.
Caution: LayoutTransform is much slower and won't usually give a smooth animation.
<Border BorderThickness="5" BorderBrush="Red" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Width="50" Height="50">
<Image.LayoutTransform>
<RotateTransform Angle="45" />
</Image.LayoutTransform>
</Image>
</Border>
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).