Пример GUI клиента к 1С на Java.
Приводить весь исходный код мы не будем. Кому интересно, может его скачать полностью.
Приведем только фрагменты, на которые стоит обратить внимание.
Основная сложность в реализации нашего примера это буферизация данных. Исходя из того, что в реальной базе может быть достаточно большой объем данных, а пользователь видит только несколько десятков строк, мы должны сделать так, что брать из базы только то, что нужно.
Для показа данных мы используем JTable. Кроме того, так же, как и сама 1С будем использовать TYPE_SCROLL_SENSITIVE CONCUR_READ_ONLY курсор. Т.е. мы можем бегать курсором по данным взад и вперед и при этом считываем измененные данные.
Кроме того, нужно так же оптимизировать вывод полей - так как реквизитов в справочнике может быть тоже много, которые выведены на просмотр, то нужно сделать, что чтение данных было один раз на одно обновления буфера.
Основное действие разворачивается в модели данных таблицы. Приведем его полностью.
class CatalogTableModel extends AbstractTableModel{
private static final int SIZEBUF = 1;//коэф. неотображаемых строк буфера от кол-ва видимых строк
private ArrayList<Object[]> buf = new ArrayList<Object[]>();
private RecordSet rs;
private ResultSet cursor;
private int count;
private boolean lastMoveForward = false; //true - forward, false - backward
private final int columnCount;
private final String[] columnName;
private String query;
@Override
protected void finalize() throws Throwable {
cursor.close();
}
public CatalogTableModel() {
int result = 1;
if(meta.getLengthCode()>0) result++;
if(meta.getLengthName()>0) result++;
columnCount = result+=meta.getPropertys().size();
columnName = new String[columnCount];
int ind =0;
columnName[ind]="";
ind++;
if(meta.getLengthCode()>0){
columnName[ind]="Код";
ind++;
}
if(meta.getLengthName()>0){
columnName[ind]="Наименование";
ind++;
}
for(Iterator<Metadatas.Property>i=meta.getPropertys().iterator();i.hasNext();){
columnName[ind]=i.next().getName();
ind++;
}
rs = v7.new RecordSet(V7.RecordSet.TYPE_SCROLL_SENSITIVE, V7.RecordSet.CONCUR_READ_ONLY);
query = "select * from $Справочник."+meta.getName();
if(grouping&&meta.getCountLevel()>1) {
query+=" where parentid=:parent order by isFolder"+(sortField!=null?","+sortField:"")+",id,row_id";
}
else{
query+=" order by "+(sortField!=null?sortField+",":"")+"id,row_id";
}
init();
}
@Override
public String getColumnName(int column) {
return columnName[column];
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return getValueAt(0, columnIndex).getClass();
}
void fillBufUp(){
if(lastMoveForward){
int ind=0;
while(cursor.previous()&&ind<buf.size()){
ind++;
}
}
//должны стоять на строке соответствующей первой строке буфера
cursor.next();
count=0;
while(cursor.previous()&&count<SIZEBUF*rowScreen){
buf.add(0, getRowBuf());
count++;
}
if(count>0){
fireTableRowsInserted(0, count-1);
initColumnSizes();
}
lastMoveForward=false;
truncateDown();
}
void fillBufDown(){
if(!lastMoveForward){
int ind=0;
while(cursor.next()&&ind<buf.size()){
ind++;
}
}
//должны стоять на строке соответствующей последней строке буфера
cursor.previous();
count=0;
while(cursor.next()&&count<SIZEBUF*rowScreen){
buf.add(getRowBuf());
count++;
}
if(count>0) {
fireTableRowsInserted(buf.size()-1-count, buf.size()-1);
initColumnSizes();
}
lastMoveForward=true;
truncateUp();
}
/**Процедура нужна для того, что бы переустанавливать размеры колонок после обновления
* буфера
*
*/
private void initColumnSizes() {
TableColumn column = null;
Component comp = null;
int headerWidth = 0;
int columnWidth = 0;
int midleSize = 4;
TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer();
for (int i = 0; i < columnCount; i++) {
column = table.getColumnModel().getColumn(i);
comp = headerRenderer.getTableCellRendererComponent(
null, column.getHeaderValue(),
false, false, 0, 0);
headerWidth = comp.getPreferredSize().width;
if(i==0) {
column.setResizable(false);
columnWidth = 0;
}
else if(columnName[i].equals("Код")){
columnWidth = midleSize*meta.getLengthCode();
}
else if(columnName[i].equals("Наименование")){
columnWidth = midleSize*meta.getLengthName();
}
else{
Property property =meta.getProperty(columnName[i]);
if(property.getType() instanceof Row){
columnWidth = midleSize*property.getLength();
}
else if(property.getType() instanceof Date){
columnWidth = midleSize*20;
}
else if(property.getType() instanceof Numeric){
columnWidth = midleSize*property.getLength();
}
else{//далее здесь нужно прописать аккуратно все типы
//и "подбирания" оптимальной" длины каждого
//но мы сделаем проще (ведь пример все-таки)
columnWidth = midleSize*30;
}
}
column.setPreferredWidth(Math.max(columnWidth, headerWidth));
}
}
void init(){
buf.clear();
rs.setParameter("parent", parent);
cursor = rs.executeQuery(query);
fillBufDown();
}
private void truncateUp(){
int possible = Math.min(count, buf.size()-count-rowScreen-rowScreen*SIZEBUF);
int ind = possible;
while(ind>0){
buf.remove(0);
ind--;
}
if(possible>0){
fireTableRowsDeleted(0, possible-1);
}
}
private void truncateDown(){
int possible = Math.min(count, buf.size()-count-rowScreen-rowScreen*SIZEBUF);
int ind = possible;
while(ind>0){
buf.remove(buf.size()-1);
ind--;
}
if(possible>0){
fireTableRowsDeleted(buf.size(), buf.size()+possible-1);
}
}
private Object[] getRowBuf() {
Object[] temp = new Object[columnCount];
CatalogReference ref = (CatalogReference)cursor.getRef("id", meta);
int ind = 0;
if(ref.isFolder()) temp[0]=folderIcon;
else temp[0]=leafIcon;
ind++;
if(meta.getLengthCode()>0) {
temp[ind]=ref.getCode();
ind++;
}
if(meta.getLengthName()>0){
temp[ind]=ref.getName();
ind++;
}
for(int i=ind;i<columnName.length;i++){
temp[i]=ref.getAttribute(columnName[i]);
}
return temp;
}
@Override
public int getColumnCount() {
return columnCount;
}
@Override
public int getRowCount() {
return buf.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return buf.get(rowIndex)[columnIndex];
}
}
Получение данных из курсора происходит в методе getRowBuf(). Обратите внимание на место, где происходит обращение к ссылке объекта 1С:
... temp[i]=ref.getAttribute(columnName[i]); ... ...
Метод getAttribute() ссылочного объекта справочника получает атрибут справочника и возвращает ссылочный объект.
Момент, на который хотим обратить внимание, это мест в конструкторе, где мы строим строку запроса к базе:
... query = "select * from $Справочник."+meta.getName(); ...
Метаподстановки в J1C поддерживают нотацию 1CPP. Здесь конечно приведен простой пример, но возможно и так:
... query = "select * from $Справочник.Сотрудники.Оклад(:id, :data)"; ...
или так:
... query = "select id as [id $Справочник.Сотрудники] from $Справочник.Сотрудники"; ...
с последующим использованием автотипизации через метод rs.getObject()