ViewModel에서 프로퍼티의 추가는 어떻게들 하시나요?

안녕하세요.

ViewModel에서 프로퍼티에서 INotifyPropertyChanged 구현할때 너무 반복적인 작업으로 코드량도
많아지고 가독성도 좋지 않아서 여쭈어 봅니다.

INotifyPropertyChanged를 구현하는 class나 viewModel에서 프로퍼티 생성하실때 쉽게 할 수 있는
팁이 있을까요? 클래스 만들때 프로퍼티 많으면 한숨부터 나옵니다 .^.^

감사합니다.

2개의 좋아요
  1. PropertyChanged.Fody

다음처럼 사용할 수 있게 해주는 소스 생성 라이브러리 입니다.

| 이렇게 코딩하면,

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
    public string FullName => $"{GivenNames} {FamilyName}";
}

| → 이렇게 변환해줍니다.

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    string givenNames;
    public string GivenNames
    {
        get => givenNames;
        set
        {
            if (value != givenNames)
            {
                givenNames = value;
                OnPropertyChanged(InternalEventArgsCache.GivenNames);
                OnPropertyChanged(InternalEventArgsCache.FullName);
            }
        }
    }

    string familyName;
    public string FamilyName
    {
        get => familyName;
        set 
        {
            if (value != familyName)
            {
                familyName = value;
                OnPropertyChanged(InternalEventArgsCache.FamilyName);
                OnPropertyChanged(InternalEventArgsCache.FullName);
            }
        }
    }

    public string FullName => $"{GivenNames} {FamilyName}";

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        PropertyChanged?.Invoke(this, eventArgs);
    }
}

internal static class InternalEventArgsCache
{
    internal static PropertyChangedEventArgs FamilyName = new PropertyChangedEventArgs("FamilyName");
    internal static PropertyChangedEventArgs FullName = new PropertyChangedEventArgs("FullName");
    internal static PropertyChangedEventArgs GivenNames = new PropertyChangedEventArgs("GivenNames");
}
  1. Community Toolkit MVVM 소스 생성기 특성 이용

| 이렇게 코딩하면 Name이라는 반응하는 속성을 만들어줍니다.

[INotifyPropertyChanged]
public partial class ObservableRecord
{
    [ObservableProperty]
    private bool _name;
}

저는 PropertyChanged.Fody 한참 쓰다가 Community Toolkit MVVM에서 제공하는 소스 생성기 특성으로 갈아 탔습니다.

3개의 좋아요

저는 순수 WPF를 사용하지 않고 DevExpress WPF MVVM 라이브러리를 사용하기 때문에 링크드립니다.

위 문서대로 하면되는데 소스 예시는

public class ImageAndName : BindableBase
{
    public Image Image
    {
        get => GetValue<Image>();
        set => SetValue(value);
    }

    public Uri Uri
    {
        get => GetValue<Uri>();
        set => SetValue(value);
    }

    public Guid AwesomeImageId
    {
        get => GetValue<Guid>();
        set => SetValue(value);
    }

    public string Name
    {
        get => GetValue<string>();
        set => SetValue(value);
    }

    public int Index
    {
        get => GetValue<int>();
        set => SetValue(value);
    }
}

그냥 이렇게만하면 INotifyPropertyChanged 가 구현된 프로퍼티들을 얻을 수 있습니다.

3개의 좋아요

정말 감사드립니다.
Community Toolkit MVVM 이쪽이 많이 편해보이긴 합니다.
잘 활용하겠습니다. ^.^

2개의 좋아요

감사합니다.~

1개의 좋아요

저같은 경우는 요번에 DevExpress WPF MVVM 라이브러리 기능중 View Models Generated at Compile Time 기능 부분을 이용해서 했는데 만족스러웠습니다.

DevExpress.Mvvm 과 DevExpress.Mvvm.CodeGenerators 라이브러리는 mit라이센스로 알고있습니다.

https://docs.devexpress.com/WPF/402989/mvvm-framework/viewmodels/compile-time-generated-viewmodels

using DevExpress.Mvvm.CodeGenerators;

[GenerateViewModel]
partial class ViewModel {
    [GenerateProperty]
    string username;
    [GenerateProperty]
    string status;

    [GenerateCommand]
    void Login() => Status = "User: " + Username;
    bool CanLogin() => !string.IsNullOrEmpty(Username);
}

Source Generator로 생성되는 코드는 다음과 같습니다.

partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    public string? Status {
        get => status;
        set {
            if(EqualityComparer<string?>.Default.Equals(status, value)) return;
            status = value;
            RaisePropertyChanged(StatusChangedEventArgs);
        }
    }

    DelegateCommand? loginCommand;
    public DelegateCommand LoginCommand {
        get => loginCommand ??= new DelegateCommand(Login, CanLogin, true);
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
    static PropertyChangedEventArgs StatusChangedEventArgs = new PropertyChangedEventArgs(nameof(Status));
}
3개의 좋아요

