Is there any way to merge margins in WPF? - wpf

In HTML, for a table (at least), one can style the element so that margins are merged. I.e. two adjacent rows both have top and bottom margins of 10, so the gap between these two rows will be 20. When their margins are merged, the gap is only 10.
Is there any way to achieve this in WPF?

There is no easy way to do this, and when you look at how WPF handles layout you'll see why, but also see that there is a hard way if you're up to it.
As you can see from the WPF source code for FrameworkElement, the MeasureCore sealed override method (which prevents us from overriding it further) adds the element's margins before returning with its desired size. Annoyingly, they seem to have a BypassLayoutPolicies option which would prevent this, but for reasons possibly ranging from short-sighted to sadistic, they made this internal so it's not an option. Thus the fully margined size of the element is what always winds up being assigned to DesiredSize, which is basically what all layout panels (StackPanel) etc. naively use to arrange the items during ArrangeOverride.
But therein is the solution, if you're willing to subclass all the panels and override their MeasureOverride and ArrangeOverride. Knowing the margin values of two adjacent children you could collapse their respective right/left or bottom/top margins together during the measure and arrange passes, by just subtracting the duplicative portion of the margin from their `DesiredSize's.
But if you're gonna subclass anyway, another option, which I would consider cleaner and a better practice overall, is to add a Spacing property to your subclassed Panels, which exists in the WinUI version of StackPanel. This would require you to either ensure that your child items all have zero margins, or to subtract their actual margin values out from their DesiredSize's. You would then add this spacing instead between the items during the arrange pass.
StackPanel is easy enough to extend this way as both passes are very simple. Grid, unfortunately, is a lot more complicated. VirtualizingStackPanel is probably out of the question... So this is by no means a silver bullet, but given how common StackPanel is, even just extending that control would cover a whole lot of ground and lead to much cleaner layouts IMO.

If I had more reputation I'd mark this as a duplicate of Is it possible to emulate border-collapse (ala CSS) in a WPF ItemsControl? . It looks as if this is kludgeable (for a ListBox at least) using a DataTrigger to check the value of preceding entries and set borders accordingly for null values.

Related

WPF WrapPanel, but fills empty space due to different size items?

