Panel with percentage coordinates - wpf

I would like use a panel whose children have coordinates specified as percentage of total panel's width/height. Moreover, I should be able to animate the coordinate property, for example to make a button move from 10% to 50% panel's width.
I've made 2 attempts:
Use a Grid and specify size as stars - this was not enough, because AFAIK by default WPF cannot animate distance properties specified by stars. I've found somewhere a custom class that enabled me to do so, it even worked, hovewer I consider that solution overly complicated an I am looking for something simpler.
Use a Canvas with fixed width and height and put it inside a Viewbox - this is a simple solution, but when resizing the Viewbox the whole content of Canvas is resized too. I want the content to have fixed size.
Is there a simple solution or should I implement my own panel (or maybe extend one of the existing ones, i.e. Canvas)?
Cheers!

I would:
subclass Canvas, perhaps calling it RelativeCanvas or RatioCanvas
add two attached properties: XRatio and YRatio
override ArrangeOverride and loop over all children. For each child, use their XRatio and YRatio along with the ActualWidth and ActualHeight of the RelativeCanvas to calculate and apply values for their Canvas.Left and Canvas.Top attached properties
You would use it as follows:
<local:RelativeCanvas>
<!-- the top-left of this button will be center of panel -->
<Button local:RelativeCanvas.XRatio="50" local:RelativeCanvas.YRatio="50"/>
</local:RelativeCanvas>
One thing you might like to add after you get that working is control over alignment. For example, I might to align the center of a control to the specified ratio, not its top-left corner.

There's one here: WPF Proportional Panel

Related

How do you have a panel `ClipToBounds` without clipping children?

I have a custom Panel implementation that renders objects relative to a physical space (think like a floor plan). The panel allows the following actions:
Zoom in/out
Pan up/down/left/right
and more that isn't relevant to this question
The panel lives in with several other elements on screen, and I need to make sure the custom panel's graphics don't spill over the navigation and other controls.
The problem is this:
If the panel is set to clip, it clips the children before arranging them.
Let's say I have a circle in the floor plan and the user zooms in enough to make the circle bigger than the parent control. The panel applies clipping to the circle as if it were placed dead center, then arranges the circle where it is supposed to be. The end result is that the circle no longer looks like a circle and I have gaps in the image.
I need the clipping to be applied after arranging the elements, or only applied to the overall image as compiled by the children. How can I do this?
It turns out I had the opposite problem of the question the OP had in his letter to Dr. WPF where ClipToBounds = "Maybe"
If I override the GetLayoutClip() method in my control and return null, I essentially turn off clipping for the element. However, since ClipToBounds is still true for my panel, the rendered item doesn't spill over the rest of the application.
In the base class for all items that will be added to my custom panel I have this:
protected override Geometry GetLayoutClip(Size layoutSlotSize)
{
return null;
}
In my case this is exactly what I want. I honestly want my layout clip to be based on the parent panel and not the child size (with no sense of location within the panel).

WPF ScrollViewer set ScrollableHeight

I have a user control which paints content in OnRender. The Height of that control grows by and by.
I added this control to a ScrollViewer. The control does only repaint the currently visible area (viewport) (+/- a view lines for smoother scrolling).
Everything works fine so far...
But since the control usualy grows up to a few hundred thousands of pixels I want to keep the Height of my control as small as possible and provide a different Height value to bind to the ScrollableHeight of the ScrollViewer (same goes for VerticalOffset). But there is no setter for ScrollableHeight. It binds automatically to the Height of my Control. Neither can I override Height.
How can I customize my ScrollViewer (or VerticalScrollbar) to keep the real Height of my control small?
I did something like this in the past. What you need to do is to write your own layout Panel and implement the IScrollInfo in it. The interface looks big, but most of it is just calling one of the main set methods. The layouter needs to set some of the IScrollInfo properties, like ExtentHeight, Offset etc. and these are your way to customize how the ScrollViewer will calculate the scroll position and the scrollable area for your "virtual" canvas size. For implementing the IScrollInfo i used this tutorial as a guidance.

wpf custom panel

