Reputation: 41
This question might be a bit open ended but I am looking for advice on how to tackle the issue of merging and splitting Grid cells dynamically.
So currently I generate a Grid of X rows and Y columns and each of these cells is then populated with a StackPanel. When clicked a StackPanel has a border added to it and an array holding bool values representing each Grid cell is modified to show that it has been clicked. The grid holds no other controls. This is all done programmatically in c# rather than in the xaml.
What I want to be able to do is to merge and split selected cells in the grid if they are next to each other by hitting a button. I have found no way of easily doing this and I am wondering if anyone can recommend a good approach for this issue.
I am not tied to my current approach and any alternate method suggestions are appreciated.
Upvotes: 0
Views: 3083
Reputation: 7437
I just posted one example of how to do this on Github.
The basic idea is to initialize the grid with the maximum number of rows and columns and then create and delete cells while modifying their positions and spans as necessary to fill the grid.
Here are the most relevant snippets:
public MainWindow()
{
InitializeComponent();
var numRows = grid.RowDefinitions.Count;
var numCols = grid.ColumnDefinitions.Count;
for (int i = 0; i < numRows; i++)
{
for (int j = 0; j < numCols; j++)
{
var item = new DynamicGridItem(j, i);
item.Merge += HandleMerge;
item.Split += HandleSplit;
grid.Children.Add(item);
Grid.SetRow(item, i);
Grid.SetColumn(item, j);
}
}
}
Here you can see that my cell items are a custom UserControl
class with a constructor that takes two integers as X and Y grid coordinates. These coordinates can then be used to simplify grid row and column assignment. They aren't strictly necessary in my example, but could be helpful if storing/loading grid items and working with them in other code modules.
My example uses a single Merge
event with a boolean event argument to indicate whether the merge is to the left or to the right. The actual event handler just figures out whether to offset left or right and passes that information as well as the source item to the HandleMergeOffset
method below.
private void HandleMergeOffset(DynamicGridItem item, int offset)
{
var otherItem = FindItemByXOffset(item, offset);
if (otherItem == null)
{
return; // Nothing to do
}
otherItem.Merge -= HandleMerge;
otherItem.Split -= HandleSplit;
Grid.SetColumnSpan(item, Grid.GetColumnSpan(item) + Grid.GetColumnSpan(otherItem));
grid.Children.Remove(otherItem);
if (offset < 0) // Reposition item if merging left
{
Grid.SetColumn(item, otherItem.X);
item.X = otherItem.X;
}
}
You could probably write a better FindItem
method than I did. The basic idea is to find the neighbor to remove, expand the current item to include both original column spans, unhook events from the removed item, and of course remove it from the grid.
Since we remove an item on Merge
, we create a new item on Split
. I decided not to bother with a SplitLeft
or SplitRight
designation. This code will always create a minimum cell at the left-most index of the original cell and push the remainder one column right.
private void HandleSplit(object sender, EventArgs e)
{
var item = (DynamicGridItem)sender;
var itemColSpan = Grid.GetColumnSpan(item);
if (itemColSpan < 2)
{
return; // Nothing to do
}
var newItem = new DynamicGridItem(item.X, item.Y);
newItem.Merge += HandleMerge;
newItem.Split += HandleSplit;
grid.Children.Add(newItem);
Grid.SetColumn(newItem, newItem.X);
Grid.SetRow(newItem, newItem.Y);
Grid.SetColumn(item, item.X + 1);
Grid.SetColumnSpan(item, itemColSpan - 1);
item.X += 1;
}
Note that this makes no attempt to handle splitting a single cell into half cells (so that we never mutate the original grid or deal with sub-grids). If you need arbitrary dynamic grid behavior, you'll want to look elsewhere.
Upvotes: 2