none
ListBoxのクリックイベントが起こせない RRS feed

  • 質問

  • お世話になっております。

    表題の通り、ListBoxにてクリックイベントが起こせません。

    <ListBox ItemsSource="{Binding ListString}" 
         SelectedItem="{Binding SelectedItem, Mode=TwoWay}" 
         BorderThickness="0,0,0,0" 
         ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        
        <ListBox.ItemTemplate>
            <DataTemplate>
    
                <Grid Name="gridItem" Height="16" Background="#FFFFFFFF">
                    <TextBlock Text="{Binding}" FontSize="14" Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=ActualWidth}">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="MouseLeftButtonUp">
                                <i:InvokeCommandAction Command="{Binding SelectedItemClickCommand}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </TextBlock>
                </Grid>
    
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    どこが間違っているのでしょうか?
    素人質問で申し訳ないですが、どうかご教示ください。

    2017年3月29日 4:46

回答

  • バインドしているCommandが、"SelectedItem"ClickCommandとなっているのが気になります。

    このバインド式の場合、"SelectedItemClickCommand"を実装するのは個々のアイテムを表すクラスになりますが、そうなっていますか? ネーミング的に、個々のアイテムよりも上位のクラスに実装されているような気がするのですが…。

    ListBoxのItemsSourceにバインドしているのがListStringというプロパティのようですが、これはひょっとしてList<string>かなにかでしょうか。となれば、個々のアイテムはstring型と言うことになり、当然ICommandなSelectedItemClickCommandを実装することもできないということになりますが。

    class Item {
        public ICommand ClickCommand { ... }
        public string Name { ... }
    }
    みたいにして、List<string>の代わりにList<Item>のように保持し、TextBlockはText={Binding Name}とするような形になります。

    • 編集済み Hongliang 2017年3月29日 5:30
    • 回答としてマーク コーベル 2017年3月29日 6:19
    2017年3月29日 5:30
  • ネーミングは、単に私が違和感を覚えるようになった理由ってだけです。基本的に命名は自由です。

    バインド式は、SourceやRelativeSourceを明示しない場合、バインディングソースとして、現在のDataContextを使用します。DataContextは、基本的にはビジュアルツリーの親を引き継ぎます。<Window><Grid><ListBox>という構造になっていれば、ListBoxはGridのDataContextを、GridはWindowのDataContextを。

    なので、WindowのDataContextにItemsプロパティを持つオブジェクトを設定した場合、上記構造でListBoxにItemsSource={Binding Items}と指定すれば、Windowから引き継いだオブジェクトをソースにすることができるのです。

    ところが、一部のケースによってはDataContextがツリー途中で継承されないケースがあります。今回のListBoxのItemTemplateもその一つです。

    考えれば当然の話ですが、ItemTemplateは個々のアイテムについてどう描画するかを定義するものなので、ListBoxのDataContextを引き継いでも何も嬉しくありません。必要なのは「どのアイテムか」です。なので、ここでDataContextは、ListBoxのDataContextの代わりに、ItemsSourceの各要素となっているオブジェクト(コーベルさんが元々使用していたコードではString型のものですし、私の先のレスではItem型のものです)が設定されることになります。

    DataContextの継承はDataTemplateで一旦途切れますが、DataTemplateからは再びDataContextが継承されます。ここで継承されるのは、当然ItemsSourceの各要素となっているオブジェクト(String/Item)です。

    StringやItemが継承されるので、Stringが個々の要素である場合はTextBlockにてText="{Binding}"とやったときにそのStringが表示されますし、Itemが個々の要素である場合はText={Binding Name}とやればItemのNameプロパティが表示されます。

    さて、ここでツリーを改めて見返すと、i:InvokeCommandActionもまたDataTemplate以下のツリーに存在しています。

    そのため、i:InvokeCommandActionにてCommand={Binding HogeCommand}とした場合、DataTemplateのDataContextを継承するので、やはりソースはStringやItemが該当することになります。String型であれば、当然ながらそんなプロパティは持っていないので、このバインディングは失敗します。

    // どうしても説明が長く分かりづらくなってしまうなぁ…。

    // 先に解決されたようだけど一応解説として投稿。

    • 回答としてマーク コーベル 2017年3月29日 6:39
    2017年3月29日 6:21

