Reputation: 3801
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
To replicate the problem, try the small wpf application below with these steps:
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
Reputation: 37066
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