Home > Software design >  Position a Winforms dialog directly over a DataGridView cell
Position a Winforms dialog directly over a DataGridView cell

Time:12-08

I have a DataGridView with 2 columns of file-names. I would like to emulate the Windows File Explorer 'Rename' context menu on these file-names. To that end, I created a simple WinForms dialog with no header and a textbox entry for renaming. I display it when a grid's file-name cell is right-clicked. I am trying to position it directly over the cell, but have been unable to get it to display in the correct location. It's down by several rows and to the right by a few character widths. I'm positioning the dialog thusly:

Point location;
void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) {
    var cellRect = dataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
    // Point location = dataGridView.Location;
    location = dataGridView.Bounds.Location;
    location.Offset(cellRect.Location);
    location = dataGridView.PointToScreen(location);
}

async void renameToolStripMenuItem_Click(object sender, EventArgs e) {
    using (var rfd = new RenameFileDialog(fi)) {
      // Lifted from designer
        rfd.ControlBox = false;
        rfd.Text = string.Empty;
        rfd.formBorderStyle = FormBorderStyle.SizableToolWindow;
      // Actually in method
        rfd.StartPosition = FormStartPosition.Manual;
        rfd.Location = location;
        rfd.ShowDialog(dataGridView);
    }
}

I suspect I'm getting tripped up by Location vs ClientRectangle vs Control Bounds or margins or padding, but I haven't been able to identify where the undesired offsets are coming from. Can someone tell me how to position the dialog, or otherwise suggest a way to emulate Explorer's "Rename' in a dataGridView?

CodePudding user response:

The original sin is here:

location = dataGridView.Bounds.Location;

to translate to Screen coordinates the origin point of a Control, using the Control itself as the relative reference, you have to consider its own origin, which is always (0, 0) (Point.Empty).
If you use its Location property, you instead consider the Control's offset in relation to its Parent.
If you then use this measure and call Control.PointToScreen(), you retrieve a position inside the Control's Client area
The offset of a location inside its ClientRectangle, added to this measure, is then of course moved right and down (since the Control's origin is not at (0, 0))

In other words, the Screen coordinates of the origin point of a Control are:

 [Control].PointToScreen(Point.Empty);

As described in Open a Form under a DataGridView, you just need to consider the bounds of the Cell that raised the CellMouseDown event:

    Point location = Point.Empty;
    private void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
    {
        var dgv = sender as DataGridView;
        var cellRect = dgv.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
        location = dgv.RectangleToScreen(cellRect).Location;
    }

As a note, in normal conditions, the coordinates that GetCellDisplayRectangle() returns are moved 7 pixels to the right and 1 pixel down, in relation to a Cell's grid, since it considers the internal bounds
If you want to position your Form over the Cell's grid, you could add:

location.Offset(-7, -1);
  • Related