2014年1月30日木曜日

CakePHP Ajaxを使う

Ajaxを使って動的にMySQLに接続。テーブルに対する処理を行う。
ボタンが押されたらページ遷移せずにデータをテーブルに追加するor持ってくるなど。

CakePHPにはAjaxヘルパーなるものが存在しているが、どうやら古いものなうえ、
どこかからダウンロードしてこないと使えないようだ。
jQueryを使えば問題なく動作するようなので、jQueryを使用する。

参考サイト

上の参考サイトが分かりやすいがCakePHPを使っていないので、今回は上のサイトの例を
下のサイトを参考にしながらCakePHPに落とし込んで行く。
なので細かい部分は参考サイトを参考に。

テーブルとモデルを作成する


Ajaxで操作するテーブルを作成。

/*---------------------------------------------------------------------------*/
CREATE TABLE `Telephones` (
`id` INT NOT NULL AUTO_INCREMENT ,
`name` VARCHAR( 64 ) NOT NULL ,
`phone` VARCHAR( 16 ) NOT NULL ,
PRIMARY KEY ( `id` ) 
);
/*---------------------------------------------------------------------------*/

モデルをDirectoryで作成するとCakePHPのデフォルトのクラスと被るので名前だけ変える。
変えた名前に対応したモデルを作って適当なデータを挿入すればテーブル周りは完了。

jQueryを使用したviewファイルを作成する


jQueryでMySQLに接続。ヒアドキュメントの存在を知ってたけど初めて使ったのでメモ代わりに。

/*---------------------------------------------------------------------------*/
<?php
echo $this->Html->script("jquery-1.10.2.js", array('inline' => false));
$this->Html->scriptStart(array('inline' => false));
echo <<<EOT
$(document).ready(function(){ 
$("#search_results").slideUp(); 
   $("#search_button").click(function(e){ 
       e.preventDefault(); 
       ajax_search(); 
   }); 
   $("#search_term").keyup(function(e){ 
       e.preventDefault(); 
       ajax_search(); 
   }); 
});

