LazyRegion 컨트롤을 만들어봤어요.

LazyRegion.WPF은 WPF (Windows Presentation Foundation) 라이브러리로, ContentControl의 내용이 바뀔 때 즉각적인 전환 대신 부드러운 애니메이션 효과를 제공합니다. 기존 ContentControl은 콘텐츠가 변경되면 화면이 즉시 바뀌지만, LazyRegion.WPF은 페이드, 슬라이드 등 다양한 애니메이션을 통해 자연스럽고 시각적으로 매력적인 전환을 구현합니다. 이 라이브러리는 코드 비하인드, MVVM, 그리고 RegionManager를 통한 동적 콘텐츠 관리 등 다양한 개발 환경에 최적화되어 있습니다.


주요 특징

  • :film_frames: 애니메이션 전환 지원: 설정한 애니메이션 효과와 속도에 맞춰 콘텐츠를 자연스럽게 변경합니다. 다양한 전환 효과(예: Fade, SlideLeft, ZoomIn)를 지원합니다.

  • :puzzle_piece: MVVM 친화적: ViewModel의 변경을 자동으로 감지하여 뷰를 전환합니다. 데이터 템플릿을 사용하여 ViewModel에 따라 다른 화면을 자동으로 표시할 수 있습니다.

  • :laptop: 코드 비하인드 친화적: 코드 비하인드에서 Content 속성 값을 직접 변경하더라도 동일한 애니메이션 효과가 적용됩니다.

  • :package: RegionManager 지원: RegionManager를 통해 미리 지정된 영역(Region)에 동적으로 화면을 주입하거나 변경할 수 있어, 모듈화된 애플리케이션 개발에 유용합니다.


사용 예시

MVVM 친화적

LazyRegion.WPFCurrentViewModel 속성에 바인딩되어 ViewModel이 변경될 때마다 데이터 템플릿에 따라 다른 화면을 표시하고 애니메이션을 적용합니다.

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:AViewModel}">...</DataTemplate>
    <DataTemplate DataType="{x:Type vm:BViewModel}">...</DataTemplate>
</Window.Resources>

<Grid>
    <wpf:LazyRegion Content="{Binding CurrentViewModel}" TransitionAnimation="{Binding SelectAnimationType}" />
    <Button Command="{Binding GoCommand}" Content="변환" />
</Grid>

RegionManager 지원

App.xaml에서 LazyRegion.WPF을 활성화하고 뷰를 등록한 후, RegionManager를 통해 특정 영역(Region)에 원하는 뷰를 동적으로 로드할 수 있습니다.

// App.xaml.cs에서 DI 등록
serviceCollection.UseLazyRegion();
serviceCollection.AddLazyView<AControl>("a");
serviceCollection.AddLazyView<BControls>("b");

// RegionManager를 통해 "Root" 영역에 "a" 키를 가진 뷰를 로드
this._lazyRegionManager.NavigateAsync("Root", "a");

이처럼 LazyRegion은 WPF 애플리케이션의 사용자 경험을 향상시키는 데 효과적인 도구입니다.


앞으로

시간 나는 대로 데모 영상과 샘플 프로젝트도 공유하겠습니다.
궁금한 점이나 피드백은 댓글로 남겨주세요! :blush:

레포지토리

15개의 좋아요

Animation 전환 데모 영상

향후 계획

  • 샘플 프로젝트 공유
  • 화면1, 2에 대한 애니메이션을 커스텀할 수 있게 StoryBoard로 제공 기능 구현 예정
10개의 좋아요

샘플 프로젝트 공유

안녕하세요.

