nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

ルーティングイベントの学習 (WPF)

ルーティングイベントとは

  • 観点を機能に置いた場合、イベントを生成したオブジェクト上だけでなく、要素ツリー内の複数のリスナー上でハンドラーを呼び出すことができる種類のイベント
  • 観点を実装に置いた場合、ルーティング イベントは、RoutedEvent クラスのインスタンスによってサポートされる CLR イベントであり、Windows Presentation Foundation (WPF) イベント システムによって処理される

サンプル例

Buttonが3つあり、親要素にStatckPanelがあり、さらに、親要素にBorderがあり、さらに親要素にWindowがある画面。

Buttonのどれかを押すと、StackPanelのClickイベントハンドラが呼ばれ、次に、BorderのClickイベントハンドラが呼ばれ、次に、WindowsのClickイベントハンドラが呼ばれる。

f:id:nprogram:20180418235227p:plain

f:id:nprogram:20180418235435p:plain

f:id:nprogram:20180418235447p:plain

f:id:nprogram:20180418235456p:plain

[MainWindow.xaml]

<Window x:Class="WpfApp1.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"
        mc:Ignorable="d"
        Title="MainWindow"
        SizeToContent="WidthAndHeight" Button.Click="WindowClickHandler">
    <Grid>
        <Border Height="50" Width="Auto" BorderBrush="Gray" BorderThickness="1" Button.Click="BorderClickHandler">
            <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="StackPanelClickHandler">
                <Button Name="YesButton" Width="Auto" >Yes</Button>
                <Button Name="NoButton" Width="Auto" >No</Button>
                <Button Name="CancelButton" Width="Auto" >Cancel</Button>
            </StackPanel>
        </Border>
    </Grid>
</Window>

[MainWindow.xaml.cs]

using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void StackPanelClickHandler(object sender, RoutedEventArgs e)
        {
            FrameworkElement feSource = e.Source as FrameworkElement;
            switch (feSource.Name)
            {
                case "YesButton":
                    MessageBox.Show("[StackPanel] Push Yes Button!");
                    break;
                case "NoButton":
                    MessageBox.Show("[StackPanel] Push No Button!");
                    break;
                case "CancelButton":
                    MessageBox.Show("[StackPanel] Push Cancel Button!");
                    break;
            }
            // e.Handledをtrueにしないかぎり、親要素にボタンクリックイベントに伝搬していく
            e.Handled = false;
        }

        private void BorderClickHandler(object sender, RoutedEventArgs e)
        {
            FrameworkElement feSource = e.Source as FrameworkElement;
            switch (feSource.Name)
            {
                case "YesButton":
                    MessageBox.Show("[Border] Push Yes Button!");
                    break;
                case "NoButton":
                    MessageBox.Show("[Border]Push No Button!");
                    break;
                case "CancelButton":
                    MessageBox.Show("[Border]Push Cancel Button!");
                    break;
            }
            // e.Handledをtrueにしないかぎり、親要素にボタンクリックイベントに伝搬していく
            e.Handled = false;
        }

        private void WindowClickHandler(object sender, RoutedEventArgs e)
        {
            FrameworkElement feSource = e.Source as FrameworkElement;
            switch (feSource.Name)
            {
                case "YesButton":
                    MessageBox.Show("[Window] Push Yes Button!");
                    break;
                case "NoButton":
                    MessageBox.Show("[Window]Push No Button!");
                    break;
                case "CancelButton":
                    MessageBox.Show("[Window]Push Cancel Button!");
                    break;
            }
            e.Handled = true;
        }
    }
}

ハンドラの引数であるRoutedEventArgsに定義されているHandledプロパティを使用すると、イベントルーティングを制御できます。 ハンドラ内でHandledプロパティをtrueにセットした場合、それ以降のイベントルーティングは呼び出されません。 例えば、StackPanelClickHandlerメソッドのe.Handled = falsee.Handled = trueに変更すると、イベントが親要素(Border)に伝搬しなくなります。

RoutedEventについては、この後、記載します。

参考ページ

  • ルーティング イベントの概要

https://msdn.microsoft.com/ja-jp/library/ms742806(v=vs.100).aspx

WPFで、自作イベントの作り方

はじめに

WPFで、自作イベントを作成する方法を調べてみました。

参考サイト

以下のサイトを参考にさせていただきました。

https://msdn.microsoft.com/ja-jp/library/ms742806(v=vs.100).aspx

  • あと、tocsworld 様のCLRイベントとWPF Routedイベントの違いの記事はすごく勉強になります。 tocsworld.wordpress.com

  • あと、英語のページだけどこれもよさげ。 www.c-sharpcorner.com

WPFで簡単なUserControlの作り方

UserControlは便利

繰り返し使用するUI部品は、UserControlにして何度も使用できるようにしたほうが便利です。

例えば、MainWindow.xamlを以下のように記載した場合、同じような表記が繰り返し発生します。 これをUserControlに置き換えます。

<Window x:Class="WpfApp5.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:WpfApp5"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent ="WidthAndHeight">
    <StackPanel Width="Auto" Height="Auto" Margin="10">
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBlock Text="Name : "/>
            <TextBlock Text="Ichiro"/>
            <Button Content="Show Message"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBlock Text="Name : "/>
            <TextBlock Text="Jiro"/>
            <Button Content="Show Message"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBlock Text="Name : "/>
            <TextBlock Text="Saburo"/>
            <Button Content="Show Message"/>
        </StackPanel>
    </StackPanel>
