views:

181

answers:

2

I have the following code currently:

<DataTemplate DataType="{x:Type vm:SectionViewModel}">
    <ScrollViewer>
        <ItemsControl ItemsSource="{Binding ViewModels}">
        </ItemsControl>
    </ScrollViewer>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:StringViewModel}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>
       <Label Name="Left" Grid.Row="0" Grid.Column="0" Content="{Binding Label}"/>
       <TextBox Name="Right" HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="1" Text="{Binding Value}"/>
    </Grid>
</DataTemplate>

The ViewModels property bound to SectionViewModel ItemsControl is a list of StringViewModel. I want to insert each StringViewModel into some sort of content control in the ItemsControl. Currently I just have each StringViewModel to make its own Grid, but that leaves things unaligned. I'd like to insert these items into some sort of content control in ItemsControl, it doesn't necessarily have to be a grid, but it should be within the ItemsControl. How can I do this? I'm also following MVVM, using MVVM Light.

EDIT: I modified the XAML to reflect how I currently have it setup.

+1  A: 

It will be easiest to change your DataTemplate (for SectionViewModel) to use a ListBox instead of your more generic controls there. Once you have that set up, then change your DataTemplate to be attached to the ListBox.

Unfortunately, there's no really easy way to set up the controls like you are describing without getting into much more complex layouts. I would recommend setting it up like this, then if you need to bind to a certain width for your columns, attach it to your parent controls (or set up a property that determines the maximum width and have them all bind to that).

    <ListBox ItemsSource="{Binding Path=ViewModels}">
        <ListBox.ItemTemplate>
            <DataTemplate DataType="{x:Type vm:StringViewModel}">
                <StackPanel Orientation="Horizontal">
                    <Label x:Name="Left" Content="{Binding Label}" Width="{Binding ElementName=SourceControlHere, Path=WidthToBindTo}"/>
                    <TextBox x:Name="Right" HorizontalAlignment="Stretch" Text="{Binding Value}" Width="{Binding ElementName=SourceControlHere, Path=WidthToBindTo2}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
Jeff Wain
Good answer (+1) but I would use an inherited attached property instead and change the StackPanel to a DockPanel so the TextBox can fill the available space. Or use proportional sizing on the grid. Guess I'd better elaborate in an actual answer.
Ray Burns
Was going for simple, your answer would be preferable though :)
Jeff Wain
+2  A: 

If you want to control the width in the containing template you can use an inherited attached property:

public class WidthInformation
{
   // Use propa snippet to create LabelWidth property with this metadata:
   ... RegisterAttached("LabelWidth", typeof(double), typeof(WidthInformation), new FrameworkPropertyMetadata
   {
     Inherits = true
   });
}

It would be used thusly:

<DataTemplate DataType="{x:Type vm:SectionViewModel}">      
  <ScrollViewer>      
    <ItemsControl ItemsSource="{Binding ViewModels}"
                  local:WidthInformation.LabelWidth="60" />
  </ScrollViewer>      
</DataTemplate>      
<DataTemplate DataType="{x:Type vm:StringViewModel}">      
  <DockPanel>
    <Label Content="{Binding Label}"
           Width="{Binding Path=(local:WidthInformation.LabelWidth)"/>      
    <TextBox Text="{Binding Value}"/>      
  </DockPanel>
</DataTemplate>

The use of a DockPanel will cause the TextBox width to automatically fill the remaining space.

On the other hand, if you want the two columns to be the same percentage size relative to each other, you could use star sizing on the columns:

<DataTemplate DataType="{x:Type vm:StringViewModel}">      
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="2*" />
      <ColumnDefinition Width="3*" />
    </Grid.ColumnDefinitions>
    <Label Content="{Binding Label}" />      
    <TextBox Text="{Binding Value}" Grid.Column="1" />      
  </DockPanel>
</DataTemplate>

A very simple solution is to hard-code the width inside the DockPanel instead of using an attached property:

<DataTemplate DataType="{x:Type vm:StringViewModel}">      
  <DockPanel>
    <Label Content="{Binding Label}" Width="80" />
    <TextBox Text="{Binding Value}"/>      
  </DockPanel>
</DataTemplate>

Lastly, if you need the width to adjust based on label size you can use a grid with shared sizing:

<DataTemplate DataType="{x:Type vm:SectionViewModel}">      
  <ScrollViewer>      
    <ItemsControl ItemsSource="{Binding ViewModels}"
                  Grid.IsSharedSizeScope="true" />
  </ScrollViewer>      
</DataTemplate>      
<DataTemplate DataType="{x:Type vm:StringViewModel}">      
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Label Content="{Binding Label}" />      
    <TextBox Text="{Binding Value}" Grid.Column="1" />      
  </DockPanel>
</DataTemplate>

WPF is just full of possibilities!

Ray Burns
I ended up implementing this completely different (using a DataGrid) but this was essentially what I was looking for. It will definitely help since I always seem to end up doing something that requires this. Thanks a bunch!
MGSoto