programing

MVVM을 사용하여 WPF ListView 항목에서 더블 클릭 이벤트 실행

magicmemo 2023. 4. 11. 21:56
반응형

MVVM을 사용하여 WPF ListView 항목에서 더블 클릭 이벤트 실행

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

목록 보기의 항목을 두 번 클릭하면 뷰 모델에서 해당 이벤트가 실행되고 해당 항목에 대한 참조가 클릭되도록 목록 보기의 항목에 두 번 클릭 이벤트를 첨부하는 올바른 방법은 무엇입니까?

깨끗한 MVVM 방식으로(View 뒤에 코드가 없음) 하려면 어떻게 해야 합니까?

제발, 코드 배후에 있는 건 나쁜 게 아니야불행하게도, WPF 커뮤니티의 많은 사람들이 이것을 잘못 알고 있다.

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

데이터 바인딩이 모든 것을 해결할 수 있는 것은 아니기 때문에 뒤에 코드를 작성해야 하는 시나리오를 충분히 알고 있습니다.이 시나리오에서는 파일의 코드 배후에 있는 Double Click 이벤트를 처리하고 이 콜을 View Model에 위임합니다.

뒤에 코드를 사용하면서도 여전히 MVVM 분리를 충족하는 샘플 애플리케이션은 다음과 같습니다.

WAF ( Win Application Framework ) - https://github.com/jbe2277/waf

이 기능을 에 적용할 수 있습니다.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는 접속된 명령어 동작을 매우 적절하게 구현하고 있습니다.이를 사용하여 ListView의 ItemContainerStyle 속성에 각 ListViewItem에 대한 명령을 설정하는 스타일을 할당할 수 있습니다.

여기서는 Mouse Double Click 이벤트에서 실행할 명령어를 설정하고 Command Parameter를 클릭하는 데이터 개체가 됩니다.여기서는 사용하고 있는 명령어를 얻기 위해 비주얼 트리로 이동하지만 애플리케이션 전체에 걸친 명령어를 쉽게 작성할 수 있습니다.

<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 Event 트리거를 사용하여 매우 쉽고 깔끔한 방법을 찾았습니다.MVVM을 청소하고 재사용 가능하며 코드 비하인드가 없습니다.

이미 다음과 같은 기능이 있을 수 있습니다.

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

ListView용 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의 "위"에 시각적 요소가 있는 경우(그리드로 시작하는 Probalby) 트리거를 배치할 수도 있습니다.

