Home > Mobile >  Button formatting not working inside ListBox
Button formatting not working inside ListBox

Time:08-24

My goal is a list of custom buttons bound to a list of users, each button has that users image and name. I created my custom button and it looks great, until I add it to a ListBox, then all the formatting is suddenly ignored. The data binding and all appears to be fine, it's just the formatting that is ignored. Can someone please nudge me in the right direction? My code and screenshots are below. The only difference between images is uncommenting the top 3 (and matching bottom 3) lines to enable the ListBox.

<!--<ListBox ItemsSource="{Binding userRecords}">
    <ListBox.ItemTemplate>
        <DataTemplate>-->
            <Button Click="UserButton_Click">
                <Button.Template>
                    <ControlTemplate>
                        <StackPanel Orientation="Vertical">
                            <Border BorderBrush="Blue" BorderThickness="1" CornerRadius="20,20,20,20">
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="1*"/>
                                    </Grid.RowDefinitions>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width=".2*"/>
                                        <ColumnDefinition Width="1*"/>
                                        <ColumnDefinition Width="1*"/>
                                        <ColumnDefinition Width=".2*"/>
                                    </Grid.ColumnDefinitions>
                                    <Image Source="{Binding Path=userImage}" Grid.Column="1" HorizontalAlignment="Stretch"  VerticalAlignment="Stretch" Stretch="Fill" />
                                    <TextBlock Text="{Binding Path=userName}" Grid.Column="2" TextAlignment="Center" VerticalAlignment="Center" FontSize="20" Foreground="Blue"/>
                                </Grid>
                            </Border>
                            <TextBlock></TextBlock>
                        </StackPanel>
                    </ControlTemplate>

                </Button.Template>
            </Button>
        <!--</DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>-->

Single button by itself, looks great (no ListBox).

Custom button centered in the available space with the image keeping its aspect ratio.

Enabling the ListBox code and all formatting is out the window.

Custom button in a ListBox where the image is scaled to exceed the available space cutting-off half of it.

CodePudding user response:

Your issue is mainly caused by misconceptions about the layouting system. Let us get to it step by step. First we look at the control template of your Button. It has a Border that contains a Grid. Both will occupy all of the space that their parent element leaves them. Then you have an Image that has its Stretch property set to Fill. According to the documentation this means:

The content is resized to fill the destination dimensions. The aspect ratio is not preserved.

Let us summarizes this. The Border and Grid will occupy any space it is given and the image will strech non-uniformly to fit the width and height it is given. Now, that does not make sense if you look at your first image. The image does not stretch fit the whole area at all, in fact it even keeps its aspect ratio. So is WPF broken? No.

You put the Border inside a StackPanel that is oriented vertically. This means the horizontal space is restricted to the width of the parent container, but the vertical space is not. A StackPanel allows to stack an arbitrary number of elements and does not care about the availbable space in the stacking direction. It measures its children with infinity. In other words, it asks: How much space do you need? If it exceeds the visible space, the rest will be cut-off. Now, the image cannot stretch to an infinite height, how would it? Instead it simply falls back to the height that fits the aspect ratio, effectively acting as if its Stetch property was set to Uniform and the width is already fixed. The same would be true, if you removed the StackPanel from the control template, but put one around the Button.

That explains the behavior in the first image, but what about the second? There you have a collection of items to display in a ListBox. For each of these items the data template will be instantiated, creating a button, that is put in an item container for the items control, here a ListBoxItem. It has properties for the HorizontalContentAlignment and VerticalContentAlignment that are bound to the same properties on ListBox, so you can set them once for all items. The defaults are Left and Top which act like measuring with infinity, e.g. letting the content stretch to its maximum dimensions both vertically and horizontally. Consequently, as above the image stretches to its orginal size but in both directions independently keeping its aspect ratio. Obviously, if the content exceeds the avilable space, it is cut-off, too, which is what you see in the second image.

