WPF DataGrid Multiple Cell Selection and also allow Multile Row Selection from Row Header

Jan 18, 2010 at 7:55 AM

Hi,
I have a WPF datagrid, where i Need to allow the users to select multiple Cells and also allow selection of multiple rows through row Header, so that they can deleted selected Cells contents and also able to paste into selected cells (Copy from selected cells work), But have following problem:
1. Row Selection does not work even from Row header until user right clicks on the Row Header of Desired row.
2. Not able to get the selected Cells (Multiple cells) values in Binding, so that i can Copy and paste into those cells.

Sample Code:
<my:DataGrid Visibility="Visible"

SelectionMode

 

="Extended"

 

RowHeaderWidth="10"

 

SelectionUnit="CellOrRowHeader"

 

IsReadOnly="False"

 

Margin="5"

 

ItemsSource="{Binding MapValues}"

 

AutoGenerateColumns="False"

 

c:DataGridMultiSelectBehaviour.SelectedItems="{Binding SelectedMapValue}"

 

c:DataGridCellChangeBehavior.CellValueChanged="{Binding DataChangedCommand}"

 

filter:DataGridExtensions.IsFilterVisible="False"

 

CanUserAddRows="False" >

 

 

<my:DataGrid.ContextMenu>

 

 

<ContextMenu>

 

 

<MenuItem Header="Add" Command="{Binding NewMapValueCommand}">

 

 

<MenuItem.Icon>

 

 

<Image Source="..\..\Image\add.gif" />

 

 

</MenuItem.Icon>

 

 

</MenuItem>

 

 

</ContextMenu>

 

 

</my:DataGrid.ContextMenu>

 

 

<my:DataGrid.Columns>

 

 

<my:DataGridTextColumn Header="Source Value" Width="Auto" Binding="{Binding ViewSourceValue}"

 

CanUserSort="True" filter:DataGridColumnExtensions.IsClearFilterColumn="True"/>

 

 

<my:DataGridTextColumn Header="Target Value" Width="Auto" Binding="{Binding ViewTargetValue}"

 

CanUserSort="True" />

 

 

</my:DataGrid.Columns>

 

 

 

 

Jan 19, 2010 at 5:46 PM

Any update on this. Im also having similar problem

Feb 5, 2010 at 3:16 AM
Edited Feb 5, 2010 at 3:19 AM

Finally Problem got resolved:

Problem 1. Row Selection does not work even from Row header until user right clicks on the Row Header of Desired row.

Resolution: The rowHeaderwidth of the Grid have to be increased so that User can click on the rowheader easily and since the

RowHeaderGriperWidth is 8 in actual grid generic.xml, we decreased that width to making it much easier to select a row from row header.

Now next inorder to enable the user to select the item using just aselected cell we have written a behaviour that helps to het teh selected row of teh selected cell.

2. Not able to get the selected Cells (Multiple cells) values in Binding, so that i can Copy and paste into those cells.

in order to paste into the selected cells teh actual grid code is modified and a deafult paste function is added: here is the code:

/// 
public static List ParseClipboardData()
        {
            List clipboardData = null;
            object clipboardRawData = null;
            ParseFormat parseFormat = null;

            // get the data and set the parsing method based on the format
            // currently works with CSV and Text DataFormats            
            IDataObject dataObj = Clipboard.GetDataObject();
            if ((clipboardRawData = dataObj.GetData(DataFormats.CommaSeparatedValue)) != null)
            {
                parseFormat = ParseCsvFormat;
            }
            else if ((clipboardRawData = dataObj.GetData(DataFormats.Text)) != null)
            {
                parseFormat = ParseTextFormat;
            }

            if (parseFormat != null)
            {
                string rawDataStr = clipboardRawData as string;

                if (rawDataStr == null && clipboardRawData is MemoryStream)
                {
                    // cannot convert to a string so try a MemoryStream
                    MemoryStream ms = clipboardRawData as MemoryStream;
                    StreamReader sr = new StreamReader(ms);
                    rawDataStr = sr.ReadToEnd();
                }

                if (rawDataStr.IndexOf("\0") > 0)
                {
                    rawDataStr = rawDataStr.Substring(0, rawDataStr.Length - 1);
                }

                string[] rows = rawDataStr.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                if (rows != null && rows.Length > 0)
                {
                    clipboardData = new List();
                    foreach (string row in rows)
                    {
                        clipboardData.Add(parseFormat(row));
                    }
                }
            }

            return clipboardData;
        }

        public static string[] ParseCsvFormat(string value)
        {
            return ParseCsvOrTextFormat(value, true);
        }

        public static string[] ParseTextFormat(string value)
        {
            return ParseCsvOrTextFormat(value, false);
        }

        private static string[] ParseCsvOrTextFormat(string value, bool isCSV)
        {
            List outputList = new List();

            char separator = isCSV ? ',' : '\t';
            int startIndex = 0;
            int endIndex = 0;

            for (int i = 0; i < value.Length; i++)
            {
                char ch = value[i];
                if (ch == separator)
                {
                    outputList.Add(value.Substring(startIndex, endIndex - startIndex));

                    startIndex = endIndex + 1;
                    endIndex = startIndex;
                }
                else if (ch == '\"' && isCSV)
                {
                    // skip until the ending quotes
                    i++;
                    if (i >= value.Length)
                    {
                        throw new FormatException(string.Format("value: {0} had a format exception", value));
                    }
                    char tempCh = value[i];
                    while (tempCh != '\"' && i < value.Length)
                        i++;

                    endIndex = i;
                }
                else if (i + 1 == value.Length)
                {
                    // add the last value
                    outputList.Add(value.Substring(startIndex));
                    break;
                }
                else
                {
                    endIndex++;
                }
            }

            return outputList.ToArray();
        }
