Урок 20. Пользовательские настройки

В этом уроке мы создадим карточку для редактирования пользовательских настроек, в которой каждый пользователь сможет настроить индивидуальные параметры интерфейса. К таким параметрам относится временная зона, относительно которой будут отображаться все даты со временем, и язык интерфейса.

Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.

При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.

Инструкция по подключению шаблонов находится по ссылке.

Начнем с настройки прав доступа к новой функциональности.

Права доступа

Перейдем в файл описания работы серверной части приложения (Template.xml) .

Добавим точку доступа UserSettingsEditAccessPoint для просмотра и редактирования пользовательских настроек.

Создадим разрешение UserSettingsPermission, в которое добавим эту точку доступа. Добавим это разрешение в таблицу template.permission и настроим права доступа для пользовательских групп:

INSERT INTO template.permission_block_item(permission_block_id, id_title, title)
SELECT
  PB.permission_block_id, 'user_settings', 'Пользовательские настройки'
FROM
  template.permission_block PB
WHERE
  PB.id_title = 'user_action'
ON CONFLICT (id_title) DO NOTHING;

INSERT INTO template.permission(name, permission_block_item_id)
SELECT
  'UserSettingsPermission', PBI.permission_block_item_id
FROM
  template.permission_block_item PBI
WHERE
  PBI.id_title = 'user_settings'
ON CONFLICT (name) DO NOTHING;

INSERT INTO template.group_permission(group_id, permission_id)
SELECT
  group_id,
  permission_id
FROM
  template.permission P,
  template."group" G
WHERE
  (G.name = 'AdministratorGroup' OR G.name ISNULL) AND
  P.name = 'UserSettingsPermission'
ON CONFLICT (group_id, permission_id) DO NOTHING;

Теперь при добавлении новой функциональности необходимо каждый раз выполнять подобные запросы.

Часовой пояс

В уроках первого блока мы затрагивали тему временных зон и дат со временем. А в этом уроке предоставим пользователю возможность менять временную зону, в которой он хотел бы работать с датами со временем на формах. Это актуально, когда бизнес-процесс предполагает распределенную систему офисов: кол-центр, обрабатывающий заявки, находится в одном часовом поясе, а исполнители заявок в других временных зонах.

В приложении "Временные зоны" собрана полная информация о работе с датами со временем в платформе WT.

Форма

Создайте форму для редактирования пользовательских настроек (TemplateUserSettings.xml):

На главной форме (TemplateStart.xml) в меню добавьте пункт Опции -> Пользовательские настройки..., по которому будет открываться новая форма. Не забудьте использовать UserSettingsEditAccessPoint, чтобы ограничить доступ к пункту меню.

Запросы

Добавим запрос на получение списка часовых поясов:

Template.xml
<SqlQuery Name="TimeZoneInfoSelectSqlQuery">
  <Text>
    SELECT
      time_zone_info_id AS "TimeZoneInfoId",
      string_value(id_title, {PublicUserId}) AS "Title",
      by_default AS "ByDefault"
    FROM
      public.time_zone_info;
  </Text>
</SqlQuery>

Добавим запросы на чтение и изменение значения пользовательской временной зоны:

Template.xml
<SqlQuery Name="UserSettingsSelectSqlQuery">
  <Text>
    SELECT time_zone_info_id AS "TimeZoneInfoId"
    FROM 
      public."user"
    WHERE
      user_id = {PublicUserId};
  </Text>
</SqlQuery>

<SqlQuery Name="UserSettingsUpdateSqlQuery">
  <Text>
    UPDATE public."user"
    SET
      time_zone_info_id = {TimeZoneInfoId}
    WHERE
      user_id = {PublicUserId};
  </Text>
</SqlQuery>

Добавьте три новых запроса в UserSettingsPermission.

Формы

Если пользователь изменил свой часовой пояс в настройках, то необходимо перезапустить приложение, чтобы новые настройки применились.

Добавим условие проверки изменения часового пояса:

