Это очень распространенная проблема, с которой мы сталкиваемся, борясь с «таинствами» JavaScript.
Давайте начнем с простой функции JavaScript:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
Это простой синхронный вызов функции (где каждая строка кода выполняется одна за другой в последовательность), и результат будет таким же, как ожидалось.
Теперь добавим немного завихрения, введя небольшую задержку в нашей функции, чтобы все строки кода не выполнялись последовательно. Таким образом, он будет эмулировать асинхронное поведение функции:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
Итак, вы идете, эта задержка просто сломала функциональность, которую мы ожидали! Но что именно произошло? Ну, на самом деле это довольно логично, если вы посмотрите на код. функция foo()
после выполнения ничего не возвращает (таким образом, возвращаемое значение равно undefined
), но оно запускает таймер, который выполняет функцию после 1s, чтобы вернуть «wohoo». Но, как вы можете видеть, значение, присвоенное бару, является немедленно возвращенным материалом из foo (), а не что-либо еще, что приходит позже.
Итак, как мы решаем эту проблему?
Давайте попросим нашу функцию для ОБЕЩАНИЯ. Обещание действительно о том, что это означает: это означает, что функция гарантирует, что вы предоставите любой результат, который он получит в будущем. поэтому давайте посмотрим на это в нашей маленькой проблеме выше:
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when exececution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
Таким образом, резюме - для решения асинхронных функций, таких как вызовы на основе ajax и т. д., вы можете использовать обещание resolve
значение (которое вы намерены вернуть). Таким образом, короче говоря, вы разрешаете значение вместо возврата в асинхронных функциях.
Это в основном (преднамеренное) следствие того, как проектируется API. Каждый Node
имеет набор свойств, включая свойство parent
(один и только один - родительский узел узла в графе сцены), а также такие свойства, как layoutX
и layoutY
, которые являются координатами узла относительно его родителя. Следовательно, узел может принадлежать только одному родителю и может быть добавлен только одному родителю один раз (поскольку он может иметь только одно местоположение в родительском элементе). Организация этого способа позволяет очень эффективный процесс компоновки.
Еще один способ подумать об этом: предположим, что ваш первый блок кода сделал то, что вы хотели; поэтому текстовое поле tf
появилось дважды в области потока. Какой результат вы ожидаете получить от tf.getBoundsInParent()
? Поскольку tf
появляется дважды в родительском, API не сможет дать разумного значения для этого вызова.
В заявлениях, которые вы делаете в своем вопросе, есть несколько неточностей:
Например, почему это приводит к ошибке компиляции? Не создается ли новое текстовое поле внутри панели, поскольку это композиция?
blockquote>Во-первых, технически это агрегирование, а не состав; хотя я не уверен, что различие поможет вам понять, что происходит на данный момент.
Во-вторых, здесь нет ошибки компиляции; вы получаете ошибку во время выполнения (
pane
обнаруживает, что один и тот жеnode
был добавлен дважды, у компилятора нет возможности проверить это).В-третьих, родители не создают копии узлов вы добавляете к ним. Если это так, вы не сможете изменить свойства отображаемых узлов. Например, если
FlowPane
в вашем примере создавал новыйTextField
, когда вы вызывалиpane.getChildren().add(tf);
, а затем отображал это новое текстовое поле, то, если вы впоследствии назоветеtf.setText("new text")
, это не повлияет, так как это будет не изменяйте текст текстового поля, отображаемогоpane
.Когда вы вызываете
pane.getChildren().add(...)
, вы передаете ссылку на узел, который хотите добавить; это тот узел, который затем отображается как дочерний элемент панели. Любая другая реализация создавала бы довольно противоречивое поведение.В вашем втором кодовом блоке:
pane.getChildren().add(tf); pane2.getChildren().add(tf);
второй вызов неявно устанавливает свойство
parent
вtf
наpane2
; следовательно,tf
уже не является дочерним элементомpane
. Таким образом, этот код имеет эффект удаленияtf
от первого родителя,pane
. Насколько мне известно, этот побочный эффект не документирован, поэтому вам, вероятно, следует избегать написания такого кода.
Попробуйте следующее:
TextField tf = new TextField();
TextField tf2 = new TextField();
pane.getChildren().add(tf);
pane.getChildren().add(tf2);
Причина, по которой вы не можете добавить один и тот же узел дважды, состоит в том, что в gui можно просматривать только один узел с теми же спецификациями и размерами. Это было бы похоже на копирование идентичного синего круга на оригинальный синий круг. Для пользователя он выглядит одинаково, но он занимает больше памяти.