Всплывающее время от времени мерцает при установке поля (после анимации) - PullRequest
1 голос
/ 02 мая 2020

Я создаю раскрывающуюся кнопку, которая состоит из ToggleButton и ContextMenu. Меню открывается при нажатии кнопки и выравнивается по кнопке с помощью CustomPopupPlacementCallback.

. Я применяю настраиваемую анимацию к меню при ее открытии, например:

private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
    ContextMenu contextMenu = (ContextMenu)sender;

    NameScope.SetNameScope(this, new NameScope());

    TranslateTransform translation = new TranslateTransform();
    RegisterName("TranslateTransform", translation);

    contextMenu.RenderTransform = translation;

    DoubleAnimation translationAnimation = new DoubleAnimation()
    {
        From = -20,
        To = 0,
        Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200)),
        EasingFunction = new PowerEase() { EasingMode = EasingMode.EaseOut, Power = 3 }
    };

    Storyboard.SetTargetProperty(translationAnimation, new PropertyPath(TranslateTransform.YProperty));
    Storyboard.SetTargetName(translationAnimation, "TranslateTransform");

    Storyboard storyboard = new Storyboard();
    storyboard.Children.Add(translationAnimation);

    storyboard.Begin(this);
}

Это прекрасно работает и делает меню «выдвигающимся из кнопки»:

Dropdown animation as intended but without dropshadow

Примечания стороны:

  • Я использую собственную анимацию, потому что значение по умолчанию PopupAnimation.Slide не соответствует моим потребностям с точки зрения времени
  • Я знаю, что я мог бы также анимировать ContextMenu.VerticalOffsetProperty, но это заставило бы меню перекрывать кнопку во время анимации. Анимация RenderTransform, с другой стороны, похоже, делает то, что я хочу.

Теперь я добавил тень к меню, которая требует, чтобы у ContextMenu был запас (в противном случае Тень будет обрезана). Этот вид нарушает выравнивание меню, потому что теперь оно перекрывает кнопку во время анимации (чего я хотел избежать в первую очередь):

Menu with dropshadow overlapping the button

Итак, мне пришла в голову идея убрать верхнее поле из меню во время анимации и восстановить его после этого, например так:

private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
    Thickness originalMargin = contextMenu.Margin;
    contextMenu.Margin = new Thickness(
        contextMenu.Margin.Left, 0, contextMenu.Margin.Right, contextMenu.Margin.Bottom);

    /* Prepare animation as shown before... */

    storyboard.Completed += new EventHandler((animation, eventArgs) =>
    {
        contextMenu.Margin = originalMargin; 
    });

    storyboard.Begin(this);
}

В большинстве случаев это работает как задумано - всплывающее окно идеально выровнен во время анимации, а тени падают наверху после завершения анимации. Однако при восстановлении поля всплывающее окно иногда ненадолго перемещается:

Menu with shadow, moving out of place sporadically

Я знаю, что изменение Margin свойство ContextMenu приведет к тому, что система верстки пересмотрит размещение всплывающего окна. Но я не знаю, почему иногда появляется этот промежуточный фрейм до обновления размещения. Кроме того, я не совсем понимаю, почему всплывающее окно прыгает вверх, когда верхнее поле фактически увеличивается.

Обновление: По иронии судьбы, на более медленных машинах поведение наблюдается значительно реже. ..

Могу ли я что-нибудь сделать с этим вопросом?

Как всегда, большое спасибо заранее!

1 Ответ

0 голосов
/ 11 мая 2020

Ладно, думаю, я нашел обходной путь.

Я оставляю свойство Margin нетронутым на протяжении всего процесса. Это позволяет избежать первоначальной проблемы, но, как показано выше, меню перекрывает кнопку (см. Вторую анимацию в моем исходном вопросе).

Теперь я должен компенсировать нежелательное смещение, но я не могу использовать VerticalOffset свойство (потому что удаление его после завершения анимации снова приведет к мерцанию споради c). Вместо этого я использую геометрию отсечения и анимирую ее так, чтобы она всегда обрезала ту часть меню, которая перекрывает кнопку:

private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
    /* Prepare translation animation as shown before... */

    RectangleGeometry clipGeometry = new RectangleGeometry(new Rect(
        new Point(-contextMenu.Margin.Left, 0),
        new Size(contextMenu.ActualWidth + contextMenu.Margin.Left + contextMenu.Margin.Right,
            contextMenu.ActualHeight + contextMenu.Margin.Bottom)));
    contextMenu.RegisterName("RectangleGeometry", clipGeometry);

    contextMenu.Clip = clipGeometry;

    RectAnimation clippingAnimation = new RectAnimation()
    {
        Duration = new Duration(new TimeSpan(0, 0, 0, 0, 200)),
        EasingFunction = new PowerEase() { EasingMode = EasingMode.EaseOut, Power = 3 },
        From = new Rect(
            new Point(-contextMenu.Margin.Left, 20),
            new Size(contextMenu.ActualWidth + contextMenu.Margin.Left + contextMenu.Margin.Right,
                contextMenu.ActualHeight - 20 + contextMenu.Margin.Bottom))
    };

    SetTargetProperty(clippingAnimation, new PropertyPath(RectangleGeometry.RectProperty));
    SetTargetName(clippingAnimation, "RectangleGeometry");

    storyboard.Children.Add(clippingAnimation);

    storyboard.Completed += new EventHandler((animation, eventArgs) =>
    {
        contextMenu.RenderTransform = null;
        contextMenu.Clip = null; 
    });

    storyboard.Begin(this);
}
...