각 프로젝트 샘플 방식에 대해 간단히 설명해드리겠습니다!

  • CodeBehind 방식

    • 설명: 뷰(View)의 코드 비하인드 파일에서 UI 로직을 직접 처리하는 전통적인 방식입니다.

    • 주요 활용: WPF 기본 문법 학습, 소규모 프로젝트, LazyRegion과 같은 특정 라이브러리 연동 테스트 등.

  • DataTemplate 기반 방식

    • 설명: DataTemplate을 활용해 데이터 모델에 따라 UI를 동적으로 생성하는 방식입니다.

    • 주요 활용: 유연하고 재사용성이 높은 UI 컴포넌트 구성이 필요한 경우.

  • LazyRegionManager (View Injection 방식)

    • 설명: LazyRegionManager를 사용해 뷰를 특정 영역(Region)에 동적으로 삽입하는 방식입니다. 뷰 로직이 뷰에 종속되어 있어, 뷰 중심의 개발에 적합합니다.

    • 주요 활용: ViewModel을 사용하지 않고 View 간의 결합도를 낮춰야 하는 경우.

  • LazyRegionManager (ViewModel Injection 방식)

    • 설명: LazyRegionManager를 통해 View의 고유키를 호출하여 View를 띄어주는방식입니다.
    • 주요 활용: MVVM 패턴 기반의 대규모 프로젝트나, 로직의 테스트 용이성을 확보해야 하는 경우.

개발 방식은 프로젝트의 규모와 팀의 스타일에 따라 달라질 수 있습니다. 각 방식의 장단점을 이해하고 프로젝트에 가장 적합한 방식을 선택하는 것이 중요합니다.


p.s LazyRegionManager의 ViewModel Injection방식에는 앞으로 더 개발할 것이 있습니다.

  • View ←> ViewModel 자동맵핑 기능(App 세팅 당시 Mapper 설정해야함)
  • 뷰모델 타입을 선언해줌으로써 View, ViewModel을 바인딩 처리해주는 기능
    • ex) this._lazyRegionManager.NavigateAsync<뷰모델 타입>(리전 이름, 뷰 키 이름)
7개의 좋아요

LazyRegion.WPF 1.1.0 기능 업데이트

1. 초기 네비게이션 구성 (ConfigureInitialNavigation)

  • 앱 시작 시 특정 Region으로 자동 화면 이동 설정 가능.

  • configure.NavigateAsync("RegionName", "ViewKey") 형태로 사용.

사용 예시

serviceCollection.UseLazyRegion()
                 .AddLazyView<ScreenA>("a")
                 .AddLazyView<ScreenB>("b")
                 .ConfigureInitialNavigation(configure =>
                 {
                     configure.NavigateAsync("Root", "a"); // 앱 시작 시 Root Region에 ScreenA 이동
                 });


2. Region Timeout

  • NavigateAsync는 기본적으로 Region 등록될 때까지 무제한 대기.

  • TimeSpan을 사용해 대기 시간을 설정 가능.

  • 지정 시간 내 Region이 등록되지 않으면 자동으로 네비게이션 취소.

사용 예시

regionManager.NavigateAsync("Root", "a", new TimeSpan(0, 0, 30)); // 30초 동안 대기 후 취소

6개의 좋아요

LazyRegion.WPF 1.2.0 기능 업데이트

RegionControl에 상태 기반 화면 관리 기능(RegionState)이 새롭게 추가되었습니다.
이제 각 Region은 자동으로 LoadingError 상태 화면을 처리할 수 있습니다.

주요 기능

  • 지정된 시간 동안 표시되는 Loading 화면
  • 설정된 시간이 지나도 해당 Region에 변화가 없을 경우 자동으로 표시되는 Error 화면
  • Error 상태 이후에도 ViewKey 변화 시 정상적으로 화면 전환 가능

이 기능을 통해 Region 단위의 상태 관리가 더욱 단순하고 직관적으로 개선되었습니다.


지정 된 시간 초과 시 에러 화면 노출

지정 된 내 시간 내 리전에 화면 전환


기대효과

기존에는 API 요청 전후로 Region.NavigateAsync()를 직접 호출하여 “로딩 화면 → 실제 화면”으로 전환해야 했습니다.
RegionState 도입 후에는 이러한 제어 로직을 ViewModel에서 제거하고, Region 자체가 자동으로 로딩/에러 상태를 관리합니다.

Before

// ViewModel 내부
public async Task LoadAsync()
{
    await _regionManager.NavigateAsync("MainRegion", "LoadingView");
    
    var result = await _api.GetDataAsync();

    if (result.IsSuccess)
        await _regionManager.NavigateAsync("MainRegion", "DataView");
    else
        await _regionManager.NavigateAsync("MainRegion", "ErrorView");
}

After

// ViewModel 내부
public async Task LoadAsync()
{
    var result = await _api.GetDataAsync();

    if (result.IsSuccess)
        await _regionManager.NavigateAsync("MainRegion", "DataView");
}
8개의 좋아요

LazyRegion.Maui 라이브러리도 출시해봤습니다.

MAUI는 기본적으로 Page 기반의 네비게이션을 사용하기 때문에 LazyRegion.Maui의 사용이 다소 의문스러울 수 있습니다.

하지만 이런 경우가 있습니다.

이런 경우가 있을겁니다.
Shell, TabbedPage 에서는 자유롭게 페이지를 생성하고 네비게이션할 수 있지만,
네비게이션 바 아이콘을 변형하는 등 자유도가 낮아 원하는 UI를 구현하기 어려울 때가 종종 있어, 이럴 때 LazyRegion.Maui를 고려할 수 있습니다.

요즘 앱은 네비게이션 바 자체가 다양하며, Flutter 진영에서는 다양한 네비게이션 바 스타일을 패키지하는 사례도 있습니다.


MAUI에서도 이러한 생태계를 구축할 수 있도록 ContentView 간 전환 기술이 있다면, NavigationBar 디자인만 입히면 자연스럽게 구현되지 않을까 하는 생각입니다.

5개의 좋아요

Region 초기 흐름 정의 방식의 재정의

이번 기능은 완전히 새로운 아이디어라기보다는,
기존에 제공하던 RegionStateConfigureInitialNavigation 개념을
조금 더 직관적으로 정의해 볼 수 있지 않을까 하는 고민에서 출발하였습니다.

기존의 ConfigureInitialNavigation

“특정 Region 영역에 최초로 어떤 화면을 띄울 것인가”

에 초점이 맞춰진 기능이었습니다.
다만 실제 화면 구성이나 앱 초기 진입 흐름을 설계하다 보면,
단순히 하나의 화면을 띄우는 것보다 조건에 따른 화면 전개 흐름
함께 표현하고 싶은 경우가 많았습니다.

이러한 부분을 좀 더 자연스럽게 표현하기 위해,
InitialNavigation 대신 Initial Flow라는 개념을 도입하게 되었습니다.


기존 방식의 한계

기존 ConfigureInitialNavigation은 다음과 같은 특징을 가지고 있습니다.

  • Region 단위로 최초 View를 지정할 수 있고
  • 구현 자체는 단순하지만
  • 조건 분기나 흐름 제어는 별도의 코드로 분리될 수밖에 없습니다

그 결과,
“앱이 시작될 때 화면이 어떤 순서로 전개되는지”를
한눈에 파악하기 어렵다는 아쉬움이 있었습니다.


새로운 Initial Flow 개념

새로운 방식에서는 Region에 대해
“처음부터 어떤 순서와 조건으로 화면이 전개되는지”
코드 상에서 그대로 표현하는 것을 목표로 합니다.

기본 사용 예

.ConfigureRegions(configure =>
{
    configure.ForRegion("Root")
             .WithInitialFlow(flow =>
             {
                 flow.Show("a");
             });
});
  • ForRegion : 대상 Region을 지정합니다
  • WithInitialFlow : 해당 Region의 초기 화면 흐름을 정의합니다
  • Show : 최초로 노출할 화면을 지정합니다

조건 기반 화면 흐름 정의

Initial Flow의 핵심은
조건에 따른 화면 전환 흐름을 직관적으로 정의할 수 있다는 점입니다.

예를 들어,

  1. 앱 실행 시 Splash 화면을 먼저 표시하고
  2. 로그인 상태라면 Home 화면으로 이동하며
  3. 로그인되어 있지 않다면 Login 화면으로 이동하는

흐름을 아래와 같이 설정할 수 있습니다.

.ConfigureRegions(configure =>
{
    configure.ForRegion("Root")
             .WithInitialFlow(flow =>
             {
                 flow.Show("Splash")
                     .Then<LoginService>("Home", s => s.IsLoggedIn)
                     .Then("Login");
             });
});

흐름 해석

  • Show("Splash")
    → 초기 진입 시 가장 먼저 표시되는 화면입니다
  • Then<LoginService>("Home", s => s.IsLoggedIn)
    LoginService.IsLoggedIn 값이 true인 경우 Home 화면으로 이동합니다
  • Then("Login")
    → 위 조건이 만족되지 않을 경우 진입하는 대체 흐름입니다

이 방식을 사용하면,
앱의 초기 진입 시나리오 자체가 선언적으로 드러나기 때문에
코드를 따라가며 흐름을 추적할 필요가 줄어듭니다.


정리

이번 Initial Flow 설계의 목적은 다음과 같습니다.

  • Region 초기 화면 구성을 선언적이고 읽기 쉬운 형태로 정의하고
  • “무엇을 띄울 것인가”보다는
    “어떤 흐름으로 화면이 전개되는가” 에 집중하며
  • 앱 시작 시나리오를 한 곳에서 명확하게 관리할 수 있도록 하는 것입니다

결과적으로 ConfigureInitialNavigation보다
의도 전달력이 높고 확장 가능한 초기 화면 정의 방식을 제공하는 것을
목표로 하고 있습니다.

3개의 좋아요

안녕하세요.

앞서 공유드린 내용을 모두 반영하여 기능 보완, 스타일 정리, 그리고 기존 로직 전반 리팩터링을 진행했고, 이를 기준으로 V2.0.0 업데이트를 진행했습니다.


Version 1 → Version 2 마이그레이션 가이드

기존 1.x.x 버전을 사용 중이신 경우, 아래 항목만 수정하시면 무리 없이 이전하실 수 있습니다.

1. ConfigureInitialNavigation 제거

초기 네비게이션 설정 방식은 InitialFlow로 통합되었습니다.

configure.ForRegion("Root")
         .WithInitialFlow(flow =>
         {
             flow.Show("a");
         });

2. 참조 네임스페이스 추가

// 기존
using LazyRegion.Core;

// 변경
using LazyRegion.Core;
using LazyRegion.WPF; // WPF 사용 시

// MAUI 사용 시
using LazyRegion.Maui;

V2에서는 무엇이 달라졌나

V2에서는 LazyRegion을 더 직관적으로 사용할 수 있도록 구조를 정리했습니다.
특히 MAUI 샘플 스타일을 참고해, “읽히는 코드”에 최대한 가깝게 구성하는 데 초점을 두었습니다.

기존 방식

serviceCollection.UseLazyRegion()
                 .AddLazyView<ScreenA>("a")
                 .AddLazyView<ScreenB>("b")
                 .ConfigureRegions(configure =>
                 {
                     configure.ForRegion("Root")
                              .WithInitialFlow(flow =>
                              {
                                  flow.Show("a");
                              });
                 });

변경된 방식 (V2)

serviceCollection.UseLazyRegion(lazy =>
{
    lazy.Register<SplashView>("Splash");
    lazy.Register<LoginView>("Login");
    lazy.Register<MainView>("Main");

    lazy.ConfigureRegions(config =>
    {
        config.ForRegion("Root")
              .WithInitialFlow(flow =>
              {
                  flow.Show("Splash")
                      .Then("Main", async () =>
                      {
                          await Task.Delay(5000);
                          return false;
                      })
                      .Then("Login");
              });
    });
});

왜 이렇게 바꿨나

기존 AddLazyViewIServiceCollection 확장 메서드로 제공되고 있었지만,
실제로는 UseLazyRegion이 호출되지 않으면 정상 동작하지 않는 구조였습니다.

이 구조는 사용자 입장에서 흐름을 이해하기 어렵고,
“이게 왜 안 되지?”라는 의문을 만들 가능성이 컸습니다.

그래서 V2에서는:

  • LazyRegion을 사용하는 진입점을 UseLazyRegion 하나로 명확히 하고
  • View 등록과 Region 설정을 모두 그 안에서 처리하도록 변경
  • 결과적으로 설정 → 흐름 → 화면 전개가 한눈에 보이도록 정리했습니다.

이번 V2는 단순한 API 변경이 아니라,
LazyRegion을 어떻게 쓰게 하고 싶은지에 대한 방향성을 정리한 버전이라고 보셔도 됩니다.

사용해보시면서 불편한 점이나
“이건 이렇게 쓰고 싶은데…” 같은 의견 있으면 언제든지 주세요.
그런 피드백을 기준으로 V2를 더 단단하게 다듬어갈 생각입니다.

3개의 좋아요