クリック範囲、タッチ範囲、ダブルクリックの猶予時間について 補足

クリック範囲、タッチ範囲、ダブルクリックの猶予時間について - kitunechan’s blogの補足です

WPFでの話です。Windows Formsでは意味が無いかもしれない

MouseDoubleClickイベントを使うとマウスの範囲で判定されてしまいます。
指でのダブルタッチをうまく認識させるためにはMouseDownイベントでClickCountが2の時で判定するとうまいくいきます。

ダブルタッチを判定したくても、TouchDoubleClickイベントは存在しないので気をつけてください!!!

クリック範囲、タッチ範囲、ダブルクリックの猶予時間について

ダブルクリック範囲
System.Windows.Forms.SystemInformation.DoubleClickSize
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/SystemInformation.cs

タッチ範囲は System.Windows.Input.StylusLogic をリフレクションで取得すること
ただし.Net4.6以降じゃないと記述が足りないので.Net4.6のソースからコピーしましょう
https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Input/Stylus/StylusLogic.cs

[追記]
StylusLogic.csは.Net4.7のReferenceSourceで公開されていません。


// 謎
DoubleTapDelta
DoubleTapDeltaTime

// タッチ
touchDoubleTapDelta
touchDoubleTapDeltaTime

// ペン
stylusDoubleTapDelta
stylusDoubleTapDeltaTime

/// <summary>
/// .net 4.5 だと ラッパー では足りないため、コピーした
/// TouchModeN_DtapDist、TouchModeN_DtapTimeがない
/// </summary>
public class StylusLogic {

	public static StylusLogic Current = new StylusLogic();


	public StylusLogic() {
		ReadSystemConfig();
	}

	#region DoubleTapDelta
	/// <summary>
	/// ダブルタップの有効範囲を取得します
	/// </summary>
	public int DoubleTapDelta {
		get {
			//if( _DoubleTapDelta == -1 ) {
			//	_DoubleTapDelta = (int)stylusLogicType.InvokeMember( "DoubleTapDelta", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, stylusLogic, null );
			//}
			return _DoubleTapDelta;
		}
	}
	int _DoubleTapDelta = -1;
	#endregion


	#region DoubleTapDeltaTime
	/// <summary>
	/// ダブルタップの有効速度を取得します
	/// </summary>
	public int DoubleTapDeltaTime {
		get {
			return _DoubleTapDeltaTime;
		}
	}
	int _DoubleTapDeltaTime = -1;
	#endregion


	#region touchDoubleTapDelta
	/// <summary>
	/// タッチでのダブルタップの有効範囲を取得します
	/// </summary>
	public int touchDoubleTapDelta {
		get {
			return _touchDoubleTapDelta;
		}
	}
	int _touchDoubleTapDelta = 45;

	#endregion

	#region touchDoubleTapDeltaTime
	/// <summary>
	/// タッチでのダブルタップの有効速度を取得します
	/// </summary>
	public int touchDoubleTapDeltaTime {
		get {
			return _touchDoubleTapDeltaTime;
		}
	}
	int _touchDoubleTapDeltaTime = 300;
	#endregion


	#region stylusDoubleTapDelta
	/// <summary>
	/// スタイラスでのダブルタップの有効範囲を取得します
	/// </summary>
	public int stylusDoubleTapDelta {
		get {
			return _stylusDoubleTapDelta;
		}
	}
	int _stylusDoubleTapDelta = 15;
	#endregion

	#region stylusDoubleTapDeltaTime
	/// <summary>
	/// スタイラスでのダブルタップの有効速度を取得します
	/// </summary>
	public int stylusDoubleTapDeltaTime {
		get {
			return _stylusDoubleTapDeltaTime;
		}
	}
	int _stylusDoubleTapDeltaTime = 800;
	#endregion

	private const double DoubleTapMinFactor = 0.7; // 70% of the default threshold.
	private const double DoubleTapMaxFactor = 1.3; // 130% of the default threshold.

	private int _cancelDelta = 10;

