在WPF网格中,如何找到鼠标位置的行和列?

10

我有一个WPF Grid,其中有一些行和列,例如:

<Grid Name="myGrid" MouseMove="OnMouseMove">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
</Grid>

在.cs文件中使用MouseMove处理程序,例如:

private void OnMouseMove(object sender, MouseEventArgs e)
{
    var position = e.GetPosition(myGrid);

    // What row & col is the mouse over?
}

我想要能够找到鼠标悬停在哪一行和哪一列的格子上,这个可能吗?

[注意:这只是问题的简化版本,所以看起来有些奇怪——它是某些网格之间拖放功能的一部分]


请查看FluidKit中的拖放功能。您将不必担心像这样的hacky shnit。 - user1228
我已经在使用FluidKit,其中'MouseMove'实际上是'OnDropCompleted',而鼠标位置实际上是'dropPoint' - 我想能够将其放置到表格的特定单元格中,但我还没有成功。 - Wilka
8个回答

19

我希望你已经找到了解决方案。我也遇到了同样的问题,并发现这篇未回答的帖子。所以我想下一个人会很高兴找到这个解决方案:

private void OnMouseMove(object sender, MouseEventArgs e)
{
    var element = (UIElement)e.Source;

    int c = Grid.GetColumn(element);
    int r = Grid.GetRow(element);
}

6
只有在每个单元格中都有UI元素时,此方法才有效。因此,这不是对所提问题的答案。 - GreenEyedAndy
为了解决这个问题,您可以只需为每一行添加一个透明边框,并将其列跨度设置为该行中的总列数。点击测试将获得该边框,它将在所有单元格上工作,无论它们是否已填充。如果需要,这还将方便通过将透明边框的背景更改为颜色来突出显示行特征。 - Krythic

5
以下操作可以实现(但不应在较大的网格上使用)。它仅适用于行,但可以对列应用第二个循环...
Nicolas的答案仅在网格包含一些UI元素时有效(这些元素的位置在网格中确定-我猜如果一个元素跨越多行/列,那也会引起问题)。
    private void ItemsGrid_MouseMove(object sender, MouseEventArgs e)
    {
        double y = e.GetPosition(ItemsGrid).Y;
        double start = 0.0;
        int row = 0;
        foreach(RowDefinition rd in ItemsGrid.RowDefinitions)
        {
            start += rd.ActualHeight;
            if (y < start)
            {
                break;
            }
            row++;
        }
        System.Diagnostics.Debug.WriteLine("Row : " + row);
    }

2
public static class GridExtentions
{
    public static T Parent<T>(this DependencyObject root) where T : class
    {
        if (root is T) { return root as T; }

        DependencyObject parent = VisualTreeHelper.GetParent(root);
        return parent != null ? parent.Parent<T>() : null;
    }

    public static Point GetColumnRow(this Grid obj, Point relativePoint) { return new Point(GetColumn(obj, relativePoint.X), GetRow(obj, relativePoint.Y)); }
    private static int GetRow(Grid obj, double relativeY) { return GetData(obj.RowDefinitions, relativeY); }
    private static int GetColumn(Grid obj, double relativeX) { return GetData(obj.ColumnDefinitions, relativeX); }

    private static int GetData<T>(IEnumerable<T> list, double value) where T : DefinitionBase
    {
        var start = 0.0;
        var result = 0;

        var property = typeof(T).GetProperties().FirstOrDefault(p => p.Name.StartsWith("Actual"));
        if (property == null) { return result; }

        foreach (var definition in list)
        {
            start += (double)property.GetValue(definition);
            if (value < start) { break; }

            result++;
        }

        return result;
    }
}

使用方法:

protected override void OnMouseDown(MouseButtonEventArgs e)
{
    base.OnMouseDown(e);
    var hit = VisualTreeHelper.HitTest(this, e.GetPosition(this));
    if (hit == null) { return; }

    var grid = hit.VisualHit.Parent<Grid>();
    if (grid == null) { return; }

    var gridPosition = grid.GetColumnRow(e.GetPosition(grid));
    MessageBox.Show(string.Format("Grid location Row: {1} Column: {0}", gridPosition.X, gridPosition.Y));
}

1

将Grid.ColumnDefinitions替换为对Grid组件的引用

int GetColumn(double point)
{
   int index = 0;
   foreach(var column in Grid.ColumnDefinitions)
   {
      if(point > column.Offset && point < (column.Offset + column.ActualWidth))
          return index;
      index++;
   }
   return 0;
}

