.net 如何获得 ListView GridViewColumn 来填充网格中的剩余空间?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8331940/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 16:04:23  来源:igfitidea点击:

How can I get a ListView GridViewColumn to fill the remaining space in my grid?

.netwpflistview

提问by JChristian

I want to create a ListView that has two columns with a fixed width and a third column to fill in the remaining space. So something like this:

我想创建一个 ListView,它有两列固定宽度和第三列填充剩余空间。所以像这样:

<ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Name" Width="*" />
            <GridViewColumn Header="Age" Width="50" />
            <GridViewColumn Header="Gender" Width="50" />
        </GridView>
    </ListView.View>
</ListView>

The problem is I can't find a way to get the Namecolumn to fill in the remaining space, as setting the width to *doesn't work. It looks like there is a way to do this with a value converter, but it seems like there should be a simpler way. Like with a DataGrid control, you can specify the widths of columns with *s.

问题是我找不到让Name列填充剩余空间的方法,因为将宽度设置为*不起作用。看起来有一种方法可以使用value converter来做到这一点,但似乎应该有一种更简单的方法。与 DataGrid 控件一样,您可以使用*s指定列的宽度。

采纳答案by Josh

The issue is the column width of a GridViewColumn is double, rather than a GridLength object, and there is no conversion in place to handle the *. Not sure if this is an oversight by the WPF team or not. You would think it should be supported.

问题是 GridViewColumn 的列宽是两倍,而不是 GridLength 对象,并且没有适当的转换来处理 *. 不确定这是否是 WPF 团队的疏忽。你会认为它应该得到支持。

Aside from the converter, the only other way I've seen it done is here: http://www.ontheblog.net/CMS/Default.aspx?tabid=36&EntryID=37.

除了转换器之外,我看到它完成的唯一其他方法是:http: //www.ontheblog.net/CMS/Default.aspx? tabid=36& EntryID=37

Both are additional work that should not be required. I have found other "weird" things with the ListView and GridView combo so I quit using them. If I need a datagrid I use the 3rd party one we license, if I need a complex ListBox style menu, I just use a templated ListBox.

两者都是不需要的额外工作。我在 ListView 和 GridView 组合中发现了其他“奇怪”的东西,所以我不再使用它们。如果我需要一个数据网格,我使用我们许可的第 3 方,如果我需要一个复杂的 ListBox 样式菜单,我只使用模板化的 ListBox。

回答by GONeale

I was trying to achieve the same thing but then decided I would like my ListView columns to consume a percentage of the ListView instead, the result of this is all columns consuming a portion of space and all space being consumed in the ListView. You could set this up to have whatever percentage you like on the last column to directly achieve your 'fill remaining space on last column' goal.

我试图实现同样的事情,但后来决定我希望我的 ListView 列消耗 ListView 的一部分,结果是所有列都消耗了一部分空间,而所有空间都在 ListView 中被消耗。您可以将其设置为在最后一列中使用您喜欢的任何百分比,以直接实现“填充最后一列的剩余空间”目标。

I find this method fairly robust and reliable (even on resize!) so thought I might share.

我发现这种方法相当健壮和可靠(即使在调整大小时!)所以我想我可以分享。

I have four columns in my ListView for this example. All you need is to register the SizeChangedevent in your ListView with the below event handler:

对于此示例,我的 ListView 中有四列。您只需要SizeChanged使用以下事件处理程序在 ListView 中注册事件:

private void ProductsListView_SizeChanged(object sender, SizeChangedEventArgs e)
{
    ListView listView = sender as ListView;
    GridView gView = listView.View as GridView;

    var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
    var col1 = 0.50;
    var col2 = 0.20;
    var col3 = 0.15;
    var col4 = 0.15;

    gView.Columns[0].Width = workingWidth*col1;
    gView.Columns[1].Width = workingWidth*col2;
    gView.Columns[2].Width = workingWidth*col3;
    gView.Columns[3].Width = workingWidth*col4;
}

回答by Gary Connell

Came across this when looking into a similar problem, my issue was I wanted all columns to be 'Auto' expect the first, which would just fill in the extra space, so I expanded on GONeale's solution.

在查看类似问题时遇到了这个问题,我的问题是我希望所有列都是“自动”,除了第一个,这只会填充额外的空间,所以我扩展了 GONeale 的解决方案。

