첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

■ Popup 클래스를 사용해 다른 엘리먼트 위에 엘리먼트를 오버레이 배치하는 방법을 보여준다.

TestProject.zip
0.01MB

▶ AirspacePopup.cs

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;

namespace TestProject
{
    /// <summary>
    /// 에어스페이스 팝업
    /// </summary>
    public class AirspacePopup : Popup
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Structure
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 사각형 - RECT

        /// <summary>
        /// 사각형
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            /// <summary>
            /// 왼쪽
            /// </summary>
            public int Left;

            /// <summary>
            /// 위쪽
            /// </summary>
            public int Top;

            /// <summary>
            /// 오른쪽
            /// </summary>
            public int Right;

            /// <summary>
            /// 아래쪽
            /// </summary>
            public int Bottom;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Import
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 윈도우 사각형 구하기 - GetWindowRect(windowHandle, rectangle)

        /// <summary>
        /// 윈도우 사각형 구하기
        /// </summary>
        /// <param name="windowHandle">윈도우 핸들</param>
        /// <param name="rectangle">사각형</param>
        /// <returns>처리 결과</returns>
        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetWindowRect(IntPtr windowHandle, out RECT rectangle);

        #endregion
        #region 윈도우 위치 설정하기 - SetWindowPos(windowHandle, insertAfterWindowHandle, x, y, width, height, flag)

        /// <summary>
        /// 윈도우 위치 설정하기
        /// </summary>
        /// <param name="windowHandle">윈도우 핸들</param>
        /// <param name="insertAfterWindowHandle">삽입 이후 윈도우 핸들</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="width">너비</param>
        /// <param name="height">높이</param>
        /// <param name="flag">플래그</param>
        /// <returns>처리 결과</returns>
        [DllImport("user32.dll")]
        private static extern bool SetWindowPos(IntPtr windowHandle, IntPtr insertAfterWindowHandle, int x, int y, int width, int height, uint flag);

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 최상위 여부 속성 - IsTopmostProperty

