[wpf] MVVM을 사용하여 WPF ListView 항목에서 두 번 클릭 이벤트 발생

MVVM을 사용하는 WPF 응용 프로그램에서 목록보기 항목이있는 사용자 컨트롤이 있습니다. 런타임에는 데이터 바인딩을 사용하여 목록보기를 개체 컬렉션으로 채 웁니다.

목록보기의 항목에 더블 클릭 이벤트를 첨부하여 목록보기의 항목을 두 번 클릭하면보기 모델의 해당 이벤트가 발생하고 클릭 된 항목에 대한 참조를 갖도록하는 올바른 방법은 무엇입니까?

어떻게 깨끗한 MVVM 방식으로 할 수 있습니까?



답변

코드 숨김은 전혀 나쁜 것이 아닙니다. 불행히도 WPF 커뮤니티의 많은 사람들이이 문제를 잘못 알고 있습니다.

MVVM은 뒤에있는 코드를 제거하는 패턴이 아닙니다. 뷰 파트 (모양, 애니메이션 등)와 로직 파트 (워크 플로)를 분리하는 것입니다. 또한 논리 부분을 단위 테스트 할 수 있습니다.

데이터 바인딩이 모든 것에 대한 해결책이 아니기 때문에 코드를 작성해야하는 시나리오를 충분히 알고 있습니다. 귀하의 시나리오에서는 파일 뒤에있는 코드에서 DoubleClick 이벤트를 처리하고이 호출을 ViewModel에 위임합니다.

코드 숨김을 사용하고 MVVM 분리를 수행하는 샘플 응용 프로그램은 여기에서 찾을 수 있습니다.

WPF 응용 프로그램 프레임 워크 (WAF) – http://waf.codeplex.com


답변

.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>


답변

첨부 된 명령 동작 및 명령 을 사용하고 싶습니다 . Marlon Grech 는 Attached Command Behaviors를 아주 잘 구현했습니다. 이를 사용하여 각 ListViewItem에 대한 명령을 설정하는 ListView의 ItemContainerStyle 속성에 스타일을 할당 할 수 있습니다.

여기서는 MouseDoubleClick 이벤트에서 실행될 명령을 설정하고 CommandParameter는 클릭하는 데이터 개체가됩니다. 여기에서는 사용중인 명령을 얻기 위해 시각적 트리를 살펴 보지만 응용 프로그램 전체의 명령을 쉽게 만들 수 있습니다.

<Style x:Key="Local_OpenEntityStyle"
       TargetType="{x:Type ListViewItem}">
    <Setter Property="acb:CommandBehavior.Event"
            Value="MouseDoubleClick" />
    <Setter Property="acb:CommandBehavior.Command"
            Value="{Binding ElementName=uiEntityListDisplay, Path=DataContext.OpenEntityCommand}" />
    <Setter Property="acb:CommandBehavior.CommandParameter"
            Value="{Binding}" />
</Style>

명령의 경우 ICommand를 직접 구현 하거나 MVVM Toolkit 에있는 것과 같은 일부 도우미를 사용할 수 있습니다 .


답변

Blend SDK 이벤트 트리거로이 작업을 수행하는 매우 쉽고 깔끔한 방법을 찾았습니다. 재사용 가능하고 코드 숨김이없는 깨끗한 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 이벤트는 모든 시각적 요소에서 생성되지는 않습니다 (예를 들어 FrameworkElements가 아닌 Controls에서 생성됨). 해결 방법은 EventTrigger에서 클래스를 파생시키고 ClickCount가 2 인 MouseButtonEventArgs를 찾는 것입니다. 이렇게하면 모든 비 MouseButtonEvents와 ClickCount! = 2 인 모든 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>


답변

이 토론은 1 년이 지났지 만 .NET 4에서이 솔루션에 대한 생각이 있습니까? 나는 MVVM의 요점이 파일 뒤에있는 코드를 제거하는 것이 아니라는 데 절대적으로 동의합니다. 나는 또한 무언가가 복잡하다고해서 그것이 더 낫다는 의미는 아니라는 것을 매우 강하게 느낍니다. 다음은 내가 코드에 넣은 내용입니다.

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


답변

Caliburn 의 Action 기능을 사용하여 이벤트를 ViewModel의 메서드에 매핑 할 수 있습니다 . 에 ItemActivated메서드 가 있다고 가정하면 ViewModel해당 XAML은 다음과 같습니다.

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

자세한 내용은 Caliburn의 문서 및 샘플을 검토 할 수 있습니다.


답변

보기를 만들 때 명령을 연결하는 것이 더 간단하다는 것을 알았습니다.

var r = new MyView();
r.MouseDoubleClick += (s, ev) => ViewModel.MyCommand.Execute(null);
BindAndShow(r, ViewModel);

제 경우에는 BindAndShow다음과 같습니다 (updatecontrols + avalondock).

private void BindAndShow(DockableContent view, object viewModel)
{
    view.DataContext = ForView.Wrap(viewModel);
    view.ShowAsDocument(dockManager);
    view.Focus();
}

접근 방식은 새로운 뷰를 여는 방법에 관계없이 작동해야합니다.