TemplateUserSettings.xml
<Condition Name="TimeZoneInfoNotEqualPreviousValueCondition" Type="NotEqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="TimeZoneInfoComboBox" />
    </Item>
    <Item>
      <DataConnection SourceDataConnection="UserSettingsPrimaryGetDataConnection">
        <Fields>
          <Field Name="TimeZoneInfoId" />
        </Fields>
      </DataConnection>
    </Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>

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

TemplateUserSettings.xml
<Command Name="UserTimeZoneInfoChangedMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>Изменение часового пояса пользователя</Caption>
  <Text>ВНИМАНИЕ!\rВыполните перезапуск программы.</Text>
  <Icon Type="Information" />
  <Buttons Type="Ok" />
</Command>

Скорректируем команду SaveSequentialCommand:

TemplateUserSettings.xml
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="UserSettingsSaveCommand" />
    <If>
      <When>
        <Condition Name="TimeZoneInfoNotEqualPreviousValueCondition" />
      </When>
      <Then>
        <Command Name="UserTimeZoneInfoChangedMessageBoxCommand" />
      </Then>
    </If>
    <Command Name="FormCloseCommand" />
  </Commands>
</Command>

Запустите приложение и проверьте смену часового пояса.

Мультиязычность

Помимо выбора собственной часовой зоны пользователь может настраивать язык интерфейса своей клиентской части.

Настройка языка

Переделайте форму пользовательских настроек, добавив выпадающий список "Язык":

Добавим запрос на получения списка поддерживаемых языков:

Template.xml
<SqlQuery Name="LanguageSelectSqlQuery">
  <Text>
    SELECT
      language_id AS "LanguageId",
      title AS "Title",
      by_default AS "ByDefault"
    FROM
      public.language;
  </Text>
</SqlQuery>

Не забудьте добавить этот запрос в UserSettingsPermission.

В запросы UserSettingsSelectSqlQuery и UserSettingsUpdateSqlQuery добавим поле LanguageId:

Template.xml
<SqlQuery Name="UserSettingsSelectSqlQuery">
  <Text>
    SELECT
      time_zone_info_id AS "TimeZoneInfoId",
      language_id AS "LanguageId"
    FROM
      public."user"
    WHERE
      user_id = {PublicUserId};
  </Text>
</SqlQuery>

<SqlQuery Name="UserSettingsUpdateSqlQuery">
  <Text>
    UPDATE public."user"
    SET
      language_id = {LanguageId},
      time_zone_info_id = {TimeZoneInfoId}
    WHERE
      user_id = {PublicUserId};
  </Text>
</SqlQuery>

Чтобы стартовая форма узнала об изменении языка пользователя, необходимо внести доработки.

Заменим параметр Updated на параметр LanguageUpdated - так будет более наглядно:

TemplateUserSettings.xml
<Parameter Name="LanguageUpdated">False</Parameter>

Скорректируйте команду типа ValueSetCommand, которая будет присваивать этому параметру значение True, если значение языка изменилось.

Добавим условие проверки изменения зыка:

TemplateUserSettings.xml
<Condition Name="LanguageNotEqualPreviousValueCondition" Type="NotEqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="LanguageComboBox" />
    </Item>
    <Item>
      <DataConnection SourceDataConnection="UserSettingsPrimaryGetDataConnection">
        <Fields>
          <Field Name="LanguageId" />
        </Fields>
      </DataConnection>
    </Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>

Скорректируем команду SaveSequentialCommand:

TemplateUserSettings.xml
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="UserSettingsSaveCommand" />
    <If>
      <When>
        <Condition Name="TimeZoneInfoNotEqualPreviousValueCondition" />
      </When>
      <Then>
        <Command Name="UserTimeZoneInfoChangedMessageBoxCommand" />
      </Then>
    </If>
    <If>
      <When>
        <Condition Name="LanguageNotEqualPreviousValueCondition" />
      </When>
      <Then>
        <Command Name="LanguageUpdatedSetCommand" />
      </Then>
    </If>
    <Command Name="FormCloseCommand" />
  </Commands>
