[[20150717220505]] 『特定のセル横にフォームを表示させる方法は?』(やっぱり初歩) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『特定のセル横にフォームを表示させる方法は?』(やっぱり初歩)

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

  ・行列の表示・非表示
  ・ウィンドの固定・非固定  ←★FreezePane の時が問題発生

 夫々の組合せでフォームを操作しようとすると、時にはエクセル画面からはみ出て閉じる事さえ出来ない状態となる事が有ります。
 幾度と無くその原因を探ってきましたが分かりませんでした。現在は適当に処理をして表示させていますが・・・
 エクセルウィンドの座標との相対関係にあることは理解出来ますが、旨くいきません。
 直接関係あるのか否か分かりませんが、PointsToScreenPixels等も含めての試行も駄目でした。

 1)夫々の条件下でどの様にすればいいのでしょうか?
 2)フォームが見えなくても終了させる方法は?

 よろしくお願いします。

< 使用 Excel:Excel2013、使用 OS:Windows7 >


 >>旨くいきません

 どのようにうまくいかないかをコード等でアップされたらとも思いますが、本件、枠固定対応も含めると
 コード量としては結構、膨大になります。別の板ですが、ここで紹介されている2つのコードは、いずれも
 枠固定の問題を解決しています。

http://www.moug.net/faq/viewtopic.php?t=72229&highlight=

 >>フォームが見えなくても終了させる方法は?

 意味がちょっと・・・
 もし、フォームが、とんでもないところに表示されて(表示されないのに表示されてという表現もなんですが)
 でも、それをUNLOAD したいということですか?

 であれば、ふつうに Unload ユーザーフォーム名 というコードを実行すればよろしいですけど?

(β) 2015/07/18(土) 05:30


 もしかして、質問の意図はそんな難しいことではなく、とんでもないところに表示された(と思われる)ユーザーフォームの制御ということですか?

 ユーザーフォームがモーダル表示されている場合は操作そのものができませんのでモードレス表示だとして。

 新規ブックにUserForm1を準備し(からっぽでいいです)標準モジュールに以下を記述してください。
 まずTest1 を実行。ユーザーフォームがとんでもないところに表示された状態です。

 で、Test2 を実行しますと、Window画面の左上隅(エクセル画面ではなくWindow画面)に【戻ってきます】
 あるいは、Test3 は、ユーザーフォームがどこにあろうと、直接、閉じます。

 Sub Test1()
    With UserForm1
        .StartUpPosition = 0
        .Top = 10000
        .Show vbModeless
    End With
 End Sub

 Sub Test2()
    With UserForm1
        .Left = 0
        .Top = 0
    End With
 End Sub

 Sub Test3()
    Unload UserForm1
 End Sub

(β) 2015/07/18(土) 06:33


 参考情報として。
 ご紹介したスレの2つのコードは、「正攻法」? ですが、このトピ内で角田さんが紹介したURLのページ

https://web.archive.org/web/20140710093810/http://www.moug.net/faq/viewtopic.php?t=69717

 ここで、Abyssさんが別の手法をアップしておられます。アップ時、ご本人から「簡便法」という注釈も出ていますが
 特段不都合なく動きます。コード量としては、2つの「正攻法」バージョンより、ずいぶん少ないもので実現可能です。

(β) 2015/07/18(土) 08:38


 >>エクセルウィンドの座標との相対関係にあることは理解出来ますが、旨くいきません。
 >>直接関係あるのか否か分かりませんが、PointsToScreenPixels等も含めての試行も駄目でした。

 関係すると思われるポイントを列挙してみます。最初のほうの項目は、先刻ご承知のことばかりだとは思いますが。

 1.まず、ウィンドウ座標(スクリーン座標)とドキュメント座標
  前者はデスクトップ画面の左上隅を起点とした座標、後者はエクセルの画面のA1左上隅を起点とした座標です。
  前者はピクセル値で与えることが多いですし、後者はポイント値で与えることが多いです。
 2.ユーザーフォームのLeftやTopは、ウィンドウ座標です。ただし、与える値は(例外的に)ポイント値です。
 3.セルのLeftやTopはドキュメント座標です。値はポイント値です。
 4.従い、ユーザーフォームのLeftやTopにセルのLeftやTopの値を指定する場合、セル座標のポイント値をウィンドウ座標に変換が必要です。
 5.1つの方法としてトライして【ドツボ】にはまるのが、PointsToScreenPixelsX(Y) です。
  MSDNの各種説明では、ほとんどのページで『変換する横方向の長さを文書ウィンドウの左端を基点としたポイント単位の値で指定します。』と
  【大嘘】が書かれています。(言い過ぎですかね?少なくとも閲覧者が誤解する表現)
  ただ、以下のページでは「こそっと」これはメソッド名が適切ではない。実は ドキュメント座標としてのピクセル値 -> ウィンドウ座標としてのピクセル値
  の変換だと【ひかえめに?】書かれています。

