ショートカットメニューを実装する(非MVVM)
WindowsForms でショートカットキーを実装するのは、プロパティエディタでササッと指定できましたが、WPFではそうはいきません。
ここでは MVVM を使わずに最も簡単な実装と思われる方法を紹介します。( MVVM で実施すると、かなり面倒です )
まず MainWindow.xaml.cs に、ショートカットをつけたいメニューについて、下記の CustomCommands クラスを定義します。
public static class CustomCommands
{
// ここは同じ名前にする必要がある.
public static readonly RoutedCommand RtdCmdSave = new RoutedCommand( "RtdCmdSave", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdOpen = new RoutedCommand( "RtdCmdOpen", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdQuit = new RoutedCommand( "RtdCmdQuit", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec000 = new RoutedCommand( "RtdCmdDebugExec000", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec001 = new RoutedCommand( "RtdCmdDebugExec001", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec002 = new RoutedCommand( "RtdCmdDebugExec002", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec003 = new RoutedCommand( "RtdCmdDebugExec003", typeof( MainWindow ) );
}
つぎに MainWindow.xaml に下記のようなコードを書きます。
<CommandBinding>で定義した CustomCommands クラスと関連付けます。
<KeyBinding>の Gesture プロパティにショートカットキーの組み合わせを指定します。
<Window.CommandBindings>
<CommandBinding Command="local:CustomCommands.RtdCmdSave" Executed="menuFileSave_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdOpen" Executed="menuFileOpen_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdQuit" Executed="menuApplicationQuit_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec000" Executed="menuDebugExec000_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec001" Executed="menuDebugExec001_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec002" Executed="menuDebugExec002_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec003" Executed="menuDebugExec003_Click" />
</Window.CommandBindings>
<Window.InputBindings>
<!-- ここの Gesture でショートカットキーの組み合わせを決定する -->
<KeyBinding Command="local:CustomCommands.RtdCmdSave" Gesture="Ctrl+S" />
<KeyBinding Command="local:CustomCommands.RtdCmdOpen" Gesture="Ctrl+O" />
<KeyBinding Command="local:CustomCommands.RtdCmdQuit" Gesture="Ctrl+Shift+Alt+Q" />
<!-- ここの Gesture でショートカットキーの組み合わせを決定する -->
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec000" Gesture="F5" />
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec001" Gesture="F6" />
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec002" Gesture="F7" />
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec003" Gesture="F8" />
</Window.InputBindings>
以降に全体のコードを示しますが、3つのファイルの紹介なので、ビルドして試すのは困難だと思われます。ソースコードのアーカイブを用意しておきましたので、よろしければダウンロードしてご利用ください。
ショートカットメニューを実装する(非MVVM)
1 ファイル 14 KB
全体のコード
MainWindow.xaml.cs の冒頭にショートカットキーを効かせるために必要な CustomCommands クラスを定義しています。コンパイルが通る場所ならば、どこに置いてもかまいません。( public にしておいてください )
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace aaa
{
// ショートカットキーを効かせるために必要なクラス.
public static class CustomCommands
{
// ここは同じ名前にする必要がある.
public static readonly RoutedCommand RtdCmdSave = new RoutedCommand( "RtdCmdSave", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdOpen = new RoutedCommand( "RtdCmdOpen", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdQuit = new RoutedCommand( "RtdCmdQuit", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec000 = new RoutedCommand( "RtdCmdDebugExec000", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec001 = new RoutedCommand( "RtdCmdDebugExec001", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec002 = new RoutedCommand( "RtdCmdDebugExec002", typeof( MainWindow ) );
public static readonly RoutedCommand RtdCmdDebugExec003 = new RoutedCommand( "RtdCmdDebugExec003", typeof( MainWindow ) );
}
public partial class MainWindow : Window
{
const int INI_W = 512;
const int INI_H = 256;
byte [] TheData;
WriteableBitmap TheBmp;
List <Label> StbLabels = new List<Label>(0);
int FillType = 0;
public MainWindow()
{
// デフォルトで用意されているコード.
InitializeComponent();
// データ領域を確保する.
int w = INI_W;
int h = INI_H;
int numpix = w * h;
TheData = new byte[ numpix ];
// 画像データを仕込む.
MyUtility myu = new MyUtility();
FillType = 0;
myu.SetDataTypeN( TheData, w, h, FillType );
// ビットマップのDPI、とりあえず 96dpi で決め打ちした.
const double DPI_X = 96.0;
const double DPI_Y = 96.0;
// byte[] データを表示するのでグレースケールな256要素のカラーパレット形式とする.
PixelFormat pixfmt = PixelFormats.Indexed8;
// 256要素のカラーパレット生成のために必要な256要素の色リストを作成する.
List<Color> list_colors = new List<Color>(0);
const int NUM_COLORS = 256;
for ( int k = 0; k < NUM_COLORS; k++ )
{
const byte LEVEL_ALPHA = 0xff;
byte level = (byte)( k );
Color c = Color.FromArgb( LEVEL_ALPHA, level, level, level );
list_colors.Add( c );
}
// 256要素の色リストからパレットを生成する.
BitmapPalette palette = new BitmapPalette( list_colors );
// 書き込み可能なビットマップを作成する.
TheBmp = new WriteableBitmap( w, h, DPI_X, DPI_Y, pixfmt, palette );
// 書き込み可能ビットマップにデータを仕込む.
Int32Rect rct = new Int32Rect( 0, 0, w, h );
int stride_bytes = w * sizeof( byte );
const int OFFSET_BYTES = 0;
TheBmp.WritePixels( rct, TheData, stride_bytes, OFFSET_BYTES );
// 表示させるデータを内包したビットマップと表示イメージを関連付ける.
image000.Width = w;
image000.Height = h;
image000.Source = TheBmp;
// ステータスバーのラベルをリスト化する.
StbLabels.Add( stbLabel000 );
StbLabels.Add( stbLabel001 );
StbLabels.Add( stbLabel002 );
StbLabels.Add( stbLabel003 );
// ステータスバーのラベルの再描画を要請する.
this.InvalidateVisual();
}
protected override void OnRender( DrawingContext dc )
{
// ステータスバーのラベルに表示する文字列を指定する.
for ( int n = 0; n < MyUtility.MAX_NUM_TYPE_OF_FILL; n++ )
{
StbLabels[n].Content = String.Format( "{0:d3}", n );
}
// ステータスバーのラベルの背景色を設定する.
for ( int n = 0; n < MyUtility.MAX_NUM_TYPE_OF_FILL; n++ )
{
if ( n == FillType )
{
// ステータスバーのフィルタイプに該当するラベルのみ色を変更する.
StbLabels[n].Background = Brushes.Aqua;
}
else
{
// それ以外は背景色を透明にする.
StbLabels[n].Background = Brushes.Transparent;
}
}
}
private void menuApplicationQuit_Click( object sender, RoutedEventArgs e )
{
this.Close();
}
private void menuFileOpen_Click( object sender, RoutedEventArgs e )
{
Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();
// ダイアログのタイトル.
ofd.Title = "ふぁいるをひらく";
// 拡張子フィルタ.
ofd.Filter = "Bitmap|*.bmp|JPEG|*.jpeg|PNG|*.png|ALL|*.*";
// 基数は1.
ofd.FilterIndex = 3;
// デフォルト表示ファイル名.
ofd.FileName = "default_filename";
// 戻り値は bool ではなくて bool? であることに注目.
bool? ret = ofd.ShowDialog();
// bool ではないので if ( ret ) にはできない.
if ( ret == true )
{
String filepath = ofd.FileName;
MessageBox.Show( filepath );
}
else
{
MessageBox.Show( "Canceled." );
}
}
private void menuFileSave_Click( object sender, RoutedEventArgs e )
{
Microsoft.Win32.SaveFileDialog sfd = new Microsoft.Win32.SaveFileDialog();
// ダイアログのタイトル.
sfd.Title = "ふぁいるをほぞんする";
// 拡張子フィルタ.
sfd.Filter = "Bitmap|*.bmp|JPEG|*.jpeg|PNG|*.png|ALL|*.*";
// 基数は1.
sfd.FilterIndex = 2;
// デフォルト表示ファイル名.
sfd.FileName = "default_filename";
// 戻り値は bool ではなくて bool? であることに注目.
bool? ret = sfd.ShowDialog();
// bool ではないので if ( ret ) にはできない.
if ( ret == true )
{
String filepath = sfd.FileName;
MessageBox.Show( filepath );
}
else
{
MessageBox.Show( "Canceled." );
}
}
private void menuDebugExec000_Click( object sender, RoutedEventArgs e )
{
// タイプ0のデータを仕込む.
MyUtility myu = new MyUtility();
int w = TheBmp.PixelWidth;
int h = TheBmp.PixelHeight;
FillType = 0;
bool bret = myu.SetDataTypeN( TheData, w, h, FillType );
if ( !bret )
{
String strerr = String.Format( "Error: SetDataType{0}();", FillType );
MessageBox.Show( strerr );
return;
}
// 書き込み可能ビットマップにデータを仕込む.
Int32Rect rct = new Int32Rect( 0, 0, w, h );
int stride_bytes = w * sizeof( byte );
const int OFFSET_BYTES = 0;
TheBmp.WritePixels( rct, TheData, stride_bytes, OFFSET_BYTES );
// ステータスバーのラベルの再描画を要請する.
this.InvalidateVisual();
}
private void menuDebugExec001_Click( object sender, RoutedEventArgs e )
{
// タイプ1のデータを仕込む.
MyUtility myu = new MyUtility();
int w = TheBmp.PixelWidth;
int h = TheBmp.PixelHeight;
FillType = 1;
bool bret = myu.SetDataTypeN( TheData, w, h, FillType );
if ( !bret )
{
String strerr = String.Format( "Error: SetDataType{0}();", FillType );
MessageBox.Show( strerr );
return;
}
// 書き込み可能ビットマップにデータを仕込む.
Int32Rect rct = new Int32Rect( 0, 0, w, h );
int stride_bytes = w * sizeof( byte );
const int OFFSET_BYTES = 0;
TheBmp.WritePixels( rct, TheData, stride_bytes, OFFSET_BYTES );
// ステータスバーのラベルの再描画を要請する.
this.InvalidateVisual();
}
private void menuDebugExec002_Click( object sender, RoutedEventArgs e )
{
// タイプ2のデータを仕込む.
MyUtility myu = new MyUtility();
int w = TheBmp.PixelWidth;
int h = TheBmp.PixelHeight;
FillType = 2;
bool bret = myu.SetDataTypeN( TheData, w, h, FillType );
if ( !bret )
{
String strerr = String.Format( "Error: SetDataType{0}();", FillType );
MessageBox.Show( strerr );
return;
}
// 書き込み可能ビットマップにデータを仕込む.
Int32Rect rct = new Int32Rect( 0, 0, w, h );
int stride_bytes = w * sizeof( byte );
const int OFFSET_BYTES = 0;
TheBmp.WritePixels( rct, TheData, stride_bytes, OFFSET_BYTES );
// ステータスバーのラベルの再描画を要請する.
this.InvalidateVisual();
}
private void menuDebugExec003_Click( object sender, RoutedEventArgs e )
{
// タイプ3のデータを仕込む.
MyUtility myu = new MyUtility();
int w = TheBmp.PixelWidth;
int h = TheBmp.PixelHeight;
FillType = 3;
bool bret = myu.SetDataTypeN( TheData, w, h, FillType );
if ( !bret )
{
String strerr = String.Format( "Error: SetDataType{0}();", FillType );
MessageBox.Show( strerr );
return;
}
// 書き込み可能ビットマップにデータを仕込む.
Int32Rect rct = new Int32Rect( 0, 0, w, h );
int stride_bytes = w * sizeof( byte );
const int OFFSET_BYTES = 0;
TheBmp.WritePixels( rct, TheData, stride_bytes, OFFSET_BYTES );
// ステータスバーのラベルの再描画を要請する.
this.InvalidateVisual();
}
}
}
下記がウインドウの外観を決める XAML です。
<Window x:Class="aaa.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:aaa"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="local:CustomCommands.RtdCmdSave" Executed="menuFileSave_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdOpen" Executed="menuFileOpen_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdQuit" Executed="menuApplicationQuit_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec000" Executed="menuDebugExec000_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec001" Executed="menuDebugExec001_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec002" Executed="menuDebugExec002_Click" />
<CommandBinding Command="local:CustomCommands.RtdCmdDebugExec003" Executed="menuDebugExec003_Click" />
</Window.CommandBindings>
<Window.InputBindings>
<!-- ここの Gesture でショートカットキーの組み合わせを決定する -->
<KeyBinding Command="local:CustomCommands.RtdCmdSave" Gesture="Ctrl+S" />
<KeyBinding Command="local:CustomCommands.RtdCmdOpen" Gesture="Ctrl+O" />
<KeyBinding Command="local:CustomCommands.RtdCmdQuit" Gesture="Ctrl+Shift+Alt+Q" />
<!-- ここの Gesture でショートカットキーの組み合わせを決定する -->
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec000" Gesture="F5" />
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec001" Gesture="F6" />
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec002" Gesture="F7" />
<KeyBinding Command="local:CustomCommands.RtdCmdDebugExec003" Gesture="F8" />
</Window.InputBindings>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem x:Name="menuApplication" Header="Application">
<!-- ここのInputGetstureText はメニューの右側に表示される文字列 -->
<MenuItem x:Name="menuApplicationQuit" Header="Quit" Click="menuApplicationQuit_Click" InputGestureText="コントロール+シフト+オルト+Q"/>
</MenuItem>
<MenuItem x:Name="menuFile" Header="File">
<!-- ここのInputGetstureText はメニューの右側に表示される文字列 -->
<MenuItem x:Name="menuFileOpen" Header="Open" Click="menuFileOpen_Click" InputGestureText="コントロール+O"/>
<MenuItem x:Name="menuFileSave" Header="Save" Click="menuFileSave_Click" InputGestureText="コントロール+S"/>
</MenuItem>
<MenuItem x:Name="menuDebug" Header="Debug">
<!-- ここのInputGetstureText はメニューの右側に表示される文字列 -->
<MenuItem x:Name="menuDebugExec000" Header="Exec000" Click="menuDebugExec000_Click" InputGestureText="エフ5"/>
<MenuItem x:Name="menuDebugExec001" Header="Exec001" Click="menuDebugExec001_Click" InputGestureText="エフ6"/>
<MenuItem x:Name="menuDebugExec002" Header="Exec002" Click="menuDebugExec002_Click" InputGestureText="エフ7"/>
<MenuItem x:Name="menuDebugExec003" Header="Exec003" Click="menuDebugExec003_Click" InputGestureText="エフ8"/>
</MenuItem>
</Menu>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Border BorderBrush="DarkViolet" BorderThickness="1" >
<Button x:Name="tbnApplicationQuit" Content="Quit" Width ="48" Height="48" Click="menuApplicationQuit_Click"></Button>
</Border>
<Separator />
<Button x:Name="tbnFileOpen" Content="Open" Width ="48" Height="48" Click="menuFileOpen_Click"></Button>
<Button x:Name="tbnFileSave" Content="Save" Width ="48" Height="48" Click="menuFileSave_Click"></Button>
<Separator />
<Border BorderBrush="DarkViolet" BorderThickness="0.0" >
<Button x:Name="tbnDebugExec000" Width ="48" Height="48" Click="menuDebugExec000_Click">
<Button.Content>
<TextBlock Text="Exec000&#x0a;[F5]" TextAlignment="Center" />
</Button.Content>
</Button>
</Border>
<Border BorderBrush="DarkViolet" BorderThickness="0.1" >
<Button x:Name="tbnDebugExec001" Width ="48" Height="48" Click="menuDebugExec001_Click">
<Button.Content>
<TextBlock Text="Exec001&#x0a;[F6]" TextAlignment="Center" />
</Button.Content>
</Button>
</Border>
<Border BorderBrush="DarkViolet" BorderThickness="0.2" >
<Button x:Name="tbnDebugExec002" Width ="48" Height="48" Click="menuDebugExec002_Click">
<Button.Content>
<TextBlock Text="Exec002&#x0a;[F7]" TextAlignment="Center" />
</Button.Content>
</Button>
</Border>
<Border BorderBrush="DarkViolet" BorderThickness="0.4" >
<Button x:Name="tbnDebugExec003" Width ="48" Height="48" Click="menuDebugExec003_Click">
<Button.Content>
<TextBlock Text="Exec003&#x0a;[F8]" TextAlignment="Center" />
</Button.Content>
</Button>
</Border>
</ToolBar>
</ToolBarTray>
<StatusBar DockPanel.Dock="Bottom" Height="30">
<Label x:Name="stbLabel000"/>
<Separator/>
<Label x:Name="stbLabel001"/>
<Separator/>
<Label x:Name="stbLabel002"/>
<Separator/>
<Label x:Name="stbLabel003"/>
</StatusBar>
<Border BorderBrush="DarkViolet" BorderThickness="1" >
<ScrollViewer x:Name="scv000" Background="DarkViolet" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible">
<Grid x:Name="grid000">
<Image x:Name="image000" Stretch="Uniform" />
<Canvas x:Name="canvas000" Background="Transparent"/>
</Grid>
</ScrollViewer>
</Border>
</DockPanel>
</Window>
下記が WriteableBitmap に仕込む byte[] 型のデータをフィルするメソッドを定義したクラスです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace aaa
{
public class MyUtility
{
public const int MAX_NUM_TYPE_OF_FILL = 4;
public MyUtility()
{
}
public bool SetDataTypeN( byte [] data, int width, int height, int type_of_fill )
{
if (!(( 0 <= type_of_fill ) && ( type_of_fill < MAX_NUM_TYPE_OF_FILL )))
{
return false;
}
if ( data == null ) return false;
if (( width <= 0 ) || ( height <= 0 )) return false;
int w = width;
int h = height;
bool bret;
if ( type_of_fill == 0 ) { bret = set_data_type0( data, w, h ); }
else if ( type_of_fill == 1 ) { bret = set_data_type1( data, w, h ); }
else if ( type_of_fill == 2 ) { bret = set_data_type2( data, w, h ); }
else if ( type_of_fill == 3 ) { bret = set_data_type3( data, w, h ); }
else { return false; }
return bret;
}
private bool set_data_type0( byte [] data, int width, int height )
{
if ( data == null ) return false;
if ( width <= 0 || height <= 0 ) return false;
int w = width;
int h = height;
for ( int j = 0; j < h; j++ )
{
int adrs = w * j;
for ( int i = 0; i < w; i++ )
{
data[ adrs ] = (byte)( i + j );
adrs++;
}
}
return true;
}
private bool set_data_type1( byte [] data, int width, int height )
{
if ( data == null ) return false;
if ( width <= 0 || height <= 0 ) return false;
int w = width;
int h = height;
for ( int j = 0; j < h; j++ )
{
int adrs = w * j;
for ( int i = 0; i < w; i++ )
{
double rate_x = (double)( i )/(double)( w - 1 );
data[ adrs ] = (byte)( rate_x * 255.0 );
adrs++;
}
}
return true;
}
private bool set_data_type2( byte [] data, int width, int height )
{
if ( data == null ) return false;
if ( width <= 0 || height <= 0 ) return false;
int w = width;
int h = height;
for ( int j = 0; j < h; j++ )
{
double rate_y = (double)(j)/(double)( h - 1 );
byte v = (byte)( rate_y * 255.0 );
int adrs = w * j;
for ( int i = 0; i < w; i++ )
{
data[ adrs ] = v;
adrs++;
}
}
return true;
}
private bool set_data_type3( byte [] data, int width, int height )
{
if ( data == null ) return false;
if ( width <= 0 || height <= 0 ) return false;
int w = width;
int h = height;
for ( int j = 0; j < h; j++ )
{
double rate_y = (double)( j )/(double)( h - 1 );
byte v_y = (byte)( rate_y * 128.0 );
int adrs = w * j;
for ( int i = 0; i < w; i++ )
{
double rate_x = (double)( i )/(double)( w - 1 );
byte v_x = (byte)( rate_x * 127.0 );
data[ adrs ] = ( byte )( v_x + v_y );
adrs++;
}
}
return true;
}
}
}