</Command>

Изменение языка приложения

Скорректируем представление template.user_info, расширив его новыми полями language_id и language_code:

CREATE OR REPLACE VIEW template.user_info AS 
  SELECT
    au.user_id,
    pu.user_name,
    pu.user_full_name,
    pu.person,
    NOT pu.enabled AS archive,
    pu.user_id AS public_user_id,
    pu.language_id,
    l.code AS language_code
  FROM
    template."user" au
    JOIN "user" pu ON au.public_user_id = pu.user_id
    JOIN public.language l ON l.language_id = pu.language_id;

В серверном xml-файле скорректируем запрос UserCurrentSelectSqlQuery, добавив поле LanguageCode:

Template.xml
<SqlQuery Name="UserCurrentSelectSqlQuery">
  <Text>
    SELECT
      user_id AS "UserId",
      user_name AS "UserName",
      user_full_name AS "UserFullName",
      language_code AS "LanguageCode"
    FROM
      template.user_info UI
    WHERE
      user_id = {UserId};
  </Text>
</SqlQuery>

Перейдем в файл стартовой формы и в UserCurrentPrimaryGetDataConnection добавим новое поле LanguageCode:

TemplateStart.xml
<DataConnection Name="UserCurrentPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <ManualLoad>True</ManualLoad>
  <SqlQuery Name="UserCurrentSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="UserId" />
      <Field Name="LanguageCode" />
    </Fields>
  </SqlQuery>
</DataConnection>

Создадим команду обновления для UserCurrentPrimaryGetDataConnection, удалив его из команды AllPrimaryGetDataConnectionRefreshCommand:

TemplateStart.xml
<Command Name="UserCurrentDataConnectionRefreshCommand" Type="DataConnectionRefreshCommand" Assembly="Commands">
  <DataConnections>
    <DataConnection Name="UserCurrentPrimaryGetDataConnection" />
  </DataConnections>
</Command>

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

TemplateStart.xml
<Command Name="LocaleSetCommand" Type="LocaleSetCommand" Assembly="Commands">
  <Locale>
    <DataConnection SourceDataConnection="UserCurrentPrimaryGetDataConnection">
      <Fields>
        <Field Name="LanguageCode" />
      </Fields>
    </DataConnection>
  </Locale>
</Command>

Соберем обе команды в одну последовательность:

TemplateStart.xml
<Command Name="LocaleSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="UserCurrentDataConnectionRefreshCommand" />
    <Command Name="LocaleSetCommand" />
  </Commands>
</Command>

Добавим Execution на параметр LanguageUpdated команды UserSettingsFormShowCommand:

TemplateStart.xml
<Execution>
  <ConditionExpression>
    <Command Name="UserSettingsFormShowCommand" Parameter="LanguageUpdated" />
  </ConditionExpression>
  <Commands>
    <Command Name="LocaleSequentialCommand" />
  </Commands>
</Execution>

Добавим вызов команды LocaleSequentialCommand в Execution, которые срабатывают на команды LoginFormShowCommand и ReloginFormShowCommand - тем самым мы будем задавать настройки текущего пользователя при его аутентификации в программе.

Доработка форм

Чтобы интерфейс приложения поддерживал различные языки, необходимо организовать файлы строковых ресурсов и на формах указать привязку к нужным ресурсам.

Файлы строковых ресурсов

Скачайте архив с файлами строковых ресурсов и распакуйте его в папку с формами проекта (\Template\Projects\1. Template\Forms).

В папке Language вы найдете файлы:

По договоренности внутри компании: файлы ru.wxlf и en.wxlf используем для задания строковых ресурсов для форм, а файлы object-ru.wxlf и object-en.wxlf - для кастомных элементов (команд, объектов и т.д.), которые рассмотрим в одном из следующих уроков. На самом деле платформа считывает из папки Forms\Language все файлы одной локали и записывает их в один словарь. И файлов по одной локали может быть сколько угодно - отбор происходит по имени файла, оно должно оканчиваться именем локали (ru/en).

