Recently we moved our WPF Application on dot net 6, and we discovered that one of the one of the control is not working. It kind of a custom combo box control that work as a popup to a button, so whenever we click on the button a popup is shown, but after migration that pop up is not visible.
// this is the control that is supposed to popup on button click
public class BalonCtrl : ContentControl
{
static BalonCtrl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BalonCtrl),
new FrameworkPropertyMetadata(typeof(BalonCtrl)));
}
// some properties like
public void Show()
{
var popup = new PopUp()
{
RenderTransform = new ScaleTransform(App.Window.ApplicationScale, App.Window.ApplicationScale),
Focusable = true,
MinWidth = MinWidth,
PlacementTarget = Target as FrameworkElement,
Child = this, // <== Delegate the Content to the Popup so that it can render it
AllowsTransparency = true,
StaysOpen = false,
Placement = Placement,
PlacementRectangle = PlacementRectangle,
PopupAnimation = PopupAnimation.Slide
};
popup.isOpen = true;
}
}
// This is how the above piece of code is being called
onButtonclick()
{
var list = new windows.ComboBoxPopup();
var balloon = new windows.BalonControl()
{
Content = list,
};
ballon.show();
}
When I remove the Content, which a content propert in ContentControl class, from the balloon object and set listbox as child directly in BallononControl show method, the pop up works, just styling is the issue.
I'm mostly concerned with why this behavior is happening is there something I can do to fix it.
It doesn't look like it has anything to do with .NET 6. It rather looks like a refactoring error.
The point is that BalloonControl
is a ContentControl
. In order to make it show it must be a child of a visual tree (which BalloonControl
isn't). The only chance to display any content is to delegate it to the Popup
that BalloonControl
creates and shows. But you never do this. Instead, you set the Popup.Child
to the BalloonControl
instance itself. Just fix the Popup
initialization.
Important!
You are currently creating a potential memory leak for the BalloonControl
. If you retain a reference to a BalloonControl
instance and call Show
again, the old Popup
won't get garbage collected because the event handlers of BalloonControl
are keeping it alive.
To be on the safe side for the future (when you forgot about those details or when other developers that don't know the implementation details use that code) you should a) use the WeakEventManager
to listen to the Popup
events or b) unregister all event handlers from the Popup.Closed
event or c) reuse the Popup
and only update the Popup.Child
property (lifetime of Popup
and BalloonControl
is now the same).
The fixes applied to your example:
public class BalloonControl : ContentControl
{
static BalloonControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BalloonControl), new FrameworkPropertyMetadata(typeof(BalloonControl)));
}
// some properties like
public void Show()
{
var popupContent = this.Content;
this. Content = null;
_Popup = new Popup()
{
RenderTransform = new ScaleTransform(App.Window.ApplicationScale, App.Window.ApplicationScale),
Focusable = true,
MinWidth = MinWidth,
PlacementTarget = Target as FrameworkElement,
Child = popupContent, // <== Delegate the Content to the Popup so that it can render it
AllowsTransparency = true,
StaysOpen = false,
Placement = Placement,
PlacementRectangle = PlacementRectangle,
PopupAnimation = PopupAnimation.Slide
};
this.SizeChanged += OnSizeChanged;
_Popup.Opened += OnPopupOpened;
_Popup.Closed += OnPopupClosed;
_Popup.IsOpen = true;
}
private void OnPopupOpened(object sender, EventArgs e)
{
Mouse.Capture(_Popup.Child, CaptureMode.SubTree);
// right now up or down only
if (PointerOrientation == Common.PointerOrientation.Bottom)
{
var target = Target as FrameworkElement;
if (target != null)
{
Point pt = target.TranslatePoint(PlacementRectangle.TopLeft, Content as FrameworkElement);
if (pt.Y > 0)
{
PointerOrientation = Common.PointerOrientation.Top;
}
}
}
}
private void OnPopupClosed(object sender, EventArgs e)
{
this.SizeChanged -= OnSizeChanged;
_Popup.Opened -= OnPopupOpened;
_Popup.Closed -= OnPopupClosed;
if (IsCancelled)
{
if (OnCancel != null) OnCancel(this, new EventArgs());
}
else
{
if (OnApply != null) OnApply(this, new EventArgs());
}
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
if (Placement == PlacementMode.Top || Placement == PlacementMode.Bottom)
{
if (UseCanvasScale)
{
Graphic.GraphicCanvas canvas = Graphic.GraphicCanvas.FocusedCanvas;
_Popup.HorizontalOffset = (-e.NewSize.Width / 2) / canvas.CanvasScale.ScaleX;
}
else
{
_Popup.HorizontalOffset = -e.NewSize.Width / 2;
}
}
}
}