Алгоритм подбора ширины столбцов

Разговоры на любые темы: вы можете обсудить здесь какой-либо сайт, найти единомышленников или просто пообщаться...
alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Алгоритм подбора ширины столбцов

Сообщение alibek » 11.10.2004 (Пн) 15:15

Камрады, спасайте от умственного истощения :)
Вот функциональная часть. Есть грид, надо реализовать автоматический подбор ширины столбцов в зависимости от ширины грида.
У каждого столбца есть следующие атрибуты:
Min - минимальная ширина столбца в пикселах. Если задано (т.е. не равно 0), то при любой ширине грида ширина данного столбца будет не меньше этой величины (если не умещается, появляется горизонтальный скроллинг).
Max - максимальная ширина столбца в пикселах. Если задано, то ширина этого столбца не будет превышать указанную величину (в этом случае "резерв ширины" распределяется между остальными столбцами).
Percent - ширина столбца в процентах (от общей ширины грида).
Width - ширина столбца в пикселах (рассчитывается).

Ньюансы:
Термин "ширина грида" означает ширину грида за исключением ширины вертикальной полосы прокрутки. Но если столбцы в любом случае не умещаются в грид (т.е. появляется горизонтальная полоса прокрутки), то в этом случае под шириной подразумевается ширина зоны горизонтальной прокрутки.
Если столбец имеет фиксированную ширину (т.е. Min = Max <> 0), то в этом случае его Percent равен 0 (т.к. столбец не учавствует в распределении свободного пространства).
Сумма всех значащих Percent равна 100%.

Сложности:
Самый простой способ (Width = GridWidth * Percent) не пойдет, т.к. столбцы фиксированной ширины должны исключаться из перерасчета.
Если столбец имеет минимальную ширину и рассчитываемая ширина меньше минимальной, то эту разницу надо вычесть из общей ширины грида и пересчитать ширину остальных столбцов.
Если столбец имеет максимальную ширину и рассчитываемая ширина больше максимальной, то эту разницу надо перераспределить между остальными столбцами.

Хотелось бы все сделать за один проход, но никак не выходит. Сейчас у меня трехпроходной алгоритм (в первом вычисляется сумма минимальных "ширин" столбцов, во втором предварительный рассчет ширины, в третьем перераспределение ширины столбцов, которые превышают максимальную ширину). Алгоритм работает но криво (т.е. неправильно) и тормознуто, ибо три прохода, причем третий проход итерационный.

Свой метод выкладывать не буду (чтобы не сбивать мысли общественности :) ), но если он поможет, то могу выложить.

Буду благодарен за предложения :)
Lasciate ogni speranza, voi ch'entrate.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 11.10.2004 (Пн) 15:38

Ммм... Идеи такие:

Заводишь пул ширины, изначально в нём GridWidth пикселов.
Инициализация:
Код: Выделить всё
    Pool = GridWidth
    For Each Col In Grid
        Width(Col) = 0
    Next

Проход по столбцам:
Код: Выделить всё
    For Each Col In Grid
        Dim w: w = Col.Percent * Pool / 100
        If w < Col.Min Then   'добавляем из пула до минимума
           Pool = Pool - Col.Min + w
           Width(Col) = Col.Min
        ElseIf w > Col.Max Then   'убираем лишку в пул
           Pool = Pool + Col.Max - w
           Width(Col) = Col.Max
        End If
    Next

Завершение: распределяем пул по свободным столбцам
Код: Выделить всё
    For Each Col In Grid
        If Width(Col) = 0 Then Width(Col) = Col.Percent * Pool / 100
    Next


Не гарантирую, что это работает правильно, но так бы стал делать я...
Изображение

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 11.10.2004 (Пн) 15:57

Проходил через это, не совсем это правильно.
Первое: надо учитывать, что если ширина столбца достигла граничных значений, то этот столбец больше не учавствует в распределении (хотя если разные столбцы достигли границы с разных сторон, то тогда учавствуют). А второе: распределение в процентном отношении будет неправильным. После того, как какие-то столбцы выбыли из расчета, у оставшихся надо пересчитать процентные доли (привести их к 100%).
Тут еще такой ньюанс, что значение ширины вычисляется как реальное, а фактически является целым. Т.е. надо обеспечить "стыкуемость", чтобы сумма столбцов была равна ширине грида. Правда это делается довольно легко, примерно так:
Код: Выделить всё
DW = 0
'начало цикла
W = (Width + DW) * Percent
DW = DW + (W - Fix(W))
W = Fix(W)
'конец цикла


