Подтвердить что ты не робот

Разметка списка WPF: несколько столбцов

У меня есть ListBox (WPF), который содержит CheckBoxes. Я использую экран конфигурации. Пример схемы ниже:

alt text

Теперь я хочу добавить CheckBox "Test 5". У меня ограниченное вертикальное пространство, поэтому я хочу, чтобы он отображался в горизонтальном направлении, как показано ниже:

alt text

Можно ли изменить макет ListBox, чтобы CheckBoxes был устроен так?

4b9b3361

Ответ 1

<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>

или просто:

<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>

Ответ 2

Я столкнулся с подобной проблемой, и ответ на eibhrum дал мне некоторую идею. Я использовал следующий код, и я думаю, что это тоже то, что вам нужно. Я использовал UniformGrid вместо 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>

Ответ 3

Я знаю, что это более старое сообщение, но я наткнулся на довольно простой способ сделать это, пока я пытался решить ту же проблему: http://social.technet.microsoft.com/wiki/contents/articles/19395.multiple-columns-in-wpf-listbox.aspx

Просто добавьте источник данных привязки (или добавьте элементы по мере необходимости).

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

Ответ 4

ListBox с несколькими столбцами и ListBoxItem Ориентация является вертикальной. ListBox имеет фиксированную высоту и автоматическую ширину. Когда добавить ListBoxItem, ListBox будет автоматически увеличивать ширину.

<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 с несколькими столбцами и ListBoxItem Ориентация по горизонтали. ListBox имеет фиксированную ширину и автоматическую высоту. Когда добавить ListBoxItem, ListBox автоматически увеличит высоту.

<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>

Ответ 5

Если вам нужно перемещать строки между несколькими регионами (в моем случае, несколькими окнами), вы можете использовать пользовательскую реализацию панели.

Пример использования:

<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>

Результаты в:

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

Полная демонстрационная реализация доступна на Github, но ключевые биты приведены ниже.

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;
    }
}

Ответ 6

Мое решение: использование WrapPanel с вертикальной ориентацией. Работайте нормально, если вы отрегулируете высоту панели WrapPanel на высоту 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>