How to rearrange children of canvas on ArrangeOverride wpf - wpf

I have canvas, where children are add dynamically to the canvas, lets say an image is placed at left = 50, top 50 when canvas width (500) and height(200). When the window is maximized the canvas width and height changes ( 1000, 400), this time want to rearrange the image position as per canvas width and height. Based on research found that, have to implement
MeasureOverride and ArrangeOverride methods of panel.
Here user may add any number of items to the canvass, want to scale the children relative to the canvas size.
How to implement the logic in above methods?

You could your implementing your logic (whatever that is) in an event handler for the SizeChanged event:
private void Canvas_SizeChanged(object sender, SizeChangedEventArgs e)
{
Canvas canvas = sender as Canvas;
Size size = e.NewSize;
foreach (UIElement child in canvas.Children)
{
//set the new postion of each element according to your logic
Canvas.SetLeft(child, );
}
}
<Canvas x:Name="canvas" Width="500" Height="200" Background="Yellow" SizeChanged="Canvas_SizeChanged">
<Button Content="..." Canvas.Left="50" Canvas.Top="50" />
</Canvas>
There is no reason to create a custom Panel and override the MeasureOverride and ArrangeOverride methods.

Related

Alternate between "Stretch.Uniform" and "Stretching.None" for ViewBox with ScrollViewer

I want to achieve a very well known behavior seen in the browser when you have an image to display that is larger then the monitor:
Originally, the image is displayed fitting inside the window area, and the mouse cursor is a magnifying glass with a "+" icon;
If you click, two things happen:
a. The image is displayed with its native pixel size;
b. Scroll bars appear;
I want this effect with a larger-than-screen UniformGrid. For that, I can use ViewBox. I have already got what I want putting the control inside a ViewBox with Stretch.Uniform property, and upon MouseLeftButtonDown event it toggles between Stretch.None and Stretch.Uniform, just like the large image in browser analogy, only without scroll bars.
Now if I add the ScrollViewer (ViewBox -> ScrollViewer -> UniformGrid), the effect doesn't work anymore, because the ScrollViewer always displays the (larger than window) MyUserControl with its native resolution, that is, clipped and with scroll bars activated, while I would like to alternate between this and a "fitting in ViewBox" version.
Here is how I get the resizing, but the ScrollViewer never displays:
<Viewbox x:Name="vbox" Stretch="None">
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</ScrollViewer>
</Viewbox>
And if change the order, then the Scroll bars always display and the zoom doesn't toggle upon mouse click (although the event fires):
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<Viewbox x:Name="vbox" Stretch="None">
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</Viewbox>
</ScrollViewer>
And here the code behind event:
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (vbox.Stretch == Stretch.None)
{
vbox.Stretch = Stretch.Uniform;
}
else
vbox.Stretch = Stretch.None;
}
So what am I doing wrong, or what should I do so that the intended behavior works?
The way I see it, I would like to alternate between having the control in a ViewBox (Stretch.Uniform) and having the control inside a ScrollViewer, but I wonder how to have the same effect with both elements being part of the layout tree (one inside another), or even if I should, move the UniformGrid in and out of containers I would manipulate programmatically in code behind.
Got it to work in sort of a hackish way, by having a Grid with both a ViewBox and a ScrollViewer, and putting the UniformGrid inside one of them in XAML. Then, in code-behind, I programmatically detach the UniformGrid from its present container, and attach it to the other (using a boolean flag to control where it is, but that is debatable):
<Grid x:Name="grid">
<ScrollViewer x:Name="scroll" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
<Viewbox x:Name="viewbox" Stretch="Uniform">
<UniformGrid x:Name="ugrid" Columns="2" MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior/>
<local:AtlasMasculinoPosterior/>
</UniformGrid>
</Viewbox>
</Grid>
and
bool atlasfullscreen = false;
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UniformGrid ug = sender as UniformGrid;
if (atlasfullscreen)
{
scroll.Content = null;
viewbox.Child = ug;
atlasfullscreen = false;
}
else
{
viewbox.Child = null;
scroll.Content = ug;
atlasfullscreen = true;
}
}
I had a similar use case where I had an item that I needed to alternate between Stretch.None and Stretch.Uniform, and when Stretch.None, I needed the scrollbars to be visible.
What I finally figured out was that when I set Stretch.None, I needed to set the ScrollViewer's Width & Height to the ViewBox's parent ActualWidth / Height, and when Stretch.Uniform, I needed to clear the ScollViewer's width and height.
So using your original XAML, plus the new Grid, here's the new XAML:
<Grid x:Name="grid">
<Viewbox x:Name="vbox"
Stretch="Uniform">
<ScrollViewer x:Name="scroll"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<UniformGrid x:Name="ugrid"
Columns="2"
MouseLeftButtonDown="UniformGrid_MouseLeftButtonDown">
<local:AtlasMasculinoAnterior />
<local:AtlasMasculinoPosterior />
</UniformGrid>
</ScrollViewer>
</Viewbox>
</Grid>
New code behind:
private void UniformGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (vbox.Stretch == Stretch.None)
{
vbox.Stretch = Stretch.Uniform;
scroll.Width = double.NaN;
scroll.Height = double.NaN;
}
else
{
vbox.Stretch = Stretch.None;
scroll.Width = grid.ActualWidth;
scroll.Height = grid.ActualHeight;
}
}
You might need to tweak the above example for how the Viewbox now being in a grid - but for my use case with similar XAML / code I got mine working without having to constantly move the child from the Viewbox to another control and back again.
So in summary: when Viewbox.Stretch = Uniform, set scrollviewer's width / height to double.NaN, and when Viewbox.Stretch = None, set scrollviewer's width / height to Viewbox.Parent.ActualWidth / Height.