/// This virtual method is called when ApplicationCommands.Copy command is executed. /// /// <param name="args" /> protected virtual void OnExecutedPaste(ExecutedRoutedEventArgs args) { // Todo: Default paste handler // parse the clipboard data List rowData = ClipboardHelper.ParseClipboardData(); ////if (rowData.Count == 1) // call OnPastingCellClipboardContent for each cell foreach (var selectedCell in SelectedCells) { this.CurrentCell = selectedCell; int minRowIndex = Items.IndexOf(selectedCell.Item); int maxRowIndex = Items.Count - 1; int minColumnDisplayIndex = (SelectionUnit != DataGridSelectionUnit.FullRow) ? Columns.IndexOf(CurrentColumn) : 0; int maxColumnDisplayIndex = Columns.Count - 1; int rowDataIndex = 0; for (int i = minRowIndex; i <= maxRowIndex && rowDataIndex < rowData.Count; i++, rowDataIndex++) { int columnDataIndex = 0; for (int j = minColumnDisplayIndex; j <= maxColumnDisplayIndex && columnDataIndex < rowData[rowDataIndex].Length; j++, columnDataIndex++) { DataGridColumn column = ColumnFromDisplayIndex(j); this.CurrentCell = new DataGridCellInfo(Items[i], column); this.BeginEdit(); column.OnPastingCellClipboardContent(Items[i], rowData[rowDataIndex][columnDataIndex]); this.CommitEdit(DataGridEditingUnit.Row, true); } } if (rowData.Count > 1) { break; } } }
/// We don't provide default Paste but this public method is exposed to help custom implementation of Paste
        /// 
        /// This method stores the cellContent into the item object using ClipboardContentBinding.
        /// 
        /// <param name="item" />
        /// <param name="cellContent" />
        public virtual void OnPastingCellClipboardContent(object item, object cellContent)
        {
            Binding binding = ClipboardContentBinding as Binding;
            if (binding != null)
            {
                // Raise the event to give a chance for external listeners to modify the cell content
                // before it gets stored into the cell
                if (PastingCellClipboardContent != null)
                {
                    DataGridCellClipboardEventArgs args = new DataGridCellClipboardEventArgs(item, this, cellContent);
                    PastingCellClipboardContent(this, args);
                    cellContent = args.Content;
                }

                // Event handlers can cancel Paste of a cell by setting its content to null
                if (cellContent != null)
                {
                    SetTemplateBoundValue(item, binding.Path.Path.Split('.'), cellContent);
                    ////FrameworkElement fe = new FrameworkElement();
                    ////fe.DataContext = item;
                    ////fe.SetBinding(CellValueProperty, binding);
                    ////fe.SetValue(CellValueProperty, cellContent);
                    ////BindingExpression be = fe.GetBindingExpression(CellValueProperty);
                    ////be.UpdateSource();
                }
            }
        }

        /// 
        /// Method to set the value of a property (path) in the datacontext of grid row.
        /// 
        /// <param name="obj" />The context object.
        /// <param name="pathParts" />The path parts of property to fetch value from.
        /// <param name="value" />The new value to be set for the property.
        private void SetTemplateBoundValue(object obj, string[] pathParts, object value)
        {
            if (pathParts.Length == 1)
            {
                if (obj.GetType().GetProperty(pathParts[0]) == null)
                {
                    return;
                }

                if (obj.GetType().GetProperty(pathParts[0]).PropertyType == typeof(string))
                {
                    obj.GetType().GetProperty(pathParts[0]).SetValue(obj, value, null);
                }
            }
            else
            {
                SetTemplateBoundValue(
                    obj.GetType().GetProperty(pathParts[0]).GetValue(obj, null),
                    string.Join(".", pathParts, 1, pathParts.Length - 1).Split('.'),
                    value);
            }
        }
