1

I've a listview that is dynamically populated by code. In some case I need to put a note that has different height respect other components. My goal is to have Note and listview items wrap all around, like in Word with image and Wrap Text set to "Square"

Layout

I tried to use UniformGrid and WrapPanel. I tried to understand how write my own WrapPanel, but I don't find a way to solve it. I think that I can have an external component for Note and leave listview items that are under Note like "Hidden"

<ListView x:Name="PluginListView"
          HorizontalAlignment="Left"
          VerticalAlignment="Bottom"
          SelectionMode="Single"
          VerticalContentAlignment="Top"
          BorderThickness="0"
          Margin="0,10,0,0">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <!--<UniformGrid Columns="4" HorizontalAlignment="Stretch" Rows="10"/>-->
            <WrapPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ListView>

ADDED

I created a custom UniformGrid, I added a property NoteRowToSkip that rapresent the rows to skip in the last column on the left. I override ArrangeOverride and added the the skip behavior that I want. Debugging seems that values are correct, but has the same behavior of UniformGrid.

    public class UniformNoteWrapGrid : Panel
    {
        private int m_Rows;
        private int m_Columns;

        public static readonly DependencyProperty NoteRowToSkipProperty = DependencyProperty.Register(nameof(NoteRowToSkip), typeof(int), typeof(UniformGrid), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure), ValidateNoteRowToSkip);
        private static bool ValidateNoteRowToSkip(object o)
        {
            return (int)o >= 0;
        }
        public int Columns
        {
            get => (int)GetValue(ColumnsProperty);
            set => SetValue(ColumnsProperty, value);
        }

        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register(
                "Columns",
                typeof(int),
                typeof(UniformGrid),
                new FrameworkPropertyMetadata(
                    0,
                    FrameworkPropertyMetadataOptions.AffectsMeasure),
                ValidateColumns);

        private static bool ValidateColumns(object o)
        {
            return (int)o >= 0;
        }

        public int Rows
        {
            get => (int)GetValue(RowsProperty);
            set => SetValue(RowsProperty, value);
        }

        public static readonly DependencyProperty RowsProperty =
            DependencyProperty.Register("Rows", typeof(int),
                typeof(UniformGrid),
                new FrameworkPropertyMetadata(
                    0,
                    FrameworkPropertyMetadataOptions.AffectsMeasure),
                ValidateRows);

        private static bool ValidateRows(object o)
        {
            return (int)o >= 0;
        }

        public int NoteRowToSkip
        {
            get => (int)GetValue(NoteRowToSkipProperty);
            set => SetValue(NoteRowToSkipProperty, value);
        }

        protected override Size MeasureOverride(Size constraint)
        {
            UpdateComputedValues();

            var childConstraint = new Size(constraint.Width / m_Columns, constraint.Height / m_Rows);
            double maxChildDesiredWidth = 0.0;
            double maxChildDesiredHeight = 0.0;

            for (int i = 0, count = InternalChildren.Count; i < count; ++i)
            {
                var child = InternalChildren[i];

                child.Measure(childConstraint);
                var childDesiredSize = child.DesiredSize;

                if (maxChildDesiredWidth < childDesiredSize.Width)
                    maxChildDesiredWidth = childDesiredSize.Width;

                if (maxChildDesiredHeight < childDesiredSize.Height)
                    maxChildDesiredHeight = childDesiredSize.Height;
            }

            return new Size(maxChildDesiredWidth * m_Columns, maxChildDesiredHeight * m_Rows);
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            var childBounds = new Rect(0, 0, arrangeSize.Width / m_Columns, arrangeSize.Height / m_Rows);
            double xStep = childBounds.Width;
            double xBound = arrangeSize.Width - 1.0;
            var row = 1;
            var column = 1;

            foreach (UIElement child in InternalChildren)
            {
                child.Arrange(childBounds);

                if (child.Visibility == Visibility.Collapsed)
                    continue;

                childBounds.X += xStep;
                column++;
                var testXBound = xBound;
                if (IsCellForNote(row, column))
                    testXBound -= childBounds.Height;

                if (!(childBounds.X >= testXBound))
                    continue;

                childBounds.Y += childBounds.Height;
                childBounds.X = 0;
                row++;
                column = 1;
            }

            return arrangeSize;
        }

        private bool IsCellForNote(int row, int column)
        {
            if (row > NoteRowToSkip)
                return true;

            return column != Columns;
        }

        private void UpdateComputedValues()
        {
            m_Columns = Columns;
            m_Rows = Rows;

            //if (FirstColumn >= m_Columns)
            //  FirstColumn = 0;

            if (m_Rows != 0 && m_Columns != 0)
                return;

            int nonCollapsedCount = 0;

            for (int i = 0, count = InternalChildren.Count; i < count; ++i)
            {
                var child = InternalChildren[i];
                if (child.Visibility != Visibility.Collapsed)
                    nonCollapsedCount++;
            }

            if (nonCollapsedCount == 0)
                nonCollapsedCount = 1;

            if (m_Rows == 0)
            {
                if (m_Columns > 0)
                    m_Rows = (nonCollapsedCount + (m_Columns - 1)) / m_Columns;
                else
                {
                    m_Rows = (int)Math.Sqrt(nonCollapsedCount);
                    if ((m_Rows * m_Rows) < nonCollapsedCount)
                        m_Rows++;

                    m_Columns = m_Rows;
                }
            }
            else if (m_Columns == 0)
                m_Columns = (nonCollapsedCount + (m_Rows - 1)) / m_Rows;
        }
    }

