WPF ListView에서 항목을 효과적으로 제거하는 방법

현재 DevExpress MVVM 프레임워크를 이용해서 MVVM을 하고 있습니다.
Native ListView에 DataTemplate 형태로 button이 들어있는데, 이 버튼을 클릭했을 때 ListView에서 Item이 제거되게 하고 싶은데, 그러려면 조상 찾기로 DataContext를 참조해서 Command를 바인딩하거나, ListView에 DataContext를 따로 할당해서 Command를 바인딩 해야할 것 같습니다.

이 경우 어느 방법이 효율적일까요?

또한 제거를 한다고 했을 때 데이터를 어떤식으로 전달해서 제거할 수 있을까요?

좋아요 1

@Vincent 저도 개인적으로 ListBox나 DataGrid, TreeView 등에서 자주 사용하고, 또 고민하고 있어요. :smile:

저도 일반적으로 가장 가까운 조상(ViewModel) 찾아 처리하고 있습니다.
(되도록이면 Window, UserControl 수준의 큼직한 ViewModel에서 Command를 선언하고 있습니다.)

.xaml

<DataTemplate x:Key="ListViewItem1">
    <Button Command="{Binding 
        RelativeSource={RelativeSource AncestorType=ListView},
        Path=DataContext.ItemClickCommand}"
        CommandParameter="{Binding RelativeSource Self}"/>
</DataTemplate>

.cs

public class MyViewModel
{
    public ICommmand ItemClickCommand { get; set; }
    public ObservableCollection<UserModel> Users { get; set;}

    public MyViewModel()
    {
        Users = GetUsers();
        ItemClickCommand = new RelayCommand<Button>(ItemClick);   
    }

    private void ItemClick(Button item)
    {
        UserModel selected = (UserModel)item.DataContext;
        Users.Remove(selected);
    }
}

전 거의 이렇게 사용하고 있고요!

그리고 저는 무언가를 전달(CommandParameter)할때 UI객체를 보내면 포함된 DataContext까지 함께 활용할 수 있기 때문에 이 방법을 선호합니다.

그리고 컨트롤 Class를 상속받아 Command를 등록(DependencyProperty)하여 사용하기도 합니다.

<custom:JamesTreeView>
    <Setter Property="SelectedCommand" Value="{Binding UserSelectedCommand"/>
</custom:JamesTreeView>

이러면 컨트롤 내부에서 다양한 이벤트 동작을 섬세하게 판단하고, Command를 좀 더 자유롭게 동작(invoke) 시킬 수 있어서 좋은 것 같습니다.

좋아요 2

제 생각으로는 RelativeSource를 쉽게 사용하는게 별로 좋은 방법이 아닌거 같습니다.
RelativeSource를 쓰면 꽤(?) 복잡한 경로의 개체를 참조할 순 있지만 남용한다면 스파게티 참조가 이러납니다.

ViewModel에 직접적으로 ICommand 타입의 Remove용 커맨드를 속성으로 포함시키고 Binding 하는편은 어떠신가요.

@james.lee 님의 MyViewModelUserModel을 예로 든다면…

UserModelpublic ICommand RemoveCommand를 포함시키는 것입니다.
만약 UserModel이 도메인, 혹은 DTO 같은 다른 종류의 개체라 ICommand를 포함시킬 수 없다면
UserViewModel로 새로 만드는것을 추천드립니다.

오히려 MVVM의 ViewModel은 View의 추상화이기에 ViewModel을 만들어 사용가능한 명령(ICommand)까지 추상화하는게 좋다고 생각합니다.

추가적으로 전용 ViewModel을 만들긴 했지만, RemoveCommand를 위한 로직을 포함시키기 애매하다면 (종속따위와 같은 문제로) 외부에서 RemoveCommand에 대한 인스턴스를 생성자로 주입받을 수 있습니다.

예를 들어 아래 코드와 비슷합니다.

public class MyViewModel
{
    public ObservableCollection<UserViewModel> Users { get; set;}
    ....
}

public class UserViewModel
{
   Protected UserModel Source { get; }
   ....
   public ICommand RemoveCommand { get; }

   public UserViewModel(UserModel user, ICommand removeCommand)
   {
        this.Source = user;
        this.RemoveCommand = removeCommand;
   }
}
좋아요 4

저도 이 방법이 맞다고 생각합니다. ICommand 가 사실 그런 의도에 가깝게 만들어진 것 이라고 생각합니다.

좋아요 2

하지만 RelativeSource를 사용할 때 ViewModel이 없는 하위에서 상위 ViewModel을 찾는 것은 괜찮은 방법이지 않을까요? 이 방식을 지지하시는 분도 나오셨으면 좋겠습니다… :smile:

