オーソドックスなウインドウスタイルを実現する(非MVVM)

Microsoft がデベロッパーに期待するWPFのアプリケーションGUIは、フラットデザインを基調としたもののようです。MSDN のサンプルなどを見ているとそれが顕著に感じられます。

とはいえ、Windows95 の時代から使われてきたGUIというのは、誰もが知っているという利点があり、これ使わない手はありません。

最上部にタイトルバーがあって、
その下にメニューがあり、
その下にツールボタンがあり、
その下にコンテンツ表示領域があり、
最下部にはステータスバーがある。

というオーソドックスなスタイルがそれです。

WPF の XAML で記述したオーソドックスなウインドウGUI

Windows Forms の開発において、上記のような GUI のウインドウを作るには、フォームデザイナでフォームの上にコントロールをマウスでポチポチおいていきます。このマウスでポチポチおいていく行為の裏で Form1.designer.cs にそれに応じた内容が自動記述されていきます。

しかし、WPF では開発者がウインドウに配置するコントロールを 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">


    <DockPanel>

        <Menu DockPanel.Dock="Top">

            <MenuItem x:Name="menuApplication" Header="Application">
                <MenuItem x:Name="menuApplicationQuit" Header="Quit" Click="menuApplicationQuit_Click"/>
            </MenuItem>

            <MenuItem x:Name="menuFile" Header="File">
                <MenuItem x:Name="menuFileOpen" Header="Open" Click="menuFileOpen_Click" />
                <MenuItem x:Name="menuFileSave" Header="Save" Click="menuFileSave_Click" />
            </MenuItem>

            <MenuItem x:Name="menuDebug" Header="Debug">
                <MenuItem x:Name="menuDebugExec000" Header="Exec000" Click="menuDebugExec000_Click" />
                <MenuItem x:Name="menuDebugExec001" Header="Exec001" Click="menuDebugExec001_Click" />
                <MenuItem x:Name="menuDebugExec002" Header="Exec002" Click="menuDebugExec002_Click" />
                <MenuItem x:Name="menuDebugExec003" Header="Exec003" Click="menuDebugExec003_Click" />
            </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 />

                <Button x:Name="tbnDebugExec000" Content="Exec000" Width ="48" Height="48" Click="menuDebugExec000_Click"></Button>
                <Button x:Name="tbnDebugExec001" Content="Exec001" Width ="48" Height="48" Click="menuDebugExec001_Click"></Button>
                <Button x:Name="tbnDebugExec002" Content="Exec002" Width ="48" Height="48" Click="menuDebugExec002_Click"></Button>
                <Button x:Name="tbnDebugExec003" Content="Exec003" Width ="48" Height="48" Click="menuDebugExec003_Click"></Button>
                
            </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>
using System;
using System.Collections.Generic;
using System.Linq;
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 partial class MainWindow : Window
	{
		public MainWindow()
		{
			InitializeComponent();
		}

		protected override void OnRender( DrawingContext dc )
		{

			this.stbLabel000.Content = "000";
			this.stbLabel001.Content = "001";
			this.stbLabel002.Content = "002";
			this.stbLabel003.Content = "003";

		}

		private void menuApplicationQuit_Click( object sender, RoutedEventArgs e )
		{
			this.Close();
		}

		private void menuFileOpen_Click( object sender, RoutedEventArgs e )
		{

			if ( sender.Equals( menuFileOpen ))
			{
				MessageBox.Show( "FileOpen() sender is Menu." );
			}
			else
			{
				MessageBox.Show( "FileOpen() sender is ToolButton." );
			}

		}

		private void menuFileSave_Click( object sender, RoutedEventArgs e )
		{

			if ( sender.Equals( menuFileSave ))
			{
				MessageBox.Show( "FileSave() sender is Menu." );
			}
			else
			{
				MessageBox.Show( "FileSave() sender is ToolButton." );
			}

		}

		private void menuDebugExec000_Click( object sender, RoutedEventArgs e )
		{

			if ( sender.Equals( menuDebugExec000 ))
			{
				MessageBox.Show( "DebugExec000() sender is Menu." );
			}
			else
			{
				MessageBox.Show( "DebugExec000() sender is ToolButton." );
			}

		}

		private void menuDebugExec001_Click( object sender, RoutedEventArgs e )
		{

			if ( sender.Equals( menuDebugExec001 ))
			{
				MessageBox.Show( "DebugExec001() sender is Menu." );
			}
			else
			{
				MessageBox.Show( "DebugExec001() sender is ToolButton." );
			}

		}

		private void menuDebugExec002_Click( object sender, RoutedEventArgs e )
		{

			if ( sender.Equals( menuDebugExec002 ))
			{
				MessageBox.Show( "DebugExec002() sender is Menu." );
			}
			else
			{
				MessageBox.Show( "DebugExec002() sender is ToolButton." );
			}

		}

		private void menuDebugExec003_Click( object sender, RoutedEventArgs e )
		{

			if ( sender.Equals( menuDebugExec003 ))
			{
				MessageBox.Show( "DebugExec003() sender is Menu." );
			}
			else
			{
				MessageBox.Show( "DebugExec003() sender is ToolButton." );
			}

		}
	}

}

サンプルソースを用意しました。上記のコードのコピーペーストでうまく動かない場合にお試しください。

上記の xaml と xaml.cs のコードは、従来の Windows Forms アプリケーションのようにイベントハンドラが密結合しており、WPF の売りである MVVM な疎結合ではありません。Windows Forms から移行してきて、てっとりばやく WPF の世界をのぞきたいという場合に参考にしてください。

MVVM な xaml と xaml.cs のコードは、また別の機会でご紹介します。実はキーボードショートカットをつけるには MVVM 必須なのです。面倒だからといっていつまでも MVVM を避けているわけにはいきません。(ショートカットがないアプリケーションはノートパソコンユーザーにとっては不便)

ここからは余談です。

Windows Forms の開発では、フォームデザイナにバグが潜んでいるようで、Form1.cs の GUI デザイン時に Form1.designer.cs との同期がうまくいかず、画面設計がすべて消しとんでしまうという絶望的な経験をしたことが多々ありました。

例えば、プロパティエディタで ToolStripItem.AutoSize プロパティの True/False をしているときに、よくそういうことが起こりました。

WPFでは、いまのところそういう悲しい経験をしたことはありません。