Структура файлов одинакова:

<Wxliff xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Context>
    <Text Id="">
      <Target></Target>
    </Text>
    <Text Id="">
      <Source></Source>
      <Target></Target>
    </Text>
  </Context>
  
  <Context File="">
    <Text Id="">
      <Target></Target>
    </Text>
    <Text Id="">
      <Source></Source>
      <Target></Target>
    </Text>
  </Context>
</Wxliff>

Тэг <Wxliff> содержит список вложенных тэгов <Context>. Каждый такой тэг привязывается к конкретному файлу, имя которого указывается в атрибуте File. Если атрибут отсутствует, то такой контекст считается общим для всех форм.

Каждый контекст содержит набор строковых ресурсов, представленных тэгом <Text>, у которого есть атрибуты:

  • Id - уникальный (в рамках контекста) идентификатор ресурса, по которому форма будет его искать. Обязательный атрибут.

Тэг <Text> содержит тэги:

  • <Source> - оригинальный текст, который выступает в качестве подсказки в файле перевода. Необязательный тэг;

  • <Target> - целевая строка, которая будет использоваться на форме.

В файлах ru.wxlf и en.wxlf есть начальные данные, и указаны два тэга <Context>: общий контекст с ресурсами, которые используются на многих формах, и контекст для формы TemplateUserSettings.xml.

Использование строкового ресурса на форме

Давайте перейдем в файл формы пользовательских настроек (TemplateUserSettings.xml) и на его примере разберем использование строковых ресурсов.

В файле формы необходимо все строковые константы заменить тэгом <Text>, значением которого будет строка:

<Text Id=""></Text>

Например, на форме есть параметр Title:

TemplateUserSettings.xml
<Parameter Name="Title">Пользовательские настройки</Parameter>

Заменим его описание:

TemplateUserSettings.xml
<Parameter Name="Title">
  <Text Id="FormTitle">Пользовательские настройки</Text>
</Parameter>

В файлах ru.wxlf и en.wxlf в соответствующем контексте есть строковые ресурсы:

<!-- ru.wxlf -->
<Text Id="FormTitle">
  <Target>Пользовательские настройки</Target>
</Text>

<!-- en.wxlf -->
<Text Id="FormTitle">
  <Source>Пользовательские настройки</Source>
  <Target>User settings</Target>
</Text>

Замените текст объектов LanguageLabel и TimeZoneInfoLabel, используя идентификатор строковых ресурсов из контекста данной формы.

А для текста кнопки SaveButton используйте идентификатор строкового ресурса из общего контекста:

<Text Id="Save">Сохранить</Text>

Запустите приложение и проверьте смену языка.

Как это работает

В процессе загрузки форма считывает файл строковых ресурсов, соответствующий установленной локали приложения, и ищет в нем ресурс с указанным идентификатором.

Сначала форма ищет в общем контексте, если там нет нужного ресурса, то продолжает искать в собственном контексте.

Когда форма находит подходящий ресурс, то берет из него значение тэга <Target> и использует это значение в своих элементах.

Если форма не находит подходящего ресурса в файле строковых ресурсов, то использует текст, который указали в качестве значения тэга <Text>.

Другие замены

Продолжим работать с формой пользовательских настроек (TemplateUserSettings.xml).

На форме есть команды SaveOnCloseMessageBoxCommand и CloseOnCloseMessageBoxCommand - в них тоже необходимо подобным образом заменить значения тэгов <Caption> и <Text>:

TemplateUserSettings.xml
<Command Name="SaveOnCloseMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>
    <Text Id="SaveOnCloseMessageBox.Caption">Сохранение</Text>
  </Caption>
  <Text>
    <Text Id="SaveOnCloseMessageBox.Text">Форма содержит несохраненные изменения.\rСохранить их перед закрытием?</Text>
  </Text>
  <Icon Type="Question" />
  <Buttons Type="YesNoCancel" />
</Command>