@SangHyeon.Kim
그리고 아무래도 계속 해왔던 방식이 익숙하다 보니 ListViewItem과 같은 부분에 Model이 아닌 생성자가 포함된 ViewModel을 사용하는 것이 아직도 약간 어색하다고 느껴지는데요. 저도 많이 생각해보고 배워야 할 것 같습니다.

저도 큰 도움이 되는 것 같습니다. :smile:

적극 사용해보겠습니다. 좋은 설명 감사합니다!!

좋아요 1

저는 UI와 직결되는 기능은 VM이 아닌 비하인드 코드쪽에서 처리하는게 맞지 않나… 생각합니다.

제가 생각한 방식은 ListView의 새 아이템이 생성될 이벤트에서 새롭게 생성하는 버튼 객체에 버튼클릭 이벤트를 추가하는 것 입니다.

WPF에서 UI 작업은 메인 스레드에서 이뤄져야 하고 이를 VM에서 처리하는 것은 비동기쪽 다룰 때 문제가 발생할 확률이 높다고 생각하기 때문입니다.

지금은 컴퓨터를 못 써서 확인을 못 해봤는데 내일 이 방법으로 해보고 이 내용에 업데이트 하겠습니다.

좋아요 2

아 근데 말씀해주신것도 좋은 방법이라 생각합니다! ㅎㅎㅎ
제가 요즘 좀 WPF좀 해보니…
MVVM의 ViewModel은 View의 추상화인데 어느 정도 수준까지 추상화하는지는 개인마다 틀린거 같습니다.

저 같은 경우에는 View의 모든 데이터(Public Property)와 동작(ICommand)을 ViewModel로 추상화합니다.

이 경우 해당 View가 갖추어야하는 모든 Logic 코드가 ViewModel에 존재하게 되는데,
이럴 경우 온전한 단위테스트가 가능하며, 해당 ViewModel에 어떠한 껍데기 (Xaml로 만든 View)를 만들어다 붙여도 동작에 변함과 이상이 없습니다.

특정 ViewModel에 어떠한 View(Xaml)로 UI를 표현해 갖다 붙여도 동작에 변함과 이상이 없다는건
MVVM이 추구하는 Presentation과 Presentation Logic이 완벽히 분리되었음을 의미하기도 합니다.

근데 뭐… MVVM은 참 개인의 차이가 큰거 같습니다.

좋아요 3

저도 level120님 말씀처럼 뷰에 관한 로직은 이벤트로 처리하는게 맞다고 생각하긴 하는데…커맨드를 이벤트로 바인딩하는걸 프레임워크를 통해 사용할 수 있다보니 제로 코드비하인드를 좀 추구하는 편이기도 합니다. 말씀하신 단위테스트 이유에서도 mvvm은 꼭 필요하다고 생각하고 그것은 곧 생산성(유지보수)에도 포함된다고 생각합니다.

좋아요 1

저도 뷰에 관한 로직은 이벤트로 해도 무방하다고 생각하는 편입니다. 근데 뭔가 로직의 관리포인트가 뷰모델에도 있고 코드비하인드에도 있다고하면 유지보수가 힘들거같다고 동시에 생각도 합니다… 혹시 이 부분은 어떻게 생각하시는지요…?

좋아요 1

혹시 조상찾기로 커맨드를 바인딩했을때 성능이슈가 있다고 아티클에서 보긴했는데 조상 찾기 방식을 자주 사용하신다면 그런 부분이 체감되는 수준인가요? 그게 실재하는 것인지 궁금하네요…ㅎㅎ

좋아요 1

@Vincent 제가 체감하기에는 크게 문제는 없어보입니다! :smile:
근데 저는 실제로 RelativeSource를 ControlTemplate 단위에서 간단간단하게 사용하고 있기 때문에 체감될만한 케이스가 없었을 수도 있을 것 같아요.

그리고 ListViewitem과 같은 반복 부분 템플릿에서는 컨트롤에서 ICommand를 DependencyProperty로 등록해서 사용하고 있습니다.

좋아요 1

답변 감사드립니다. 내용은 20자 이상이어야 합니다.

좋아요 1

@Vincent 맞습니다. 근데 정말 정답이 없는 것 같아요.

저는 WPF가 나온 해 부터 오로지 이것만 했는데도 결국 매 해, 매 순간 정답이 바뀌는 것만 같습니다. :joy:

그럼에도 제 개인적으로는 기본적으로 MVVM을 고수해야 한다고 생각합니다.
ViewModel에서 부족한 부분(이벤트를 포함한)은 각각의 핵심 컨트롤들이 DependencyPropery 추가를 통해 ViewModel 규칙을 깨지 않도록 단단하게 보완하고, ContentControl 기반의 Template이 중심이 되는 구조를 이상적으로 생각하고 있습니다.

그리고 계속 거듭하며 보완하고 토론하고 고쳐나갈 수 있는 것에 큰 기대가 들어요!!

좋아요 3