Проблема с позиционированием всплывающего окна WPF с FlowDirection="RightToLeft"C#

Место общения программистов C#
Ответить
Anonymous
 Проблема с позиционированием всплывающего окна WPF с FlowDirection="RightToLeft"

Сообщение Anonymous »

Описание проблемы
Я разрабатываю собственный компонент WPF Flyout, который использует Popup с CustomPopupPlacementCallback. Всплывающее меню позиционируется правильно, если окно имеет FlowDirection="LeftToRight", но когда я устанавливаю FlowDirection="RightToLeft" в окне, всплывающее окно отображается в неправильном положении - часто полностью не совпадает с целью размещения.

Среда
  • Framework: .NET 9.0 / WPF
  • Проблема: Неправильное размещение пользовательского всплывающего окна в макетах RTL
  • Затронутые компоненты: CustomPopupPlacementCallback, PopupPositioner
Структура кода
Моя реализация состоит из трех основных компонентов:
  • CustomPopupPlacementHelper – вычисляет позиции всплывающих окон с помощью CustomPopupPlacementCallback.
  • PopupPositioner – использует внутренние методы WPF для расширенного позиционирования.
  • FlyoutBase – основной элемент управления всплывающим окном, управляющий всплывающим окном.
Что Я попробовал
Попытка 1: ручное зеркалирование RTL в CustomPopupPlacementHelper
Я обнаружил RTL FlowDirection и вручную поменял местами координаты:

Код: Выделить всё

private static bool ShouldMirrorForRTL(FrameworkElement child)
{
var popup = FindParentPopup(child);
if (popup?.PlacementTarget is FrameworkElement target)
{
return target.FlowDirection == FlowDirection.RightToLeft;
}
return false;
}

// In CalculatePopupPlacement:
case CustomPlacementMode.TopEdgeAlignedLeft:
point = shouldMirrorForRTL
? new Point(targetSize.Width - popupSize.Width, -popupSize.Height)
: new Point(0, -popupSize.Height);
break;
Результат: Это вызвало проблемы с двойным преобразованием, поскольку всплывающее окно WPF уже имеет внутреннюю обработку RTL.
Попытка 2: коррекция RTL в PopupPositioner
Я добавил коррекцию RTL после расчета позиции:

Код: Выделить всё

if (_popup.PlacementTarget is FrameworkElement feRtl &&
feRtl.FlowDirection == FlowDirection.RightToLeft)
{
if (PlacementInternal == PlacementMode.Left ||
PlacementInternal == PlacementMode.Right)
{
double mirroredX = bestTranslation.X + (targetWidth - popupWidth)
- 2 * (bestTranslation.X - targetBounds.Left);
bestTranslation.X = mirroredX;
}
}
Результат: Все еще неверно: исправление вручную противоречило внутренним преобразованиям RTL в WPF.
Попытка 3: скорректированный PlacementRectangle во FlyoutBase
Я попробовал настроить прямоугольник размещения для RTL:

Код: Выделить всё

bool rtl = target is FrameworkElement fe &&
fe.FlowDirection == FlowDirection.RightToLeft;

if (rtl)
{
// Move rect left by target width so right edge (x=0) aligns with target's right edge
value = new Rect(
new Point(-targetSize.Width, -Offset),
new Point(0, targetSize.Height + Offset));
}
Результат: Это также вызывало проблемы с позиционированием из-за конфликта с собственной обработкой WPF.
Анализ первопричин
После исследования исходного кода WPF Popup (из справочного источника) я обнаружил, что WPF Popup имеет встроенную поддержку RTL:
  • Отмена преобразования — всплывающее окно автоматически применяет преобразование масштаба для отмены зеркального отображения FlowDirection:

    Код: Выделить всё

    if (parent != null &&
    (FlowDirection)parent.GetValue(FlowDirectionProperty) == FlowDirection.RightToLeft)
    {
    popupTransform.Scale(-1.0, 1.0); // Undo FlowDirection Mirror
    }
    
  • Обмен точками интереса. Когда FlowDirection у целевого и дочернего элемента различается, WPF меняет точки интереса:

    Код: Выделить всё

    if ((FlowDirection)target.GetValue(FlowDirectionProperty) !=
    (FlowDirection)child.GetValue(FlowDirectionProperty))
    {
    SwapPoints(ref interestPoints[(int)InterestPoint.TopLeft],
    ref interestPoints[(int)InterestPoint.TopRight]);
    SwapPoints(ref interestPoints[(int)InterestPoint.BottomLeft],
    ref interestPoints[(int)InterestPoint.BottomRight]);
    }
    