	/// <summary>
	/// Grab the defualts from the registry for the double tap thresholds.
	/// </summary>
	///<SecurityNote>
	/// Critical - Asserts read registry permission...
	///           - TreatAsSafe boundry is the constructor
	///           - called by constructor
	///</SecurityNote>
	[SecurityCritical]
	private void ReadSystemConfig() {
		object obj;
		RegistryKey stylusKey = null; // This object has finalizer to close the key.
		RegistryKey touchKey = null; // This object has finalizer to close the key.

		// Acquire permissions to read the one key we care about from the registry
		new RegistryPermission( RegistryPermissionAccess.Read,
			"HKEY_CURRENT_USER\\Software\\Microsoft\\Wisp" ).Assert(); // BlessedAssert

		try {
			stylusKey = Registry.CurrentUser.OpenSubKey( "Software\\Microsoft\\Wisp\\Pen\\SysEventParameters" );

			if( stylusKey != null ) {
				obj = stylusKey.GetValue( "DblDist" );
				_stylusDoubleTapDelta = ( obj == null ) ? _stylusDoubleTapDelta : (Int32)obj;      // The default double tap distance is 15 pixels (value is given in pixels)

				obj = stylusKey.GetValue( "DblTime" );
				_stylusDoubleTapDeltaTime = ( obj == null ) ? _stylusDoubleTapDeltaTime : (Int32)obj;      // The default double tap timeout is 800ms

				obj = stylusKey.GetValue( "Cancel" );
				_cancelDelta = ( obj == null ) ? _cancelDelta : (Int32)obj;      // The default move delta is 40 (4mm)
			}

			touchKey = Registry.CurrentUser.OpenSubKey( "Software\\Microsoft\\Wisp\\Touch" );

			if( touchKey != null ) {
				obj = touchKey.GetValue( "TouchModeN_DtapDist" );
				// min = 70%; max = 130%, these values are taken from //depot/winblue_gdr/drivers/tablet/platform/pen/inteng/core/TapsParameterizer.cpp
				_touchDoubleTapDelta = ( obj == null ) ? _touchDoubleTapDelta : FitToCplCurve( _touchDoubleTapDelta * DoubleTapMinFactor, _touchDoubleTapDelta, _touchDoubleTapDelta * DoubleTapMaxFactor, (Int32)obj );

				obj = touchKey.GetValue( "TouchModeN_DtapTime" );
				_touchDoubleTapDeltaTime = ( obj == null ) ? _touchDoubleTapDeltaTime : FitToCplCurve( _touchDoubleTapDeltaTime * DoubleTapMinFactor, _touchDoubleTapDeltaTime, _touchDoubleTapDeltaTime * DoubleTapMaxFactor, (Int32)obj );
			}
		} finally {
			RegistryPermission.RevertAssert();
			if( stylusKey != null ) {
				stylusKey.Close();
			}
			if( touchKey != null ) {
				touchKey.Close();
			}
		}
	}


	/// <summary>
	/// Fit to control panel controlled curve. 0 matches min, 50 - default, 100 - max
	/// (the curve is 2 straight line segments connecting  the 3 points)
	/// </summary>
	private int FitToCplCurve( double vMin, double vMid, double vMax, int value ) {
		if( value < 0 ) {
			return (int)vMin;
		}
		if( value > 100 ) {
			return (int)vMax;
		}
		double f = (double)value / 100.0;
		double v = Math.Round( f <= 0.5 ? vMin + 2.0 * f * ( vMid - vMin ) : vMid + 2.0 * ( f - 0.5 ) * ( vMax - vMid ) );

		return (int)v;
	}
}

レジストリ情報読み取ってるだけかな?

補足情報
クリック範囲、タッチ範囲、ダブルクリックの猶予時間について 補足 - kitunechan’s blog

WindowsForms DataGridView コントロール上でのタッチ操作について

Windows FormsのDataGridViewはタッチ操作(フリック、スワイプっていうらしい)でのスクロールに不具合があります。
もうWindows Formsのバグは修正してくれないので回避コードでいくしかないようです。

DataGridView コントロール上でのタッチ操作について – Visual Studio サポート チーム blog

回避コード

