最近我们将 WPF 应用程序移到了 dot net 6 上,我们发现其中一个控件无法正常工作。它是一种自定义组合框控件,用作按钮的弹出窗口,因此每当我们单击按钮时,都会显示弹出窗口,但迁移后该弹出窗口不可见。
// this is the control that is supposed to popup on button click
public class BalloonControl : ContentControl
{
static BalloonControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BalloonControl), new FrameworkPropertyMetadata(typeof(BalloonControl)));
}
// some properties like
public void Show()
{
_Popup = new Popup()
{
RenderTransform = new ScaleTransform(App.Window.ApplicationScale, App.Window.ApplicationScale),
Focusable = true,
MinWidth = MinWidth,
PlacementTarget = Target as FrameworkElement,
Child = this,
AllowsTransparency = true,
StaysOpen = false,
Placement = Placement,
PlacementRectangle = PlacementRectangle,
PopupAnimation = PopupAnimation.Slide
};
this.SizeChanged += (s, 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;
}
}
};
_Popup.Opened += (s, 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;
}
}
}
};
_Popup.Closed += (s, e) =>
{
if( IsCancelled )
{
if (OnCancel != null) OnCancel(this, new EventArgs());
}
else
{
if (OnApply != null) OnApply(this, new EventArgs());
}
};
_Popup.IsOpen = true;
}
}
//上面这段代码就是这样被调用的
onButtonclick()
{
Graphic.GraphicCanvas.FocusedCanvas = canvas;
// show popup
var list = new windows.ComboBoxPopup()
{
MinWidth = Style.Size.Width * canvas.CanvasScale.ScaleX,
MaxHeight = 1080 * canvas.CanvasScale.ScaleY,
FontSize = CssStyle.FontSize * canvas.CanvasScale.ScaleX,
};
list.Background = Brushes.Transparent;
Util.FontManager.StuffControl(CssStyle, list);
//Point tx = TransformToVisual(canvas).Transform(new Point(rc.Width / 2, 0));
Point tx = new Point((rc.Width / 2) + rc.X, rc.Y);
list.Choices = Style.Value.Choices.Select(c => new ListBoxItem(c, ActualTextColor, CssStyle) { HorizontalAlignment = Design.AlignmentConverters.ToWindows(_Overrider.HorizontalAlignment) } );
list.SelectedValue = list.Choices.FirstOrDefault(v => v.JSON == Style.Value.String);
double scale = canvas.CanvasScale.ScaleX;
var balloon = new windows.BalloonControl()
{
MinWidth = Style.Size.Width * scale,
Content = list,
Ratio = 0.5,
Foreground = new SolidColorBrush(_Style.TextColor),
Target = GraphicCanvas,
Placement = PlacementMode.Bottom,
PlacementRectangle = new Rect(tx.X, tx.Y, Size.Width, Size.Height),
Background = Util.Frozen.CreateBrush(CssStyle.BackgroundColor),
BorderBrush = Util.Frozen.CreateBrush(CssStyle.Border2.Color.Left)
};
ballon.show();
}
当我从气球对象中删除 Content(ContentControl 类中的内容属性)并直接在 BallononControl show 方法中将列表框设置为子项时,弹出窗口可以工作,只是样式是问题。
我最关心的是为什么会发生这种行为,我可以做些什么来解决它。
它看起来与 .NET 6 没有任何关系。它看起来像是一个重构错误。
重点是
BalloonControl
是一个ContentControl
。为了使其显示,它必须是视觉树的子级(BalloonControl
不是)。显示内容的唯一机会是将其委托给 Popup
创建和显示的 BalloonControl
。但你这样做。相反,您将 Popup.Child
设置为 BalloonControl
实例本身。只需修复 Popup
初始化即可。
重要!
您当前正在为
BalloonControl
创建潜在的内存泄漏。如果您保留对 BalloonControl
实例的引用并再次调用 Show
,旧的 Popup
将不会被垃圾回收,因为 BalloonControl
的事件处理程序正在使其保持活动状态。
为了将来安全起见(当您忘记这些细节或其他不知道实现细节的开发人员使用该代码时),您应该 a) 使用 WeakEventManager
来监听 Popup
事件或b) 从 Popup.Closed
事件取消注册所有事件处理程序,或 c) 重用 Popup
并仅更新 Popup.Child
属性(Popup
和 BalloonControl
的生命周期现在相同)。
适用于您的示例的修复:
public class BalloonControl : ContentControl
{
static BalloonControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BalloonControl), new FrameworkPropertyMetadata(typeof(BalloonControl)));
}
// some properties like
public void Show()
{
_Popup = new Popup()
{
RenderTransform = new ScaleTransform(App.Window.ApplicationScale, App.Window.ApplicationScale),
Focusable = true,
MinWidth = MinWidth,
PlacementTarget = Target as FrameworkElement,
Child = this.Content, // <== 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;
}
}
}
}