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

 

🤹장작 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

WPF를 사용하면서 처음 알게 된 ObservableCollection 클래스와 프로젝트 진행 시, 헤맸던 부분을 정리해봅니다.

 

1. ObservableCollection Class

항목이 추가 또는 제거되거나 전체 목록을 새로 고칠 때 알림을 제공하는 동적 데이터 컬렉션

 

저는 프로젝트를 진행할 때, MVVM 패턴에 맞춰서 진행을 하다보니 처음에 가장 어려웠던 점은 VIew와 ViewModel의 연동이었습니다. 대표적으로 ViewModel 단에서 데이터가 변경되어도 View 즉, UI에는 반영이 안되는 것이죠.

(🥸 리액트 배울 때는 State에 설정만 해주면 페이지 고침없이도 딴딴하고 바뀌는데,, 라는 생각을 잠깐 했습니다.)

 

보통은 이럴 때, INotifyPropertyChanged를 구현해서 ViewModel 데이터 변경 시, UI에 알림을 보내는데, 여러 개의 데이터를 저장하는 List 타입도 마찬가지입니다.

 

그러나, 이 때 ObservableCollection을 사용한다면 INotifyPropertyChanged 사용하지 않고도 데이터 변경 시 자동으로 UI에 업데이트 됩니다(왜냐면, ObservableCollection 내에서 INotifyPropertyChanged를 구현했기 때문입니다😄).

 

 

2. ObservableCollection 특징

 * 자동 UI 업데이트: 데이터 변경 시, View에 자동 반영

 * 일반 컬렉션과 높은 호환성: List 등 기존 컬렉션을 ObservableCollection로 쉽게 변경 가능

 * 항목이 추가, 삭제, 변경될 때마다 이벤트가 발생하므로 대량의 데이터를 빠른 속도로 변경해야 하는 경우 성능 저하의 원인이 될 수 있음

 * 스레드 안전성: 여러 스레드에서 동시에 접근하고 변경하는 경우, 동기화 처리를 직접해야 함

 

 

3. ObservableCollection 사용 시, 어려웠던 점

프로젝트를 하면서 ObservableCollection 사용 시, 처음에 가장 당황했던 부분은 데이터가 변경되었는데 UI변경이 일어나지 않는 것입니다☠️.

 

정확히는 인스턴스 자체가 변경되면 UI에 반영이 되지 않는다는 것입니다.

이는 ObservableCollection<T>이 항목의 추가, 삭제, 수정에 대한 알림을 제공하지만, 컬렉션 인스턴스 자체의 참조 변경에 대해서는 알림을 제공하지 않기 때문입니다.

 

따라서,

MyCollection = new ObservableCollection<MyObject>();

 

새로운 ObservableCollection을 만들어 할당할 경우, UI는 변경되지 않으므로

private ObservableCollection<MyObject> myCollection;
public ObservableCollection<MyObject> MyCollection
{
    get { return myCollection; }
    set
    {
        if (myCollection != value)
        {
            myCollection = value;
            OnPropertyChanged(nameof(MyCollection));
        }
    }
}

 

이렇게 ObservableCollection 자체를 다시 한 번 INotifyPropertyChanged 처리를 해줘야 합니다.

 

그래서 ObservableCollection을 도중에 초기화해줄 때에도, 변경 감지를 위해 저는 새 인스턴스보단 Clear 메서드를 사용합니다.

public void ClearData()
{
    MyCollection.Clear();
}

 

Clear 메서드 사용의 장점으로는

 * UI 자동 업데이트: 컬렉션을 비우는 것이 UI에 즉시 반영됩니다.

 * 메모리 관리: 새 인스턴스를 생성하고 기존 인스턴스를 버리면, 가비지 컬렉터가 기존 인스턴스를 수집할 때까지 해당 메모리가 여전히 사용 중인 상태로 남게 됩니다. 반면, Clear 메서드를 사용하면 기존 컬렉션 인스턴스를 재사용하므로 불필요한 메모리 할당을 줄일 수 있습니다.

 * 코드 가독성 및 유지보수: Clear 메서드를 사용하는 것은 컬렉션의 내용을 초기화하려는 의도를 명확하게 표현합니다.

 

 

 

📚 참고 자료

 

ObservableCollection<T> 클래스 (System.Collections.ObjectModel)

