C#/WPF

[WPF] ListView와 ItemsSource

HJ0216 2024. 3. 9. 23:19

최근에 프로젝트를 진행하면서 ListView Control을 하며 어려웠던 부분을 정리하였습니다.

 

👉 기본 환경

- Language: C#, xaml

- IDE: Visual Basic 2022


 

ListView의 ItemsSource는 ToDoList에 맞추고 ListView의 Item은 ToDoItem의 필드에 바인딩을 하였습니다.

 

그 중 하나의 필드는 Button의 Command와 연결하여 간단하게 Message Box를 표시할 예정이었습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Grid>
    <ListView ItemsSource="{Binding ToDoList}"
                SelectedItem="{Binding SelectedToDoItem}"
                >
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Tag}" Width="70"/>
                    <TextBlock Text="{Binding Content}" Width="60"/>
                    <TextBlock Text="{Binding RegisteredDate, StringFormat=yyyy-MM-dd}" Width="80"/>
                    <Button Content="Is it Complete?"
                            Command="{Binding CheckCompleteCommand}"
                            />
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

 

이 상태로 실행을 하면,

'CheckCompleteCommand'를 찾을 수 없다는 바인딩 오류가 발생합니다.

(심지어 ListView의 Item 개수만큼 바인딩 오류가 나서 놀람😮 * Item 개수가 됩니다.)

 

가능성은 크게 3가지입니다.

1. 오타

2. 다른 ViewModel에 선언

3. ItemsSource

 

이번에는 ItemsSource와 관련한 문제에 대해 작성하고자 합니다.

 

1
2
3
4
5
6
7
8
9
10
private ObservableCollection<ToDoItem> _toDoList = new ObservableCollection<ToDoItem>();
public ObservableCollection<ToDoItem> ToDoList
{
    get { return _toDoList; }
    set 
    {
        _toDoList = value;
        OnPropertyChanged("ToDoList");
    }
}

 

현재 ListView는 ItemsSource가 ToDoList로 선언되어있습니다.

때문에 ListView의 각 Item들은 ToDoList를 구성하고 있는 요소들, 여기서는 ToDoItem이 Source가 됩니다.

따라서 ListViewItem에 속한 Button과 바인딩된 CheckCompleteCommand를 ViewModel에서 찾게 되는 게 아니라 Item의 Source인 ToDoItem에서 찾게됩니다.

 

그러나 제 Command는 그 곳에 없습니다🥸.

ViewModel에 선언해놨습니다🥸.

 

좀 더 큰 세상에 있는 Command는 ListViewItem과는 다른 Source에서 찾을 수 있도록 코드를 변경해야합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Grid>
    <ListView ItemsSource="{Binding ToDoList}"
                SelectedItem="{Binding SelectedToDoItem}"
                >
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Tag}" Width="70"/>
                    <TextBlock Text="{Binding Content}" Width="60"/>
                    <TextBlock Text="{Binding RegisteredDate, StringFormat=yyyy-MM-dd}" Width="80"/>
                    <Button Content="Is it Complete?"
                            Command="{Binding DataContext.CheckCompleteCommand, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
                            />
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

 

DataContext.CheckCompleteCommand

- 내 소중한 Command는 DataContext(= ViewModel)에 위치해 있고,

RelativeSource

- 상대 참조로 데이터를 바인딩할 것이고,

AncestorType={x:Type ListView}

- 그 상태 참조의 경로는 현재 Button의 상위 요소 중 ListView의 DataConext임을 나타냅니다.

 

 

 

⭐ ListView 등 ItemsSource를 따로 정해주는 경우, 그 안의 요소를 컨트롤할 때는 어떤 데이터를 바라보고 있는지도 유의해야 합니다.

 

(🤓 이 글을 작성하면서 ListView와 ListBox가 혼재되어있는 제 코드를 발견했습니다.

월요일에 출근하면 조용히 바꿔놓아야겠습니다..!)