</Window>

ユーザーコントロールは以下のようにプロジェクトに追加します。 プロジェクトのプロパティで、追加を選択して、ユーザーコントロールを選択。 f:id:nprogram:20180409214834p:plain

次に、ユーザーコントロールのxamlファイルを以下のように書き換えて、

<UserControl x:Class="WpfApp5.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp5"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel Orientation="Horizontal" Margin="10">
        <TextBlock Text="Name : "/>
        <TextBlock Text="Ichiro"/>
        <Button Content="Show Message"/>
    </StackPanel>
</UserControl>

MainWindow.xamlも以下のように書き換えます。

<Window x:Class="WpfApp5.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:WpfApp5"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent ="WidthAndHeight">
    <StackPanel Width="Auto" Height="Auto" Margin="10">
        <local:UserControl1/>
        <local:UserControl1/>
        <local:UserControl1/>
    </StackPanel>
</Window>

[表示結果]

f:id:nprogram:20180409215435p:plain

これですと、全部表示が同じですので、UserControlの外側から値を設定できるようにさせます。

(1) MainWindow.xaml書き換え

<Window x:Class="WpfApp5.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:WpfApp5"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent ="WidthAndHeight">
    <StackPanel Width="Auto" Height="Auto" Margin="10">
        <local:UserControl1 DataContext="Ichiro"/>
        <local:UserControl1 DataContext="Jiro"/>
        <local:UserControl1 DataContext="Saburo"/>
    </StackPanel>
</Window>

(2) UserControl1.xaml書き換え

<UserControl x:Class="WpfApp5.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp5"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300" Name="parent">
    <StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=parent}" Margin="10">
        <TextBlock Text="Name : "/>
        <TextBlock Text="{Binding DataContext}"/>
        <Button Content="Show Message"/>
    </StackPanel>
</UserControl>

[表示結果]

f:id:nprogram:20180409221143p:plain

ここで、DataContext="{Binding ElementName=parent}"で、ElementNameプロパティにparentを指定することで、UserControlのUI要素をバインディング・ソースにしてデータ・バインディングを行えます。 StackPanel以下のUIアイテムから、上記のDataContextに対して、直接バインディングできるようです。 これは、コードが短くなります。

あと、上のxamlコードは次にようにも書き換えることができます。 RelativeSourceは、バインディング ターゲットの位置に対して相対的な位置を指定することにより、バインディング ソースを取得または設定します。 例えば {RelativeSource Self} は自身を参照し、{RelativeSource FindAncestor} では自分の要素を内包する、親の要素を対象に指定します。 以下は、UserControlを指定したパターン

<UserControl x:Class="WpfApp5.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp5"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel Orientation="Horizontal" Margin="10">
        <TextBlock Text="Name : "/>
        <TextBlock Text="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
        <Button Content="Show Message"/>
    </StackPanel>
</UserControl>

以下のサイトを参考にしました

  • 独自の依存関係プロパティを作成する

http://yujiro15.net/YKSoftware/tips_DependencyProperty.html

qiita.com

d.hatena.ne.jp

Git

需要はなさそうだけど、一応Gitにもコードを上げます。 github.com

Visual Studioの便利なショートカットまとめ

Visual Studioのショートカットを覚えると、プログラミング時にとても便利です。

特に便利だと思ったのが、コードリーディング時に使用する ・定義へ飛んだ後、戻るときはCtrl + -

qiita.com

あと、コードスニペットも便利です。 C#のWPFで、依存関係プロパティを作成したい場合は、propdpで作成できます。(VS2017で確認済み)

ほかにもたくさんあるようです。 minus9d.hatenablog.com

Visual Studio 2017のプロジェクト テンプレートで、簡単プロジェクトひな形作成

はじめに

MVVMを意識したWPFのプロジェクトを作成するのは結構大変です。もともとVisual Studioのプロジェクトで用意されているWPFプロジェクトは、MVVMを意識したプロジェクトになっていないためです。(´;ω;`)

そこで、自分で、プロジェクトのテンプレートを作成するのがらくちんです。

MVVMを意識したWPFのプロジェクトのひな形作成方法は以下のページが素晴らしいです。
http://yujiro15.net/YKSoftware/MVVM_Tree.html

<MVVMを意識したWPFプロジェクトひな形構成例>
f:id:nprogram:20180220233509p:plain

プロジェクトテンプレート作成方法は、上記のようなプロジェクトのひな形を作成した状態で、 Visual Studio 2017のメニューのプロジェクトから、テンプレートのエクスポートを選択します。 プロジェクトのテンプレートをラジオボタンで選択します。 f:id:nprogram:20180220233644p:plain

テンプレートオプション画面で、テンプレートの説明を入力した後、完了ボタンを押してください。 f:id:nprogram:20180220233700p:plain

自作したプロジェクトテンプレートを、新しいプロジェクト作成時に使用できるようになります。 f:id:nprogram:20180220233838p:plain

なお、NuGetで外部ライブラリ(例えばPrism.WPF)を登録することもできますので、プロジェクトを新規作成するたびに、NuGetで外部ライブラリをインストールする必要がなくなります。(とてもありがたい。)