private const int WM_VSCROLL = 0x0115;
private const int SB_THUMBPOSITION = 0x0004;
private const int SB_THUMBTRACK = 0x0005;
[System.Runtime.InteropServices.DllImport("user32")]
private static extern int PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

public class MyDataGridView : DataGridView {
  // メッセージを処理します。
  protected override void WndProc(ref Message m){
    base.WndProc(ref m);

    if (m.Msg == WM_VSCROLL){
      if (LoWord((long)m.WParam) == LoWord((long)SB_THUMBPOSITION)){
        BeginInvoke((Action<IntPtr, IntPtr>)((WParam, LParam) => {
          // SB_THUMBPOSITION を SB_THUMBTRACK に変更します。
          IntPtr testWParam = new IntPtr(SB_THUMBTRACK);
          // WM_VSCROLL メッセージを再送します。
          PostMessage(this.Handle, WM_VSCROLL, testWParam, LParam);
        }), m.WParam, m.LParam);
      }
    }
  }
}

WindowsFormsでタッチ操作は正直限界だと思う。

そしてこの一文

※ 上記回避方法については、お客様において十分にご確認、ご検証くださいますようお願いします。

バグ直せや

参考URL:
Windows 8 でタッチ操作でグリッドをスクロールしたときに描画がズレる。(最後が隠れる) - Microsoft.NET - Project Group
Windows8でデータグリッドビューをタッチ操作するとスクロールバーと描画内容がずれる
Windows8 対応タッチパネルでDataGridViewコントロールによるタッチ操作でのMoseMoveイベントが発生しない。 | Microsoft Connect

[WPF] Imageがぼやける問題

Image を使って画像を表示させていると等倍で表示しているはずなのにぼやけてしまいました。
SnapsToDevicePixels や UseLayoutRounding では直らなかったので途方に暮れていたらいいものがありました。

blogs.msdn.microsoft.com


専用のコントロールを作っているので、Imageコントロールに移植します。
※2016/06/03 LayoutUpdatedイベントの処理を追記

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public class Image : System.Windows.Controls.Image {

    public Image() {
        this.LayoutUpdated += Image_LayoutUpdated;
    }

    private Point _pixelOffset;

    void Image_LayoutUpdated( object sender, EventArgs e ) {
        Point pixelOffset = GetPixelOffset();
        if( !AreClose( pixelOffset, _pixelOffset ) ) {
            InvalidateVisual();
        }
    }

    private bool AreClose( Point point1, Point point2 ) {
        return AreClose( point1.X, point2.X ) && AreClose( point1.Y, point2.Y );
    }

    private bool AreClose( double value1, double value2 ) {
        if( value1 == value2 ) {
            return true;
        }
        double delta = value1 - value2;
        return ( ( delta < 1.53E-06 ) && ( delta > -1.53E-06 ) );
    }

    protected override Size MeasureOverride( Size constraint ) {
        // ちょっとだけ修正 BitmapSourceの時だけ処理する
        var bitmapSource = Source as BitmapSource;
        if( bitmapSource != null ) {
            Size measureSize = new Size();

            PresentationSource ps = PresentationSource.FromVisual( this );
            if( ps != null ) {
                Matrix fromDevice = ps.CompositionTarget.TransformFromDevice;

                Vector pixelSize = new Vector( bitmapSource.PixelWidth, bitmapSource.PixelHeight );
                Vector measureSizeV = fromDevice.Transform( pixelSize );
                measureSize = new Size( measureSizeV.X, measureSizeV.Y );
            }

            return measureSize;
        }

        return base.MeasureOverride( constraint );
    }

    protected override void OnRender( System.Windows.Media.DrawingContext dc ) {
        _pixelOffset = GetPixelOffset();
        dc.DrawImage( this.Source, new Rect( _pixelOffset, DesiredSize ) );
    }

    private Point GetPixelOffset() {
        Point pixelOffset = new Point();

        PresentationSource ps = PresentationSource.FromVisual( this );
        if( ps != null ) {
            Visual rootVisual = ps.RootVisual;

            // Transform (0,0) from this element up to pixels.
            pixelOffset = this.TransformToAncestor( rootVisual ).Transform( pixelOffset );
            pixelOffset = ApplyVisualTransform( pixelOffset, rootVisual, false );
            pixelOffset = ps.CompositionTarget.TransformToDevice.Transform( pixelOffset );

            // Round the origin to the nearest whole pixel.
            pixelOffset.X = Math.Round( pixelOffset.X );
            pixelOffset.Y = Math.Round( pixelOffset.Y );

            // Transform the whole-pixel back to this element.
            pixelOffset = ps.CompositionTarget.TransformFromDevice.Transform( pixelOffset );
            pixelOffset = ApplyVisualTransform( pixelOffset, rootVisual, true );
            // エラーが出るので nullの時の処理を追記
            if( rootVisual.TransformToDescendant( this ) != null ) {
                pixelOffset = rootVisual.TransformToDescendant( this ).Transform( pixelOffset );
            }
        }

        return pixelOffset;
    }

    private Point TryApplyVisualTransform( Point point, Visual v, bool inverse, bool throwOnError, out bool success ) {
        success = true;
        if( v != null ) {
            Matrix visualTransform = GetVisualTransform( v );
            if( inverse ) {
                if( !throwOnError && !visualTransform.HasInverse ) {
                    success = false;
                    return new Point( 0, 0 );
                }
                visualTransform.Invert();
            }
            point = visualTransform.Transform( point );
        }
        return point;
    }

    private Point ApplyVisualTransform( Point point, Visual v, bool inverse ) {
        bool success = true;
        return TryApplyVisualTransform( point, v, inverse, true, out success );
    }

    private Matrix GetVisualTransform( Visual v ) {
        if( v != null ) {
            Matrix m = Matrix.Identity;

            Transform transform = VisualTreeHelper.GetTransform( v );
            if( transform != null ) {
                Matrix cm = transform.Value;
                m = Matrix.Multiply( m, cm );
            }

            Vector offset = VisualTreeHelper.GetOffset( v );
            m.Translate( offset.X, offset.Y );

            return m;
        }

        return Matrix.Identity;
    }
  }

