티스토리 뷰

반응형

ContentControl의 Content에는 View를 생성해서 넣을 수 있습니다.

다만, ViewModel에서 View를 생성해서 바인딩하는 방식을 사용하지는 않습니다.

이렇게되면, View가 ViewModel에 종속된 모양이되어서 메모리 누수가 발생할 수 있기 때문입니다.

2가지 방법이 있는데 여기서는 Behavior를 이용하는 방법에 대해서 알아 보도록 하겠습니다.

이 셈플은 PrismStep7 프로젝트에 추가로 작업되었습니다.

1. ContentControlBehavior.cs

/// <summary>
/// 컨텐츠 컨트롤 비헤이비어
/// </summary>
public class ContentControlBehavior : Behavior<ContentControl>
{
    protected override void OnAttached()
    {
        //AssociatedObject에 이벤트를 연결 할 필요가 있으면 여기에
        ResolveView();
    }
    protected override void OnDetaching()
    {
        //AssociatedObject에 연결했던 이벤트 해제는 여기서
    }

    public string ViewName
    {
        get => (string)GetValue(ViewNameProperty);
        set => SetValue(ViewNameProperty, value);
    }

    /// <summary>
    /// 뷰이름
    /// </summary>
    public static readonly DependencyProperty ViewNameProperty =
        DependencyProperty.Register("ViewName", typeof(string), typeof(ContentControlBehavior), new PropertyMetadata(null));


    public Type ViewType
    {
        get => (Type)GetValue(ViewTypeProperty);
        set => SetValue(ViewTypeProperty, value);
    }

    /// <summary>
    /// 뷰유형
    /// </summary>
    public static readonly DependencyProperty ViewTypeProperty =
        DependencyProperty.Register("ViewType", typeof(Type), typeof(ContentControlBehavior), new PropertyMetadata(null));

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        switch (e.Property.Name)
        {
            case nameof(ViewName):
            case nameof(ViewType):
                //ViewName이나 ViewType이 변경되면 ResolveView()를 실행합니다.
                ResolveView();
                break;
        }
        base.OnPropertyChanged(e);
    }

    private void ResolveView()
    {
        //AssociatedObject 연결되지 않았으면 종료합니다.
        if (AssociatedObject == null)
        {
            return;
        }
        //ViewName이 있을 때
        if (string.IsNullOrEmpty(ViewName) == false)
        {
            Type type = Type.GetType(ViewName);
            if (type != null)
            {
                object view = App.ContainerProvider.Resolve(type);
                AssociatedObject.Content = view;
            }
        }
        //ViewType이 있을 때
        else if (ViewType != null)
        {
            object view = App.ContainerProvider.Resolve(ViewType);
            AssociatedObject.Content = view;
        }
        //todo : AssociatedObject.Content에 이미 연결된 View가 있을 경우 반드시 뷰를 제거하고 입력해야함
    }
}
  • Behavior는 ContentControl에 붙어있는 형태이기 때문에 ViewModel과는 연결관계가 없이 사용할 수 있습니다.
  • ViewModel과 Binding이 되는 프로퍼티는 ViewName과 ViewType입니다.
  • 둘 중 1개의 한개라도 값이 변경되거나, 혹은 OnAttached()가 실행될 때 ResolveView()를 실행해서 View를 생성하고, ContentControl.Content 속성어 넣게 됩니다.
  • ViewName에는 AssemblyQualifiedName을 이용하거나 문자열을 조합해서 사용할 수 있습니다.
    • PrismStep7.Views.InjectionView, PrismStep7, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
  • ContentControl.Content에 생성된 View를 넣기 전에 이미 들어가있던 View 인스턴스가 있다면 제거를 하고 넣어주는 것이 좋습니다.
  • Behavior는 생성자 주입을 할 수 없기 때문에, App.xaml.cs에 ContainerProvider라는 static 프로퍼티를 추가했습니다.
App.xaml.cs에 ContainerProvider를 사용하는 방법 말고 다른 방법도 있는데, 나중에 기회가되면 올리도록 하겠습니다.

2. App.xaml.cs

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
    private static IContainerProvider _containerProvider;
    /// <summary>
    /// 컨테이너 프로바이더 - 생성자 인젝션 할 수 없는 환경에서 사용하기 위해 추가
    /// </summary>
    public static IContainerProvider ContainerProvider
    {
        get { return _containerProvider; }
    }
    protected override Window CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        _containerProvider = Container;

        //AppContext 싱글톤으로 등록
        _ = containerRegistry.RegisterSingleton<IAppContext, AppContext>();

        //Region Navigation할 화면 등록
        containerRegistry.RegisterForNavigation(typeof(Login), nameof(Login));
        containerRegistry.RegisterForNavigation(typeof(Home), nameof(Home));
    }
}

3. Home.xaml

 <!--  ContentControl에 View Injection  -->
 <Grid Grid.Row="1" Grid.Column="1">
     <Grid.RowDefinitions>
         <RowDefinition />
         <RowDefinition />
     </Grid.RowDefinitions>
     <ContentControl>
         <b:Interaction.Behaviors>
             <behaviors:ContentControlBehavior ViewName="{Binding ViewName}" ViewType="{Binding ViewType}" />
         </b:Interaction.Behaviors>
     </ContentControl>
     <ContentControl Grid.Row="1" />
 </Grid>
  • Behavior를 사용하기 위해서는 Microsoft Behavior nuget package가 설치되어 있어야하고,  xmlns:b="http://schemas.microsoft.com/xaml/behaviors" 네임스페이스가 필요합니다.
  • ViewName과 ViewType 프로퍼티가 ViewModel의 ViewName, ViewType 프로퍼티와 바인딩되어있는 것을 알 수 있습니다.

4. HomeViewModel.cs

/// <summary>
/// BehaviorCommand에 연결된 메서드
/// </summary>
/// <param name="para"></param>
private void OnBehavior(string para)
{
    switch (para)
    {
        case "Name":
            //어셈블리 이름만 주면 인젝션 시킬 수 있기 때문에, 참조를 추가하지 않아도 생성 시킬 수 있음
            ViewName = typeof(InjectionView).AssemblyQualifiedName;
            break;
        case "Type":
            //생성할 Type을 반환해야해서 참조가 필요
            ViewType = typeof(InjectionView);
            break;
    }
}

5. 소스

kaki104/WpfTest at PrismStep7/add-contentcontrol-behavior (github.com)

이 셈플은 PrismStep7 프로젝트에 추가로 작업되었습니다.

 

GitHub - kaki104/WpfTest

Contribute to kaki104/WpfTest development by creating an account on GitHub.

github.com

 

반응형

'WPF .NET' 카테고리의 다른 글

Kiosk 만들기 - Part4  (2) 2023.10.23
Kiosk 만들기 - Part3  (0) 2023.10.20
Kiosk 만들기 - Part2  (0) 2023.10.18
Kiosk 만들기 - Part1  (0) 2023.10.16
Kiosk 만들기 - Part0  (0) 2023.10.13
댓글