티스토리 뷰

반응형

2022.03.02 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part3 - Busy 화면 구현

2022.02.24 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part2 - Frame Navigation

2022.02.21 - [WPF] - Microsoft.Toolkit.Mvvm을 이용한 간단한 프레임워크 part1

여러가지 형태의 팝업을 만들어서 사용할 수 있는데, 이번 포스팅에서는 레이어 팝업을 만들어 보도록 하겠습니다. 

 

레이어 팝업이라는 것은 팝업 처럼 보이기는 하지만 실제 팝업은 아니고, Border같은 컨트롤의 보이기 속성을 변경하면서 팝업 처럼 보이는 효과를 내는 것을 이야기 합니다. 또한, 별도의 윈도우를 가지지 않고 메인 윈도우에 붙어있기 때문에 위치 변경, 크기 조정 등을 하지 않습니다.

 

제가 만드는 LayerPopup에는 UserControl을 이용해서 만든 컨트롤들을 IoC를 이용해서 인스턴스 시켜서 출력하는 구조를 가지고 있습니다.

1. MainWindow.xaml 수정

메인 윈도우에 LayerPopup을 추가합니다. UserControl을 보여주는 위치는 ContentControl 내부에 보여줍니다.

            <Border Background="#66000000" Visibility="{Binding ShowLayerPopup, Converter={StaticResource BoolToVisibilityConverter}}">
                <Border
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Background="White"
                    CornerRadius="5">
                    <Border.Effect>
                        <DropShadowEffect />
                    </Border.Effect>
                    <ContentControl Width="400" MinHeight="200">
                        <b:Interaction.Behaviors>
                            <behaviors:ContentControlBehavior ControlName="{Binding ControlName}"
                                                              ShowLayerPopup="{Binding ShowLayerPopup}"/>
                        </b:Interaction.Behaviors>
                    </ContentControl>
                </Border>
            </Border>

2. ContentControlBehavior.cs

ContentControl과 뷰모델을 연결하는 Behavior로 ControlName을 입력하면, 해당 UserControl을 인스턴스 시켜서 ContentControl.Content 속성에 넣어서 화면에 출력합니다.

using Microsoft.Xaml.Behaviors;
using System;
using System.Windows;
using System.Windows.Controls;

namespace WpfFramework.Behaviors
{
    /// <summary>
    /// ContentControlBehavior
    /// </summary>
    public class ContentControlBehavior : Behavior<ContentControl>
    {
        public string ControlName
        {
            get { return (string)GetValue(ControlNameProperty); }
            set { SetValue(ControlNameProperty, value); }
        }

        /// <summary>
        /// ControlName DP
        /// </summary>
        public static readonly DependencyProperty ControlNameProperty =
            DependencyProperty.Register(nameof(ControlName), typeof(string), typeof(ContentControlBehavior), new PropertyMetadata(null, ControlNameChanged));

        private static void ControlNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = (ContentControlBehavior)d;
            behavior.ResolveControl();
        }

        /// <summary>
        /// ControlName을 이용해서 컨트롤 인스턴스 시켜서 사용
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        private void ResolveControl()
        {
            if (string.IsNullOrEmpty(ControlName))
            {
                AssociatedObject.Content = null;
            }
            else
            {
                //GetType을 이용하기 위해서 AssemblyQualifiedName이 필요합니다.
                //예) typeof(AboutControl).AssemblyQualifiedName
                //다른 클래스라이브러리에 있는 컨트롤도 이름만 알면 만들 수 있습니다.
                //프로젝트 이름이 WpfFramework가 아니라면 아래 WpfFramework를 프로젝트명으로 수정하셔야 합니다.
                var type = Type.GetType($"WpfFramework.Controls.{ControlName}, WpfFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
                if (type == null)
                {
                    return;
                }
                var control = App.Current.Services.GetService(type);
                AssociatedObject.Content = control;
            }
        }

        public bool ShowLayerPopup
        {
            get { return (bool)GetValue(ShowLayerPopupProperty); }
            set { SetValue(ShowLayerPopupProperty, value); }
        }

        /// <summary>
        /// ShowLayerPopup DP
        /// </summary>
        public static readonly DependencyProperty ShowLayerPopupProperty =
            DependencyProperty.Register(nameof(ShowLayerPopup), typeof(bool), typeof(ContentControlBehavior), new PropertyMetadata(false, ShowLayerPopupChanged));

        private static void ShowLayerPopupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var behavior = (ContentControlBehavior)d;
            behavior.CheckShowLayerPopup();
        }
        /// <summary>
        /// ShowLayerPopup 속성 확인 후 false이면 ContentControl에 연결되어 있던 인스턴스 연결 삭제
        /// </summary>
        private void CheckShowLayerPopup()
        {
            if (ShowLayerPopup == false)
            {
                AssociatedObject.Content = null;
            }
        }
    }
}

3. AboutControl.xaml

