■101142 / inTopicNo.26) |
Re[19]: COMオブジェクトで起動したExcelの印刷を行うと例外発生 |
□投稿者/ 魔界の仮面弁士 (3529回)-(2023/01/09(Mon) 12:55:54)
|
■No101131 (ジェイド さん) に返信
> 皆さんに教えて頂いた内容を元に以下のような対応をしてみました。
> これにて解決にさせていただきたいと思います。
解決済みチェックは付けたままにしておきますが、幾つか気になった点をひとりごちてみるなど。
(主問題は解決済みのようなので、この投稿は無視して頂いていも構いません)
■No101125 (ジェイド さん) に返信
> ただ、Load後にどうやって流そうかと考えているのですが、、、
そもそも、表示させたくないフォームに判断処理を書いていることが、
根本的な要因であるような気もしなくもなかったり。
それはさておき、イベント処理後に何かしたい場合には、
「Application の Idle イベント」を使うという手もあります。
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.application.idle?view=netframework-3.5
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
'Load されてアイドリング状態になった時に、AutoClose が呼び出されるようにする
AddHandler Application.Idle, AddressOf AutoClose
End Sub
Private Sub AutoClose(sender As Object, e As EventArgs)
'一度しか処理したくないので、初回実行時に解除する
RemoveHandler Application.Idle, AddressOf AutoClose
'一度だけ実行させたい処理
Me.Close()
End Sub
> 当プログラムなのですが、メインプログラムからプロセス起動されるサブプログラムでして、
> 表に出ない仕様となっています。
表示されていないという事は、終了ボタンとかも無いという事ですよね…。
それを終了する手段は、サブプログラム自身が自殺することだけだったりするのかな?
(あるいはタスクマネージャーからの強制終了とか)
常時非表示なのであれば、サブプログラム側は Form は一切使わずに、
Sub Main() 起動の実装で十分なのでは…とも思っていたのですが、
あえて Form を残した実装にしているのは、前任者(あるいはさらにその前の設計者)に、
何かしらの意図があったのかもしれないのが悩ましいところ。
コードを見ていない第三者側で判断できることでは無いですけれど。
たとえば、メインプログラム側から終了コマンドを受信できるような仕組みが用意されている場合、
非表示フォームを使った方がやりとりしやすいですし、Windows のシャットダウン通知を
捉えたい場合も、Form の FormClosing のイベント引数を用いて判断できるといったメリットがあるかも。
ただ No10191 でも述べたように、Load 中に Close する実装というのは少々乱暴かな…と思わなくもない。
「Form を開く前に開くかどうかを先に判断し、開いても良い場合だけ Form を呼び出す」か、
「Close するのは、非表示フォームのロードが終わった後にする」方が望ましいかと。
https://atmarkit.itmedia.co.jp/bbs/phpBB/viewtopic.php?topic=33492&forum=7
COM オブジェクトを操作する場合、メッセージループが必要になることもありますが
その場合は ApplicationContext を用いることもできるかと。
https://smdn.jp/programming/netfx/fcl/System.Windows.Forms.ApplicationContext/
ApplicationContext の代わりに非表示フォームを使っても実装できるでしょうけれども、
いずれにせよ、一つのイベントが長時間実行されるようなことが無いような作りにすべきでしょうね。
たとえば Process の終了待ち処理なら、 WaitForExit メソッドは使わずに
Process の Exited イベント(と SynchronizingObject プロパティ)を使うか、
終了待ちを他のワーカースレッドに行わせるなど。
https://dobon.net/vb/dotnet/process/openfile.html
>>『Load 処理』ではなく、その前の『コンストラクタ部(VB でいえば Sub New のこと)』を心配しています。
> 何度も見返しているのですが、Sub New がないのです。
> (これがそもそも問題なのでしょうか・・・)
VB2019 の場合、特に明示しない限りは Form に Sub New() が書かれないのが普通なので、
無いなら無いで問題無いですね。
VB でコンストラクタを明示的に用意する場合は、コードウィンドウ上部のドロップダウンリストで、
クラス一覧から [Form1]を選択し([Form1 イベント]ではない)、
右上からは [New]を選択(バージョンによっては [新規作成]を選択)します。
また、クラスにコンストラクタの宣言が一切無い場合、そのクラスにはコンパイル時に
「引数無しの public なコンストラクタ」が自動生成される仕様です。
そして VB2005 以降で新規 Form を追加した場合は、フォーム名.designer.vb 側のコードに対して
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
または
<Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
という属性が付与されるようになっています。これが付いていると、自動生成されたコンストラクタ内に、
下記のような「InitializeComponent() メソッドの呼び出し」が自動挿入される仕様です。
Public Sub New()
'デザイン時の設定(フォームの初期サイズやコントロールの配置など)を呼び出す
InitializeComponent()
End Sub
これは Form 継承クラスに限った話ではなく、任意の自作クラスにおいても当てはまります。
コンストラクタが未定義で、かつ DesignerGenerated 属性が付与されたクラスの場合、
「InitializeComponent という名前(大文字小文字は区別しない)で、引数の無いメソッド」
を呼び出すためのコンストラクタが自動生成されます。
※ InitializeComponent が定義されていない場合は、空のコンストラクタが生成されます。
Public Partial Class Form1
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Class Example
Private Sub InitializeComponent()
MsgBox("自動生成")
End Sub
End Class
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim a As New Example() 'メッセージボックスが表示される
End Sub
End Class
※Microsoft の .NET コンパイラー "Roslyn" における InitializeComponent の自動挿入処理
https://sourceroslyn.io/#Microsoft.CodeAnalysis.VisualBasic.EditorFeatures/Utilities/NamedTypeSymbolExtensions.vb
※Mono の VB.NET コンパイラーにおける InitializeComponent の自動挿入処理
https://github.com/mono/mono-basic/blob/bdb5276f7d85100e8e9ddd7e5ba2360a792644a9/vbnc/vbnc/source/TypeDeclarations/ClassDeclaration.vb#L145-L157
ちなみに C# のコンパイラにはこの仕組みがありません。 C# で Form を追加した場合には、
VB とは違って、最初からコンストラクタが記述されたファイルが追加されるようになっています。
また、C# で Form のコンストラクタをすべて消した場合、「public な引数無しコンストラクタ」が
追加されるまでは同じなのですが、InitializeComponent を呼び出す処理は自動追加されません。
■No101131 (ジェイド さん) に返信
> Public Sub New()
> ' この呼び出しはデザイナーで必要です。
> InitializeComponent()
> AddHandler Timer.Tick, AddressOf Timer_Tick
> ' InitializeComponent() 呼び出しの後で初期化を追加します。
> End Sub
今回の場合、わざわざ引数無しコンストラクタを追加する必要は無かったような。
それはさておき、Timer_Tick の定義が次のうちのどちらだったのかが気になっていたり。
(1) 『Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick』
(2) 『Private Sub Timer_Tick(sender As Object, e As EventArgs)』
通常は前者のはずで、その場合はそもそも AddHandler を使うべきでは無いんですよね。
イベントが多重呼び出しになってしまう可能性が高まるので。
また、「Friend WithEvents Timer As Timer」のような宣言になっている場合、
AddHandler を使わずに (1) だけで記述することをお奨めしています。逆に、(2) でコーディングするなら
その変数の定義は WithEvents にしない方が良いでしょう(混乱のもとなので)。
> Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
> Me.Visible = False
> Me.Show()
> End Sub
個人的には、これもあまり良い回避策とは思えなかったり…。
「Hide() / Show()」か「Visible = False / Visible = True」かという対比性はさておき、
「非表示化」→「別処理」→「表示化」ならまだしも、
「非表示化」→「表示化」というコードが、コメント説明すらなく並んでいると、
後で保守する後任の担当者が、意図を読み取れずに削除してしまう恐れがありそうで。
そもそも Shown で常に Close させるのなら、表示する意味がないですし、
そもそも Visible = False が要るのか? という話になって、後から読む人が混乱するんじゃないかと。
「非表示が前提」のフォームなのに、一瞬とはいえ、一時的に表示させることが許容されているのかどうか、という疑念も。
エラーメッセージの表示や、選択ダイアログを表示するといった目的がある場合は良いでしょうけれども、
即時終了を目的として表示するというのは、処理として不自然では無いだろうかと。
たとえばそれによって、フォーカスの遷移が起きるなどといった問題が起きるかも知れないわけで。
> If LoadProcess(strErrMsg) = False Then
> If Not String.IsNullOrEmpty(strErrMsg) Then ShowMessage(strErrMsg, MessageBoxIcon.Exclamation)
> Return
> End If
ここも微妙に違和感が。
たとえば上記の処理なら、
Dim strErrMsg As String = LoadProcess()
If Not String.IsNullOrEmpty(strErrMsg) Then
ShowMessage(strErrMsg, MessageBoxIcon.Exclamation)
Return
End If
となるよう設計の方が素直に思えます。
殆どの場合、「ByRef な引数」は使用しない方が良いので、情報を戻り値で返すことを検討すべきかな、と。
出力引数が必要になるのは、 P/Invoke の時か Try何某 系のメソッドぐらいのものでしょう。
(VB6 からマイグレーションしたコードだと、ByRef だらけになってしまうのですが)
下記は .NET のデザインガイドにおける記述ですが、パラメーター設計においては
「AVOID using out or ref parameters.」
「out または ref パラメーターは使用しないようにしてください。」
と明記されています。なお、 C# における out や ref とは、Visual Basic における ByRef キーワードに相当します。
https://learn.microsoft.com/ja-jp/dotnet/standard/design-guidelines/parameter-design
ところで、この LoadProcess の処理って、どのぐらいの時間続くものなのでしょうかね。
数十ミリ秒程度であれば良いですが、数秒以上かかるのであれば、非同期パターンで実装して、
その処理を別スレッドで行わせることを検討した方が良いかと思います。
https://learn.microsoft.com/ja-jp/dotnet/standard/asynchronous-programming-patterns/
|
解決済み
|