<Command Name="CloseOnCloseMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>
    <Text Id="CloseOnCloseMessageBox.Caption">Закрытие</Text>
  </Caption>
  <Text>
    <Text Id="CloseOnCloseMessageBox.Text">При закрытии все несохраненные изменения будут утеряны.\rВы уверены, что хотите закрыть форму?</Text>
  </Text>
  <Icon Type="Question" />
  <Buttons Type="YesNo" />
</Command>

Текст кнопок в диалоговом окне отображается на том языке, который задан в настройках операционной системы.

Аналогичным образом сделайте с командой UserTimeZoneInfoChangedMessageBoxCommand.

Замена констант

На самом деле подобные подстановки ресурсов мы можем делать с любыми константами на форме.

В качестве примера мы можем привязать ширину кнопки SaveButton к локали.

Добавим в файлы строковых ресурсов значения:

<!-- ru.wxlf -->
<Text Id="SaveButton.Width">
  <Source>200</Source>
  <Target>200</Target>
</Text>

<!-- en.wxlf -->
<Text Id="SaveButton.Width">
  <Source>200</Source>
  <Target>100</Target>
</Text>

Запустите приложение и проверьте отображение кнопки при использовании разных языков:

Эти изменения можно убрать - они носили демонстрационный характер.

ComboBox

Перейдем в файл карточки назначения платежа (TemplateOperationEdit.xml) и найдем там описание объекта CategoryComboBox.

В объекте используется вложенный тэг <NullValue> для настройки отображения NULL-значения выпадающего списка:

<NullValue Show="True" Title="[Не выбрано]" />

Использование атрибута Title в тэге <NullValue>не позволяет заменять его значение на строковый ресурс. Поэтому заменим атрибут Title на тэг <NullValueTitle>. Это позволит динамически задавать отображение NULL-значения объекта, в том числе для поддержки мультиязычности.

В файлах строковых ресурсов в общем контексте уже есть нужное значение. Его и будем использовать:

<NullValue Show="True" />
<NullValueTitle>
  <Text Id="NotChosen">[Не выбрано]</Text>
</NullValueTitle>

На некоторых формах у нас есть фильтр архивных актуальных записей (ArchiveFilterComboBox):

<MyObject Name="ArchiveFilterComboBox" Type="ComboBox" Assembly="BaseControls">
  <Top>...</Top>
  <Right>...</Right>
  <Width>100</Width>
  <TabIndex>1</TabIndex>
  <NullValue Show="True" Title="Все" />
  <AutoCompleteMode>SmartSuggest</AutoCompleteMode>
  <ValueList>
    <Structure Type="Table">
      <Row>
        <Item>False</Item>
        <Item>Актуальные</Item>
      </Row>
      <Row>
        <Item>True</Item>
        <Item>Архивные</Item>
      </Row>
    </Structure>
  </ValueList>
  <Value>False</Value>
</MyObject>

При поддержке мультиязычности он примет вид:

<MyObject Name="ArchiveFilterComboBox" Type="ComboBox" Assembly="BaseControls">
  <Top>...</Top>
  <Right>...</Right>
  <Width>100</Width>
  <TabIndex>1</TabIndex>
  <NullValue Show="True" />
  <NullValueTitle>
    <Text Id="ArchiveFilterComboBox.All">Все</Text>
  </NullValueTitle>
  <AutoCompleteMode>SmartSuggest</AutoCompleteMode>
  <ValueList>
    <Structure Type="Table">
      <Row>
        <Item>False</Item>
        <Item>
          <Text Id="ArchiveFilterComboBox.Actual">Актуальные</Text>
        </Item>
      </Row>
      <Row>
        <Item>True</Item>
        <Item>
          <Text Id="ArchiveFilterComboBox.Archival">Архивные</Text>
        </Item>
      </Row>
    </Structure>
  </ValueList>
  <Value>False</Value>
</MyObject>

Поддержка языков в SQL-запросах

В некоторых запросах мы уже сталкивались с функцией public.string_value(character varying, integer), которая относительно настроек языка пользователя возвращала по ключу из таблицы public.strings соответствующую строку.

