66

I have a ListBox DataTemplate in WPF. I want one item to be tight against the left side of the ListBox and another item to be tight against the right side, but I can't figure out how to do this.

So far I have a Grid with three columns, the left and right ones have content and the center is a placeholder with it's width set to "*". Where am I going wrong?

Here is the code:

<DataTemplate x:Key="SmallCustomerListItem">
    <Grid HorizontalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <WrapPanel HorizontalAlignment="Stretch" Margin="0">
            <!--Some content here-->
            <TextBlock Text="{Binding Path=LastName}" TextWrapping="Wrap" FontSize="24"/>
            <TextBlock Text=", " TextWrapping="Wrap" FontSize="24"/>
            <TextBlock Text="{Binding Path=FirstName}" TextWrapping="Wrap" FontSize="24"/>

        </WrapPanel>
        <ListBox ItemsSource="{Binding Path=PhoneNumbers}" Grid.Column="2" d:DesignWidth="100" d:DesignHeight="50"
     Margin="8,0" Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False" HorizontalAlignment="Stretch"/>
    </Grid>
</DataTemplate>
Eric Haskins
  • 8,505
  • 12
  • 38
  • 47

8 Answers8

151

I also had to set:

HorizontalContentAlignment="Stretch"

on the containing ListBox.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Eric Haskins
  • 8,505
  • 12
  • 38
  • 47
  • 5
    Ta pal. Googled for help and this was on the first link. Nice one :) – Binary Worrier Mar 20 '09 at 23:20
  • @Eric Haskins I have the same kind of problem for the Pivot control for the Windows Phone (``) But where should I put that code? I tried some but I couldnt figure it out. – SynerCoder Oct 04 '12 at 15:30
  • 7
    A note to Windows Phone users - this won't work; the property gets overriden by the ListBox's ItemContainerStyle. To make it work, see Gabriel Mongeon's answer here: http://stackoverflow.com/questions/838828/how-to-get-a-listbox-itemtemplate-to-stretch-horizontally-the-full-width-of-the – Carlos P Nov 11 '12 at 10:11
  • This has to be one of the most annoying UI hacks. Why is this not set by default? Smh. – Matthew S May 14 '18 at 17:17
  • Also worth mentioning that you may need to set this to the ListBoxItem's style, if it has one: ` ` – sergeantKK Apr 10 '19 at 08:51
25
<Grid.Width>
    <Binding Path="ActualWidth" 
             RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollContentPresenter}}" />
</Grid.Width>
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Taeke
  • 251
  • 3
  • 2
  • I love the simplicity of this approach. Not only does it make sure small items fill the width, it makes sure that items whose content would prefer to be very wide are restricted to the listbox width. – Mal Ross Aug 19 '11 at 08:38
  • Exactly what I was searching for: Filling up the width when too small and limiting the width when too large. – Lensflare Jan 07 '16 at 12:43
  • 1
    I just noticed that this solution causes the ListBox width constantly to be slightly smaller than the item's width, which cuts the items off a bit and activates a horizontal scroll bar at the bottom of the listbox. I fixed this by explicitly disabling horizontal scrolling. – Lensflare Jan 07 '16 at 13:10
4

Ok, here's what you have:

Column 0: WrapPanel
Column 1: Nothing
Column 2: ListBox

It sounds like you want WrapPanel on the left edge, ListBox on the right edge, and space to take up what's left in the middle.

Easiest way to do this is actually to use a DockPanel, not a Grid.

<DockPanel>
    <WrapPanel DockPanel.Dock="Left"></WrapPanel>
    <ListBox DockPanel.Dock="Right"></ListBox>
</DockPanel>

This should leave empty space between the WrapPanel and the ListBox.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
17 of 26
  • 27,121
  • 13
  • 66
  • 85
2

Extending Taeke's answer, setting the ScrollViewer.HorizontalScrollBarVisibility="Hidden" for a ListBox allows the child control to take the parent's width and not have the scroll bar show up.

<ListBox Width="100" ScrollViewer.HorizontalScrollBarVisibility="Hidden">                
    <Label Content="{Binding Path=., Mode=OneWay}" HorizontalContentAlignment="Stretch" Height="30" Margin="-4,0,0,0" BorderThickness="0.5" BorderBrush="Black" FontFamily="Calibri" >
        <Label.Width>
            <Binding Path="Width" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}" />
        </Label.Width>
    </Label>
</ListBox >
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
1

The Grid should by default take up the whole width of the ListBox because the default ItemsPanel for it is a VirtualizingStackPanel. I'm assuming that you have not changed ListBox.ItemsPanel.

