クリックイベント - wpf ダブルクリック




MVVMを使用してWPF ListViewアイテムからダブルクリックイベントを発生させる (6)

CaliburnのAction機能を使用して、ViewModelのメソッドにイベントをマップできます。 ViewModel ItemActivatedメソッドがあると仮定すると、対応するXAMLは次のようになります。

<ListView x:Name="list" 
   Message.Attach="[Event MouseDoubleClick] = [Action ItemActivated(list.SelectedItem)]" >

詳細については、Caliburnのドキュメントとサンプルを調べることができます。

MVVMを使用しているWPFアプリケーションでは、リストビュー項目を持つusercontrolがあります。 実行時には、データバインディングを使用してリストビューにオブジェクトのコレクションを埋め込みます。

ダブルクリックイベントをリストビュー内のアイテムにアタッチする正しい方法は何ですか。リストビュー内のアイテムがダブルクリックされると、ビューモデル内の対応するイベントが発生し、クリックされたアイテムへの参照があります。

どのようにクリーンなMVVMの方法で、つまり、ビューの後ろにコードを実行することはできますか?


コードは、悪いことではありません。 残念ながら、WPFコミュニティのかなりの人がこれを間違っていました。

MVVMはコードを排除するパターンではありません。 ビュー部分(外観、アニメーションなど)をロジック部分(ワークフロー)から分離することです。 さらに、ロジック部のユニットテストも可能です。

私は、データバインディングがすべての解決策ではないため、コードを記述しなければならない十分なシナリオを知っています。 あなたのシナリオでは、コードビハインドファイルでDoubleClickイベントを処理し、この呼び出しをViewModelに委譲します。

コードを使用していて、MVVM分離を実行しているサンプルアプリケーションは、ここで見つけることができます:

WPFアプリケーションフレームワーク(WAF) - http://waf.codeplex.com


私はrushuiからInuptBindingsのソリューションを見ましたが、背景が透明になっていてもListViewItemの領域にはテキストがないところにヒットできませんでしたので、別のテンプレートを使用して解決しました。

このテンプレートは、ListViewItemが選択され、アクティブなときのためのテンプレートです:

<ControlTemplate x:Key="SelectedActiveTemplate" TargetType="{x:Type ListViewItem}">
   <Border Background="LightBlue" HorizontalAlignment="Stretch">
   <!-- Bind the double click to a command in the parent view model -->
      <Border.InputBindings>
         <MouseBinding Gesture="LeftDoubleClick" 
                       Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ItemSelectedCommand}"
                       CommandParameter="{Binding}" />
      </Border.InputBindings>
      <TextBlock Text="{Binding TextToShow}" />
   </Border>
</ControlTemplate>

このテンプレートは、ListViewItemが選択されていて、非アクティブなときに使用されます。

<ControlTemplate x:Key="SelectedInactiveTemplate" TargetType="{x:Type ListViewItem}">
   <Border Background="Lavender" HorizontalAlignment="Stretch">
      <TextBlock Text="{Binding TextToShow}" />
   </Border>
</ControlTemplate>

これは、ListViewItemに使用されるデフォルトスタイルです。

<Style TargetType="{x:Type ListViewItem}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate>
            <Border HorizontalAlignment="Stretch">
               <TextBlock Text="{Binding TextToShow}" />
            </Border>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
   <Style.Triggers>
      <MultiTrigger>
         <MultiTrigger.Conditions>
            <Condition Property="IsSelected" Value="True" />
            <Condition Property="Selector.IsSelectionActive" Value="True" />
         </MultiTrigger.Conditions>
         <Setter Property="Template" Value="{StaticResource SelectedActiveTemplate}" />
      </MultiTrigger>
      <MultiTrigger>
         <MultiTrigger.Conditions>
            <Condition Property="IsSelected" Value="True" />
            <Condition Property="Selector.IsSelectionActive" Value="False" />
         </MultiTrigger.Conditions>
         <Setter Property="Template" Value="{StaticResource SelectedInactiveTemplate}" />
      </MultiTrigger>
   </Style.Triggers>
