定義済みコマンドを使っていこう
既に定義がされているコマンドがいくつかあります。
- http://msdn.microsoft.com/ja-jp/library/system.windows.input.applicationcommands.aspx
Open や Save など一般的なアプリケーションで有用なコマンドを持つ
- ComponentCommands
ちょっと特殊。あまり使う機会はないんじゃなかろうか
- MediaCommands
Play や Stop など音楽や動画を扱うときに便利なコマンドを持つ
- NavigationCommands
ブラウザに類似したコマンドを持つ
- 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>
文字もショートカットキーも出てる!!便利ー!!
ショートカットキーの追加
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>
やったぜ!
参考: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 );
上から順に
・強い光色
・弱い光色
・弱い影
・強い影
となっています。
DarkDarkとか安直すぎない(*_*)
ウインドウの位置を保存・復元する
プログラムを再度起動した時に前回のウインドウ位置やサイズを保存・復元する方法です。
保存する情報は
・ウインドウのサイズ(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); } }
リフレクション
最近WPFを触っています。
が、ほしいフィールドやプロパティがプライベートだったりして歯がゆい思いをよくしてます。
気に入らないのでリフレクションで持ってきてしまいましょう。
.NET FOLLOWER » C#: How to set or get value of a private or internal property through the Reflection
リフレクションに便利な拡張メソッド
public static class ReflectionHelper { private static PropertyInfo GetPropertyInfo(Type type, string propertyName) { PropertyInfo propInfo = null; do { propInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type = type.BaseType; } while (propInfo == null && type != null); return propInfo; } public static object GetPropertyValue(this object obj, string propertyName) { if (obj == null) throw new ArgumentNullException("obj"); Type objType = obj.GetType(); PropertyInfo propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException("propertyName", string.Format("Couldn't find property {0} in type {1}", propertyName, objType.FullName)); return propInfo.GetValue(obj, null); } public static void SetPropertyValue(this object obj, string propertyName, object val) { if (obj == null) throw new ArgumentNullException("obj"); Type objType = obj.GetType(); PropertyInfo propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException("propertyName", string.Format("Couldn't find property {0} in type {1}", propertyName, objType.FullName)); propInfo.SetValue(obj, val, null); } }
Blend for Visual Studio のデザイン表示について
WPFのデザイン(Xaml)表示がされなくなってしまう事がありました。
調べた結果、Blend for Visual Studio 2013 で .NET Framework 4.5 より上のバージョンを使用するとデザイン表示ができないようです。
.NET Framework 4.5.1
.NET Framework 4.5.2
.NET Framework 4.6
このバージョンを使うときは Blend for Visual Studio 2015 を使いましょう!
Windows API Code Packのインストール
Windows7以降に追加された機能を使用するには追加ライブラリを入れると簡単です。
以前はMicrosoftのコードギャラリーから入手できましたが消滅してしまいました。
現在ではNuGetから入手・インストールします。
NuGetの画面
- Core
- Shell
- ShellExtensions
- DirectX
- ExtendedLinguisticServices
- Sensors
Windows API Code Packで何ができるのかは参考リンクを参照で。。。
ちなみにCoreとShellをインストールした状態では、参照設定で以下のものが必要でした。
- PresentationCore
- PresentationFramework
- System.Xaml
- WindowsBase
参考リンク:Windows API Code Pack連載 第1回~第10回の一覧 - 田中達彦のブログ - Site Home - MSDN Blogs
Windows API Code Pack連載 第1回~第10回の一覧 | 田中達彦のブログ
Windows API Code PackのソースはGithubで公開されています。
github.com
オブジェクトをファイルに保存する
自分で作成したクラスをファイルに保存する方法です。
DataContractSerializerを使用してXML形式で保存します。
DataContractSerializer は System.Runtime.Serialization を使います。
多分初期設定では参照設定がないので追加して下さいね。
早速ですが、読み書き用のクラスにまとめました。 ジェネリック仕様になります。
using System; using System.IO; using System.Runtime.Serialization; using System.Xml; public class FileXML { /// <summary> /// XMLファイルに保存 /// </summary> static public bool Save<T>( T obj, string FilePath ) { try { // XmlSerializerを使ってファイルに保存(T型オブジェクトの内容を書き込む) var serializer = new DataContractSerializer(typeof(T)); using( var fs = new FileStream(FilePath, FileMode.Create) ) using( var xw = XmlWriter.Create(fs, new XmlWriterSettings { Indent = true, IndentChars = "\t" }) ) { // オブジェクトをシリアル化してXMLファイルに書き込む serializer.WriteObject(xw, obj); } } catch( Exception ) { return false; } return true; } /// <summary> /// XMLファイルを読み込み /// </summary> static public T Load<T>( string FilePath ) where T: new() { try { using( var sr = new StreamReader(FilePath) ) using( var xr = XmlReader.Create(sr, new XmlReaderSettings() )) { // XMLをオブジェクトに読み込む var serializer = new DataContractSerializer(typeof(T)); // XMLファイルを読み込み、逆シリアル化(復元)する return (T)serializer.ReadObject(xr); } } catch( Exception ) { return default(T); } } }
保存するオブジェクトのクラスを作成します。
public class 保存クラス { public int PublicNum1; private int PrivateNum2; public void SetNumber(int num) { this.PrivateNum2 = num; } public int PublicProperty3 { get; set; } private int PrivateProperty4 { get; set; } public void SetNumberProperty( int num ) { this.PrivateProperty4 = num; } }
実際に使うとこんな感じ
// 保存 var Save = new 保存クラス(); Save.PublicNum1 = 100; Save.SetNumber(200); Save.PublicProperty3 = 300; Save.SetNumberProperty(400); FileXML.Save<保存クラス>(Save, "./保存.xml"); // 読込 var Load = FileXML.Load<保存クラス>("./保存.xml"); // Load // PublicNum1 100 // PrivateNum2 0 // PublicProperty3 300 // PrivateProperty4 0
出力されるファイルの中身がこちら
読込したデータからわかるようにPublicのものが全て保存されます
<?xml version="1.0" encoding="utf-8"?> <保存クラス xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB"> <PublicNum1>100</PublicNum1> <PublicProperty3>300</PublicProperty3> </保存クラス>
Privateや保存したいデータを指定する場合は[DataContract]属性をクラスにつけて、保存したいフィールドやプロパティに[DataMember]属性を付けます。
using System.Runtime.Serialization; [DataContract] public class 保存クラス { [DataMember] public int PublicNum1; [DataMember] private int PrivateNum2; public void SetNumber(int num) { this.PrivateNum2 = num; } //[DataMember] ← 付けないと保存されない public int PublicProperty3 { get; set; } [DataMember] private int PrivateProperty4 { get; set; } public void SetNumberProperty( int num ) { this.PrivateProperty4 = num; } }
PublicProperty3 は Public ですが [DataMember]をつけていないので保存されません。
<?xml version="1.0" encoding="utf-8"?> <保存クラス xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB"> <PublicNum1>100</PublicNum1> <PrivateNum2>200</PrivateNum2> <PrivateProperty4>400</PrivateProperty4> </保存クラス>
ちなみにオブジェクトなら何でも保存できるので、こんなのもできます
var tuple = Tuple.Create(10, 20, 30, "string"); FileXML.Save<Tuple<int,int,int,string>>(tuple, "./保存.xml");
<?xml version="1.0" encoding="utf-8"?> <TupleOfintintintstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System"> <m_Item1>10</m_Item1> <m_Item2>20</m_Item2> <m_Item3>30</m_Item3> <m_Item4>string</m_Item4> </TupleOfintintintstring>
こんな感じで好き放題保存してやりましょう!
参考:
DataContractSerializerを使って、オブジェクトのXMLシリアル化、逆シリアル化を行う: .NET Tips: C#, VB.NET
neue cc - .NETの標準シリアライザ(XML/JSON)の使い分けまとめ
[Windows Form] データバインドのやり方
テキストボックスの内容をいちいち変数に入れる処理を書くのはめんどくさいですね。
データバインドを使いましょう。
Visual Studioのデザイナーで設定すると簡単にできます。
※ スクリーンショットではアドイン画面がいくつか含まれています
適当にフォームを作りました。
テキストボックスが3つあるだけです。
ComboBoxの見た目と中身を別にする
コンボボックスの見た目と欲しいデータが別なんてことはよくあることですね。
簡単な物だったら匿名クラスを使ってちょいちょいです。
this.comboBox1.ValueMember = "value"; this.comboBox1.DisplayMember = "display"; this.comboBox1.DataSource = new[]{ new { value="1", display="でーた1" }, new { value="2", display="でーた2"}, new { value="3", display="でーた3"}, new { value="4", display="でーた4"}, new { value="5", display="でーた5"}, }; //ValueMember と DisplayMember はデザイナーの方で指定したほうがスッキリすると思うます
DisplayMember が見た目に使うプロパティ名
ValueMember が実際のデータのプロパティ名 になります。
this.comboBox1.SelectedValueにデータバインドをしておけばvalueが勝手に取得できて楽になります。
ユーザーコントロールのスナップを自作する
ユーザーコントロールを作成してテキストボックスなんか置いたりすると、スナップ(ピンクの線)が消えてしまって配置するのがめんどくさくなります。
そこで、ユーザーコントロールにもスナップを追加します。
とりあえずUserControlを継承したクラスを作成します。
この後作成するスナップ付きのユーザーコントロールにこのクラスを使います。
[Designer(typeof(UserControlSnapLineDesigner))] public class UserControlBase: UserControl { protected virtual Control SnapLineControl { get { return null; } } private class UserControlSnapLineDesigner: ControlDesigner { public override IList SnapLines { get { IList snapLines = base.SnapLines; Control targetControl = ( this.Control as UserControlBase ).SnapLineControl; if( targetControl == null ) return snapLines; using( ControlDesigner controlDesigner = TypeDescriptor.CreateDesigner(targetControl, typeof(IDesigner)) as ControlDesigner ) { if( controlDesigner == null ) return snapLines; controlDesigner.Initialize(targetControl); foreach( SnapLine line in controlDesigner.SnapLines ) { if( line.SnapLineType == SnapLineType.Baseline ) { snapLines.Add(new SnapLine(SnapLineType.Baseline, line.Offset + targetControl.Top, line.Filter, line.Priority)); break; } } } return snapLines; } } } }
先ほど作ったクラスを使用してユーザーコントロールを作成します。
作成するユーザーコントロールにはテキストボックスが配置してあり、このテキストボックスのスナップを持ってきます。
UserControlBase で作成したSnapLineControlをオーバーライドでコントロールを指定します。
public partial class ユーザーコントロール: UserControlBase { // ~ 細かいところは省略します ~ //ここで指定するだけ↓ protected override Control SnapLineControl { get { return this.textBox1; } } }
これでテキストボックスのピンクの線がユーザーコントロールにもつくようになりました。
引用:.net - Baseline snaplines in custom Winforms controls - Stack Overflow
自作プロパティを(DataBindings)に追加する方法
Visual Studioで自作プロパティをデザイナー内の(DataBindings)に追加する方法
System.ComponentModel.Bindable(true)をプロパティに付けましょう
[System.ComponentModel.Bindable(true)] public int プロパティ{ get;set; }
デザイナーから直接いじれるのは便利です。
キーが押されているか調べる
Control.MouseButtons とか Control.ModifierKeysを使うだけ
2つのキー同時押しはビット演算を使いましょう。
if( ( Control.MouseButtons & MouseButtons.Right ) == MouseButtons.Right ) { if( ( Control.ModifierKeys & ( Keys.Control | Keys.Shift ) ) == ( Keys.Shift | Keys.Control ) ) { // キー押されてます } }
はてなブログのソースコード記述
はてな記法で、csは小文字じゃないとダメなのね・・・
>|cs| // C# /// <summary> /// MainWindow の ViewModel です。 /// </summary> class MainWindowViewModel { /// <summary> /// Hoge を取得または設定します。 /// </summary> public String Hoge { get; set; } } ||< ↑ここのスペースを消すべし!