Show popup during editing of WPF DataGrid cell

10,094

I have figured out a solution that works well so far. It involves some event handlers, but the code does not have to access the view model so hopefully it won't incur the wrath of MVVM purists.

XAML:

<Grid>
    <DataGrid ItemsSource="{Binding Path=Options}" BeginningEdit="DataGrid_BeginningEdit" CellEditEnding="DataGrid_CellEditEnding" />
    <Popup Name="pop1">
        <Border Width="300" Height="200" Background="LemonChiffon" BorderThickness="2" BorderBrush="Black" />
    </Popup>
</Grid>

MainWindow.xaml.cs event handlers:

private void DataGrid_BeginningEdit (Object sender, DataGridBeginningEditEventArgs e)
{
    DataGrid grid = (DataGrid) sender;
    Popup pop1 = (Popup) grid.FindName("pop1");
    pop1.PlacementTarget = grid.GetCell(e.Row.GetIndex(), e.Column.DisplayIndex);
    pop1.IsOpen = true;
}

private void DataGrid_CellEditEnding (Object sender, DataGridCellEditEndingEventArgs e)
{
    Popup pop1 = (Popup) ((DataGrid) sender).FindName("pop1");
    pop1.IsOpen = false;
}

DataGridExtensions.cs:

/// <summary>
/// Extension methods for DataGrid
/// These methods are thanks to http://blogs.msdn.com/b/vinsibal/archive/2008/11/05/wpf-datagrid-new-item-template-sample.aspx
/// </summary>
public static class DataGridExtensions
{
    /// <summary>
    /// Returns a DataGridCell for the given row and column
    /// </summary>
    /// <param name="grid">The DataGrid</param>
    /// <param name="row">The zero-based row index</param>
    /// <param name="column">The zero-based column index</param>
    /// <returns>The requested DataGridCell, or null if the indices are out of range</returns>
    public static DataGridCell GetCell (this DataGrid grid, Int32 row, Int32 column)
    {
        DataGridRow gridrow = grid.GetRow(row);
        if (gridrow != null)
        {
            DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(gridrow);

            // try to get the cell but it may possibly be virtualized
            DataGridCell cell = (DataGridCell) presenter.ItemContainerGenerator.ContainerFromIndex(column);
            if (cell == null)
            {
                // now try to bring into view and retreive the cell
                grid.ScrollIntoView(gridrow, grid.Columns[column]);

                cell = (DataGridCell) presenter.ItemContainerGenerator.ContainerFromIndex(column);
            }

            return (cell);
        }

        return (null);
    }

    /// <summary>
    /// Gets the DataGridRow based on the given index
    /// </summary>
    /// <param name="idx">The zero-based index of the container to get</param>
    public static DataGridRow GetRow (this DataGrid dataGrid, Int32 idx)
    {
        DataGridRow row = (DataGridRow) dataGrid.ItemContainerGenerator.ContainerFromIndex(idx);
        if (row == null)
        {
            // may be virtualized, bring into view and try again
            dataGrid.ScrollIntoView(dataGrid.Items[idx]);
            dataGrid.UpdateLayout();

            row = (DataGridRow) dataGrid.ItemContainerGenerator.ContainerFromIndex(idx);
        }

        return (row);
    }

    private static T GetVisualChild<T> (Visual parent) where T : Visual
    {
        T child = default(T);

        Int32 numvisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (Int32 i = 0; i < numvisuals; ++i)
        {
            Visual v = (Visual) VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
                child = GetVisualChild<T>(v);
            else
                break;
        }

        return child;
    }
}
Share:
10,094
Walter Williams
Author by

Walter Williams

I'm a C++ and C# programmer who has been working in WinForms for a long time and now learning WPF for some new projects.

Updated on June 29, 2022

Comments

  • Walter Williams
    Walter Williams almost 2 years

    Along the same lines as How do I place a Popup in my DataGridTemplateColumn.CellEditingTemplate correctly?, I am trying to have a PopUp appear below a cell in a DataGrid when the cell is being edited, and disappear when the cell is no longer being edited. The final thing is that the PopUp contents are dynamic according to the column, and the columns are created dynamically via binding.

    I started out with the following XAML, but I get a XamlParseException "Add value to collection of type 'System.Windows.Controls.ItemCollection' threw an exception".

    <DataGrid ItemsSource="{Binding Path=Options}">
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <Grid>
                        <Popup Placement="Bottom" IsOpen="True" Width="200" Height="100">
                            <TextBlock>Somethingn here</TextBlock>
                        </Popup>
                    </Grid>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
    </DataGrid>
    
  • Walter Williams
    Walter Williams over 10 years
    That seemed to do some weird behavior of adding rows, and did not create my dynamic columns; I am also not looking to replace the normal cell edit behavior with the popup, but have the popup display supplementary info.