        /// <summary>
        /// 최상위 여부 속성
        /// </summary>
        public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register
        (
            "IsTopmost",
            typeof(bool),
            typeof(AirspacePopup),
            new FrameworkPropertyMetadata(false, IsTopmostPropertyChangedCallback)
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Attached Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 배치 대상 동행 여부 속성 - FollowPlacementTargetProperty

        /// <summary>
        /// 배치 대상 동행 여부 속성
        /// </summary>
        public static readonly DependencyProperty FollowPlacementTargetProperty = DependencyProperty.RegisterAttached
        (
            "FollowPlacementTarget",
            typeof(bool),
            typeof(AirspacePopup),
            new UIPropertyMetadata(false)
        );

        #endregion
        #region 외부 화면 배치 허용 여부 속성 - AllowOutsideScreenPlacementProperty

        /// <summary>
        /// 외부 화면 배치 허용 여부 속성
        /// </summary>
        public static readonly DependencyProperty AllowOutsideScreenPlacementProperty = DependencyProperty.RegisterAttached
        (
            "AllowOutsideScreenPlacement",
            typeof(bool),
            typeof(AirspacePopup),
            new UIPropertyMetadata(false)
        );

        #endregion
        #region 부모 윈도우 속성 - ParentWindowProperty

        /// <summary>
        /// 부모 윈도우 속성
        /// </summary>
        public static readonly DependencyProperty ParentWindowProperty = DependencyProperty.RegisterAttached
        (
            "ParentWindow",
            typeof(Window),
            typeof(AirspacePopup),
            new UIPropertyMetadata(null, ParentWindowPropertyChangedCallback)
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 최상위 윈도우 핸들
        /// </summary>
        private static readonly IntPtr _topMostWindowHandle = new IntPtr(-1);

        /// <summary>
        /// 비 최상위 윈도우 핸들
        /// </summary>
        private static readonly IntPtr _noTopMostWindowHandle = new IntPtr(-2);

        /// <summary>
        /// 상위 윈도우 핸들
        /// </summary>
        private static readonly IntPtr _topWindowHandle = new IntPtr(0);

        /// <summary>
        /// 하위 윈도우 핸들
        /// </summary>
        private static readonly IntPtr _bottomWindowHandle = new IntPtr(1);

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// SWP_NOSIZE
        /// </summary>
        private const uint SWP_NOSIZE = 0x0001;

        /// <summary>
        /// SWP_NOMOVE
        /// </summary>
        private const uint SWP_NOMOVE = 0x0002;

        /// <summary>
        /// SWP_NOZORDER
        /// </summary>
        private const uint SWP_NOZORDER = 0x0004;

        /// <summary>
        /// SWP_NOREDRAW
        /// </summary>
        private const uint SWP_NOREDRAW = 0x0008;

        /// <summary>
        /// SWP_NOACTIVATE
        /// </summary>
        private const uint SWP_NOACTIVATE = 0x0010;

        /// <summary>
        /// SWP_FRAMECHANGED
        /// </summary>
        private const uint SWP_FRAMECHANGED = 0x0020;

        /// <summary>
        /// SWP_SHOWWINDOW
        /// </summary>
        private const uint SWP_SHOWWINDOW = 0x0040;

        /// <summary>
        /// SWP_HIDEWINDOW
        /// </summary>
        private const uint SWP_HIDEWINDOW = 0x0080;

        /// <summary>
        /// SWP_NOCOPYBITS
        /// </summary>
        private const uint SWP_NOCOPYBITS = 0x0100;

        /// <summary>
        /// SWP_NOOWNERZORDER
        /// </summary>
        private const uint SWP_NOOWNERZORDER = 0x0200;

        /// <summary>
        /// SWP_NOSENDCHANGING
        /// </summary>
        private const uint SWP_NOSENDCHANGING = 0x0400;

        /// <summary>
        /// TOPMOST_FLAGS
        /// </summary>
        private const uint TOPMOST_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;

        /// <summary>
        /// 최상위 적용 여부
        /// </summary>
        private bool? topMostApplied;

        /// <summary>
        /// 기 로드 여부
        /// </summary>
        private bool loaded;

        /// <summary>
        /// 부모 윈도우
        /// </summary>
        private Window parentWindow;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 최상위 여부 - IsTopmost

        /// <summary>
        /// 최상위 여부
        /// </summary>
        public bool IsTopmost
        {
            get
            {
                return (bool)GetValue(IsTopmostProperty);
            }
            set
            {
                SetValue(IsTopmostProperty, value);
            }
        }

        #endregion

        #region 배치 대상 동행 여부 - FollowPlacementTarget

        /// <summary>
        /// 배치 대상 동행 여부
        /// </summary>
        public bool FollowPlacementTarget
        {
            get
            {
                return (bool)GetValue(FollowPlacementTargetProperty);
            }
            set
            {
                SetValue(FollowPlacementTargetProperty, value);
            }
        }

        #endregion
        #region 외부 화면 배치 허용 여부 - AllowOutsideScreenPlacement

        /// <summary>
        /// 외부 화면 배치 허용 여부
        /// </summary>
        public bool AllowOutsideScreenPlacement
        {
            get
            {
                return (bool)GetValue(AllowOutsideScreenPlacementProperty);
            }
            set
            {
                SetValue(AllowOutsideScreenPlacementProperty, value);
            }
        }

        #endregion
        #region 부모 윈도우 - ParentWindow

        /// <summary>
        /// 부모 윈도우
        /// </summary>
        public Window ParentWindow
        {
            get
            {
                return (Window)GetValue(ParentWindowProperty);
            }
            set
            {
                SetValue(ParentWindowProperty, value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - AirspacePopup()

        /// <summary>
        /// 생성자
        /// </summary>
        public AirspacePopup()
        {
            Loaded   += Popup_Loaded;
            Unloaded += Popup_Unloaded;

            DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty
            (
                PlacementTargetProperty,
                typeof(AirspacePopup)
            );

            descriptor.AddValueChanged(this, PlacementTargetPropertyChangedCallback);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 최상위 여부 속성 변경시 콜백 처리하기 - IsTopmostPropertyChangedCallback(d, e)

        /// <summary>
        /// 최상위 여부 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void IsTopmostPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            AirspacePopup airspacePopup = d as AirspacePopup;

            airspacePopup.SetTopMostState(airspacePopup.IsTopmost);
        }

        #endregion
        #region 부모 윈도우 속성 변경시 콜백 처리하기 - ParentWindowPropertyChangedCallback(d, e)

        /// <summary>
        /// 부모 윈도우 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void ParentWindowPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            AirspacePopup airspacePopup = d as AirspacePopup;

            airspacePopup.ProcessParentWindowChanged();
        }

        #endregion
        #region 배치 대상 속성 변경시 콜백 처리하기 - PlacementTargetPropertyChangedCallback(sender, e)

        /// <summary>
        /// 배치 대상 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void PlacementTargetPropertyChangedCallback(object sender, EventArgs e)
        {
            FrameworkElement placementTarget = PlacementTarget as FrameworkElement;

            if(placementTarget != null)
            {
                placementTarget.SizeChanged += (sender2, e2) =>
                {
                    UpdatePopupPosition();
                };
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Protected
        ////////////////////////////////////////////////////////////////////// Function

        #region 오픈시 처리하기 - OnOpened(e)

        /// <summary>
        /// 오픈시 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected override void OnOpened(EventArgs e)
        {
            SetTopMostState(IsTopmost);

            base.OnOpened(e);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private
        ////////////////////////////////////////////////////////////////////// Event

        #region 자식 PREVIEW 마우스 왼쪽 버튼 DOWN 처리하기 - Child_PreviewMouseLeftButtonDown(sender, e)

        /// <summary>
        /// 자식 PREVIEW 마우스 왼쪽 버튼 DOWN 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void Child_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            SetTopMostState(true);

            if(!this.parentWindow.IsActive && IsTopmost == false)
            {
                this.parentWindow.Activate();
            }
        }

        #endregion
        #region 부모 윈도우 활성화시 처리하기 - parentWindow_Activated(sender, e)

        /// <summary>
        /// 부모 윈도우 활성화시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void parentWindow_Activated(object sender, EventArgs e)
        {
            SetTopMostState(true);
        }

        #endregion
        #region 부모 윈도우 비활성화시 처리하기 - parentWindow_Deactivated(sender, e)

        /// <summary>
        /// 부모 윈도우 비활성화시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void parentWindow_Deactivated(object sender, EventArgs e)
        {
            if(IsTopmost == false)
            {
                SetTopMostState(IsTopmost);
            }
        }

        #endregion
        #region 팝업 로드시 처리하기 - Popup_Loaded(sender, e)

        /// <summary>
        /// 팝업 로드시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void Popup_Loaded(object sender, RoutedEventArgs e)
        {
            if(this.loaded)
            {
                return;
            }

            this.loaded = true;

            if(Child != null)
            {
                Child.AddHandler
                (
                    PreviewMouseLeftButtonDownEvent,
                    new MouseButtonEventHandler(Child_PreviewMouseLeftButtonDown),
                    true
                );
            }

            this.parentWindow = Window.GetWindow(this);

            if(this.parentWindow == null)
            {
                return;
            }

            this.parentWindow.Activated   += parentWindow_Activated;
            this.parentWindow.Deactivated += parentWindow_Deactivated;
        }

        #endregion
        #region 팝업 언로드시 처리하기 - Popup_Unloaded(sender, e)

        /// <summary>
        /// 팝업 언로드시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void Popup_Unloaded(object sender, RoutedEventArgs e)
        {
            if(this.parentWindow == null)
            {
                return;
            }

            this.parentWindow.Activated   -= parentWindow_Activated;
            this.parentWindow.Deactivated -= parentWindow_Deactivated;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////// Function

        #region 왼쪽 잘라내기 - CutLeft(placementTarget)

        /// <summary>
        /// 왼쪽 잘라내기
        /// </summary>
        /// <param name="placementTarget">배치 대상</param>
        /// <returns>왼쪽</returns>
        private double CutLeft(FrameworkElement placementTarget)
        {
            Point point = placementTarget.PointToScreen(new Point(0, placementTarget.ActualWidth));

            return Math.Min(0, point.X);
        }

        #endregion
        #region 위쪽 잘라내기 - CutTop(placementTarget)

        /// <summary>
        /// 위쪽 잘라내기
        /// </summary>
        /// <param name="placementTarget">배치 대상</param>
        /// <returns>위쪽</returns>
        private double CutTop(FrameworkElement placementTarget)
        {
            Point point = placementTarget.PointToScreen(new Point(placementTarget.ActualHeight, 0));

            return Math.Min(0, point.Y);
        }

        #endregion
        #region 오른쪽 잘라내기 - CutRight(placementTarget)

        /// <summary>
        /// 오른쪽 잘라내기
        /// </summary>
        /// <param name="placementTarget">배치 대상</param>
        /// <returns>오른쪽</returns>
        private double CutRight(FrameworkElement placementTarget)
        {
            Point point = placementTarget.PointToScreen(new Point(0, placementTarget.ActualWidth));

            point.X += placementTarget.ActualWidth;

            return Math.Min
            (
                0,
                SystemParameters.VirtualScreenWidth - (Math.Max(SystemParameters.VirtualScreenWidth, point.X))
            );
        }

        #endregion
        #region 아래쪽 잘라내기 - CutBottom(placementTarget)

        /// <summary>
        /// 아래쪽 잘라내기
        /// </summary>
        /// <param name="placementTarget">배치 대상</param>
        /// <returns>아래쪽</returns>
        private double CutBottom(FrameworkElement placementTarget)
        {
            Point point = placementTarget.PointToScreen(new Point(placementTarget.ActualHeight, 0));

            point.Y += placementTarget.ActualHeight;

            return Math.Min
            (
                0,
                SystemParameters.VirtualScreenHeight - (Math.Max(SystemParameters.VirtualScreenHeight, point.Y))
            );
        }

        #endregion
        #region 팝업 위치 업데이트하기 - UpdatePopupPosition()

        /// <summary>
        /// 팝업 위치 업데이트하기
        /// </summary>
        private void UpdatePopupPosition()
        {
            FrameworkElement placementTarget = PlacementTarget as FrameworkElement;

            FrameworkElement child = Child as FrameworkElement;

            if(PresentationSource.FromVisual(placementTarget) != null && AllowOutsideScreenPlacement == true)
            {
                double leftOffset   = CutLeft(placementTarget);
                double topOffset    = CutTop(placementTarget);
                double rightOffset  = CutRight(placementTarget);
                double bottomOffset = CutBottom(placementTarget);

                Width  = Math.Max(0, Math.Min(leftOffset, rightOffset ) + placementTarget.ActualWidth );
                Height = Math.Max(0, Math.Min(topOffset , bottomOffset) + placementTarget.ActualHeight);

                if(child != null)
                {
                    child.Margin = new Thickness(leftOffset, topOffset, rightOffset, bottomOffset);
                }
            }

            if(FollowPlacementTarget == true)
            {
                HorizontalOffset += 0.01;
                HorizontalOffset -= 0.01;
            }
        }

        #endregion
        #region 부모 윈도우 변경시 처리하기 - ProcessParentWindowChanged()

        /// <summary>
        /// 부모 윈도우 변경시 처리하기
        /// </summary>
        private void ProcessParentWindowChanged()
        {
            if(ParentWindow != null)
            {
                ParentWindow.LocationChanged += (sender, e) =>
                {
                    UpdatePopupPosition();
                };

                ParentWindow.SizeChanged += (sender, e) =>
                {
                    UpdatePopupPosition();
                };
            }
        }

        #endregion
        #region 최상위 상태 설정하기 - SetTopMostState(isTopMost)

        /// <summary>
        /// 최상위 상태 설정하기
        /// </summary>
        /// <param name="isTopMost">최상위 여부</param>
        private void SetTopMostState(bool isTopMost)
        {
            if(this.topMostApplied.HasValue && this.topMostApplied == isTopMost)
            {
                return;
            }

            if(Child == null)
            {
                return;
            }

            HwndSource hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource;

            if(hwndSource == null)
            {
                return;
            }

            IntPtr windowHandle = hwndSource.Handle;

            RECT rectangle;

            if(!GetWindowRect(windowHandle, out rectangle))
            {
                return;
            }

            if(isTopMost)
            {
                SetWindowPos
                (
                    windowHandle,
                    _topMostWindowHandle,
                    rectangle.Left,
                    rectangle.Top,
                    (int)Width,
                    (int)Height,
                    TOPMOST_FLAGS
                );
            }
            else
            {
                SetWindowPos
                (
                    windowHandle,
                    _bottomWindowHandle,
                    rectangle.Left,
                    rectangle.Top,
                    (int)Width,
                    (int)Height,
                    TOPMOST_FLAGS
                );

                SetWindowPos
                (
                    windowHandle,
                    _topWindowHandle,
                    rectangle.Left,
                    rectangle.Top,
                    (int)Width,
                    (int)Height,
                    TOPMOST_FLAGS
                );

                SetWindowPos
                (
                    windowHandle,
                    _noTopMostWindowHandle,
                    rectangle.Left,
                    rectangle.Top,
                    (int)Width,
                    (int)Height,
                    TOPMOST_FLAGS
                );
            }

            this.topMostApplied = isTopMost;
        }

        #endregion
    }
}

 

▶ MainWindow.xaml

<Window x:Class="TestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestProject"
    Width="800"
    Height="600"
    Title="Popup 클래스 : 다른 엘리먼트 위에 엘리먼트를 오버레이 배치하기"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Grid>
        <local:AirspacePopup
            Width="{Binding ElementName=webBrowser, Path=ActualWidth}"
            Height="{Binding ElementName=webBrowser, Path=ActualHeight}"
            ParentWindow="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
            PlacementTarget="{Binding ElementName=webBrowser}"
            FollowPlacementTarget="True"
            AllowOutsideScreenPlacement="True"
            AllowsTransparency="True"
            Placement="Center"
            IsOpen="True">
            <Grid>
                <Ellipse
                    Margin="100"
                    Width="300"
                    Height="300"
                    Fill="RoyalBlue"
                    Opacity="0.5" />
            </Grid>
        </local:AirspacePopup>
        <WebBrowser Name="webBrowser" />
    </Grid>
</Window>

 

▶ MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace TestProject
{
    /// <summary>
    /// 메인 윈도우
    /// </summary>
    public partial class MainWindow : Window
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainWindow()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            this.webBrowser.Loaded += WebBrowser_Loaded;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 웹 브라우저 로드시 처리하기 - WebBrowser_Loaded(sender, e)

        /// <summary>
        /// 웹 브라우저 로드시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void WebBrowser_Loaded(object sender, RoutedEventArgs e)
        {
            WebBrowser webbrowser = sender as WebBrowser;

            webbrowser.Navigate("http://www.daum.net");
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요