views:

176

answers:

2

Hi there,

I have a WPF app that has a ViewBox to display the items in my collection, diplaying a 2-column grid for my results.

What I'd like to do is, depending on the number of items in my collection, change the number of columns. E.g if there are < 10 items in the list, then show them in just 1 column; If there are 10 items in my list, then show them in 2 columns; If there are 20 items in my list, then show 3 columns.

Here's what I have at present:

<Viewbox>
   <ItemsControl ItemsSource="{Binding myCollection}" Style="{DynamicResource myStyle}" />
</Viewbox>

Here's what myStyle currently defines:

<Style x:Key="myStyle" TargetType="{x:Type ItemsControl}">
   <Setter Property=ItemsControl.ItemsPanel">
      <Setter.Value>
         <ItemsPanelTemplate>
           <UniformGrid Columns="2" />
         </ItemsPanelTemplate>
      </Setter.Value>
   </Setter>
</Style>

How can I make this code work to the above requirement? Thanks.

+1  A: 

How about using a DataTrigger to set a specific style ? Might be feasible if you have a small number of 'if size then columns' tuples.
I see there is no ItemsPanelStyleSelector equivalent (similar to an ItemContainerStyleSelector).

Update: It works. Although I'd also take a look at the other response. Use a valueconverter to bind the Columns value to a value returned by ValueConverter.Convert(list.Count) - sounds cleaner.

   public string[] Options { get; set;}

   public bool NeedsTwoColumns
   {
       get
       {
           return this.Options.Length > 4;
       }
   }

//Xaml
<ListBox ItemsSource="{Binding Options}">
            <ListBox.Style>
                <Style>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding NeedsTwoColumns}" Value="True">
                            <Setter Property="ItemsControl.ItemsPanel">
                                <Setter.Value>
                                    <ItemsPanelTemplate>
                                        <UniformGrid Columns="2"/>
                                    </ItemsPanelTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ListBox.Style>
        </ListBox>
    <ListBox ItemsSource="{Binding Options}">
        <ListBox.Resources>
            <local:MyConverter x:Key="ListLengthToColumnCountConverter"/>
        </ListBox.Resources>
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="{Binding Options.Length, Converter={StaticResource ListLengthToColumnCountConverter}}"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>

//ValueConverter
public class MyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int listSize = (int)value;
        return (int)(listSize / 3);
    } ...
Gishu
Ok, sounds good... how does that work?
Brett Rigby
@Brett - see update. I like the approach with the ValueConverter... less verbose and maintainable.
Gishu
+2  A: 

You could bind the Columns property to the number of items and use an appropriate IValueConverter to determine the number of columns, like so:

<UniformGrid Columns="{Binding Items.Count, Converter={local:ItemsToColumnConverter}}" />

Note that you might need to add a RelativeSource to this Binding in order to make it work.

And an IValueConverter similar to that:

public class ItemsToColumnConverter : IValueConverter
{
    // ...
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int numItems = (int)value;
        if (numItems < 10)
            return 1;
        else if (numItems < 20)
            return 2;
        else if (numItems < 30)
            return 3;
        else
            return numItems / 10;
    }

    public object ConvertBack(...)
    {
        throw new NotSupportedException();
    }
}

Of course, you could also make that converter use another math logic which avoids all the if-elseif-else stuff.

gehho
+1. Nice and clean.
Gishu
In case any other WPF noobs see this code, note that it does not compile as is (at least for me). I had to add a `ValueConversion` attribute to the converter class, create a static resource of that type, then set `Converter={StaticResource myConverter}`. Perhaps I am missing something, but that worked.
Pat
Just to clarify things: 1) You do not need to declare a `StaticResource`. Just specify the namespace where your converter lives as `xmlns:local` and use it as above: `Converter={local:ItemsToColumnConverter}`. 2) The `ValueConversion` attribute is not required. From [MSDN](http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx): *"...it is a good practice to decorate the implementation with a ValueConversionAttribute..."* This does not prevent the code from compiling. It is the definition of the `ConvertBack(...)` method which is incomplete (just an example!).
gehho