Использование селектора sibling - это общее решение для стилизации других элементов при наведении курсора на заданный, но оно работает , только если другие элементы следуют за данным в DOM . Что мы можем сделать, когда другие элементы должны быть на самом деле до зависания? Предположим, что мы хотим реализовать виджет оценки уровня сигнала, подобный приведенному ниже:
Это можно легко сделать с использованием модели flexbox CSS, установив flex-direction
- reverse
, так что элементы отображаются в противоположном порядке от того, который находится в DOM.
Flexbox очень хорошо поддерживается на 95% современных браузеров.
.rating {
display: flex;
flex-direction: row-reverse;
width: 9rem;
}
.rating div {
flex: 1;
align-self: flex-end;
background-color: black;
border: 0.1rem solid white;
}
.rating div:hover {
background-color: lightblue;
}
.rating div[data-rating="1"] {
height: 5rem;
}
.rating div[data-rating="2"] {
height: 4rem;
}
.rating div[data-rating="3"] {
height: 3rem;
}
.rating div[data-rating="4"] {
height: 2rem;
}
.rating div[data-rating="5"] {
height: 1rem;
}
.rating div:hover ~ div {
background-color: lightblue;
}
Встроенный WrapPanel
не позволит вам выровнять его содержимое - только себя. Вот методика, которая позволяет установить HorizontalContentAlignment
:
using System;
using System.Windows;
using System.Windows.Controls;
public class AlignableWrapPanel : Panel
{
public HorizontalAlignment HorizontalContentAlignment
{
get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
}
public static readonly DependencyProperty HorizontalContentAlignmentProperty =
DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
{
Size curLineSize = new Size();
Size panelSize = new Size();
UIElementCollection children = base.InternalChildren;
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i] as UIElement;
// Flow passes its own constraint to children
child.Measure(constraint);
Size sz = child.DesiredSize;
if (curLineSize.Width + sz.Width > constraint.Width) //need to switch to another line
{
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
curLineSize = sz;
if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line
{
panelSize.Width = Math.Max(sz.Width, panelSize.Width);
panelSize.Height += sz.Height;
curLineSize = new Size();
}
}
else //continue to accumulate a line
{
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
}
}
// the last line size, if any need to be added
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
return panelSize;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
int firstInLine = 0;
Size curLineSize = new Size();
double accumulatedHeight = 0;
UIElementCollection children = this.InternalChildren;
for (int i = 0; i < children.Count; i++)
{
Size sz = children[i].DesiredSize;
if (curLineSize.Width + sz.Width > arrangeBounds.Width) //need to switch to another line
{
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);
accumulatedHeight += curLineSize.Height;
curLineSize = sz;
if (sz.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line
{
ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i);
accumulatedHeight += sz.Height;
curLineSize = new Size();
}
firstInLine = i;
}
else //continue to accumulate a line
{
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
}
}
if (firstInLine < children.Count)
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
return arrangeBounds;
}
private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
{
double x = 0;
if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
{
x = (boundsWidth - lineSize.Width) / 2;
}
else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
{
x = (boundsWidth - lineSize.Width);
}
UIElementCollection children = InternalChildren;
for (int i = start; i < end; i++)
{
UIElement child = children[i];
child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineSize.Height));
x += child.DesiredSize.Width;
}
}
}
К сожалению, стандартная WrapPanel не будет делать то, что вы просите, потому что она предназначена для заполнения всего доступного пространства по горизонтали (или по вертикали) перед переносом. Вам, вероятно, придется разработать свой собственный WrapPanel для обработки этого случая. Вы можете начать с этого примера, который показывает, как создать свою собственную WrapPanel .
Вот версия Silverlight
Особая благодарность @DTig
,
using System.Windows.Controls;
using System.Windows;
using Telerik.Windows;
using System;
using System.Linq;
public class AlignableWrapPanel : Panel
{
public HorizontalAlignment HorizontalContentAlignment
{
get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
}
public static readonly DependencyProperty HorizontalContentAlignmentProperty =
DependencyProperty.Register("HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
{
Size curLineSize = new Size();
Size panelSize = new Size();
UIElementCollection children = base.Children;
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i] as UIElement;
// Flow passes its own constraint to children
child.Measure(constraint);
Size sz = child.DesiredSize;
if (curLineSize.Width + sz.Width > constraint.Width) //need to switch to another line
{
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
curLineSize = sz;
if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line
{
panelSize.Width = Math.Max(sz.Width, panelSize.Width);
panelSize.Height += sz.Height;
curLineSize = new Size();
}
}
else //continue to accumulate a line
{
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
}
}
// the last line size, if any need to be added
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
return panelSize;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
int firstInLine = 0;
Size curLineSize = new Size();
double accumulatedHeight = 0;
UIElementCollection children = this.Children;
for (int i = 0; i < children.Count; i++)
{
Size sz = children[i].DesiredSize;
if (curLineSize.Width + sz.Width > arrangeBounds.Width) //need to switch to another line
{
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);
accumulatedHeight += curLineSize.Height;
curLineSize = sz;
if (sz.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line
{
ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i);
accumulatedHeight += sz.Height;
curLineSize = new Size();
}
firstInLine = i;
}
else //continue to accumulate a line
{
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
}
}
if (firstInLine < children.Count)
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
return arrangeBounds;
}
private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
{
double x = 0;
if (this.HorizontalContentAlignment == HorizontalAlignment.Center)
{
x = (boundsWidth - lineSize.Width) / 2;
}
else if (this.HorizontalContentAlignment == HorizontalAlignment.Right)
{
x = (boundsWidth - lineSize.Width);
}
UIElementCollection children = Children;
for (int i = start; i < end; i++)
{
UIElement child = children[i];
var rect = new System.Windows.Rect(x, y, child.DesiredSize.Width, lineSize.Height);
child.Arrange(rect);
x += child.DesiredSize.Width;
}
}
}
Версия с VerticalContentAlignment (для вертикальных панелей):
public class AlignableWrapPanel : Panel {
public AlignableWrapPanel() {
_orientation = Orientation.Horizontal;
}
private static bool IsWidthHeightValid(object value) {
var v = (double)value;
return (double.IsNaN(v)) || (v >= 0.0d && !double.IsPositiveInfinity(v));
}
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
IsWidthHeightValid);
[TypeConverter(typeof(LengthConverter))]
public double ItemWidth {
get { return (double)GetValue(ItemWidthProperty); }
set { SetValue(ItemWidthProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
IsWidthHeightValid);
[TypeConverter(typeof(LengthConverter))]
public double ItemHeight {
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(AlignableWrapPanel),
new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure, OnOrientationChanged));
public Orientation Orientation {
get { return _orientation; }
set { SetValue(OrientationProperty, value); }
}
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var p = (AlignableWrapPanel)d;
p._orientation = (Orientation)e.NewValue;
}
private Orientation _orientation;
private struct UvSize {
internal UvSize(Orientation orientation, double width, double height) {
U = V = 0d;
_orientation = orientation;
Width = width;
Height = height;
}
internal UvSize(Orientation orientation) {
U = V = 0d;
_orientation = orientation;
}
internal double U;
internal double V;
private readonly Orientation _orientation;
internal double Width {
get { return (_orientation == Orientation.Horizontal ? U : V); }
private set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
}
internal double Height {
get { return (_orientation == Orientation.Horizontal ? V : U); }
private set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
}
}
protected override Size MeasureOverride(Size constraint) {
var curLineSize = new UvSize(Orientation);
var panelSize = new UvSize(Orientation);
var uvConstraint = new UvSize(Orientation, constraint.Width, constraint.Height);
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
var itemWidthSet = !double.IsNaN(itemWidth);
var itemHeightSet = !double.IsNaN(itemHeight);
var childConstraint = new Size(
(itemWidthSet ? itemWidth : constraint.Width),
(itemHeightSet ? itemHeight : constraint.Height));
var children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++) {
var child = children[i];
if (child == null) continue;
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
var sz = new UvSize(
Orientation,
(itemWidthSet ? itemWidth : child.DesiredSize.Width),
(itemHeightSet ? itemHeight : child.DesiredSize.Height));
if (curLineSize.U + sz.U > uvConstraint.U) {
//need to switch to another line
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (!(sz.U > uvConstraint.U)) continue;
//the element is wider then the constrint - give it a separate line
panelSize.U = Math.Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UvSize(Orientation);
} else {
//continue to accumulate a line
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
}
}
//the last line size, if any should be added
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
//go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
protected override Size ArrangeOverride(Size finalSize) {
var firstInLine = 0;
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
double accumulatedV = 0;
var itemU = (Orientation == Orientation.Horizontal ? itemWidth : itemHeight);
var curLineSize = new UvSize(Orientation);
var uvFinalSize = new UvSize(Orientation, finalSize.Width, finalSize.Height);
var itemWidthSet = !double.IsNaN(itemWidth);
var itemHeightSet = !double.IsNaN(itemHeight);
var useItemU = (Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet);
var children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++) {
var child = children[i];
if (child == null) continue;
var sz = new UvSize(
Orientation,
(itemWidthSet ? itemWidth : child.DesiredSize.Width),
(itemHeightSet ? itemHeight : child.DesiredSize.Height));
if (curLineSize.U + sz.U > uvFinalSize.U) {
//need to switch to another line
ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, i, useItemU, itemU);
accumulatedV += curLineSize.V;
curLineSize = sz;
if (sz.U > uvFinalSize.U) {
//the element is wider then the constraint - give it a separate line
//switch to next line which only contain one element
ArrangeLine(finalSize, accumulatedV, sz, i, ++i, useItemU, itemU);
accumulatedV += sz.V;
curLineSize = new UvSize(Orientation);
}
firstInLine = i;
} else {
//continue to accumulate a line
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
}
}
//arrange the last line, if any
if (firstInLine < children.Count) {
ArrangeLine(finalSize, accumulatedV, curLineSize, firstInLine, children.Count, useItemU, itemU);
}
return finalSize;
}
private void ArrangeLine(Size finalSize, double v, UvSize line, int start, int end, bool useItemU, double itemU) {
double u;
var isHorizontal = Orientation == Orientation.Horizontal;
if (_orientation == Orientation.Vertical) {
switch (VerticalContentAlignment) {
case VerticalAlignment.Center:
u = (finalSize.Height - line.U) / 2;
break;
case VerticalAlignment.Bottom:
u = finalSize.Height - line.U;
break;
default:
u = 0;
break;
}
} else {
switch (HorizontalContentAlignment) {
case HorizontalAlignment.Center:
u = (finalSize.Width - line.U) / 2;
break;
case HorizontalAlignment.Right:
u = finalSize.Width - line.U;
break;
default:
u = 0;
break;
}
}
var children = InternalChildren;
for (var i = start; i < end; i++) {
var child = children[i];
if (child == null) continue;
var childSize = new UvSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
var layoutSlotU = (useItemU ? itemU : childSize.U);
child.Arrange(new Rect(
isHorizontal ? u : v,
isHorizontal ? v : u,
isHorizontal ? layoutSlotU : line.V,
isHorizontal ? line.V : layoutSlotU));
u += layoutSlotU;
}
}
public static readonly DependencyProperty HorizontalContentAlignmentProperty = DependencyProperty.Register(nameof(HorizontalContentAlignment), typeof(HorizontalAlignment),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
public HorizontalAlignment HorizontalContentAlignment {
get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
}
public static readonly DependencyProperty VerticalContentAlignmentProperty = DependencyProperty.Register(nameof(VerticalContentAlignment), typeof(VerticalAlignment),
typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(VerticalAlignment.Top, FrameworkPropertyMetadataOptions.AffectsArrange));
public VerticalAlignment VerticalContentAlignment {
get { return (VerticalAlignment)GetValue(VerticalContentAlignmentProperty); }
set { SetValue(VerticalContentAlignmentProperty, value); }
}
}
Простой ответ заключается в том, что вы не можете выровнять по центру содержимое WrapPanel. Вы можете выровнять по центру саму панель, но последняя строка все равно останется выровненной в пределах панели.
Изменить предложения:
Использовать сетку со строками и столбцами. Если вы не добавляете элементы в коллекцию динамически, это может сработать.
Создайте свою собственную версию WrapPanel, которая работает так, как вам нужно. Этот документ MSDN описывает работу панелей и включает в себя раздел по созданию пользовательских панелей. Также имеется ссылка на образец пользовательской панели.
Мне нужен был WrapPanel, который может растягивать его содержимое, поэтому на основе https://stackoverflow.com/a/7747002/121122 и некоторой тряски я придумал это:
public class AlignableWrapPanel : Panel
{
public HorizontalAlignment HorizontalContentAlignment
{
get => (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty);
set => SetValue(HorizontalContentAlignmentProperty, value);
}
public static readonly DependencyProperty HorizontalContentAlignmentProperty = DependencyProperty.Register(
nameof(HorizontalContentAlignment),
typeof(HorizontalAlignment),
typeof(AlignableWrapPanel),
new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
{
var curLineSize = new Size();
var panelSize = new Size();
var children = InternalChildren;
for (var i = 0; i < children.Count; i++)
{
var child = children[i];
// flow passes its own constraint to children
child.Measure(constraint);
var sz = child.DesiredSize;
if (curLineSize.Width + sz.Width > constraint.Width) // need to switch to another line
{
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
curLineSize = sz;
if (sz.Width > constraint.Width) // if the element is wider then the constraint - give it a separate line
{
panelSize.Width = Math.Max(sz.Width, panelSize.Width);
panelSize.Height += sz.Height;
curLineSize = new Size();
}
}
else // continue to add to the line
{
curLineSize.Width += sz.Width;
curLineSize.Height = Math.Max(sz.Height, curLineSize.Height);
}
}
// the last line size, if any need to be added
panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width);
panelSize.Height += curLineSize.Height;
return panelSize;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
var firstInLine = 0;
var curLineSize = new Size();
var accumulatedHeight = 0D;
var children = InternalChildren;
for (var i = 0; i < children.Count; i++)
{
var desiredSize = children[i].DesiredSize;
if (curLineSize.Width + desiredSize.Width > arrangeBounds.Width) // need to switch to another line
{
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i);
accumulatedHeight += curLineSize.Height;
curLineSize = desiredSize;
if(
desiredSize.Width > arrangeBounds.Width ||
children[i] is FrameworkElement element && element.HorizontalAlignment == HorizontalAlignment.Stretch)
{
// the element is wider then the constraint or it stretches - give it a separate line
ArrangeLine(accumulatedHeight, desiredSize, arrangeBounds.Width, i, ++i);
accumulatedHeight += desiredSize.Height;
curLineSize = new Size();
}
firstInLine = i;
}
else // continue to add to the line
{
curLineSize.Width += desiredSize.Width;
curLineSize.Height = Math.Max(desiredSize.Height, curLineSize.Height);
}
}
if (firstInLine < children.Count)
{
ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count);
}
return arrangeBounds;
}
private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end)
{
var children = InternalChildren;
var x = 0D;
if (HorizontalContentAlignment == HorizontalAlignment.Center)
{
x = (boundsWidth - lineSize.Width) / 2;
}
else if (HorizontalContentAlignment == HorizontalAlignment.Right)
{
x = boundsWidth - lineSize.Width;
}
var stretchChildren = new List<UIElement>();
for (var i = start; i < end; i++)
{
var child = children[i];
if (child is FrameworkElement element && element.HorizontalAlignment == HorizontalAlignment.Stretch)
{
stretchChildren.Add(child);
}
}
var spaceAvailableForStretchChildren = boundsWidth - lineSize.Width;
var spaceAvailablePerStretchChild = 0D;
if (stretchChildren.Any())
{
x = 0; // all available space will be filled so start at 0
spaceAvailablePerStretchChild = spaceAvailableForStretchChildren / stretchChildren.Count;
spaceAvailablePerStretchChild = spaceAvailablePerStretchChild >= 0 ? spaceAvailablePerStretchChild : 0;
}
for (var i = start; i < end; i++)
{
var child = children[i];
double itemWidth;
if(stretchChildren.Contains(child))
{
itemWidth = child.DesiredSize.Width + spaceAvailablePerStretchChild;
}
else
{
itemWidth = child.DesiredSize.Width;
}
child.Arrange(new Rect(x, y, itemWidth, lineSize.Height));
x += itemWidth;
}
}
}