PreviewControl border

PreviewControl border

nice3point
Advocate Advocate
1,210 Views
16 Replies
Message 1 of 17

PreviewControl border

nice3point
Advocate
Advocate

Is it possible to disable the PreviewControl border ? This border comes from the Win32 window. Setting User32.WindowStyles by Hwnd handle does not give any results. Except WS_CHILD and similar, no other styles are applied. Is this border added by Revit development team or is it a HwndHost issue ?

 

nice3point_1-1708385197084.png

 

 

0 Likes
Accepted solutions (1)
1,211 Views
16 Replies
Replies (16)
Message 2 of 17

Speed_CAD
Collaborator
Collaborator

Hi,

 

The only way I could do it when I used it, was to set the Grid margin to -4, but this only works if the grid is set entirely to the window.

Mauricio Jorquera
0 Likes
Message 3 of 17

nice3point
Advocate
Advocate
It's not a solution, negative margin just covers other content
0 Likes
Message 4 of 17

Speed_CAD
Collaborator
Collaborator

I don't like the negative margin either, but it was the only way to hide the border. And for it to work, the container (Grid) must cover the entire window.

Mauricio Jorquera
0 Likes
Message 5 of 17

nice3point
Advocate
Advocate

@jeremytammikhi Jeremy, can you ask the Revit team where this border comes from?

0 Likes
Message 6 of 17

jeremy_tammik
Alumni
Alumni

Sure. Thank you for asking. I passed it on to them internally.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 7 of 17

cwaluga
Advocate
Advocate

You can use Spy++ to determine the window structure and use WinAPI to remove the borders and some WPF magic to trigger the repaint. It took me hours of experimenting, but it is doable. If I recall correctly, there are multiple levels of controls and you have to figure out which ones carry the borders.

 

I am off for a while and have no access to the code, but don’t feel discouraged if it doesn’t work the first time. It was really painful to solve this and I wish Autodesk would just remove the borders in an upcoming release. It is hard to make software look good if the underlying API takes you back to the nineties ;-).

Message 8 of 17

nice3point
Advocate
Advocate

@cwaluga  perfect) can you share your code?
@jeremy_tammik   any updates from the Revit team?

0 Likes
Message 9 of 17

jeremy_tammik
Alumni
Alumni

Nope, no updates yet. I added @cwaluga 's comments to my query and reprompted. Thank you for those!

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 10 of 17

cwaluga
Advocate
Advocate

@nice3point @jeremy_tammik 

I cannot share the actual code, unfortunately. Our codebase is really massive and closed ;-).

While I cannot find the time to provide a full working example, I can post some code for you to fill the gaps. The important snippet (to be called after previewControl.Loaded AND previewControll.IsVisibleChanged) is the following:

 

 

// get preview window host
var previewWndHost = previewControl.Content;
if (previewWndHost is null)
	return;

// get preview view handle
var previewHwnd = (IntPtr)previewWndHost.GetType().GetProperty("Handle").GetValue(previewWndHost, null);
if (previewHwnd == IntPtr.Zero)
	return;

// remove WS_EX_CLIENTEDGE on all child windows
foreach (var hwnd in HwndHelpers.GetAllChildHandles(previewHwnd).Append(previewHwnd))
{
	var style = User32.GetWindowLong(hwnd, Constants.GWL_EXSTYLE).ToInt32() & ~(int)Constants.WS_EX_CLIENTEDGE;
	User32.SetWindowLong(hwnd, Constants.GWL_EXSTYLE, style);
}

// trigger redraw by adding or removing a slight padding at the bottom
// the original padding is stored in the tag, so try to avoid using the
// tag property for anything else if you want this to work.
var p = previewControl.Padding;
if (previewControl.Tag is null)
	previewControl.Tag = p;
if (previewControl.Tag is System.Windows.Thickness t)
{
	p.Bottom = p.Bottom == t.Bottom ? p.Bottom + 1 : t.Bottom;
	previewControl.Padding = p;
}

 

 

The IsVisibleChanged handler is required for use in tab controls, since Revit seems to re-create the view in case of visibility changes. I misused the tag to save the previous state and avoid shrinking/growing of the control due to the padding-changes at "reentry". If you find a better solution to trigger the redraw, please let me know. This part is pretty hacky, but I had to move on at some point and got stuck with whatever did the job.

 

I also use some WinAPI functions which can be easily imported (google, pinvoke). The HwndHelpers function is just syntactic sugar around EnumChildWindows.

 

 

public static IList<IntPtr> GetAllChildHandles(IntPtr hwnd)
{
	var childHandles = new List<IntPtr>();
	var gcChildHandles = GCHandle.Alloc(childHandles);

	try
	{
		bool EnumWindow(IntPtr hWnd, IntPtr lParam)
		{
			(GCHandle.FromIntPtr(lParam).Target as List<IntPtr>)?.Add(hWnd);
			return true;
		}

		var childProc = new User32.EnumWindowsProc(EnumWindow);
		User32.EnumChildWindows(hwnd, childProc, GCHandle.ToIntPtr(gcChildHandles));
	}
	finally
	{
		gcChildHandles.Free();
	}

	return childHandles;
}

 

 

 

Message 11 of 17

rawava1350
Explorer
Explorer

