16

I have a ListBox (WPF) that contains CheckBoxes. I'm using is in a configuration screen. Schematic example below:

alt text

Now I want to add a "Test 5" CheckBox. I have limited vertical space, so I want it to appear in the horizontal direction, as shown below:

alt text

Can the ListBox layout be modified so the CheckBoxes will be arranged like this?

6
  • Maybe a Listbox can do that, but why not a WrapPanel? Do you really need a SelectedItem? Commented Jan 18, 2011 at 8:56
  • You can use a grid as ItemsPanel for the list box: scottlogic.co.uk/blog/colin/2010/11/… Commented Jan 18, 2011 at 9:00
  • @Henk, setting a WrapPanel as ListBox's ItemsPanel will do. Commented Jan 18, 2011 at 9:12
  • @decyc, I know but I still question the need/desire for a LB in the first place. Commented Jan 18, 2011 at 9:32
  • @Henk: Yes, I agree with that. But, I still feel the need to use ItemsControl for data-binding purposes instead of using a panel directly. Commented Jan 18, 2011 at 11:03

6 Answers 6

18
<ListBox Name="CategoryListBox"
         ScrollViewer.HorizontalScrollBarVisibility="Disabled"
         ItemsSource="{Binding Path=RefValues,
                UpdateSourceTrigger=PropertyChanged}"
                SelectionMode="Multiple">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate >
            <StackPanel Orientation="Horizontal"
                        MinWidth="150" MaxWidth="150"
                        Margin="0,5, 0, 5" >
                <CheckBox
                    Name="checkedListBoxItem"
                    IsChecked="{Binding
                            RelativeSource={RelativeSource FindAncestor,
                            AncestorType={x:Type ListBoxItem} },
                            Path=IsSelected, Mode=TwoWay}" />
                <ContentPresenter
                    Content="{Binding
                            RelativeSource={RelativeSource TemplatedParent},
                            Path=Content}"
                    Margin="5,0, 0, 0" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

or as simple as this:

<Grid>
    <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel IsItemsHost="True" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBoxItem>listbox item 1</ListBoxItem>
        <ListBoxItem>listbox item 2</ListBoxItem>
        <ListBoxItem>listbox item 3</ListBoxItem>
        <ListBoxItem>listbox item 4</ListBoxItem>
        <ListBoxItem>listbox item 5</ListBoxItem>
    </ListBox>
</Grid>
Sign up to request clarification or add additional context in comments.

Comments

17

I encountered a similar problem and eibhrum's answer gave me some idea. I used the following code and I think this also what you need. I used UniformGrid instead of WrapPanel.

<ListBox HorizontalAlignment="Stretch" 
      ItemsSource="{Binding Timers}" 
      >
   <ListBox.ItemContainerStyle>
      <Style TargetType="ListBoxItem">
         <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
      </Style>
   </ListBox.ItemContainerStyle>

      <ListBox.ItemsPanel>
         <ItemsPanelTemplate>
            <!-- UNIFORM GRID HERE -->
            <UniformGrid Columns="3" IsItemsHost="True" 
               HorizontalAlignment="Stretch"/>
         </ItemsPanelTemplate>
      </ListBox.ItemsPanel>

      <ListBox.ItemTemplate>
         <DataTemplate>
            <Border>
               <StackPanel Orientation="Vertical" >
                  <TextBlock Text="{Binding Label}" TextWrapping="Wrap"/>
                  <Separator Margin="5,0,10,0"/>
               </StackPanel>
            </Border>
         </DataTemplate>
      </ListBox.ItemTemplate>

   </ListBox>

2 Comments

This works better than the solution that uses a WrapPanel, cause it is possible to restrict the horizontal extend (via the Columns property).
@Andrzej Gis, a fantastic solution !
7

I know this is an older post, but I stumbled across a fairly simple way to do this while I was trying to solve the same problem here: http://social.technet.microsoft.com/wiki/contents/articles/19395.multiple-columns-in-wpf-listbox.aspx

Just add your binding data source (or add items as needed).

<ListBox Name="myLB" ScrollViewer.HorizontalScrollBarVisiblity="Disabled">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Columns="2" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

1 Comment

I like that you included a link, but I'm trying to understand how your answer is different than the one left by @gisek. If there is a significant difference (I acknowledge that I may just be missing it), you may want to point out what that is.
0

ListBox with multi column and ListBoxItem Orientation is Vertical. ListBox has fix Height and Auto Width. When add ListBoxItem, ListBox will Auto increase Width.