Вообщем, спасибо за идею, но тут парочка багов есть. Первый -- какие-то смутные подозрения второго блока ("чую, что бесовское, а доказать не могу" :) ). А второй -- твой заключительный блок. Ты не учитываешь, что после перераспределения надо убедится, что ширины столбцов не превышают своих границ.
Lasciate ogni speranza, voi ch'entrate.

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 13.10.2004 (Ср) 10:29

Эх... :)
Вообщем вот как я сделал. Первый пример это само распределение по ширине, второй -- чтобы пикселы распределялись без остатка.
Но самому вариант не нравится -- чреват тормозами. Может кто посоветует, как оптимизировать?
Lasciate ogni speranza, voi ch'entrate.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 13.10.2004 (Ср) 11:49

Чего-то я в твоём коде вообще ничего понять не смог... Не прокомментируешь?
Изображение

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 13.10.2004 (Ср) 13:30

Да с радостью :)
Код: Выделить всё
Private Sub RefreshGrid(ByRef Width As Long)
' *** Width - общая ширина грида, в которую надо "вписать" колонки
Dim I As Long, M As Long, MinW As Long, W As Long, W0 As Long, P As Single
' *** Рассуждаем логически. Если задана минимальная ширина, то колонка не может быть меньше это величины.
' *** Т.е. чтобы избежать дальнейшего пересчета, определяем минимальную ширину грида,
' *** при которой еще в нее можно вписать колонки. Это будет MinW
' *** В M определяем ширину колонок фиксированной ширины. Раз их ширина никогда не меняется,
' *** то исключим их из расчета.
' *** В P суммируем процентное значение ширины. Если сумма процентов будет не равна 100%,
' *** то позже можно будет учесть коэффициент, чтобы это поправить.
' *** Т.е. можно задать 10%, 10% и 20% (всего 40%), а использовать 25%, 25% и 50%. Или задавать значения 1, 2, 3, а они будут пересчитаны в отношении 1:2:3 (16.7%, 33.3%, 50%)
For I = 1 To UBound(C)
  MinW = MinW + C(I).Min
  If C(I).Min = C(I).Max Then M = M + C(I).Min
  P = P + C(I).Prc
Next I

' *** Если ширина грида недостаточна, то увеличить ее
If Width < MinW Then Width = MinW

' *** Начальный проход.
' *** Задается ориентировачная ширина столбцов.
For I = 1 To UBound(C)
  If C(I).Min = C(I).Max And C(I).Min > 0 Then
    ' *** Если колонка фиксированной ширины, то присвоить ей это значение ширины
    C(I).Width = C(I).Min
  Else
    ' *** Иначе сделаем следующее.

    ' *** Пересчитаем процент ширины (чтобы сумма процентов всегда была равна 100%)
    C(I).Prc = C(I).Prc * (1! / P)

    ' *** Определить ширину колонки. В общей ширине, доступной для распределения,
    ' *** не учавствует ширина фиксированных колонок
    C(I).Width = C(I).Prc * (Width - M)

    If C(I).Min > 0 Then
      ' *** если задана минимальная ширина колонки
      If C(I).Width < C(I).Min Then
        ' *** если ширина колонки меньше минимальной, то:
        ' *** сложить с накоплением (вернее вычесть, т.к. со знаком -) эту разницу
        W = W + (C(I).Width - C(I).Min)
        ' *** и присвоить колонке минимальную ширину
        C(I).Width = C(I).Min
      End If
    End If
    If C(I).Max > 0 Then
      ' *** если задана максимальная ширина колонки
      If C(I).Width > C(I).Max Then
        ' *** если ширина колонки больше максимальной, то:
        ' *** сложить с накоплением эту разницу
        W = W + (C(I).Width - C(I).Max)
        ' *** и присвоить колонке максимальную ширину
        C(I).Width = C(I).Max
      End If
    End If
  End If
Next I