</Style>

私が気に入らないのは、TextBlockとそのテキストバインディングの繰り返しです。私は2つの場所を宣言することはできません。

これが誰かを助けることを願っています


私はBlend SDK Eventトリガーでこれを行うための非常に簡単でクリーンな方法を見つけました。 MVVMをクリーンで、再利用可能で、コードビハインドなし。

あなたはおそらく既に次のようなものを持っています:

<Style x:Key="MyListStyle" TargetType="{x:Type ListViewItem}">

ListViewItemのControlTemplateをまだ使用していない場合は、次のように追加します。

<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type ListViewItem}">
      <GridViewRowPresenter Content="{TemplateBinding Content}"
                            Columns="{TemplateBinding GridView.ColumnCollection}" />
    </ControlTemplate>
  </Setter.Value>
 </Setter>

GridViewRowPresenterは、リストの行要素を構成するすべての要素の「内側」のビジュアルルートになります。 これで、MouseDoubleClickのルーティングイベントを検索し、InvokeCommandAction経由で次のようなコマンドを呼び出すためのトリガーを挿入することができました:

<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type ListViewItem}">
      <GridViewRowPresenter Content="{TemplateBinding Content}"
                            Columns="{TemplateBinding GridView.ColumnCollection}">
        <i:Interaction.Triggers>
          <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
          </i:EventTrigger>
        </i:Interaction.Triggers>
      </GridViewRowPresenter>
    </ControlTemplate>
  </Setter.Value>
 </Setter>

視覚的な要素がGridRowPresenterの上にある場合(グリッドで始まると思われる)、そこにトリガを置くこともできます。

残念ながら、MouseDoubleClickイベントは、すべてのビジュアル要素から生成されるわけではありません(Controlsからのもので、FrameworkElementsからのものではありません)。 この問題を回避するには、EventTriggerからクラスを派生させ、ClickCountが2のMouseButtonEventArgsを探します。これにより、ClickCount!= 2で非MouseButtonEventsおよびすべてのMoseButtonEventsを効果的にフィルタリングします。

class DoubleClickEventTrigger : EventTrigger
{
    protected override void OnEvent(EventArgs eventArgs)
    {
        var e = eventArgs as MouseButtonEventArgs;
        if (e == null)
        {
            return;
        }
        if (e.ClickCount == 2)
        {
            base.OnEvent(eventArgs);
        }
    }
}

これを書くことができます( 'h'は上記のヘルパークラスの名前空間です):

<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type ListViewItem}">
      <GridViewRowPresenter Content="{TemplateBinding Content}"
                            Columns="{TemplateBinding GridView.ColumnCollection}">
        <i:Interaction.Triggers>
          <h:DoubleClickEventTrigger EventName="MouseDown">
            <i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
          </h:DoubleClickEventTrigger>
        </i:Interaction.Triggers>
      </GridViewRowPresenter>
    </ControlTemplate>
  </Setter.Value>
 </Setter>

私はこの議論が一年であることを認識していますが、.NET 4ではこの解決策について考えていますか? 私は絶対にMVVMのポイントは、ファイルの背後にあるコードを排除することではないことに同意します。 私はまた、何かが複雑であるという理由だけで、それが良いというわけではないということを強く感じています。 私がコードの中に入れたのは以下のとおりです:

    private void ButtonClick(object sender, RoutedEventArgs e)
    {
        dynamic viewModel = DataContext;
        viewModel.ButtonClick(sender, e);
    }

私はこれを.NET 4.5で動作させることができます。 まっすぐ進むと思われ、第三者やコードビハインドは必要ありません。

<ListView ItemsSource="{Binding Data}">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid Margin="2">
                    <Grid.InputBindings>
                        <MouseBinding Gesture="LeftDoubleClick" Command="{Binding ShowDetailCommand}"/>
                    </Grid.InputBindings>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Image Source="..\images\48.png" Width="48" Height="48"/>
                    <TextBlock Grid.Row="1" Text="{Binding Name}" />
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>




mvvm