在给定 space 中显示 X 个图片框的最佳尺寸

Optimal sizes to display X picture boxes in an given space

我在发帖前进行了搜索,但找不到与我的问题相近的内容。

我需要弄清楚的是如何获得图片框的最佳宽度和高度(4:3 比例),给定需要显示的框数,以及可用的 space。

现在,它不像将可用 space 除以所需框数那么简单,因为可用 space 不是统一的形状,而是两个大小相同的矩形可能会有所不同(参见这张图片,它是 a+b space)。

如果是这样,我尝试从那里开始使用以下代码:

    Private Sub LayoutSnapshots()
    Dim lTotalSpace As Single, lSnapsize As Single, sXSize As Single, sYSize As Single
    Dim I As Integer, J As Integer, X As Integer = 0, Y As Integer = 0, oPic As PictureBox

    ' bSnaps is the number of picture boxes to be displayed
    If stSetting.bSnaps = 0 Then Exit Sub

    ' oSnaps is a List(Of PictureBoxe) to groupp the actual picture boxes
    If oSnaps.Count > 0 Then
        For Each oCtrl As PictureBox In oSnaps
            Me.Controls.Remove(oCtrl)
        Next
    End If
    oSnaps.Clear()

    ' Calculating the a+b space shown on the picture
    lTotalSpace = ((Me.ClientSize.Height - MenuStrip1.Height) * Me.ClientSize.Width) - ((picPreview.Width + iMargin) * (picPreview.Height + iMargin))

    If lTotalSpace < 1 Then
        MsgBox("Window is too small. Please adjust one of these settings : Window size, Snapshots count, Live free view size.", MsgBoxStyle.ApplicationModal Or MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly)
        Exit Sub
    End If

    'calculating a single picture's size by dividing total space by the number of snaps
    lSnapsize = Math.Truncate(lTotalSpace / stSetting.bSnaps)

    'Calculating Height and Width, with 4:3 ratio
    sXSize = Math.Truncate(Math.Sqrt((4 * lSnapsize) / 3))
    sYSize = Math.Truncate(Math.Sqrt((3 * lSnapsize) / 4))

    For I = 1 To stSetting.bSnaps
        If oPic IsNot Nothing Then oPic = Nothing
        oPic = New PictureBox
        oPic.BackColor = Color.White
        oPic.BorderStyle = BorderStyle.FixedSingle
        oPic.Size = New Size(sXSize - 1, sYSize - 1)
        oPic.Location = New Point(X * sXSize, (Y * sYSize) + MenuStrip1.Height)

        oSnaps.Add(oPic)
        ' Layed them successively on screen, need to optimize this
        If ((X + 2) * sXSize) > (Me.ClientSize.Width) Then
            X = 0
            Y += 1
        Else
            X += 1
        End If
    Next

    For Each oCtrl As PictureBox In oSnaps
        Me.Controls.Add(oCtrl)
    Next
End Sub

但很明显,windows 调整大小的所有可能性,我想不出任何实用的方法来优化它。

我很确定这与 "operation research" 有关,因为我记得我还是学生的时候我们做过这样的优化问题,但我不确定如何实际建模甚至如果它可以通过线性规划解决。

我已经弄明白了。该解决方案是一种 "brute force" 技术,它并不总是 return 最佳,但误差仅为几个像素。我使用了下面的代码,它可以工作,但它可能需要在间距方面进一步优化。由于现在时间紧迫,我无法评论所有内容,但仍然想分享答案,所以请花一些时间来分析它:

