Microsoft Access 掲示板

フォームを閉じる際のDirty処理につきまして

9 コメント
views
4 フォロー

いつもお世話になっております。

今現在、ACCESSのフォームとして、起動時に開く『F_メイン』と、
情報の編集・閲覧を行う『F_部品』『F_製品』があります。

メインフォームの終了ボタンクリック時に以下のように、2つのフォームが変更されているか
どうかを確認する処理を入れています。

Private Sub 終了ボタン_Click()

  DoCmd.OpenForm "F_部品"
  If Forms!F_部品.Dirty Then
    MsgBox "[部品フォーム] が変更されています。" & Chr(13) _
    & "データの変更を保存してください。", vbOKOnly + vbCritical, "エラー! 終了できません"
    Exit Sub
  Else
    DoCmd.Close acForm, "F_部品"
  End If

  DoCmd.OpenForm "F_製品"
  If Forms!F_製品.Dirty Then
    MsgBox "[製品フォーム] が変更されています。" & Chr(13) _
    & "データの変更を保存してください。", vbOKOnly + vbCritical, "エラー! 終了できません"
    Exit Sub
  Else
    DoCmd.Close acForm, "F_製品"
  End If

  DoCmd.Quit acQuitPrompt
End Sub

これで、うっかりデータを変更しちゃってフォームを閉じちゃった際にも、
データ変更云々のメッセージを表示できるようになりました。
しかし、部品フォーム・製品フォームの読み込み時イベントに、下記のような
UseLog を Call Uselog("●●","△△") で呼び出しており、
メインフォームを閉じる際に(上記で部品フォームとかを開くので)それが働いてしまいます。


Public Sub UserLog(ByVal PlaceName As String, ByVal ActionName As String)

  Dim DB As DAO.Database: Set DB = CurrentDb
  Dim WSH As Object: Set WSH = CreateObject("WScript.Network")
  Dim PN As String: PN = PlaceName
  Dim AN As String: AN = ActionName
    
  DB.Execute "insert into T_UserLog (Place,Action,UserN,MachineN) values('" & PN _
  & "','" & AN & "','" & WSH.UserName & "','" & WSH.ComputerName & "')"

これにより、ユーザログ蓄積テーブルに、部品フォームを開いた旨等が記録されてしまいます。
(しかも何故か部品フォームの方だけ毎回、どちらかを変更しているとどちらも記録)
このUserLogを働かせずに、かつ、閉じてしまったフォームの変更の是非を問えるようにするには
どうしたらよいのでしょうか?

稚拙な質問ではございますがよろしくお願い致します。

また、VBAに問題や改善点などありましたら、そこもお教え下さると幸甚に存じます。

ゲッキョク駐車場
作成: 2020/12/24 (木) 15:47:51
通報 ...
1

これで、うっかりデータを変更しちゃってフォームを閉じちゃった際にも、
データ変更云々のメッセージを表示できるようになりました。

そもそも、これが勘違いではないでしょうか。
フォームを閉じる時に、もしレコードが更新されていたら、自動的に保存されて、Dirtyプロパティはリセットされます。
DoCmd.OpenForm "F_部品" で開いた直後、DirtyプロパティはFalseになっているはずです。

もし、「データ変更云々のメッセージを表示」のメッセージが出るなら、フォームを開く時、読み込み時、レコード移動時、、などで、レコードを更新する処理が記述されていることになります。

2
ゲッキョク駐車場 2020/12/25 (金) 08:57:15 修正 cb55f@f6500 >> 1

お早いご返信、ありがとうございます。
前に1度、部品フォームを変えてから閉じて、メインフォームを閉じようとしたら閉じれなかった
と他の人から連絡を受けたため、このような形になっていました。
しかし、考えてみると、確かにご指摘通りへんてこりんですね…。
申し訳ありません。
MSGBOXの出現も、いろいろ試したところ、フォーム開きっぱなしの時だけでした。

部品フォームを閉じるときに、以下のようにすれば問題ないでしょうか…?

Private Sub Form_Close()

  If Me.Dirty Then
    If MsgBox("データが変更されています。保存して終了しますか?", vbYesNo) = vbNo Then
      Exit Sub

    Else
      Me.BeforeUpdate = ""  'キャンセル無効
      DoCmd.RunCommand acCmdSaveRecord
      'DoCmd.RunCommand acCmdRefresh
      Me.BeforeUpdate = "[イベント プロシージャ]"  'キャンセル有効

      Call UserLog("MNFCT", "Update")
    End If
  End If
End Sub

以上、よろしくお願いいたします。

3
hiroton 2020/12/25 (金) 13:35:17 ca77e@f966d

よくわからないんですが、編集(変更)したのに保存したくない場合があるんですか?

データの保存(する/しない)と、フォームを閉じるは、直接的には無関係です。変更中の内容をどうこうしたいなら更新前処理で制御すべきでしょう

4
ゲッキョク駐車場 2020/12/25 (金) 14:19:18 修正 cb55f@f6500 >> 3

ありがとうございます。

都合により、データの閲覧と編集を同じフォームで行っています。
(フォームを分けるという理解を得られていない為、これは変更できなさそうです)

データ編集ユーザ用に、保存ボタンや編集クリアボタン等を配置しています。
しかし、データを見るだけのつもりのユーザーが誤って一部のデータを変更し、
それに気が付かぬまま(=編集クリアボタン等を押さぬまま)フォームを
閉じようとする際(およびレコード移動する際)に、MSGBOXで警告するようにしています。