@cwalugagreat solution

0 Likes
Message 12 of 17

nice3point
Advocate
Advocate

@cwaluga  amazing, i completely forgot about the Child when was writing a similar code. Now all borders are gone, in addition, I have solved the redrawing problem, for which you used Padding (it was not working correctly)

Before:

nice3point_0-1711460240466.png

After:

nice3point_0-1711458548335.png

 

0 Likes
Message 13 of 17

nice3point
Advocate
Advocate
Accepted solution

Solution:

 

 

public void Initialize()
{
    var previewControl = new PreviewControl(_context, view.Id);
    previewControl.Loaded += RemovePreviewControlStyles;
}

private void RemovePreviewControlStyles(object sender, EventArgs args)
{
    var control = (PreviewControl)sender;
    var previewHost = (FrameworkElement)control.Content;
    var previewType = previewHost.GetType();
    var hostField = previewType.GetField("m_hwndHost", BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance)!;
    var handle = (IntPtr)hostField.GetValue(previewHost);

    var childHandles = UnsafeNativeMethods.GetChildHandles(handle);

    UnsafeNativeMethods.RemoveWindowStyles(handle);
    UnsafeNativeMethods.RemoveWindowCaption(handle);
    foreach (var childHandle in childHandles)
    {
        UnsafeNativeMethods.RemoveWindowStyles(childHandle);
    }
}

 

 

UnsafeNativeMethods:

 

 

/// <summary>
/// Tries to remove styles from selected window handle.
/// </summary>
/// <param name="handle">Window handle.</param>
/// <returns><see langword="true"/> if invocation of native Windows function succeeds.</returns>
public static bool RemoveWindowStyles(IntPtr handle)
{
    if (handle == IntPtr.Zero)
    {
        return false;
    }

    if (!User32.IsWindow(handle))
    {
        return false;
    }

    var cornerResult = ApplyWindowCornerPreference(handle, WindowCornerPreference.DoNotRound);
    if (!cornerResult) return false;

    var windowStyleLong = User32.GetWindowLong(handle, User32.GWL.GWL_EXSTYLE);
    windowStyleLong &= ~(int)User32.WS_EX.CLIENTEDGE;

    var styleResult = SetWindowLong(handle, User32.GWL.GWL_EXSTYLE, windowStyleLong);
    return styleResult.ToInt64() > 0x0;
}

/// <summary>
///     Get the child windows that belong to the specified parent window by passing the handle to each child window.
/// </summary>
/// <param name="hwnd">Window handle.</param>
public static IList<IntPtr> GetChildHandles(IntPtr hwnd)
{
    var handles = new List<IntPtr>();
    var gcHandles = GCHandle.Alloc(handles);

    try
    {
        var callbackPointer = new User32.EnumWindowsProc(EnumWindowCallback);
        User32.EnumChildWindows(hwnd, callbackPointer, GCHandle.ToIntPtr(gcHandles));
    }
    finally
    {
        gcHandles.Free();
    }

    return handles;
}

private static bool EnumWindowCallback(IntPtr hwnd, IntPtr lParam)
{
    var target = GCHandle.FromIntPtr(lParam).Target as List<IntPtr>;
    if (target is null) return false;

    target.Add(hwnd);
    return true;
}    

 

 

User32:

 

/// <summary>
///     An application-defined callback function used with the EnumChildWindows function. It receives the child window handles. The WNDENUMPROC type defines a pointer to this callback function. EnumChildProc is a placeholder for the application-defined function name.
/// </summary>
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

/// <summary>
///     Enumerates the child windows that belong to the specified parent window by passing the handle to each child window, in turn, to an application-defined callback function. EnumChildWindows continues until the last child window is enumerated or the callback function returns FALSE.
/// </summary>
/// <param name="hwnd">The window that you want to get information about.</param>
/// <param name="func">A pointer to an application-defined callback function</param>
/// <param name="lParam">An application-defined value to be passed to the callback function.</param>
/// <returns></returns>
[DllImport(Libraries.User32)]
public static extern bool EnumChildWindows(IntPtr hwnd, EnumWindowsProc func, IntPtr lParam);

 

 

I've also disabled edge rounding for Windows 11 as well.

The used methods can be found in the WPF UI repository.

User32: https://github.com/lepoco/wpfui/blob/development/src/Wpf.Ui/Interop/User32.cs

UnsafeNativeMethods: https://github.com/lepoco/wpfui/blob/development/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs

Message 14 of 17

nice3point
Advocate
Advocate

@jeremy_tammik problem solved, I think it will be useful to share this on the blog. However, I would like to ask Revit development team to turn this off by default, as it is easier for users to configure the control themselves than to mess with Win API and native code

Message 15 of 17

cwaluga
Advocate
Advocate

@nice3point: Nice, can you please elaborate on which of these lines can get me rid of the padding-trick?

0 Likes
Message 16 of 17

nice3point
Advocate
Advocate

UnsafeNativeMethods.RemoveWindowCaption(handle); where handle is hwndHost

https://github.com/lepoco/wpfui/blob/development/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs#L468

Message 17 of 17

jeremy_tammik
Alumni
Alumni

Thank you for the nice discussion, research and solution. I followed your suggestion and added it to the blog:

  

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open