I'm looking at creating a panel that takes all the children and wraps them inside of a ViewBox so it can scale them down to a certain uniform size.
I'm not sure if this is even possible to do, but I thought I'd ask. The reason why I'm not sure if this is possible is because Panel's don't have any xaml associated to them. They measure and arrange children in code. Any tips?
After not being clear enough, I figure I'll be more specific:
I want a Panel because I want to arrange and measure my children.
The measure find the AVERAGE height/width of all the children elements.
The arrange puts them into a grid-like pattern and scales them to the average height/width. I don't want them cliptobounds, I want them scaled up or scaled down appropriately.
I already got the measureoverride function working to find the average height/width, but I can't figure out how to scale them in my arrangeoverride.
You haven't completely specified how you want your panel to work so I can't give you a sample panel, but I can point you to a working panel that does something similar to what you want to do:
Making a Viewbox scale vertically but stretch horizontally
In the example you can see that we can simulate a Viewbox by just scaling the children of the panel ourselves. As I mentioned, exactly how you want to scale them is up to you.
Even after your update, I agree with Markus. It doesn't seem you need a custom panel. What you need is an ItemsControl with UniformGrid as ItemsPanel and ViewBox as ItemsContainer. UniformGrid decides how your items container are arranged. ViewBox handles stretch and scale of each item.

Silverlight canvas scrollbars

I have read that placing a canvas inside a scrollviewer won't work because the canvas does not report its size. I have been experimenting with different containers (borders, grids, canvases and scrollviewers) and could really do with a simple explanation of how scrollviewers behave within nested containers. e.g. If I have a container hierarchy of UserControl>Grid1>Canvas1>ScrollViewer>Grid2>Canvas2 should scrollbars appear around Grid2 when Canvas2 becomes wider than Canvas1? (or indeed wider than UserControl) If not, how should I organise my containers so I can add loads of uielements to Canvas2 and have scrollbars appear as necessary. (My usercontrol width and height are set to 100%)
Canvas will work with a ScrollViewer if you explicitly give it a size. The problem comes from the fact that if you don't supply a Height and Width for any control it will try and determine it's available area based off it's parent container. A ScrollViewer however has infinite available area.
Take a Grid for example. If I define a Grid that has 2 Star Width columns and 2 Star Height rows. How does the Grid know how wide each of those columns should be? The star says they should be half of the available area, but inside a ScrollViewer the available area is infinity.
What controls are you adding to Canvas2? If you are adding them with fixed positions than the ScrollViewer will expand to house all elements. If you aren't giving them fixed positions than all the controls will stack on top of each other, because that is the arrange behavior of a Canvas

WPF Resizable Canvas

I need to implement a Canvas which scales its contents according to its size. I know there is Viewbox, which scales everything inside of it. However I cannot use that, because some elements have a fixed size and cannot be scaled.
Also how can I bind the size of the Canvas to the parent element (for example a resizable window). There is sizeToContent for windows, I want the size fitting exactly the other way round. Also the canvas uses some drawing based on the size of the hosting element, how is redraw triggered and how can I ensure that it only draws if it gets a valid (or min) size?
If you don't specify any width or height to the canvas it automatically uses all the available space. This is because the default VerticalAlignment and HorizontalAlignment are set to Stretch.
What do you mean by canvas that scales it's contents according to it's size without scaling all the contents as some have fixed size?
Update after comments
If your drawing algorithm already scales the content to the canvas' height and width then all you need to do is to resize the canvas to fit the area I believe? In that case just remove the hardcoded height/width values and the canvas will resize to fit the container.
You might need to use ActualHeight/ActualWidth instead of Height/Width in the drawing algorithm after this though. ActualHeight/ActualWidth return the values that the layout container will give your canvas so these represents the values the canvas is drawn with.
I think you can find the answers to all your questions in my London Underground demo.
I'm doing this from memory, but if I recall correctly a Window uses either a Panel or a Canvas as part of it's ControlTemplate (in which lies the ContentPresenter), which means that a Canvas placed directly in a Window will have issues resizing automatically like it might elsewhere. There are a few basic ways to address this.
1 Write a new ControlTemplate for your Window to use. :(2 Place your content directly in the Window rather than in a Canvas inside the Window. :/
3 Do a by-name binding. :)
<MyWindow x:Name="topWindow">
<Canvas x:Name="topCanvas" Width="{Binding ElementName=topWindow, Path=ActualWidth}" Height="{Binding ElementName=topWindow, Path=ActualHeight}">
...Content...
</Canvas>
</MyWindow>
(As it happens, I often bind grids inside Canvases in this fashion, so I can easily animate items moving from one grid position to another.)

Resources