I'm Fairly new to WPF and MVVM in general, I'm trying to follow this tutorial to create a stacked bar chart in telerik/WPF C#:
https://docs.telerik.com/devtools/winforms/knowledge-base/chartview-summary-labels-stacked-bars
But I'm, unsure where to implement the "Custom Renderer and Labels" Code, Can't seem to have it work. Should I declare a seperate class or something? A rough step by step guide is all i need, thanks in advance
this is the example code (I'm not sure where to put it)
public class CustomCartesianRenderer : CartesianRenderer
{
public CustomCartesianRenderer(CartesianArea area)
: base(area)
{ }
protected override void InitializeSeriesLabels()
{
base.InitializeSeriesLabels();
IDictionary<object, List<double?>> summaryValues = new Dictionary<object, List<double?>>();
for (int i = 0; i < this.Area.Series.Count; i++)
{
BarSeries barSeries = this.Area.Series[i] as BarSeries;
if (barSeries == null)
{
continue;
}
for (int j = 0; j < barSeries.DataPoints.Count; j++)
{
CategoricalDataPoint dp = (CategoricalDataPoint)barSeries.DataPoints[j];
if (!summaryValues.ContainsKey(dp.Category))
{
summaryValues.Add(dp.Category, new List<double?>() { dp.Value });
}
else
{
summaryValues[dp.Category].Add(dp.Value);
}
}
}
string lastSeriesName = this.Area.Series[this.Area.Series.Count - 1].Name;
for (int i = 0; i < this.DrawParts.Count; i++)
{
BarLabelElementDrawPart labelPart = this.DrawParts[i] as BarLabelElementDrawPart;
if (labelPart != null && labelPart.Element.Name == lastSeriesName)
{
CustomBarLabelElementDrawPart customLabelPart = new CustomBarLabelElementDrawPart((BarSeries)labelPart.Element, this);
customLabelPart.SummaryValues = summaryValues;
this.DrawParts[i] = customLabelPart;
}
}
}
}
public class CustomBarLabelElementDrawPart : BarLabelElementDrawPart
{
private IDictionary<object, List<double?>> summaryValues;
public CustomBarLabelElementDrawPart(BarSeries series, IChartRenderer renderer)
: base(series, renderer)
{ }
public IDictionary<object, List<double?>> SummaryValues
{
get
{
return this.summaryValues;
}
set
{
this.summaryValues = value;
}
}
public override void Draw()
{
Graphics graphics = this.Renderer.Surface as Graphics;
RadGdiGraphics radGraphics = new RadGdiGraphics(graphics);
foreach (DataPointElement dataPointElement in this.Element.Children)
{
CategoricalDataPoint categoricalDataPoint = dataPointElement.DataPoint as CategoricalDataPoint;
if (!this.summaryValues.ContainsKey(categoricalDataPoint.Category))
{
continue;
}
double? sum = this.summaryValues[categoricalDataPoint.Category].Sum();
string summaryText = string.Format("Sum: {0}", sum);
RadRect slot = categoricalDataPoint.LayoutSlot;
RectangleF barBounds = new RectangleF((float)(this.OffsetX + slot.X), (float)(this.OffsetY + slot.Y), (float)slot.Width, (float)slot.Height);
float realHeight = barBounds.Height * dataPointElement.HeightAspectRatio;
barBounds.Y += barBounds.Height - realHeight;
barBounds.Height = realHeight;
barBounds = this.AdjustBarDataPointBounds(dataPointElement, barBounds);
barBounds.Width = Math.Max(barBounds.Width, 1f);
object state = radGraphics.SaveState();
int horizontalTranslate = (int)(barBounds.X + barBounds.Width / 2);
int verticalTranslate = (int)(barBounds.Y + barBounds.Height / 2);
float angle = (float)this.Element.LabelRotationAngle % 360f;
if (angle != 0)
{
radGraphics.TranslateTransform(horizontalTranslate, verticalTranslate);
radGraphics.RotateTransform(angle);
radGraphics.TranslateTransform(-horizontalTranslate, -verticalTranslate);
}
Size desiredSize = TextRenderer.MeasureText(summaryText, dataPointElement.Font);
FillPrimitiveImpl fill = new FillPrimitiveImpl(dataPointElement, null);
fill.PaintFill(radGraphics, 0, Size.Empty, barBounds);
BorderPrimitiveImpl border = new BorderPrimitiveImpl(dataPointElement, null);
border.PaintBorder(radGraphics, 0, Size.Empty, barBounds);
using (Brush brush = new SolidBrush(dataPointElement.ForeColor))
{
RectangleF drawRectangle = new RectangleF();
drawRectangle.X = barBounds.X + dataPointElement.Padding.Left + (barBounds.Width - desiredSize.Width) /2;
drawRectangle.Y = barBounds.Y + dataPointElement.Padding.Top - desiredSize.Height;
drawRectangle.Width = barBounds.Width - dataPointElement.Padding.Right;
drawRectangle.Height = barBounds.Height - dataPointElement.Padding.Bottom;
StringFormat format = new StringFormat();
graphics.DrawString(summaryText, dataPointElement.Font, brush, drawRectangle, format);
}
if (angle != 0)
{
radGraphics.ResetTransform();
}
radGraphics.RestoreState(state);
}
base.Draw();
}
private RectangleF AdjustBarDataPointBounds(DataPointElement point, RectangleF bounds)
{
RectangleF barBounds = bounds;
if (point.BorderBoxStyle == BorderBoxStyle.SingleBorder || point.BorderBoxStyle == BorderBoxStyle.OuterInnerBorders)
{
barBounds.X += point.BorderWidth - (int)((point.BorderWidth - 1f) / 2f);
barBounds.Width -= point.BorderWidth;
barBounds.Y += point.BorderWidth - (int)((point.BorderWidth - 1f) / 2f);
barBounds.Height -= point.BorderWidth;
}
else if (point.BorderBoxStyle == BorderBoxStyle.FourBorders)
{
barBounds.Y += 1;
barBounds.Height -= 1;
barBounds.X += 1;
barBounds.Width -= 1;
}
if (((CartesianRenderer)this.Renderer).Area.Orientation == System.Windows.Forms.Orientation.Horizontal)
{
barBounds.X--;
}
return barBounds;
}
}
I want to fit my VideoView to screen size in xamarin forms ios,
I use it but i can't get fit size
MediaPlayer = new MediaPlayer(LibVLC)
{
Media = media,
EnableHardwareDecoding = true,
AspectRatio = $"{App.ScreenWidth}:{App.ScreenHeight}"
};
so I used fullscreen property
MediaPlayer = new MediaPlayer(LibVLC)
{
Media = media,
EnableHardwareDecoding = true,
Fullscreen = true
};
It couldn't give my want result
i made custom renderer and overridden OnSizeAllocated() Method,
I guessed that changing the videoview size would change the video size as well.
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (width > height)
{
if (height * 16 / 9 > width)
{
VideoView.WidthRequest = width;
VideoView.HeightRequest = width * 9 / 16;
}
else
{
VideoView.HeightRequest = height;
VideoView.WidthRequest = height * 16 / 9;
}
}
else
{
if (width * 9 / 16 > height)
{
VideoView.HeightRequest = height;
VideoView.WidthRequest = height * 16 / 9;
}
else
{
VideoView.WidthRequest = width;
VideoView.HeightRequest = width * 9 / 16;
}
}
}
Likewise I didn't get the desired result
In addition, I tried various methods, for example, --fullscreen option, etc.
if i has some mistake, please advice for me
https://github.com/Sunday5214/vlcExample
above link is my example
after I checked answer, i tried solution
MediaPlayer = new MediaPlayer(LibVLC)
{
Media = media,
EnableHardwareDecoding = true,
Scale = 0,
AspectRatio = $"{App.ScreenHeight}:{App.ScreenWidth}"
};
still i couldn't get fullscreen,
i got to know some interest things, if i rotate my phone to landscape then rotate to portrait, i can get full screen
before rotating,
after rotating
This is how you are supposed to change aspect ratio
private void UpdateAspectRatio(AspectRatio? aspectRatio = null)
{
var mediaPlayer = MediaPlayer;
var videoView = VideoView;
if (aspectRatio == null)
{
aspectRatio = _aspectRatio ?? GetAspectRatio(mediaPlayer);
if (aspectRatio == null)
{
return;
}
}
if (videoView != null && mediaPlayer != null)
{
switch (aspectRatio)
{
case AspectRatio.Original:
mediaPlayer.AspectRatio = null;
mediaPlayer.Scale = 1;
break;
case AspectRatio.Fill:
var videoTrack = GetVideoTrack(mediaPlayer);
if (videoTrack == null)
{
break;
}
mediaPlayer.Scale = 0;
mediaPlayer.AspectRatio = IsVideoSwapped((VideoTrack)videoTrack) ? $"{videoView.Height}:{videoView.Width}" :
$"{videoView.Width}:{videoView.Height}";
break;
case AspectRatio.BestFit:
mediaPlayer.AspectRatio = null;
mediaPlayer.Scale = 0;
break;
case AspectRatio.FitScreen:
videoTrack = GetVideoTrack(mediaPlayer);
if (videoTrack == null)
{
break;
}
var track = (VideoTrack)videoTrack;
var videoSwapped = IsVideoSwapped(track);
var videoWidth = videoSwapped ? track.Height : track.Width;
var videoHeigth = videoSwapped ? track.Width : track.Height;
if (videoWidth == 0 || videoHeigth == 0)
{
mediaPlayer.Scale = 0;
}
else
{
if (track.SarNum != track.SarDen)
{
videoWidth = videoWidth * track.SarNum / track.SarDen;
}
var ar = videoWidth / (double)videoHeigth;
var videoViewWidth = videoView.Width;
var videoViewHeight = videoView.Height;
var dar = videoViewWidth / videoViewHeight;
var rawPixelsPerViewPixel = DisplayInformation.ScalingFactor;
var displayWidth = videoViewWidth * rawPixelsPerViewPixel;
var displayHeight = videoViewHeight * rawPixelsPerViewPixel;
mediaPlayer.Scale = (float)(dar >= ar ? (displayWidth / videoWidth) : (displayHeight / videoHeigth));
}
mediaPlayer.AspectRatio = null;
break;
case AspectRatio._16_9:
mediaPlayer.AspectRatio = "16:9";
mediaPlayer.Scale = 0;
break;
case AspectRatio._4_3:
mediaPlayer.AspectRatio = "4:3";
mediaPlayer.Scale = 0;
break;
}
}
if (_aspectRatio != aspectRatio)
{
_aspectRatio = aspectRatio;
AspectRatioChanged?.Invoke(this, EventArgs.Empty);
}
}
You need to set both the Scale and AspectRatio properties. This is a helper only available from the MediaPlayerElement when using Xamarin.Forms, but you can copy that behavior in your app.
I had Gantt chart like this. I'd like to show AxisX2 with value is percentage that I prepared formula for it. I had challenges to show AxisX2 and set series for it.
Here is the Gantt chart I captured click here.
I expect to one more axis like this.
Please help, Thank you .
Here are some basic function to render that chart
public void setUpGantt(Chart chart)
{
chart.Series.Clear();
Series s = chart.Series.Add("sszs");
s.ChartType = SeriesChartType.RangeBar;
s.YValueType = ChartValueType.DateTime;
s.ResetIsVisibleInLegend () ;
s.IsVisibleInLegend = true;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
ax.MajorGrid.Enabled = false;
ay.IntervalType = DateTimeIntervalType.Minutes;
ay.Interval = 60;
ay.LabelStyle.Format = "HH:mm";
ay.Minimum = 0;
ay.Maximum = 0.2;
limitGantt(chart, "0:00", "24:00");
s.ToolTip = "#VALY1{HH:mm}~#VALY2{HH:mm}";
}
public void limitGantt(Chart chart, string start, string end)
{
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
ay.Minimum = fromTimeString(start).ToOADate();
ay.Maximum = fromTimeString(end).ToOADate();
}
DateTime fromTimeString(string time)
{
var p = time.Split(':');
int sec = p.Length == 3 ? Convert.ToInt16(p[2]) : 0;
TimeSpan t = new TimeSpan(Convert.ToInt16(p[0]), Convert.ToInt16(p[1]), sec);
return DateTime.Today.Add(t);
}
public void addGanttTask(Series s, string start, string end, Color c, int slot, string [] array)
{
DateTime start_ = fromTimeString(start);
DateTime end_ = fromTimeString(end);
int pt = s.Points.AddXY(slot, start_, end_);
s.Points[pt].Color = c;
s.IsVisibleInLegend = true;
if (array != null)
{
for (int i = 0; i < array.Length; i++)
{
if (slot == i + 1)
{ s.Points[pt].AxisLabel = array[i];
}
}
}
}
Taw's comment
public void setUpGantt(Chart chart)
{
chart.Series.Clear();
Series s = chart.Series.Add("sszs");
s.ChartType = SeriesChartType.RangeBar;
s.YValueType = ChartValueType.DateTime;
s.ResetIsVisibleInLegend () ;
s.IsVisibleInLegend = true;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
ax.MajorGrid.Enabled = false;
Axis ax2 = chart.ChartAreas[0].AxisX2;
ax2.Enabled = AxisEnabled.True;
ax2.Maximum = 100;
ax2.MajorGrid.Enabled = false;
ay.IntervalType = DateTimeIntervalType.Minutes;
ay.Interval = 60;
ay.LabelStyle.Format = "HH:mm";
ay.Minimum = 0;
ay.Maximum = 0.2;
limitGantt(chart, "0:00", "24:00");
s.ToolTip = "#VALY1{HH:mm}~#VALY2{HH:mm}";
}
I'd like to have percentage = Green Time/Total time (from 00:
00 to Current time) example
Thank you for TaW's suggestion. It worked for me.
I post here to share who want to know
public void setUpGantt(Chart chart)
{
chart.Series.Clear();
Series s = chart.Series.Add("sszs");
s.ChartType = SeriesChartType.RangeBar;
s.YValueType = ChartValueType.DateTime;
s.ResetIsVisibleInLegend () ;
s.IsVisibleInLegend = true;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
ax.MajorGrid.Enabled = false;
Axis ax2 = chart.ChartAreas[0].AxisX2;
ax2.Enabled = AxisEnabled.True;
ax2.MajorGrid.Enabled = true;
ax2.CustomLabels.Clear();// clear previous value when switch another data
for (int i = 0; i < 12; i++)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = i+0.5;
cl.ToPosition = i+1.5;
cl.Text = i+" %"; // example value to show on CustomLabel
ax2.CustomLabels.Add(cl);
}
ay.IntervalType = DateTimeIntervalType.Minutes;
ay.Interval = 60;
ay.LabelStyle.Format = "HH:mm";
ay.Minimum = 0;
ay.Maximum = 0.2;
limitGantt(chart, "0:00", "24:00");
s.ToolTip = "#VALY1{HH:mm}~#VALY2{HH:mm}";
}
The main script to show one more Axis with CustomLabel
Axis ax2 = chart.ChartAreas[0].AxisX2;
ax2.Enabled = AxisEnabled.True;
ax2.MajorGrid.Enabled = true;
ax2.CustomLabels.Clear();// clear previous value when switch another data
for (int i = 0; i < 12; i++)
{
CustomLabel cl = new CustomLabel();
cl.FromPosition = i+0.5;
cl.ToPosition = i+1.5;
cl.Text = i+" %"; // example value to show on CustomLabel
ax2.CustomLabels.Add(cl);
}
The Children of WrapPanel are populated sequentially like attached screenshot.
Therefore, according to the length of each child, the Panel makes long blank space.
How can I utilize the blank space with re-arrangng the children ?
It seems only few people use WrapPanel so far and no sufficient examples.
Is there some automatic way for this ?
Or do I only need to make own algorithm ?
The WrapPanel plays a very important role to display things but has limited space to display.
Thank you !
Here is a WrapPanel which can optionally rearrange elements using FFDH algorithm, as well as optionally stretch them to remove blank areas (example).
public class StretchyWrapPanel : Panel {
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(nameof(ItemWidth), typeof(double),
typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => {
((StretchyWrapPanel)o)._itemWidth = (double)e.NewValue;
}));
private double _itemWidth = double.NaN;
[TypeConverter(typeof(LengthConverter))]
public double ItemWidth {
get => _itemWidth;
set => SetValue(ItemWidthProperty, value);
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(nameof(ItemHeight), typeof(double),
typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => {
((StretchyWrapPanel)o)._itemHeight = (double)e.NewValue;
}));
private double _itemHeight = double.NaN;
[TypeConverter(typeof(LengthConverter))]
public double ItemHeight {
get => _itemHeight;
set => SetValue(ItemHeightProperty, value);
}
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(Orientation),
typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsArrange, (o, e) => {
((StretchyWrapPanel)o)._orientation = (Orientation)e.NewValue;
}));
private Orientation _orientation = Orientation.Horizontal;
public Orientation Orientation {
get => _orientation;
set => SetValue(OrientationProperty, value);
}
public static readonly DependencyProperty StretchToFillProperty = DependencyProperty.Register(nameof(StretchToFill), typeof(bool),
typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange, (o, e) => {
((StretchyWrapPanel)o)._stretchToFill = (bool)e.NewValue;
}));
private bool _stretchToFill = true;
public bool StretchToFill {
get => _stretchToFill;
set => SetValue(StretchToFillProperty, value);
}
public static readonly DependencyProperty StretchProportionallyProperty = DependencyProperty.Register(nameof(StretchProportionally), typeof(bool),
typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange, (o, e) => {
((StretchyWrapPanel)o)._stretchProportionally = (bool)e.NewValue;
}));
private bool _stretchProportionally = true;
public bool StretchProportionally {
get => _stretchProportionally;
set => SetValue(StretchProportionallyProperty, value);
}
public static readonly DependencyProperty RearrangeForBestFitProperty = DependencyProperty.Register(nameof(RearrangeForBestFit), typeof(bool),
typeof(StretchyWrapPanel), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure, (o, e) => {
((StretchyWrapPanel)o)._rearrangeForBestFit = (bool)e.NewValue;
}));
private bool _rearrangeForBestFit;
public bool RearrangeForBestFit {
get => _rearrangeForBestFit;
set => SetValue(RearrangeForBestFitProperty, value);
}
private struct UVSize {
internal UVSize(Orientation orientation, Size size) {
U = V = 0d;
_isHorizontal = orientation == Orientation.Horizontal;
Width = size.Width;
Height = size.Height;
}
internal UVSize(Orientation orientation, double width, double height) {
U = V = 0d;
_isHorizontal = orientation == Orientation.Horizontal;
Width = width;
Height = height;
}
internal UVSize(Orientation orientation) {
U = V = 0d;
_isHorizontal = orientation == Orientation.Horizontal;
}
internal double U;
internal double V;
private bool _isHorizontal;
internal double Width {
get => _isHorizontal ? U : V;
set {
if (_isHorizontal) {
U = value;
} else {
V = value;
}
}
}
internal double Height {
get => _isHorizontal ? V : U;
set {
if (_isHorizontal) {
V = value;
} else {
U = value;
}
}
}
}
protected override Size MeasureOverride(Size constraint) {
return RearrangeForBestFit ? MeasureBestFit(constraint) : MeasureKeepInOrder(constraint);
}
private Size MeasureKeepInOrder(Size constraint) {
var orientation = Orientation;
var uLimit = new UVSize(orientation, constraint.Width, constraint.Height).U;
var curLineSize = new UVSize(orientation);
var panelSize = new UVSize(orientation);
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 > uLimit) {
// Need to switch to another line
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (sz.U > uLimit) {
// 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);
}
private Size MeasureBestFit(Size constraint) {
var orientation = Orientation;
var uLimit = new UVSize(orientation, constraint.Width, constraint.Height).U;
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;
// First-Fit Decreasing Height (FFDH) algorithm
var lines = new List<UVSize>();
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 childSize = new UVSize(orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
for (var j = 0; j < lines.Count; j++) {
var line = lines[j];
if (line.U + childSize.U <= uLimit) {
lines[j] = new UVSize(orientation) { U = childSize.U, V = Math.Max(childSize.V, line.V) };
goto Next;
}
}
lines.Add(childSize);
Next:
{ }
}
var panelSize = new UVSize(orientation);
for (var i = 0; i < lines.Count; i++) {
var line = lines[i];
panelSize.U = Math.Max(line.U, panelSize.U);
panelSize.V += line.V;
}
// Go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
protected override Size ArrangeOverride(Size finalSize) {
return RearrangeForBestFit ? ArrangeBestFit(finalSize) : ArrangeKeepInOrder(finalSize);
}
private static UVSize GetChildSize(Orientation orientation, UIElement child, UVSize fixedChildSize) {
var childSize = new UVSize(orientation, child.DesiredSize);
if (!double.IsNaN(fixedChildSize.U)) childSize.U = fixedChildSize.U;
if (!double.IsNaN(fixedChildSize.V)) childSize.V = fixedChildSize.V;
return childSize;
}
private Size ArrangeKeepInOrder(Size finalSize) {
var orientation = Orientation;
var fixedChildSize = new UVSize(orientation, ItemWidth, ItemHeight);
var children = InternalChildren;
var firstInLine = 0;
var uLimit = new UVSize(orientation, finalSize).U;
var currentLineSize = new UVSize(orientation);
var accumulatedV = 0d;
for (int i = 0, count = children.Count; i < count; i++) {
var child = children[i];
if (child == null) continue;
var childSize = GetChildSize(orientation, child, fixedChildSize);
if (currentLineSize.U + childSize.U > uLimit) {
// Need to switch to another line
if (!double.IsNaN(fixedChildSize.U)) {
ArrangeLineFixedSize(orientation, children, accumulatedV, currentLineSize.V, firstInLine, i, fixedChildSize.U);
} else if (!StretchToFill) {
ArrangeLineDefault(orientation, children, accumulatedV, currentLineSize.V, firstInLine, i);
} else {
ArrangeLineStretch(orientation, children, accumulatedV, currentLineSize.V, firstInLine, i, uLimit, StretchProportionally);
}
accumulatedV += currentLineSize.V;
currentLineSize = childSize;
firstInLine = i;
} else {
// Continue to accumulate a line
currentLineSize.U += childSize.U;
currentLineSize.V = Math.Max(childSize.V, currentLineSize.V);
}
}
// Arrange the last line, if any
if (!double.IsNaN(fixedChildSize.U)) {
ArrangeLineFixedSize(orientation, children, accumulatedV, currentLineSize.V, firstInLine, children.Count, fixedChildSize.U);
} else if (!StretchToFill) {
ArrangeLineDefault(orientation, children, accumulatedV, currentLineSize.V, firstInLine, children.Count);
} else {
ArrangeLineStretch(orientation, children, accumulatedV, currentLineSize.V, firstInLine, children.Count, uLimit, StretchProportionally);
}
return finalSize;
}
private static void ArrangeLineDefault(Orientation orientation, UIElementCollection children, double v, double lineV, int start, int end) {
var position = new UVSize(orientation){ U = 0d, V = v };
for (var i = start; i < end; i++) {
var child = children[i];
if (child != null) {
var childSize = new UVSize(orientation, child.DesiredSize) { V = lineV };
child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
position.U += childSize.U;
}
}
}
private static void ArrangeLineStretch(Orientation orientation, UIElementCollection children, double v, double lineV, int start, int end,
double limitU, bool stretchProportionally) {
var totalU = 0d;
for (var i = start; i < end; i++) {
totalU += new UVSize(orientation, children[i].DesiredSize).U;
}
var position = new UVSize(orientation) { U = 0d, V = v };
var uExtra = stretchProportionally ? limitU / totalU : (limitU - totalU) / (end - start);
for (var i = start; i < end; i++) {
var child = children[i];
if (child != null) {
var childSize = new UVSize(orientation, child.DesiredSize) { V = lineV };
childSize.U = stretchProportionally ? childSize.U * uExtra : Math.Max(childSize.U + uExtra, 1d);
child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
position.U += childSize.U;
}
}
}
private static void ArrangeLineFixedSize(Orientation orientation, UIElementCollection children, double v, double lineV, int start, int end, double itemU) {
var position = new UVSize(orientation) { U = 0d, V = v };
var childSize = new UVSize(orientation){ U = itemU, V = lineV };
for (var i = start; i < end; i++) {
var child = children[i];
if (child != null) {
child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
position.U += childSize.U;
}
}
}
private class ArrangeBestFitLine {
public UVSize Size;
public readonly List<int> ItemIndices = new List<int>();
public void ArrangeDefault(Orientation orientation, UIElementCollection children, double v) {
var position = new UVSize(orientation){ U = 0d, V = v };
for (var i = 0; i < ItemIndices.Count; i++) {
var child = children[ItemIndices[i]];
if (child != null) {
var childSize = new UVSize(orientation, child.DesiredSize) { V = Size.V };
child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
position.U += childSize.U;
}
}
}
public void ArrangeStretch(Orientation orientation, UIElementCollection children, double v, double limitU, bool stretchProportionally) {
var totalU = 0d;
for (var i = 0; i < ItemIndices.Count; i++) {
totalU += new UVSize(orientation, children[ItemIndices[i]].DesiredSize).U;
}
var position = new UVSize(orientation) { U = 0d, V = v };
var uExtra = stretchProportionally ? limitU / totalU : (limitU - totalU) / ItemIndices.Count;
for (var i = 0; i < ItemIndices.Count; i++) {
var child = children[ItemIndices[i]];
if (child != null) {
var childSize = new UVSize(orientation, child.DesiredSize) { V = Size.V };
childSize.U = stretchProportionally ? childSize.U * uExtra : Math.Max(childSize.U + uExtra, 1d);
child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
position.U += childSize.U;
}
}
}
public void ArrangeFixedSize(Orientation orientation, UIElementCollection children, double v, double itemU) {
var position = new UVSize(orientation) { U = 0d, V = v };
var childSize = new UVSize(orientation){ U = itemU, V = Size.V };
for (var i = 0; i < ItemIndices.Count; i++) {
var child = children[ItemIndices[i]];
if (child != null) {
child.Arrange(new Rect(position.Width, position.Height, childSize.Width, childSize.Height));
position.U += childSize.U;
}
}
}
}
private Size ArrangeBestFit(Size finalSize) {
var orientation = Orientation;
var fixedChildSize = new UVSize(orientation, ItemWidth, ItemHeight);
var uLimit = new UVSize(orientation, finalSize).U;
// First-Fit Decreasing Height (FFDH) algorithm
var lines = new List<ArrangeBestFitLine>();
var children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++) {
var child = children[i];
if (child == null) continue;
var childSize = GetChildSize(orientation, child, fixedChildSize);
for (var j = 0; j < lines.Count; j++) {
var line = lines[j];
if (line.Size.U + childSize.U <= uLimit) {
line.Size.U += childSize.U;
line.Size.V = Math.Max(childSize.V, line.Size.V);
line.ItemIndices.Add(i);
goto Next;
}
}
lines.Add(new ArrangeBestFitLine {
Size = childSize,
ItemIndices = { i }
});
Next:
{ }
}
var accumulatedV = 0d;
for (var i = 0; i < lines.Count; i++) {
var line = lines[i];
if (!double.IsNaN(fixedChildSize.U)) {
line.ArrangeFixedSize(orientation, children, accumulatedV, fixedChildSize.U);
} else if (!StretchToFill) {
line.ArrangeDefault(orientation, children, accumulatedV);
} else {
line.ArrangeStretch(orientation, children, accumulatedV, uLimit, StretchProportionally);
}
accumulatedV += line.Size.V;
}
return finalSize;
}
}
Are all your elements in the WrapPanel of the same height? In either case, why not start with a TreeMap control like https://marketplace.visualstudio.com/items?itemName=DevExpress.WPFTreeMapControl and customize it?
EDIT 1 - Based on the comment
It shouldn't be too difficult to implement - I am pretty sure the minimal functionality that you need can be pulled off by inheriting from a panel and hooking the rearrange code in its MeasureOverride and ArrangeOverride methods.
Take a look here too (implements a similar control), could be a good start if you choose to do so: https://codeblitz.wordpress.com/2009/03/20/wpf-auto-arrange-animated-panel/
This simple Panel should do the job:
public class MySpecialWrapPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
}
return new Size();
}
protected override Size ArrangeOverride(Size finalSize)
{
var x = 0d;
var y = 0d;
var height = 0d;
var children = InternalChildren.Cast<UIElement>().ToList();
while (children.Count > 0)
{
var child = children.First();
if (x > 0d && x + child.DesiredSize.Width > finalSize.Width)
{
// try to find child that fits
var fit = children.FirstOrDefault(
c => x + c.DesiredSize.Width <= finalSize.Width);
child = fit ?? child;
if (x + child.DesiredSize.Width > finalSize.Width)
{
x = 0d;
y = height;
}
}
children.Remove(child);
child.Arrange(
new Rect(x, y, child.DesiredSize.Width, child.DesiredSize.Height));
x += child.DesiredSize.Width;
height = Math.Max(height, y + child.DesiredSize.Height);
}
return finalSize;
}
}
You need a custom algorithm for this. I believe the StretchyWrapPanel mentioned here is a good start. After measuring the child items, you can then sort them using custom logic (e.g. bin packing).