https://msdn.microsoft.com/ja-jp/library/cc390345.aspx

 【このメソッドの名前はポイント値をピクセル値に変換するような印象を与えますが、実際には、ピクセル値だけを使用して上記の処理を行います。】

 6.つまり、このメソッドには セル座標の値(ポイント値)を ピクセル値に変換したものを与える必要がでてきます。
  この変換には、エクセル表示倍率なども加味し、かつPCの画面解像度も加味した計算が必要になります。
 7.で、枠固定なしの環境では、この取得数値は正しくなりますが、枠固定環境下では、以下の記述では、なお、狂いが生じます。
   求めるべきウィンドウ座標 = ウィンドウ.PointsToScreenPixelsX(Y)(ピクセル値に変換したドキュメント座標)

 8.試行錯誤結果ですが、以下の記述が必要になります。
   求めるべきウィンドウ座標 = 属するペイン.PointsToScreenPixelsX(Y)(0) + ピクセル値に変換したドキュメント座標の値つまり長さ、高さ

 9.で、このピクセル値をポイント値に、単純変換(表示倍率無視)してユーザーフォームに与えます。

(β) 2015/07/18(土) 10:15


 βさん 所用で出かけていて遅くなりました。

 私の設定条件と、拙いフォームモジュールコード(表示倍率は無視)です。
 ・シート内の列幅は殆どの場合、夫々異なっています。
 ・私の好みで行列番号は非表示が多く、枠固定とするシートでフォームを表示する事が多い。

   Set Rg=Range("H8")
    With ActiveSheet
       Me.StartUpPosition = 0
       Me.Left = .PointsToScreenPixelsX(0) + Rg.Left + 20  ←Left(or Top)の右端の数値は適当!
       Me.Top = .PointsToScreenPixelsY(0) + Rg.Top + 20    ※いつも合わないから適宜の補正値
    End With

 実際の動作は『補正値』が適当である為、Rg近辺の列幅を変更すると予期せぬ位置に出現、
 或いは『行方不明』となります。(←強制終了)
 [Moug]を参考できればと思いググリましたが私には今のところ難解で頓挫。
 枠固定をしたり、外したりして試行しましたが結局分かりませんでした。
こんな簡単な処理では出来ないのですか?
 

(やっぱり初歩) 2015/07/18(土) 14:48


 >>[Moug]を参考できればと思いググリましたが私には今のところ難解で頓挫。

 もちろん、内容が理解できればそれにこしたことはありませんが、たとえば 投稿日時: 15/06/25 19:38:49 で
 共通プロシジャとかかれたものを1つの標準モジュールにコピペしておいて、これは「ブラックボックス」というか『おまじない』の位置づけ。

 で、そちらのユーザーフォームの Initialize で

 Private Sub UserForm_Initialize()
    Dim myPix As Corners
    Dim rtn As Long
    Dim Target As Range

    Set Target = Range("H8")
    myPix = GetWinPosByCell(Target)

    StartUpPosition = 0
    Left = X_pix2point(myPix.TopLeftX + 5)
    Top = Y_pix2point(myPix.TopLeftY + 5)

 End Sub

 このように書いて、ユーザーフォームを表示してみてください。

 >>予期せぬ位置に出現、 或いは『行方不明』となります。(←強制終了)

 上記の対応で行方不明にはならないわけですが、それとは別に、万が一行方不明になった場合の復旧プロシジャとして

 Sub 呼び戻し()
    With UserForm1
        .Left = 0
        .Top = 0
    End With
 End Sub

 といったものを準備しておいて、行方不明になったら実行ということもいいかも。
 ただし、コメントしたように、モーダル表示であれば一切の操作ができませんので強制終了しかないですね。

 いずれにしても

 >>こんな簡単な処理では出来ないのですか?

 はい。簡単にはできません。
 最低でも、(β) 2015/07/18(土) 10:15 の1.〜4.は理解いただきたいと思います。

 追記で。
 ユーザーフォーム表示がモーダルであった場合、上記手当てで、たとえば H8 にちゃんとユーザーフォームはあわせられますが
 その時点で H8 がスクロールされて、画面上になかった場合、ユーザーフォームは見えませんし
 モーダルなので操作一切できませんのでスクロールさせることもできません。
 行方不明ではなく、H8 の場所にちゃんと存在するのですが、手を伸ばせませんので強制終了になります。

 そのあたりも、工夫が必要ですね。H8 が エクセル画面上に見えていない場合はユーザーフォームを表示させないとか。
 見えているかどうかは、ActiveWindow.VisibleRangeとH8のIntersectで確認できます。
 この手当ては、ユーザーフォームを表示させるプロシジャ側で行うか、あるいはユーザーフォームのAtivateイベントで行います。

