アプリ版:「スタンプのみでお礼する」機能のリリースについて

http://www.serpress.co.jp/excel/vba047.html
を参考にエクセルのユーザーフォームに
最小化、最大化ボタンを付けたいのですがうまく出来ません。

バージョンは2003です。

リンク先の「作成するモジュール」の中身を標準モジュールにコピペしたら
「vbModeless」が「プロシージャの外では無効です。」になります。

なので、全てをフォームモジュールに貼り付けると
Public Constがエラーになるので
Public Constだけ標準モジュールに貼り付けました。

でもフォームモジュールでも
「vbModeless」
が「プロシージャの外では無効です。」になります。

なにを標準モジュールにはりつけて
何をフォームモジュールにはりつければいいか教えてください。

A 回答 (4件)

#1、cjです。


では、実際の運用例を掲げておきますね。
もうかれこれ8年ぐらい使ってもらっている、という意味では実績ある手法です。
標準モジュールとUserFormモジュールを併せて使う例、ですが、
#こういう書き方を不慣れな人に奨めるつもりはないのですけれどね。
無用な混乱で消化不良になってしまうのは、私の、本意ではありません。
必要がないとお感じになられたら、遠慮なくスルーしてください。

' ' =====================UserFormモジュール====================

Option Explicit

Private Sub UserForm_Initialize()
  Call FrmDec(Me)
End Sub

Private Sub UserForm_Activate()
  Repaint
End Sub

' ' ===================以上UserFormモジュール==================

' ' =======================標準モジュール======================