<UserControl x:Class="WpfFramework.Controls.AboutControl"
             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:WpfFramework.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="250" d:DesignWidth="400">
    <Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <StackPanel Margin="5">
                <TextBlock Text="Microsoft Toolkit Mvvm Simple Framework"
                           FontSize="25" TextWrapping="Wrap"/>
                <TextBlock FontSize="15" Margin="0,10,0,0">
                    <Run Text="Create :"/>
                    <Run Text="kaki104"/>
                </TextBlock>
            </StackPanel>
            <Button Content="O_K" Grid.Row="1" HorizontalAlignment="Right"
                    Width="80" Margin="5"
                    Click="Button_Click"/>
        </Grid>
    </Border>
</UserControl>

4. AboutControl.xaml.cs

일반적으로는 코드 비하인드(Code Behind)에 코딩을 하지 않지만, 아래 몇가지 경우에는 사용하고 있습니다.

 

제가 생각하는 코드 비하인드에 코딩을 하는 경우

  • Page는 무조건 ViewModel을 만들어 놓습니다, UserControl은 경우에 따라 다르게 처리합니다. Custom Control은 무조건 코드 비하인드에 코딩 합니다.
  • 화면에 사용자 인터렉션이 복잡하지 않고, 단순하게 처리가 가능한 경우 코드 비하인드를 사용합니다.
  • 다른 화면에 올려놓고 사용하는 UserControl : Page의 ViewModel을 사용해야하는데, UserControl 자체적으로 ViewModel을 가지고 있으면, DataContext에 UserControl의 ViewModel이 들어가기 때문에 사용상 문제가 발생할 수 있습니다. 예) TitleControl 같이 Page 상단에 올려놓고 사용하는 UserControl
주의 사항 : 코드 비하인드에 코딩시 컨트롤의 이벤트 핸들러를 컨트롤 생성자에서 추가해서 사용하는 방법은 추천하지 않습니다. 추천 방법은 xaml에서 이벤트 핸들러를 추가하고, 코드 비하인드에 코딩하면, 메모리 누수 현상 방지에 도움이 됩니다.
using Microsoft.Toolkit.Mvvm.Messaging;
using System.Windows;
using System.Windows.Controls;
using WpfFramework.Models;

namespace WpfFramework.Controls
{
    /// <summary>
    /// Interaction logic for AboutControl.xaml
    /// </summary>
    public partial class AboutControl : UserControl
    {
        public AboutControl()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            WeakReferenceMessenger.Default.Send(new LayerPopupMessage(false));
        }
    }
}

5. LayerPopupMessage.cs

레이어 팝업 메시지를 추가합니다.

using Microsoft.Toolkit.Mvvm.Messaging.Messages;

namespace WpfFramework.Models
{
    /// <summary>
    /// 레이어 팝업 메시지
    /// </summary>
    public class LayerPopupMessage : ValueChangedMessage<bool>
    {
        /// <summary>
        /// 컨트롤 이름
        /// </summary>
        public string ControlName { get; set; }
        /// <summary>
        /// 컨트롤에 전달할 파라메터
        /// </summary>
        public object Parameter { get; set; } = null;
        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="value">true : 레이어팝업 오픈, false : 레이어 팝업 닫기</param>
        public LayerPopupMessage(bool value) : base(value)
        {
        }
    }
}

6. MainViewModel.cs

LayerPopup을 위한 프로퍼티와 message 수신 처리 부분을 등록합니다.

        private bool _showLayerPopup;
        /// <summary>
        /// 레이어 팝업 출력여부
        /// </summary>
        public bool ShowLayerPopup
        {
            get { return _showLayerPopup; }
            set { SetProperty(ref _showLayerPopup, value); }
        }
        
        private string _controlName;
        /// <summary>
        /// 레이어 팝업 내부 컨트롤 이름
        /// </summary>
        public string ControlName
        {
            get { return _controlName; }
            set { SetProperty(ref _controlName, value); }
        }
...
            //LayerPopupMessage 수신 등록
            WeakReferenceMessenger.Default.Register<LayerPopupMessage>(this, OnLayerPopupMessage);
...
        private void OnLayerPopupMessage(object recipient, LayerPopupMessage message)
        {
            ShowLayerPopup = message.Value;
            ControlName = message.ControlName;
        }

7. App.xaml.cs

Resolve를 하기 위해서는 services에 미리 등록해야합니다.

            //Control 등록
            services.AddTransient(typeof(AboutControl));

8. 테스트 코드

HomeViewModel.cs

버튼을 누르면 LayerPopupMessage를 이용해서 레이어 팝업을 출력합니다.

        private void Init()
        {
            BusyTestCommand = new AsyncRelayCommand(OnBusyTestAsync);
            LayerPopupTestCommand = new RelayCommand(OnLayerPopupTest);
        }

        private void OnLayerPopupTest()
        {
            WeakReferenceMessenger.Default.Send(new LayerPopupMessage(true) { ControlName = "AboutControl" });
        }

9. 실행 결과

실행 후 Layer Popup Test 버튼 클릭

10. 소스

프레임워크에 추가할 기능이 있으면 리플로 요청하시면 구현하도록 하겠습니다.

part4/add-layerpopup

kaki104/WpfFramework at part4/add-layerpopup (github.com)

 

GitHub - kaki104/WpfFramework

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

github.com

 

반응형
댓글