Winformsで自分用のペイントソフトつくるよ！(2)

## 概要

[Winformsで自分用のペイントソフトつくるよ！](https://qiita.com/NyancoRitter/items/51caea3b59ebc4da9f2c) の続き．

まぁ動くかな，とか思ってたらさっそくバグっていたぜ！　……というわけで対処中，っていう日記．

## キー操作への対処

マウスドラッグで描画とかしている最中に矢印キーとかに触れると例外で死ぬとかいう大変なことになっていた（キー操作のこと全然考えてなかったよ！）

とにかくいきなり矢印キーとかTabキーとかで想定外の操作が行われると困るので，都合が悪い状態ではキーによる GUI 操作を抑制することにした．

普通のキーではなくて矢印キーとかが相手なので，Form の `ProcessCmdKey()` をオーバライドして対処するのが良い模様である．
（ Form の `KeyPreview` プロパティを true にして `KeyPress` イベントあたりを使う方法だと，どうにもうまくいかないようであった．先にキー操作がGUIに消費されてしまう．"Preview" とは名ばかりである．）

```CSharp
protected override bool ProcessCmdKey( ref System.Windows.Forms.Message Msg, System.Windows.Forms.Keys keys )
{
    //先に自前の処理にキー処理の機会を与える．
    //戻り値が「処理したよー」と言ってきたら GUI 操作に処理を回さない．
    if( 自前ペイントロジック.ProcessKeyInput( keys ) )return true;
    return base.ProcessCmdKey( ref Msg, keys );
}
```

ＯＫ，これで描画中にタブが切り替わって死んだりしなくなった．よかった．

……が，これだけだと，Alt キーとかでメニュー操作状態になった際にも操作を抑制してしまうという問題が発覚．
（メニューが一段ドロップダウンしてあれば矢印キーでメニューが操作できるが，メニュー操作状態になっただけの状態では操作できないっていう謎の状態に．）

なので，こんな感じ↓に対処．
Winformsでは(?)メニューとかはフォーカスを得ない特殊な存在なので `メニュー.Focused` とかでは判断できないから，`MenuStrip` の `MenuActivate` イベントと `MenuDeactivate` イベントのハンドラでフラグを上げ下げして状態管理する必要がありそう．

```CSharp
//メニュー操作状態か否かを示すフラグ
private bool m_IsInMenuActiveCondition = false;
//フラグの上げ下げ
private void Main_menuStrip_MenuActivate(object sender, EventArgs e){	m_IsInMenuActiveCondition = true;	}
private void Main_menuStrip_MenuDeactivate(object sender, EventArgs e){	m_IsInMenuActiveCondition = false;	}

protected override bool ProcessCmdKey( ref System.Windows.Forms.Message Msg, System.Windows.Forms.Keys keys )
{
    //今現在メニュー操作状態であれば，キー操作は GUI に回す
    if( !m_IsInMenuActiveCondition )
    {
        //先に自前の処理にキー処理の機会を与える．
        //戻り値が「処理したよー」と言ってきたら GUI 操作に処理を回さない．
        if( 自前ペイントロジック.ProcessKeyInput( keys ) )return true;
    }
    return base.ProcessCmdKey( ref Msg, keys );
}
```

## コピペの問題点への対処

全く別の話になるが，前回の状態では「自由形状で選択した範囲をコピーすると，それを他のAPPにペーストできない」という問題があったので，そこを解決する．

クリップボードには複数のデータを突っ込めるという話なので，自APP用の固有のデータだけでなく DIB 形式のものも一緒に入れておけばよいらしい．

```CSharp
//（※扱うBitmapは 24bit or 32bit だという前提のコード）

/// <summary>
/// Bitmapをクリップボードにコピーする．
/// ただし，Format32bppArgb の場合には他APPにうまくペーストできる形にはならない
/// </summary>
/// <param name="bmp">クリップボードにコピーするBitmap</param>
public static void CopyBMP_To_Clipboard( Bitmap bmp )
{
    if( bmp.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb )
    {
        //※ClipboardはBtimapの透明度をサポートしないらしいので，
        //アルファCHがある場合,PNGのデータをクリップボードに入れることで対処するとかいう話． 
        //この場合，自分は良いけども他のAPPには意味不明だからペーストできないという問題があるが……
        var DataObj = new System.Windows.Forms.DataObject();
        using( var PngMemStrm = new System.IO.MemoryStream() )
        {
            //てきとーに"PNG" というフォーマット名で突っ込む
            bmp.Save( PngMemStrm, System.Drawing.Imaging.ImageFormat.Png );
            DataObj.SetData( "PNG", false, PngMemStrm );

            //他のAPPへのペーストを考慮して DIB 形式も入れておく．
            //これだと「透明箇所が灰色になった画像」になるようだ．
            DataObj.SetData( System.Windows.Forms.DataFormats.Dib, true, bmp );

            //
            System.Windows.Forms.Clipboard.SetDataObject( DataObj, true );
        }
    }
    else
    {	System.Windows.Forms.Clipboard.SetImage( bmp );	}
}
```

他のAPPにペーストしてみると，透明にしてた部分は「謎のちょっと明るい灰色」になる模様．どうなの？って感じだけど，まぁペーストできないよりはマシだと思うのでＯＫかな．

他APPにペーストしたら画像の右上と左下が灰色になっている様子：
![Fig.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3574818/cbd05612-82c2-4422-923a-34cd1daae6cc.png)

### Paste の可否は諦め中

現在，メニューの "Paste" は常に Enable である．ここがちょっと不満ではある．
Enable/Disable をきちんとやりたいところであるが，クリップボードの状態は自APPだけで閉じているわけではないから簡単ではない．要は「クリップボードの状態を監視」する必要があるわけだが，どうすればいいのか？
「クリップボードの中身が変わったとき」というタイミングを捉えたいのだが，どうやらそういうイベントとかは無いっぽい．
ググると「そういうのは無ぇから， `User32.dll` を使って `Win32API` でやれや」みたいな話だ．えぇー……やりたくねぇ．

あと，たとえそれをやったとしても，それだけやってれば完全に賄えるの？っていうのがわからんというか．すなわち「そのメッセージの処理と，ユーザ操作との間のタイミングがレースコンディション的な(?)話になったりしないのだろうか？」っていう：【クリップボードの内容が変更される→OSがメッセージをポストする→それを俺のAPPがメッセージキューから取り出して処理する】という期間内に俺のAPPが Ctrl+V とかやれちゃう期間ってのがあるんじゃないのか？と．
仮にそこが微妙なのであれば（：クリップボードの中身的に Paste できない状況（=タイミング）で Paste 処理を実施しようとすることが完全に防止できないのであれば），タイマか何かでポーリングするとかでも話的にはたいして変わらないのはあるまいか？　とかなんとか思ったり．

……まぁ，とりあえず不格好ではあるけども実害は無さそう(?)だから，そういう余計なことはせずに放置中．