在单元格下方显示WinForm

时间:2015-03-30 13:03:53

标签: vb.net excel winforms

如何在活动单元格下方显示我在VB.NET中创建的winform?

我不知道如何解决这个问题。我发现了以下有希望的解决方案 Excel addin: Cell absolute position

- 接受的解决方案似乎太复杂,无法可靠地工作。我在第一行收到错误(私有声明函数GetDC Lib" user32"(ByVal hwnd As Long)As Long)

- 第二个解决方案看起来很有希望,但它并没有为我的窗体提供合适的位置。

第二个提议的解决方案的以下改编不会产生任何错误,但不会将窗体形式放在正确的位置:

    Public Sub GetScreenPositionFromCell(cell As Excel.Range, excel As Excel.Application)

    Dim x As Double
    Dim y As Double
    If Not excel.ActiveWindow Is Nothing Then
        x = excel.ActiveWindow.PointsToScreenPixelsX(cell.Left)
        y = excel.ActiveWindow.PointsToScreenPixelsY(cell.Top)
    End If

    Me.Left = x
    Me.Top = y

    Me.Show()
    Me.TopMost = True
End Sub

编辑:@Loating,以下是我使用您的代码的方法。这很棒,我很高兴你花时间帮我解决问题。 x坐标似乎有效,而x坐标略微偏离,或多或少取决于缩放级别。

    Public Sub ShowMeBelowActiveCell()
        Dim ExcelApp As Excel.Application = CType(AddinExpress.MSO.ADXAddinModule.CurrentInstance, AddinModule).ExcelApp
        Dim excelWindow = ExcelApp.ActiveWindow
        Dim cell = ExcelApp.ActiveCell
        Dim zoomFactor As Double = excelWindow.Zoom / 100
        Dim ws = cell.Worksheet

        ' PointsToScreenPixels returns different values if the scroll is not currently 1
        ' Temporarily set the scroll back to 1 so that PointsToScreenPixels returns a
        ' value we know how to handle.
        Dim origScrollCol = excelWindow.ScrollColumn
        Dim origScrollRow = excelWindow.ScrollRow
        excelWindow.ScrollColumn = 1
        excelWindow.ScrollRow = 1

        ' (x,y) are screen coordinates for the top left corner of the top left cell
        Dim x As Integer = excelWindow.PointsToScreenPixelsX(0)
        ' e.g. window.x + row header width
        Dim y As Integer = excelWindow.PointsToScreenPixelsY(0)
        ' e.g. window.y + ribbon height + column headers height
        Dim dpiX As Single = 0
        Dim dpiY As Single = 0
        Using g = Drawing.Graphics.FromHwnd(IntPtr.Zero)
            dpiX = g.DpiX
            dpiY = g.DpiY
        End Using

        ' Note: Each column width / row height has to be calculated individually.
        ' Before, tried to use this approach:
        ' var r2 = (Microsoft.Office.Interop.Excel.Range) cell.Worksheet.Cells[origScrollRow, origScrollCol];
        ' double dw = cell.Left - r2.Left;
        ' double dh = cell.Top - r2.Top;
        ' However, that only works when the zoom factor is a whole number.
        ' A fractional zoom (e.g. 1.27) causes each individual row or column to round to the closest whole number,
        ' which means having to loop through.
        For i As Integer = origScrollCol To cell.Column - 1
            Dim col = DirectCast(ws.Cells(cell.Row, i), Microsoft.Office.Interop.Excel.Range)
            Dim ww As Double = col.Width * dpiX / 72
            Dim newW As Double = zoomFactor * ww
            x += CInt(Math.Round(newW))
        Next

        For i As Integer = origScrollRow To cell.Row - 1
            Dim row = DirectCast(ws.Cells(i, cell.Column), Microsoft.Office.Interop.Excel.Range)
            Dim hh As Double = row.Height * dpiY / 72
            Dim newH As Double = zoomFactor * hh
            y += CInt(Math.Round(newH))
        Next

        excelWindow.ScrollColumn = origScrollCol
        excelWindow.ScrollRow = origScrollRow

        Me.StartPosition = Windows.Forms.FormStartPosition.Manual
        Me.Location = New Drawing.Point(x, y)
        Me.Show()

    End Sub