Any idea in what's not working in my code?

SOLVED

Was a problem with if conditions. The working version is below.

    public class UniformNoteWrapGrid : Panel
    {
        private int m_Rows;
        private int m_Columns;

        public static readonly DependencyProperty NoteRowToSkipProperty = DependencyProperty.Register(nameof(NoteRowToSkip), typeof(int), typeof(UniformGrid), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure), ValidateNoteRowToSkip);
        private static bool ValidateNoteRowToSkip(object o)
        {
            return (int)o >= 0;
        }

        public int Columns
        {
            get => (int)GetValue(ColumnsProperty);
            set => SetValue(ColumnsProperty, value);
        }

        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register(
                "Columns",
                typeof(int),
                typeof(UniformGrid),
                new FrameworkPropertyMetadata(
                    0,
                    FrameworkPropertyMetadataOptions.AffectsMeasure),
                ValidateColumns);

        private static bool ValidateColumns(object o)
        {
            return (int)o >= 0;
        }

        public int Rows
        {
            get => (int)GetValue(RowsProperty);
            set => SetValue(RowsProperty, value);
        }

        public static readonly DependencyProperty RowsProperty =
            DependencyProperty.Register("Rows", typeof(int),
                typeof(UniformGrid),
                new FrameworkPropertyMetadata(
                    0,
                    FrameworkPropertyMetadataOptions.AffectsMeasure),
                ValidateRows);

        private static bool ValidateRows(object o)
        {
            return (int)o >= 0;
        }

        public int NoteRowToSkip
        {
            get => (int)GetValue(NoteRowToSkipProperty);
            set => SetValue(NoteRowToSkipProperty, value);
        }

        protected override Size MeasureOverride(Size constraint)
        {
            UpdateComputedValues();

            var childConstraint = new Size(constraint.Width / m_Columns, constraint.Height / m_Rows);
            double maxChildDesiredWidth = 0.0;
            double maxChildDesiredHeight = 0.0;

            for (int i = 0, count = InternalChildren.Count; i < count; ++i)
            {
                var child = InternalChildren[i];

                child.Measure(childConstraint);
                var childDesiredSize = child.DesiredSize;

                if (maxChildDesiredWidth < childDesiredSize.Width)
                    maxChildDesiredWidth = childDesiredSize.Width;

                if (maxChildDesiredHeight < childDesiredSize.Height)
                    maxChildDesiredHeight = childDesiredSize.Height;
            }

            return new Size(maxChildDesiredWidth * m_Columns, maxChildDesiredHeight * m_Rows);
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            var childBounds = new Rect(0, 0, arrangeSize.Width / m_Columns, arrangeSize.Height / m_Rows);
            double xStep = childBounds.Width;
            double xBound = arrangeSize.Width - 1.0;
            var row = 1;
            var column = 1;

            foreach (UIElement child in InternalChildren)
            {
                child.Arrange(childBounds);

                if (child.Visibility == Visibility.Collapsed)
                    continue;

                childBounds.X += xStep;
                column++;
                var testXBound = xBound;
                if (IsCellForNote(row, column))
                    testXBound -= xStep;

                if (!(childBounds.X >= testXBound))
                    continue;

                childBounds.Y += childBounds.Height;
                childBounds.X = 0;
                row++;
                column = 1;
            }

            return arrangeSize;
        }

        private bool IsCellForNote(int row, int column)
        {
            if (row > NoteRowToSkip)
                return false;

            return column == Columns;
        }

        private void UpdateComputedValues()
        {
            m_Columns = Columns;
            m_Rows = Rows;

            //if (FirstColumn >= m_Columns)
            //  FirstColumn = 0;

            if (m_Rows != 0 && m_Columns != 0)
                return;

            int nonCollapsedCount = 0;

            for (int i = 0, count = InternalChildren.Count; i < count; ++i)
            {
                var child = InternalChildren[i];
                if (child.Visibility != Visibility.Collapsed)
                    nonCollapsedCount++;
            }

            if (nonCollapsedCount == 0)
                nonCollapsedCount = 1;

            if (m_Rows == 0)
            {
                if (m_Columns > 0)
                    m_Rows = (nonCollapsedCount + (m_Columns - 1)) / m_Columns;
                else
                {
                    m_Rows = (int)Math.Sqrt(nonCollapsedCount);
                    if ((m_Rows * m_Rows) < nonCollapsedCount)
                        m_Rows++;

                    m_Columns = m_Rows;
                }
            }
            else if (m_Columns == 0)
                m_Columns = (nonCollapsedCount + (m_Rows - 1)) / m_Rows;
        }
    }
  • As a note, you don't need a ListView. The simpler base class ListBox will also do. Besides that, to achieve such a layout you will need to write a custom Panel that implements it. Not a trivial task. This might get you started: https://stackoverflow.com/a/15876049/1136211 – Clemens Sep 25 '19 at 11:07
  • _dynamically populated by code_ is a bad sign, is `ItemsSource="{Binding}"` so hard? – XAMlMAX Sep 25 '19 at 14:09
  • Yes is ItemsSource="{Binding}", sorry for the misunderstanding. I removed from code when I made some tests – Alan Serpini Sep 25 '19 at 14:29

0 Answers0