본문 바로가기
C#/WPF

[WPF] MVVM 패턴에서의 전체 선택

by HJ0216 2024. 5. 19.

이번 프로젝트에서는 수동 전체 선택을 구현하게 되었습니다.

 

🤹장작 6시간의 대장정..

 

 


 

무엇이 문제이고, 어떻게 해결했는지 정리해봅니다.

 

사실 전체 선택을 누르면 전체가 선택되고, 전체 선택을 해제하면 전체가 해제되도록 만드는 것은 쉽습니다.

View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Window>
    <Grid>
        <StackPanel Orientation="Vertical">
            <CheckBox Content="All"
                      IsChecked="{Binding IsAllChecked, Mode=TwoWay}"
                      />
            <Separator/>
            <CheckBox Content="1"
                      IsChecked="{Binding IsOneChecked, Mode=TwoWay}"                      
                      />
            <CheckBox Content="2"
                      IsChecked="{Binding IsTwoChecked, Mode=TwoWay}"
                      />
            <CheckBox Content="3"
                      IsChecked="{Binding IsThreeChecked, Mode=TwoWay}"
                      />
            <CheckBox Content="4"
                      IsChecked="{Binding IsFourChecked, Mode=TwoWay}"
                      />
        </StackPanel>
    </Grid>
</Window>

 

ViewModel

1
2
3
4
5
6
7
private void ChangeCheckboxFromAll()
{
    IsOneChecked = IsAllChecked;
    IsTwoChecked = IsAllChecked;
    IsThreeChecked = IsAllChecked;
    IsFourChecked = IsAllChecked;
}

 

AllCheckbox에 변경이 일어날 때마다 ChangeCheckboxFromAll 메서드를 실행해서 각 Checkbox의 값을 업데이트 시켜주면 되기 때문이죠.

 

🫠 하지만, 문제는 개별 선택 변경으로 인한 전체 선택 변경에서 일어납니다.

즉, 1, 2, 3만 선택한 상황에서 4를 추가로 선택하면 전체 선택이 표시되어야 하고

전체 선택 된 상태에서 4를 체크 해제하면 전체 선택이 해제 되어야 합니다.

 

그래서 다음과 같이 코드를 추가하면,

ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void ChangeCheckboxFromAll()
{
    IsOneChecked = IsAllChecked;
    IsTwoChecked = IsAllChecked;
    IsThreeChecked = IsAllChecked;
    IsFourChecked = IsAllChecked;
}
 
private void ChangeCheckboxFromEach()
{
    bool alreadyCheckedAll = IsOneChecked && IsTwoChecked && IsThreeChecked && IsFourChecked;
 
    if (IsAllChecked == alreadyCheckedAll)
    {
        return;
    }
    else
    {
        IsAllChecked = alreadyCheckedAll;
    }
 
}

 

개별 체크 박스마다 ChangeCheckboxFromEach를 연결하면, 개별 체크 박스의 상태 변화를 All Checkbox와 연결지을 수 있습니다.

 

☠️ 그러나, 그 때부터 문제는 시작됩니다.

왜냐면 전체 선택 체크박스 체크 → ChangeCheckFromAll()→ 1번 체크박스 체크 → ChangeCheckFromEach(): else 구문 실행 → 전체 선택 체크박스 체크 해제 → ChangeCheckFromAll()→ 1번 체크박스 체크 해제 → ChangeCheckFromEach(): if 구문 실행

 

결국 전체 선택 체크박스는 체크가 안되고 끝이 납니다...

안녕 전체 선택..👋

 

다른 방법을 찾아보았지만, 최대한 간단하게 끝내고 싶은 마음에 플래그를 사용하는 것으로 합의를 보았습니다.

인터페이스를 활용한 방법도 있었는데, 궁금하신 분들을 아래 주소로 들어가보시면 됩니다.

 

전체 선택 체크박스 로직 - 2

1. 문제의 시작. 지인이 체크박스 동작이 이상하다고 해서 잠깐 봐줬는데 전체 선택/해제 체크 박스를 만들...

blog.naver.com

 

ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public event PropertyChangedEventHandler PropertyChanged;
 
private bool _ignoreCheckAll;
 
private bool _isAllChecked;
 
public bool IsAllChecked
{
    get => _isAllChecked;
    set
    {
        if (_isAllChecked != value)
        {
            _isAllChecked = value;
            OnPropertyChanged(nameof(IsAllChecked));
            if (!_ignoreCheckAll)
            {
                _ignoreCheckAll = true;
                IsOneChecked = value;
                IsTwoChecked = value;
                IsThreeChecked = value;
                IsFourChecked = value;
                _ignoreCheckAll = false;
            }
        }
    }
}
 
private bool _isOneChecked;
 
public bool IsOneChecked
{
    get => _isOneChecked;
    set
    {
        if (_isOneChecked != value)
        {
            _isOneChecked = value;
            OnPropertyChanged(nameof(IsOneChecked));
            UpdateIsAllChecked();
        }
    }
}
 
private bool _isTwoChecked;
 
public bool IsTwoChecked
{
    get => _isTwoChecked;
    set
    {
        if (_isTwoChecked != value)
        {
            _isTwoChecked = value;
            OnPropertyChanged(nameof(IsTwoChecked));
            UpdateIsAllChecked();
        }
    }
}
 
private bool _isThreeChecked;
 
public bool IsThreeChecked
{
    get => _isThreeChecked;
    set
    {
        if (_isThreeChecked != value)
        {
            _isThreeChecked = value;
            OnPropertyChanged(nameof(IsThreeChecked));
            UpdateIsAllChecked();
        }
    }
}
 
private bool _isFourChecked;
 
public bool IsFourChecked
{
    get => _isFourChecked;
    set
    {
        if (_isFourChecked != value)
        {
            _isFourChecked = value;
            OnPropertyChanged(nameof(IsFourChecked));
            UpdateIsAllChecked();
        }
    }
}
 
private void UpdateIsAllChecked()
{
    if (!_ignoreCheckAll)
    {
        _ignoreCheckAll = true;
        IsAllChecked = _isOneChecked && _isTwoChecked && _isThreeChecked && _isFourChecked;
        _ignoreCheckAll = false;
    }
}

 

중요한 점은 전체 선택 체크 박스를 클릭해서 일어난 상태변화는 다시 전체 선택 체크 박스에 영향을 미치지 않도록 하는 것입니다.

 

따라서 _ignoreCheckAll을 플래그로 선언해서 전체 선택 체크 박스를 클릭해서 일어난 개별 체크 박스의 상태변화가 다시 전체 선택 체크박스로 오지 않도록 막았습니다.

 

물론 이 방법은 체크박스와 관련된 모든 checkbox의 상태 변화 발생 시, 메서드를 호출하므로 성능 저하 문제가 발생할 수도 있겠지만 작은 데이터 규모에서는, 그리고 달리 방도를 못찾은 상황에서는 대안으로 사용할 수 있습니다🤓.

 

 

 

⭐ 오늘의 교훈: 메서드 간 상호 호출 현상을 방지하는 방법 중 하나는 Flag를 이용하는 것이다.

 

'C# > WPF' 카테고리의 다른 글

[WPF] ControlTemplate의 영향력  (0) 2024.06.30
[WPF] 생성자 단위 초기화와 필드 단위 초기화  (0) 2024.06.04
[WPF] ObservableCollection  (0) 2024.05.15
[WPF] View, ViewModel Singleton  (0) 2024.05.12
[WPF] 생성자 Overloading (2)  (0) 2024.05.11