В этой таблице хранятся системные строки, редактировать которые пользователи не могут.

Например, в запросе LoadModeSelectSqlQuery для получения списка доступных режимов загрузки данных мы использовали эту функцию. В таблице уже были необходимые строковые ресурсы:

В своей практике мы используем функцию public.check_strings(), которая проверяет наличие переводов для всех языков и вернет список ключей, у которых отсутствует перевод для какого-либо языка.

Ниже приведен код этой функции, чтобы вы могли добавить ее в свой проект и использовать для проверки строк перевода.

CREATE OR REPLACE FUNCTION public.check_strings()
  RETURNS character varying AS
$BODY$
DECLARE
  _row record;
  _result varchar[];
BEGIN
  FOR _row IN
    (WITH min_value AS
      (SELECT MIN(language_id) AS val FROM language)
      SELECT
        val,
        language_id
      FROM
        min_value
        CROSS JOIN
        language
       WHERE
        language_id != val
       ORDER BY 2)
  LOOP
    EXECUTE 'SELECT
      array_agg(strings_id)
    FROM
      (SELECT strings_id FROM strings WHERE language_id = ' || _row.val || ') S1
      FULL JOIN
      (SELECT strings_id FROM strings WHERE language_id = ' || _row.language_id || ') S2
      USING(strings_id)
    WHERE
      S1.strings_id IS NULL OR S2.strings_id IS NULL'
    INTO
      _result;

    IF (_result IS NOT NULL) THEN
      RETURN _result::varchar;
    END IF;
  END LOOP;
  
  RETURN NULL;
END;  
$BODY$
  LANGUAGE plpgsql;

Самостоятельно

Переведите все формы на поддержку языков.

Не забудьте перевести строковые константы в запросах для списка назначений платежей и отчета по бюджету. Добавьте строковые ресурсы в таблицу public.strings и используйте функцию public.string_value(character varying, integer) для получения нужного значения, как это сделано в запросе TimeZoneInfoSelectSqlQuery и LoadModeSelectSqlQuery.

Также добавьте перевод для описания динамических прав доступа - запрос PermissionBlockItemSelectSqlQuery. Используйте значения в колонках id_title таблиц template.permission_block и template.permission_block_item в качестве ключа для строковых ресурсов.

На форму настроек добавьте возможность редактировать временную зону, выбранную по умолчанию для новых пользователей.

Пользовательские настройки форм

Дополнительно к пользовательским настройкам можно отнести настройки видимости, порядка и ширины колонок таблицы DatabaseTable, сортировку строк и фильтры в таблице. Также можно запоминать состояние формы и значения переменных, а при следующем открытии формы восстанавливать их.

Все это можно сохранять для каждого пользователя отдельно.

Настройки таблиц

Для сохранения настроек в таблице DatabaseTable реализован вложенный тэг <SaveOnFormClose>:

<SaveOnFormClose Columns="True" Filter="True" Sort="True" />

Информация о настройках пишется в таблицу public.user_form_info. Для каждой формы и каждого пользователя отдельная запись.

Давайте перейдем в файл списка клиентов (TemplateClientList.xml) и в таблицу ClientDatabaseTable добавим тэг <SaveOnFormClose>.

Когда откроем и закроем окно списка клиентов, в базе данных в таблице появится запись:

Значение в колонке name совпадает со значение в атрибуте Name тэга <Form>.

Так как мы просто открыли и закрыли форму, то в колонке tables_info хранится информация только о колонках:

{
  "(
    ClientDatabaseTable,
    \"{
      \"\"(ClientId,0,0,f)\"\",
      \"\"(RowNumber,30,1,t)\"\",
      \"\"(Name,0,2,t)\"\",
      \"\"(CityTitle,0,3,t)\"\",
      \"\"(Phone,150,4,t)\"\",
      \"\"(Archive,0,5,f)\"\"
    }\",
    {},
    {}
  )"
}

Чтобы можно было менять размер колонок, необходимо в таблице атрибуту Value тэга <AllowResizeColumns> поставить значение True, а для колонки атрибуту Value тэга <AutoSizeMode> поставить значение None.

