3

I have an ObservableCollection which contains ViewModel which in turns defines my buttons definitions.

I've been at it for hours, reading articles after articles but to no avail. I've tried using a Listbox and this is the closest I've got to. My buttons are getting build horizontally but assuming I'm displaying 3 buttons, I want one displayed on the left, one displayed in the middle and one displayed on the right.

<ListBox Grid.Row="2" ItemsSource="{Binding Links}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <StackPanel Background="Beige" Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Button Grid.Column="{Binding Column}" 
                    Grid.Row="0" 
                    Width="90" 
                    Height="90">
                <ContentControl>
                    <StackPanel Orientation="Vertical">
                        <Image Source="{Binding Image}" Width="36" Height="36"
                               Margin="5" Stretch="UniformToFill" 
                               HorizontalAlignment="Center"/>
                        <TextBlock Text="{Binding Description}" 
                                   Foreground="#0F558E" 
                                   FontSize="18" 
                                   HorizontalAlignment="Center"/>
                    </StackPanel>
                </ContentControl>
            </Button>
        </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

As you can see, I set the column dynamically using a property from my ViewModel but I no longer have a grid as I couldn't get it to work, but ideally I'd like to use a grid so that I can specify in which Column to set the button.

When I use a StackPanel, it works but the buttons are right next to each other rather than being split evenly through the entire width of the screen.

I've done something similar to the above using ItemsControl and using a grid, and I can see each button getting added but they overlap each other in row 0, col 0. If I bind the row to the Column property for testing purposes, it build it correctly but my buttons are displayed on different rows which is not what I want. I want each button to be aligned horizontally.

How can I achieve this? I know I could just hard code 3 buttons and just change their icons and text and handle the relevant code by passing the relevant button as binded parameter, but ideally I'd like to build the buttons dynamically in a grid and position them using the column.

Note that the number of column would be fixed i.e. 3, as I'll never have more than this.

Thanks.

Thierry
  • 6,142
  • 13
  • 66
  • 117

3 Answers3

13

but ideally I'd like to use a grid so that I can specify in which Column to set the button.

In any Xaml variant, why not just use the Grid, such as shown below, where the Grid is set to consume all the horizontal space?

Then with the grid's center column to be star sized and to have the rest of the remaining space be consumed after button 1 and button 3, which auto size into their own spaces:

<Grid>

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

   <Button Grid.Column="0"/>
   <Button Grid.Column="1" HorizontalAlignment="Center"/>
   <Button Grid.Column="2"/>

</Grid>

If that fails, set button one's HorizontalAlignment to be Left and button three's as Right.


As an aside with the list box, it may not be stretching to the whole horizontal size of the screen. Check out my answer to a WP8 sizing issue: ListBoxItem HorizontalContentAlignment.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • Here when it is known that 3 buttons will be needed probably the Grid columns width should be defined as below – Arushi Agrawal Oct 15 '14 at 03:17
  • @ArushiAgrawal nah, his is correct for that simple of a layout – Chris W. Oct 15 '14 at 05:01
  • This is what I did originally but then I thought it wasn't as clean if I wanted to bind this to my collection as it would have meant assigning name to each button and dealing with code behind instead of using pure MVVM which I thought was more appropriate for this task. Mad to see how easy it is to display vertically. I was done in under 5 mins! Why can't there be a quick an easy way to change the layout orientation within a list from vertical to horizontal?? As for the stretching, it is definitely stretch. See the ItemContainerStyle and I can confirm it myself that it works. – Thierry Oct 15 '14 at 08:47
  • As for the way you have the grid, this is exactly how I have my grid defined :) This is why I thought I'd introduce a grid in my listbox to see if it would help but nope!! I just wish I could fill it with buttons on the same row rather than different rows but it just won't work but the "grid" part works exactly as it want it. – Thierry Oct 15 '14 at 08:52
  • @Thierry That is understood. Something else is in play. I suggest you try some one-off attempts outside your project and see if you can get the desired effect away from the main app before trying a dynamic solution, then work up to it. Maybe that process will bare out the issue. – ΩmegaMan Oct 15 '14 at 12:55
  • @OmegaMan Unfortunately that's what I've been doing, well to some extend. I build an empty page with a simple view model giving me access to my "Links" observable collection which contains the info for creating the buttons. Nothing else in it. I'll keep trying this tonight and see if I can figure this out. – Thierry Oct 15 '14 at 14:56
  • @Thierry Can you provide one of those distilled examples with the links observable? I don't want to attempt a trial example in fear of "That is not what I meant" situation. – ΩmegaMan Oct 15 '14 at 15:05
  • I'll update the question later with the relevant code. Thanks. – Thierry Oct 15 '14 at 15:54