<ListBox Height="81" VerticalAlignment="Top" HorizontalAlignment="Left" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Vertical"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBoxItem Content="1"/>
    <ListBoxItem Content="2"/>
    <ListBoxItem Content="3"/>
    <ListBoxItem Content="4"/>
    <ListBoxItem Content="5"/>
    <ListBoxItem Content="6"/>
    <ListBoxItem Content="7"/>
    <ListBoxItem Content="8"/>
    <ListBoxItem Content="9"/>
    <ListBoxItem Content="10"/>
</ListBox>

ListBox with multi column and ListBoxItem Orientation is Horizontal. ListBox has fix Width and Auto Height. When add ListBoxItem, ListBox will Auto increase Height.

<ListBox Width="81" VerticalAlignment="Top" HorizontalAlignment="Left" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBoxItem Content="1"/>
    <ListBoxItem Content="2"/>
    <ListBoxItem Content="3"/>
    <ListBoxItem Content="4"/>
    <ListBoxItem Content="5"/>
    <ListBoxItem Content="6"/>
    <ListBoxItem Content="7"/>
    <ListBoxItem Content="8"/>
    <ListBoxItem Content="9"/>
    <ListBoxItem Content="10"/>
</ListBox>

Comments

0

If you need to flow rows between multiple regions (multiple windows, in my case), you can use a custom panel implementation.

Example usage:

<Grid>
    <Grid.Resources>
        <ItemsPanelTemplate x:Key="ItemsPanelTemplate">
            <local:SharedLayoutStackPanel IsItemsHost="True"/>
        </ItemsPanelTemplate>

        <local:SharedLayoutCoordinator x:Key="slc" ItemsSource="{Binding Path=MyItems}" />
    </Grid.Resources>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Border Grid.Column="0" Margin="10,10,5,10" BorderBrush="Black" BorderThickness="1">
        <ListBox 
            local:SharedLayoutCoordinator.Region="{Binding Source={StaticResource slc}, Path=[0]}" 
            ItemsPanel="{StaticResource ResourceKey=ItemsPanelTemplate}" />
    </Border>
    <Border Grid.Column="1" Margin="5,10" BorderBrush="Black" BorderThickness="1">
        <ListBox 
            local:SharedLayoutCoordinator.Region="{Binding Source={StaticResource slc}, Path=[1]}" 
            ItemsPanel="{StaticResource ResourceKey=ItemsPanelTemplate}" />
    </Border>
    <Border Grid.Column="2" Margin="5,10,10,10" BorderBrush="Black" BorderThickness="1">
        <ListBox 
            local:SharedLayoutCoordinator.Region="{Binding Source={StaticResource slc}, Path=[2]}" 
            ItemsPanel="{StaticResource ResourceKey=ItemsPanelTemplate}" />
    </Border>
</Grid>

Results in:

Window showing three listboxes displaying portions of the same list in columns with scrolling only on the last one

Full demo implementation is available on Github, but the key bits are below.

