Files
icarus/3dPrintApp/AirspacePopup.vb
T
2022-06-10 15:53:41 +02:00

276 lines
12 KiB
VB.net

' Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
'
' Step 1a) Using this custom control in a XAML file that exists in the current project.
' Add this XmlNamespace attribute to the root element of the markup file where it is
' to be used:
'
' xmlns:MyNamespace="clr-namespace:_3dPrintApp"
'
'
' Step 1b) Using this custom control in a XAML file that exists in a different project.
' Add this XmlNamespace attribute to the root element of the markup file where it is
' to be used:
'
' xmlns:MyNamespace="clr-namespace:_3dPrintApp;assembly=_3dPrintApp"
'
' You will also need to add a project reference from the project where the XAML file lives
' to this project and Rebuild to avoid compilation errors:
'
' Right click on the target project in the Solution Explorer and
' "Add Reference"->"Projects"->[Browse to and select this project]
'
'
' Step 2)
' Go ahead and use your control in the XAML file. Note that Intellisense in the
' XML editor does not currently work on custom controls and its child elements.
'
' <MyNamespace:AirspacePopup/>
'
Imports System.Windows.Controls.Primitives
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports System.Windows
Imports System.Windows.Input
Imports System.Windows.Interop
Public Class AirspacePopup
Inherits Popup
Public Shared ReadOnly IsTopmostProperty As DependencyProperty = DependencyProperty.Register("IsTopmost", GetType(Boolean), GetType(AirspacePopup), New FrameworkPropertyMetadata(False, AddressOf OnIsTopmostChanged))
Public Shared ReadOnly FollowPlacementTargetProperty As DependencyProperty = DependencyProperty.RegisterAttached("FollowPlacementTarget", GetType(Boolean), GetType(AirspacePopup), New UIPropertyMetadata(False))
Public Shared ReadOnly AllowOutsideScreenPlacementProperty As DependencyProperty = DependencyProperty.RegisterAttached("AllowOutsideScreenPlacement", GetType(Boolean), GetType(AirspacePopup), New UIPropertyMetadata(False))
Public Shared ReadOnly ParentWindowProperty As DependencyProperty = DependencyProperty.RegisterAttached("ParentWindow", GetType(Window), GetType(AirspacePopup), New UIPropertyMetadata(Nothing, AddressOf ParentWindowPropertyChanged))
Private Shared Sub OnIsTopmostChanged(ByVal source As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim airspacePopup As AirspacePopup = TryCast(source, AirspacePopup)
airspacePopup.SetTopmostState(airspacePopup.IsTopmost)
End Sub
Private Shared Sub ParentWindowPropertyChanged(ByVal source As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim airspacePopup As AirspacePopup = TryCast(source, AirspacePopup)
airspacePopup.ParentWindowChanged()
End Sub
Private m_appliedTopMost As Boolean?
Private m_alreadyLoaded As Boolean
Private m_parentWindow As Window
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(AirspacePopup), New FrameworkPropertyMetadata(GetType(AirspacePopup)))
End Sub
Public Sub New()
AddHandler Loaded, AddressOf OnPopupLoaded
AddHandler Unloaded, AddressOf OnPopupUnloaded
Dim descriptor As DependencyPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(PlacementTargetProperty, GetType(AirspacePopup))
descriptor.AddValueChanged(Me, AddressOf PlacementTargetChanged)
End Sub
Public Property IsTopmost As Boolean
Get
Return CBool(GetValue(IsTopmostProperty))
End Get
Set(ByVal value As Boolean)
SetValue(IsTopmostProperty, value)
End Set
End Property
Public Property FollowPlacementTarget As Boolean
Get
Return CBool(GetValue(FollowPlacementTargetProperty))
End Get
Set(ByVal value As Boolean)
SetValue(FollowPlacementTargetProperty, value)
End Set
End Property
Public Property AllowOutsideScreenPlacement As Boolean
Get
Return CBool(GetValue(AllowOutsideScreenPlacementProperty))
End Get
Set(ByVal value As Boolean)
SetValue(AllowOutsideScreenPlacementProperty, value)
End Set
End Property
Public Property ParentWindow As Window
Get
Return CType(GetValue(ParentWindowProperty), Window)
End Get
Set(ByVal value As Window)
SetValue(ParentWindowProperty, value)
End Set
End Property
Private Sub ParentWindowChanged()
If ParentWindow IsNot Nothing Then
AddHandler ParentWindow.LocationChanged, Function(sender, e2)
UpdatePopupPosition()
End Function
AddHandler ParentWindow.SizeChanged, Function(sender, e2)
UpdatePopupPosition()
End Function
End If
End Sub
Private Sub PlacementTargetChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim placementTarget As FrameworkElement = TryCast(Me.PlacementTarget, FrameworkElement)
If placementTarget IsNot Nothing Then
AddHandler placementTarget.SizeChanged, Function(sender2, e2)
UpdatePopupPosition()
End Function
End If
End Sub
Private Sub UpdatePopupPosition()
Dim placementTarget As FrameworkElement = TryCast(Me.PlacementTarget, FrameworkElement)
Dim child As FrameworkElement = TryCast(Me.Child, FrameworkElement)
If PresentationSource.FromVisual(placementTarget) IsNot Nothing AndAlso AllowOutsideScreenPlacement = True Then
Dim leftOffset As Double = CutLeft(placementTarget)
Dim topOffset As Double = CutTop(placementTarget)
Dim rightOffset As Double = CutRight(placementTarget)
Dim bottomOffset As Double = CutBottom(placementTarget)
Debug.WriteLine(bottomOffset)
Me.Width = Math.Max(0, Math.Min(leftOffset, rightOffset) + placementTarget.ActualWidth)
Me.Height = Math.Max(0, Math.Min(topOffset, bottomOffset) + placementTarget.ActualHeight)
If child IsNot Nothing Then
child.Margin = New Thickness(leftOffset, topOffset, rightOffset, bottomOffset)
End If
End If
If FollowPlacementTarget = True Then
Me.HorizontalOffset += 0.01
Me.HorizontalOffset -= 0.01
End If
End Sub
Private Function CutLeft(ByVal placementTarget As FrameworkElement) As Double
Dim point As Point = placementTarget.PointToScreen(New Point(0, placementTarget.ActualWidth))
Return Math.Min(0, point.X)
End Function
Private Function CutTop(ByVal placementTarget As FrameworkElement) As Double
Dim point As Point = placementTarget.PointToScreen(New Point(placementTarget.ActualHeight, 0))
Return Math.Min(0, point.Y)
End Function
Private Function CutRight(ByVal placementTarget As FrameworkElement) As Double
Dim point As Point = placementTarget.PointToScreen(New Point(0, placementTarget.ActualWidth))
point.X += placementTarget.ActualWidth
Return Math.Min(0, SystemParameters.VirtualScreenWidth - (Math.Max(SystemParameters.VirtualScreenWidth, point.X)))
End Function
Private Function CutBottom(ByVal placementTarget As FrameworkElement) As Double
Dim point As Point = placementTarget.PointToScreen(New Point(placementTarget.ActualHeight, 0))
point.Y += placementTarget.ActualHeight
Return Math.Min(0, SystemParameters.VirtualScreenHeight - (Math.Max(SystemParameters.VirtualScreenHeight, point.Y)))
End Function
Private Sub OnPopupLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
If m_alreadyLoaded Then Return
m_alreadyLoaded = True
If Child IsNot Nothing Then
Child.[AddHandler](PreviewMouseLeftButtonDownEvent, New MouseButtonEventHandler(AddressOf OnChildPreviewMouseLeftButtonDown), True)
End If
m_parentWindow = Window.GetWindow(Me)
If m_parentWindow Is Nothing Then Return
AddHandler m_parentWindow.Activated, AddressOf OnParentWindowActivated
AddHandler m_parentWindow.Deactivated, AddressOf OnParentWindowDeactivated
End Sub
Private Sub OnPopupUnloaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
If m_parentWindow Is Nothing Then Return
RemoveHandler m_parentWindow.Activated, AddressOf OnParentWindowActivated
RemoveHandler m_parentWindow.Deactivated, AddressOf OnParentWindowDeactivated
End Sub
Private Sub OnParentWindowActivated(ByVal sender As Object, ByVal e As EventArgs)
SetTopmostState(True)
End Sub
Private Sub OnParentWindowDeactivated(ByVal sender As Object, ByVal e As EventArgs)
If IsTopmost = False Then
SetTopmostState(IsTopmost)
End If
End Sub
Private Sub OnChildPreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
SetTopmostState(True)
If Not m_parentWindow.IsActive AndAlso IsTopmost = False Then
m_parentWindow.Activate()
End If
End Sub
Protected Overrides Sub OnOpened(ByVal e As EventArgs)
SetTopmostState(IsTopmost)
MyBase.OnOpened(e)
End Sub
Private Sub SetTopmostState(ByVal isTop As Boolean)
If m_appliedTopMost.HasValue AndAlso m_appliedTopMost = isTop Then
Return
End If
If Child Is Nothing Then Return
Dim hwndSource = TryCast((PresentationSource.FromVisual(Child)), HwndSource)
If hwndSource Is Nothing Then Return
Dim hwnd = hwndSource.Handle
Dim rect As RECT
If Not GetWindowRect(hwnd, rect) Then Return
Debug.WriteLine("setting z-order " & isTop)
If isTop Then
SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, CInt(Width), CInt(Height), TOPMOST_FLAGS)
Else
SetWindowPos(hwnd, HWND_BOTTOM, rect.Left, rect.Top, CInt(Width), CInt(Height), TOPMOST_FLAGS)
SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, CInt(Width), CInt(Height), TOPMOST_FLAGS)
SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, CInt(Width), CInt(Height), TOPMOST_FLAGS)
End If
m_appliedTopMost = isTop
End Sub
<StructLayout(LayoutKind.Sequential)>
Public Structure RECT
Public Left As Integer
Public Top As Integer
Public Right As Integer
Public Bottom As Integer
End Structure
<DllImport("user32.dll")>
Private Shared Function GetWindowRect(ByVal hWnd As IntPtr, <Out> ByRef lpRect As RECT) As Boolean
End Function
<DllImport("user32.dll")>
Private Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As UInteger) As Boolean
End Function
Shared ReadOnly HWND_TOPMOST As IntPtr = New IntPtr(-1)
Shared ReadOnly HWND_NOTOPMOST As IntPtr = New IntPtr(-2)
Shared ReadOnly HWND_TOP As IntPtr = New IntPtr(0)
Shared ReadOnly HWND_BOTTOM As IntPtr = New IntPtr(1)
Private Const SWP_NOSIZE As UInt32 = &H1
Const SWP_NOMOVE As UInt32 = &H2
Const SWP_NOZORDER As UInt32 = &H4
Const SWP_NOREDRAW As UInt32 = &H8
Const SWP_NOACTIVATE As UInt32 = &H10
Const SWP_FRAMECHANGED As UInt32 = &H20
Const SWP_SHOWWINDOW As UInt32 = &H40
Const SWP_HIDEWINDOW As UInt32 = &H80
Const SWP_NOCOPYBITS As UInt32 = &H100
Const SWP_NOOWNERZORDER As UInt32 = &H200
Const SWP_NOSENDCHANGING As UInt32 = &H400
Const TOPMOST_FLAGS As UInt32 = SWP_NOACTIVATE Or SWP_NOOWNERZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_NOREDRAW Or SWP_NOSENDCHANGING
End Class