Option Explicit
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
    (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
    (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long
Private Const GWL_STYLE = (-16)
Private Const WS_THICKFRAME = &H40000
Private Const WS_MINIMIZEBOX = &H20000
Private Const WS_MAXIMIZEBOX = &H10000
Private Const LP_CLASSNAME = "ThunderDFrame"

Sub FrmDec(frm As UserForm)
  Dim fRet As Long
  Dim hWnd As Long
  Dim fStyle As Long

  Load frm
  With frm
    hWnd = FindWindow(LP_CLASSNAME, .Caption)
    fStyle = GetWindowLong(hWnd, GWL_STYLE)
    fStyle = (fStyle Or WS_THICKFRAME Or WS_MAXIMIZEBOX Or WS_MINIMIZEBOX)
    fRet = SetWindowLong(hWnd, GWL_STYLE, fStyle)
    fRet = DrawMenuBar(hWnd)
  End With
End Sub

' ' =====================以上標準モジュール====================

' ' =========呼び出し=========

Sub test7764861c() ' UserFormのオブジェクト名を、正しく指定
  UserForm1.Show vbModeless
End Sub

' ' =======以上呼び出し=======

複数のUserFormに適用できるような書き方、というご都合的な面もありますが、
本来標準モジュールに書くべきものを標準モジュールに書く、という到って普通のことをしているだけです。
実際には、他の標準モジュールの記述と混ぜないように、専用の標準モジュールに纏めるべきですね。
確認しておいて欲しいのは、この場合でも「Publicである"必要"はまったくない」ということです。

一応、UserFormモジュールを使わずに標準モジュールだけで完結するシンプルな書き方もありますが、
一長一短、後で、仕様変更したい時とかの対応のし易さなどから、
_Initialize() イベント、_Activate() イベントは併用しておいた方がよい、
というのが私の結論です。

複数のUserFormといっても、多くて2つもあれば、大概のことはできますし、
せいぜい3つ、それ以上はそもそもExcelにとってアウトオブオーダーだと思います。
UserFormひとつであったとしても上記のような運用をした方がよいのは、Controlsの数が多い場合ですね。
何が必要で何が十分かというのは、別に取決めがある訳ではないので、
どうしても"多い"とか抽象的な言い方にはなってしまいますけれど。

で、まぁ、#1の書き方は私も初学の頃に、本を参考に書いていた覚えがあります。
解り易く、扱い易い、オリエンテーション的な答えであって、実践はまた少し違ってたりする、という話です。
また、今回用いたAPI関数については、何か重篤なトラブルを招くようなものではない筈ですから、
API関数に慣れるきっかけという意味では悪くないテーマだと思いますよ。

ただ、#2さんも仰っているように、安易に、いたずらに、APIを使うのは、難がありますね。
本当にその機能が必要なのか、目的を果たすのに他に方法はないのか、よく吟味したほうがよいです。
本当に必要ならAPIに対して消極的になることもないと思いますが、
VBAですからね。VBAやExcelで完結できるものを優先に検討して設計するのがベターではあります。

それから、リンク先のコードについては
> '***** ボタンを表示する処理 ***** < 以降の記述を
  Sub Test()

  End Sub
のようなプロシージャの内側に収めて使う、ということが省略されている
のは理解されてますでしょうか?
そこらへんで違っていたとして
> プロシージャの外では無効です。
というエラーになるのだろう、というのが#2さんのお考えで、
そういう基本的な事柄を整理できずにAPIを扱うのはやや尚早、というお話なのだと思います。
他の条件でも発動するエラーなので、私には判断付きませんけれど。

さて、本来の質問者さんが必要とする答えとは随分とかけ離れた話に拡がってしまって
却って解りにくくしてしまっているかも知れません。
先々を考えて老婆心からくるアドバイスってことになるのでしょうか。
食べきれない料理が出てきたら、残してもいい、みたいな軽い気持ちで
適当にスルーしてやってくださいませ。
    • good
    • 0
この回答へのお礼

ありがとうございます。

お礼日時:2012/11/06 20:52

#1,3、cjです。


#3について、2か所訂正です。

> Sub FrmDec(frm As UserForm)

>  Load frm
を削除。

Sub test7764861c()

Sub test7764861c() ' UserFormのオブジェクト名を、正しく指定
  Load UserForm1
  UserForm1.Show vbModeless
End Sub
に差し換え。

以上のように修正をお願いします。失礼しました。
    • good
    • 1
この回答へのお礼

ありがとうございます。

お礼日時:2012/11/06 20:52

こんにちは。



>なにを標準モジュールにはりつけて
>何をフォームモジュールにはりつければいいか教えてください。

リンク先のコードは、一部、省略している部分があります。

そのリンク先の作者の考え方は、Win32 APIの宣言は標準モジュールを使う、、
「'***** ボタンを表示する処理 *****」
以降は、UserForm モジュール以外の、どこかのモジュールに、Sub プロシージャか、コマンドボタンにして、中身のコードを貼り付けることです。

厳しいようですが、そのコードをみて、ひと目でこの程度は分からないと、Win32 APIはあまりお勧めできないように思います。

「vbModeless」でUserFormを立ち上げれば、十分ではないでしょうか。
参照先では、
「Excelでユーザーフォームを表示しているときにせっかくのExcelワークシートが参照できない。」と書いてありますが、そんなことはありません。UserFormを画面からずらせばよいだけです。

誰でも、最初はありますし、最初は見よう見まねで試すものだと思います。ただ、問題は、その先に、あまり、Win32 APIやC言語の知識のない方は乱用はしてほしくない、とも個人的には思うのです。Win32 APIの大事な基礎的知識があります。また、Win32 APIは使い方によっては、今お使いのPCに重大なトラブルを発生させる原因にもなりかねません。

コーディング・スタイルについては、VBの場合は、Formモジュールにまとめることが多いのですが、VBAの場合は、少し特殊な事情があって、私の単なる想像なのかもしれませんが、UserForm, Sheet などのVBAを司る部分、いわゆるローカルモジュールは、VBA側のメモリ割り当てが少ないのではないか、という懸念があって、そのモジュール当てるコードの量を制限することがあります。したがって、複雑な操作は、標準モジュールに助けを借りるというようなスタイルにしています。

単独で、UserFormオブジェクトを用いる場合は、標準モジュールは使わないほうが、可読性が上がります。しかし、Win32 APIを使っていて、モノによっては突然のクラッシュ(AppCrash)に見舞われることがあるので、なかなか、ローカルモジュールのみに、あれこれと詰め込むことが出来ないのです。もちろん、標準モジュールに逃した所で、その度合が変わるという実証があるわけではありません。たんなる、感覚的なものだけです。

概して、このWin32 APIの宣言だけを別にして、標準モジュールに単独でまとめてしまうことが多いようです。そのワークブックで、Win32 APIを必要とするものの全体が分かり、重複が避けられるようになります。私自身、そのようにすることを、学びました。
    • good
    • 0
この回答へのお礼

ありがとうございます。

お礼日時:2012/11/06 20:52

こんにちは。



とりあえず、すべてUserFormモジュールで完結させた方が扱い易いですから、
まとめてみました。
冒頭宣言部分は必ずUserFormモジュールの先頭においてください。
試す時は、新規のブックにUserFormの追加だけしてからの方が紛れがなくて
いいかも知れません。
注意点として、UserFormを右上の×で閉じる分には良いのですが
UserForm1.Hide とか使うようでしたら、最後に必ず Unload UserForm1
で終わるようにしてください。
UserFormモジュールの記述はUserFormの名前やキャプションに依存しない形で
書いてあります。

' ' =====================UserFormモジュール====================

Option Explicit ' 7764861cj
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
    (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
    (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long
Private Const GWL_STYLE = (-16)
Private Const WS_THICKFRAME = &H40000
Private Const WS_MINIMIZEBOX = &H20000
Private Const WS_MAXIMIZEBOX = &H10000
Private Const LP_CLASSNAME = "ThunderDFrame"

Private Sub UserForm_Initialize()
  Dim fRet As Long
  Dim hWnd As Long
  Dim fStyle As Long
  hWnd = FindWindow(LP_CLASSNAME, Caption)
  fStyle = GetWindowLong(hWnd, GWL_STYLE)
  fStyle = (fStyle Or WS_THICKFRAME Or WS_MAXIMIZEBOX Or WS_MINIMIZEBOX)
  fRet = SetWindowLong(hWnd, GWL_STYLE, fStyle)
  fRet = DrawMenuBar(hWnd)
End Sub

Private Sub UserForm_Activate()
  Repaint
End Sub

' ' ===========================================================

UserFormを表示する記述は以下です。UserForm名は指定を正してください。

' ' ======================

Sub test()
  Load UserForm1
  UserForm1.Show vbModeless
End Sub

' ' ======================

Load → .Show → .Hide → .Show → .Hide .... → UnLoad (QueryClose)
のような使い方をするならば、
  Load UserForm1
は、1回で十分です。
  UserForm1.Show vbModeless
だけでも、いいのですけれど、表示を操作したUserFormは
.Show → .Hide → .Show → .Hide ....で使うのが普通ですから、
どこでLoadするのか明示的にした方が良いかと思います。


備考ですが、
手動でリサイズできるというのも却って面倒な面もあります。
もし、リサイズ出来ないようにしたい場合は
  fStyle = (fStyle Or WS_THICKFRAME Or WS_MAXIMIZEBOX Or WS_MINIMIZEBOX)

  fStyle = (fStyle Or WS_MAXIMIZEBOX Or WS_MINIMIZEBOX)
にするといいでしょう。

蛇足ですが、、、。
私個人は、標準モジュールを使うこともありますが、必要でなければ
UserFormモジュールに纏めたほうがいいと思っています。
ここら辺は考え方が分かれるところかも知れませんが、
必要ないのにPublicを使わない、Privateで十分なものはPrivate、
という考え方です。
    • good
    • 0
この回答へのお礼

ありがとうございます。

お礼日時:2012/11/06 20:52

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています


このQ&Aを見た人がよく見るQ&A