SharedLayoutCoordinator.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace MultiRegionListBox
{
    internal class SharedLayoutCoordinator : DependencyObject
    {
        private List<SharedLayoutRegion> Regions = new List<SharedLayoutRegion>();
        public SharedLayoutRegion this[int index]
        {
            get
            {
                var slr = new SharedLayoutRegion(this, index);
                for (int i = 0; i < Regions.Count; i++)
                {
                    if (Regions[i].Index > index)
                    {
                        Regions.Insert(i, slr);
                        return slr;
                    }
                }
                Regions.Add(slr);
                return slr;
            }
        }

        public object ItemsSource
        {
            get { return (object)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(object), typeof(SharedLayoutCoordinator), new PropertyMetadata(null));

        public static SharedLayoutRegion GetRegion(DependencyObject obj)
        {
            return (SharedLayoutRegion)obj.GetValue(RegionProperty);
        }

        public static void SetRegion(DependencyObject obj, SharedLayoutRegion value)
        {
            obj.SetValue(RegionProperty, value);
        }

        public static readonly DependencyProperty RegionProperty =
            DependencyProperty.RegisterAttached("Region", typeof(SharedLayoutRegion),
                typeof(SharedLayoutCoordinator), new PropertyMetadata(null, Region_Changed));

        private static void Region_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var itemsControl = (ItemsControl)d;
            var newController = (SharedLayoutRegion)e.NewValue;

            if (newController == null)
            {
                return;
            }

            itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding(nameof(ItemsSource)) { Source = newController.Coordinator });
        }

        public static SharedLayoutRegion GetParentSharedLayoutController(DependencyObject obj)
        {
            while (obj != null)
            {
                if (obj is ItemsControl ic)
                {
                    var slc = GetRegion(ic);
                    if (slc != null)
                    {
                        return slc;
                    }
                }
                obj = VisualTreeHelper.GetParent(obj);
            }

            return null;
        }

        public IEnumerable<SharedLayoutRegion> GetPreceedingRegions(SharedLayoutRegion region)
        {
            return Regions.Where(r => r.Index < region.Index);
        }

        internal SharedLayoutRegion GetNextRegion(SharedLayoutRegion region)
        {
            var idx = Regions.IndexOf(region);
            if (idx + 1 < Regions.Count)
            {
                return Regions[idx + 1];
            }
            return null;
        }

        internal SharedLayoutRegion GetPreviousRegion(SharedLayoutRegion region)
        {
            var idx = Regions.IndexOf(region);
            if (idx > 0)
            {
                return Regions[idx - 1];
            }
            return null;
        }
    }

    internal class SharedLayoutRegion
    {
        private Action InvalidateMeasureCallback;

        public SharedLayoutRegion(SharedLayoutCoordinator coord, int index)
        {
            this.Coordinator = coord;
            this.Index = index;
        }

        public SharedLayoutCoordinator Coordinator { get; }
        public int Index { get; }

        public SharedLayoutStackPanel Panel { get; set; }
        public bool IsMeasureValid
            => !(Panel == null || !Panel.IsMeasureValid || Panel.IsMeasureMeaningless);

        internal bool CanMeasure(Action invalidateMeasure)
        {
            if (Coordinator.GetPreceedingRegions(this).All(pr => pr.IsMeasureValid))
            {
                return true;
            }

            this.InvalidateMeasureCallback = invalidateMeasure;
            return false;
        }

        public int StartOfRegion => Coordinator.GetPreviousRegion(this)?.EndOfRegion ?? 0;
        public int CountInRegion { get; set; }
        public int EndOfRegion => CountInRegion + StartOfRegion;

        public bool HasNextRegion => Coordinator.GetNextRegion(this) != null;

        internal void OnMeasure()
        {
            var nextRegion = Coordinator.GetNextRegion(this);
            if (nextRegion != null && nextRegion.InvalidateMeasureCallback != null)
            {
                nextRegion.InvalidateMeasureCallback();
                nextRegion.InvalidateMeasureCallback = null;
            }
        }
    }
}

