如何根据 X、Y 坐标以编程方式确定表单上字段的选项卡索引?

Posted

技术标签:

【中文标题】如何根据 X、Y 坐标以编程方式确定表单上字段的选项卡索引?【英文标题】:How to programmatically determine tab index of fields on a form based on their X,Y coordinates? 【发布时间】:2021-08-20 21:01:42 【问题描述】:

我需要根据页面上的绝对 X、Y 坐标找出 html 表单字段的正确跳格顺序。我们使用它们所在的div(页面容器)左下角的X、Y。

例如,在下图中,框内的数字表示我期望的最终tabIndex 顺序,因为如果框在 Y 轴上完全重叠,则结果是;最低的 X 轴值将获胜,而 Y 轴根本无关紧要。如果没有重叠,则 Y 轴值最高。

上下文

基本上在填写 PDF 表单时,即使框 #3 比框 #2 高一点,自然标签索引也应该从左到右;你仍然想从左到右填写它。但是,由于框 #1 与其他框位于完全不同的 X 平面上(无论它是否比其他框更靠右),在填写表格时,它在逻辑上仍应位于其他框之前。你不会过去然后向上。

字段位于具有 X 和 Y 属性的 C# 对象中。 (下面是伪代码)

var fields = new List<TestFieldModel>()

    new TestFieldModel()
    
        ExpectedOrderNumberResult = 3,
        PageNumber = 1,
        X = 7,
        Y = 6,
        Width = 5,
        Height = 2
    ,
    new TestFieldModel()
    
        ExpectedOrderNumberResult = 4,
        PageNumber = 1,
        X = 14,
        Y = 4,
        Width = 5,
        Height = 3
    ,
    new TestFieldModel()
    
        ExpectedOrderNumberResult = 1,
        PageNumber = 1,
        X = 17,
        Y = 9,
        Width = 5,
        Height = 3
    ,
    new TestFieldModel()
    
        ExpectedOrderNumberResult = 2,
        PageNumber = 1,
        X = 2,
        Y = 5,
        Width = 4,
        Height = 2
    
;

【问题讨论】:

根据什么规则,您决定了这个顺序?为什么“1”是第一个,为什么“2”是第二个,“3”是第三个? 根据最高 Y 值规则,“2”应该排在第三位,“3”应该排在第二位吗? @TcKs 如果框在 Y 轴上完全重叠,则最低 X 轴值获胜,Y 轴根本无关紧要。如果没有重叠,则最高 Y 轴值获胜。基本上在填写 PDF 表单时,自然标签索引应该从左到右,即使框 #3 比框 #2 高一点,您仍然希望从左到右填写。但是,由于框 #1 与其他框位于完全不同的 X 平面上(无论它是否比其他框更靠右),在填写表格时,它在逻辑上仍应位于其他框之前。你不会过去然后向上。 @RichC 然后您需要在第 1 步中创建重叠的框组。按 Y 轴对组进行排序,然后按 X 轴对组中的框进行排序。 @TcKs 是的,我想我必须做这样的事情,但我希望可能已经有一些公式可以用我可以订购的数字结果从数学上计算出来。 【参考方案1】:

我的回答与yours 非常相似。主要区别是

不使用布尔值打破两个循环。 在“双重休息”场景中,我更喜欢被诽谤的goto。这是主观的,但对我来说更清楚,因为它避免了外循环中的检查。 用Math.Min/Max 替换关于重新计算组边界的if 语句。这种速记更清楚地表达了您的意图。 为每个字段调用Group.Add();如果您有一个空列表可供使用,则无需以不同方式初始化组中的字段列表

伪 C#:

foreach (var f in fields)

   foreach (var g in groups)
   
      if (g.VerticallyOverlapsWith(f))
      
          g.Add(f);
          goto NEXT_FIELD;
      
   
   
   // no overlap detected, so make a new group
   var newGroup = new Group();
   newGroup.Add(f);   
   groups.Add(newGroup)
   
   NEXT_FIELD :;


class Group

   void AddField(Field f)
   
       _group.Add(f);
       _yTop = Max(f.Top, _yTop);
       _yBottom = Min(f.Bottom, _yBottom);
   
   
   List<Field> _group = new List<Field>();
   int _yTop = int.MinValue();
   int _yBottom = int.MaxValue();

此时您已经有了自己的小组。您现在必须对组进行降序排序,然后按字段升序 (which you have done)。

几个设计点。

