Почему SWT Composite иногда требует правильного преобразования размера() в макет?
Иногда мы сталкиваемся с SWT-композицией, который абсолютно отказывается правильно отлаживаться. Часто мы сталкиваемся с этим, когда мы вызываем dispose на композите, а затем заменяем его другим; хотя это, судя по всему, строго не ограничивается этим случаем.
Когда мы сталкиваемся с этой проблемой, примерно в 50% случаев, мы можем называть pack()
и layout()
на оскорбительном композите, и все будет хорошо. Примерно в 50% случаев мы должны это сделать:
Point p = c.getSize();
c.setSize(p.x+1, p.y+1);
c.setSize(p);
Мы это случались практически с каждой комбинацией менеджеров макетов и т.д.
Хотелось бы, чтобы у меня был хороший, простой, воспроизводимый случай, но я этого не делаю. Я надеюсь, что кто-то узнает эту проблему и скажет: "Ну, ну, тебе не хватает xyz...."
Ответы
Ответ 1
Мне кажется, что кеш-память устарела и нуждается в обновлении.
Макеты в SWT-кешах поддержки и обычно кэшируют предпочтительные размеры элементов управления или независимо от того, что им нравится кэшировать:
public abstract class Layout {
protected abstract Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache);
protected boolean flushCache (Control control) {...}
protected abstract void layout (Composite composite, boolean flushCache);
}
Я относительно новичок в программировании SWT (бывший программист Swing), но столкнулся с аналогичными ситуациями, в которых макет не был должным образом обновлен. Обычно я мог их разрешить, используя другие методы компоновки, которые также приведут к тому, что макет будет очищать его кеш:
layout(boolean changed)
layout(boolean changed, boolean allChildren)
Надеюсь, что это поможет...
Ответ 2
Тем временем я узнал немного больше о недостатках SWT при изменении или изменении частей иерархии управления во время выполнения. ScrolledComposite
и ExpandBar
необходимо также явно обновлять, когда они должны адаптировать свои минимальные или предпочтительные размеры контента.
Я написал небольшой вспомогательный метод, который переопределяет макет иерархии элементов управления для измененного элемента управления:
public static void revalidateLayout (Control control) {
Control c = control;
do {
if (c instanceof ExpandBar) {
ExpandBar expandBar = (ExpandBar) c;
for (ExpandItem expandItem : expandBar.getItems()) {
expandItem
.setHeight(expandItem.getControl().computeSize(expandBar.getSize().x, SWT.DEFAULT, true).y);
}
}
c = c.getParent();
} while (c != null && c.getParent() != null && !(c instanceof ScrolledComposite));
if (c instanceof ScrolledComposite) {
ScrolledComposite scrolledComposite = (ScrolledComposite) c;
if (scrolledComposite.getExpandHorizontal() || scrolledComposite.getExpandVertical()) {
scrolledComposite
.setMinSize(scrolledComposite.getContent().computeSize(SWT.DEFAULT, SWT.DEFAULT, true));
} else {
scrolledComposite.getContent().pack(true);
}
}
if (c instanceof Composite) {
Composite composite = (Composite) c;
composite.layout(true, true);
}
}
Ответ 3
Составная компоновка отвечает за укладку дочерних элементов этого композита. Поэтому, если составной размер не изменяется, но необходимо обновить относительные позиции и размеры детей, вы вызываете layout()
в композите. Если, однако, размер или положение самого композита необходимо обновить, вам придется вызывать layout()
в своем родительском составе (и так далее, пока не дойдете до оболочки).
Эмпирическое правило: если вы добавили или удалили элемент управления или иным образом сделали то, что требует ретрансляции, поднимите иерархию виджетов, пока не найдете композит с полосами прокрутки и вызовите layout()
на нем. Причиной остановки на композите с полосами прокрутки является то, что его размер не изменится в ответ на изменение - его полоски прокрутки будут "поглощать" это.
Обратите внимание, что если изменение, требующее макета, не является новым дочерним или удаленным дочерним, вы должны вызвать Composite.changed(new Control[] {changedControl})
перед вызовом макета.
Ответ 4
Я только что узнал о Composite.changed(Control [] children). Существует обширная статья, которую я прочитал пару лет назад:
http://www.eclipse.org/articles/article.php?file=Article-Understanding-Layouts/index.html
В этой статье также упоминается Composite.layout(boolean changed, boolean all), чтобы обновить макет: "Вызов макета() совпадает с вызовом layout (true), который сообщает ColumnLayout, чтобы очистить свои кеши до установки границ детей". Это все правильно и что я делаю с тех пор. Но это не то, что нужно, поскольку он в основном побеждает преимущество кеша макета, когда вы хотите обновить макет, потому что один или несколько элементов управления изменили требования.
Представьте, что у вас есть куча виджетов StyledText в GridLayout, и вам нужно изменить размер одного из них. Вызов computeSize() на StyledText очень дорого. Вместо этого:
Неправильно:
parent.layout(true);
... который вызывает computeSize() для всех детей, хотя их требования не изменились. Вы должны сделать это:
Справа:
parent.changed(new Control[] { theChangedChild });
Тогда либо
rootComposite.layout(false, true);
или
parent.layout(false);
Не очень интуитивно. Параметр layout() просто плохо назван. Вместо того, чтобы называть его "измененным", его следовало называть "ignoreCache" или что-то в этом роде. Интуитивная вещь - передать "истину", когда что-то изменилось. Вместо этого вам нужно передать "ложь", но недействить кеш только для измененного элемента управления с измененным(), прежде чем вы сделаете...
Обратите внимание, что вызов changed() также рекурсивно недействителен для кеша только для родительского элемента управления в его собственном родителе, что совершенно имеет смысл. Поэтому, когда вы вызываете layout(), вы должны вызывать его на корневой компоновке (чаще всего в оболочке) со значением all = true, если вы не знаете, что размер родителя измененного элемента управления будет или не сможет измениться в ответ.