Форма документа

Продолжение цикла статей о создании мобильного приложения «Заказ покупателя», предыдущие: 1 часть, 2 часть и 3 часть

Рассмотрим приемы интерактивного изменения реквизитов объекта. В 1С наиболее близкое понятие это «форма документа» или «форма элемента справочника».

В общем случае, порядок действий следующий:
- на «форме» (это условное понятие, у вас это может быть activity, fragment или кастомный View) разместить контрол View: поле ввода, переключатель, список  и т.п.;
- считать редактируемый документ, в контрол вывести значение редактируемого реквизита;
- назначить обработчики изменения, где считывать введенное пользователем значение и сохранять его в реквизит объекта.

Сложно? С помощью FBA это можно сделать изящнее. Давайте создадим простую форму для редактирования «реквизитов шапки» нашего документа «Заказ покупателя».

1. Создайте новую Activity наследуясь от FbaDBActivity, которая будет использоваться для редактирования или добавления нового документа. Создайте и установите для нее макет doc_zakaz_item.xml

1
2
3
4
5
6
7
8
public class DocZakazItemActivity extends FbaDBActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.doc_zakaz_item);
    }
}
public class DocZakazItemActivity extends FbaDBActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
        	super.onCreate(savedInstanceState);
        	setContentView(R.layout.doc_zakaz_item);
	}
}

2. В макет добавьте ObjectView и дочерние контролы для реквизитов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<ru.profi1c.engine.widget.ObjectView
        android:id="@+id/ovDocZakaz"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
 
        <ru.profi1c.engine.widget.FieldDateView
                android:id="@+id/date"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
 
        <ru.profi1c.engine.widget.FieldTimeView
                android:id="@+id/time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
 
        <ru.profi1c.engine.widget.FieldPresentationSpinner
                android:id="@+id/kontragent"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:spinnerMode="dialog" />
 
        <ru.profi1c.engine.widget.FieldPresentationSpinner
                android:id="@+id/dogovorKontragenta"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content" />
        <ru.profi1c.engine.widget.FieldEditText
                android:id="@+id/summa"
                android:layout_width="120dp"
                android:layout_height="wrap_content"
                android:inputType="numberDecimal" />
 
</ru.profi1c.engine.widget.ObjectView>
<ru.profi1c.engine.widget.ObjectView
    	android:id="@+id/ovDocZakaz"
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content">

        <ru.profi1c.engine.widget.FieldDateView
    	    	android:id="@+id/date"
    	    	android:layout_width="wrap_content"
    	    	android:layout_height="wrap_content"/>

    	<ru.profi1c.engine.widget.FieldTimeView
    	    	android:id="@+id/time"
    	    	android:layout_width="wrap_content"
    	    	android:layout_height="wrap_content"/>

    	<ru.profi1c.engine.widget.FieldPresentationSpinner
    	    	android:id="@+id/kontragent"
    	    	android:layout_width="fill_parent"
    	    	android:layout_height="wrap_content"
    	    	android:spinnerMode="dialog" />

    	<ru.profi1c.engine.widget.FieldPresentationSpinner
    	    	android:id="@+id/dogovorKontragenta"
    	    	android:layout_width="fill_parent"
    	    	android:layout_height="wrap_content" />
    	<ru.profi1c.engine.widget.FieldEditText
    	    	android:id="@+id/summa"
    	    	android:layout_width="120dp"
    	    	android:layout_height="wrap_content"
    	    	android:inputType="numberDecimal" />

</ru.profi1c.engine.widget.ObjectView>

Здесь добавлены элементы управления для редактирования:
- FieldDateView для редактирования даты документа
- FieldTimeView для изменения только времени у документа
- FieldPresentationSpinner с идентификатором kontragent для выбора контрагента из списка
- FieldPresentationSpinner с идентификатором dogovorKontragenta для выбора договора контагента
- FieldEditText поле ввода для суммы документа, допустимый формат значений – десятичное число.
Макет приведен частично, полностью см. в исходниках примера.

3. Добавьте код инициализации контролов, например в OnCrete вашей activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
DocumentZakazPokupatelya doc = null;
DocumentZakazPokupatelyaDao docDao = new DocumentZakazPokupatelyaDao(getConnectionSource());
 
/*
 * Если в доп. информации к намерению (Intent) передано значение ссылки
 * считает, что изменяется документ, иначе – создается новый.
 */
Bundle extras = getIntent().getExtras();
if (extras != null) {
        String ref = extras.getString(EXTRA_REF);
        doc = docDao.queryForId(ref);
} else {
        doc = docDao.newItem();
        initNewDoc();
}
 