1
希望能帮到你。对我的应用程序来说,它运行得很好。
        public class GridCell
        {
            public int GridRow { get; set; }
            public int GridCol { get; set; }
        }

        private void Window_MouseMove(object sender, MouseEventArgs e)
        {
            Point point = e.GetPosition(this.myGrid);

            Grid gridTarget = GetLastedGridContainPoint(point, this.myGrid);

            Point pointTarget = this.myGrid.TranslatePoint(point, gridTarget);

            GridCell cell = GetGridCellContainPoint(pointTarget, gridTarget);
        }

        private bool IsPointInGrid(Point relativePoint, Grid grid)
        {
            if (relativePoint.X < 0 || relativePoint.X > grid.ActualWidth ||
                relativePoint.Y < 0 || relativePoint.Y > grid.ActualHeight)
            {
                return false;
            }

            return true;
        }

        private Grid GetLastedGridContainPoint(Point relativePoint, Grid gridParent)
        {
            Grid gridReturn = null;

            if (gridParent.Children.Count > 0)
            {
                Point relativeChildPoint;
                foreach (UIElement e in gridParent.Children)
                {
                    if (e is Grid)
                    {
                        relativeChildPoint = gridParent.TranslatePoint(relativePoint, (e as Grid));
                        gridReturn = GetLastedGridContainPoint(relativeChildPoint, (e as Grid));

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

            if (gridReturn == null)
            {
                if (IsPointInGrid(relativePoint, gridParent))
                {
                    gridReturn = gridParent;
                }
            }

            return gridReturn;
        }

        private GridCell GetGridCellContainPoint(Point relativePoint, Grid gridTarget)
        {
            if (!IsPointInGrid(relativePoint, gridTarget))
            {
                return null;
            }

            GridCell cell = new GridCell();
            double dbStart = 0;
            double dbEnd = 0;

            if (gridTarget.ColumnDefinitions.Count > 0)
            {
                for (int i = 0; i < gridTarget.ColumnDefinitions.Count; i++)
                {
                    dbStart = dbEnd;
                    dbEnd += gridTarget.ColumnDefinitions[i].ActualWidth;

                    if (relativePoint.X >= dbStart && relativePoint.X < dbEnd)
                    {
                        cell.GridCol = i;
                        break;
                    }
                }
            }

            dbStart = 0;
            dbEnd = 0;

            if (gridTarget.RowDefinitions.Count > 0)
            {
                for (int i = 0; i < gridTarget.RowDefinitions.Count; i++)
                {
                    dbStart = dbEnd;
                    dbEnd += gridTarget.RowDefinitions[i].ActualHeight;

                    if (relativePoint.Y >= dbStart && relativePoint.Y < dbEnd)
                    {
                        cell.GridRow = i;
                        break;
                    }
                }
            }

            return cell;
        }

0
对于Telerik RadGridView,如果网格不包含UI元素,则最好的方法是在带有IsMouseOver的Linq表达式中使用ChildrenOfType<>。
 private void myGridView_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
            {
                MyCustomClass myClass = null;

                var rows = this.myGridView.ChildrenOfType<GridViewRow>().Where(r => r.IsMouseOver == true);
                foreach (var row in rows)
                {
                    if (row is GridViewNewRow) break;
                    GridViewRow gvr = (GridViewRow)row;
                    myClass = (MyCustomClass)gvr.Item;
                } 
// do something with myClass here if we have found a row under mouse
}

0

我曾经有过和你完全相同的问题,而且我也在使用 FluidKit。我正在尝试构建一个表单设计器,在这个设计器中,您可以从工具箱中拖动控件并将其放入网格单元格中。以下是我的解决方法:

我创建了一个带有两个虚拟矩形的网格,这些矩形位于第一行:

        <Grid Name="myCanvas" ShowGridLines="True" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Rectangle DragDrop:DragDropManager.DropTargetAdvisor="{StaticResource targetAdvisor1}" 
                       Grid.Row="0" Grid.Column="0" Width="200" Height="100" Fill="Blue" Stroke="Black" StrokeThickness="4" />
            <Rectangle DragDrop:DragDropManager.DropTargetAdvisor="{StaticResource targetAdvisor2}"
                Grid.Row="0" Grid.Column="1" Width="200" Height="100" Fill="Red" Stroke="Black" StrokeThickness="4" />
        </Grid>

它看起来如下:

Grid

注意,我为每个矩形定义了一个DropTargetAdvisor,现在逻辑如下:
  • 您从工具箱中拖放控件到单元格中
  • DropTargetAdvisor中的OnDropCompleted方法将删除您放置控件的矩形,并从Grid.Row和Grid.Column附加属性中获取其坐标。
  • 一旦您获得了坐标,就可以将这些附加属性设置为您拖动的控件并将其添加到网格中。
这是我的DefaultDropTargetAdvisor.OnDropCompleted方法:
public void OnDropCompleted(IDataObject obj, Point dropPoint)
    {
        UIElement dragged_control = ExtractElement(obj);
        //Get the current Rectangle
        FrameworkElement fe = TargetUI as FrameworkElement;
        //Get parent Grid from this Rectangle
        Grid g = fe.Parent as Grid;
        //Get row and columns of this Rectangle
        int row = Grid.GetRow(TargetUI);
        int col = Grid.GetColumn(TargetUI);
        //Remove Rectangle
        g.Children.Remove(TargetUI);
        //Set row and column for the dragged control
        Grid.SetRow(dragged_control, row);
        Grid.SetColumn(dragged_control, col);
        //Add dragged control to Grid in that row/col
        g.Children.Add(dragged_control);
    }

0

Andre的答案有一个小错误,因为所获得的坐标没有考虑DataGrid中的行和列标题。至少在我用Visual Basic实现解决方案时是这样的。

您还可以修改所示示例以适应大型DataGrid。在我看来,限制取决于滚动视图,因此我展示了两种实现此更正的方法:

Private Sub myGrid_MouseMove(sender As Object, e As MouseEventArgs) Handles myGrid.MouseMove
    Dim total As Double
    Dim myScrollViewer As ScrollViewer = FindVisualChild(Of ScrollViewer)(myGrid)

    Dim cursorPositionX = e.GetPosition(myGrid).X 
    Dim columnIndex As Integer = -1
    total = 0

    'Horizontal offset'        
    Dim rowHeaders As DataGridRowHeader = FindVisualChild(Of DataGridRowHeader)(myGrid)
    cursorPositionX -= (rowHeaders.ActualWidth - myScrollViewer.HorizontalOffset)

    For Each column As DataGridColumn In myGrid.Columns
        If cursorPositionX < total Then Exit For

        columnIndex += 1
        total += column.Width.DisplayValue
    Next

    Dim cursorPositionY = e.GetPosition(myGrid).Y  
    Dim rowIndex As Integer = -1
    total = 0

    'Vertical offset'
    Dim originalOffset As Double = myScrollViewer.VerticalOffset
    Dim colHeadersPresenter As DataGridColumnHeadersPresenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(myGrid)
    cursorPositionY -= colHeadersPresenter.ActualHeight

    For Each row As System.Data.DataRowView In myGrid.Items
        If cursorPositionY < total Then Exit For

        rowIndex += 1
        Dim dgRow As DataGridRow = GetRowByIndex(myGrid, rowIndex)
        total += dgRow.ActualHeight

        'GetRowByIndex will scroll the view to bring the DataGridRow of interest into view, which throws off the counter. This adjusts for that'
        myGrid.UpdateLayout()
        If Not myScrollViewer.VerticalOffset = originalOffset Then myGrid.ScrollIntoView(myGrid.Items(CInt(myScrollViewer.ViewportHeight + originalOffset - 1)))
        myGrid.UpdateLayout()

        If myScrollViewer.VerticalOffset > rowIndex Then cursorPositionY += dgRow.ActualHeight
    Next
End Sub

请注意,ScrollViewer.HorizontalOffset Property 返回设备独立像素值,因此在循环列之前,我只需偏移一次我的位置。
请注意,如果 CanContentScroll = True,则 ScrollViewer.VerticalOffset Property 返回项目数。因此,在每个循环中,我将计数器偏移一个项目的高度(DataGridRow)。如果 CanContentScroll = False,则可以处理为列索引循环的情况。
我在 Visual Basic 的 .Net 4.0 DataGrid 中找不到行定义,但以下支持函数有助于获取 DataGridRow:
Function GetRowByIndex(ByVal p_dataGrid As DataGrid,
                       ByVal p_index As Integer) As DataGridRow
    Dim row As DataGridRow

    row = CType(p_dataGrid.ItemContainerGenerator.ContainerFromIndex(p_index), DataGridRow)
    If IsNothing(row) Then
        'May be virtualized, bring into view and try again.'
        p_dataGrid.UpdateLayout()
        p_dataGrid.ScrollIntoView(p_dataGrid.Items(p_index))
        row = CType(p_dataGrid.ItemContainerGenerator.ContainerFromIndex(p_index), DataGridRow)

    End If
    Return row
End Function

在 Visual Basic 中,FindVisualChild 函数如下:

Function FindVisualChild(Of childItem As DependencyObject)(ByVal p_obj As DependencyObject) As childItem
    For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(p_obj) - 1
        Dim child As DependencyObject = VisualTreeHelper.GetChild(p_obj, i)
        If child IsNot Nothing AndAlso TypeOf child Is childItem Then
            Return CType(child, childItem)
        Else
            Dim childOfChild As childItem = FindVisualChild(Of childItem)(child)
            If childOfChild IsNot Nothing Then
                Return childOfChild
            End If
        End If
    Next i
    Return Nothing
End Function

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接