SharedLayoutStackPanel.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace MultiRegionListBox
{
    class SharedLayoutStackPanel : Panel, IScrollInfo
    {
        internal const double _scrollLineDelta = 16.0;

        public void LineUp() => SetVerticalOffset(VerticalOffset - _scrollLineDelta);
        public void LineDown() => SetVerticalOffset(VerticalOffset + _scrollLineDelta);
        public void LineLeft() => SetHorizontalOffset(HorizontalOffset - 1.0);
        public void LineRight() => SetHorizontalOffset(HorizontalOffset + 1.0);
        public void PageUp() => SetVerticalOffset(VerticalOffset - ViewportHeight);
        public void PageDown() => SetVerticalOffset(VerticalOffset + ViewportHeight);
        public void PageLeft() => SetHorizontalOffset(HorizontalOffset - ViewportWidth);
        public void PageRight() => SetHorizontalOffset(HorizontalOffset + ViewportWidth);
        public void MouseWheelUp() => SetVerticalOffset(VerticalOffset - SystemParameters.WheelScrollLines * _scrollLineDelta);
        public void MouseWheelDown() => SetVerticalOffset(VerticalOffset + SystemParameters.WheelScrollLines * _scrollLineDelta);
        public void MouseWheelLeft() => SetHorizontalOffset(HorizontalOffset - 3.0 * _scrollLineDelta);
        public void MouseWheelRight() => SetHorizontalOffset(HorizontalOffset + 3.0 * _scrollLineDelta);

        public double ExtentWidth => Extent.Width;
        public double ExtentHeight => Extent.Height;
        public double ViewportWidth => Viewport.Width;
        public double ViewportHeight => Viewport.Height;
        public double HorizontalOffset => ComputedOffset.X;
        public double VerticalOffset => ComputedOffset.Y;

        public void SetHorizontalOffset(double offset)
        {
            if (double.IsNaN(offset))
            {
                throw new ArgumentOutOfRangeException();
            }
            if (offset < 0d)
            {
                offset = 0d;
            }
            if (offset != Offset.X)
            {
                Offset.X = offset;
                InvalidateMeasure();
            }
        }

        /// <summary>
        /// Set the VerticalOffset to the passed value.
        /// </summary>
        public void SetVerticalOffset(double offset)
        {
            if (double.IsNaN(offset))
            {
                throw new ArgumentOutOfRangeException();
            }
            if (offset < 0d)
            {
                offset = 0d;
            }
            if (offset != Offset.Y)
            {
                Offset.Y = offset;
                InvalidateMeasure();
            }
        }

        public ScrollViewer ScrollOwner
        {
            get { return _scrollOwner; }
            set
            {
                if (value == _scrollOwner)
                {
                    return;
                }

                InvalidateMeasure();

                Offset = new Vector();
                Viewport = Extent = new Size();

                _scrollOwner = value;
            }
        }

        public bool CanVerticallyScroll
        {
            get { return true; }
            set { /* noop */ }
        }

        public bool CanHorizontallyScroll
        {
            get { return false; }
            set { /* noop */ }
        }

        internal bool IsMeasureMeaningless { get; private set; }
        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            base.OnVisualParentChanged(oldParent);

            this.SLC = SharedLayoutCoordinator.GetParentSharedLayoutController(this);
            if (SLC != null)
            {
                this.SLC.Panel = this;
            }
            InvalidateMeasure();
        }

        protected override Size MeasureOverride(Size viewportSize)
        {
            if (SLC == null || !SLC.CanMeasure(InvalidateMeasure))
            {
                IsMeasureMeaningless = true;
                return viewportSize;
            }
            IsMeasureMeaningless = false;

            var extent = new Size();
            var countInRegion = 0; var hasNextRegion = SLC.HasNextRegion;
            foreach (var child in InternalChildren.Cast<UIElement>().Skip(SLC.StartOfRegion))
            {
                child.Measure(new Size(viewportSize.Width, double.PositiveInfinity));
                var childDesiredSize = child.DesiredSize;

                if (hasNextRegion && extent.Height + childDesiredSize.Height > viewportSize.Height)
                {
                    break;
                }

                extent.Width = Math.Max(extent.Width, childDesiredSize.Width);
                extent.Height += childDesiredSize.Height;
                SLC.CountInRegion = countInRegion += 1;
            }

            // Update ISI
            this.Extent = extent;
            this.Viewport = viewportSize;
            this.ComputedOffset.Y = Bound(Offset.Y, 0, extent.Height - viewportSize.Height);
            this.OnScrollChange();

            SLC.OnMeasure();

            return new Size(
                Math.Min(extent.Width, viewportSize.Width),
                Math.Min(extent.Height, viewportSize.Height));
        }

        private static double Bound(double c, double min, double max)
            => Math.Min(Math.Max(c, Math.Min(min, max)), Math.Max(min, max));

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            if (IsMeasureMeaningless)
            {
                return arrangeSize;
            }

            double cy = -ComputedOffset.Y;
            int i = 0, i_start = SLC.StartOfRegion, i_end = SLC.EndOfRegion;
            foreach (UIElement child in InternalChildren)
            {
                if (i >= i_start && i < i_end)
                {
                    child.Arrange(new Rect(0, cy, Math.Max(child.DesiredSize.Width, arrangeSize.Width), child.DesiredSize.Height));
                    cy += child.DesiredSize.Height;
                }
                else if (child.RenderSize != new Size())
                {
                    child.Arrange(new Rect());
                }

                i += 1;
            }

            return arrangeSize;
        }

        private void OnScrollChange() => ScrollOwner?.InvalidateScrollInfo();

        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            // no-op
            return rectangle;
        }

        internal ScrollViewer _scrollOwner;

        internal Vector Offset;

        private Size Viewport;

        private Size Extent;

        private Vector ComputedOffset;
        private SharedLayoutRegion SLC;
    }
}

Comments

0

My Solution : Using a WrapPanel with a vertical Orientation. Work fine if you adjust the height of the WrapPanel to the Height of the ListBox.

<ListBox ItemsSource="{Binding mySource}">
      <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
          <WrapPanel Orientation="Vertical" Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight}" />
        </ItemsPanelTemplate>
      </ListBox.ItemsPanel>
      <ListBox.ItemTemplate>
        <DataTemplate>
          <ListBoxItem IsChecked="{Binding checked}">
            <CheckBox IsChecked="{Binding checked}" Content="{Binding Name}" />
          </ListBoxItem>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.