티스토리 뷰

반응형

2022.11.30 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part6 - TabControl Region Navigation

2022.11.25 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part5 - Region & ContentControl Region Navigation

2022.11.18 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part4 - Register Types

2022.11.15 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part3 - DelegateCommand

2022.10.28 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part2 - 프로젝트 구성 살펴 보기

2022.10.27 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part1

지금까지 ContentControl, TabControl에 Region을 지정하고 사용하는 방법에 대해서 알아 보았습니다. 

이번 포스팅에서는 사용자가 원하는 컨트롤에 Region을 지정하고 사용하기 위해, RegionAdapter를 생성하고, 사용하는 방법을 알아 보도록 하겠습니다.

참고 포스트

Create a Custom Prism RegionAdapter - Brian Lagunas

 

Create a Custom Prism RegionAdapter - Brian Lagunas

Don’t want to read the article?  Watch the video tutorial on Xaml TV. Prism provides 4 region adapters out of the box for you: ContentControlRegionAdapter SelectorRegionAdaptor ItemsControlRegionAdapter TabControlRegionAdapter (Silverlight only) Well, w

brianlagunas.com

1. RegionAdapter를 만들어야 하는 이유

  • RegionAdapter의 동작에 기능을 추가해야할 때
    • RegionAdapter를 이용할 때 Behavior를 추가해서 동작에 대한 기능을 추가해야 하는 경우가 있습니다. 
    • TabItem이 Add, Remove, Close될 때 ViewModel의 특정 메서드 호출하거나, EventAggregator를 통해 이벤트를 발생시키는 등의 동작이 필요한 경우.
  • 새로운 컨트롤에 Region을 만들고 사용하기 위해
    • StackPanel 같은 기존에 기본 컨트롤에 Region 사용
    • 3rd party 컴포넌트들에 Region을 설정

2. StackPanelRegionAdapter.cs

ReginAdapter를 추가할 때 기본적인 사항만 알면 쉽게 만들 수 있습니다.

  • RegionAdapterBase<T>를 상속 받는 클래스를 만듭니다.
  • Adapt 메서드 구현
    • region은 논리적인 영역으로, region에 추가/삭제되는 view들을 어떻게 물리적인 regionTarget에 구현을 할 것인지를 정해야 합니다.
  • CreateRegion()
    • Region에서 한번에 한개의 뷰만 Active 시킬 것인지, 모두 Active 시킬지를 반환해야합니다.
using Prism.Regions;
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;

namespace PrismStep6
{
    /// <summary>
    /// StackPanel RegionAdapter
    /// </summary>
    public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
    {
        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="regionBehaviorFactory"></param>
        public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
            : base(regionBehaviorFactory)
        {
        }
        /// <summary>
        /// 논리적 Region과 물리적 Region간의 연결 관계 설정
        /// </summary>
        /// <param name="region"></param>
        /// <param name="regionTarget"></param>
        /// <exception cref="NotImplementedException"></exception>
        protected override void Adapt(IRegion region, StackPanel regionTarget)
        {
            region.Views.CollectionChanged += (s, e) =>
            {
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        foreach (FrameworkElement element in e.NewItems)
                        {
                            _ = regionTarget.Children.Add(element);
                        }
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        break;
                    case NotifyCollectionChangedAction.Move:
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        break;
                }
            };
        }
        /// <summary>
        /// 논리적 Region 생성
        /// </summary>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        protected override IRegion CreateRegion()
        {
            //StackPanel에는 모든 컨텐츠가 다 보이기 때문에 전체 ActiveRegion으로 생성
            return new AllActiveRegion();

            //TabItem과 같은 1개만 선택되어야 하는 Region은 SingleActiveRegion으로 생성
            //return new SingleActiveRegion();
        }
    }
}

새로 생성한 RegionAdapter를 사용하기 위해서는 App.xaml.cs에서 StackPanel과 연결해 주어야 합니다.

App.xaml.cs

protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{
    regionAdapterMappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>());
    base.ConfigureRegionAdapterMappings(regionAdapterMappings);
}

MainWindow.xaml

<StackPanel
    Grid.Row="1"
    Margin="10"
    prism:RegionManager.RegionName="ContentRegion" />

실행

시작하면서 바로 Test1 View가 화면에 출력됩니다.

Test2View를 클릭해서 Navigate 시켜 줍니다

Test1View와 Test2View 모두 IsActive가 True로 되어있는 것을 볼 수 있습니다.

