WPF 列表框布局:多列

发布于 2024-10-12 12:55:33 字数 276 浏览 5 评论 0原文

我有一个包含复选框的列表框(WPF)。我正在配置屏幕中使用。原理图示例如下:

alt text

现在我想添加一个“Test 5”复选框。我垂直空间有限,所以希望它出现在水平方向,如下图:

alt text

ListBox 可以吗布局是否需要修改,以便复选框会这样排列?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

初心 2024-10-19 12:55:33
<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>
<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>
最偏执的依靠 2024-10-19 12:55:33

我遇到了类似的问题,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>

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>
南风起 2024-10-19 12:55:33

我知道这是一篇较旧的帖子,但当我试图在这里解决同样的问题时,我偶然发现了一种相当简单的方法: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>

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>
ゃ懵逼小萝莉 2024-10-19 12:55:33

具有多列的 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>

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>
冷︶言冷语的世界 2024-10-19 12:55:33

如果您需要在多个区域(在我的例子中是多个窗口)之间流动行,您可以使用自定义面板实现。

用法示例:

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

结果:

显示三个列表框的窗口,在列中显示同一列表的部分内容,仅在最后一个上滚动

完整的演示实现是 可在 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;
    }
}

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;
    }
}
爺獨霸怡葒院 2024-10-19 12:55:33

我的解决方案:使用垂直方向的 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>

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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文