Давайте это сделаем для колонки CityTitle (Город). Откроем форму списка клиентов, изменим ширину колонки "Город" и закроем окно. При повторном открытии формы мы увидим, что размер колонки сохранился, а в базе будет запись вида:

{
  "(
    ClientDatabaseTable,
    \"{
      \"\"(ClientId,0,0,f)\"\",
      \"\"(RowNumber,30,1,t)\"\",
      \"\"(Name,0,2,t)\"\",
      \"\"(CityTitle,200,3,t)\"\",
      \"\"(Phone,150,4,t)\"\",
      \"\"(Archive,0,5,f)\"\"
    }\",
    {},
    {}
  )"
}

Добавим настраиваемый фильтр в таблицу через контекстное меню, кликнув ПКМ по заголовку таблицы:

В таблицу сохранится запись вида:

{
  "(
    ClientDatabaseTable,
    \"{
      \"\"(ClientId,0,0,f)\"\",
      \"\"(RowNumber,30,1,t)\"\",
      \"\"(Name,0,2,t)\"\",
      \"\"(CityTitle,200,3,t)\"\",
      \"\"(Phone,150,4,t)\"\",
      \"\"(Archive,0,5,f)\"\"
    }\",
    \"{
      \"\"ClassName\"\": \"\"WorkflowForms.Controls.DatabaseTableFilterGroupOperation\"\",
      \"\"Condition\"\": \"\"AND\"\",
      \"\"OperationList\"\": [{
        \"\"Value\"\": \"\"че\"\",
        \"\"ClassName\"\": \"\"WorkflowForms.Controls.DatabaseTableFilterStartWithOperation\"\",
        \"\"ColumnName\"\": \"\"CityTitle\"\"
      }]
    }\",
    {}
  )"
}

Давайте сбросим фильтрацию и добавим настраиваемую сортировку:

Теперь в таблицу сохранится запись вида:

{
  "(
    ClientDatabaseTable,
    \"{
      \"\"(ClientId,0,0,f)\"\",
      \"\"(RowNumber,30,1,t)\"\",
      \"\"(Name,0,2,t)\"\",
      \"\"(CityTitle,200,3,t)\"\",
      \"\"(Phone,150,4,t)\"\",
      \"\"(Archive,0,5,f)\"\"
    }\",
    {},
    \"[
      {
        \"\"Order\"\": 0,
        \"\"SortOrder\"\": \"\"ASC\"\",
        \"\"ColumnName\"\": \"\"CityTitle\"\"
      },
      {
        \"\"Order\"\": 1,
        \"\"SortOrder\"\": \"\"DESC\"\",
        \"\"ColumnName\"\": \"\"Name\"\"
      }
    ]\"
  )"
}

Состояние формы

Чтобы сохранять состояние формы, у тэга <Form> есть атрибут RestoreLastFormState. Если значение атрибута равно True, то состояние формы будет запоминаться в колонку form_state таблицы public.user_form_info.

Поддерживается три состояния:

  • 0 - окно с размерами по умолчанию (Normal);

  • 1 - свернутое окно (Minimized);

  • 2 - развернутое окно (Maximized).

Значение Variable

Для сохранения значения переменной Variable реализован вложенный тэг <SaveOnFormClose>:

<SaveOnFormClose Value="True" />

Значения переменных сохраняются в колонку variables_info таблицы public.user_form_info.

На форме списка клиентов создайте тестовую переменную, которой укажите тэг <SaveOnFormClose> со значением True и каким-нибудь значением. Откройте и закройте форму списка, а затем проверьте содержимое таблицы public.user_form_info.

Итоги

В этом уроке мы познакомились с поддержкой языков в платформе WT и реализовали пользовательские настройки. Узнали, что на формах можно сохранять настройки видимости, ширину и порядок столбцов таблицы, фильтры и сортировку строк в DatabaseTable, а также состояние самой формы.

Ответы

В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.

Last updated