In my previous article, Implementing Copy & Paste between cells in Xceed WPF DataGrid, I outined the method I was using to copy and paste values between cells in the Xceed WPF grid. To recap, this involves using the ApplicationCommands to register to the Copy command and copying the content to the clipboard as text. We then register to the Paste command and when this is received, set the current cell to Edit mode, simulate the text input, and commit the change. The next requirement is to support the selection of a range of cells, and to allow the user to paste a single value to all selected cells. Selection should be in one of three ways - either by dragging the mouse from the first cell to the last, by clicking the cell then clicking the last with shift held down, or by clicking cells with control pressed. As Xceed grid doesn't support multiple cell selection, and I don't want to mess around with the internal workings of Xceed's grid, I decided to create an attached property of type Boolean to attach to each selected cell. The attached property would indicate that that cell is currently selected. However, I then realised that as the Xceed grid is not inheriting from the Selector class, I might as well just reuse the Selector.IsSelected attached property, and avoid having to create my own. So here is the function to select a range of cells and set the attached property on them all. The range to be selected is defined by the "current" cell (which we can get natively from the DataGridControl) and a cell passed to the function.
/// <summary>
/// Selects the range of cell from the "current" cell to the specified cell
/// </summary>
/// <param name="toCell">The cell to select to</param>
private static void SelectRangeFromCurrent(Cell toCell)
{
DataGridControl dataGridControl = DataGridControl.GetParentDataGridControl(toCell);
// Need a current item to do anything
if (dataGridControl.CurrentItem == null)
{
return;
}
// Find the current cell
Row currentRow = (Row) dataGridControl.GetContainerFromItem(dataGridControl.CurrentItem);
Cell currentCell = null;
if (currentRow != null && dataGridControl.CurrentColumn != null)
{
currentCell = currentRow.Cells[dataGridControl.CurrentColumn.Index];
}
// Do nothing if it's being edited
if (currentCell == null || currentCell.IsBeingEdited)
{
return;
}
// Clear the selected status on all the cells
ClearSelectedStatus(DataGridControl.GetParentDataGridControl(toCell));
// Get the indexes of the rows and columns we need to loop through
int fromRow, toRow, fromColumn, toColumn;
GetFromAndToRowAndColumns(dataGridControl, currentCell, toCell, out fromRow, out toRow, out fromColumn, out toColumn);
// Loop and set the IsSelected on all cells
for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++)
{
Row row = (Row) dataGridControl.GetContainerFromIndex(rowIndex);
for (int columnIndex = fromColumn; columnIndex <= toColumn; columnIndex++)
{
Selector.SetIsSelected(row.Cells[columnIndex], true);
}
}
<Style TargetType="xcdg:DataCell">
<Style.Triggers>
<Trigger Property="Selector.IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource LightBlueBrush}" />
</Trigger>
</Style.Triggers>
</Style>
/// <summary>
/// Handles when the mouse goes down over a cell, either deselecting everything, or selecting the single cell (if control is pressed)
/// or selecting the range of cells (if shift is pressed)
/// </summary>
private static void HandleCellMouseDown(object sender, RoutedEventArgs e)
{
Cell cell = sender as Cell;
if (cell != null && Mouse.LeftButton == MouseButtonState.Pressed)
{
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
SelectRangeFromCurrent(cell);
}
else if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
Selector.SetIsSelected(cell, true);
}
else
{
ClearSelectedStatus(DataGridControl.GetParentDataGridControl(cell));
}
}
}
/// <summary>
/// Handles when the mouse enters a cell with the left button depressed, selecting the appropriate range of cells
/// </summary>
private static void HandleCellMouseEnter(object sender, RoutedEventArgs e)
{
Xceed.Wpf.DataGrid.Cell cell = sender as Xceed.Wpf.DataGrid.Cell;
if (cell != null && Mouse.LeftButton == MouseButtonState.Pressed)
{
SelectRangeFromCurrent(cell);
}
}
13 comments:
You stated that you put all the code for pasting multiple cells in Xceed WPF Datagrid in a code file named DataGridControlHelper.cs. Where can I download this file?
Yeah the place where I was hosting it died, and I haven't had a chance to upload it again. I will upload it and update the post when it's done.
Thanks
Neil
It's uploaded now - see the inline link in the blog
Let me know if you have any issues using it
Neil
I will start coding what I need now. Will let you know what the result is. Thanks in advance!
Hey thanks for the code it works great. When using databinding one must not forget to set the binding mode to twoway and the cells should not be read-only.
Great to hear. You are correct about the two way databinding. What was the problem with read only cells? They should be handled in the control helper - I actually made a very recent fix around read only columns and read only rows which may solve your problem?
Yes you are right. I only have one questions left. Is it possible to update the source of each cell after leaving that cell? Currently the cell only gets updated when you leave the current row.
I've added a contextmenu to your datagridcontrolhelper.cs
CommandManager.RegisterClassCommandBinding(typeof(DataGridControl), new CommandBinding(ApplicationCommands.Paste, HandleDataGridControlPasteCommandExecuted));
CommandManager.RegisterClassCommandBinding(typeof(DataGridControl), new CommandBinding(ApplicationCommands.Copy, HandleDataGridControlCopyCommandExecuted));
To register copy and paste command.
<xcdg:DataGridControl.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Paste" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"/>
<MenuItem Command="ApplicationCommands.Copy" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"/>
</ContextMenu>
</xcdg:DataGridControl.ContextMenu>
to add the context menu with the commands
Hi Rabin
Looks good!
I seem to remember having the same problem when I was building my WPF application with XCeed's Grid. We solved it in the following way:
- We had the CellEditor's EditTemplate set to an AutoSelectTextBox.
- We registered an event handler for the Unloaded event of this AutoSelectTextBox.
- In the Unloaded event handler, we use the following code to force the grid to commit data back to the source
DataRow dataRow = this.grid.GetContainerFromItem(this.grid.CurrentItem) as DataRow;
if (dataRow != null && dataRow.IsBeingEdited && DataGridCommands.EndEdit.CanExecute(null, dataRow))
{
DataGridCommands.EndEdit.Execute(null, dataRow);
}
Hope this helps
Neil
Hai,
I want to prevent the background color of the current cell to changing into different color.Can you please help me how to do?
Venkat, my guess is you'd have to redefine the template for the cell. You are probably best off posting your question to the XCeed WPF forums
Regards
Neil
Hi,
Thanks for the great DataGridControlHelper, I am using it but I have a problem.
After selecting multiple cells when I am scrolling the data grid I see that other cells marked as selected instead the ones that I selected and if I scroll back to the first location sometimes it display the cells that I selected and sometimes others.
It seems that xceed "recycling" the cell objects and just change the content.
Any solution for that problem ?
Thanks for the Helper, it works fine except when you scroll. I agree that it seems that the grid is recycling the selection.
Any idea of what could be wrong?
I'm surprises that thsi feature has not been implemented in the WPF Datagrid 3.5.
Olivier
i can't see this file.
DataGridControlHelper.cs. Where can I download this file?
Post a Comment