private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
{
    ListView _ListView = sender as ListView;
    GridView _GridView = _ListView.View as GridView;
    var _ActualWidth = _ListView.ActualWidth - SystemParameters.VerticalScrollBarWidth;
    for (Int32 i = 1; i < _GridView.Columns.Count; i++)
    {
        _ActualWidth = _ActualWidth - _GridView.Columns[i].ActualWidth;
    }
    _GridView.Columns[0].Width = _ActualWidth;
}

Then the XAML is simply:

那么 XAML 很简单:

...
<ListView.View>
    <GridView>
        <GridViewColumn Header="Title" />
        <GridViewColumn Header="Artist" Width="Auto" />
        <GridViewColumn Header="Album" Width="Auto" />
        <GridViewColumn Header="Genre" Width="Auto" />
    </GridView>
</ListView.View>
...

This code could also be used on more generically as number of columns isn't hard-coded and with a little tweaking you could probably make the 'fill column' definable through some sort of logic.

这段代码也可以更广泛地使用,因为列数不是硬编码的,只要稍作调整,您就可以通过某种逻辑来定义“填充列”。

Hope it helps someone :)

希望它可以帮助某人:)

回答by Palle Due

The solution from David Hanson-Greville's OnTheBlogmentioned in one of the first answers isn't available anymore, even though the blog still exists. I was able to find it on the Wayback Machine and with a few moderations, here it is:

尽管博客仍然存在,但在第一个答案中提到的David Hanson-Greville 的OnTheBlog中的解决方案不再可用。我能够在 Wayback Machine 上找到它,并进行了一些审核,这里是:

The trick is that you set Stretch=true on your ListView and it will stretch the columns that do not have a width equally.

诀窍是你在 ListView 上设置 Stretch=true ,它会拉伸没有等宽的列。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace Demo.Extension.Properties
{
    ///
    /// ListViewColumnStretch
    ///
    public class ListViewColumns : DependencyObject
    {
        ///
        /// IsStretched Dependancy property which can be attached to gridview columns.
        ///
        public static readonly DependencyProperty StretchProperty =
            DependencyProperty.RegisterAttached("Stretch",
            typeof(bool),
            typeof(ListViewColumns),
            new UIPropertyMetadata(true, null, OnCoerceStretch));

        ///
        /// Gets the stretch.
        ///
        /// The obj.
        ///
        public static bool GetStretch(DependencyObject obj)
        {
            return (bool)obj.GetValue(StretchProperty);
        }

        ///
        /// Sets the stretch.
        ///
        /// The obj.
        /// if set to true [value].
        public static void SetStretch(DependencyObject obj, bool value)
        {
            obj.SetValue(StretchProperty, value);
        }

        ///
        /// Called when [coerce stretch].
        ///
        ///If this callback seems unfamilar then please read
        /// the great blog post by Paul Hymanson found here.
        /// http://compilewith.net/2007/08/wpf-dependency-properties.html
        /// The source.
        /// The value.
        ///
        public static object OnCoerceStretch(DependencyObject source, object value)
        {
            ListView lv = (source as ListView);

            //Ensure we dont have an invalid dependancy object of type ListView.
            if (lv == null)
            {
                throw new ArgumentException("This property may only be used on ListViews");
            }

            //Setup our event handlers for this list view.
            lv.Loaded += new RoutedEventHandler(lv_Loaded);
            lv.SizeChanged += new SizeChangedEventHandler(lv_SizeChanged);
            return value;
        }

        ///
        /// Handles the SizeChanged event of the lv control.
        ///
        /// The source of the event.
        /// The instance containing the event data.
        private static void lv_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            ListView lv = (sender as ListView);
            if (lv.IsLoaded)
            {
                //Set our initial widths.
                SetColumnWidths(lv);
            }
        }

        ///
        /// Handles the Loaded event of the lv control.
        ///
        /// The source of the event.
        /// The instance containing the event data.
        private static void lv_Loaded(object sender, RoutedEventArgs e)
        {
            ListView lv = (sender as ListView);
            //Set our initial widths.
            SetColumnWidths(lv);
        }

        ///
        /// Sets the column widths.
        ///
        private static void SetColumnWidths(ListView listView)
        {
            //Pull the stretch columns fromt the tag property.
            List<GridViewColumn> columns = (listView.Tag as List<GridViewColumn>);
            double specifiedWidth = 0;
            GridView gridView = listView.View as GridView;
            if (gridView != null)
            {
                if (columns == null)
                {
                    //Instance if its our first run.
                    columns = new List<GridViewColumn>();
                    // Get all columns with no width having been set.
                    foreach (GridViewColumn column in gridView.Columns)
                    {
                        if (!(column.Width >= 0))
                        {
                            columns.Add(column);
                        }
                        else
                        {
                            specifiedWidth += column.ActualWidth;
                        }
                    }
                }
                else
                {
                    // Get all columns with no width having been set.
                    foreach (GridViewColumn column in gridView.Columns)
                    {
                        if (!columns.Contains(column))
                        {
                            specifiedWidth += column.ActualWidth;
                        }
                    }
                }

                // Allocate remaining space equally.
                foreach (GridViewColumn column in columns)
                {
                    double newWidth = (listView.ActualWidth - specifiedWidth) / columns.Count;
                    if (newWidth >= 10)
                    {
                        column.Width = newWidth - 10;
                    }
                }

                //Store the columns in the TAG property for later use.
                listView.Tag = columns;
            }
        }
    }
}