Perhaps if you got rid of the middle ColumnDefinition (the others are default "*"), and put HorizontalAlignment="Left" on your WrapPanel and HorizontalAlignment="Right" on the ListBox for phone numbers. You may have to alter that ListBox a bit to get the phone numbers even more right-aligned, such as creating a DataTemplate for them.

Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
1

If you want to use a Grid, then you need to change your ColumnDefinitions to be:

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>

If you don't need to use a Grid, then you could use a DockPanel:

    <DockPanel>
        <WrapPanel DockPanel.Dock="Left">
            <!--Some content here-->
            <TextBlock Text="{Binding Path=LastName}" TextWrapping="Wrap" FontSize="24"/>
            <TextBlock Text=", " TextWrapping="Wrap" FontSize="24"/>
            <TextBlock Text="{Binding Path=FirstName}" TextWrapping="Wrap" FontSize="24"/>
        </WrapPanel>
        <ListBox DockPanel.Dock="Right" ItemsSource="{Binding Path=PhoneNumbers}" 
 Margin="8,0" Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/>
        <TextBlock />
    </DockPanel>

Notice the TextBlock at the end. Any control with no "DockPanel.Dock" defined will fill the remaining space.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Phobis
  • 7,524
  • 10
  • 47
  • 76
0

Taeke's answer works well, and as per vancutterromney's answer you can disable the horizontal scrollbar to get rid of the annoying size mismatch. However, if you do want the best of both worlds--to remove the scrollbar when it is not needed, but have it automatically enabled when the ListBox becomes too small, you can use the following converter:

/// <summary>
/// Value converter that adjusts the value of a double according to min and max limiting values, as well as an offset. These values are set by object configuration, handled in XAML resource definition.
/// </summary>
[ValueConversion(typeof(double), typeof(double))]
public sealed class DoubleLimiterConverter : IValueConverter
{
    /// <summary>
    /// Minimum value, if set. If not set, there is no minimum limit.
    /// </summary>
    public double? Min { get; set; }

    /// <summary>
    /// Maximum value, if set. If not set, there is no minimum limit.
    /// </summary>
    public double? Max { get; set; }

    /// <summary>
    /// Offset value to be applied after the limiting is done.
    /// </summary>
    public double Offset { get; set; }

    public static double _defaultFailureValue = 0;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || !(value is double))
            return _defaultFailureValue;

        double dValue = (double)value;
        double minimum = Min.HasValue ? Min.Value : double.NegativeInfinity;
        double maximum = Max.HasValue ? Max.Value : double.PositiveInfinity;
        double retVal = dValue.LimitToRange(minimum, maximum) + Offset;
        return retVal;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Then define it in XAML according to the desired max/min values, as well an offset to deal with that annoying 2-pixel size mismatch as mentioned in the other answers:

<ListBox.Resources>
    <con:DoubleLimiterConverter x:Key="conDoubleLimiter" Min="450" Offset="-2"/>
</ListBox.Resources>

Then use the converter in the Width binding:

<Grid.Width>
    <Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollContentPresenter}}" Converter="{StaticResource conDoubleLimiter}"  />
</Grid.Width>
BCA
  • 7,776
  • 3
  • 38
  • 53
0

The method in Taeke's answer forces a horizontal scroll bar. This can be fixed by adding a converter to reduce the grid's width by the width of the vertical scrollbar control.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace Converters
{
    public class ListBoxItemWidthConverter : MarkupExtension, IValueConverter
    {
        private static ListBoxItemWidthConverter _instance;

        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return System.Convert.ToInt32(value) - SystemParameters.VerticalScrollBarWidth;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return _instance ?? (_instance = new ListBoxItemWidthConverter());
        }
    }
}

Add a namespace to the root node of your XAML.

xmlns:converters="clr-namespace:Converters"

And update the Grid width to use the converter.

<Grid.Width>
    <Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollContentPresenter}}" Converter="{converters:ListBoxItemWidthConverter}"/>
</Grid.Width>
  • This should be done with HorizontalContentAlignment. – 15ee8f99-57ff-4f92-890c-b56153 Jun 22 '17 at 22:39
  • @EdPlunkett Do you mean HorizontalContentAlignment on the ListBox, as in Eric Haskins's accepted answer? If so, I could not get that to work, which is why I moved onto this worse method. – Kevin Hilt Jun 23 '17 at 19:26
  • OMG, the correct general answer isn't even on this page. It's in this answer here: https://stackoverflow.com/a/2924249/424129 -- set `HorizontalContentAlignment="Stretch"` on the listbox *item* controls within the listbox, which you do via `ListBox.ItemContainerStyle`. – 15ee8f99-57ff-4f92-890c-b56153 Jun 23 '17 at 19:29
  • @EdPlunkett I did run across that answer, but unless I keep overlooking something simple, it also is not working for me. It may be because I'm using a data template. – Kevin Hilt Jun 23 '17 at 19:54