안타깝게도 Mouse Double Click 이벤트는 모든 시각적 요소에서 생성되지 않습니다(예를 들어 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>

이 논의는 1년이 지났습니다만, 와의 논의입니다.NET 4, 이 솔루션에 대한 의견이 있습니까?MVVM의 목적은 파일 뒤에 있는 코드를 제거하는 것이 아니라는 것에 전적으로 동의합니다.저도 뭔가 복잡하다고 해서 좋은 건 아니라는 생각이 들어요.뒤에 있는 코드에 입력한 내용은 다음과 같습니다.

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

Caliburn's Action 기능을 사용하여 ViewModel의 메서드에 이벤트를 매핑할 수 있습니다.예를 들어,ItemActivated your 서 method on on のViewModelXAML을 사용하다

<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 controls (update controls+avalondock):

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

새로운 뷰를 여는 방법이라면 어떤 방법으로든 접근해야 합니다.

로 이 기능을 확립할 수 있습니다.인터랙티브 라이브러리를 사용한 Net 4.7 프레임워크, 우선 XAML 파일에서 네임스페이스를 선언합니다.

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

그런 다음 다음과 같이 ListView 내에서 각각의 InvokeCommandAction을 사용하여 이벤트 트리거를 설정합니다.

표시:

<ListView x:Name="lv" IsSynchronizedWithCurrentItem="True" 
          ItemsSource="{Binding Path=AppsSource}"  >
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction CommandParameter="{Binding ElementName=lv, Path=SelectedItem}"
                                   Command="{Binding OnOpenLinkCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
            <GridViewColumn Header="Developed By" DisplayMemberBinding="{Binding DevelopedBy}" />
        </GridView>
    </ListView.View>
</ListView>

위의 코드를 수정하면 ViewModel에서 더블클릭이벤트가 작동하는데 충분합니다만, 완전한 아이디어를 얻을 수 있도록 예제의 모델과 View Model 클래스를 추가했습니다.

모델:

public class ApplicationModel
{
    public string Name { get; set; }

    public string DevelopedBy { get; set; }
}

모델 표시:

public class AppListVM : BaseVM
{
        public AppListVM()
        {
            _onOpenLinkCommand = new DelegateCommand(OnOpenLink);
            _appsSource = new ObservableCollection<ApplicationModel>();
            _appsSource.Add(new ApplicationModel("TEST", "Luis"));
            _appsSource.Add(new ApplicationModel("PROD", "Laurent"));
        }

        private ObservableCollection<ApplicationModel> _appsSource = null;

        public ObservableCollection<ApplicationModel> AppsSource
        {
            get => _appsSource;
            set => SetProperty(ref _appsSource, value, nameof(AppsSource));
        }

        private readonly DelegateCommand _onOpenLinkCommand = null;

        public ICommand OnOpenLinkCommand => _onOpenLinkCommand;

        private void OnOpenLink(object commandParameter)
        {
            ApplicationModel app = commandParameter as ApplicationModel;

            if (app != null)
            {
                //Your code here
            }
        }
}

DelegateCommand 클래스의 구현이 필요한 경우.

InuptBindings에서 rushui에서 솔루션을 보았지만 ListView 영역을 찾을 수 없었습니다.텍스트가 없는 항목 - 배경을 투명하게 설정한 후에도 다른 템플릿을 사용하여 해결했습니다.

이 템플릿은 ListView가항목이 선택되었으며 활성 상태입니다.

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

이 템플릿은 ListView가항목이 선택되었으며 비활성 상태입니다.

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

이것은 ListView에 사용되는 기본 스타일입니다.항목:

<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의 반복과 텍스트 바인딩이 마음에 들지 않습니다.한 곳에서만 선언할 수 있을지 모르겠습니다.

이게 누군가에게 도움이 됐으면 좋겠어!

여기 둘 다에게 그렇게 하는 행동이 있다.ListBox그리고.ListView.

public class ItemDoubleClickBehavior : Behavior<ListBox>
{
    #region Properties
    MouseButtonEventHandler Handler;
    #endregion

    #region Methods

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.PreviewMouseDoubleClick += Handler = (s, e) =>
        {
            e.Handled = true;
            if (!(e.OriginalSource is DependencyObject source)) return;

            ListBoxItem sourceItem = source is ListBoxItem ? (ListBoxItem)source : 
                source.FindParent<ListBoxItem>();

            if (sourceItem == null) return;

            foreach (var binding in AssociatedObject.InputBindings.OfType<MouseBinding>())
            {
                if (binding.MouseAction != MouseAction.LeftDoubleClick) continue;

                ICommand command = binding.Command;
                object parameter = binding.CommandParameter;

                if (command.CanExecute(parameter))
                    command.Execute(parameter);
            }
        };
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewMouseDoubleClick -= Handler;
    }

    #endregion
}

다음은 부모를 찾는 데 사용되는 확장 클래스입니다.

public static class UIHelper
{
    public static T FindParent<T>(this DependencyObject child, bool debug = false) where T : DependencyObject
    {
        DependencyObject parentObject = VisualTreeHelper.GetParent(child);

        //we've reached the end of the tree
        if (parentObject == null) return null;

        //check if the parent matches the type we're looking for
        if (parentObject is T parent)
            return parent;
        else
            return FindParent<T>(parentObject);
    }
}

사용방법:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:coreBehaviors="{{Your Behavior Namespace}}"


<ListView AllowDrop="True" ItemsSource="{Binding Data}">
    <i:Interaction.Behaviors>
       <coreBehaviors:ItemDoubleClickBehavior/>
    </i:Interaction.Behaviors>

    <ListBox.InputBindings>
       <MouseBinding MouseAction="LeftDoubleClick" Command="{Binding YourCommand}"/>
    </ListBox.InputBindings>
</ListView>

언급URL : https://stackoverflow.com/questions/1035023/firing-a-double-click-event-from-a-wpf-listview-item-using-mvvm

반응형