728x90
반응형
728x170
■ Popup 클래스를 사용해 다른 엘리먼트 위에 엘리먼트를 오버레이 배치하는 방법을 보여준다.
▶ 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
반응형
그리드형(광고전용)
'C# > WPF' 카테고리의 다른 글
[C#/WPF] Application 클래스 : GetContentStream 정적 메소드를 사용해 컨텐트 XAML 페이지 로드하기 (0) | 2022.01.02 |
---|---|
[C#/WPF] Frame 엘리먼트 : Source 속성을 사용해 리소스 XAML 페이지 로드하기 (0) | 2022.01.02 |
[C#/WPF] Frame 클래스 : Source 속성을 사용해 리소스 XAML 페이지 로드하기 (0) | 2022.01.02 |
[C#/WPF] Application 클래스 : GetResourceStream 정적 메소드를 사용해 리소스 XAML 페이지 로드하기 (0) | 2022.01.02 |
[C#/WPF] VirtualizingStackPanel 클래스 : IsVirtualizing/VirtualizationMode 속성을 사용해 대용량 데이터 바인딩하기 (0) | 2021.12.31 |
[C#/WPF] HwndHost 클래스 : WPF 윈도우 내부에서 메모장 윈도우 호스팅하기 (0) | 2021.12.31 |
[C#/WPF] 3D 애니메이션 시계 사용하기 (절전 모드 방지 기능 추가) (0) | 2021.11.17 |
[C#/WPF] DataGrid 클래스 : 데이터 업데이트 성능 측정하기 (0) | 2021.10.22 |
[C#/WPF] DataGrid 클래스 : 대용량 데이터 바인딩하기 (0) | 2021.10.03 |
[C#/WPF/.NETCORE] FaceDetector 클래스 : DetectFacesAsync 메소드를 사용해 얼굴 탐지하기 (0) | 2021.09.23 |
댓글을 달아 주세요