private 생성자를 가진 뷰모델을 디자인 타임에서도 실제 데이터를 볼 수 있도록 설정하는 방법

d:DataContext에 관하여 개인적으로 받은 질문을 재구성해서 공유합니다.

WPF 프로젝트에서 뷰모델(ViewModel)의 생성자를 private으로 설정하면서도 디자인 타임에 실제 데이터를 표시하고, 인텔리센스를 사용할 수 있도록 하고 싶습니다.

x:DesignInstanceprivate 생성자 때문에 사용할 수 없습니다. 이러한 상황에서 디자인 타임에서도 뷰모델의 실제 데이터를 표시하려면 어떻게 해야 하나요?

구체적으로는, 다음과 같은 요구사항을 충족하는 방법을 알고 싶습니다:

  • 뷰모델의 생성자를 private으로 설정하여 싱글턴 패턴을 유지하고 싶습니다.
  • 일반적으로 public 생성자를 가진 뷰모델은 d:DataContext="{d:DesignInstance local:MainViewModel, IsDesignTimeCreatable=True}" 설정을 통해 디자인 타임에 인스턴스를 생성하고 데이터를 볼 수 있지만, private 생성자 때문에 이러한 방식이 동작하지 않습니다.
  • 디자인 타임에서도 뷰모델의 실제 데이터를 표시하여 디자이너에서 편리하게 작업하고 인텔리센스를 사용하고 싶습니다.
  • XAML 디자이너에서 디자인 타임 데이터 컨텍스트를 설정하여 런타임과 디자인 타임에서 다른 데이터를 사용할 수 있도록 하고 싶습니다.

이러한 요구사항을 충족하기 위한 구현 방법을 예제 코드와 함께 설명 부탁 드립니다.

image

디자인 타임에서도 실제 데이터를 볼 수 있도록 하기 위해서는 private 생성자를 가진 뷰모델을 설정하는 방법을 개선해야 합니다. x:DesignInstanceprivate 생성자를 가진 클래스에 사용할 수 없으므로, 다른 접근 방식을 사용해야 합니다. 이를 위해 XAML 디자이너에서 디자인 타임 전용 데이터 컨텍스트를 설정하여 인텔리센스를 사용할 수 있게 하고, 실제 데이터를 표시할 수 있습니다. 다음은 이를 구현하는 방법입니다:

1. 뷰모델 정의

뷰모델의 생성자를 private으로 두고, 정적 생성자를 사용하여 디자인 타임 데이터를 설정합니다.

public class MainViewModel
{
    public string James { get; set; } = "James1";

    private static MainViewModel _instance;

    private MainViewModel()
    {
        // Initialization code here
    }

    static MainViewModel()
    {
        if (IsInDesignMode)
        {
            _instance = new MainViewModel
            {
                James = "Design Time James"
            };
        }
        else
        {
            _instance = new MainViewModel();
        }
    }

    public static MainViewModel Instance => _instance;

    public static bool IsInDesignMode
    {
        get
        {
            return System.ComponentModel.DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject());
        }
    }
}

2. XAML 설정

디자인 타임 데이터 컨텍스트를 설정하여 실제 데이터를 보여줍니다.

<Window x:Class="YourNamespace.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:YourNamespace"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    
    <!-- 런타임 데이터 컨텍스트 설정 -->
    <Window.DataContext>
        <Binding Source="{x:Static local:MainViewModel.Instance}" />
    </Window.DataContext>
    
    <!-- 디자인 타임 데이터 컨텍스트 설정 -->
    <d:Window.DataContext>
        <Binding Source="{x:Static local:MainViewModel.Instance}" />
    </d:Window.DataContext>
    
    <!-- UI elements here -->
    <Grid>
        <TextBlock Text="{Binding James}" />
    </Grid>
</Window>

이 설정을 통해 디자인 타임에서는 MainViewModel.InstanceJames 속성 값이 "Design Time James"로 설정되어 표시됩니다. 런타임에서는 정적 생성자를 통해 초기화된 값인 "James1"이 표시됩니다.

이렇게 하면 디자인 타임에서도 실제 데이터를 볼 수 있고, 인텔리센스도 사용할 수 있습니다.

2 Likes

약간 지저분한데 혹시 더 좋은 방법 있으면 알려 주세요.
저는 CommunityToolkit mvvm 보다 ReactiveUI 를 좋아해서 해당 툴킷 사용 하겠습니다.

// MainView.xaml
<Window
    x:Class="WpfApp7.Views.MainView"
    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:vm="clr-namespace:WpfApp7.ViewModels"
    Title="MainView"
    d:DataContext="{d:DesignInstance vm:MainViewModel}"
    mc:Ignorable="d">
    <StackPanel>
        <TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Button Command="{Binding ShowTextCommand}" Content="Show Text" />
    </StackPanel>
</Window>

d:DataContext=“{d:DesignInstance vm:MainViewModel}”
요렇게 써주면 인텔리센스를 쓸수 있습니다. 디자인에서 데이터 표시는 안되네요.

// MainView.xaml.cs
using System.Windows;
using WpfApp7.ViewModels;

namespace WpfApp7.Views;
public partial class MainView : Window
{
    public MainView(MainViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}
// MainViewModel.cs
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System.Windows;

namespace WpfApp7.ViewModels;
public class MainViewModel : ReactiveObject
{
    [Reactive] public string Text { get; set; } = string.Empty;
    public IReactiveCommand ShowTextCommand { get; init; }

    private static MainViewModel instance = null!;
    public static MainViewModel Instance => instance ?? (instance = new MainViewModel());

    private MainViewModel()
    {
        ShowTextCommand = ReactiveCommand.Create(() => MessageBox.Show(Text));
    }
}
// App.xaml.cs
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System.Windows;
using WpfApp7.ViewModels;
using WpfApp7.Views;

namespace WpfApp7;

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var services = new ServiceCollection();
        services.AddSingleton(MainViewModel.Instance);
        services.AddSingleton<MainView>();
        Ioc.Default.ConfigureServices(services.BuildServiceProvider());

        Ioc.Default.GetService<MainView>()!.Show();
    }
}
1 Like

@code 네 맞습니다. 요구 조건 중에서 하나가 private이기 때문에 MainViewModel을 DesignInstance에 넣어도 인스턴스를 생성할 수 없어 디자인에서 데이터 표시가 불가능하게 되죠,