private void SetRoundingRegion(NativeMethods.WINDOWPOS? wp) { const int MONITOR_DEFAULTTONEAREST = 0x00000002; // We're early - WPF hasn't necessarily updated the state of the window. // Need to query it ourselves. NativeMethods.WINDOWPLACEMENT wpl = new NativeMethods.WINDOWPLACEMENT(); NativeMethods.GetWindowPlacement(handle, wpl); if (wpl.showCmd == NativeMethods.SW_SHOWMAXIMIZED && IsDwmEnabled == true) { int left; int top; if (wp.HasValue) { left = wp.Value.x; top = wp.Value.y; } else { NativeMethods.Rect r = new NativeMethods.Rect(); NativeMethods.GetWindowRect(handle, ref r); left = r.Left; top = r.Top; } IntPtr hMon = NativeMethods.MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST); NativeMethods.MonitorInfo mi = new NativeMethods.MonitorInfo(); NativeMethods.GetMonitorInfo(hMon, mi); NativeMethods.Rect rcMax = mi.Work; // The location of maximized window takes into account the border that Windows was // going to remove, so we also need to consider it. rcMax.Left -= left; rcMax.Right -= left; rcMax.Top -= top; rcMax.Bottom -= top; IntPtr hrgn = IntPtr.Zero; try { hrgn = NativeMethods.CreateRectRgnIndirect(ref rcMax); NativeMethods.SetWindowRgn(handle, hrgn, NativeMethods.IsWindowVisible(handle)); hrgn = IntPtr.Zero; } finally { NativeMethods.DeleteObject(hrgn); } } else { Size windowSize; // Use the size if it's specified. if (null != wp && !IsFlagSet(wp.Value.flags, NativeMethods.SWP_NOSIZE)) { windowSize = new Size((double)wp.Value.cx, (double)wp.Value.cy); } else if (null != wp && (lastRoundingState == WindowState)) { return; } else { NativeMethods.Rect r = new NativeMethods.Rect(); NativeMethods.GetWindowRect(handle, ref r); Rect rect = new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); windowSize = rect.Size; } lastRoundingState = WindowState; IntPtr hrgn = IntPtr.Zero; try { double shortestDimension = Math.Min(windowSize.Width, windowSize.Height); double topLeftRadius = DpiHelper.LogicalPixelsToDevice(new Point(CornerRadius.TopLeft, 0)).X; topLeftRadius = Math.Min(topLeftRadius, shortestDimension / 2); if (IsUniform(CornerRadius)) { // RoundedRect HRGNs require an additional pixel of padding. hrgn = CreateRoundRectRgn(new Rect(windowSize), topLeftRadius); } else { // We need to combine HRGNs for each of the corners. // Create one for each quadrant, but let it overlap into the two adjacent ones // by the radius amount to ensure that there aren't corners etched into the middle // of the window. hrgn = CreateRoundRectRgn(new Rect(0, 0, windowSize.Width / 2 + topLeftRadius, windowSize.Height / 2 + topLeftRadius), topLeftRadius); double topRightRadius = DpiHelper.LogicalPixelsToDevice(new Point(CornerRadius.TopRight, 0)).X; topRightRadius = Math.Min(topRightRadius, shortestDimension / 2); Rect topRightRegionRect = new Rect(0, 0, windowSize.Width / 2 + topRightRadius, windowSize.Height / 2 + topRightRadius); topRightRegionRect.Offset(windowSize.Width / 2 - topRightRadius, 0); CreateAndCombineRoundRectRgn(hrgn, topRightRegionRect, topRightRadius); double bottomLeftRadius = /*DpiHelper.LogicalPixelsToDevice*/(new Point(CornerRadius.BottomLeft, 0)).X; bottomLeftRadius = Math.Min(bottomLeftRadius, shortestDimension / 2); Rect bottomLeftRegionRect = new Rect(0, 0, windowSize.Width / 2 + bottomLeftRadius, windowSize.Height / 2 + bottomLeftRadius); bottomLeftRegionRect.Offset(0, windowSize.Height / 2 - bottomLeftRadius); CreateAndCombineRoundRectRgn(hrgn, bottomLeftRegionRect, bottomLeftRadius); double bottomRightRadius = DpiHelper.LogicalPixelsToDevice(new Point(CornerRadius.BottomRight, 0)).X; bottomRightRadius = Math.Min(bottomRightRadius, shortestDimension / 2); Rect bottomRightRegionRect = new Rect(0, 0, windowSize.Width / 2 + bottomRightRadius, windowSize.Height / 2 + bottomRightRadius); bottomRightRegionRect.Offset(windowSize.Width / 2 - bottomRightRadius, windowSize.Height / 2 - bottomRightRadius); CreateAndCombineRoundRectRgn(hrgn, bottomRightRegionRect, bottomRightRadius); } NativeMethods.SetWindowRgn(handle, hrgn, NativeMethods.IsWindowVisible(handle)); hrgn = IntPtr.Zero; } finally { // Free the memory associated with the HRGN if it wasn't assigned to the HWND. NativeMethods.DeleteObject(hrgn); } } }
/// <summary> /// Implements custom placement for ribbon popup /// </summary> /// <param name="popupsize"></param> /// <param name="targetsize"></param> /// <param name="offset"></param> /// <returns></returns> CustomPopupPlacement[] CustomPopupPlacementMethod(Size popupsize, Size targetsize, Point offset) { if ((popup != null) && (SelectedTabItem != null)) { // Get current workarea Point tabItemPos = SelectedTabItem.PointToScreen(new Point(0, 0)); NativeMethods.Rect tabItemRect = new NativeMethods.Rect(); tabItemRect.Left = (int)tabItemPos.X; tabItemRect.Top = (int)tabItemPos.Y; tabItemRect.Right = (int)tabItemPos.X + (int)SelectedTabItem.ActualWidth; tabItemRect.Bottom = (int)tabItemPos.Y + (int)SelectedTabItem.ActualHeight; uint MONITOR_DEFAULTTONEAREST = 0x00000002; System.IntPtr monitor = NativeMethods.MonitorFromRect(ref tabItemRect, MONITOR_DEFAULTTONEAREST); if (monitor != System.IntPtr.Zero) { NativeMethods.MonitorInfo monitorInfo = new NativeMethods.MonitorInfo(); monitorInfo.Size = Marshal.SizeOf(monitorInfo); NativeMethods.GetMonitorInfo(monitor, monitorInfo); Point startPoint = PointToScreen(new Point(0, 0)); if (FlowDirection == FlowDirection.RightToLeft) startPoint.X -= ActualWidth; double inWindowRibbonWidth = monitorInfo.Work.Right - Math.Max(monitorInfo.Work.Left, startPoint.X); double actualWidth = ActualWidth; if (startPoint.X < monitorInfo.Work.Left) { actualWidth -= monitorInfo.Work.Left - startPoint.X; startPoint.X = monitorInfo.Work.Left; } // Set width popup.Width = Math.Min(actualWidth, inWindowRibbonWidth); return new CustomPopupPlacement[] { new CustomPopupPlacement(new Point(startPoint.X - tabItemPos.X, SelectedTabItem.ActualHeight-(popup.Child as FrameworkElement).Margin.Top), PopupPrimaryAxis.None), new CustomPopupPlacement(new Point(startPoint.X - tabItemPos.X, -(SelectedContent as ScrollViewer).ActualHeight-(popup.Child as FrameworkElement).Margin.Bottom), PopupPrimaryAxis.None), }; } } return null; }
private IntPtr DoHitTest(int msg, IntPtr wParam, IntPtr lParam, out bool handled) { IntPtr lRet = IntPtr.Zero; handled = false; // Give DWM a chance at this first. if (IsDwmEnabled && (Mouse.Captured == null)) { // If we're on Vista, give the DWM a chance to handle the message first. handled = NativeMethods.DwmDefWindowProc(handle, msg, wParam, lParam, ref lRet); } // Handle letting the system know if we consider the mouse to be in our effective non-client area. // If DWM already handled this by way of DwmDefWindowProc, then respect their call. if (IntPtr.Zero == lRet) { var mousePosScreen = new Point(NativeMethods.LowWord(lParam), NativeMethods.HiWord(lParam)); NativeMethods.Rect wndPosition = new NativeMethods.Rect(); NativeMethods.GetWindowRect(handle, ref wndPosition); Rect windowPosition = new Rect(wndPosition.Left, wndPosition.Top, wndPosition.Right - wndPosition.Left, wndPosition.Bottom - wndPosition.Top); int ht = HitTestNonClientArea( DpiHelper.DeviceRectToLogical(windowPosition), DpiHelper.DevicePixelsToLogical(mousePosScreen)); // Don't blindly respect HTCAPTION. // We want UIElements in the caption area to be actionable so run through a hittest first. if ((ht != NativeMethods.HTCLIENT) && (mainGrid != null) && mainGrid.IsLoaded) { /*int mp = lParam.ToInt32(); if (!mainGrid.IsVisible) return IntPtr.Zero; Point ptMouse = new Point((short)(mp & 0x0000FFFF), (short)((mp >> 16) & 0x0000FFFF)); //ptMouse = DpiHelper.DevicePixelsToLogical(ptMouse); ptMouse = mainGrid.PointFromScreen(ptMouse);*/ var ptMouse = mainGrid.PointFromScreen(mousePosScreen); /* Point mousePosWindow = mousePosScreen; mousePosWindow.Offset(-windowPosition.X, -windowPosition.Y);*/ IInputElement inputElement = mainGrid.InputHitTest(ptMouse); if (inputElement != null) { FrameworkElement frameworkElement = inputElement as FrameworkElement; if ((frameworkElement != null) && (frameworkElement.Name == "PART_TitleBar")) ht = NativeMethods.HTCAPTION; else if (inputElement != mainGrid) ht = NativeMethods.HTCLIENT; } } // Check resize grip ResizeGrip grip = (GetTemplateChild("PART_ResizeGrip") as ResizeGrip); if ((grip != null) && (grip.IsLoaded) && (grip.InputHitTest(grip.PointFromScreen(mousePosScreen)) != null)) { if (FlowDirection == FlowDirection.LeftToRight) ht = NativeMethods.HTBOTTOMRIGHT; else ht = NativeMethods.HTBOTTOMLEFT; } handled = true; lRet = new IntPtr(ht); } return lRet; }
private void FixFrameworkIssues() { // This margin is only necessary if the client rectangle is going to be calculated incorrectly by WPF version less then 4.0 if (FrameworkHelper.PresentationFrameworkVersion >= new Version("4.0")) { return; } if (Template == null) { // Nothing to fixup yet. This will get called again when a template does get set. return; } // Guard against the visual tree being empty. if (VisualTreeHelper.GetChildrenCount(this) == 0) { // The template isn't null, but we don't have a visual tree. // Hope that ApplyTemplate is in the queue and repost this, because there's not much we can do right now. Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (ThreadStart)FixFrameworkIssues); return; } var rootElement = (FrameworkElement)VisualTreeHelper.GetChild(this, 0); NativeMethods.Rect rcWindow = new NativeMethods.Rect(); NativeMethods.GetWindowRect(handle, ref rcWindow); NativeMethods.Rect rcAdjustedClient = GetAdjustedWindowRect(rcWindow); Rect rcLogicalWindow = DpiHelper.DeviceRectToLogical(new Rect(rcWindow.Left, rcWindow.Top, rcWindow.Right - rcWindow.Left, rcWindow.Bottom - rcWindow.Top)); Rect rcLogicalClient = DpiHelper.DeviceRectToLogical(new Rect(rcAdjustedClient.Left, rcAdjustedClient.Top, rcAdjustedClient.Right - rcAdjustedClient.Left, rcAdjustedClient.Bottom - rcAdjustedClient.Top)); Thickness nonClientThickness = new Thickness( rcLogicalWindow.Left - rcLogicalClient.Left, rcLogicalWindow.Top - rcLogicalClient.Top, rcLogicalClient.Right - rcLogicalWindow.Right, rcLogicalClient.Bottom - rcLogicalWindow.Bottom); rootElement.Margin = new Thickness( 0, 0, -(nonClientThickness.Left + nonClientThickness.Right), -(nonClientThickness.Top + nonClientThickness.Bottom)); // The negative thickness on the margin doesn't properly get applied in RTL layouts. // The width is right, but there is a black bar on the right. // To fix this we just add an additional RenderTransform to the root element. // This works fine, but if the window is dynamically changing its FlowDirection then this can have really bizarre side effects. // This will mostly work if the FlowDirection is dynamically changed, but there aren't many real scenarios that would call for // that so I'm not addressing the rest of the quirkiness. if (FlowDirection == FlowDirection.RightToLeft) { rootElement.RenderTransform = new MatrixTransform(1, 0, 0, 1, -(nonClientThickness.Left + nonClientThickness.Right), 0); } else { rootElement.RenderTransform = null; } if (!isFixedUp) { hasUserMovedWindow = false; StateChanged += FixRestoreBounds; isFixedUp = true; } }
/// <summary> /// Returns screen workarea in witch control is placed /// </summary> /// <param name="control">Control</param> /// <returns>Workarea in witch control is placed</returns> public static Rect GetControlWorkArea(FrameworkElement control) { Point tabItemPos = control.PointToScreen(new Point(0, 0)); NativeMethods.Rect tabItemRect = new NativeMethods.Rect(); tabItemRect.Left = (int)tabItemPos.X; tabItemRect.Top = (int)tabItemPos.Y; tabItemRect.Right = (int)tabItemPos.X + (int)control.ActualWidth; tabItemRect.Bottom = (int)tabItemPos.Y + (int)control.ActualHeight; uint MONITOR_DEFAULTTONEAREST = 0x00000002; System.IntPtr monitor = NativeMethods.MonitorFromRect(ref tabItemRect, MONITOR_DEFAULTTONEAREST); if (monitor != System.IntPtr.Zero) { NativeMethods.MonitorInfo monitorInfo = new NativeMethods.MonitorInfo(); monitorInfo.Size = Marshal.SizeOf(monitorInfo); NativeMethods.GetMonitorInfo(monitor, monitorInfo); return new Rect(monitorInfo.Work.Left, monitorInfo.Work.Top, monitorInfo.Work.Right - monitorInfo.Work.Left, monitorInfo.Work.Bottom - monitorInfo.Work.Top); } return new Rect(); }
/// <summary> /// Implements custom placement for ribbon popup /// </summary> /// <param name="popupsize"></param> /// <param name="targetsize"></param> /// <param name="offset"></param> /// <returns></returns> private CustomPopupPlacement[] CustomPopupPlacementMethod(Size popupsize, Size targetsize, Point offset) { if (this.DropDownPopup != null && this.SelectedTabItem != null) { // Get current workarea var tabItemPos = this.SelectedTabItem.PointToScreen(new Point(0, 0)); var tabItemRect = new NativeMethods.Rect { Left = (int)tabItemPos.X, Top = (int)tabItemPos.Y, Right = (int)tabItemPos.X + (int)this.SelectedTabItem.ActualWidth, Bottom = (int)tabItemPos.Y + (int)this.SelectedTabItem.ActualHeight }; const uint MONITOR_DEFAULTTONEAREST = 0x00000002; var monitor = NativeMethods.MonitorFromRect(ref tabItemRect, MONITOR_DEFAULTTONEAREST); if (monitor != IntPtr.Zero) { var monitorInfo = new NativeMethods.MonitorInfo(); monitorInfo.Size = Marshal.SizeOf(monitorInfo); NativeMethods.GetMonitorInfo(monitor, monitorInfo); var startPoint = this.PointToScreen(new Point(0, 0)); if (this.FlowDirection == FlowDirection.RightToLeft) { startPoint.X -= this.ActualWidth; } var inWindowRibbonWidth = monitorInfo.Work.Right - Math.Max(monitorInfo.Work.Left, startPoint.X); var actualWidth = this.ActualWidth; if (startPoint.X < monitorInfo.Work.Left) { actualWidth -= monitorInfo.Work.Left - startPoint.X; startPoint.X = monitorInfo.Work.Left; } // Set width this.DropDownPopup.Width = Math.Min(actualWidth, inWindowRibbonWidth); return new[] { new CustomPopupPlacement(new Point(startPoint.X - tabItemPos.X, this.SelectedTabItem.ActualHeight - ((FrameworkElement)this.DropDownPopup.Child).Margin.Top), PopupPrimaryAxis.None), new CustomPopupPlacement(new Point(startPoint.X - tabItemPos.X, -((ScrollViewer)this.SelectedContent).ActualHeight - ((FrameworkElement)this.DropDownPopup.Child).Margin.Bottom), PopupPrimaryAxis.None) }; } } return null; }
private void FixRestoreBounds(object sender, EventArgs e) { if (WindowState == WindowState.Maximized || WindowState == WindowState.Minimized) { // Old versions of WPF sometimes force their incorrect idea of the Window's location // on the Win32 restore bounds. If we have reason to think this is the case, then // try to undo what WPF did after it has done its thing. if (hasUserMovedWindow) { hasUserMovedWindow = false; NativeMethods.WINDOWPLACEMENT wp = new NativeMethods.WINDOWPLACEMENT(); NativeMethods.GetWindowPlacement(handle, wp); NativeMethods.Rect adjustedDeviceRc = /*GetAdjustedWindowRect(*/new NativeMethods.Rect { Bottom = 100, Right = 100 }/*)*/; Point adjustedTopLeft = /*DpiHelper.DevicePixelsToLogical(*/ new Point( wp.rcNormalPosition.Left - adjustedDeviceRc.Left, wp.rcNormalPosition.Top - adjustedDeviceRc.Top)/*)*/; Top = adjustedTopLeft.Y; Left = adjustedTopLeft.X; } } }
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { case NativeMethods.WM_SETTEXT: case NativeMethods.WM_SETICON: { bool modified = ModifyStyle(NativeMethods.WS_VISIBLE, 0); // Setting the caption text and icon cause Windows to redraw the caption. // Letting the default WndProc handle the message without the WS_VISIBLE // style applied bypasses the redraw. IntPtr lRet = NativeMethods.DefWindowProc(handle, msg, wParam, lParam); // Put back the style we removed. if (modified) { ModifyStyle(0, NativeMethods.WS_VISIBLE); } handled = true; return lRet; } case NativeMethods.WM_NCACTIVATE: { // Despite MSDN's documentation of lParam not being used, // calling DefWindowProc with lParam set to -1 causes Windows not to draw over the caption. // Directly call DefWindowProc with a custom parameter // which bypasses any other handling of the message. IntPtr lRet = NativeMethods.DefWindowProc(handle, NativeMethods.WM_NCACTIVATE, wParam, new IntPtr(-1)); IsNonClientAreaActive = (wParam != IntPtr.Zero); handled = true; return lRet; } case NativeMethods.WM_NCCALCSIZE: { handled = true; return new IntPtr((int)NativeMethods.WVR_REDRAW); } case NativeMethods.WM_NCHITTEST: { IntPtr lRet = IntPtr.Zero; handled = false; // Give DWM a chance at this first. if (IsDwmEnabled) { // If we're on Vista, give the DWM a chance to handle the message first. handled = NativeMethods.DwmDefWindowProc(handle, msg, wParam, lParam, ref lRet); } // Handle letting the system know if we consider the mouse to be in our effective non-client area. // If DWM already handled this by way of DwmDefWindowProc, then respect their call. if (IntPtr.Zero == lRet) { var mousePosScreen = new Point(NativeMethods.LowWord(lParam), NativeMethods.HiWord(lParam)); NativeMethods.Rect wndPosition = new NativeMethods.Rect(); NativeMethods.GetWindowRect(handle, ref wndPosition); Rect windowPosition = new Rect(wndPosition.Left, wndPosition.Top, wndPosition.Right - wndPosition.Left, wndPosition.Bottom - wndPosition.Top); int ht = HitTestNonClientArea( /*DpiHelper.DeviceRectToLogical(*/windowPosition/*)*/, /*DpiHelper.DevicePixelsToLogical(*/mousePosScreen)/*)*/; // Don't blindly respect HTCAPTION. // We want UIElements in the caption area to be actionable so run through a hittest first. if ((ht != NativeMethods.HTCLIENT) && (mainGrid != null)) { Point mousePosWindow = mousePosScreen; mousePosWindow.Offset(-windowPosition.X, -windowPosition.Y); //mousePosWindow = /*DpiHelper.DevicePixelsToLogical(*/mousePosWindow/*)*/; IInputElement inputElement = mainGrid.InputHitTest(mousePosWindow); if (inputElement != null) { if ((inputElement as FrameworkElement).Name == "PART_TitleBar") ht = NativeMethods.HTCAPTION; else if (inputElement != mainGrid) ht = NativeMethods.HTCLIENT; } } handled = true; lRet = new IntPtr((int)ht); } return lRet; } case NativeMethods.WM_NCRBUTTONUP: { // Emulate the system behavior of clicking the right mouse button over the caption area // to bring up the system menu. if (NativeMethods.HTCAPTION == wParam.ToInt32()) { ShowSystemMenuPhysicalCoordinates(new Point(NativeMethods.LowWord(lParam), NativeMethods.HiWord(lParam))); } handled = false; return IntPtr.Zero; } case NativeMethods.WM_SIZE: { const int SIZE_MAXIMIZED = 2; // Force when maximized. // We can tell what's happening right now, but the Window doesn't yet know it's // maximized. Not forcing this update will eventually cause the // default caption to be drawn. WindowState? state = null; if (wParam.ToInt32() == SIZE_MAXIMIZED) { state = WindowState.Maximized; } UpdateSystemMenu(state); // Still let the default WndProc handle this. handled = false; return IntPtr.Zero; } case NativeMethods.WM_WINDOWPOSCHANGED: { if (!IsDwmEnabled) { var wp = (NativeMethods.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(NativeMethods.WINDOWPOS)); SetRoundingRegion(wp); } // Still want to pass this to DefWndProc handled = false; return IntPtr.Zero; } case NativeMethods.WM_DWMCOMPOSITIONCHANGED: { IsDwmEnabled = NativeMethods.IsDwmEnabled(); UpdateFrameState(false); handled = false; return IntPtr.Zero; } } if (PresentationFrameworkVersion < new Version("4.0")) { switch (msg) { case NativeMethods.WM_SETTINGCHANGE: { FixFrameworkIssues(); handled = false; return IntPtr.Zero; } case NativeMethods.WM_ENTERSIZEMOVE: { isUserResizing = true; if (WindowState != WindowState.Maximized) { // Check for the docked window case. The window can still be restored when it's in this position so // try to account for that and not update the start position. if (!IsWindowDocked) { windowPosAtStartOfUserMove = new Point(Left, Top); } // Realistically we also don't want to update the start position when moving from one docked state to another (or to and from maximized), // but it's tricky to detect and this is already a workaround for a bug that's fixed in newer versions of the framework. // Not going to try to handle all cases. } handled = false; return IntPtr.Zero; } case NativeMethods.WM_EXITSIZEMOVE: { isUserResizing = false; // On Win7 the user can change the Window's state by dragging the window to the top of the monitor. // If they did that, then we need to try to update the restore bounds or else WPF will put the window at the maximized location (e.g. (-8,-8)). if (WindowState == WindowState.Maximized) { Top = windowPosAtStartOfUserMove.Y; Left = windowPosAtStartOfUserMove.X; } handled = false; return IntPtr.Zero; } case NativeMethods.WM_MOVE: { if (isUserResizing) { hasUserMovedWindow = true; } handled = false; return IntPtr.Zero; } } } return IntPtr.Zero; }
/// <summary> /// Responds to the condition in which the value of the Popup.IsOpen property /// changes from false to true. /// </summary> /// <param name="e">The event arguments.</param> protected override void OnOpened(EventArgs e) { base.OnOpened(e); isFirstMouseUp = true; PopupAnimation = PopupAnimation.None; if(Child!=null)hwndSource = (HwndSource)PresentationSource.FromVisual(this.Child); if (hwndSource != null) { hwndSource.AddHook(WindowProc); // Set popup non-topmost to fix bug with tooltips NativeMethods.Rect rect = new NativeMethods.Rect(); if (NativeMethods.GetWindowRect(hwndSource.Handle, ref rect)) { NativeMethods.SetWindowPos(hwndSource.Handle, new IntPtr(-2), rect.Left, rect.Top, (int) this.Width, (int) this.Height, NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOACTIVATE); } } openedPopups.Add(this); Activate(); }