WPF image that stretches and crops

I want to have an image as the background of a grid. By default, the image should be right- and bottom-aligned. The image should also uniformly stretch to the height of the container. However, if the container gets resized such that the width is thinner than the size of the image, the image starts resizing to become smaller. Instead of this happening, I want to clip the image so that parts from the right start getting hidden.
Here is my image XAML right now:
<Image Source="blahblah.png"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Stretch="Uniform" />
Here is the behavior I want described in a picture (the black border is the container size):
Try a mix of different panels, like this:
<Grid x:Name="mainGrid" >
<Grid x:Name="backgroundSpacer" HorizontalAlignment="Stretch"
MinWidth="{Binding ElementName=backgroundImage, Path=ActualWidth}" >
<StackPanel x:Name="backgroundPanel"
HorizontalAlignment="Right" Orientation="Horizontal" >
<Image x:Name="backgroundImage" Stretch="Fill"
Source="http://programming.enthuses.me/1.png" />
</StackPanel>
</Grid>
...
</Grid>
Here, we put the image in a horizontal (and right-aligned) StackPanel. The StackPanel stretches vertically by default, and a feature of the StackPanel is that it tells its child items that they have unlimited width. Therefore, the Image is always rendered as tall as it can be.
Next, we have a "backgroundSpacer" which normally stretches across the whole container grid, but through the MinWidth binding we make sure it is always wide enough to contain the whole image. If "mainGrid" gets too thin, "backgroundSpacer" gets clipped, and WPF's default clipping behavior clips off its right side.
I don't see a way either by just setting some "magic" properties. I would implement a panel that hosts your image and manages the desired behaviour (scaling and positioning). That's how the panel looks like:
public class ImagePanel : Panel
{
public ImagePanel ()
{
}
protected override Size MeasureOverride (Size availableSize)
{
if (InternalChildren.Count > 0)
{
FrameworkElement child = (FrameworkElement)InternalChildren[0];
var childMaxSize = new Size (double.PositiveInfinity, availableSize.Height);
child.Measure (childMaxSize);
}
return availableSize;
}
protected override Size ArrangeOverride (Size finalSize)
{
if (InternalChildren.Count > 0)
{
FrameworkElement child = (FrameworkElement)InternalChildren[0];
double x = finalSize.Width - child.DesiredSize.Width;
if (x < 0)
{
x = 0;
}
child.Arrange (new Rect (new Point (x, 0), child.DesiredSize));
}
return finalSize; // Returns the final Arranged size
}
}
Use it this way:
<local:ImagePanel>
<Image Source="Image.jpg" Fill="Uniform"/>
</local:ImagePanel>
Have you ever tried used a ViewBox Element? Put this on a Parent with only MaxWidth/Width defined and set the ClipToBounds to True. For more information about this Control (That helps a lot when we talking about resizing):
http://msdn.microsoft.com/pt-br/library/system.windows.controls.viewbox(v=vs.110).aspx

WPF Canvas Desired size

