К сожалению, нет общедоступного API для перемещения стрелки вправо от TitledPane
. Это не означает, что это невозможно, однако нам просто нужно динамически перевести стрелку, используя привязки. Чтобы остальная часть заголовка выглядела правильно, нам также нужно будет перевести текст и графику, если они есть, влево. Самый простой способ сделать все это - создать подкласс TitledPaneSkin
и получить доступ к внутренним частям «области заголовка».
Вот пример реализации. Это позволяет вам позиционировать стрелку слева или справа с помощью CSS. Он также реагирует на изменение размеров, а также выравнивание и графические изменения.
package com.example;
import static javafx.css.StyleConverter.getEnumConverter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TitledPane;
import javafx.scene.control.skin.TitledPaneSkin;
import javafx.scene.layout.Region;
import javafx.scene.text.Text;
public class CustomTitledPaneSkin extends TitledPaneSkin {
public enum ArrowSide {
LEFT, RIGHT
}
/* ********************************************************
* *
* Properties *
* *
**********************************************************/
private final StyleableObjectProperty<ArrowSide> arrowSide
= new SimpleStyleableObjectProperty<>(StyleableProperties.ARROW_SIDE, this, "arrowSide", ArrowSide.LEFT) {
@Override protected void invalidated() {
adjustTitleLayout();
}
};
public final void setArrowSide(ArrowSide arrowSide) { this.arrowSide.set(arrowSide); }
public final ArrowSide getArrowSide() { return arrowSide.get(); }
public final ObjectProperty<ArrowSide> arrowSideProperty() { return arrowSide; }
/* ********************************************************
* *
* Instance Fields *
* *
**********************************************************/
private final Region title;
private final Region arrow;
private final Text text;
private DoubleBinding arrowTranslateBinding;
private DoubleBinding textGraphicTranslateBinding;
private Node graphic;
/* ********************************************************
* *
* Constructors *
* *
**********************************************************/
public CustomTitledPaneSkin(TitledPane control) {
super(control);
title = (Region) Objects.requireNonNull(control.lookup(".title"));
arrow = (Region) Objects.requireNonNull(title.lookup(".arrow-button"));
text = (Text) Objects.requireNonNull(title.lookup(".text"));
registerChangeListener(control.graphicProperty(), ov -> adjustTitleLayout());
}
/* ********************************************************
* *
* Skin Stuff *
* *
**********************************************************/
private void adjustTitleLayout() {
clearBindings();
if (getArrowSide() != ArrowSide.RIGHT) {
// if arrow is on the left we don't need to translate anything
return;
}
arrowTranslateBinding = Bindings.createDoubleBinding(() -> {
double rightInset = title.getPadding().getRight();
return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
}, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty());
arrow.translateXProperty().bind(arrowTranslateBinding);
textGraphicTranslateBinding = Bindings.createDoubleBinding(() -> {
switch (getSkinnable().getAlignment()) {
case TOP_CENTER:
case CENTER:
case BOTTOM_CENTER:
case BASELINE_CENTER:
return 0.0;
default:
return -(arrow.getWidth());
}
}, getSkinnable().alignmentProperty(), arrow.widthProperty());
text.translateXProperty().bind(textGraphicTranslateBinding);
graphic = getSkinnable().getGraphic();
if (graphic != null) {
graphic.translateXProperty().bind(textGraphicTranslateBinding);
}
}
private void clearBindings() {
if (arrowTranslateBinding != null) {
arrow.translateXProperty().unbind();
arrow.setTranslateX(0);
arrowTranslateBinding.dispose();
arrowTranslateBinding = null;
}
if (textGraphicTranslateBinding != null) {
text.translateXProperty().unbind();
text.setTranslateX(0);
if (graphic != null) {
graphic.translateXProperty().unbind();
graphic.setTranslateX(0);
graphic = null;
}
textGraphicTranslateBinding.dispose();
textGraphicTranslateBinding = null;
}
}
@Override
public void dispose() {
clearBindings();
unregisterChangeListeners(getSkinnable().graphicProperty());
super.dispose();
}
/* ********************************************************
* *
* Stylesheet Handling *
* *
**********************************************************/
public static List<CssMetaData<?, ?>> getClassCssMetaData() {
return StyleableProperties.CSS_META_DATA;
}
@Override
public List<CssMetaData<?, ?>> getCssMetaData() {
return getClassCssMetaData();
}
private static class StyleableProperties {
private static final CssMetaData<TitledPane, ArrowSide> ARROW_SIDE
= new CssMetaData<>("-fx-arrow-side", getEnumConverter(ArrowSide.class), ArrowSide.LEFT) {
@Override
public boolean isSettable(TitledPane styleable) {
Property<?> prop = (Property<?>) getStyleableProperty(styleable);
return prop != null && !prop.isBound();
}
@Override
public StyleableProperty<ArrowSide> getStyleableProperty(TitledPane styleable) {
Skin<?> skin = styleable.getSkin();
if (skin instanceof CustomTitledPaneSkin) {
return ((CustomTitledPaneSkin) skin).arrowSide;
}
return null;
}
};
private static final List<CssMetaData<?, ?>> CSS_META_DATA;
static {
List<CssMetaData<?,?>> list = new ArrayList<>(TitledPane.getClassCssMetaData().size() + 1);
list.addAll(TitledPaneSkin.getClassCssMetaData());
list.add(ARROW_SIDE);
CSS_META_DATA = Collections.unmodifiableList(list);
}
}
}
Затем вы можете применить этот скин ко всем TitledPane
в вашем приложении из CSS, например, так:
.titled-pane {
-fx-skin: "com.example.CustomTitledPaneSkin";
-fx-arrow-side: right;
}
/*
* The arrow button has some right padding that's added
* by "modena.css". This simply puts the padding on the
* left since the arrow is positioned on the right.
*/
.titled-pane > .title > .arrow-button {
-fx-padding: 0.0em 0.0em 0.0em 0.583em;
}
Или вы можете настроить таргетинг только на определенные TitledPane
с помощью добавления стиля. класс и использование указанного класса вместо .titled-pane
.
Вышесказанное работает с JavaFX 11 и, вероятно, с JavaFX 10 и 9. Чтобы заставить его скомпилироваться на JavaFX 8, вам нужно изменить некоторые вещи:
Вместо этого импортировать com.sun.javafx.scene.control.skin.TitledPaneSkin
.
Удалить вызовы на registerChangeListener(...)
и unregisterChangeListeners(...)
. Я считаю правильным заменить их следующим:
@Override
protected void handleControlPropertyChange(String p) {
super.handleControlPropertyChange(p);
if ("GRAPHIC".equals(p)) {
adjustTitleLayout();
}
}
Используйте new SimpleStyleableObjectProperty<ArrowSide>(...) {...}
и new CssMetaData<TitledPane, ArrowSide>(...) {...}
.
Использовать (StyleConverter<?, ArrowSide>) getEnumConverter(ArrowSide.class)
.
getEnumConverter
была ошибка, исправленная в более поздней версии. Использование броска работает вокруг проблемы. Вы можете @SuppressWarnings("unchecked")
сыграть. Проблема: Даже с учетом вышеуказанных изменений в JavaFX 8 возникает проблема - стрелка переводится только после фокусировки TitledPane
. Похоже, что это не проблема с приведенным выше кодом, поскольку даже изменение свойства alignment
не приводит к обновлению TitledPane
до тех пор, пока оно не будет сфокусировано (даже если не используется вышеуказанный скин, а скорее только скин по умолчанию) , Мне не удалось найти решение этой проблемы (при использовании пользовательского скина), но, возможно, вы или кто-то еще можете. Я использовал Java 1.8.0_202 при тестировании на JavaFX 8.
Если вы не хотите использовать пользовательский скин или используете JavaFX 8 (это приведет к переводу стрелки без необходимости сначала фокусировать TitledPane
), вы можете извлечь необходимые код, с некоторыми изменениями, в служебный метод:
public static void putArrowOnRight(TitledPane pane) {
Region title = (Region) pane.lookup(".title");
Region arrow = (Region) title.lookup(".arrow-button");
Text text = (Text) title.lookup(".text");
arrow.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
double rightInset = title.getPadding().getRight();
return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
}, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty()));
arrow.setStyle("-fx-padding: 0.0em 0.0em 0.0em 0.583em;");
DoubleBinding textGraphicBinding = Bindings.createDoubleBinding(() -> {
switch (pane.getAlignment()) {
case TOP_CENTER:
case CENTER:
case BOTTOM_CENTER:
case BASELINE_CENTER:
return 0.0;
default:
return -(arrow.getWidth());
}
}, arrow.widthProperty(), pane.alignmentProperty());
text.translateXProperty().bind(textGraphicBinding);
pane.graphicProperty().addListener((observable, oldGraphic, newGraphic) -> {
if (oldGraphic != null) {
oldGraphic.translateXProperty().unbind();
oldGraphic.setTranslateX(0);
}
if (newGraphic != null) {
newGraphic.translateXProperty().bind(textGraphicBinding);
}
});
if (pane.getGraphic() != null) {
pane.getGraphic().translateXProperty().bind(textGraphicBinding);
}
}
Примечание: хотя при этом стрелка справа помещается без необходимости сначала фокусировать TitledPane
, TitledPane
все еще страдает от проблема отмечена выше. Например, изменение свойства alignment
не обновляет TitledPane
, пока оно не будет сфокусировано. Я предполагаю, что это просто ошибка в JavaFX 8.
Этот способ работы не так «прост», как скин-подход, и требует двух вещей:
TitledPane
должен использовать значение по умолчанию TitledPaneSkin
. TitledPane
должно должно отображаться в Window
(окно было , показывающее ) до вызова метода утилиты. [1176 ]
NullPointerException
, так как вызовы lookup
вернут null
. При использовании FXML обратите внимание, что метод initialize
вызывается во время вызова FXMLLoader.load
(любой из перегрузок). Это означает, что при нормальных обстоятельствах созданные узлы еще не могут быть частью Scene
, не говоря уже о показе Window
. Вы должны подождать, пока сначала не отобразится TitledPane
, затем , а затем вызвать служебный метод.
Ожидание отображения TitledPane
может быть достигнуто путем прослушивания свойства Node.scene
, свойства Scene.window
и свойства Window.showing
(или вы можете прослушивать события WindowEvent.WINDOW_SHOWN
). Однако, , если вы немедленно поместите загруженные узлы в показ Window
, то вы можете отказаться от наблюдения за свойствами; вызовите служебный метод внутри вызова Platform.runLater
изнутри initialize
.
При использовании подхода с использованием скинов вся проблема ожидания окна показа исключается.
Обычное предупреждение. Этот ответ опирается на внутреннюю структуру TitledPane
, которая может измениться в будущем выпуске. Будьте осторожны при изменении версий JavaFX. Я только (несколько) протестировал это на JavaFX 8u202 и JavaFX 11.0.2.
как дела?
Есть ли причина, по которой у вас нет контейнеров объектов, представляющих строку в вашем база данных? С созданием пользовательского объекта гораздо проще работать на других уровнях вашего решения. Таким образом, благодаря этому подходу есть два очень жизнеспособных решения ваших проблем.
Скажем, у вас есть пользовательский объект, который представляет Продукт в вашей базе данных. Вы бы определили объект следующим образом:
public class Product {
public int ProductID { get; set; }
public string Name { get; set; }
public byte[] Image { get; set; }
}
И заполнили бы коллекцию Продуктов (Коллекция) следующим образом:
var collection = new Collection<Product>();
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
var product = new Product();
int ordinal = reader.GetOrdinal("ProductID");
if (!reader.IsDBNull(ordinal) {
product.ProductID = reader.GetInt32(ordinal);
}
ordinal = reader.GetOrdinal("Name");
if (!reader.IsDBNull(ordinal)) {
product.Name = reader.GetString(ordinal);
}
ordinal = reader.GetOrdinal("Image");
if (!reader.IsDBNull(ordinal)) {
var sqlBytes = reader.GetSqlBytes(ordinal);
product.Image = sqlBytes.Value;
}
collection.Add(product);
}
}
Обратите внимание, что я получаю значение через читатель Get x где x - это тип, который я хочу получить из столбца. Это рекомендуемый Microsoft способ получения данных для столбца в соответствии с http://msdn.microsoft.com/en-us/library/haa3afyz. aspx (второй абзац), поскольку извлеченное значение не нужно помещать в System.Object и распаковывать в примитивный тип.
Поскольку вы упомянули, что этот метод будет вызываться много, много раз в ASP Приложение .NET, вы можете пересмотреть такой общий подход, как этот. Метод, который вы используете для возврата NameValueCollection , очень неэффективен в этом сценарии (и, возможно, во многих других сценариях). Не говоря уже о том, что вы преобразуете каждый столбец базы данных в строку без учета культуры текущего пользователя, и культура является важным фактором в приложении ASP.NET. Я бы сказал, что эта NameValueCollection не должна использоваться и в других ваших усилиях по разработке. Я мог бы продолжать и продолжать об этом, но я спасу вас от моего беспорядка.
Конечно, если вы собираетесь создавать объекты, которые непосредственно отображаются в ваши таблицы, вы можете также обратиться к LINQ to SQL или ADO.NET Entity Framework . Вы будете счастливы, что сделали.
Вы получите гораздо больше пользы от кэширования данных, чем попытка оптимизировать возвращение одной строки. Если вы выбираете по первичному ключу, маловероятно, что вы увидите разницу между возвратом DataTable, DataRow или пользовательского объекта. Это кажется мне преждевременной оптимизацией. Я был бы более определенным, но я не уверен, что изменение байтового массива в миксе меняет дело.
То, что вы демонстрируете, - это запах кода, называемый Примитивная одержимость . Создайте пользовательский тип и верните его из метода репозитория. Не пытайтесь быть чрезмерно универсальными ... вы просто столкнетесь с этой сложностью в своем бизнес-коде, потому что вы будете взаимодействовать со своими сущностями, используя чисто процедурный код. Лучше создавать объекты, которые моделируют ваш бизнес.
Если вас беспокоит слишком большое количество кода доступа к данным, обратите внимание на использование инфраструктуры ORM, чтобы позаботиться о том, чтобы сгенерировать ее для вас. Вы не должны позволять этой проблеме диктовать плохой дизайн на уровне приложений.
С точки зрения эффективности кода, вы, вероятно, сделали это за наименьшее количество нажатий клавиш, и хотя это кажется расточительным, вероятно, самый простой в обслуживании. Тем не менее, если вы заинтересованы только в эффективности выполнения только того, что строго необходимо, вы можете создать облегченную структуру / класс для заполнения данными и использовать нечто похожее на:
public class MyAsset
{
public int ID;
public string Name;
public string Description;
}
public MyAsset GetAsset(IDBConnection con, Int AssetId)
{
using (var cmd = con.CreateCommand("sp_GetAsset"))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(cmd.CreateParameter("AssetID"));
using(IDataReader dr = cmd.ExecuteReader())
{
if (!dr.Read()) return null;
return new MyAsset() {
ID = dr.GetInt32(0),
Name = dr.GetString(1),
Description = dr.GetString(2)
};
}
}
}
Аналогично, вы можете выгружать данные аналогичным образом прямо в ваша коллекция KVP ...
Она выглядит не так чисто, как ваш оригинальный код, но она не создает всю таблицу только для того, чтобы получить единственную строку ...
Как уже упоминалось в другом посте относительно хотя запах кода, вероятно, я бы не стал передавать команду в качестве параметра, я думаю, что я бы с большей вероятностью инкапсулировал команду внутри этого метода, передать только соединение с базой данных и идентификатор актива, который я хотел - при условии, что я, конечно, не использовал кэширование, и передать обратно экземпляр MyAsset. Это делает метод достаточно универсальным, чтобы его можно было использовать с любым типом базы данных - конечно, при условии, что сохраненный процесс существует. Таким образом, остальная часть моего кода защищена от необходимости знать что-либо о базе данных, кроме того, какой это тип базы данных ... и в остальной части моего приложения я могу ссылаться на информацию об активах, используя MyAssetInstance.ID, MyAssetInstance.Name, MyAssetInstance. Описание и т. Д.
Спасибо за все отзывы, ребята. Я знаю, что ORM - это, пожалуй, верный путь, и этот и MVC фреймворк находятся в моем списке.
Чтобы дать немного больше подробностей, код, который я показываю, взят из раздела помощников в моем слое доступа к данным, который затем передает коллекцию значений строк или имен в бизнес-уровень для превращения в объекты.
Я думаю, что примеры кода mnero0429 и balabaster дают мне правильное направление. Используйте datareader и вручную выводите данные таким образом, не возиться с промежуточными объектами. Спасибо за подробную ссылку на MS mnero0429. Справедливо в отношении основной одержимости - хотя я действительно делаю из нее правильный класс активов на бизнес-уровне;)
Я также буду изучать структуру сущностей ADO.
Еще раз,