フォームはマスタテーブルをレコードソースとしており、フォーム内の
TXTBOXを変更した際に、すぐテーブル内容が書き換わってしまわないように、

Private Sub Form_BeforeUpdate(Cancel As Integer)
  Cancel = True
End Sub

としています。

根本的にデータ更新タイミングなどが間違っているということでしょうか…

5
hiroton 2020/12/25 (金) 15:34:54 ca77e@f966d

とりあえず
フォームの状態を知るには?(T'sWareさん)

質問のコードを次のように変更すれば動作はすると思います

  DoCmd.OpenForm "F_部品"
↓
  If SysCmd(acSysCmdGetObjectState, acForm, "F_部品") <> 0 Then
(中略)
  End If

フォームが開いているかどうかをチェックし、開いているときに限りDirtyプロパティをチェックすれば良いということですね


根本的にデータ更新タイミングなどが間違っているということでしょうか

データの更新自体は様々なタイミングで発生します。フォームを閉じるというアクションを取った場合は、Closeイベントの前にBeforeUpdateイベントが発生し、BeforeUpdateイベントが終わると保存内容が決定されます
(実際にはもっと様々なイベントが発生します)

イベントは1つのアクションに対して連鎖的に複数発生する場合があるので、どのイベントで処理するか適切に判断する必要があります

Closeイベントの時点ではすでにデータが決定されてしまっているので、そこで保存するかどうかの確認や、再度データを変更・保存しなおすような処理はすべきではありません

「データの更新処理が済んでないうちはフォームを閉じる処理をしてはいけない」という意味で、修正しようとした内容よりも質問時点でのコードのほうが考え方が正しいです

6
ゲッキョク駐車場 2020/12/25 (金) 16:07:41 修正 cb55f@f6500

毎度、手取り足取り本当にありがたいです。

Dirtyは、開いてないと使えないヨ、くらいしか知識が無かったので
Syscmdも有効活用していきたいと思います!
ありがとうございます…!
まだまだイベントのタイミングがうまくつかめていないので、
その部分についてももう一度勉強しなおしてみようと思います。

また、部品フォームのDirty部分なんですが、以下のような形にしました。

Private Sub 部品F閉ボタン_click()

  If Me.Dirty Then
    If MsgBox("データが変更されています。保存して終了しますか?", vbYesNo) = vbNo Then
      Exit Sub

    Else
      Me.BeforeUpdate = ""  'キャンセル無効
      DoCmd.RunCommand acCmdSaveRecord
      'DoCmd.RunCommand acCmdRefresh
      Me.BeforeUpdate = "[イベント プロシージャ]"  'キャンセル有効

      Call UserLog("MNFCT", "Update")
    End If
  End If

  DoCmd.Close
End Sub

メインフォームの方はこのように

Private Sub 終了ボタン_Click()
  On Error GoTo ErrHandler

  If SysCmd(acSysCmdGetObjectState, acForm, "F_部品") <> 0 Then
    Forms!F_部品.終了ボタン_Click
  End If

  If SysCmd(acSysCmdGetObjectState, acForm, "F_製品") <> 0 Then
    Forms!F_製品.終了ボタン_Click
  End If

  DoCmd.Quit acQuitPrompt
ErrHandler:
End Sub

SysCmdを用いた、hirotonさんの方法でなく、上記のようなものでも
今回の件については問題なく動きますでしょうか?

7
hiroton 2020/12/25 (金) 16:48:44 ca77e@f966d >> 6

理想の運用ならそれでいいと思いますが、フォームって予期せぬ閉じ方をされるんですよね
フォーム右上の×をクリックされたり(非表示にできるので何とかなる)、ACCESS本体の右上の×をクリックされたり(本体の表示をしない方法があるので何とかなる)、タスクバーから終了されたり、タスクマネージャーから強制終了されたり・・・

なので、実際にデータが決定されるタイミング(BeforeUpdate)で内容を決めましょう。という話です

Private Sub Form_BeforeUpdate(Cancel As Integer)
  If (保存しない条件) Then Cancel = True
End Sub

こうしておけば、どのような形でフォームが閉じられてもBeforeUpdateイベントを通るので保存内容の確認ができます

8
hiroton 2020/12/25 (金) 17:13:20 ca77e@f966d

ちなみに、>> 5が微妙な書き方なのは、このような処理を考えるとドツボにはまるからです。(本当にやりたいことは別なのに)BeforeUpdateCancel = Trueしたいがためだけに

閉じるときに更新前処理をキャンセルすると出るメッセージを変更(hatena chipsさん)

こんな処理考えるくらいなら

都合により、データの閲覧と編集を同じフォームで行っています。
(フォームを分けるという理解を得られていない為、これは変更できなさそうです)

これの要求を整理して、メニューに編集用、閲覧用2つボタン置く形じゃダメなの?とかの方向性を考えたほうがよっぽど建設的です

9
ゲッキョク駐車場 2020/12/26 (土) 08:35:51 cb55f@f6500

ありがとうございます。

とりあえず、通常通りの操作であれば問題なく動くようにした状態まで
もっていってから、フォームを分ける用に変えていきたいと思います。
(本格的に変えていくのは新年からとなると思いますが)

フォームの更新前処理の条件式や、フォーム構成なども見直す方向で
考えていきたいと思います。

本当にありがとうございます!