WPF 고민 해소를 위한 SLOG

안녕하세요. :slight_smile:

WPF 고민 해소를 위한 SLOG는…

WPF 작업을 할 때 잘 풀리지 않거나 고민 되는 부분들을 알아보고 해결해나가기 위한 공간입니다.

그리고 함께 공유하고 싶은 내용들을 하나 씩 작성할 계획입니다.

감사합니다.

8개의 좋아요

DataTemplateSelector에 대한 고민입니다.

TabControl 또는 TabItem에서 ContentDataTemplateSelector를 사용했는데,
매번 선택할 때 마다 SelectTemplate 메서드가 호출되는데요.

제가 DataTemplate에 각각 UserControl을 넣어버렸습니다.
(무겁게도)

<DataTemplate x:Key="GettingStarted">
    <UserControl1/>
</DataTemplate>
<DataTemplate x:Key="CommitHistory">
    <UserControl2/>
</DataTemplate>

이렇다 보니 탭 변경이 발생할 때 마다 SelectTemplate이 호출되면서 UserControl 무게에 따라 전환되는 움직임이 느려졌습니다.

그래서 무거운 화면을 DataTemplate에 넣는 것이 잘못 되었다고 생각했는데요.

애초에 TabItem의 Content에는 모델을 넣는 것이 아닌 UI 객체를 넣는 것이 옳은 방법일까요?


제 생각을 정리하면,

이렇습니다.

<!-- 이건 기존 보통의 방식이고 -->
<TabItem Header="Getting Started.">
    <TabItem.Content>
        <UserControl1>
            <UserControl1.DataContext>
                 <data:UserControl1ViewModel/>
            </UserControl1.DataContext>
        </UserControl1>
    </TabItem.Content>
</TabItem>
<!-- 이건 제가 구현했다 폐기한 방식입니다. -->
<DataTemplate x:Key="GettingStarted">
    <UserControl1/>
</DataTemplate>
<DataTemplate x:Key="CommitHistory">
    <UserControl2/>
</DataTemplate>

<Style TargetType="TabItem">
    <Setter Property="ContentDataTemplateSelector" Value="ContentTempalteRoute"/>
</Style>

그래서 DataTemplate은 Content를 아예 바꾸기 위한 처리가 아니구나? 라고 이해했어요.

TabControl을 ListBox처럼 가볍게 생각하고 만들었다면 어땠을까 생각도 해봤는데요.

그래서 Visual Studio 프로젝트 속성 화면에 있는 텝과 같은 가벼운 TabControl을 만들 때 DataTemplateSelector를 사용해보면 어떨까? 하는 생각을 해봅니다.

image

7개의 좋아요

TemplateSelector 호출이 무겁다는 게 잘 이해가 안 가는데요.
TemplateSelector 호출 자체가 무겁다면 ItemsSource 를 이용해 호출하면 됩니다만…

제가 보기엔 View 로딩 자체가 무겁다는 것으로 인지가 됩니다.
결국 매 탭 스위칭 마다 view 가 새로 생성되어 로딩되는 게 문제인 거 같습니다.
이건 TemplateSelector를 이용한 해결보다는 View cache 를 이용한 해결이 더 적절한 방향이 아닌가 싶어요.

물론 TabControl 역시 ItemsSource에서 파생되었고
기본적으로 ItemsSource 는 ItemsSource 로 로딩되는 View 들을 cache 하지만

할당되는 대상 ItemsSource 들의 타입이 같을 때만 cache가 됩니다.

만약 각 ViewModel 마다 다른 DataTemplate을 렌더링할 용도로
ItemsSource 에 각기 다른 타입의 객체를 할당한 후 DataTemplate으로 연결하면
해당 view 들은 cache되지 않고매 view 가 표시될 때마다 새로 생성이 되어 로딩됩니다.
(아마도 그래서 무겁다고 느껴지는 거라고 예상이 됩니다…-ㅂ-)

따라서 제가 생각하기에는 View cache 쪽을 좀 더 손보는 게 어떨까하네요.

그럼 View cache는 어떻게 하냐… 하고 물으신다면…

https://blog.naver.com/vactorman/222449542805

요 정도면 되지 않을까요?

2개의 좋아요

저도 @Greg.Lee 님 의견과 같은 생각이고 폐기한 사례입니다!

그리고 자세하게 좋은 설명 해주신 내용도 조만간 정리해서 글 이어나가 보도록 할게요. :slight_smile:

그리고 앞서 언급 했던 프로젝트 속성 창도 만들어 보고 있는데, 공유 해보면 재밌을 것 같습니다!

2개의 좋아요

아, 참고로 ItemsSource 를 이용하는 건 이런 식으로 할 수 있을 거 같습니다.

public abstract class State {}

public class Started : State {}

public class Stopped : State {}

public class ViewModel
{
   public ObservableCollection<State> Source {get;} = new ObservableCollection<State>();
}

<DataTemplate DataType="{x:Type Started}">
   <utils:ViewCache ContentType="{x:Type views:ItemView}" />
</DataTemplate>
<DataTemplate DataType="{x:Type Stopped}">
   <utils:ViewCache ContentType="{x:Type views:OtherView}" />
</DataTemplate>
<TabControl ItemsSource="{Binding Source}"/>

요정도면 되지 않을까 하네욤 ~ㅂ~

1개의 좋아요

TBD…

기존 WPF 개발 방식에서 CommunityToolkit.Mvvm 대체 부분에 대한 연구 중…

RelayCommand

기존 방식에서는 ClickCommand 속성과 초기화 선언, 메서드 까지 3개의 필수 구현 요소가 필요합니다.

public ICommand ClickCommand { get; init }

public MainViewModel()
{
    ClickCommand = new RelayCommand<string>(Click);
}

private void Click(object value)
{
}

소스코드의 양은 여전히 많지만 장점이라면 선언 부분을 정확하게 확인하고 참조 부분이 모두 연결되어있다는 점입니다.

이에 반해, CommunityToolkit.Mvvm에서는 RelayCommand Attribute만으로도 나머지 필수 요소들이 또 다른 partial 클래스에 자동으로 생성되기 때문에 잔 실수도 줄어들게 됩니다.

[RelayCommand]
private void Click(object value)
{

}

물론 처음에는 기존 참조 체계의 익숙함 때문에 분석이나 디버깅 등을 할 때 어색함을 계속 느낄 수 있습니다.

1개의 좋아요

TBD … 작성중

ObservableProperty