3. 기능 추가

현재 StackPanelRegionAdapter에는 Add는 가능하나 Remove가 불가능 합니다. 닫기 버튼이 없어서 동작을 실행할 수 없기 때문입니다.

닫기 버튼을 눌러서 Region의 View를 지우는 것이 단순한 작업이 아니라 코드를 천천히 살펴 보아야 합니다.

아래 구현한 방법이 정답은 아니기 때문에 원하시는 대로 구현하셔도 괜찮습니다. 다만, Remove시에 Memory Leak이 발생하지 않도록 주의할 필요가 있습니다.

RegionBase.xaml

닫기 버튼과 ContentControl이 있는 UserControl을 하나 추가했습니다.

Close 버튼은 Click이벤트를 코드 비하인드에 만들어 주었고, 해당 뷰를 닫는 용도로 사용되는 중간 컨트롤 입니다.

단순 컨트롤를 만드는 경우에 코드 비하인드를 사용하면, ViewModel이 변형 혹은 변경되는 경우를 막을 수 있습니다.

 

<UserControl
    x:Class="PrismStep6.Views.RegionBase"
    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:local="clr-namespace:PrismStep6.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Button
            Width="20"
            HorizontalAlignment="Right"
            Content="❌" 
            Click="Button_Click"/>
        <ContentControl
            x:Name="RegionBaseContent"
            Grid.Row="1"
            Margin="0,5,0,0"
            HorizontalContentAlignment="Stretch" />
    </Grid>
</UserControl>

RegionBase.xaml.cs

이 컨트롤은 반드시 Container에서 Resolve를 받아서 사용해야 하고, Resolve시 RegionName을 넘겨줘야 합니다.

/// <summary>
/// RegionBase 컨트롤
/// </summary>
public partial class RegionBase : UserControl
{
    /// <summary>
    /// 리즌 메니저
    /// </summary>
    private readonly IRegionManager _regionManager;
    /// <summary>
    /// 리즌 이름
    /// </summary>
    private readonly string _regionName;

    public RegionBase()
    {
        InitializeComponent();
    }

    public RegionBase(IRegionManager regionManager, string regionName) : this()
    {
        _regionManager = regionManager;
        _regionName = regionName;
    }
    /// <summary>
    /// 닫기 버튼 클릭
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        //리즌 찾기
        IRegion region = _regionManager.Regions[_regionName];
        if (region == null)
        {
            return;
        }
        //리즌에서 제거할 뷰 찾기
        object removeItem = region.Views.FirstOrDefault(v => v.Equals(RegionBaseContent.Content));
        if (removeItem != null)
        {
            //뷰 제거 - 이렇게해야 case NotifyCollectionChangedAction.Remove: 이벤트가 발생합니다.
            region.Remove(removeItem);
        }
    }
}

StackPanelRegionAdapter.cs

case NotifyCollectionChangedAction.Add:
    foreach (FrameworkElement element in e.NewItems)
    {
        //문자열을 전달하면서 Injection하기 - 리즌명 입력
        var regionBase = _containerProvider.Resolve<RegionBase>((typeof(string), _regionName));
        regionBase.RegionBaseContent.Content = element;
        _ = regionTarget.Children.Add(regionBase);
    }
    break;

실행

노란색 영역이 RegionBase이고, 파란색이 Test1View의 영역입니다.

Test2View 버튼 클릭

Test2View 오른쪽 X 버튼 클릭

Region에 있는 View를 삭제하면 NotifyCollectionChangedAction.Remove 이벤트가 발생하고, 그 때 StackPanel에서 RegionBase와 Test2View를 삭제하게 됩니다.

case NotifyCollectionChangedAction.Remove:
    foreach (FrameworkElement element in e.OldItems)
    {
        var removeItem = regionTarget.Children.OfType<RegionBase>()
                        .FirstOrDefault(rb => rb.RegionBaseContent.Content == element);
        if (removeItem != null)
        {
            regionTarget.Children.Remove(removeItem);
        }
    }
    break;

 

4. 소스

WpfTest/PrismStep6 at master · kaki104/WpfTest (github.com)

 

GitHub - kaki104/WpfTest

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

github.com

이 외에도 Telerik 컨트롤들에 사용되는 리즌 어뎁터도 몇개 만들어 놓은 것이 있는데.. 요청이 있으면 공개하도록 하겠습니다.

반응형
댓글