(β) 2015/07/18(土) 15:29


 昨日と今日現在に到るまで
 >>[Moug] 投稿日時: 15/06/25 19:38:49 のコードとAbyssさんの[簡便法コード]をずっと見ていました。
 然し、私の知識では両方とも理解不能と結論付けしました。特にAbyssさんのコードは全く理解出来ずでした。
 結論として、βさんのご指摘通り共通モジュール部分はおまじないとして考え利用する事にしました。

 但し、UO3さんのコードはβさんの考えられた流れを感じます。(言葉使いからしてβさん自身のコード?)
  ・固定枠使用の時:現在セルのPane位置を知る。 固定位置からの距離を測る。
  ・夫々の座標軸変換。 (→ 本当に手間暇が掛かり大変そうです。)
  ・フォームの表示位置を取得する。
 全体的にはこんな流れかと思います。API関数は分かりませんがあくまで関数として捉えコード全体の理解に
 努めたいと思っています。(時間が掛かっても理解出来るまで・・・)
 理解する上で1つ質問させて下さい。
  Const LogPixelsX = 88
  Const LogPixelsY = 90
 この数値は何ですか?(想像でLog…はロジックの意味では。何れにしろ理解が重要だと感じます)

 どうぞ宜しくご教授をお願いします。

(やっぱり初歩) 2015/07/19(日) 08:48


 >>Const LogPixelsX = 88
 >>Const LogPixelsY = 90

 共通処理の中で GetDPIX や GetDPIY があります。ここは、そのPCに設定された横方向や縦方向の画面解像度を取得するんですが
 API関数のGetDPIを使っています。この関数に対して、取得すべきは横方向の値か、縦方向の値かを引数で与えます。
 この引数の値が、横方向の場合は 88 、縦方向の場合は 90。これは GetDPIが、そう決めているだけですが。
 それを、数字固定で与えてもいいのですが、マジックナンバーになりわかりづらくなるということで、一般にAPIを使い、そこに値を与える場合は、Constで規定しておくことが多いです。

 ついでに。

 (β) 2015/07/18(土) 15:29 で、表示しようとする場所が、その時点でエクセル画面からとびでていた場合は具合悪いと申し上げました。
 で、その制御は呼び出し側またはユーザーフォーム側で対応しましょうと申し上げました。
 どちらで対応するかは(テクニックとしてはどちらでもいけますが)よく考えるべきところです。
 表示場所が、このユーザーフォームにとって極めて重要であれば、ユーザーフォーム側で処理すべきですが
 通常は、ユーザーフォーム自体は、どこに表示されようと機能する。呼び出す側が、今、H8 あたりが見やすいのでそこにとか
 H8 に、ある項目があるので、その横に といったような要件があることが多いですね。

 なので、標準モジュールに

 Function ShowForm(Target As Range, Optional mdl As Boolean = vbModal) As Boolean
    Dim myPix As Corners
    Dim rtn As Long

    If Intersect(ActiveWindow.VisibleRange, Target) Is Nothing Then
        MsgBox Target.Address & " は画面上に表示されていないのでユーザーフォームの表示はやめましょうよ"
        Exit Function
    End If

    myPix = GetWinPosByCell(Target)
    With UserForm1
         .StartUpPosition = 0
         .Left = X_pix2point(myPix.TopLeftX)
         .Top = Y_pix2point(myPix.TopLeftY)
         .Show
    End With

    ShowForm = True

 End Function

 こんなプロシジャを書いておいて、今、UserForm1.Show と、どこかで書いておられるところを

    If ShowForm(Range("H8")) Then
        '処理
    End If

 こうされたらいいと思います。(今、ユーザーフォームのInitializeでやっている関連コードは削除)

 ##UO3さんのコードはβさんの考えられた流れを感じます

 まぁ、そのあたりは、言わぬが花、聞かぬが花(?)ということで。

(β) 2015/07/19(日) 09:16


 βさん 早速のご返答有難うございます。
 理解力・知識力の乏しい私に力添えを頂いた事に感謝感激です。
 今後共、ご指導ご鞭撻のほど宜しくお願いします。
(やっぱり初歩) 2015/07/19(日) 09:36

コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.