論理ピクセルとデバイピクセルのズレはもういや><

Visual Studio Installer 編集の自動化

Visual Studio Installer - kitunechan’s blog の続き

毎回Orcaを使って編集するのはめんどくさいし、”よく忘れます”← ここ重要
コマンドラインやバッチファイル的なものはないかと探したらありました。

vbsを使う方法が一番簡単そうです。

MSIファイルのWindows Installerデータベースをプログラムで編集する: .NET Tips: C#, VB.NET

以下の内容でvbsファイルを作成します。

[アドバタイズショートカットから通常のショートカットに切り替える]
NormalShortCut.vbs

Option Explicit

Const msiOpenDatabaseModeTransact = 1

Dim msiPath : msiPath = Wscript.Arguments(0)

Dim installer
Set installer = Wscript.CreateObject("WindowsInstaller.Installer")
Dim database
Set database = installer.OpenDatabase(msiPath, msiOpenDatabaseModeTransact)

Dim query
    query = "INSERT INTO Property(Property, Value) VALUES('DISABLEADVTSHORTCUTS', '1')"
Dim view
Set view = database.OpenView(query)
view.Execute
database.Commit

あとはこのファイルにmsiファイルを渡すだけです。
何かのウインドウが開くこともなく、msiファイルが書き換えられます。

Visual StudioでPostBuildEventに以下のコードを入力して自動化します。

"[パス]\NormalShortCut.vbs" "$(BuiltOuputPath)"

コードだけでlog4netを使う

ログ出力のライブラリとしてlog4netやらNLogが人気があります。
紹介しているサイトを見るとすぐXMLの設定を書かせたがるのですが、後から変更するのか?と。

ということで、log4netをコードから設定する方法です。
ログをファイル出力します。

// 大体一つを使いまわすので static readonly で作る
// GetLogger内の文字列は好きなのでいい
public static readonly log4net.ILog logger = log4net.LogManager.GetLogger( "Log" );

