none
Formの[閉じる]ボタンの無効化について RRS feed

  • 質問

  • 毎回初歩的な質問になりますが、Formの閉じるボタンを無効にする

    ため、GetSystemMenuなどのサンプルをあれこれとパッチしてみまし

    たがうまくいきません。ご指導よろしくお願いします。

     

     環境:OS:WindowsXP,Windows Vista Visual C++ ExpresEdition

    2008年5月3日 2:34

回答

  • 確かに結果的には同等の効果を得られますが、現状のコードには問題があります。

     

    FormClosingイベントの引数、FormClosingEventArgsクラスにはCloseReasonプロパティがあります。

    そのCloseReasonプロパティが示す値の範囲をご確認下さい。

     

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.form.formclosing.aspx

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.formclosingeventargs.aspx

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.formclosingeventargs.closereason.aspx

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.closereason.aspx

     

    シャットダウン時もCloseを拒否することになりますが、問題ないですか?

     

    FormClosingイベントに限らず、初めて利用するイベント、メソッド、プロパティ、クラス等の要素については一度、ヘルプやMSDNを流し読みでも構いませんで、目を通すことをお薦めします。

    2008年5月7日 13:46
    モデレータ
  • 本当はご自身で修正に至って欲しかったのですが、このままですとこの道を通らず仕舞いになりそうですので、間違っている箇所・怪しい箇所を色づけしておきます。

    どのように直すべきかは一度、ご検討下さい。

     

     ひらり さんからの引用

      public ref class Form1 : public System::Windows::Forms::Form {
      private: String^ fileName; //読み書きファイル名
      public:
         Form1(void) {
        InitializeComponent();
        this->fileName = "";

    ------------ 以下追加した部分--------------------------------- 
        // コントロールボックスの[閉じる]ボタンの無効化
        IntPtr hMenu = GetSystemMenu(this.Handle, 0);
        RemoveMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
    ------------ 以上追加した部分--------------------------------- 

       }

    ------------ 以下追加した部分--------------------------------- 
       // Win32 APIのインポート
    *1 [ DllImport("USER32.DLL") ]
    *2 private static extern IntPtr GetSystemMenu(IntPtr hWnd, UInt32 bRevert);
    *1 [ DllImport("USER32.DLL") ]
    *2 private static extern UInt32 RemoveMenu(IntPtr hMenu, UInt32 nPosition, UInt32 wFlags);

       // [閉じる]ボタンを無効化するための値
    *3 private const UInt32 SC_CLOSE = 0x0000F060;
    *4 private const UInt32 MF_BYCOMMAND = 0x00000000;

    ------------ 以上追加した部分--------------------------------- 

    2008年5月7日 15:05
    モデレータ
  •  ひらり さんからの引用

          // システムの終了、再起動などの場合
          e->Cancel = false;
          this->Close();

    この場合のCloseは必要ありません。

    また、CancelEventArgsのCancelプロパティは通常falseであるため、改めて代入する必要はないかと思います。

     

     

    ところで、「質問(問題)は解決した」と判断して良かったのでしょうか?

    先のレスでは「まだ分からないところがある」「解決した」のどちらとも判断がしにくかったので…。

    2008年5月8日 14:17
    モデレータ

すべての返信

  • どのタイプのプロジェクトを作成し、どのような操作で、どのようなコードを書いた結果、うまくいかなかったのですか?

    他に既に参考にされたサイトも記載して頂ければ、解決までの手間が減るかと思います。

    2008年5月3日 10:39
    モデレータ
  • Windows XP なら問題なくできると思いますが、Windows Vista ではできなかったような気がしますが。

    2008年5月3日 17:01
  • みなさんサポートありがとうございます。

    Azuleanさんすみません、記述した内容はつぎのとおりです。

     

    namespace Test {

     using namespace System;
     using namespace System::ComponentModel;
     using namespace System::Collections;
     using namespace System::Windows::Forms;
     using namespace System:Big Smileata;
     using namespace System:Big Smilerawing;
     using namespace System:Big Smileiagnostics;
     using namespace System::IO;
    ------------ 次の一行を追加--------------------------------- 
     using namespace System::Runtime::InteropServices;

      public ref class Form1 : public System::Windows::Forms::Form {
      private: String^ fileName; //読み書きファイル名
      public:
         Form1(void) {
        InitializeComponent();
        this->fileName = "";

    ------------ 以下追加した部分--------------------------------- 
        // コントロールボックスの[閉じる]ボタンの無効化
        IntPtr hMenu = GetSystemMenu(this.Handle, 0);
        RemoveMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
    ------------ 以上追加した部分--------------------------------- 

       }

    ------------ 以下追加した部分--------------------------------- 
       // Win32 APIのインポート
    *1 [ DllImport("USER32.DLL") ]
    *2 private static extern IntPtr GetSystemMenu(IntPtr hWnd, UInt32 bRevert);
    *1 [ DllImport("USER32.DLL") ]
    *2 private static extern UInt32 RemoveMenu(IntPtr hMenu, UInt32 nPosition, UInt32 wFlags);

       // [閉じる]ボタンを無効化するための値
    *3 private const UInt32 SC_CLOSE = 0x0000F060;
    *4 private const UInt32 MF_BYCOMMAND = 0x00000000;

    ------------ 以上追加した部分--------------------------------- 

     

    主なエラー内容
     *1:System::Runtime::InteropServices:Big SmilellImportAttribute': 匿名使用は許可されていません
     *2:2 つ以上のストレージ クラスが指定されています
     *3:Test::Form1:Tongue TiedC_CLOSE': ref クラス または値型の内部で、スタティック データ メンバのみ

        初期化することができます
        Test::Form1:Tongue TiedC_CLOSE' の宣言を確認してください。
     *4:Test::Form1::MF_BYCOMMAND': ref クラス または値型の内部で、スタティック データ メン

        バのみ初期化することができます
        Test::Form1::MF_BYCOMMAND' の宣言を確認してください。

     

     なお、次のURLの記事を参考にしました。よく理解しないままの記述ではエラーが出るのは当たり

    前と思われそうですが、よろしくご指導ください。

     

     参考にしたURL:http://www.atmarkit.co.jp/fdotnet/dotnettips/142closebtn/closebtn.html

     

    2008年5月4日 0:16
  • そのサイトはあくまで、C#での書き方を紹介しているものです。

    C#とC++/CLI(CLR)は書き方が異なります。

    C#のサンプルを参考にする場合は、適切に書き換えてあげる必要があります。

     

    ※手元で修正したところ、期待結果通りに動作することを確認しています。

     

    DllImportについては次のサイトを参考にして下さい。

    http://msdn.microsoft.com/ja-jp/library/55d3thsc(VS.80).aspx

     

    private/public/protectedはclassやstructには先頭につけますが、メンバーに対してはprivate:/public:/protected:というように、これまでのC++同様に振ります。

     

    Code Snippet

    public ref class Test

    {

      public:

        int a();

        int b();

      private:

        int c();

    };

     

    クラス定数として全てのインスタンスで同じ値にする場合はstaticをつけましょう。

    (インスタンスごとに異なる場合はコンストラクタで代入するようにしましょう。)

     

    また、thisに対して . 演算子でアクセスできるのはC#の話です。

    C++ではポインタのようなもの(正確にはハンドル)になりますので、正しい演算子に置き換えて下さい。

     

    余談(押しつけがましいものですが、この手の話の場合には一応触れさせて下さい)

    C++/CLRでのWindowsフォームアプリケーションは結構手間です。

    サンプルもごくわずかしかないので、自力で書き換える力が必要になります。

    要件として求められていないのであれば、C#やVB.NETの方がお手軽だと考えています。

    2008年5月4日 1:21
    モデレータ
  •  じゃんぬねっと さんからの引用

    Windows XP なら問題なくできると思いますが、Windows Vista ではできなかったような気がしますが。

    EnableMenuItemでグレーアウトしてあげれば、Vistaでもできるそうです。

    http://forums.microsoft.com/msdn-ja/ShowPost.aspx?PostID=2701823&SiteID=7

    2008年5月4日 1:24
    モデレータ
  • Azuleanさんアドバイスありがとうございます。

     

    >C++/CLRでのWindowsフォームアプリケーションは結構手間です。

    >サンプルもごくわずかしかないので、自力で書き換える力が必要になります。

    >要件として求められていないのであれば、C#やVB.NETの方がお手軽だと考えています。

     

    C さえまともにマスターしていないのにC++への高飛びであれこれと悩んで{?}います。

    やはり、C#、VB.NETと段階的にマスタするのが常套と思っているのですが。たまたま

    C++をベースにしたアプリケーションの開発例の本を手にしたのが、そもそものきっかけです。

     

    >手元で修正したところ、期待結果通りに動作することを確認しています。

    C++への修正で実現できたとのこと、再度挑戦してみます。

     

    ありがとうございました。

    2008年5月4日 3:48
  •  ひらり さんからの引用

    やはり、C#、VB.NETと段階的にマスタするのが常套と思っているのですが。たまたま

    C++をベースにしたアプリケーションの開発例の本を手にしたのが、そもそものきっかけです。

    C++とC++/CLI(CLR)は似ていますが、異なる言語と考えるべきです。

     

    C++でコードを書けるようになりたいという主旨であれば、C++/CLI(CLR)はちょっと違うかなと感じます。

    C++/CLI(CLR)でWindowsフォームアプリケーションが書けるからと言って、「C++ができる」とは言えないと思いますし、「C++ができる」とは.NET Frameworkのクラスライブラリを使わずにコードが書けることを期待されてしまいます。

    2008年5月4日 4:43
    モデレータ
  •  

    >C++とC++/CLI(CLR)は似ていますが、異なる言語と考えるべきです。

     

     すみません、C++/CLI(CLR)でした。

    2008年5月4日 7:35
  • Azuleanさんお世話になっています。

     

    >C#のサンプルを参考にする場合は、適切に書き換えてあげる必要があります。

    >※手元で修正したところ、期待結果通りに動作することを確認しています。

     

    これまでいろいろと試してみましたがやり方が悪いのかうまくいきません。

    差し支えなければ、修正箇所をご教授いただければ助かります。

    よろしくお願いします。

    2008年5月6日 5:47
  •  ひらり さんからの引用

    これまでいろいろと試してみましたがやり方が悪いのかうまくいきません。

    差し支えなければ、修正箇所をご教授いただければ助かります。

    先の発言でかなり近いポイントを指摘させて頂きました。

    これ以上の具体的な指摘・解説をする前に、どの程度の修正ができたのか見せて頂きたいと思います。

    (意味合い:どのように修正を試したかを知りたい。あるいはどのように考えたかを聞きたい。)

     

    また、「うまくいかない」とはどういう状態かご説明下さい。

    コンパイルが通らない、コンパイルが通って起動するが例外が出る、起動するが期待結果通りに動作しない等、いくつか考えられます。

     

    正解(あるいはそれに近いもの)を示すのは簡単ですが、それではこの先も同じような問題にハマります。

    ぜひとも、テクニックあるいはコツのようなものを掴んで欲しいので、このように書いています。

    2008年5月6日 11:29
    モデレータ
  • Azuleanさん、無事解決しました。

     

    返信を頂いて、短絡的に質問したかなと反省、?

    全く違う発想で次のようにしたところ閉じるボタンの制御が行えました。

    単純な方法で、ひとまずは解決、ただし、対症療法的な記述とは思っ

    ていますが。・・・・・・・・・・

     

    1.bool型のグローバル変数を用意し、fasleで初期化しておく

      例:bool  Close_Flarg = false;

     

    2..閉じるボタンのイベント

      private: System::Void Form1_FormClosing(System:Surprisebject^  sender,

          System::Windows::Forms::FormClosingEventArgs^  e) {

        if ( Close_Flag ) {

              e->Cancel = false;
        }  else {

         e->Cancel = true;
        }
      }

     

    3.実際に終了する場合

      private: System::Void button1_Click(System:Surprisebject^  sender, System::EventArgs^  e) {

        Close_Flag = true;  // 終了のためのフラグを立てる
        this->Close();     // Windowsフォームを閉じる
       }
     

    なお、フラグを使った処理方法は本件で紹介したサンプルを参考にしました。

     

    以上、報告いたします。

    この件では大変お世話になりました。今後ともよろしくお願いします。

     

    2008年5月7日 11:26
  • 確かに結果的には同等の効果を得られますが、現状のコードには問題があります。

     

    FormClosingイベントの引数、FormClosingEventArgsクラスにはCloseReasonプロパティがあります。

    そのCloseReasonプロパティが示す値の範囲をご確認下さい。

     

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.form.formclosing.aspx

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.formclosingeventargs.aspx

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.formclosingeventargs.closereason.aspx

    http://msdn.microsoft.com/ja-jp/library/system.windows.forms.closereason.aspx

     

    シャットダウン時もCloseを拒否することになりますが、問題ないですか?

     

    FormClosingイベントに限らず、初めて利用するイベント、メソッド、プロパティ、クラス等の要素については一度、ヘルプやMSDNを流し読みでも構いませんで、目を通すことをお薦めします。

    2008年5月7日 13:46
    モデレータ
  • 本当はご自身で修正に至って欲しかったのですが、このままですとこの道を通らず仕舞いになりそうですので、間違っている箇所・怪しい箇所を色づけしておきます。

    どのように直すべきかは一度、ご検討下さい。

     

     ひらり さんからの引用

      public ref class Form1 : public System::Windows::Forms::Form {
      private: String^ fileName; //読み書きファイル名
      public:
         Form1(void) {
        InitializeComponent();
        this->fileName = "";

    ------------ 以下追加した部分--------------------------------- 
        // コントロールボックスの[閉じる]ボタンの無効化
        IntPtr hMenu = GetSystemMenu(this.Handle, 0);
        RemoveMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
    ------------ 以上追加した部分--------------------------------- 

       }

    ------------ 以下追加した部分--------------------------------- 
       // Win32 APIのインポート
    *1 [ DllImport("USER32.DLL") ]
    *2 private static extern IntPtr GetSystemMenu(IntPtr hWnd, UInt32 bRevert);
    *1 [ DllImport("USER32.DLL") ]
    *2 private static extern UInt32 RemoveMenu(IntPtr hMenu, UInt32 nPosition, UInt32 wFlags);

       // [閉じる]ボタンを無効化するための値
    *3 private const UInt32 SC_CLOSE = 0x0000F060;
    *4 private const UInt32 MF_BYCOMMAND = 0x00000000;

    ------------ 以上追加した部分--------------------------------- 

    2008年5月7日 15:05
    モデレータ
  • ご指導ありがとうございます。

     

     public: Form1(void)
     {
        // 初期化処理
       InitializeComponent();


      // コントロールボックスの[閉じる]ボタンの無効化
      IntPtr hMenu = GetSystemMenu(this->Handle, 0);
      RemoveMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
     }
     // Win32 APIのインポート
      [ DllImport("USER32.DLL") ]
      static IntPtr GetSystemMenu(IntPtr hWnd, UInt32 bRevert);
      [ DllImport("USER32.DLL") ]
      static UInt32 RemoveMenu(IntPtr hMenu, UInt32 nPosition, UInt32 wFlags);

     const UInt32 SC_CLOSE = 0x0000F060;    <-----*1
     const UInt32 MF_BYCOMMAND = 0x00000000;  <-----*2


      C++への変更はご指摘いただいた箇所について行っていましたが、上記*1及び2での行で

    単純にConstとしておりました。記述箇所をいろいろと変えてみましたがどれもNG、satatic

    を先頭に付けてOK、またconstを付けなくてもコンパイルは通過しForm自体の終了処理はOK

    となりました。しかしシャットダウンはNGでした。(昨日の件とは別ですが)

       この場合のClosinngイベント内の記述はサンプルのとおりとしています。

          // 終了フラグを立ってなければ、フォームが閉じられるのを阻止
          if (CloseFlag == true)
          {
            e->Cancel = false;
          }
          else
          {
            e->Cancel = true;
          }


     なお、上記の方法によらない方法を昨日報告し、シャットダウン時の動作のご指摘を受けましたが

    次のように変更したところFormの【閉じる】ボタンは表示されたままですが、シャットダウン等など

    の場合も問題なく動作しています。(強調文字の箇所を追加)


    1.フラグの宣言(Bool型)

      bool CloseFlag = false;

    2.ユーザが終了指示した場合の処理ルーチン内

      CloseFlag = true;
      this->Close();

    3.Closingイベント内の記述

      if ( !CloseFlag ) {
        if ( e->CloseReason  ==  ::CloseReason::UserClosing  ) {
          // ユーザが閉じる(×)ボタンを押した場合はフォームが閉じられるのを阻止する。
          e->Cancel = true;
        } else {
          // システムの終了、再起動などの場合
          e->Cancel = false;
          this->Close();
        }
      } else {
        // フォーム終了を許可
        e->Cancel = false;
      }

     

    2008年5月8日 9:27
  •  ひらり さんからの引用

          // システムの終了、再起動などの場合
          e->Cancel = false;
          this->Close();

    この場合のCloseは必要ありません。

    また、CancelEventArgsのCancelプロパティは通常falseであるため、改めて代入する必要はないかと思います。

     

     

    ところで、「質問(問題)は解決した」と判断して良かったのでしょうか?

    先のレスでは「まだ分からないところがある」「解決した」のどちらとも判断がしにくかったので…。

    2008年5月8日 14:17
    モデレータ
  • 本件については延々とご指導いただきありがとうございました。

    おかげさまで無事解決しました。

    多くの資料を提供いただき大変参考になりました。特に終了処理では

    CloseReason 列挙体のヒントを頂き、適用したことでシャットダウン

    等に対応することができました。

     

     

    2008年5月9日 1:01