/*
 * Имена реквизитов документа отображаемых на форме (имена полей класса)
 */
String[] fields = new String[] {
        DocumentZakazPokupatelya.FIELD_NAME_DATE,
        DocumentZakazPokupatelya.FIELD_NAME_DATE,
        DocumentZakazPokupatelya.FIELD_NAME_KONTRAGENT,
        DocumentZakazPokupatelya.FIELD_NAME_DOGOVOR_KONTRAGENTA,
        DocumentZakazPokupatelya.FIELD_NAME_SKLAD,
        DocumentZakazPokupatelya.FIELD_NAME_TIP_CEN,
        DocumentZakazPokupatelya.FIELD_NAME_VALYUTA_DOKUMENTA,
        DocumentZakazPokupatelya.FIELD_NAME_SUMMA,
        DocumentZakazPokupatelya.FIELD_NAME_DATA_OTGRUZKI,
        DocumentZakazPokupatelya.FIELD_NAME_KOMMENTARII };
 
        /*
         * Идентификаторы view-элементов для отображения реквизитов на форме
         */
int[] ids = new int[] { R.id.date, R.id.time, R.id.kontragent,
        R.id.dogovorKontragenta, R.id.sklad, R.id.tipCen,
        R.id.valyutaDokumenta, R.id.summa, R.id.dataOtgruzki,
        R.id.kommentarii };
 
ovDocZakaz = (ObjectView) findViewById(R.id.ovDocZakaz);
ovDocZakaz.setChildTextAutoHint(true);
ovDocZakaz.setChildSpinAutoPromt(true);
 
//Построить объект и дочерние контролы
ovDocZakaz.build(doc, getHelper(), fields, ids);
DocumentZakazPokupatelya doc = null;
DocumentZakazPokupatelyaDao docDao = new DocumentZakazPokupatelyaDao(getConnectionSource());

/*
 * Если в доп. информации к намерению (Intent) передано значение ссылки
 * считает, что изменяется документ, иначе – создается новый.
 */
Bundle extras = getIntent().getExtras();
if (extras != null) {
    	String ref = extras.getString(EXTRA_REF);
    	doc = docDao.queryForId(ref);
} else {
    	doc = docDao.newItem();
    	initNewDoc();
}

/*
 * Имена реквизитов документа отображаемых на форме (имена полей класса)
 */
String[] fields = new String[] {
    	DocumentZakazPokupatelya.FIELD_NAME_DATE,
    	DocumentZakazPokupatelya.FIELD_NAME_DATE,
    	DocumentZakazPokupatelya.FIELD_NAME_KONTRAGENT,
    	DocumentZakazPokupatelya.FIELD_NAME_DOGOVOR_KONTRAGENTA,
    	DocumentZakazPokupatelya.FIELD_NAME_SKLAD,
    	DocumentZakazPokupatelya.FIELD_NAME_TIP_CEN,
    	DocumentZakazPokupatelya.FIELD_NAME_VALYUTA_DOKUMENTA,
    	DocumentZakazPokupatelya.FIELD_NAME_SUMMA,
    	DocumentZakazPokupatelya.FIELD_NAME_DATA_OTGRUZKI,
    	DocumentZakazPokupatelya.FIELD_NAME_KOMMENTARII };

    	/*
    	 * Идентификаторы view-элементов для отображения реквизитов на форме
    	 */
int[] ids = new int[] { R.id.date, R.id.time, R.id.kontragent,
    	R.id.dogovorKontragenta, R.id.sklad, R.id.tipCen,
    	R.id.valyutaDokumenta, R.id.summa, R.id.dataOtgruzki,
    	R.id.kommentarii };

ovDocZakaz = (ObjectView) findViewById(R.id.ovDocZakaz);
ovDocZakaz.setChildTextAutoHint(true);
ovDocZakaz.setChildSpinAutoPromt(true);

//Построить объект и дочерние контролы
ovDocZakaz.build(doc, getHelper(), fields, ids);

4. Где-то в вашей программе, например, при отображении списка документов, можно открыть  «форму» документа:

1
2
3
4
5
6
7
8
/*
 * Изменить документ
 */
protected void editDoc(DocumentZakazPokupatelya doc) {
    Intent i = new Intent(this,DocZakazItemActivity.class);
    i.putExtra(DocZakazItemActivity.EXTRA_REF, doc.getRef().toString());
    startActivity(i);
}
/*
 * Изменить документ
 */
protected void editDoc(DocumentZakazPokupatelya doc) {
	Intent i = new Intent(this,DocZakazItemActivity.class);
	i.putExtra(DocZakazItemActivity.EXTRA_REF, doc.getRef().toString());
	startActivity(i);
}