function ajax_search(){ 
$("#search_results").show(); 
var search_val=$("#search_term").val(); 
$.post("/hoge/fuga/find", {search_term : search_val}, function(data){
if (data.length>0){ 
$("#search_results").html(data); 
}) 
EOT;
$this->Html->scriptEnd();
/*---------------------------------------------------------------------------*/

普通のphpファイルで操作していた要素をCakePHPのviewファイルに合わせて
書き換えるだけなので難しいことは無いはず。

コントローラにAjaxからアクセスする操作を記述する


上の記述どおりだとFugaControllerにfindメソッドを記述する。

/*---------------------------------------------------------------------------*/
public function find() {
$this->layout = "ajax";
if($this->RequestHandler->isAjax()) {
$term = strip_tags(substr($this->request->data("search_term"), 0, 100));
$sql = "select name,phone
from directory
where name like '%$term%'
or phone like '%$term%'
order by name asc";
$result = $this->Telephone->query($sql);
$string = "";
foreach($result as $row) {
   $string .= "<b>".$row["Telephone"]["name"]."</b> - ";
   $string .= $row["Telephone"]["phone"]."</a>";
   $string .= "<br/>\n";
}
if($string === "") {
$string = "No matches!";
}
$this->set("data", $string);
}
}
/*---------------------------------------------------------------------------*/

これもほぼ参考サイト丸コピ。レイアウトの変更やAjaxからの送信かを確認しているくらい。

ここまでできたら一度ちゃんと動くかを普通にアクセスして確かめたほうがいいと思う。
条件がよくわからないが、エラーがあると普通にエラーメッセージを表示してくれるときと、
500エラーになるときで動作が分かれることがある
最初に普通にエラーメッセージが出てきたので、500エラーが出ている理由が
コントローラ側でエラーを吐いているのだと気づかずにしばらく積んだ。

最後にビューのポストに値を入力して動けば完了。

2014年1月28日火曜日

CentOS インストール

なにかがどうにかこうにかして、「そうだ! 自宅サーバーを作ろう!」と思い立った。
というわけで使わずに放置していた古いPCにCentOSをインストールしてみる。

CentOSはRedHatというディストリビューションから有償の部分を取り除きリビルドしたものらしい。
RedHatとの完全互換がテーマで、ネットにも大変情報が多い。

参考サイト
CentOS 6インストール
CentOSのダウンロードについて | Linux系OSのQ&A【OKWave】

isoをダウンロードする


CentOSの公式ページからisoをダウンロード。
今回は参考サイトに倣い、minimalという最小構成のものをダウンロード。

色々なisoファイルがあって戸惑ったので調べたそれぞれの違いをメモ。
  • bin : 多分全部入っているイメージ。直接インストール。
  • minimal : 最小構成のイメージ。最小なのはいいけどなにが入っているのですか?
  • live : CDやDVDから直接CentOSを起動できる。インストールする気が無いなら。

ネットに転がっているスクリーンショットからデザインが変わっているがそれっぽいリンクを
クリックして行けば辿り着く。最新のバージョンをいただきます。

ダウンロードしたisoはディスクメディアに焼いてやってインストール準備完了。

CentOSをインストールする


イメージを焼き付けたディスクメディアを使って早速PCにインストール開始。
手順が多くて面倒なので、参考サイトに丸投げ。
とりあえず日本的な選択をしていけばオーケーだと思う。

インストールが完了して、設定したパスワードでログイン。

2014年1月27日月曜日

CakePHP2.x Datepickerを使ってみる

HTMLのフォームに簡単に日付を入力する素敵なjQueryのライブラリがあるそうな。
その名もDatepicker! 早速使ってみよう。

参考サイト

jQueryUIをダウンロードする


サイトから普通にダウンロードしてくる。最新のものをダウンロードしてくれば問題なかろう。
使うのは以下の通り。
  • js/jquery-1.10.2.js
  • js/jquery-ui-1.10.4.custom.min.js
  • development-bundle/ui/jquery.ui.datepicker.js
  • css/ui-lightness/jquery-ui-1.10.4.custom.min.css
ファイルに付いている数値はバージョンなので時期によっては違う。
解凍したzipのフォルダ構成に基づいてファイル名を記述しているので、
これもバージョンによっては違うかも。

テスト用のHTMLを作成する


/*---------------------------------------------------------------------------*/
echo $this->Html->css('jquery-ui-1.10.4.custom.min.css', null, array('inline' => false));
echo $this->Html->script('jquery-1.10.2.js', array('inline' => false));
echo $this->Html->script('jquery-ui-1.10.4.custom.min.js', array('inline' => false));
echo $this->Html->script('jquery.ui.datepicker.js', array('inline' => false));
echo $this->Html->scriptStart(array('inline' => false));
   $(function() {
       $("#datepicker").datepicker();
   });
echo $this->Html->scriptEnd();
echo $this->Form->input("Test", array("type" => "text", "id" => "datepicker"));
/*---------------------------------------------------------------------------*/

テストしていないからエラーが出るかも。
IEだとinlineにfalseを指定してheadタグに構文を記述しないと動かないっぽい。
しかし、ブラウザキャッシュに気づかずテストを繰り返したため、原因は全然別かも。
とりあえずIE8だと動いた。

2014年1月24日金曜日

CakePHP2.x BASIC認証を使ってみた

色々あってCakePHPでBASIC認証を使うことに。パスワードを平文のままサーバに送って処理を
行うためセキュリティはあまりよろしくないとのことなので、ダイジェスト認証ももしかしたらやるかも
しれない。やらないかもしれない。

参考サイト

認証用のユーザーを登録するテーブルを作る


多分usernameとpasswordカラムがあればなんとかなる。

/*---------------------------------------------------------------------------*/
CREATE TABLE IF NOT EXISTS admin_user (
   username VARCHAR(64),
   password VARCHAR(64)
);
/*---------------------------------------------------------------------------*/

でも大抵管理ページのユーザー管理用に使うと思うので、参考サイトのようにそれなりにデータは
持っておいたほうがいいかも。

モデルを作成する


作成したテーブルを操作するモデルを作ろう。
今回は作成したテーブルからしてCakePHPのデフォルトから外れているのでモデルも外れる。

/*---------------------------------------------------------------------------*/
class AdminUser extends AppModel {
public function beforeSave($options = array()) {
$password = &$this->data['User']['password'];
password = AuthComponent::password($password);
return true;
}
}
/*---------------------------------------------------------------------------*/

Authコンポーネントをコントローラに設定する前に、
このモデルを使用して管理ユーザーを作っておくことをオススメする。
パスワード照合の際はハッシュ化された文字列で行うので、テーブル側にもハッシュ化済みの
文字列が入っていないと認証に失敗するからだ。

Authコンポーネントを設定する


AppControllerにAuthコンポーネントを設定する。

/*---------------------------------------------------------------------------*/
class AppController extends Controller {
public $components = array(
"Session",
"Auth" => array(
"loginAction" => array(
"controller" => "AdminUser",
"action" => "login",
"admin" => true,
),
"loginRedirect" => array(
"controller" => "applies",
"actopm" => "index",
"admin" => true,
),
"authenticate" => array(
"Basic" => array("userModel" => "AdminUser"),
),
),
);

public function beforeFileter()
{
$this->Auth->logoutRedirect = $this->webroot;
}
}
/*---------------------------------------------------------------------------*/

loginActionのコントローラに次で作成するコントローラを指定。
認証用のモデルに作成しておいたモデルを指定。

ログイン用コントローラを作成する


ログイン処理用のコントローラ。

/*---------------------------------------------------------------------------*/
class AdminUserController extends AppController
{
public $uses = array("AdminUser");

public function login()
{
if($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
}
else {
$this->Session->setFlash(
__('Username or password is incorrect'),
'default',
array(),
'auth'
);
}
}

public function logout()
{
$this->Auth->logout();
}
}
/*---------------------------------------------------------------------------*/

参考サイトをほぼもろパクリさ! 違いと言えばコントローラとモデル名?


これでちゃんとユーザーを作っていればBASIC認証ができるようになったはず。

2014年1月23日木曜日

CakePHP2.x 環境変数を調べる

CakePHPには環境変数を調べるための便利関数が用意されている。

/*---------------------------------------------------------------------------*/
env("HTTP_HOST");
/*---------------------------------------------------------------------------*/

これで指定した環境変数の中身が返ってくる。

2014年1月22日水曜日

CentOS php-mysqlndをインストールする

なぜかCakeを通してのデータベース接続ができない。

/*---------------------------------------------------------------------------*/
Cake is NOT able to connect to the database.
Database connection “Mysql” is missing, or could not be created.
/*---------------------------------------------------------------------------*/

Cake側ではこんな感じのエラーが出る。
調べるphp_pdo_mysqlなるものが無いせいらしい。
確かにphpinfoで調べてみてもそんなものは見当たらない。

しかし、ネットにあるのはどうにもwindows側の情報ばかりでlinux側の情報が見当たらない。
yumで検索かけてみてもないぞう。

で、四苦八苦した挙句、近くに解決策を知ってる人がいてあっさり解決。

php-mysqlndをインストールするのが正解らしい。
バージョンアップを重ねて今ではこっちが標準になっているらしいので、yumになかったのかな?
とにかくインストール。

/*---------------------------------------------------------------------------*/
yum install --enablerepo=remi mysqlnd
/*---------------------------------------------------------------------------*/

remiは新しめのパッケージをインストールするための外部レポジトリ。
インストールしたらapache再起動。
動いた。

CentOS PHPUnitを入れる

外部のサーバーを通してユニットテストを実行しようとしたら、PHPUnitが入っていないと怒られた。
なので、なぜかサーバーなんぞ全く触ったことないのに、自力でPHPUnitを入れた。
最近自分がどこに向かっているのかさっぱり分からない。
技術が増えるのは全然構わないんだけどもね。
なにかに尖らないといずれ食いっぱぐれそうな恐ろしい予感がある。

参考サイト

PEARを入れる


と書きつつ、この記事では入れない。もう入ってた。
PHPUnitを入れるにはPEARを入れようねメモ。

PHPUnitをインストール


PEARを使ってPHPUnitをインストール。

/*---------------------------------------------------------------------------*/
pear install --force --alldeps pear.phpunit.de/PHPUnit
/*---------------------------------------------------------------------------*/

ここで気をつけねばならないのがオプションだ。
参考サイトではなんもオプション指定してないんだけど、
それだと一部のデータしかインストールされない。
forceオプションとalldepsオプションで問題は解決さ! 強制的に全部入れちゃる。
ここで大体二時間近く積んだ。

php.iniにパスを通す


PEARでインストールしたディレクトリがどこにあるのか確認して、php.iniにパスの設定を追加する。

/*---------------------------------------------------------------------------*/
$ pear config-show
Configuration (channel pear.php.net):
=====================================
...
PEAR directory                 php_dir          /usr/share/pear
...
/*---------------------------------------------------------------------------*/

php_dirの右側にあるのがインストールされたディレクトリ。
こいつをphp.iniの設定に追加する。

/*---------------------------------------------------------------------------*/
include_path=".:/usr/share/pear"
/*---------------------------------------------------------------------------*/

.はカレントディレクトリ。:は区切り文字だそうな。:はWindos環境だと;になる。

apacheを再起動だ


/*---------------------------------------------------------------------------*/
apachectl restart
/*---------------------------------------------------------------------------*/

上記コマンドで再起動して、phpunitコマンドが問題なく動作すればインストール完了。

追記


php.iniの設定いらなかった。前windows環境でやったときやった覚えないなと思って試してみた。



2014年1月16日木曜日

MongoDB 動かすだけ動かしてみた

小耳に「MongoDBいいね!」的な話を挟んだので、やるだけやってみた。
本当に実行できる環境を作って適当な動作を行っただけ。

参考サイト
Node.js、MongoDBでデータの保存 (1/4) - IT

MongoDBをダウンロードする


まずはMongoDBをダウンロード。XPだと2.2系は動かないらしいので2.0系をダウンロードする。


ダウンロードしたらファイルを解答。これは任意のファイルで可。

MongoDBサーバの起動


binフォルダの中のmongod.exeがサーバをやってくれるそうな。
デフォルトではこいつが使用するデータフォルダがc:\data\dbになっているので、
フォルダを作成するか、コマンドプロンプトから起動してデータフォルダを指定する。

/*---------------------------------------------------------------------------*/
        c:\mongodb\bin\mongod.exe --dbpath c:\mongodb\data
/*---------------------------------------------------------------------------*/

dbpathオプションでデータフォルダを指定できる。

起動するとコンソール画面が出て、ポートへの接続を待機している状態になる。

MongoDBを使用する


コンソールから今度はbinフォルダの中のmongo.exeを起動する。
あとは色々いじくってみればオーケーさ。
とりあえず適当にデータベースを作って検索してみただけなので、まだそこまでやっていない。
公式のリファレンスがあるのでそちらを参考に。


WindowsのMongoDBサーバをデーモンで使用する


Windows上で、mongod.exeはWindowsサービスとしてのインストール、実行をネイティブにサポートしています。だそうな。

参考サイト

ここに記載されているinstallコマンドを使うと、
mongodをデーモンで起動してくれるようになり、いちいち自分で起動する必要がなくなるそうだ。
試してないので情報だけ。

2014年1月14日火曜日

CakePHP2.x 単体テストをやってみる

CakePHP2.x系にはPHPUnitを用いた単体テストが導入されているようだ。
PHPUnitは入っているし、ちょっとやってみた。

参考サイト
テスト - CakePHP Cookbook 2.x ドキュメント

今回はある程度整っている環境に導入するので、モデルの作成とかはすっぽかし。
とりあえずHogeモデルがすでにあると仮定して進める。

開発環境から作成する場合はこの時にスキーマをばっちり使用するとマイグレーションが
簡単になるらしい。

フィクスチャを作成する


このフィクスチャとやら、テスト時に一時的なテーブルを作成してそこで作業してくれるらしい。
テーブルの値に依存していて、テストの度に値を設定しないと、なんていうときに便利そう。
ファイルの作成場所はapp/Test/Fixture。

/*---------------------------------------------------------------------------*/
    class HogeFixture extends CakeTestFixture
    {
        public $import = array(
            "model"           => "Hoge",
            "connection"    => "hoge_db",
        );
    }
/*---------------------------------------------------------------------------*/

importプロパティに値を設定すると、
既存のモデルのスキーマを読み取って勝手にテーブルを作成する。
モデルが存在しないときはmodelではなくtableでテーブル名を指定するといい。
connectionはHogeモデルの接続先データベース。デフォルトのままでいいなら設定不要。

フィクスチャの接続先のデータベースを作る


ここで面倒くさがらずに、フィクスチャにはちゃんと専用のデータベースを用意してあげよう。
というのも、フィクスチャは
  1. 各フィクスチャで必要なテーブルを作成する
  2. フィクスチャにデータが存在すれば、それをテーブルに投入する
  3. テストメソッドを実行する
  4. フィクスチャのテーブルを空にする
  5. データベースからフィクスチャが作成していたテーブルを削除する
という手順を通過するので、仮にフィクスチャで使用するテーブルが指定したデータベースの
既存のテーブルと被っていた場合、発つ鳥後を濁さずと言わんばかりにテーブルを消してから
作業を終えるから。

1回テストを試してからテーブルが削除されているのに気づかず、
「あれ? テーブルがないぞ。接続先間違えてるのか?」なんて全くの見当違いの方向へ
悩んでいた自分のようになりかねない。(え? ならない?)

テストケースを作成する


app/Test/ModelにHogeTest.phpを作成。

/*---------------------------------------------------------------------------*/
    class HogeTest extends CakeTestCase
    {
        public $fixtures = array("app.Hoge");

        public function setUp()
        {
            parent::setUp();
            $this->Hoge ClassRegistry::init("Hoge");
        }

        public function testHoge()
        {
            $result = $this->Hoge->hogeru();
            $this->assertEquals("hogeru", $result);
        }
    }
/*---------------------------------------------------------------------------*/

テストケースのクラスとファイル名には必ず最後にTestをつける必要がある。
fixtureプロパティでさっき作成したHogeフィクスチャを指定。
setUpメソッドでテスト前の初期化を行う。ここではHogeモデルを作成している。
testHogeがテスト時に呼ばれるメソッド。
ひとつのファイルに定義されているものは一度のテストで全て実行される。
Hogeモデルのhogeruメソッドを呼び出している。中身はさっぱり知らないが"hogeru"が返ってくる。
assertEqualsで望みどおりの結果が返ってきているかチェック。
アサーション系のメソッドは他にもあるので状況にあわせて使おう。

テストを行う


http://[ホスト名]/[アプリケーション名]/test.phpにアクセスすると、行えるテストが表示される。
app/Test/Case/[フォルダ名]以下にある末尾にTest.phpが付いたファイルの一覧。
最初はController/Model/Viewの3つしかないが、自分で適当なフォルダを追加することもできる。

今回作成したHogeテストへのリンクがModel/Hogeという風に表示されているはず。
カチッと押せばテスト開始。バーの色でテストの成否を確認できる。緑が成功。赤が失敗。
失敗していたら失敗箇所を修正して緑を目指す。

おまけ:XDebugを導入する(XAMPP)


XDebugをインストールすると、Analyze Code Coverageという機能を使えるようになる。
今回のテストでコードをどれくらい使用したのか色付きで表示してくれる。

XAMPPの最新版には最初から入っており、php.iniのコメントアウトを数行外せばいい。
(多分)[XDebug]セクションが存在すると思うので、そこのコメントアウトを外してやろう。
とりあえず全部外しちゃったけど、特に問題起こってないので多分大丈夫。

apacheを再起動してphpinfoの中にxdebugの項目があればオーケー。

2014年1月10日金曜日

ExcelVBA マクロでオートフィルを使用する

Excelで選択されているセルの右下をえいやって引っ張ると連続データが作成される。
この機能をオートフィルというらしい。(機能は知ってたけど名前は知らなかった)
この機能をマクロで使用する。

参考サイト
Office TANAKA - Excel VBA Tips[連続データを作成する]

/*---------------------------------------------------------------------------*/
    With Range("A1:A2")
        .Value = "1月"
        .AutoFill Destination:=.Resize(12)
    End With
/*---------------------------------------------------------------------------*/

これだけで12月までの連番が出来上がる。
しかし、このやり方だと設定されていない値は連番にならない。
そういう時は2つ以上データを入力してからオートフィルを行う。

/*---------------------------------------------------------------------------*/
    Range("A1") = "1"
    Range("A2") = "2"
    Range("A1:A2").AutoFill Destination:= Range("A1:A2").Resize(100)
 /*---------------------------------------------------------------------------*/

これであっという間に1から100までの連番が出来上がり。
Resizeの代わりにRangeで範囲を指定することもできる。

ExcelVBA ワークシートの最終行を取得する

ワークシートの最終行を取得する。

参考サイト
No.8 ワークシートの最終行、最終列を取得する

/*---------------------------------------------------------------------------*/
Dim rowCount As Integer
rowCount = Range("A1").End(xlDown).row
/*---------------------------------------------------------------------------*/

任意のシートの最終行を取得したい場合はそのシートを取得してRange以下を呼んでやる。

ExcelVBA UTF-8でファイルを保存する

ExcelVBAはExcelなので、普通にファイルを保存するとShift-JISで保存する。
文字コードをUTF-8で保存したい。のでやる。

BOM付きでファイルを保存する


参考サイト
[Excel]Excel VBAでUTF-8のテキストファイルを扱う(ADODB.Stream)

ADODB.Streamとやらを使えばよござんす、ということで。

/*---------------------------------------------------------------------------*/
    Sub saveFile(filename, data)
' ADODB.Streamのモード
        Dim adTypeBinary : adTypeBinary = 1
        Dim adTypeText : adTypeText = 2
        Dim adSaveCreateOverWrite : adSaveCreateOverWrite = 2

        ' ADODB.Streamを作成
        Dim adodb : Set adodb = CreateObject("ADODB.Stream")
       
        ' データを書き込む
        With adodb
            .Type = adTypeText
            .Charset = "UTF-8"
            .Open
            .WriteText data
            .SaveToFile filename, adSaveCreateOverWrite
            .Close
        End With
    End Sub
/*---------------------------------------------------------------------------*/

渡されたデータをUTF-8で保存するためのプロシージャを作成。
ADODB.Streamにデータを書き込み保存している。

BOMなしでファイルを保存する


環境によってはBOMがあるとエラーを起こすので、BOMなしでの保存もやってみる。

参考サイト

/*---------------------------------------------------------------------------*/
    ' BOMデータのバイトサイズ
    Dim bomSize As Integer: bomSize = 3

    ' データを書き込む
    With temp
        .Type = adTypeText
        .Charset = "UTF-8"
        .Open
        .WriteText data
    End With

    ' BOMを飛ばす
    With temp
        .Position = 0
        .Type = adTypeBinary
        .Position = bomSize
    End With
    
    ' 出力用のストリームを使って出力
    With output
        .Type = adTypeBinary
        .Open
        .Write temp.Read()
        .SaveToFile filename, adSaveCreateOverWrite
        .Close
    End With
    
    ' ストリームを閉じる
    temp.Close
/*---------------------------------------------------------------------------*/

すでにストリームが作成されている前提のサンプル。
データを書き込んだストリームのデータ位置をBOMを飛ばした位置に設定。
出力用のストリームに書き込み保存している。
出力用のストリームのCharsetプロパティを弄ると
「このコンテキストで操作は許可されていません」
というエラーが出たのでしないように。ここで割りと悩んだ。



ExcelVBA With~End Withステートメントってなんじゃい

なにかがどうしてExcelVBAを使ってマクロを記述。
気づけば言語も色々触っているので大体はわかるけどぱっと見でわからなかった
With~End Withステートメントについてメモを残しておく。

参考サイト
Excel VBA 入門講座 With ~End With

このステートメントの中では指定したオブジェクトに対する操作をまとめることができる。

/*---------------------------------------------------------------------------*/
With object
.Hoge = "Hoge"
.Fuga "Fuga"
End With
/*---------------------------------------------------------------------------*/

オブジェクトの中身は適当。
.HogeでobjectのHogeプロパティに値を代入。
.FugaでobjectのFugaメソッドに引数を渡して実行している。

処理をまとめて可読性を上げようということなのかもしれんけど、どんな操作もできるとなると、
かえって混乱しないような気がしないでもない。

2014年1月7日火曜日

C# 閉じるイベントのキャンセル

親子関係のフォームを使うアプリケーションで、子のフォームを自由に出したり消したりしたい。
それだけならボタンを押されるたびにフォームの生成か破棄をすればいいだけなのだけども、
そのフォームの確保しているリソース量が多いので新しく作るたびに確保するのもなあ、と思った。
というわけで、閉じるイベントをキャンセルしてフォームを非表示にしてみる。

参考サイト
C# - フォームが閉じられるのをキャンセルする

/*---------------------------------------------------------------------------*/
this.FormClosing += delegate(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
sender.Visible = !sender.Visible;
}
/*---------------------------------------------------------------------------*/

フォーム終了時に呼び出されるイベントを追加し、
その中でFormClosingEventArgsのCancelプロパティをtrueにする。
こうするとフォームを閉じるのがキャンセルされる。
そのあとにフォームのVisibleプロパティを反転させれば完璧さ!とか思ったらだめだった。
これをやってしまうとどんなときでもフォームが閉じなくなるので、アプリケーションを終了できない!

親の閉じるボタンを押されたときは一緒に閉じてほしいので、その辺を判断できないものか。
FormClosingEventArgsの中身を調べてみたらあったぞう。

/*---------------------------------------------------------------------------*/
this.FormClosing += delegate(object sender, FormClosingEventArgs e)
{
// 閉じるボタンを押されたときに終了する
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
this.Visible = !this.Visible;
}
}
/*---------------------------------------------------------------------------*/

FormClosingEventArgsのCloseReasonプロパティがどうしてフォームが閉じるのか教えてくれる。
自分の閉じるボタンを押されたときはUserClosingが入っているようなので、その時だけ動作。

一応期待通りの動作はこれでできた。

C# 閉じるボタンを無効にする

親フォームと子フォームを作って、子フォームを消されたりすると困る状況になったので、
閉じるボタンを無効にしてみた。

参考サイト
フォームの「閉じる」ボタンを無効にする: .NET Tips: C#, VB.NET

色々方法があるようだけど、一番強力そうな方法を採用。

/*---------------------------------------------------------------------------*/
protected override System.Windows.Forms.CreateParams CreateParams
{
get
{
const int CS_NOCLOSE = 0x200;

System.Windows.Forms.CreateParams createParams = base.CreateParams;
createParams.ClassStyle |= CS_NOCLOSE;

return createParams;
}
}
/*---------------------------------------------------------------------------*/

フォームが作成されるときのパラメータを弄って閉じれなくしてしまっている。
閉じるボタンの無効はもちろんのこと、「Alt」+「F4」も無効になる。