항목이 추가 또는 제거되거나 전체 목록을 새로 고칠 때 알림을 제공하는 동적 데이터 컬렉션을 나타냅니다.

learn.microsoft.com

 

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

[WPF] 생성자 단위 초기화와 필드 단위 초기화  (0) 2024.06.04
[WPF] MVVM 패턴에서의 전체 선택  (0) 2024.05.19
[WPF] View, ViewModel Singleton  (0) 2024.05.12
[WPF] 생성자 Overloading (2)  (0) 2024.05.11
[WPF] 생성자 Overloading  (1) 2024.04.27

지난 프로젝트에서 페이지 전환이 많다보니 생성자를 통해 View나 ViewModel을 넘기는 일이 많았습니다.

 

☠️ 하지만 새로운 인스턴스를 생성하는 방식으로 넘기게 될 때 동작이 제대로 작동하지 않는 문제가 있었습니다.

 

예를 들면,

1
2
3
4
5
6
public void OpenWindowSub1()
{
    WindowSub1 window = new WindowSub1();
    window.DataContext = new ViewModelSub1(new ViewModelMain());
    window.Show();
}

 

Window를 새롭게 호출하는 경우, DataContext로 연결된 ViewModelSub1에 현재 ViewModelMain의 새로운 인스턴스를 넘겨줄 경우, 인스턴스에 저장된 데이터를 사용하지 못하게 됩니다.

 

따라서, 기존 인스턴스의 값을 사용하고 싶다면 새로운 인스턴스 생성이 아닌 this를 사용해 현재 인스턴스를 넘겨야 합니다.

1
2
3
4
5
6
public void OpenWindowSub1()
{
    WindowSub1 window = new WindowSub1();
    window.DataContext = new ViewModelSub1(this);
    window.Show();
}

 

이럴 때, 만일 singleton 패턴을 사용한다면 처음처럼 인스턴스를 만들고자해도 기존의 인스턴스가 반환되기 때문에 문제를 다른 방식으로도 해결할 수 있습니다.

 

 

Singleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static readonly object LockObject = new object();
 
private static WindowSub1 _instance = null;
public static WindowSub1 Instance
{
    get
    {
        if (_instance == null)
        {
            lock (LockObject)
            {
                if (_instance == null)
                {
                    _instance = new WindowSub1();
                }
            }
        }
 
        return _instance;
    }
}

 

LockObject는 멀티 쓰레드 환경에서 동시에 여러 스레드가 인스턴스를 생성하는 것을 방지하는 구문입니다.

 

1. 잠금 요청

  * 스레드가 lock(LockObject) 구문에 도달하면, LockObject( 잠금을 위해 사용되는 객체 )에 대한 잠금을 요청  
2. 잠금 확인 및 대기

  * 만약 다른 스레드가 이미 LockObject에 대한 잠금을 획득하고 있다면, 요청한 스레드는 대기 상태로 전환되고, 잠금이 해제될 때까지 대기

  * 만약 LockObject에 대한 잠금이 없다면, 요청한 스레드는 잠금을 획득하고 코드 블록을 실행
3. 코드 블록 실행

  * 잠금을 획득한 스레드는 lock 구문 내의 코드 블록을 실행

  * 이 코드 블록은 동시에 하나의 스레드만 실행할 수 있음
4. 잠금 해제

  * 코드 블록의 실행이 완료되면, 잠금은 자동으로 해제되고, 다른 스레드가 대기 큐에서 잠금을 요청할 수 있게 됨

 

 

⭐ 싱글톤 객체를 만들 때, 매개변수가 없는 기본 생성자를 사용

만일, 추가적으로 인자가 필요할 경우에는 매개변수가 있는 기본 생성자가 아닌 초기화 함수 사용

1
2
3
4
5
// 싱글톤 인스턴스 획득
ViewModelSub1 vmSub1 = ViewModelSub1.Instance;
 
// 초기화 메서드를 통해 데이터 설정
vmSub1.Initialize("Arg");

 

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

[WPF] MVVM 패턴에서의 전체 선택  (0) 2024.05.19
[WPF] ObservableCollection  (0) 2024.05.15
[WPF] 생성자 Overloading (2)  (0) 2024.05.11
[WPF] 생성자 Overloading  (1) 2024.04.27
[WPF] 한글 Encoding ANSI / UTF8  (0) 2024.04.22

