WPF는 View를 구성함에 있어 UserControl이 UserControl을 자식으로 두고, 또 그 UserControl이 UserControl을 자식으로 두면서 UserControl에도 Layer를 나눠 view를 최대한 재사용가능하도록 모듈화하는 것이 좋다고 생각합니다. 대표적으로 프리즘이 그런 것이죠. (안써봤지만)
이 때 UserControl을 만들 때는 웬만하면 부모-자식 간 Binding 처리를 위해서 자식 입장이 되는 UserControl에서 DependencyProperty를 만들어서(제공해서) 부모컨트롤에서 부모컨트롤의 ViewModel이 바인딩 될 수 있도록 합니다.
여기서 제가 생각했을 때 문제가 발생하는데요.
지금부터 UserControl을 uc라고 하겠습니다.
A uc, B uc, C uc가 존재합니다.
A는 가장 기본으로 쓰일 재사용률이 높고 가장 기본단위의 uc입니다. 따라서 A는 기본 WPF 컨트롤로만 구성되어 있습니다. DP를 2개 정의했습니다. 또한 단순하게 view의 역할만 하고 있어, ViewModel(이하 vm)이 딱히 필요없기 때문에 DataContext = this; 로 지정했습니다.
B는 A를 자식컨트롤으로 포함하고 있습니다. B도 DP가 필요하여 2개 정의했습니다. 역시 A처럼 view의 역할만 하고 있어서 DataContext = this; 로 지정했습니다.
C는 B를 자식컨트롤으로 포함하고 있습니다. C에는 vm이 존재합니다. 이 C-vm의 데이터를 B에 Binding 하려는데 B의 A uc에 바인딩하고 싶습니다. A는 B로 감싸져 있으니 C는 B 밖에 접근할 수 없습니다.
여기서 방법은 B에 A와 똑같은 DP를 만들어주고 B에서 만든 DP로 C에서 받은 데이터와 A의 컨트롤을 서로 바인딩으로 연결해주는 것인데요.
이걸 그럼 그냥 만들어서 C → B → A 로 데이터가 전달되도록 하려다가, 만약에 지금 이거야 고작 2층구조지만, 나중에 5층, 10층 구조의 uc가 나온다면 최종 uc까지 가면서 약간 계차수열 의 개념과 유사한만큼 dp를 만들게 될 것입니다. (컨트롤과 컨트롤 사이의 dp를 얼마나 생성해주느냐에 따라 그 다음 컨트롤의 dp가 늘어남.)
그럼 이 예제에서는 B는 A의 DP 2개를 똑같이 만들어서 전달목적으로 사용해야 할 것입니다. 사실 Command 2개면되는데 A 때문에 DP가 추가되는 것이죠.
사실 Template을 이용하면 Control을 겹겹이로 쌓은 구조가 아니고 1차원적인 구조다보니 Binding이 쉽습니다. 그럼 모든 view의 모듈화를 template으로 해야하는지…만약 template으로 한다면 dp는 어떤 식으로 우회시킬지 감이 잘 안옵니다.
C 는 MainWindow, MainViewModel로 정의하였습니다.
MainViewModel은 B 속성으로 BViewModel을 품습니다.
BViewModel은 A 속성으로 AViewModel을 품습니다.
AViewModel에는 바인딩 대상인 Text 속성이 있습니다.
ViewModels
public class MainViewModel : ObservableObject
{
private BViewModel _b;
public BViewModel B
{
get => _b;
set => SetProperty(ref _b, value);
}
}
public class BViewModel : ObservableObject
{
private AViewModel _a;
public AViewModel A
{
get => _a;
set => SetProperty(ref _a, value);
}
}
public class AViewModel : ObservableObject
{
private string _text;
public string Text
{
get => _text;
set => SetProperty(ref _text, value);
}
}
MainWindow
ContentControl 대신 BView를 직접 선언해도 됩니다만, ContentControl을 선언하고 Resource에 ViewModel과 View를 매핑하시면 바인딩된 ViewModel 타입에 따라 동적으로 View를 달리 설정하실 수 있습니다.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = new MainViewModel
{
B = new BViewModel
{
A = new AViewModel
{
Text = "Hello world"
}
}
};
DataContext = viewModel;
}