I have a list of "Group" objects, each of which contains a list of "Option" objects, that show up as checkboxes. I want these to be displayed in a condensed fashion automatically without me having to layout the UI manually with something such as a Grid (which is what I've done in the past, and takes a lot of effort).
My groups have varying numbers of options, so the size of the container for the group is not the same across groups. I'm using a WrapPanel, but it leads to a fairly ugly design because each item in the WrapPanel appears to be slotted into the same size container:
I know I've done this in HTML/CSS/JS, where I can have it automatically condense the unused space. Is there something like this for WPF? My list of options is manually created, but I can add/remove options fairly easily in my code, so I would rather not have to manually recalculate things in a grid view.
I've looked at Is there any way to occupy blank space in WrapPanel automatically?, which sounds similar, but the answer to that question does not have an example and I could not figure out what I was actually supposed to change/use in the answer (my attempts using it did not make any difference at all to layout).
I guess you use Horizontal Orientation of WrapPanel, so every row have height as the maximum its element. Your problem isn't free space in the ends of rows, so the solution that you mentioned doesn't work for you. You can try to use Vertical orientation of WrapPanel, your wrappanel' elements look like they have similar width, possible it would look better.

Is there an alternative to a WPF WrapPanel that wraps after a certain number of items, not a height?

Normally a WPF WrapPanel (Orientation="Vertical") will stack items vertically (and grow vertically) until it runs out of space from the parent container, and then it will "wrap" to the next column.
I want this functionality, but I want to add a hard limit to the number of items in a column. For instance, if my height is 100 and I have 3 items that are 30 pixels high, normally it could fit them all without wrapping. However, say I want to force it to wrap after 2. In that case, I want it to only grow to a height of 60, and wrap the 3rd item into the second column.
Is there something I can do to make this happen?
Maybe you can do it with the UniformGrid.
Use the Rows property to definie the amount of elements in vertical direction.
here is a nice little article about the available layout panels in WPF. If one of these does not fit the bill, you might have to build your own custom panel, here is a decent demo.

Setting control height explicitly

I have a XamDataGrid in one of my user controls, inside of a stackpanel. I want the grid to maintain the same height regardless of how many rows are present in the grid. To do that, I set the grid's Height property to an explicit value.
Is that how things are done in WPF? Every time I do explicit sizing I feel like I am doing WinForms and not using WPF properly. Is setting the Height directly the only/correct solution?
There's nothing wrong with setting an explicit Height in situations where you want an element to always stay the same height. Where it's less appropriate is in situations where sizing is better handled by the parent layout Panel or the element's child content which can use the available space dynamically.
WPF uses a relative measurement system which at first glance is not intuitive. I have never found an example when I was forced to use explicit sizes ( once when I paint something on Canvas). I use styles in 90% cases where I define Padding, Margin, Aligment etc. Sometimes I use MinHeight and MinWidth for simple things.
About that Grid you can put it in the ScrollViewer or ViewBox to have dynamic sizing, yet If it won't be trouble set the explicit Height.

WPF: Creating snappable grid lines with variable spacing

I'm currently creating a MSPaint-like WPF-application and struggling with the implementation of a snappable grid.
The painting of the grid is no problem with a VisualBrush and a Rectangle but the problem is that these lines are then purely for looks and can't be easily changed (for example highlighted when the snapping to a specific line triggered).
My other idea was to have a 2 Canvas solution where 1 Canvas is used for the elements and one Canvas (who is positioned above the other) contains all the grid lines. However I have the feeling that this would mean quite a performance hit.
Are there any other possible ways to implement this kind of functionality?
Efficiency considerations of a two-panel approach vs DrawingContext
I have good news for you: You are wrong about the significant performance hit. Your two-canvas idea is nearly optimal, even if you use individual objects for the grid lines. This is because WPF uses retained-mode rendering: When you create the canvas, everything on it is serialized into a compact structure at native level. This only changes when you change the grid lines in some way, such as changing the grid spacing. At all other times the performance will be indistinguishable from the very fastest possible managed-code methods.
A slight performance increase could be had by using DrawingContext as Nicholas describes.
A simpler and more efficient solution
Perhaps a better way then drawing individual lines on the grid canvas is to use two tiled visual brushes (one horizontal, one vertical) to draw all unhilighted lines, then use Rectangle(s) added in code-behind to hilight the line(s) you are snapping to.
The main advantage of this technique is that your grid can be effectively infinite, so there is no need to calculate the right number of grid lines to draw and then update this every time the window resizes or the zoom changes. You also only have three UIElements involved, plus one more for each grid line that is currently hilighted. It also seems cleaner to me than tracking collections of grid lines.
The reason you want to use two visual brushes is that drawing is more efficient: The brush drawing the vertical lines is stretched to a huge distance (eg double.MaxValue/2) in the vertical direction so the GPU gets only one drawing call per vertical line, the same for the horizontal. Doing a two-way tiling is much less efficient.
Adorner layer
Since you asked about alternatives, another possibility is to use Adorner and AdornerLayer with any of the solutions above rather than stacking your canvas using eg a Grid or containing Canvas. For a Paint-like application this is nice because the adorner layer can be above your graphic layer(s) yet the adorners can still attach to individual items that are being displayed.
You might consider drawing your grid using the DrawingContext inside of OnRender. Drawing this way does not introduce new UIElements into the visual tree, which helps to keep performance up. In some ways, it is similar to what you are currently doing with the VisualBrush, which also does not create new UI elements per copy.
However, since you will actually be individually drawing each line instead of copying the look of a single line, you'll be able to highlight the grid line(s) that participate in snapping without changing the colors of those that do not.
If you are going to go down this route, make sure to have a look into GuidelineSets for positioning your guide lines (more details here), since you'll probably want to have your guide lines snap to the device's pixels so that they draw sharply.

Which has better rendering performance, Stackpanel or Canvas+TranslateTransform? WPF/Silverlight

I always use a Canvas when I'm laying out my visuals usually because I will need adjust the RenderTransform.TranslateTransform to animate in some way. A colleague recently told me that unless I explicitly need to animate I should always use the A Stackpanel because it is faster than a RenderTransform.TranslateTransform when laying out objects to the visual.
Is this true?
Anyone have any data either way?
I don't have any data on this, but if we're just talking about stacking then you using a TranslateTransform to achieve the exact positioning of each item seems extremely fragile since the item could theoretically be of different heights/widths which could also theoretically change dynamically at runtime not to mention if the designer changes them by hand they have to redo the translate transform for N other UI elements. Using StackPanel means the Measure/Arrange phases will occur and no matter what size the items are they will be laid out precisely.

Resources