I have the following control:
<UserControl x:Class="FooBar.AnnotationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="400" Width="500" >
<ScrollViewer Height="400" Width="500">
<Canvas Height="400" Width="500" Name="ctlCanvas" MouseLeftButtonDown="MouseLeftButtonDownHandler" MouseWheel="Canvas_MouseWheel" >
<Canvas.RenderTransform>
<ScaleTransform x:Name="ZoomTransform" />
</Canvas.RenderTransform>
</Canvas>
</ScrollViewer>
</UserControl>
namespace FooBar
{
public partial class AnnotationControl : UserControl
{
public AnnotationControl()
{
InitializeComponent();
}
private void MouseLeftButtonDownHandler( object sender, MouseButtonEventArgs args)
{
//Do Something
}
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
ctlCanvas.Measure(new Size(ctlCanvas.ActualWidth * ZoomTransform.ScaleX, ctlCanvas.ActualHeight * ZoomTransform.ScaleY));
}
}
}
I'm trying to get the scroll viewer to respond to the scaling of the Canvas. The call to Canvas.Measure doesn't appear to change the Desired size of the Canvas. Any idea what is going on here?
You should NOT call Measure on your own. This method is supposed to be called in the layout step, and not somewhere else. Also a RenderTransform doesn't change your Size. The RenderTransform is applied AFTER the actual Layout is done. So you have a scrollviewer that don't need to scroll its content, because its the same size. What you might want is LayoutTransform.
Canvas is the most primitive element and it simply not designed to work with the ScrollViewer. Use Grid/StackPanel/WarPanel/UniformGrid instead.
Ok, I seem to have found a solution. It looks like I can wrap my canvas with another canvas and when I scale it, I simply set the height and width for the outer canvas = initial height and width times the current X and Y scales of the ScaleTransform.

How to create stretching clipping rectangle in Silverlight

Since Silverlight doesn't have the comfy feature of 'ClipToBounds' properties on controls, I have to define clipping shapes by myself. I was wondering if I could create a clipping rectangle that's following the size of my control. Any suggestions?
If there is an existing control in you layout that you want to dynamically clip then use its SizeChanged event. For example lets say you want to clip this Grid:-
<Grid SizeChanged="Grid_SizeChanged" Width="50" Height="20">
<Grid.Clip>
<RectangleGeometry />
</Grid.Clip>
<TextBlock Margin="0 -9 0 0" Text="This text should not be legible" />
</Grid>
With the code-behind:-
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
((RectangleGeometry)((Grid)sender).Clip).Rect = new Rect(0.0, 0.0, e.NewSize.Width, e.NewSize.Height);
}
For a your own custom control you might consider handling the clip rectangle in the ArrangeOverride instead of relying on the SizeChanged event. In this case you probably want to assign RectangleGeometry to the Clip property in code rather than relying on it being assigned in the Xaml of the default template.
Silverlight supports that:
try using HorisontalAlignment and vertical alignment propertys. Set them to stretch.
If this doesn't work then you will have to post xaml example.

WPF custom control size override

I'm creating a custom WPF control which derives from Grid.
It's a Chess board and so I want it's width to be the same as it's height.
How can I accomplish this?
Edit:
I tried the following.
private void cbcBoard_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width != e.NewSize.Height)
{
double m = Math.Min(e.NewSize.Width, e.NewSize.Height);
cbcBoard.Width = m;
cbcBoard.Height = m;
}
}
It didn't work. Any ideas?
Thanks.
New solution/workaround. The UserControl can stay as it is, we'll leave the scaling to the parent container.
We can't accomplish Min(Width, Height) with just the UserControl because if we set the Height for it, then the parent container won't scale it Verticaly and the same goes for Width. If we try to juggle them around then there are situations where we end in an endless Width/Height resizing loop.
What we need is another hidden control in the same space that fills it completely and can tell us what its Width and Height is everytime it changes. Then we can use the Math.Min(Width, Height) solution. Something like this. Notice how both controls are in Grid.Row="1" and Grid.Column="1".
<Rectangle Name="availableSpace"
SizeChanged="availableSpace_SizeChanged"
Fill="Transparent"
Grid.Row="1"
Grid.Column="1"/>
<myLib:UserControl1 x:Name="userControl11"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"/>
And then in the availableSpace_SizeChanged EventHandler
private void availableSpace_SizeChanged(object sender, SizeChangedEventArgs e)
{
double minValue = Math.Min(availableSpace.ActualWidth, availableSpace.ActualHeight);
userControl1.Width = minValue;
userControl1.Height = minValue;
}
Now we have 1:1 ratio of the UserControl and it will scale both Vertically and Horizontally
I think the simplest solution is to set the size of your UserControl to some constant value where Height == Width, and then drop the whole thing in a Viewbox which will handle the scaling (whilst maintaining aspect ratio) for you:
<Viewbox>
<myLib:UserControl1 x:Name="userControl11" />
</Viewbox>
This does mean that your control will be 'stretched' rather than being resized, which might make it inappropriate for you, but it is easy to implement!

Resources