Anthony
Anthony

Reputation: 3801

WPF: Removing a row in Grid makes other rows overlapping

I need to programmatically add new rows with several controls to a grid and these rows and controls also need to be deleted after some time. The problem is, whenever a row (not the last row) is deleted, several rows may become overlapping.

Initial state

After row 0 has been deleted

After row 0 has been deleted

To replicate the problem, try the small wpf application below with these steps:

  1. Paste the code into a new WPF application and run it.
  2. Click "add a new row" button several times (>2).
  3. Click one of the delete buttons (apart from the last one).
  4. You should see the problem.

xml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="MainGrid">

    </Grid>
</Window>

C# code:

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {

        // secondary grid
        Grid _sGrid = new Grid();

        public MainWindow()
        {
            InitializeComponent();
            SizeToContent = SizeToContent.Height;
            // divid the initial grid into two rows.
            MainGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            MainGrid.RowDefinitions.Add(new RowDefinition());

            // add a stack panel to the top row.
            var sp = new StackPanel();
            Grid.SetRow(sp, 0);
            // add a "add a new row" button to the stack panel.
            var btn = new Button { Content = "add a new row" };
            btn.Click += AddRow;
            sp.Children.Add(btn);

            // add a secondary grid to the bottom row.
            Grid.SetRow(_sGrid, 1);

            // add controls
            MainGrid.Children.Add(sp);
            MainGrid.Children.Add(_sGrid);
        }

        private int _rowCount;
        private int _rowSerialNo;
        public void AddRow(object sender, RoutedEventArgs e)
        {
            // add a row in the secondary grid, set height to auto
            var rd = new RowDefinition {Height = GridLength.Auto};
            _sGrid.RowDefinitions.Add(rd);

            // add a label to the row
            var lbl = new Label {Content = $"This is row {_rowSerialNo}"};
            Grid.SetRow(lbl,_rowCount);
            _sGrid.Children.Add(lbl);

            // add a button to the top row of the main grid for deleting this new row
            var btn = new Button {Content = $"del row {_rowSerialNo}"};
            btn.Click += DelRow;
            (MainGrid.Children[0] as StackPanel).Children.Add(btn);
            // set resources to make it easier to clear all contents in the row
            btn.Resources.Add("rd", rd);
            btn.Resources.Add("lbl",lbl);

            // advance row count
            _rowCount++;
            _rowSerialNo++;
        }

        private void DelRow(object sender, RoutedEventArgs e)
        {
            // remove contents
            _sGrid.Children.Remove((UIElement)(sender as Button).Resources["lbl"]);

            // remove row definition
            _sGrid.RowDefinitions.Remove((RowDefinition)(sender as Button).Resources["rd"]);

            // remove the delete button
            (MainGrid.Children[0] as StackPanel).Children.Remove((UIElement)sender);

            _rowCount--;
        }
    }
}

I'm using VS2015 Community

Upvotes: 1

Views: 427

Answers (1)

Here's a quick MVVM implementation of what it looks to me like your'e doing. This is what I meant about using ItemsControl. There are more lines of code but it's almost all declarative boilerplate stuff, so it's quite difficult to get it wrong.

The value of this approach is that you add and remove items from a collection, and the view updates itself automatically to reflect the current contents of the collection. Adding an item is one simple step: Add the item. Removing it is one simple step: Remove the item. If you change the way the items are displayed, that's done by altering the XAML; it has nothing to do with how items are added or removed.

If you have any questions about how any of it works, ask.

XAML:

<StackPanel>
    <Button Command="{Binding AddRowItemCommand}">Add Row</Button>
    <ItemsControl
        ItemsSource="{Binding RowItems}"
        >
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button 
                    Content="{Binding Text}"    
                    ContentStringFormat="{}Delete {0}"
                    Command="{Binding DataContext.RemoveRowItemCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" 
                    CommandParameter="{Binding}"
                    />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    <ItemsControl
        ItemsSource="{Binding RowItems}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Label Content="{Binding Text}" ContentStringFormat="{}This is {0}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

</StackPanel>

"Viewmodels": These aren't really viewmodels, technically, since they don't ever have any need to raise INotifyPropertyChanged.PropertyChanged.

public class MainViewModel
{
    public MainViewModel()
    {
        RemoveRowItemCommand = new DelegateCommand<RowItem>(RemoveRowItem);
        AddRowItemCommand = new DelegateCommand<object>(AddRowItem);
    }

    public ObservableCollection<RowItem> RowItems { get; } = new ObservableCollection<RowItem>();

    public ICommand RemoveRowItemCommand { get; private set; }
    public ICommand AddRowItemCommand { get; private set; }

    public void RemoveRowItem(RowItem rowItem)
    {
        RowItems.Remove(rowItem);
    }

    private int _nextRowItemID = 0;
    public void AddRowItem(object unused)
    {
        RowItems.Add(new RowItem { Text = $"Row {++_nextRowItemID}" });
    }
}

public class DelegateCommand<TParam> : ICommand
{
    public DelegateCommand(Action<TParam> exec)
    {
        _execute = exec;
    }

    public event EventHandler CanExecuteChanged;

    private Action<TParam> _execute;
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _execute?.Invoke(parameter == null ? default(TParam) : (TParam)parameter);
    }
}

public class RowItem
{
    public String Text { get; set; }
}

Code behind:

    public MainWindow()
    {
        InitializeComponent();

        DataContext = new MainViewModel();
    }

Upvotes: 2

Related Questions