276 lines
12 KiB
VB.net
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
|