Пример 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()