Недавно мы переместили наше приложение WPF в dot net 6 и обнаружили, что один из элементов управления не работает. Это своего рода настраиваемый элемент управления полем со списком, который работает как всплывающее окно для кнопки, поэтому всякий раз, когда мы нажимаем на кнопку, отображается всплывающее окно, но после миграции оно не отображается.
// 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;
}
}
// Вот как вызывается приведенный выше фрагмент кода
onButtonclick()
{
var list = new windows.ComboBoxPopup();
var balloon = new windows.BalonControl()
{
Content = list,
};
ballon.show();
}
Когда я удаляю контент, который является свойством контента в классе ContentControl, из объекта воздушного шара и устанавливаю список в качестве дочернего непосредственно в методе показа BallononControl, всплывающее окно работает, проблема заключается только в стиле.
Меня больше всего беспокоит, почему происходит такое поведение, могу ли я что-нибудь сделать, чтобы это исправить.
Не похоже, что это имеет какое-либо отношение к .NET 6. Скорее это похоже на ошибку рефакторинга.
Дело в том, что BalloonControl
— это ContentControl
. Чтобы его было видно, он должен быть дочерним элементом визуального дерева (что BalloonControl
не является). Единственный шанс отобразить какой-либо контент — делегировать его Popup
, который BalloonControl
создаёт и показывает. Но ты никогда этого не делаешь. Вместо этого вы устанавливаете Popup.Child
для самого экземпляра BalloonControl
. Просто исправьте инициализацию Popup
.
Важно!
В настоящее время вы создаете потенциальную утечку памяти для BalloonControl
. Если вы сохраните ссылку на экземпляр BalloonControl
и снова вызовете Show
, старый Popup
не будет собирать мусор, потому что обработчики событий BalloonControl
поддерживают его активность.
Чтобы быть в безопасности на будущее (когда вы забыли об этих деталях или когда другие разработчики, которые не знают деталей реализации, используют этот код), вам следует: а) использовать WeakEventManager
для прослушивания событий Popup
или б) отменить регистрацию всех обработчики событий из события 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()
{
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;
}
}
}
}
Вы должны установить для свойства Content значение NULL. Я обновлю пример.
Вы уверены, что это был тот самый код? Невозможно, чтобы это когда-либо работало. Невозможный.
Ага!!! точно такой же код, я еще раз проверил, и код точно такой же. Это та часть кода, которую мы не трогали при рефакторинге.
Решат ли мои предложения вашу проблему? Что касается вашего сломанного исходного кода. Если этот код никогда не трогался, проблема в чем-то другом. Вам что-нибудь не хватает? Может быть, вам не хватает стиля BalloonControl
по умолчанию, который должен быть определен в Generic.xaml? Всплывающее окно может отображать BalloonControl
только в том случае, если был найден стиль по умолчанию или стиль переопределения. Это объясняет, почему всплывающее окно ничего не показывает. Код, который вы показали, настолько прост. Убежден, что это не связано со сменой целевых рамок. Убедитесь, что стиль по умолчанию находится в правильном файле Generic.xaml.
Спасибо за предложение, позвольте мне посмотреть на это и вернуться к вам.
Извините за опоздание, немного обновленной информации по этому поводу: у элемента управления воздушным шаром есть стиль, поэтому проблема не в этом. Исправление, которое вы дали, я уже опробовал, и это будет последняя мера, если ничего не поможет. Меня больше всего беспокоит то, что изменилось между dotnet 4 и dotnet 6 и привело к возникновению этой проблемы. Как объяснено в вопросе, следующий код отлично работает в предыдущей версии, я отлаживал и сравнивал, и он точно такой же.
Таким образом, всплывающее окно может отображать содержимое элемента управления всплывающим окном в предыдущей версии, но не в dot net 6. Этот элемент управления всплывающим окном используется в другом месте, где содержимое отличается, и там оно также не работает, хотя в предыдущие версии.
Действительно сложно сказать, не зная подробностей. Я вполне убежден, что это не имеет ничего общего с целевой версией платформы (за исключением того, что вы не предоставили важную информацию). Единственное разумное объяснение, которое у меня есть, это то, что вы пропустили стиль по умолчанию в Genric.xaml. Вы можете попытаться реконструировать проблему. Создайте пустой проект, нацеленный на используемую в данный момент платформу и который, по вашему мнению, сломал ваш код. Затем переместите управление в этот проект. и использовать его там. Это просто всплывающее окно. Ничего особенного. Я ожидал, что все будет работать как обычно.
Затем добавьте дополнительный код, чтобы воспроизвести использование в вашем текущем приложении (например, внешний ресурс или дополнительные стили и т. д.). В какой-то момент вы снова сможете выйти из-под контроля. Если вам удалось создать минимальный воспроизводимый пример, вы можете создать репозиторий GitHub, чтобы я мог просмотреть ваш контроль и соответствующий контекст. Я почти уверен, что проблема в некоторых мелких деталях, которые заставляют вас смеяться, когда вы понимаете, что все это было перед вашими глазами.
Вы были правы, это была только проблема со стилем, в основном стиль Balloon Control указан неправильно.
Хороший. Я рад, что смог помочь во всем разобраться.
Я тоже попробовал, но у меня возникла ошибка, поскольку указанный элемент является логическим дочерним элементом другого элемента. Я знаю, почему это происходит, единственное решение, которое я могу придумать, — это создать отдельное свойство и назначить его дочернему элементу. В этом случае всплывающее окно, кажется, работает. Единственное, чего я не понимаю, и причина, по которой я думаю, что это проблема dot net 6, заключается в том, что тот же код правильно работал в предыдущей версии, которая была 4.8.