' *** Если сумма всех сложений и вычитаний не равна 0, это означает, что имеется некоторая "ширина",
' *** которую надо перераспределить между столбцами
' *** Используется Abs(W)>1, а не W<>0 т.к. используется реальное число, хотя на самом деле
' *** применяется целое
While Abs(W) > 1
  ' *** Проводится пересчет коэффициента, который будет применятся к процентам ширины столбцов
  ' *** Это необходимо из-за того, что часть столбцов не участвует в распределении ширины и
  ' *** поэтому суммы их процентов не будут равны 100%
  P = 0
  For I = 1 To UBound(C)
    If W < 0 Then
    ' *** Если распределить надо отрицательную величину (т.е. вычитать), то:

      If C(I).Min = 0 Or C(I).Width > C(I).Min Then
        ' *** Если минимальная ширина не задана или фактическая ширина больше минимальной
        P = P + C(I).Prc
      End If
    Else
    ' *** Если распределить надо положительную величину (т.е. складывать), то:

      If C(I).Max = 0 Or C(I).Width < C(I).Max Then
        ' *** Если максимальная ширина не задана или фактическая ширина меньше максимальной
        P = P + C(I).Prc
      End If
    End If
  Next I

  ' *** Все текущие "корректировки" будут накопляться в M
  M = 0

  For I = 1 To UBound(C)
    ' *** Определить ширину, которая "положена" столбцу для распределения, W0
    W0 = W * C(I).Prc * (1! / P)

    If W < 0 Then
      ' *** Если распределять надо отрицательную величину (т.е. вычитать), то:

      If C(I).Min = 0 Or C(I).Width > C(I).Min Then
        ' *** Если минимальная ширина не задана или фактическая ширина меньше минимальной,

        ' *** то уменьшить ширину столбца на требуемую величину
        C(I).Width = C(I).Width + W0

        If C(I).Min > 0 Then
          ' *** Если задана минимальная ширина
          If C(I).Width < C(I).Min Then
            ' *** и полученная ширина меньше минимальной, то уменьшить модуль W0 на эту величину
            ' *** и присвоить ширине минимальное значение
            W0 = W0 - (C(I).Width - C(I).Min)
            C(I).Width = C(I).Min
          End If
        End If
        If C(I).Width < 0 Then
          ' *** Если ширина почему-то получилась меньше нуля, то исправить
          W0 = W0 - C(I).Width
          C(I).Width = 0
        End If

        ' *** "Накопление" корректировок
        M = M + W0
      End If
    Else
    ' *** Если распределять надо положительную величину (т.е. прибавлять), то:

    ' *** Код для этого случая аналогичен предыдущему за исключением того,
    ' *** что проверяются максимальные значения

      If C(I).Max = 0 Or C(I).Width < C(I).Max Then
        C(I).Width = C(I).Width + W0
        If C(I).Max > 0 Then
          If C(I).Width > C(I).Max Then
            W0 = W0 - (C(I).Width - C(I).Max)
            C(I).Width = C(I).Max
          End If
        End If
        M = M + W0
      End If
    End If
  Next I

  ' *** Вычесть из текущего значения ширины, предназначенной для распределения,
  ' *** выполненные корректировки
  W = W - M
Wend
End Sub
Lasciate ogni speranza, voi ch'entrate.

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 13.10.2004 (Ср) 13:37

Не нравится именно последний этап, распределение остатков (While...Wend). Правда сейчас на реальном примере он "откалиброван" и не зависает в бесконечных циклах (из-за округления, т.к. используются пикселы, у меня везде применяется Long), но все-равно не нравится.
Lasciate ogni speranza, voi ch'entrate.

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 13.10.2004 (Ср) 14:13

Я думаю, здесь без итераций не обойтись.

pool=width
На первом проходе всем столбцам ставим width = min, вычитая из pool этот самый min. Если при этом у какого-то столбца min=max, то ставим флаг done(i) - я бы завёл параллельный массив для этого.
После первого прохода смотрим, осталось ли что-нибудь в пуле и не все ли столбцы done. Если нет, то второй проход - итеративный. Прибавляем каждому незакрытому столбцу величину, равную его % от остатка пула. Как только при этом оказывается, что итоговая ширина столбца больше max, вычитаем из пула число (max-width), ставим этому столбцу width=max, done(i)=true. И начинаем итеративный шаг с первого незакрытого столбца. Если мы достигли-таки конца цикла, значит, на текущем проходе не было достигнуто превышение max, а значит, весь пул распределён...
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Сообщение alibek » 13.10.2004 (Ср) 14:26

Ну в принципе, у меня этот подход и реализован. Только массива done() я не заводил, сравнивал границы непосредственно.
Просто "меня терзают смутные сомнения" что это можно решить без итераций. Или, по крайней мере, с меньшим количеством проходов.

P.S. Говорили мне в институте, учи мат.методы, так нет же, ленился :)
Что-то мне подсказывает, что данный пример можно преобразовать в матрицу нелинейных уравнений.
Lasciate ogni speranza, voi ch'entrate.


Вернуться в Народный треп

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 69

    TopList