The you just add the namespace to the XAML file

您只需将命名空间添加到 XAML 文件

xmlns:Extensions="clr-namespace:Demo.Extension.Properties"

and use it on your list view:

并在您的列表视图中使用它:

<ListView ItemsSource="{Binding Path=Items}" DisplayMemberPath="Name"
                          ScrollViewer.VerticalScrollBarVisibility="Auto"
                          Grid.Column="0" Margin="8" Extensions:ListViewColumns.Stretch="true">

回答by Timores

My need was to have all columns with the same width. The above solutions are fine, but I prefer to wrap such a thing in an attached property (MVVM, reusability, etc.). Here is my code, if it can help.

我的需要是让所有列都具有相同的宽度。上面的解决方案很好,但我更喜欢将这样的东西包装在附加属性中(MVVM、可重用性等)。这是我的代码,如果有帮助的话。

    public class StarSizeHelper {

    private static readonly List<FrameworkElement> s_knownElements = new List<FrameworkElement>();

    public static bool GetIsEnabled(DependencyObject d) {
        return (bool) d.GetValue(IsEnabledProperty);
    }

    public static void SetIsEnabled(ListView d, bool value) {
        d.SetValue(IsEnabledProperty, value);
    }

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", 
                                            typeof(bool), 
                                            typeof(StarSizeHelper),
                                            new FrameworkPropertyMetadata(IsEnabledChanged));

    public static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {

        var ctl = d as ListView;
        if (ctl == null) {
            throw new Exception("IsEnabled attached property only works on a ListView type");
        }

        RememberElement(ctl);
    }

    private static void RememberElement(ListView ctl) {

        if (! s_knownElements.Contains(ctl)) {
            s_knownElements.Add(ctl);

            RegisterEvents(ctl);
        } 
        // nothing to do if elt is known
    }

    private static void OnUnloaded(object sender, RoutedEventArgs e) {

        FrameworkElement ctl = (FrameworkElement) sender;
        ForgetControl(ctl);
    }

    private static void ForgetControl(FrameworkElement fe) {

        s_knownElements.Remove(fe);
        UnregisterEvents(fe);
    }

    private static void RegisterEvents(FrameworkElement fe) {
        fe.Unloaded += OnUnloaded;
        fe.SizeChanged += OnSizeChanged;
    }

    private static void UnregisterEvents(FrameworkElement fe) {
        fe.Unloaded -= OnUnloaded;
        fe.SizeChanged -= OnSizeChanged;
    }

    private static void OnSizeChanged(object sender, SizeChangedEventArgs e) {

        ListView listView = sender as ListView;
        if (listView == null) {
            return; // should not happen
        }
        GridView gView = listView.View as GridView;
        if (gView == null) {
            return; // should not happen
        }

        var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth -10; // take into account vertical scrollbar
        var colWidth = workingWidth / gView.Columns.Count;
        foreach (GridViewColumn column in gView.Columns) {
            column.Width = colWidth;
        }
    }
}