How to solve the issues? That depends on your requirements.

  • Do you want the content to expand to the ListBox width when it is smaller than the available space?
    • Then set the HorizontalContentAlignment to Stretch on the ListBox.

      <ListBox ItemsSource="{Binding userRecords}"
               HorizontalContentAlignment="Stretch">
      
  • Do you want to preserve the space of the rounded corner, so the image does not overlap?
    • Then set a Padding left and right on the Border inside your control template.

      <Border BorderBrush="Blue"
              BorderThickness="1"
              CornerRadius="20,20,20,20"
              Padding="20, 0">
      
    • Alternatively you could define an opacity mask, but that is out of scope here.

  • Do you want the button to stretch the image uniformly to fit the available space?
    1. Change the control template to use a Grid instead of a StackPanel, that has two rows - one for the image and user name to allow them taking up the remaining space (*) and one for the TextBlock that only gets as much space as it really requires.

      <ControlTemplate>
          <Grid.RowDefinitions>
              <RowDefinition/>
              <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
              <Border BorderBrush="Blue" BorderThickness="1" CornerRadius="20,20,20,20">
                  <Grid>
                      <Grid.RowDefinitions>
                          <RowDefinition Height="1*"/>
                      </Grid.RowDefinitions>
                      <Grid.ColumnDefinitions>
                          <ColumnDefinition Width=".2*"/>
                          <ColumnDefinition Width="1*"/>
                          <ColumnDefinition Width="1*"/>
                          <ColumnDefinition Width=".2*"/>
                      </Grid.ColumnDefinitions>
                      <Image Source="{Binding Path=userImage}" Grid.Column="1" HorizontalAlignment="Stretch"  VerticalAlignment="Stretch" Stretch="Uniform" />
                      <TextBlock Text="{Binding Path=userName}" Grid.Column="2" TextAlignment="Center" VerticalAlignment="Center" FontSize="20" Foreground="Blue"/>
                  </Grid>
              </Border>
              <TextBlock></TextBlock>
          </Grid>
      </ControlTemplate>
      
    2. Set the HorizontalContentAlignment to Stretch on the ListBox.

      <ListBox ItemsSource="{Binding userRecords}"
               HorizontalContentAlignment="Stretch">
      
    3. The vertical size of a ListBox is fixed after measuring, so if you want it be be less than the original image height, you have to set an explicit Height for all ListBoxItems through an ItemContainerStyle.

      <ListBox.ItemContainerStyle>
         <Style TargetType="{x:Type ListBoxItem}"
                BasedOn="{StaticResource {x:Type ListBoxItem}}">
            <Setter Property="Height" Value="100"/>
         </Style>
      </ListBox.ItemContainerStyle>
      

CodePudding user response:

I got it! From what I understand, the ListBox became the new parent container for everything underneath and all my relative sizing was lost. After lots and lots of trial and error with settings on the various containers, here's the code that works. Everything fills the space and it resizes automatically when the window is resized.

                    <ListBox ItemsSource="{Binding userRecords}" Background="LightGray" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <Button Click="UserButton_Click" HorizontalContentAlignment="Stretch">
                                <Button.Template>
                                    <ControlTemplate>
                                        <Border BorderBrush="Blue" BorderThickness="1" CornerRadius="20,20,20,20" HorizontalAlignment="Stretch">
                                            <Grid HorizontalAlignment="Stretch">
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="100"/>
                                                </Grid.RowDefinitions>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width=".2*"/>
                                                    <ColumnDefinition Width="1*"/>
                                                    <ColumnDefinition Width="1*"/>
                                                    <ColumnDefinition Width=".2*"/>
                                                </Grid.ColumnDefinitions>
                                                <Image Source="{Binding Path=userImage}" Grid.Column="1" HorizontalAlignment="Stretch"  VerticalAlignment="Stretch"  />
                                                <TextBlock Text="{Binding Path=userName}" Grid.Column="2" TextAlignment="Center" VerticalAlignment="Center" FontSize="20" Foreground="Blue"/>
                                            </Grid>
                                        </Border>
                                    </ControlTemplate>
                                </Button.Template>
                            </Button>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
  • Related