------------------------------------------------------
Now This Behaviour is used to enable delete option on the grid cells
/// 
    /// DataGridCellChangeBehavior class.
    /// 
    public sealed class DataGridDeleteCellBehaviour
    {
        /// 
        /// Dependency member for selectionChangedProperty.
        /// 
        private static readonly DependencyProperty CellDeleteEnabledProperty =
                DependencyProperty.RegisterAttached(
                                "CellDeleteEnabled",
                                typeof(bool),
                                typeof(DataGridDeleteCellBehaviour),
                                new UIPropertyMetadata(DataGridDeleteCellBehaviour.CellDeleteEnabled));

        /// 
        /// Prevents a default instance of the DataGridDeleteCellBehaviour class from being created.
        /// 
        private DataGridDeleteCellBehaviour()
        {
        }

        /// 
        /// Get teh IsBroughtIntoViewWhenSelected.
        /// 
        /// <param name="target" />DataGridRow to it applies.
        /// IsBroughtIntoViewWhenSelectedProperty value.
        public static bool GetCellDeleteEnabled(DependencyObject target)
        {
            return (bool)target.GetValue(CellDeleteEnabledProperty);
        }

        /// 
        /// Method for SetCellValueChanged.
        /// 
        /// <param name="target" />Undocumented parameter.
        /// <param name="value" />Undocumented parameter2.
        public static void SetCellDeleteEnabled(DependencyObject target, bool value)
        {
            target.SetValue(CellDeleteEnabledProperty, value);
        }

        /// 
        /// Method for SelectedItemChanged.
        /// 
        /// <param name="target" />Undocumented parameter.
        /// <param name="e" />Undocumented parameter2.
        /// Thrown when  element is null.
        private static void CellDeleteEnabled(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            DataGrid element = target as DataGrid;

            if (element == null)
            {
                throw new InvalidOperationException("This behavior can be attached to DataGrid item only.");
            }

            if ((bool)e.NewValue && !(bool)e.OldValue)
            {
                element.KeyUp += OnGridKeyUp;
            }
            else if (!(bool)e.NewValue && (bool)e.OldValue)
            {
                element.KeyUp -= OnGridKeyUp;
            }
        }

        /// 
        /// Gets the visual child of the type T.
        /// 
        /// Type of the control that need to be searched.
        /// <param name="parent" />Control in which search has to be performed.
        /// Control of type T if exists in the parent, else default value for the type T.
        private static T GetVisualChild(Visual parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
                child = v as T;
                if (child == null)
                {
                    child = GetVisualChild(v);
                }

                if (child != null)
                {
                    break;
                }
            }

            return child;
        }

        /// 
        /// Event call for KeyDown.
        /// 
        /// <param name="sender" />Sender object.
        /// <param name="e" />KeyEventArgs object.
        private static void OnGridKeyUp(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == Key.Delete)
            {
                for (int i = 0; i < ((DataGrid)sender).SelectedCells.Count; i++)
                {
                    if (((DataGrid)sender).SelectedCells[i].Column.IsReadOnly)
                    {
                        continue;
                    }

                    if (((DataGrid)sender).SelectedCells[i].Column.ClipboardContentBinding != null)
                    {
                        string propertyName =
                            ((System.Windows.Data.Binding)
                             ((DataGrid)sender).SelectedCells[i].Column.ClipboardContentBinding).Path.Path;
                        if (!string.IsNullOrEmpty(propertyName))
                        {
                            ((DataGrid)sender).CurrentCell = ((DataGrid)sender).SelectedCells[i];
                            ((DataGrid)sender).BeginEdit();
                            SetTemplateBoundValue(((DataGrid) sender).SelectedCells[i].Item, propertyName.Split('.'), string.Empty);
                            ((DataGrid) sender).CommitEdit(DataGridEditingUnit.Row, true);
                        }
                    }
                }
            }
        }

        /// 
        /// Method to set the value of a property (path) in the datacontext of grid row.
        /// 
        /// <param name="obj" />The context object.
        /// <param name="pathParts" />The path parts of property to fetch value from.
        /// <param name="value" />The new value to be set for the property.
        private static void SetTemplateBoundValue(object obj, string[] pathParts, object value)
        {
            if (pathParts.Length == 1)
            {
                if (obj.GetType().GetProperty(pathParts[0]) == null)
                {
                    return;
                }

                if (obj.GetType().GetProperty(pathParts[0]).PropertyType == typeof(string))
                {
                    obj.GetType().GetProperty(pathParts[0]).SetValue(obj, value, null);
                }
            }
            else
            {
                SetTemplateBoundValue(
                    obj.GetType().GetProperty(pathParts[0]).GetValue(obj, null),
                    string.Join(".", pathParts, 1, pathParts.Length - 1).Split('.'),
                    value);
            }
        }
    }
Feb 6, 2010 at 6:27 PM

Hello,

What exactly should I do to enable paste capability? It's matter of some binding (eg. to ApplicationCommands.Paste) or I should write some event handler ?