4
votes

I have a UserControl that arranges templated items on a grid (based on the answer from this question). Now I want to make the columns/rows of that grid resizable.

Right now, I have a bit of hack going where I detect mouse events and calculate if the user clicks near a border. If so, it adjust the appropriate column. This mostly works, but I'm having problems with relatively slow MouseMove events firing making it difficult for the user to click in the right spot. Also, I can't expand the size of the last row/column since the mouse now moves outside of the control area where I can't track it (although I could probably fix this with more work).

A better solution would be to just add GridSplitters to each column/row. The problem is, my UserControl uses an ItemsControl and DataTemplate to create the items so I'm having troubles figuring out how to add the splitter. Is it possible for me to modify the user defined template? Is there some other way to add the splitters?

* EDIT *

Here is an example of what I would like to do (if the grid cells were defined manually, instead of via ItemsControl). Note that this example still doesn't solve the resize last row problem.

<Grid
    Grid.Row="2" Grid.Column="1"
    >
    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition Height="30"/>
        <RowDefinition Height="30"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <TextBox Text="Row 0" Grid.Row="0"/>
    <GridSplitter 
        Grid.Row="0" 
        Height="5" 
        HorizontalAlignment="Stretch" 
        VerticalAlignment="Bottom"
        Background="Transparent"/>
    <TextBox Text="Row 1" Grid.Row="1"/>
    <GridSplitter 
        Grid.Row="1" 
        Height="5" 
        HorizontalAlignment="Stretch" 
        VerticalAlignment="Bottom"
        Background="Transparent"/>
    <TextBox Text="Row 2" Grid.Row="2"/>
    <GridSplitter 
        Grid.Row="2"
        Height="5"
        HorizontalAlignment="Stretch" 
        VerticalAlignment="Bottom"
        Background="Transparent"/>
</Grid>
1
could you post the XAML for the same? appreciated if there are sample images too. - pushpraj
Not sure if I understand exactly which XAML you're looking for. I added one ... does that cover it? - ryan0270
Is the grid defined in the user control? Can you post the XAML of the control that defines the grid? - Bartosz Wójtowicz
The grid is defined dynamically. The easiest reference code for it is to look at the accepted answer in the link at the top of my post. - ryan0270

1 Answers

0
votes

Columns can be done by binding the Width of the columns to properties on your viewmodel. This way as a GridSplitter moves, it will update the property, which in turn updates the other items.

For Rows, just adding a GridSplitter to the bottom of the DataTemplate should give you what you need.

I created a little hacky sample app to illustrate:

My Window:

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();

        DataContext = new Context();

        ItemsControl.ItemsSource = new ObservableCollection<Foo>
            {
                new Foo("Hello", "World1"),
                new Foo("Hello1", "World2"),
                new Foo("Hello2", "World3")
            };
    }
}

In the Xaml:

<ItemsControl Name="ItemsControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type utility:Foo}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="{Binding Path=DataContext.Width, RelativeSource={RelativeSource FindAncestor, AncestorType=utility:MainWindow}, Mode=TwoWay}" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="5" />
                </Grid.RowDefinitions>
                <TextBlock Text="{Binding Path=Name}" Grid.Column="0" Grid.Row="0" Margin="5"/>
                <GridSplitter Grid.Column="1" Grid.Row="0" Width="5" Height="Auto" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" ResizeDirection="Columns" />
                <TextBlock Text="{Binding Path=Title}" Grid.Column="2" Grid.Row="0" Margin="5"/>
                <GridSplitter Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" Height="5" Width="Auto" HorizontalAlignment="Stretch" ResizeBehavior="PreviousAndCurrent" ResizeDirection="Rows" />
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Using the following data context and item:

public class Foo
{
    public Foo(string name, string title)
    {
        Name = name;
        Title = title;
    }

    public string Name { get; set; }
    public string Title { get; set; }
}

public class Context : INotifyPropertyChanged
{
    private GridLength _width = new GridLength(60);

    public GridLength Width
    {
        get { return _width; }
        set
        {
            if (_width == value) return;
            _width = value;
            RaisePropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

One important point to note - the Width property of my viewmodel cannot be initialized to GridLength.Auto - otherwise all your columns won't line up.