Private Sub LayoutSnapshots()

    Dim sA As Single, sB As Single, sTotal As Single, sSnap As Single, sWidth As Single, sHeight As Single
    Dim iCount As Integer = stSetting.bSnaps, iFit As Integer, iX As Integer, iY As Integer, iYg As Integer, I As Integer
    Dim rA As Rectangle, rB As Rectangle, oPic As PictureBox, lpLoc As New List(Of Point), pLoc As New Point

    Static bWarn As Boolean

    Dim gPic As Graphics

    ' bSnaps is the number of picture boxes to be displayed
    If stSetting.bSnaps = 0 Then Exit Sub

    ' If controls already on form, remove them and start form scratch
    If oSnaps.Count > 0 Then
        For Each oCtrl As PictureBox In oSnaps
            Me.Controls.Remove(oCtrl)
        Next
    End If

    ' oSnaps is a List(Of PictureBox) grooping the picture boxes. Clear it for now
    oSnaps.Clear()

    'sA, sB are the sizes of spaces A and B respectively
    sA = (Me.ClientSize.Width * (Me.ClientSize.Height - (MenuStrip1.Height + picPreview.Height + iMargin)))
    sB = ((Me.ClientSize.Width - (picPreview.Width + iMargin)) * (picPreview.Height + iMargin))

    ' Total free space
    sTotal = sA + sB

    ' This condition is important. It ensures there is at least one solution
    ' before entering the loops bellow. Otherwise we might get stuck in an infinite loop
    If (sTotal < (stSetting.bSnaps * stSetting.bSnaps)) Then

        ' bWarn is a static boolean. Since this Sub is called from Form_Resize event, we 
        ' want to warn the user only once when there is no space. 
        ' Otherwise it becomes annoying.

        If bWarn Then MsgBox("Window is too small. Please adjust one of these settings : Window size, Snapshots count, Live free view size.", MsgBoxStyle.ApplicationModal Or MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly)
        bWarn = False
        Exit Sub
    End If

    bWarn = True

    Me.UseWaitCursor = True

    Do

        'rA, rB are the bounding rectangles of spaces A and B respectively
        rA = New Rectangle(0, MenuStrip1.Height, Me.ClientSize.Width, Me.ClientSize.Height - (MenuStrip1.Height + picPreview.Height + iMargin))
        rB = New Rectangle(0, picPreview.Top, Me.ClientSize.Width - (picPreview.Width + iMargin), picPreview.Height + iMargin)

        ' A single box's size
        sSnap = Math.Truncate(sTotal / iCount)

        ' Width and Height with 4:3 aspect ratio.
        sWidth = Math.Truncate(Math.Sqrt((4 * sSnap) / 3))
        sHeight = Math.Truncate(Math.Sqrt((3 * sSnap) / 4))

        ' iFit keeps track of how many boxes we could fit in total
        iFit = 0
        iYg = 0
        lpLoc.Clear()

        ' It would be a bit too long to explain the next block of code and I have a deadline to meet
        ' I'll comenting on that later
        iX = 0
        iY = 0
        Do While (rA.Height >= ((sHeight * (iY + 1)) + 1))
            If (((iX + 1) * sWidth) + 1) <= rA.Width Then
                iFit += 1
                lpLoc.Add(New Point(rA.X + ((iX * sWidth) + 1), rA.Y + ((iYg * sHeight) + 1)))
                iX += 1
            Else
                iX = 0
                iY += 1
                iYg += 1
            End If
        Loop

        'Add unused space from A to B.
        rB.Height = rB.Height + (rA.Height - ((iYg * sHeight) + 1))

        iX = 0
        iY = 0
        Do While (rB.Height >= ((sHeight * (iY + 1)) + 1))
            If (((iX + 1) * sWidth) + 1) <= rB.Width Then
                iFit += 1
                lpLoc.Add(New Point(rB.X + ((iX * sWidth) + 1), rA.Y + ((iYg * sHeight) + 1)))
                iX += 1
            Else
                iX = 0
                iY += 1
                iYg += 1
            End If
        Loop

        Application.DoEvents()

        iCount += 1

    Loop While iFit < stSetting.bSnaps

    ' Add controls to form. Lay them one next to each other.
    iX = 0
    iY = 0
    For I = 1 To stSetting.bSnaps
        If oPic IsNot Nothing Then oPic = Nothing
        oPic = New PictureBox

        oPic.BackColor = Color.Cyan
        oPic.BorderStyle = BorderStyle.FixedSingle
        oPic.Size = New Size(sWidth - 1, sHeight - 1)
        oPic.Location = lpLoc(I - 1)

        ' Just for debugging, displays index of each box inside it.
        oPic.Image = New Bitmap(oPic.Width, oPic.Height)
        gPic = Graphics.FromImage(oPic.Image)
        gPic.DrawString(I, New Font("Arial", 10, FontStyle.Regular), Brushes.Red, New Point(0, 0))

        oSnaps.Add(oPic)
        Me.Controls.Add(oSnaps.Last)
    Next

    'Catch Ex As Exception

    'Finally
    Me.UseWaitCursor = False
    'End Try
End Sub

P.S : 如果您愿意,请随时为代码添加更多解释。