// コンストラクタ的なところで設定を記述する
public App() {
  // 出力先のAppenderを作成します
  // ファイル出力で、日付やファイルサイズで新規ファイルを作成するタイプ
  var Appender = new log4net.Appender.RollingFileAppender() {
    // 出力するファイル名
    File = "log.log",
    // ファイル追記モード (Falseだと上書き)
    AppendToFile = true,
    // ログファイル名を固定にする
    StaticLogFileName = true,
    // ログファイル名のローテーション番号の順番
    CountDirection = 1,
    // ログファイルの最大世代
    MaxSizeRollBackups = 1,
    // 文字コード指定
    Encoding = System.Text.Encoding.UTF8,
    // ログを新規ファイルに切替える条件
    RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Size,
    // 最大ファイルサイズ
    MaxFileSize = 3072000,
    //ログのフォーマット
    Layout = new log4net.Layout.PatternLayout(@"%d [%t] %-5p %type - %m%n"),
  };
  // 記述した設定を有効にする 忘れないように!
  Appender.ActivateOptions();

  var log = (log4net.Repository.Hierarchy.Logger)logger.Logger ;
  // 出力レベルの設定 デフォルトはALLなのでコメントアウトしちゃう
  //log.Level = log4net.Core.Level.All;
  // 出力先を追加します
  log.AddAppender( Appender );

  // 設定を有効にする 忘れないように!
  log.Hierarchy.Configured = true;
}

後は好きなところでメッセージ出力して下さい。

logger.Debug( "Debug" );
logger.Info( "Info" );
logger.Warn( "Warn" );
logger.Error( "Error" );
logger.Fatal( "Fatal" );

参考:
log4net の Appender を プログラム上で動的に 生成、追加 する 方法
c# - How to configure NHibernate logging with log4net in code, not in xml file? - Stack Overflow
Log4Net設定 MAXファイルサイズ及び日単位で切替え | Wait Cursor
Log4net(6) RollingFileAppender 意味不明パラメータ:CountDirection - shima111の日記

C# .Net アプリケーションで複数のアイコンを登録する方法

C# .Net のアプリケーションでショートカットアイコン用に複数のアイコンファイルをEXEに入れたかったのですが、
Visual Studio 2013だけではできませんでした。

参考イメージ
ショートカット アイコンの変更 - Google 検索

では本題です。
長いので目次付けました。

  • そもそもアイコンを設定する方法
  • Win32リソースを作成して複数のアイコンを登録する方法
  • Resource Hackerを使って後から複数のアイコンを追加する方法
  • InsertIconsを使って後から複数のアイコンを追加する方法

おすすめは最後の「InsertIconsを使って後から複数のアイコンを追加する方法」です。

続きを読む

Visual Studio Installer Projects

これのお話
Microsoft Visual Studio 2015 Installer Projects - Visual Studio Marketplace

ほぼ覚書

UpgradeCode を一致させておくとインストールプログラム一覧にダブらずにインストールできる。
ProductCode は必ず変更すること。

ファイルのPermanentをTrueにしておくとアンインストール時にも削除されない。

インストーラーのバージョンは作成時はどうあがいても3桁(1.0.0)でしかビルドできない。
ビルド後にOrcaを使えば変更可能。

定義されているプロパティ一覧
Property Reference - Windows applications | Microsoft Docs
[WindowsVolume]や[ProductVersion]とか

アイコンはそれぞれ設定する必要がある
・ショートカットファイル
・インストールプログラム一覧

