MS Access

Работа с несколькими экземплярами одной формы

Опубликовано: 09 июн 04
Рейтинг:

Автор: посетители форума, Geo
Прислал: Geo

Вопросы

Q1: Как открыть несколько экземпляров одной формы?
Q2: Как программно обратиться к конкретному экземпляру формы?
Q3: Как программно закрыть экземпляр формы?

Ответы

Q1: Как открыть несколько экземпляров одной формы

При ссылках на форму в программе Visual Basic обычно производится работа со стандартным экземпляром формы. Класс формы имеет только один стандартный экземпляр. Он создается путем открытия формы в окне базы данных, или вызовом метода DoCmd.OpenForm.

Кроме того, в программах Visual Basic for Application, начиная с версии 5.0 (MS Access 97), допускается создание нескольких экземпляров класса формы. При создании нескольких экземпляров объекта приходится работать с экземплярами, не являющимися стандартными.

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

Создать нестандартный экземпляр можно, создав в программе Visual Basic новый экземпляр класса формы:
Set frm = new Form_MyForm

Нестандартный экземпляр формы создается невидимым (свойство .Visible установлено в False). Чтобы отобразить его на экране, надо выполнить команду
frm.Visible = True

Созданный таким способом экземпляр будет открыт до тех пор, пока его не закроет пользователь или программа, а также лишь то время, в течение которого на него будет указывать хотя бы одна ссылка в переменной (см. help, "Время жизни переменных").

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

1.1. Хранение ссылок на экземпляры форм в переменных стандарного модуля

Ссылку на экземпляр формы можно хранить в обычной переменной типа Variant, Form или типа класса формы (Form_MyForm). Следующие выражения определяют глобальную переменную для хранения ссылки на экземпляр формы:
Public frm [As Variant]
Public frm As Form
Public frm As Form_MyForm
причем последний вариант:
- создает переменную, способную хранить ссылку лишь на экземпляр формы MyForm;
- не позволяет объявить такую переменную WithEvents с целью дальнейшего перехвата стандартных событий формы (хотя можно описывать в модуле формы свои события и перехватывать их).

Переменная, объявленная одним из этих способов, может хранить ссылку лишь на один экземпляр формы, поэтому, если требуется открывать одновременно несколько экземпляров формы, надо либо заранее объявлять соответствующее количество переменных, либо, что гораздо удобнее, воспользоваться массивами или коллекциями:

Способ 1. Хранение ссылок в массиве

Создайте стандартный модуль с содержимым:

Option Base 0
Option Explicit

Public frmArr() As Form

Public Function KeepNewFormClone(frm As Form) As Form
  On Error Resume Next
  ReDim Preserve frmArr(UBound(frmArr) + 1)
  Select Case Err.Number
    Case 0
    Case 9: 
      ReDim frmArr(0)
        ' Массив еще не инициализирован - не
        ' создавалось ни одного экземпляра
    Case Else:
      GoTo Err_KNFC
    End Select
  On Error GoTo Err_KNFC
  Set frmArr(UBound(frmArr)) = frm
  Set KeepNewFormClone = frm
  Exit Function
Err_KNFC:
  Err.Raise Err.Number
End Function


Способ 2. Хранение ссылок в коллекции

Создайте стандартный модуль с содержимым:

Option Explicit

Public frmColl As New Collection

Public Function KeepNewFormClone(frm As Form) As Form
  On Error GoTo Err_KNFC
  frmColl.Add frm
  Set KeepNewFormClone = frm
  Exit Function
Err_KNFC:
  Err.Raise Err.Number
End Function


Чтобы создать новый нестандартный экземпляр формы MyForm и отобразить его на экране, в обоих случаях надо выполнить команду
KeepNewFormClone(New Form_MyForm).Visible = True

1.2. Хранение ссылок на экземпляры форм в самих экземплярах

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

Public ptrToMe As Form