Решение
Удалите все ручные преобразования RTL и позвольте WPF Popup обрабатывать RTL собственными средствами.
Внесенные изменения:
  • CustomPopupPlacementHelper – удалено FollowMirrorForRTL() и вся логика позиционирования, специфичная для RTL.
  • PopupPositioner – удалены ручные исправления RTL.
  • FlyoutBase – упрощен GetPlacementRectangle(), чтобы всегда использовать стандартные координаты.
Обновлено. CustomPopupPlacementHelper:

Код: Выделить всё

internal static CustomPopupPlacement[] PositionPopup(
CustomPlacementMode placement,
Size popupSize,
Size targetSize,
Point offset,
FrameworkElement child = null)
{
Matrix transformToDevice = default;
if (child != null)
{
Helper.TryGetTransformToDevice(child, out transformToDevice);
}

// Let WPF Popup handle RTL natively - no manual RTL transformations
CustomPopupPlacement preferredPlacement = CalculatePopupPlacement(
placement, popupSize, targetSize, offset, child, transformToDevice);

// ... rest of the method
}

private static CustomPopupPlacement CalculatePopupPlacement(
CustomPlacementMode placement,
Size popupSize,
Size targetSize,
Point offset,
FrameworkElement child = null,
Matrix transformToDevice = default)
{
Point point;
PopupPrimaryAxis primaryAxis;

switch (placement)
{
case CustomPlacementMode.TopEdgeAlignedLeft:
// Always use standard LTR coordinates
point = new Point(0, -popupSize.Height);
primaryAxis = PopupPrimaryAxis.Horizontal;
break;
case CustomPlacementMode.TopEdgeAlignedRight:
point = new Point(targetSize.Width - popupSize.Width, -popupSize.Height);
primaryAxis = PopupPrimaryAxis.Horizontal;
break;
// ... other cases
}

return new CustomPopupPlacement(point, primaryAxis);
}
Обновлена ​​FlyoutBase:

Код: Выделить всё

internal Rect GetPlacementRectangle(UIElement target)
{
Rect value = Rect.Empty;

if (target != null)
{
Size targetSize = target.RenderSize;

// Simple placement rectangle without RTL transformations
// WPF Popup will handle RTL layout natively
switch (Placement)
{
case FlyoutPlacementMode.Top:
case FlyoutPlacementMode.Bottom:
case FlyoutPlacementMode.TopEdgeAlignedLeft:
case FlyoutPlacementMode.TopEdgeAlignedRight:
case FlyoutPlacementMode.BottomEdgeAlignedLeft:
case FlyoutPlacementMode.BottomEdgeAlignedRight:
value = new Rect(
new Point(0, -Offset),
new Point(targetSize.Width, targetSize.Height + Offset));
break;
case FlyoutPlacementMode.Left:
case FlyoutPlacementMode.Right:
// ...  other horizontal placements
value = new Rect(
new Point(-Offset, 0),
new Point(targetSize.Width + Offset, targetSize.Height));
break;
}
}
return value;
}
Вопросы
  • Как мне обрабатывать RTL в пользовательской логике размещения всплывающих окон? Должен ли я:
    • Разрешить WPF полностью обрабатывать RTL и избегать любых ручных преобразований?
    • Обнаруживать RTL и отражать мои расчеты?
    • Сравнить FlowDirection целевого объекта и дочерний элемент, как это делает WPF?
  • Должны ли вычисления CustomPopupPlacementCallback поддерживать RTL? Или WPF применяет преобразования RTL после возврата обратного вызова?
  • Как правильно определить, когда следует применять зеркальное отображение RTL? Это:
    • Когда окно FlowDirection = RightToLeft?
    • Когда целевой и всплывающий дочерние элементы имеют разные значения FlowDirection?
    • Что-то еще?
Ожидаемое поведение
Когда FlowDirection="RightToLeft":
  • Код: Выделить всё

    BottomEdgeAlignedLeft
    должен быть выровнен по визуальному левому краю (справа в координатах RTL)
  • Код: Выделить всё

    BottomEdgeAlignedRight
    должен выравниваться по визуальному правому краю (слева в координатах RTL)
  • Всплывающие меню с выравниванием по центру должны оставаться по центру
  • Все места размещения должны соблюдать порядок макета RTL
Дополнительный контекст
  • Компонент отлично работает с FlowDirection="LeftToRight"
  • Я пытаюсь обеспечить совместимость с собственным поведением RTL WPF.
  • Компонент поддерживает анимацию, регулировку радиуса угла и инверсию смещения.
  • Полный исходный код: WPF-Flyout на GitHub.
Ссылки
  • Поведение WPF при размещении всплывающих окон
  • WPF FlowDirection
  • CustomPopupPlacementCallback
Будем очень признательны за любые рекомендации по правильному подходу к обработке RTL при произвольном размещении всплывающих окон!

Подробнее здесь: https://stackoverflow.com/questions/798 ... ighttoleft
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»