作成されるショートカットがアドバタイズショートカットというタイプなので通常のショートカットに切り替える必要がある。
OrcaMSIファイルを開き、PropertyテーブルにProperty「DISABLEADVTSHORTCUTS」、Value「1」の行を新たに追加する。
アドバタイズショートカットではなく、普通のショートカットを作成する - .NET Tips (VB.NET,C#...)

Orcaは以下の場所にインストーラーが保存されている
C:\Program Files\Microsoft SDKs\Windows\[バージョン]\Bin\Orca.exe
Program Files (x86)ではないので注意。まあ、両方見よう

WPFで画像処理中にメモリが開放されず・・・

覚書程度に記載です

WPFCanvas(正確にはVisualクラス?) を RenderTargetBitmap を使って大量に画像出力していたら

エラー「メモリが足りません」
System.OutOfMemoryException: プログラムの実行を続行するための十分なメモリがありませんでした。

はー悲しい。。。


こんなのループしました。
※ブログ用に書いたから動かないかも

// 画像の読み込み
var bmp = new BitmapImage(new Uri( "ファイル名をいれてね" ));

// 画像拡大率の指定
var Scale = 1.5D;
for ( var i = 0 ; i < 1000; i++){
  var canvas = new Canvas() {
    SnapsToDevicePixels = true,
    Children = {
      ( new Func<UIElement>( ()=>{
        var re = new Image() {
          Source = bmp,
          LayoutTransform = new ScaleTransform( Scale, Scale )//画像拡大
        };
        RenderOptions.SetBitmapScalingMode( re, BitmapScalingMode.HighQuality);
        return re;
      }))(),
    },
  };
  
  canvas.Measure( new System.Windows.Size( bmp.Width * Scale, bmp.Height * Scale ) );
  canvas.Arrange( new Rect( 0, 0, bmp.Width * Scale, bmp.Height * Scale ) );
  canvas.UpdateLayout();
  
  // ファイル書き出し
  var r = new RenderTargetBitmap( (int)canvas.ActualWidth, (int)canvas.ActualHeight, 96, 96, PixelFormats.Default );
  r.Render( canvas );
  
  var img = new PngBitmapEncoder() {
  	Frames = { BitmapFrame.Create( r ) },
  };
   
  using( var stream = new FileStream( "./"+i+".png", FileMode.Create ) ) {
  	img.Save( stream );
  }

 // 強制メモリ解放!!! GC働けええええ
 // GC.Collect();
 // GC.WaitForPendingFinalizers();
 // GC.Collect();
}

メソッドを抜けるまでガンガンメモリが増えていくやつです。
メモリリークしているわけじゃなくてGC君が「後でやるよ」をし続けた結果「メモリが足りません」の流れっぽいです。

ただし GC.Collect(); だけでは足りないみたいで GC.WaitForPendingFinalizers(); の必要がありました。

こんな感じ?

 GC.Collect();
 GC.WaitForPendingFinalizers();
 GC.Collect();

stackoverflow.com

Visual Studio 再起動の拡張機能の紹介

インテリセンスのウインドウが表示されなくなるとか、
Xamlでデザインいじってる時にGUIからプロパティが操作効かなくなったり、
と割とよく不具合があるので再起動しまくりです。
[×] 押して毎回起動するのめんどくせーーーーーと思ってたらありました。

Visual Studio Restart
visualstudiogallery.msdn.microsoft.com

通常起動と管理者で起動があるのでご注意下さい。
あと、Undoの情報も消えちゃうよね。

Visual Studio 許すまじ

Visual Studio 2013 にて
Showで作成した小ウインドウを閉じた時に親ウインドウが見えなくなってしまうという不思議な現象に見舞われました・・・
どうも原因は親ウインドウよりもVisual Studioがしゃしゃり出てきて前面に表示されるため、
親ウインドウはアクティブ状態だけど表示されてない感じになってました。


Visual Studio再起動したら直りました。


調子悪い時はVisual Studio再起動しよ。。。

続きを読む

定義済みコマンドを使っていこう

既に定義がされているコマンドがいくつかあります。

System.Windows.Input 名前空間

  • ComponentCommands
     ちょっと特殊。あまり使う機会はないんじゃなかろうか
  • MediaCommands
     Play や Stop など音楽や動画を扱うときに便利なコマンドを持つ
  • NavigationCommands
     ブラウザに類似したコマンドを持つ


System.Windows.Documents 名前空間

  • EditingCommands
     エディタでよく使われるコマンドを持つ

引用元:定義済みコマンドまとめ - しばやん雑記


ショートカットキーが定義されていたり、Copy、Pasteなどに至っては各コントロールにより処理が入っていたりして便利です。

これを使わない手はない!と思ったのですが、手順がわかるまでが大変でした。

処理の追加

Windowクラス(UIElementなら何でもOK)のCommandBindingsで処理を追加します。
※ 方法がいくつかあるので一つの手段として見て下さい。

public partial class MainWindow: Window {
    public MainWindow() {
        InitializeComponent();
        this.CommandBindings.Add( new CommandBinding( ApplicationCommands.New, New, CanNew  ) );
    }

    // 実行されるメソッド
    public void New( object sender, ExecutedRoutedEventArgs e ){
        MessageBox.Show( "New" );
    }

    // こんなのでもおっけー 
    //public ExecutedRoutedEventHandler New {
    //    get {
    //        return ( s, e ) => {
    //            MessageBox.Show( "New" );
    //        };
    //    }
    //}

    public void CanNew ( object sender, CanExecuteRoutedEventArgs e ){
        // 実行できるなら e.CanExecute = true;
        // 実行できない(グレー表示)なら e.CanExecute = false;
        e.CanExecute = true;
    }
}

この記述で

<Menu DockPanel.Dock="Top" Margin="0,0,0,1">
	<MenuItem Header="ファイル(_F)">
		<MenuItem Command="New"/>
	</MenuItem>
</Menu>

文字もショートカットキーも出てる!!便利ー!!
f:id:kitunechan:20151112182715p:plain

ショートカットキーの追加

Newコマンドでは Ctrl+N が初めから定義されていましたが、名前を付けて保存 の SaveAs にはショートカットキーが割り当てられていませんでした。
自分で割り当てろってことですね。

ショートカットキーを割り当てて、Ctrl+Shift+S の表示もさせるにはデザインが出来上がる前に記述する必要がありました。

つまり ApplicationCommands.SaveAs に Ctrl+Shift+S を割り当てるには InitializeComponent より前に書いてしまうか、

public MainWindow() {  
    ApplicationCommands.SaveAs.InputGestures.Add( new KeyGesture( Key.S, ModifierKeys.Control | ModifierKeys.Shift ) );
    InitializeComponent();  
}  

静的コンストラクタ(Static)に追加して最速で呼び出します。

static MainWindow() {  
    ApplicationCommands.SaveAs.InputGestures.Add( new KeyGesture( Key.S, ModifierKeys.Control | ModifierKeys.Shift ) );
}

全体像はこんな感じになりました。

public partial class MainWindow: Window {
    public MainWindow() {
        InitializeComponent();

        this.CommandBindings.Add( new CommandBinding( ApplicationCommands.New, New, CanNew ) );

        // SaveAs はラムダ式で強引に書いちゃう
        this.CommandBindings.Add( new CommandBinding( ApplicationCommands.SaveAs, ( s, e ) => {
            MessageBox.Show( "SaveAs" );
        } ) );
    }

    static MainWindow() {  
        ApplicationCommands.SaveAs.InputGestures.Add( new KeyGesture( Key.S, ModifierKeys.Control | ModifierKeys.Shift ) );
    }

    // 実行されるメソッド
    public void New( object sender, ExecutedRoutedEventArgs e ) {
        MessageBox.Show( "New" );
    }

    // こんなのでもおっけー 
    //public ExecutedRoutedEventHandler New {
    //    get {
    //        return ( s, e ) => {
    //            MessageBox.Show( "New" );
    //        };
    //    }
    //}

    public void CanNew( object sender, CanExecuteRoutedEventArgs e ) {
        // 実行できるなら e.CanExecute = true;
        // 実行できない(グレー表示)なら e.CanExecute = false;
        e.CanExecute = true;
    }
}

Xaml

<Window x:Class="LivetWPFApplication1.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="ファイル">
                <MenuItem Command="New"/>
                <MenuItem Command="SaveAs"/>
            </MenuItem>
        </Menu>
        <DockPanel></DockPanel>
    </DockPanel>
</Window>

やったぜ!
f:id:kitunechan:20151112183938p:plain

参考:http://blogs.wankuma.com/kazuki/archive/2008/03/20/128720.aspx

自分でコントロールを描画する

ControlPaint
ControlPaint クラス (System.Windows.Forms)


自分でコントロールを描画したい時に便利なクラスです
参考:コントロールを描画する: .NET Tips: C#, VB.NET


Button周りのボーダーグラデーションも指定色から作成することができます。
ControlPaint.LightLight( Color );
ControlPaint.Light( Color );
ControlPaint.Dark( Color );
ControlPaint.DarkDark( Color );

上から順に
・強い光色
・弱い光色
・弱い影
・強い影
となっています。

引用:C# 指定色の明暗|おーう なんじゃそら


DarkDarkとか安直すぎない(*_*)

テキストボックスに数値型のデータバインドをした時の不正文字入力後のフォーカス変更

TextBoxのTextにデータバインディングをした時に、バインディング元が数値型だと入力エラーになりフォーカスが外せなくなります。

FormのAutoValidateプロパティをAutoValidate.EnableAllowFocusChangeにするとフォーカスが外せるようになります。

ウインドウの位置を保存・復元する

プログラムを再度起動した時に前回のウインドウ位置やサイズを保存・復元する方法です。
保存する情報は
・ウインドウのサイズ(Width,Height)
・ウインドウの位置(Location)
・ウインドウの状態(最大化、最小化)
になります。

また、起動した時に画面からはみ出ている状態では困るのでそこも修正します。

ウインドウの情報を保存する

ウインドウのサイズ、位置、状態のやりとりはWinAPIを使用したほうが便利です。
画面外に吹っ飛ぶようなウインドウでも、画面内に収まるように自動で修正してくれます。

こんな感じのクラスを作って

public class WinAPI {
 // WinAPI
 // ウインドウ情報をセットする
 [DllImport("user32.dll")]
 public static extern bool SetWindowPlacement(
 	IntPtr hWnd,
 	[In] ref WINDOWPLACEMENT lpwndpl
 );
 //WinAPI
 // ウインドウ情報を取得する
 [DllImport("user32.dll")]
 public static extern bool GetWindowPlacement(
 	IntPtr hWnd,
 	out WINDOWPLACEMENT lpwndpl
 );
 
 // 以下 WinAPIで使用するクラス定義
 public struct WINDOWPLACEMENT {
 	public int length;
 	public int flags;
 	public SW showCmd;
 	public POINT minPosition;
 	public POINT maxPosition;
 	public RECT normalPosition;
 }
 
 [StructLayout(LayoutKind.Sequential)]
 public struct POINT {
 	public int X;
 	public int Y;
 
 	public POINT( int x, int y ) {
 		this.X = x;
 		this.Y = y;
 	}
 }
 
 [StructLayout(LayoutKind.Sequential)]
 public struct RECT {
 	public int Left;
 	public int Top;
 	public int Right;
 	public int Bottom;
 
 	public RECT( int left, int top, int right, int bottom ) {
 		this.Left = left;
 		this.Top = top;
 		this.Right = right;
 		this.Bottom = bottom;
 	}
 }
 
 public enum SW {
 	HIDE = 0,
 	SHOWNORMAL = 1,
 	SHOWMINIMIZED = 2,
 	SHOWMAXIMIZED = 3,
 	SHOWNOACTIVATE = 4,
 	SHOW = 5,
 	MINIMIZE = 6,
 	SHOWMINNOACTIVE = 7,
 	SHOWNA = 8,
 	RESTORE = 9,
 	SHOWDEFAULT = 10,
 }
}

こんな感じで使ってます。

public void ウインドウ情報の取得( Control c ) {
	WINDOWPLACEMENT wp = new WINDOWPLACEMENT();
	WINAPI.GetWindowPlacement(c.Handle, out wp);
}

public void ウインドウ情報を設定( WINDOWPLACEMENT wp ) {
	if( wp.length != 0 ) {
		wp.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
		wp.flags = 0;
		wp.showCmd = ( wp.showCmd == SW.SHOWMINIMIZED ? SW.SHOWNORMAL : wp.showCmd );
		IntPtr hwnd = c.Handle;
		WinAPI.SetWindowPlacement(hwnd, ref wp);
	}
}


参考:http://grabacr.net/archives/1585