Private Sub Form_Open(Cancel As Integer)
  Set ptrToMe = Me
End Sub


Чтобы создать новый нестандартный экземпляр формы MyForm и отобразить его на экране, надо выполнить следующий код:

Dim f As Form
Set f = New Form_MyForm
f.Visible = True
Set f = Nothing


Обратите внимание, что после выполнения строки
Set f = New Form_MyForm
на созданный экземпляр MyForm хранится две ссылки: одна - в самом экземпляре, в переменной frm, а другая в переменной f. Вторую ссылку после отображения формы мы удаляем, очистив переменную
Set f = Nothing
но форма остается открытой, т.к. на нее осталась еще одна ссылка в самой форме.

1.3. Примечания
  • Нельзя создать нестандартный экземпляр формы по строке - ее имени. В Access'е не существует функции вида OpenClone("MyForm"), создающей новый нестандартный экземпляр формы.
  • Вы можете менять свойства любого нестандартного экземпляра формы и его контролов, или модуль этого экземпляра (для файлов .mdb/.adp), но все сделанные изменения будут потеряны при закрытии экземпляра.
  • Переключить нестандартный экземпляр формы в режим конструктора невозможно.
  • Существует еще один способ программного создания идентичных форм - вызов функции CreateForm с последующим созданием в новой форме требуемых объектов и заданием их свойств, и переводом формы в режим формы. Использование этого способа недопустимо в файлах .mde/.ade, кроме того созданные им формы являются стандартными экземплярами независимых форм, а не экземплярами одной.

    Q2: Как программно обратиться к конкретному экземпляру формы

    К стандартному экземпляру формы можно обратиться программно (см. //https://www.sql.ru/faq/faq_topic.aspx?fid=156 ) одним из следующих способов:
    Forms![Форма1] (1)
    Forms("Строка - имя формы") (2)
    Forms(ПорядковыйНомерФормыВСемействеForms) (3)
    либо полным перебором семейства Forms и выбором формы по требуемому условию:
    Dim f As Form
    For Each f in Forms
    If f.Tag = "Искомая форма" Then Exit For
    Next f
    (4)

    К нестандартному экземпляру формы можно обратиться только способом (3) или (4). При обращении к экземплярам с помощью индекса в семействе следует не забывать, что после закрытия каждой формы индексы остальных форм могут изменяться.

    2.1. Обращение к экземпляру формы с помощью индекса в семействе Forms

    Dim t As Integer
    Dim Found as Boolean
    Dim f As Form
    
    Found = False
    For t = 0 to Forms.Count - 1
      If Forms(t).Tag = "Искомая форма" Then 
        Found = True
        Set f = Forms(t)
        Exit For
      End If
    Next t
    ' Если форма найдена, для примера изменим ее заголовок
    If Found Then
      f.Caption = "test"
    End If
    


    2.2. Обращение к экземпляру формы с помощью перебора семейства Forms

    Dim f As Form
    For Each f in Forms
      If f.Tag = "Искомая форма" Then 
        Found = True
        Exit For
      End If
    Next f
    ' Если форма найдена, для примера изменим ее заголовок
    If Found Then
      f.Caption = "test"
    End If
    


    Кроме того, если ссылки на создаваемые экземпляры хранятся в глобальных переменных - массиве или коллекции, к ним можно обратиться с помощью этих переменных.

    2.3. Для экземпляров форм, ссылки на которые хранятся в массиве

    Dim f As Form
    Dim Found as Boolean
    
    Found = False
    On Error Resume Next
    For Each f in frmArr
      Err.Clear
      If f.Tag = "Искомая форма" Then 
        If Err.Number = 0 Then ' Могла возникнуть ошибка, если
                               ' экземпляр уже был закрыт.
        Found = True
        Exit For
      End If
    Next f
    ' Если форма найдена, для примера изменим ее заголовок
    If Found Then
      f.Caption = "test"
    End If
    


    2.4. Для экземпляров форм, ссылки на которые хранятся в коллекции

    Dim f As Form
    Dim Found as Boolean
    
    Found = False
    On Error Resume Next
    For Each f in frmColl
      Err.Clear
      If f.Tag = "Искомая форма" Then 
        If Err.Number = 0 Then ' Могла возникнуть ошибка, если
                               ' экземпляр уже был закрыт.
          Found = True
          Exit For
        End If
      End If
    Next f
    ' Если форма найдена, для примера изменим ее заголовок
    If Found Then
      f.Caption = "test"
    End If
    


    2.5. Примечание
  • Как уже было сказано выше, ссылки на нестандартные экземпляры формы по ее имени в семействе Forms не допускаются. Подобная попытка приведет к перехватываемой ошибке. Однако, если одновременно открыть стандартный экземпляр формы с помощью DoCmd.OpenForm "MyForm", а также один или несколько нестандартных экземпляров, ошибки при обращении Forms("MyForm") не возникнет, и оно всегда будет возвращать ссылку именно на стандартный экземпляр формы MyForm.

    Q3: Как программно закрыть экземпляр формы

    Программно закрыть экземпляр формы, как стандартный, так и нестандартный, можно с помощью команды
    DoCmd.Close acForm, "FormName"

    Этот способ в случае с нестандартными экземплярами достаточно неудобен - неизвестно заранее, какой именно из нескольких экземпляров одной формы будет закрыт при закрытии по имени. Однако он подходит в случае, если в цикле надо закрыть все экземпляры формы.

    Вариант этого способа - это вызов DoCmd.Close без аргументов, предварительно установив фокус на тот экземпляр формы, который требуется закрыть (если это возможно - в частности, если свойство .Visible формы установлено в True и форма отображается на экране):
    Forms("FormName").SetFocus
    DoCmd.Close

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

    3.1. Закрытие конкретного экземпляра формы с помощью DoСmd.Close

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

    If Found Then
      f.SetFocus
      DoCmd.Close
      Set f = Nothing
    End If
    


    В случае если экземпляр может быть невидим, лучше изменить этот пример так:

    If Found Then
      Dim formVisible as Boolean
    
      On Error Resume Next 
      ' Программа должна обязательно дойти до конца, иначе может
      ' не включиться обновление экрана при возникновении ошибки
      DoCmd.Echo False ' Отключаем отображение на экране
                       ' наших действий во избежание
                       ' ненужных визуальных эффектов.
      formVisible = f.Visible
      f.Visible = True
      f.SetFocus
      DoCmd.Close
      If Err.Number 0 then ' Не удалось закрыть форму -
                              ' вновь делаем ее невидимой,
                              ' если надо.
        f.Visible = formVisible
      End If
      Set f = Nothing
      DoCmd.Echo True
      On Error Goto 0
    End If
    


    3.2. Закрытие экземпляра формы, ссылка на который хранится в глобальной переменной

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

    Способ 1. Для случая, если ссылки на экземпляры хранятся в массиве

    Public Sub CloseFormClone(frm as Form)
      Dim t As Integer
      
      On Error Resume Next
      For t=0 To UBound(frmArr)
        If Err.Number0 then Exit For
        ' На случай, если массив не инициализирован
        If frmArr(t) Is Frm Then 
          Set frmArr(t) = Nothing
          Exit For
        End If
      Next t
    End Sub
    


    Способ 2. Для случая, если ссылки на экземпляры хранятся в коллекции

    Public Sub CloseFormClone(frm as Form)
      Dim t As Integer
    
      For t=0 To frmColl.Count
        If frmColl(t) Is frm Then 
          frmColl.Remove t
          Exit For
        End If
      Next t
    End Sub
    


    3.3. Закрытие экземпляра формы, ссылка на который хранится в нем самом

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

    If Found Then
      Set f.ptrToMe = Nothing
      ' Осталась еще одна ссылка - в переменной f
      End If
    Set f = Nothing
    


    3.4. Закрытие конкретного экземпляра формы с помощью Windows API

    Для того чтобы закрыть форму с помощью Windows API, в стандартном модуле надо добавить строки:

    Public Declare Function SendMessage Lib "User32" Alias "SendMessageA" _
      (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
    Public Const WM_CLOSE = &H10
    


    Тогда такой код закроет окно экземпляра формы:

    If Found Then
      Call SendMessage(f.hWnd, WM_CLOSE, 0&, 0&)
    End If
    Set f = Nothing
    


    3.5. Примечание
  • Закрытие нестандартного экземпляра формы с помощью DoCmd.Close не произойдет, если оно инициирует какую-либо проверку на правильность вводимых в ее поля данных и эта проверка не выполняется. Если же закрывать экземпляр с помощью удаления ссылки на него, форма будет закрыта вне зависимости от того, выполняется или нет проверка на корректность данных. Например, если в обработчике события Form_Unload значение аргумента Cancel устанавливается в True, Form_Unload будет выполнен, но экземпляр все равно закроется.

    Дополнительные сведения

    4.1. Необработанные ошибки

    При необработанной ошибке программы Visual Basic for Application все объявленные переменные вновь инициализируются. Это приводит, в частности, к закрытию всех открытых нестандартных экземпляров формы. Избежать подобных "казусов" можно, обрабатывая все возможные ошибки времени исполнения:

    Sub MySub()
      On Error Goto Err_Sub
      ' Тело процедуры, потенциально способное вызвать ошибку
      Exit Sub
    Err_Sub:
      ' Действия при возникновении ошибки
    End Sub
    


    или вынеся переменные, хранящие ссылки на открытые экземпляры форм, в отдельный файл, подключенный с помощью ссылки в меню Tools-References… редактора VBA.

    4.2. Удаление всех ссылок

    Перед завершением работы программы рекомендуется очистить все использованные объектные переменные. Приведем способы очистки объектных переменных, хранящих ссылки на экземпляры форм, для каждого из описанных способов хранения.

    Способ 1. Для случая, если ссылки на экземпляры хранятся в массиве

    Public Sub ClearAllFormClones() As Form
      Dim t As Integer
    
      On Error Goto IsNotInitialized
                   ' На случай, если массив еще не инициализирован
      For t=0 To UBound(frmArr) 
        On Error Goro CAFC
        Set frmArr(t) = Nothing
      Next t
    
      Exit Sub
    Err_CAFC:
      Err.Raise Err.Number
    IsNotInitialized:
    End Sub
    


    Способ 2. Для случая, если ссылки на экземпляры хранятся в коллекции

    Public Sub ClearAllFormClones() As Form
      Do While frmColl.Count>0
        frmColl.Remove 0
      Loop
    End Sub
    


    Способ 3. Для случая, если ссылки на экземпляры хранятся в них самих

    Public Sub ClearAllFormClones() As Form
      Dim t As Long
    
      On Error Resume Next
      Do While t Set Forms(t).ptrToMe = Nothing
        If Err.Number 0 Then ' Эта форма - стандартный экземпляр
          t = t + 1
          Err.Clear
        Else
          DoEvents
        End If 
      Loop
    End Sub
    


    4.3. Еще один способ открытия форм

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

    Form_Сотрудники.Visible = True
    Form_Сотрудники.Caption = "Новые сотрудники"

    При выполнении этой программы Microsoft Access открывает форму «Сотрудники» в режиме формы, если эта форма еще не открыта, и выводит новый заголовок формы. Форма не является видимой до тех пор, пока для ее свойства Вывод на экран (Visible) не задано значение True (-1). После завершения выполнения процедуры, в которой вызывается эта программа, экземпляр формы уничтожается, т.е. форма закрывается.

    При попытке выполнить эту программу, когда форма «Сотрудники» открыта в режиме конструктора, Microsoft Access генерирует ошибку выполнения. Необходимо, чтобы форма была открыта в режиме формы или вообще не была открыта.

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

    Пример работы с несколькими экземплярами формы

    Приведенный ниже пример - лишь один из описанных способов работы с экземплярами форм. Для него был выбран способ с хранением ссылки на создаваемые экземпляры в самих формах. Закрытие формы, для возможности его отмены изнутри экземпляра, выполняется с помощью DoCmd.

    В форму, в которую планируется добавить возможность открывать ее в нескольких экземплярах одновременно (допустим, она называется "MyForm"), добавьте код:

    Public ptrToMe As Form
    
    Private Sub Form_Open(Cancel As Integer)
      Set ptrToMe = Me
    End Sub
    


    Создайте стандартный модуль со следующим содержимым:

    Option Compare Database
    Option Explicit
    
    Public FormClone As Form
    
    Public Sub CloseAllClones()
      ' Безусловное закрытие всех нестандартных экземпляров
      Dim t As Long
    
      On Error Resume Next
      Do While t Set Forms(t).ptrToMe = Nothing
        If Err.Number 0 then ' Эта форма - стандартный экземпляр
          t = t + 1
          Err.Clear
        Else 
          DoEvents
        End If 
      Loop
    End Sub
    
    Public Sub CloseForm(f As Form)
      ' Закрытие стандартного или нестандартного экземпляра формы
      Dim formVisible as Boolean
    
      On Error Resume Next 
      ' Программа должна обязательно дойти до конца, иначе может
      ' не включиться обновление экрана при возникновении ошибки
      DoCmd.Echo False ' Отключаем отображение на экране
                       ' наших действий во избежание
                       ' ненужных визуальных эффектов.
      formVisible = f.Visible
      f.Visible = True
      f.SetFocus
      DoCmd.Close
      If Err.Number 0 then ' Не удалось закрыть форму -
                              ' вновь делаем ее невидимой,
                              ' если надо.
        f.Visible = formVisible
      End If
      Set f = Nothing
      DoCmd.Echo True
      On Error Goto 0
    End Sub
    


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

    Private Sub Form_Close()
      CloseAllClones
    End Sub
    


    Если такой формы нет, создайте новую форму, добавьте в нее этот код и включите ее окрытие в макрос AutoExec, аргумент команды "ОткрытьФорму" "РежимОкна" установите в "Невидимое".

    Чтобы открыть новый экземпляр формы, выполните

    Set FormClone = New Form_MyForm
    FormClone.Visible = True
    ' Здесь можно выполнить необходимые действия с новым
    ' экземпляром, используя ссылку на него в переменной
    ' FormClone
    Set FormClone = Nothing
    


    Чтобы программно закрыть экземпляр, ссылка на который находится в переменной (назовем ее frm1), надо выполнить код

    CloseForm frm1
    


    Например, так можно закрыть экземпляр, имеющий в данный момент фокус:

    CloseForm Screen.ActiveForm
    

  • Комментарии


    • А как задать клону формы такие параметры метода OpenForm, как View, FilterName, WhereCondition, DataMode, WindowMode, OpenArgs?

    • А как задать клону формы такие параметры метода OpenForm, как View, FilterName, WhereCondition, DataMode, WindowMode, OpenArgs?

    • Это все отлично работает когда форма простая.
      А если в ней есть подчиненные формы?
      или внутри модуля формы оригинала есть процедуры обработки, макросы?

    • Отлично, помогло!

    • тест

    • Замечательный материал. Помог добить недообъектноориентированность Access для аналогичного случая с отчетами, когда пользователь хочет видеть два экземпляра одного отчета с разной фильтрацией. Рекомендую написать класс для хранения коллекции переменных объектного типа...



    Необходимо войти на сайт, чтобы оставлять комментарии

    Раздел FAQ: MS Access / Работа с несколькими экземплярами одной формы