.net - template - wpf listview grouping
¿Cómo puedo obtener un ListView GridViewColumn para llenar el espacio restante en mi grilla? (8)
Quiero crear un ListView que tenga dos columnas con un ancho fijo y una tercera columna para completar el espacio restante. Entonces algo como esto:
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="*" />
<GridViewColumn Header="Age" Width="50" />
<GridViewColumn Header="Gender" Width="50" />
</GridView>
</ListView.View>
</ListView>
El problema es que no puedo encontrar la forma de que la columna Name
llene el espacio restante, ya que establecer el ancho en *
no funciona. Parece que hay una manera de hacer esto con un convertidor de valor , pero parece que debería haber una manera más simple. Al igual que con un control DataGrid, puede especificar el ancho de las columnas con *
s.
Aquí hay una solución que le permite a uno tener múltiples ListViews aprovechando un controlador de eventos de "Tamaño" general.
//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);
}
}
El problema es que el ancho de columna de un GridViewColumn es doble, en lugar de un objeto GridLength, y no hay una conversión establecida para manejar el *. No estoy seguro si esto es un descuido del equipo de WPF o no. Usted pensaría que debería ser compatible.
Aparte del convertidor, la única otra forma en que lo he visto hacerlo es aquí: http://www.ontheblog.net/CMS/Default.aspx?tabid=36&EntryID=37 .
Ambos son trabajos adicionales que no deberían ser requeridos. He encontrado otras cosas "raras" con el combo ListView y GridView, así que dejé de usarlas. Si necesito una cuadrícula de datos, uso la de terceros que tenemos licencia, si necesito un menú de estilo de ListBox complejo, solo uso un ListBox con plantillas.
Intentaba lograr lo mismo, pero luego decidí que me gustaría que mis columnas ListView consuman un porcentaje del ListView, el resultado es que todas las columnas consumen una porción de espacio y todo el espacio se consume en el ListView. Puede configurarlo para tener el porcentaje que desee en la última columna para alcanzar directamente el objetivo de "llenar el espacio restante en la última columna".
Encuentro este método bastante robusto y confiable (¡incluso en el tamaño!), Así que pensé que podría compartirlo.
Tengo cuatro columnas en mi ListView para este ejemplo. Todo lo que necesita es registrar el evento SizeChanged
en su ListView con el controlador de eventos siguiente:
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;
}
La solución de OnTheBlog de David Hanson-Greville mencionada en una de las primeras respuestas ya no está disponible, aunque el blog todavía existe. Pude encontrarlo en Wayback Machine y con algunas moderaciones, aquí está:
El truco consiste en establecer Stretch = true en ListView y estirará las columnas que no tienen un ancho igual.
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 Jackson 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;
}
}
}
}
El que acaba de agregar el espacio de nombres al archivo XAML
xmlns:Extensions="clr-namespace:Demo.Extension.Properties"
y úselo en tu lista de vistas:
<ListView ItemsSource="{Binding Path=Items}" DisplayMemberPath="Name"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Grid.Column="0" Margin="8" Extensions:ListViewColumns.Stretch="true">
Me encontré con esto cuando me encontré con un problema similar, mi problema era que quería que todas las columnas estuvieran ''Auto'' esperando la primera, lo que simplemente llenaría el espacio extra, así que amplié la solución de 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;
}
Entonces el XAML es simplemente:
...
<ListView.View>
<GridView>
<GridViewColumn Header="Title" />
<GridViewColumn Header="Artist" Width="Auto" />
<GridViewColumn Header="Album" Width="Auto" />
<GridViewColumn Header="Genre" Width="Auto" />
</GridView>
</ListView.View>
...
Este código también podría usarse de manera más genérica ya que el número de columnas no está codificado y, con un pequeño ajuste, es probable que la "columna de relleno" pueda definirse mediante algún tipo de lógica.
Espero que ayude a alguien :)
Mi necesidad era tener todas las columnas con el mismo ancho. Las soluciones anteriores están bien, pero prefiero envolver tal cosa en una propiedad adjunta (MVVM, reutilización, etc.). Aquí está mi código, si puede ayudar.
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;
}
}
}
Para usarlo:
<ListView ... StarSizeHelper.IsEnabled="true" ... />
(Aún debes arreglar la declaración del espacio de nombres en el XAML, por supuesto)
Puede adaptar sus necesidades de tamaño en el método OnSizeChanged.
Mi problema era similar, pero quería corregir el ancho de la primera columna y tampoco quería que se rompiera si añadía o quitaba columnas, incluso en tiempo de ejecución. Gracias @Gary por el consejo en https://.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;
}
}
Tomé el ejemplo anterior (que es excelente) y lo mejoré un poco para evitar excepciones de tiempo de ejecución al cambiar el tamaño:
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;
}
}