VB のたまご

作成日: 2016/12/06, 更新日: 2016/12/06


WPF系でやらかした失敗を振り返る

  •  この記事は、C# Advent Calendar 2016 の6日目のエントリーです。

  •  こんにちは。 唐突ですが、今年の夏頃から WPF を勉強し始めました。WinForms が染みついている私の頭では、なかなかすんなり理解できず、難しいですね。 さて今回は、WPF を勉強中にやらかしてしまった失敗事例を共有したいと思います。

複数バインドしている ViewModel から指定メンバーを指定したい

  •  例えばこういう構成の Livet アプリケーションがあったとします。

  • イメージ
    ViewModel/MainViewModel.cs
    namespace LivetWPFApplication1.ViewModels
    {
        public class MainViewModel : ViewModel
        {
            public void MainButtonClick()
            {
                Console.WriteLine("Main");
            }
    
            public void Initialize()
            {
            }
        }
    }
    

    ViewModel/SubViewModel.cs
    namespace LivetWPFApplication1.ViewModels
    {
        public class SubViewModel : ViewModel
        {
            public void SubButtonClick()
            {
                Console.WriteLine("Sub");
            }
    
            public void Initialize()
            {
            }
        }
    }
    

    View/MainWindow.xaml
    <Window x:Class="LivetWPFApplication1.Views.MainWindow"
            ~省略~
            >
        
        ~省略~
        
        <Grid>
    
            <Grid.DataContext>
                <vm:MainViewModel />
            </Grid.DataContext>
    
            <Grid.RowDefinitions>
                <RowDefinition Height="1*" />
                <RowDefinition Height="3*" />
            </Grid.RowDefinitions>
    
            <Button Grid.Row="0" Content="header です。" />
    
            <Grid Grid.Row="1">
    
                <Grid.DataContext>
                    <vm:SubViewModel />
                </Grid.DataContext>
    
                <Button Content="contents です。">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="MainButtonClick" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Button>
    
            </Grid>
    
        </Grid>
        
    </Window>
    

  •  この場合、私の頭の中では、×の方で考えていましたが、本来は〇の方だったのでした。

  • イメージ
  •  これを実行すると、こう出ます。

  • ArgumentException はハンドルされませんでした。
    型 'System.ArgumentException' のハンドルされていない例外が Livet.dll で発生しました
    
    追加情報:SubViewModel 型に 引数を持たないメソッド MainButtonClick が見つかりません。
    

  •  それでは、どうすれば、MainViewModel の方を見てくれるのでしょうか? 調べてみると、どうやら、さらに詳細な設定をおこなう Binding.RelativeSource を使って制御するみたいです。 {周回遅れのブルース様記事(Q092. Binding.RelativeSource の使い方がよくわからない)を参考にさせていただきました}

  •  これを見ながら修正したのがこちら。

  • <Button Content="contents です。">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <l:LivetCallMethodAction MethodTarget="{Binding DataContext, RelativeSource={RelativeSource Ancestor, AncestorLevel=2, AncestorType={x:Type Grid}}}" MethodName="MainButtonClick" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>
    

  •  で、出たのがこちら。

  • 要求された値 'Ancestor' が見つかりませんでした。
    

  •  なんでじゃー!でもビルドは通るな。この波線は無視して実行だ!とやったところ、実行はできずこんなエラーが。

  • 例外がスローされました: 'System.Windows.Markup.XamlParseException' (PresentationFramework.dll の中)
    
    追加情報:'テキスト 'Ancestor' から 'RelativeSourceMode' を作成できませんでした。' 行番号 '57'、行位置 '50'

  •  ふむふむ、Ancestor がおかしいとな。 スペルミスったかな、AncestorLevel から Ancestor だけコピペ上書きして再実行だ!(インテリセンスが出ないので手入力してたので) で結果は、ちーん。でした。

  •  全然分かりません。コピペしたから変な制御文字も見えていないけど入ったからかな?とか思って、一回消してもう一度手入力してみたり、 エー、エヌ、シー、イー、エス、ティー、オー、アール、と右矢印キー押しながら目視チェックしてみたり、なぜかずっとスペル間違いにこだわっていました。

  •  もしかして、先祖探しする時って、実家の方向を向いて、ご先祖様に手を合わせないといけない仕様だったりしない? WPF って(最近の Visual Studio って)、思念を感知するような機能があるのか?、そこまですごくなってたのかな?とか思ったりして、 私の頭の中では、思考は冷静に次々可能性を考えてくれましたが、ほとんどダメなやつでした。

  •  もうダメだ、WPF 意味わかんない。Visual Studio の修復だ!再インストールだ!なんだかんだと、vs のバグのせいだ病を発病しそうになりましたが、 ちゃんと記事をなめまわすように見た結果、やっと分かりました。

  • ×Ancestor
    〇FindAncestor
    

  •  正解は、【Find】Ancestor と記載しなければいけないのでした。【相対で探してね、ご先祖様モードで。2階層上の方の Grid にバインドしてるやつね。】ではなく、 【相対で探してね、ご先祖様”探し”モードで。2階層上の方の Grid にバインドしてるやつね。】なのでした。それくらい許してよ~。 今思えば、適当に記事を見ているからだ病にかかっていたのかなと・・・orz。だびょー、だびょー。

  • <Button Content="contents です。">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <l:LivetCallMethodAction MethodTarget="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorLevel=2, AncestorType={x:Type Grid}}}" MethodName="MainButtonClick" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>
    

  •  多分、AncestorLevel, AncestorType と続いていたので、Ancestor しか覚えていなかったのかな?と思っています。 見ているのに、見えなくなっちゃうって怖いですね。

  •  こんな感じで、カメさんレベルで WPF の道を歩いています。早く Xamarin, UWP にもたどり着きたい! 最後まで読んでいただきありがとうございました。