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컨트롤

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는 어떤 식으로 우회시킬지 감이 잘 안옵니다.

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

좋아요 1

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

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

이렇게 맞는걸까요?

좋아요 2

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

좋아요 1

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

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을 사용했습니다~

좋아요 3

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

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

예제 감사드립니다.

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

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

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

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

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

좋아요 1

좋은 주말 되세요~

좋아요 1

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

좋아요 1