Не забудьте прописать в манифесте новую Activity.

Ок. Форма отображается, реквизиты доступны для изменения. Если кликнуть по полю «Дата» или «Время» – откроется стандартный диалог редактирования даты, клик по контрагенту позволяет выбрать контрагента из списка:

    
    

Списки выбора могут отображаться в виде выпадающего списка или в диалоге, за это отвечает стандартное свойство android:spinnerMode. А вот сам заголовок для диалога можно установить автоматически:

1
ovDocZakaz.setChildSpinAutoPromt(true);
ovDocZakaz.setChildSpinAutoPromt(true);

Если реквизит документа не задан, то по умолчанию для списков отображается “наименование реквизита” как приглашение выбрать элемент. Если для поля класса в аннотации @MetadataField не установлен параметр description будет отображено приглашение “Выбрать…”

1
2
3
4
5
6
/**
 * Контрагент
 */
@DatabaseField(columnName = FIELD_NAME_KONTRAGENT, foreign = true)
@MetadataField(type=MetadataFieldType.REF,name="Контрагент",description="")
public CatalogKontragenti kontragent;
/**
 * Контрагент
 */
@DatabaseField(columnName = FIELD_NAME_KONTRAGENT, foreign = true)
@MetadataField(type=MetadataFieldType.REF,name="Контрагент",description="")
public CatalogKontragenti kontragent;

    
Обратите внимание на поле выбора контрагента 

Для полей ввода можно установить подсказку автоматически, в примере это поле для ввода тестового комментария:

1
ovDocZakaz.setChildTextAutoHint(true);
ovDocZakaz.setChildTextAutoHint(true);

При изменении значения в элементах управления, соответствующие реквизиты документа обновляются автоматически. Чтобы сохранить внесенные изменения достаточно вызвать метод:

1
docDao.createOrUpdate(doc);
docDao.createOrUpdate(doc);

Этого будет достаточно для создания простой формы, на которой редактируются реквизиты не связанные логически между собой. В нашем же примере есть подчиненный реквизит «Договор контрагента», в списке выбора которого отображаются только подчиненные контрагенту договора. Однако этот список не обновляется при изменении контрагента. Давайте исправлять.

Инициализация DAO для договоров, инициализация контролов и установка обработчика для списка выбора контрагента:

1
2
3
4
5
6
7
dogovorDao = new CatalogDogovoriKontragentovDao(getConnectionSource());
 
spinKontragent = (FieldPresentationSpinner) findViewById(R.id.kontragent);
lastKontragetPosition = spinKontragent.getSelectedItemPosition();
spinKontragent.setOnItemSelectedListener(onKontragentSelectListener);
 
spinDogovor = (FieldPresentationSpinner) findViewById(R.id.dogovorKontragenta);
dogovorDao = new CatalogDogovoriKontragentovDao(getConnectionSource());

spinKontragent = (FieldPresentationSpinner) findViewById(R.id.kontragent);
lastKontragetPosition = spinKontragent.getSelectedItemPosition();
spinKontragent.setOnItemSelectedListener(onKontragentSelectListener);

spinDogovor = (FieldPresentationSpinner) findViewById(R.id.dogovorKontragenta);

И сам обработчик изменения:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
 * Обработчик выбора контрагента из выпадающего списка. Установка договора
 * по умолчанию
 */
private OnItemSelectedListener onKontragentSelectListener = new OnItemSelectedListener() {
 
        @Override
        public void onItemSelected(AdapterView<?> parent, View view,
                        int position, long id) {
 
                // Если выбрана группа – восстановим предыдущее значение
                CatalogKontragenti newKontragent = (CatalogKontragenti) spinKontragent
                                        .getSelectedItem();
                if (newKontragent.isFolder()) {
                        spinKontragent.setSelection(lastKontragetPosition);
                        showToast(getString(R.string.msg_not_select_folder));
                        return;
                }
 
                lastKontragetPosition = spinKontragent.getSelectedItemPosition();
                CatalogDogovoriKontragentov newDogovor = dogovorDao
                                        .getLast(newKontragent);
 
                // Необходимо вручную установить значение реквизита документа, если
                // переназначен обработчик изменения
                // FieldPresentationSpinner.setOnItemSelectedListener
                doc.kontragent = newKontragent;
                doc.dogovorKontragenta = newDogovor;
 
                // В списке выбора отобразим все договора по выбранному контрагенту
                spinDogovor.build(doc,
                                DocumentZakazPokupatelya.FIELD_NAME_DOGOVOR_KONTRAGENTA,
                                getHelper());
        }
 
        @Override
        public void onNothingSelected(AdapterView<?> arg0) {
        }
};
/*
 * Обработчик выбора контрагента из выпадающего списка. Установка договора
 * по умолчанию
 */