すべての返信

  • バインドしているCommandが、"SelectedItem"ClickCommandとなっているのが気になります。

    このバインド式の場合、"SelectedItemClickCommand"を実装するのは個々のアイテムを表すクラスになりますが、そうなっていますか? ネーミング的に、個々のアイテムよりも上位のクラスに実装されているような気がするのですが…。

    ListBoxのItemsSourceにバインドしているのがListStringというプロパティのようですが、これはひょっとしてList<string>かなにかでしょうか。となれば、個々のアイテムはstring型と言うことになり、当然ICommandなSelectedItemClickCommandを実装することもできないということになりますが。

    class Item {
        public ICommand ClickCommand { ... }
        public string Name { ... }
    }
    みたいにして、List<string>の代わりにList<Item>のように保持し、TextBlockはText={Binding Name}とするような形になります。

    • 編集済み Hongliang 2017年3月29日 5:30
    • 回答としてマーク コーベル 2017年3月29日 6:19
    2017年3月29日 5:30
  • Hongliangさん、ご回答ありがとうございます。

    すみません、ご教示頂いた内容についていけずにいます、、

    このバインド式の場合、"SelectedItemClickCommand"を実装するのは個々のアイテムを表すクラスになりますが、そうなっていますか? ネーミング的に、個々のアイテムよりも上位のクラスに実装されているような気がするのですが…。

    命名がおかしいということでしょうか?
    クリックイベントを拾ってSelectedItemで処理しようと思ってたのですが

    みたいにして、List<string>の代わりにList<Item>のように保持し、TextBlockはText={Binding Name}とするような形になります。

    えと、この型にするとクリック時にClickCommandが走るということですか?
    欲しいのは文字列だけだったのでList<String>にしています。

    2017年3月29日 5:41
  • あぁすみません、正常に動作しました。

    ListBoxのアイテム1つ毎にCommandを実装しなくてはいけなかったのですね。

    勉強させていただきました、ありがとうございました!

    2017年3月29日 6:18
  • ネーミングは、単に私が違和感を覚えるようになった理由ってだけです。基本的に命名は自由です。

    バインド式は、SourceやRelativeSourceを明示しない場合、バインディングソースとして、現在のDataContextを使用します。DataContextは、基本的にはビジュアルツリーの親を引き継ぎます。<Window><Grid><ListBox>という構造になっていれば、ListBoxはGridのDataContextを、GridはWindowのDataContextを。

    なので、WindowのDataContextにItemsプロパティを持つオブジェクトを設定した場合、上記構造でListBoxにItemsSource={Binding Items}と指定すれば、Windowから引き継いだオブジェクトをソースにすることができるのです。

    ところが、一部のケースによってはDataContextがツリー途中で継承されないケースがあります。今回のListBoxのItemTemplateもその一つです。

    考えれば当然の話ですが、ItemTemplateは個々のアイテムについてどう描画するかを定義するものなので、ListBoxのDataContextを引き継いでも何も嬉しくありません。必要なのは「どのアイテムか」です。なので、ここでDataContextは、ListBoxのDataContextの代わりに、ItemsSourceの各要素となっているオブジェクト(コーベルさんが元々使用していたコードではString型のものですし、私の先のレスではItem型のものです)が設定されることになります。

    DataContextの継承はDataTemplateで一旦途切れますが、DataTemplateからは再びDataContextが継承されます。ここで継承されるのは、当然ItemsSourceの各要素となっているオブジェクト(String/Item)です。

    StringやItemが継承されるので、Stringが個々の要素である場合はTextBlockにてText="{Binding}"とやったときにそのStringが表示されますし、Itemが個々の要素である場合はText={Binding Name}とやればItemのNameプロパティが表示されます。

    さて、ここでツリーを改めて見返すと、i:InvokeCommandActionもまたDataTemplate以下のツリーに存在しています。

    そのため、i:InvokeCommandActionにてCommand={Binding HogeCommand}とした場合、DataTemplateのDataContextを継承するので、やはりソースはStringやItemが該当することになります。String型であれば、当然ながらそんなプロパティは持っていないので、このバインディングは失敗します。

    // どうしても説明が長く分かりづらくなってしまうなぁ…。

    // 先に解決されたようだけど一応解説として投稿。

    • 回答としてマーク コーベル 2017年3月29日 6:39
    2017年3月29日 6:21
  • Hongliangさん、ご回答ありがとうございます。

    「あぁそういう感じなのね」って程度の理解だったので本当に詳しく解りやすく
    説明いただいて本当にスッキリ勉強できました。

    どうしてもWinFormsの感覚が抜けなくて、クリックイベントからListBox.Itemsが
    取得できると思い込んでしまいます。

    ありがとうございました!

    2017年3月29日 6:30
  • ListBoxのアイテム1つ毎にCommandを実装しなくてはいけなかったのですね。

    それでも良いのですが、私はどうしても無駄に思えてしまいますので、以下のようにすることが多いです。

    <ListView.ContextMenu>
        <ContextMenu>
            <MenuItem Header="選択" Command="{Binding 選択Command}"
             CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"/>
        </ContextMenu>
    </ListView.ContextMenu>
    EventTriggerに関しても同じようにできます。私は、ダブルクリックの処理をこのような形で実装することが多いです。

    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2017年3月30日 5:40
    モデレータ