커맨드도 처리가 되는군요!
감사합니다

1개의 좋아요
[INotifyPropertyChanged]
public partial class SubRegionInfo
{
    [ObservableProperty]
    private string _regionName;
    [ObservableProperty]
    private string _subRegionName;
    [ObservableProperty]
    private int _totalCount = 0;
    [ObservableProperty]
    private int _normalCount = 0;
    [ObservableProperty]
    private int _errorCount = 0;
}

이렇게 하고 RegionName으로 참조했는데 없는 정의라고 오류가 나오네요.

‘SubRegionInfo’ does not contain a definition for ‘RegionName’

사용환경은 아래와 같습니다.
VS 2019/.net framework 4.6.1
설치는 Microsoft.Toolkit.Mvvm이것 7.1.2 버전 사용했습니다.

제가 뭘 놓친거 같은데… 이상하네요 ㅠ

1개의 좋아요

Microsoft.Toolkit.Mvvm이 아닌 CommunityToolkit.Mvvm입니다.

아마 현재 시험판이라 검색이 안된것 같은데요, 시험판 포함을 체크해야 검색이 됩니다.

image

저는 시험판이라도 사용해보고 문제가 없으면 쓰는편이라… 시험판인지 인식을 못하고 말씀을 드렸네요

2개의 좋아요

안녕하세요. 프로그래밍 관련 질문은 자유게시판이 아닌 C# 질문 게시판에 게시되는 것이 좋다고 생각하여 글을 옮깁니다. 추후에 글을 올리실 때는 참고 부탁드립니다.

2개의 좋아요

해당버전으로해도 동일하게 오류가 나네요…
코드도 몇줄안되는데 ㅠㅠ

1개의 좋아요

저는 갠적으루 code gen 의 결과가 소스코드로 확인할 수 없는 방식은 크게 선호하지 않는 편이에요… -ㅅ-;;
왜냐면 이거 binding 디버깅할 때 자주 빡치거등요…

처음 Fody 를 사용할 때 신세계였는데, binding 디버깅할 때 빡쳐서
그 이후론 binding 과 연결되는 property에는 code gen 류를 잘 사용하지 않는 편입니다…
(게다가 Fody 1.0.53 버전 까지는 빌드할 때 바이너리 블럭되는 버그도 있어서… 그 이후로 Fody 는 안 쓰고 있지요…;;;; )

예를 들어

public class ViewModel
{
   [ObservableProperty]
   public string Status { get; set; }
}

이런 코드로 Binding 을 연결 했을 때 제대로 동작을 안 한다면
이 ViewModel 이 propertychanged 이벤트와 연결되었는지 한 눈에 확인이 안 됩니다.
(아, 뭐 물론 BP 를 걸고 this 를 따라가서 PropertyChanged 이벤트의 구독 여부를 따라가볼 수도 있지만…)

또 VisualStudio 에서 인식이 안 되는 형태로 IL 이 생성될 경우 BP 가 제대로 안 걸리는 경우도 있구요.
(근데… Community Toolkit MVVM 은 그럭저럭 잘 되는 듯…)

code gen 류는 실제로 사용하다 예기치 못 한 곳에서 문제가 생기는 경우가 종종 있어서
저는 @Vincent 님이 말씀하신 메서드 방식 위주로 씁니다.
(굳이 DevExpress 아니더라도 저 메서드 구현은 검색하면 많이 나와요. 그냥 뜯어다 쓰면 됩니다.)

근데…

최근에 좀 써보니 code gen 도 나쁘지 않은 듯… 이런 느낌을 좀 받아요.

간단하고 편리한 거 생각하면 저는 그냥 Community Toolkit MVVM 이 괜춘한 거 같긴합니닷 >ㅁ</

3개의 좋아요

만약 프로퍼티에 기본값이 필요할때는 어떤식으로 하시나요
생성자에서 초기화를 해주시나요?

1개의 좋아요

아니요. 그냥 C#에서 프로퍼티 초기화 할 때 사용하는 방식으로 합니다.

public virtual string ABC { get; set; } = "하하호호";

DevExpress WPF MVVM의 경우에 MVVM 형태가 2가지 입니다.
POCOViewModel 형태를 사용하던지, 사용안하던지 인데요.

제가 사용하는 방식은 POCOViewModel 방식인데, 프로퍼티와 런타임을 만들어놓고 그것을 virtual로 선언해두면 런타임에서 재정의해서 DevExpress의 문서에 나와있는 형태로 다시 만들어 줍니다. 이 경우 생성자에서 초기화 하는 것은 적용이 안되었던 것으로 기억납니다.

POCOViewModel이 아니라면 일반적인 MVVM 형식으로 하시면 될텐데 생성자에서 하면 될 것 같습니다.

1개의 좋아요

기본 생성자 타입이 아니라 위에처럼 초기화가 안되니 가독성이 떨어지네요.
생성자를 만들어서 해야야 할거 같습니다.
감사합니다.

2개의 좋아요