In order to use it:

为了使用它:

<ListView ... StarSizeHelper.IsEnabled="true" ... />

(you still to fix the namespace declaration in the XAML, of course)

(当然,您仍然要修复 XAML 中的命名空间声明)

You can adapt your sizing needs in the OnSizeChanged method.

您可以在 OnSizeChanged 方法中调整您的尺寸需求。

回答by CAD bloke

My problem was similar but I wanted to fix the width of the first column and I also didn't want it to break if I added or removed columns, even at runtime. Thanks @Gary for the tip at https://stackoverflow.com/a/14674830/492

我的问题很相似,但我想修复第一列的宽度,而且我也不希望它在添加或删除列时中断,即使在运行时也是如此。感谢@Gary 在https://stackoverflow.com/a/14674830/492的提示

private void ResultsListView_SizeChanged(object sender, SizeChangedEventArgs e)
    {
      double newWidthForColumnsExceptFirstColumn = ResultsListView.ActualWidth - SystemParameters.VerticalScrollBarWidth - ResultsGridView.Columns[0].Width;
      int columnsCount = ResultsGridView.Columns.Count;
      Double newColumnWidth = newWidthForColumnsExceptFirstColumn / (columnsCount -1);

      for ( int col = 1; col < columnsCount; col++ ) // skip column [0]
      {
        ResultsGridView.Columns[col].Width = newColumnWidth;
      }
    }

回答by jfm

Here is a solution that allows one to have multiple ListViews leverage a general "Resize" event handler.

这是一个解决方案,它允许多个 ListView 利用通用的“调整大小”事件处理程序。

    //Using dictionarys as trackers allows us to have multiple ListViews use the same code
    private Dictionary<string, double> _fixedWidthTracker = new Dictionary<string, double>();
    private Dictionary<string, List<GridViewColumn>> _varWidthColTracker = new Dictionary<string, List<GridViewColumn>>();
    private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        ListView lv = sender as ListView;
        if (lv != null)
        {
            //For validation during Debug
            VerifyName(lv);

            GridView gv = lv.View as GridView;
            if (gv != null)
            {
                if (!_varWidthColTracker.ContainsKey(lv.Name))
                {
                    _varWidthColTracker[lv.Name] = new List<GridViewColumn>();
                    _fixedWidthTracker[lv.Name] = 0;
                    foreach (GridViewColumn gvc in gv.Columns)
                    {
                        if (!double.IsNaN(gvc.Width)) _fixedWidthTracker[lv.Name] += gvc.Width; else _varWidthColTracker[lv.Name].Add(gvc);
                    }
                }
                double newWidthForColumns = e.NewSize.Width - SystemParameters.VerticalScrollBarWidth - _fixedWidthTracker[lv.Name];
                int columnsCount = gv.Columns.Count;
                int numberOfFixedWithColumns = columnsCount - _varWidthColTracker[lv.Name].Count;
                Double newColumnWidth = newWidthForColumns / (columnsCount - numberOfFixedWithColumns);

                foreach (GridViewColumn gvc in _varWidthColTracker[lv.Name])
                {
                    gvc.Width = newColumnWidth;
                }
            }
        }
    }

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This 
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyName(ListView listView)
    {
        if (String.IsNullOrEmpty(listView.Name))
        {
            string msg = "The Name attribute is required to be set on the ListView in order to Bind to this method";
            Debug.Fail(msg);
        }
    }

回答by Dave Oakley

I took the example above (which is great) and improved it slightly to prevent runtime exceptions on resize:

我采用了上面的例子(这很好)并稍微改进了它以防止调整大小时出现运行时异常:

private void tpList_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            ListView listView = sender as ListView;
            GridView gView = listView.View as GridView;

            var workingWidth = listView.ActualWidth - (SystemParameters.VerticalScrollBarWidth + 20); // take into account vertical scrollbar
            var col1 = 0.50;
            var col2 = 0.50;

            var t1 = workingWidth * col1;
            var t2 = workingWidth * col2;
            gView.Columns[0].Width = t1 > 0 ? t1 : 1;
            gView.Columns[1].Width = t2 > 0 ? t2 : 1;

        }
    }