Template vs UserControl

WPF 끈이 짧아서 이런 고민을 시작하는 단계입니다.

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의 컨트롤을 서로 바인딩으로 연결해주는 것인데요.

그럼 아래와 같은 구조가 될 것입니다.


A컨트롤

B컨트롤
B컨트롤

C컨트롤
C컨트롤

현재 B에 A의 DP를 만들지 않은 상태입니다.

이걸 그럼 그냥 만들어서 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는 어떤 식으로 우회시킬지 감이 잘 안옵니다.

제가 생각하는 방법 외에 다른 방법이 있을까요?

2개의 좋아요

도움을 드리고 싶은데 정리를 먼저 해야 할 것 같아서요.

  • B가 A를 품고 있다.
  • C가 B를 품고 있다.
  • C는 ViewModel이 있다.
  • C에서 B가 품고 있는 A에 바인딩 해야 한다.

이렇게 맞는걸까요?

3개의 좋아요

정확하십니다. A는 사실 Textbox를 품고있는 uc일 뿐이고 C에서 A의 Textbox에 바인딩해야하는 것입니다.

2개의 좋아요

샘플로 작성한거라 양해 바랄게요.
혹시 이런 형태를 말씀하시는 것인지 모르겠네요.

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);
    }
}

AView

<UserControl x:Class="WpfDemo.AView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfDemo"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <TextBlock Text="{Binding Text}" />
    </Grid>
</UserControl>

BView

<UserControl x:Class="WpfDemo.BView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfDemo"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <StackPanel>
        <local:AView DataContext="{Binding A}"></local:AView>
    </StackPanel>
</UserControl>

MainWindow
ContentControl 대신 BView를 직접 선언해도 됩니다만, ContentControl을 선언하고 Resource에 ViewModel과 View를 매핑하시면 바인딩된 ViewModel 타입에 따라 동적으로 View를 달리 설정하실 수 있습니다.

<Window x:Class="WpfDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfDemo"
        xmlns:viewModels="clr-namespace:WpfDemo.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ContentControl Content="{Binding B}">
            <ContentControl.Resources>
                <DataTemplate DataType="{x:Type viewModels:BViewModel}">
                    <local:BView DataContext="{Binding}" />
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </Grid>
</Window>

View와 ViewModel 연결

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var viewModel = new MainViewModel
        {
            B = new BViewModel
            {
                A = new AViewModel
                {
                    Text = "Hello world"
                }
            }
        };

        DataContext = viewModel;
    }

image

MVVM 프레임워크로 Microsoft.Toolkit.Mvvm을 사용했습니다~
image

4개의 좋아요

하하…정말 View의 기능만하고 상위 컨트롤로 바인딩 채널만 열어준채로 디테일한 기능은 가장 최상위 단에서 바인딩 처리할 꺼라서… 최상위 단에만 ViewModel이 있으면 될 거라는 생각이 독이 된 거 같네요. (B, A 에 DataContext = this;)

DP를 만들 필요 없이 하위 view단에서 바인딩이 모두 되어 있으니까 각 ViewModel 끼리 의존해서 데이터만 넘기면 알아서 표현이 되겠군요…

예제 감사드립니다.

제가 말씀드린 건 ViewModel의 ObservableProperty가 아니라 uc에서 사용되는 DependencyProperty 였었는데 DP 걷어내도 될 것 같네요.

DP로 해서 View끼리 서로 의존하도록 해서 만들어도 될 것 같지만요.

말씀하신 방식이 훨씬 효율 적일 것 같습니다.

쉽게 쉽게 갔으면 되었을 텐데 괜히 어렵게 하려 했던거네요.

깨달음 주셔서 감사합니다.

2개의 좋아요

좋은 주말 되세요~

2개의 좋아요

감사합니다 좋은 주말되시길 바랍니다^^!

2개의 좋아요