프로젝트 준비를 하면서 미리 코드를 작성해보다 마침 정리했던 개념인 생성자 Overloading를 사용해보았습니다.

 

상속을 받으면 NullPointerException이 나고, 하나의 생성자를 사용하면 문제없이 동작하여 생성자를 상속받을 때 부모 생성자의 코드 삽입 위치를 한 번 정리해보고자 합니다.

 


🤓 처음에 글을 작성할 때, Overloading을 Overriding으로 잘못 적었는데,

다시 한 번 정리하면 다음과 같습니다.

Overloading: 메서드 이름은 같지만 매개변수 개수나 타입등을 달리한 여러 개의 메서드를 정의하는 것

Overriding: 부모 클래스에서 상속받은 메서드의 내용을 '재정의' 하는 것

 

⭐ 현재 생성자의 경우, 이름은 같지만 매개변수 개수가 다르게 정의하고 있으므로 Overloading


 

1
2
3
4
5
6
7
8
9
public ViewModelMain()
{
    string name = TempPracticeModel.Name;
}
 
public ViewModelMain(PracticeModel model) : this()
{
    TempPracticeModel = model;
}

 

만일 이러한 생성자 Overloading이 있을 경우,

코드의 실행 순서는

  1. 매개변수가 없는 생성자

  2. 매개변수가 있는 생성자

 

🚨 따라서, 매개변수가 있는 생성자에서 객체를 초기화해주는데 그보다 앞서 매개변수가 없는 생성자에서 해당 객체에 접근하고 있으므로 NullPointerException 발생

 

⭐ 생성자의 호출 순서 유의

 

 

 

📚 참고 자료

 

[Java]class: 오버라이딩(overriding)과 오버로딩(overloading)

TIL이나 알고리즘 문제 풀이 정도 업로드 하였지만 공부하면서 기록하는게 리마인드도 되고 좋을 것 같다는 생각이 들어 오늘부터 시작한다.전문적인 기술 블로그가 아닌 개인적으로 공부하며

velog.io

 

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

[WPF] ObservableCollection  (0) 2024.05.15
[WPF] View, ViewModel Singleton  (0) 2024.05.12
[WPF] 생성자 Overloading  (1) 2024.04.27
[WPF] 한글 Encoding ANSI / UTF8  (0) 2024.04.22
[WPF] Static Resource와 Dynamic Resource  (0) 2024.04.20

WPF 프로젝트를 연습해보다가 프로젝트를 개선할 수 있는 방법을 찾아서 정리하고자 합니다.

 

Window안에 UserControl을 선언해서 View-ViewModel간의 데이터를 전달하는 경우가 많았습니다.

 

특히, 여러 곳에서 호출되는 View-ViewModel은 다양한 초기 데이터를 받게됩나다.

 

기존에는 생성자 Overriding을 했었고, 일정 부분 코드가 중복되는 건 어쩔 수 없다고 생각했습니다.

 

그 때, 찾아낸 BASE에 대해 정리해봅니다.

 

 

기존 ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public ViewModelMain(string data1)
{
    Data1 = data1;
}
public ViewModelMain(string data1, string data2)
{
    Data1 = data1;
    Data2 = data2;
}
public ViewModelMain(string data1, string data2, string data3)
{
    Data1 = data1;
    Data2 = data2;
    Data3 = data3;
}

 

상속 ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ViewModelMain(string data1)
{
    Data1 = data1;
}
 
public ViewModelMain(string data1, string data2) : this(data1)
{
    Data2 = data2;
}
 
public ViewModelMain(string data1, string data2, string data3) : this(data1, data2)
{
    Data3 = data3;
}

⭐ 초기화 로직의 중복을 줄일 수 있음

중복을 제거하여 수정 시, 유지보수성을 높임

 

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

[WPF] View, ViewModel Singleton  (0) 2024.05.12
[WPF] 생성자 Overloading (2)  (0) 2024.05.11
[WPF] 한글 Encoding ANSI / UTF8  (0) 2024.04.22
[WPF] Static Resource와 Dynamic Resource  (0) 2024.04.20
[WPF] MaterialDesignTheme 적용  (0) 2024.04.13