private OnItemSelectedListener onKontragentSelectListener = new OnItemSelectedListener() {

    	@Override
    	public void onItemSelected(AdapterView<?> parent, View view,
                    	int position, long id) {

            	// Если выбрана группа – восстановим предыдущее значение
            	CatalogKontragenti newKontragent = (CatalogKontragenti) spinKontragent
                                    	.getSelectedItem();
            	if (newKontragent.isFolder()) {
                    	spinKontragent.setSelection(lastKontragetPosition);
                    	showToast(getString(R.string.msg_not_select_folder));
                    	return;
            	}

            	lastKontragetPosition = spinKontragent.getSelectedItemPosition();
            	CatalogDogovoriKontragentov newDogovor = dogovorDao
                                    	.getLast(newKontragent);

            	// Необходимо вручную установить значение реквизита документа, если
            	// переназначен обработчик изменения
            	// FieldPresentationSpinner.setOnItemSelectedListener
            	doc.kontragent = newKontragent;
            	doc.dogovorKontragenta = newDogovor;

            	// В списке выбора отобразим все договора по выбранному контрагенту
            	spinDogovor.build(doc,
                            	DocumentZakazPokupatelya.FIELD_NAME_DOGOVOR_KONTRAGENTA,
                            	getHelper());
    	}

    	@Override
    	public void onNothingSelected(AdapterView<?> arg0) {
    	}
};

Теперь при изменении контрагента, список договоров будет автоматически обновляться, а в  качестве договора по умолчанию устанавливается последний –  метод getLast добавлен в менеджер CatalogDogovoriKontragentovDao.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Получить последний договор (по дате создания)
 * @param owner контрагент владелец
 * @return
 */
public CatalogDogovoriKontragentov getLast(CatalogKontragenti owner) {
        CatalogDogovoriKontragentov dogovor = null;
 
        QueryBuilder<CatalogDogovoriKontragentov, String> builder = queryBuilder();
        try {
                builder.where().eq(CatalogDogovoriKontragentov.FIELD_NAME_OWNER,
                                owner);
                builder.orderBy(CatalogDogovoriKontragentov.FIELD_NAME_DATA, false);
                builder.limit(1L);
                dogovor = queryForFirst(builder.prepare());
        } catch (SQLException e) {
                e.printStackTrace();
        }
        return dogovor;
}
/**
 * Получить последний договор (по дате создания)
 * @param owner контрагент владелец
 * @return
 */
public CatalogDogovoriKontragentov getLast(CatalogKontragenti owner) {
    	CatalogDogovoriKontragentov dogovor = null;

    	QueryBuilder<CatalogDogovoriKontragentov, String> builder = queryBuilder();
    	try {
            	builder.where().eq(CatalogDogovoriKontragentov.FIELD_NAME_OWNER,
                            	owner);
            	builder.orderBy(CatalogDogovoriKontragentov.FIELD_NAME_DATA, false);
            	builder.limit(1L);
            	dogovor = queryForFirst(builder.prepare());
    	} catch (SQLException e) {
            	e.printStackTrace();
    	}
    	return dogovor;
}

На этом все. За кадром остались контролы: FieldCheckBox, FieldTextView и FieldToggleButton. Их использование аналогично и не должно вызывать у вас трудностей. Стоит отметить также, что реквизит типа ValueStorage (если он содержит картинку) может быть автоматически отражен на стандартный ImageView.

Вообще, группировать и использовать ObjectView c контролами Filed<>  можно как угодно:
- несколько  ObjectView на одной форме;
- только контролы Filed<>  на форме в т.ч и подчиненные разным объектам;
- разработка своих кастомных «представлений» на основе ObjectView или любого другого макета наследника от ViewGroup;
- разработка “форм” с использованием фрагментом и прочее.

Дополнительно

Смотрите также в исходном коде примера:
- как использовать ActionMode для контекстного меню списка документов. Меню “Действия” отображается в верхней части приложения по длительному нажатию на элемент в списке.  

- как уведомить другое activity  при изменении или добавлении документа, получить это уведомление и обработать его.

Полный код примера доступен в SVN хранилище https://xp-dev.com/svn/fba_toolkit_public/samples/fbaSample3Order/

Переключитесь на соответствующую ревизию в SVN-репозитарии (см. комментарий).

Похожие записи: