WinUI 3의 NavigationView에서의 Navigate 메서드는 WPF의 그것과 달랐군요..

먼저 저와 비슷한 경험을 찾았어서 공유부터 드립니다…

WinUI3에는 NavigationView라는 녀석이 있습니다.

윈도 앱 좌측 메뉴와 같은 편리하고 예쁜(?) 모양을 제공해서 이걸 사용하기로 했었는데요,
평소 하던 대로 View와 ViewModel을 ServiceProvider에 맡겨서 쓰고 있었던거죠.
WPF에서 Navigate는 인스턴스를 인자로 넘기고,(애초에 NavigationView라는 녀석도 없구요.)
WinUI 3에서는 해당 Frame의 인스턴스가 아닌 Type만 넘기는걸 알고 아차 했습니다.
ServiceProvider.GetService<T>()로 관리되는 View로는 애초에 Navigate를 통한 화면 전환이 불가능했었던 거죠.

관련 해결책으로 친절히 Nuget으로 공유해주신 분이 있었는데요.
코드 생성기로 Type을 받는 경우 provider를 호출해주는 것으로 판단됩니다.
(고수분들 보시면 설명좀 부탁드립니다 ^^;)
(해당 패키지 링크)

(관련 작성 문서:동일 repo입니다.)

위 글과 같이, 굳이 View를 등록해서 쓰는 이유 중 하나가
View를 싱글턴으로 쓰고 싶(을 수도있죠!)다거나, 관리를 맡기고 싶다거나 하는 식이라고 생각했습니다.
이걸 수정 해줄지는 모르겠네요 위 이슈도 24년 10월이면 꽤 최근인 듯 한데…
조금 더 찾아보니 제안 된지는 꽤 된 내용인가 보네요.

1개의 좋아요

이왕 글 남긴 김에 삽질한 케이스를 더 말씀드려보자면,
Windows.UI.Xaml.Controls 네임스페이스, 즉 UWP/WINUI의
Navigate 메서드들 오버라이드들 전부 System.Type로 인수로 받는다고 말씀드렸는데요,
얘 때문에 또 고생을 했더랩니다.
해당 인수에는 항상 Page 및 그 파생 형식만 지원한다고 명시되어있습니다.

sourcePageType Type

The page to navigate to, specified as a type reference to its partial class type. Must be a Page-derived data type; otherwise, an exception is thrown. (A type reference is given as System.Type for Microsoft .NET, or a TypeName helper struct for Visual C++ component extensions (C++/CX)).

초보적인 실수로 Page가아니라 Frame같은 형식을 넣었었는데…
엉뚱한 개체를 집어넣으면 InvalidArgumentException 또는 그와 비슷한 식의 유추 가능성이 있을 오류가 아니라 언제나 NullReferenceException을 뱉더군요.

위 사항을 몰랐으니
어디서, 뭐가 문제인지를 알 방도가 없어서 꽤 헤맸습니다.
혹시 비슷한 오류 보신다면 꼭 해당 UI 요소가Page인지 확인해보세요ㅠㅠ


여기서부터는 그냥 TMI

심지어… 중간에 StackTrace 및 중단점을 찍어봐도
이미 바이너리/최적화 해버린 부분을 거쳐버려서 너무 Deep해져버리더군요.
image
트레이스 찍어 내려가봤다가 C#의 탈을 쓴 이상한 친구들만 만나서 웩…

internal unsafe static void Start(IObjectReference _obj, global::Microsoft.UI.Xaml.ApplicationInitializationCallback callback)
{
	IntPtr thisPtr = _obj.ThisPtr;
	ObjectReferenceValue value = default(ObjectReferenceValue);
	try
	{
		value = ApplicationInitializationCallback.CreateMarshaler2(callback);        
		ExceptionHelpers.ThrowExceptionForHR(((delegate* unmanaged[Stdcall]<IntPtr, IntPtr, int>)(*(IntPtr*)((nint)(*(IntPtr*)(void*)thisPtr) + (nint)7 * (nint)sizeof(delegate* unmanaged[Stdcall]<IntPtr, IntPtr, int>))))(thisPtr, MarshalInspectable<object>.GetAbi(value)));
        //여기서 뇌정지가..
	}
	finally
	{
		MarshalInspectable<object>.DisposeMarshaler(value);
	}
}

(찾아보니, ABI 접사를 붙인 네임스페이스, 즉 이미 바이너리화 된 Windows App SDK의 일부라고 합니다)
unsafe 구문을 모르지도 않고 그럭저럭 써본 경험이 있었지만 이건 너무 갔다 싶었지요.
아마… 디스어셈블리 또는 요런 쪽 지식이 조금 더 깊었다면 찾을 수 있었을 지도 모르겠지만요!

처음 질문으로 올리려고 정리하다가 어쩌다 보니 자문자답으로 해결해버려서,
이렇게 마무리하면서 남겨봅니다. ㅎㅎ
혹여나, DI 및 IServiceProvider를 활용해 View를 좀 더 잘 관리할 방법이 생긴다면 거기다 [해결책] 표시를 하려고 합니다.

1개의 좋아요