2

I eventually found the answer to my problem in an article I found on the web.

You can check it out here: Using Grids with ItemsControl in XAML

In short, you need to subclass the itemsControl and you need overwrite the GetContainerForItemOverride method which will take care of copying the "data" of the ItemTemplate to the ContentPresenter. In this instance, the row and column, but for my requirement, it is just the Column, since my row will always be 0.

Here is core part of the code if you don't want to check the full article which resolve the problem of setting controls horizontally in a grid using ItemsControl but note the article takes care of creating rows & columns dynamically as well, which I'm not interested in for my project.

public class GridAwareItemsControl : ItemsControl
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        ContentPresenter container = (ContentPresenter)base.GetContainerForItemOverride();
        if (ItemTemplate == null)
        {
            return container;
        }

        FrameworkElement content = (FrameworkElement)ItemTemplate.LoadContent();
        BindingExpression rowBinding = content.GetBindingExpression(Grid.RowProperty);
        BindingExpression columnBinding = content.GetBindingExpression(Grid.ColumnProperty);

        if (rowBinding != null)
        {
            container.SetBinding(Grid.RowProperty, rowBinding.ParentBinding);
        }

        if (columnBinding != null)
        {
            container.SetBinding(Grid.ColumnProperty, columnBinding.ParentBinding);
        }

        return container;
    }
}

The final xaml looks like this:

<controls:GridAwareItemsControl ItemsSource="{Binding Links}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid Background="Pink">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
            </Grid>
       </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button
                 Grid.Column="{Binding Column}" 
                 Grid.Row="0" 
                 Width="120" Height="120">
                 <ContentControl>
                     <StackPanel Orientation="Vertical">
                         <Image Source="{Binding Image}" Width="36" Height="36" Margin="5"
                                Stretch="UniformToFill" HorizontalAlignment="Center"/>
                         <TextBlock Text="{Binding Description}" Foreground="#0F558E" 
                          FontSize="18" HorizontalAlignment="Center"/>
                     </StackPanel>
                 </ContentControl>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</controls:GridAwareItemsControl>

Once I used the new control, my buttons were correctly placed inside the grid, and therefore were evenly spaced out as the grid took care of that wit the ColumnDefinitions.

If anyone knows how to achieve the same without having to create a new control and overriding the method (in other words, pure XAML), please post it as I'd be very interested to see how this can be done.

Thanks and thank you to Robert Garfoot for sharing this great code!

PS: Note that I've simplified my xaml in order to create a test sample without any style on the buttons, so these are rather large if you try based on this sample.

UPDATE:

Small typo correction but my grid column definition was defined as

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

but as @OmegaMan suggested, to be evenly spread, it should have been defined as

 <Grid.ColumnDefinitions>
     <ColumnDefinition Width="Auto"/>
     <ColumnDefinition Width="*"/>
     <ColumnDefinition Width="Auto"/>
 </Grid.ColumnDefinitions>
Thierry
  • 6,142
  • 13
  • 66
  • 117
0

I was able to accomplish this with a stackpanel inside of a grid, avoiding columns altogether. If you set the stackepanel's HorizontalAlignment to "center", it will center itself inside the grid and grow as buttons are added, still staying centered inside of the grid. Then it's just a matter of margins to have the buttons equally spaced:

<Grid>
<StackPanel
    VerticalAlignment="Stretch"
    HorizontalAlignment="Center"
    Orientation="Horizontal"
    >
    <Button HorizontalAlignment="Center" Content="Add" Width="104" Height="32" Margin="24,0"/>
    <Button HorizontalAlignment="Center" Content="Edit" Width="104" Height="32" Margin="24,0"/>
    <Button HorizontalAlignment="Center" Content="Remove" Width="104" Height="32" Margin="24,0"/>
</StackPanel></Grid>
spujia
  • 147
  • 1
  • 6