这不会解决与多个组中的字段重叠的字段,从而导致不必要的大组。如果你这样做,你的标签顺序可能会有点出乎意料(从用户的角度来看)。如果你期待这些 奇怪的重叠最好使用聚类算法或“容差”函数,而不是简单的“与第一个重叠组重叠”函数(VerticallyOverlapsWith 是后者)。 您可以通过在每种情况下将添加插入到有序集合中来避免最后的排序步骤。

【讨论】:

【参考方案2】:

好的,我就是这样解决这个问题的:

    [Fact]
    public void GetFieldsOrderTest()
    
        var fields = new List<TestFieldModel>()
                     
                         new TestFieldModel()
                         
                             ExpectedOrderNumberResult = 3,
                             PageNumber = 1,
                             X = 7,
                             Y = 6,
                             Width = 5,
                             Height = 2
                         ,
                         new TestFieldModel()
                         
                             ExpectedOrderNumberResult = 4,
                             PageNumber = 1,
                             X = 14,
                             Y = 4,
                             Width = 5,
                             Height = 3
                         ,
                         new TestFieldModel()
                         
                             ExpectedOrderNumberResult = 1,
                             PageNumber = 1,
                             X = 17,
                             Y = 9,
                             Width = 5,
                             Height = 3
                         ,
                         new TestFieldModel()
                         
                             ExpectedOrderNumberResult = 2,
                             PageNumber = 1,
                             X = 2,
                             Y = 5,
                             Width = 4,
                             Height = 2
                         
                     ;

        var pageCount = 1;
        for (var i = 1; i <= pageCount; i++)
        
            var groups = new List<Group>();
            foreach (var field in fields.Where(f => f.PageNumber == i))
            
                var needsNewGroup = true;
                foreach (var group in groups)
                
                    var fieldTop = field.Y + field.Height;
                    var fieldBottom = field.Y;
                    if ((fieldTop <= group.Top && fieldTop >= group.Bottom) || (fieldBottom >= group.Bottom && fieldBottom <= group.Top))
                    
                        if (fieldTop > group.Top)
                        
                            group.Top = fieldTop;
                        
                        if (fieldBottom < group.Bottom)
                        
                            group.Bottom = fieldBottom;
                        
                        group.GroupFields.Add(field);
                        needsNewGroup = false;
                        break;
                    
                

                if (needsNewGroup)
                
                    var group = new Group
                                
                                    Top = field.Y + field.Height,
                                    Bottom = field.Y,
                                    GroupFields = new List<TestFieldModel>
                                                  
                                                      field
                                                  
                                ;
                    groups.Add(group);
                
            

            var groupFields = groups.OrderByDescending(g => g.Top).Select(g => g.GroupFields.OrderBy(gf => gf.X).ToList()).ToList();

            fields = groupFields.Aggregate((acc, list) => acc.Concat(list).ToList());

            fields.Should().HaveCount(4, "Count is not 4: " + fields.Count);
            fields[0].ExpectedOrderNumberResult.Should().Be(1, $"Expected 1.  Got fields[0].ExpectedOrderNumberResult.");
            fields[1].ExpectedOrderNumberResult.Should().Be(2, $"Expected 2.  Got fields[1].ExpectedOrderNumberResult.");
            fields[2].ExpectedOrderNumberResult.Should().Be(3, $"Expected 3.  Got fields[2].ExpectedOrderNumberResult.");
            fields[3].ExpectedOrderNumberResult.Should().Be(4, $"Expected 4.  Got fields[3].ExpectedOrderNumberResult.");

        
    

    public class Group
    
        public List<TestFieldModel> GroupFields  get; set; 
        public int Top  get; set; 
        public int Bottom  get; set; 
    

    public class TestFieldModel
    

        public int ExpectedOrderNumberResult  get; set; 
        public int PageNumber  get; set; 
        public int Width  get; set; 
        public int Height  get; set; 
        public int X  get; set; 
        public int Y  get; set; 
    

【讨论】:

以上是关于如何根据 X、Y 坐标以编程方式确定表单上字段的选项卡索引?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据屏幕尺寸计算 x,y 坐标

我如何同时传递 x 和 y 轴以及屏幕的高度和宽度以快速以编程方式截取屏幕截图

C# ArcEngine,如何根据已有的两点的XY坐标,把这条线在地图上显示出来?

HTML5: dragover(), drop(): 如何获取当前 x,y 坐标?

使用 OpenCV 如何根据 x 和 y 坐标裁剪图像,并允许 x 和 y 坐标成为裁剪的中心?

(Python) 确定 (x, y) 坐标是不是共线的脚本 - 出现一些错误