End Class

1 个答案:

答案 0 :(得分:1)

ScrollColumnScrollRow均为1时,PointsToScreenPixelsX/Y似乎返回屏幕坐标中左上角可见单元格的左上角。使用此功能,计算活动单元格的偏移宽度和高度,同时考虑缩放设置。

        var excelApp = Globals.ThisAddIn.Application;
        var excelWindow = excelApp.ActiveWindow;

        var cell = excelApp.ActiveCell;
        double zoomFactor = excelWindow.Zoom / 100;
        var ws = cell.Worksheet;

        var ap = excelWindow.ActivePane; // might be split panes
        var origScrollCol = ap.ScrollColumn;
        var origScrollRow = ap.ScrollRow;
        excelApp.ScreenUpdating = false;
        // when FreezePanes == true, ap.ScrollColumn/Row will only reset
        // as much as the location of the frozen splitter
        ap.ScrollColumn = 1;
        ap.ScrollRow = 1;

        // PointsToScreenPixels returns different values if the scroll is not currently 1
        // Temporarily set the scroll back to 1 so that PointsToScreenPixels returns a
        // value we know how to handle.
        // (x,y) are screen coordinates for the top left corner of the top left cell
        int x = ap.PointsToScreenPixelsX(0); // e.g. window.x + row header width
        int y = ap.PointsToScreenPixelsY(0); // e.g. window.y + ribbon height + column headers height

        float dpiX = 0;
        float dpiY = 0;
        using (var g = Graphics.FromHwnd(IntPtr.Zero)) {
            dpiX = g.DpiX;
            dpiY = g.DpiY;
        }

        int deltaRow = 0;
        int deltaCol = 0;
        int fromCol = origScrollCol;
        int fromRow = origScrollRow;
        if (excelWindow.FreezePanes) {
            fromCol = 1;
            fromRow = 1;
            deltaCol = origScrollCol - ap.ScrollColumn; // Note: ap.ScrollColumn/Row <> 1
            deltaRow = origScrollRow - ap.ScrollRow; // see comment: when FreezePanes == true ...
        }

        // Note: Each column width / row height has to be calculated individually.
        // Before, tried to use this approach:
        // var r2 = (Microsoft.Office.Interop.Excel.Range) cell.Worksheet.Cells[origScrollRow, origScrollCol];
        // double dw = cell.Left - r2.Left;
        // double dh = cell.Top - r2.Top;
        // However, that only works when the zoom factor is a whole number.
        // A fractional zoom (e.g. 1.27) causes each individual row or column to round to the closest whole number,
        // which means having to loop through.
        for (int i = fromCol; i < cell.Column; i++) {
            // skip the columns between the frozen split and the first visible column
            if (i >= ap.ScrollColumn && i < ap.ScrollColumn + deltaCol)
                continue;

            var col = ((Microsoft.Office.Interop.Excel.Range) ws.Cells[cell.Row, i]);
            double ww = col.Width * dpiX / 72;
            double newW = zoomFactor * ww;
            x += (int) Math.Round(newW);
        }

        for (int i = fromRow; i < cell.Row; i++) {
            // skip the columns between the frozen split and the first visible column
            if (i >= ap.ScrollRow && i < ap.ScrollRow + deltaRow)
                continue;

            var row = ((Microsoft.Office.Interop.Excel.Range) ws.Cells[i, cell.Column]);
            double hh = row.Height * dpiY / 72;
            double newH = zoomFactor * hh;
            y += (int) Math.Round(newH);
        }

        ap.ScrollColumn = origScrollCol;
        ap.ScrollRow = origScrollRow;
        excelApp.ScreenUpdating = true;

        Form f = new Form();
        f.StartPosition = FormStartPosition.Manual;
        f.Location = new Point(x, y);
        f.Show();