Member 12606650 Ответов: 1

В WPF форму, цвет заливки останавливается обновление раскадровка мигающая анимация


Мое приложение WPF-это монитор для производственных систем. В главном окне отображается один из 3 элементов управления, отображающих состояние соединения (вниз, загрузка или вверх). Элемент управления UP имеет несколько форм кнопок высокого уровня (эллипс в приведенном ниже примере), представляющих различные элементы производственной системы (маршрутизатор, конфигурация, внутренние соединения, внешние соединения и т. д.). цвет объекта отражает "худшее" состояние его детей (или внуков, или правнуков...). Он также будет мигать/мигать (через анимацию раскадровки), если один из детей изменит статус.

Каждая форма кнопки сопоставляется с объектом ColorStatus:
public class ColorStatus: INotifyPropertyChanged
{
    private eStatusColor StatusColor {get; set;}
    public bool          IsFlashing  {get; set;}

      // ...
}

eStatusColor перечисление (не активен, нормально, предупреждение, критическое), в котором я использую конвертер Level2Color_Converter (), чтобы изменить его к solidcolorbrush (серый/зеленый/желтый/красный), и применить его к форме заполнения. Bool IsFlashing используется для запуска анимации цвета заливки (будет переходить от текущего цвета к StatusBlinked, который является черным, а затем обратно к текущему цвету, в течение примерно 1 секунды). Вот пример формы кнопки (маршрутизатор Овальный):
<!-- Router Oval -->
<Button Grid.Column="1"             Margin="0,3"
        VerticalAlignment="Center"  HorizontalAlignment="Center"
        Focusable="False"           ToolTip="Open Router Window"
        Command="{Binding OpenRouterWindow_Command}" >
    <Button.Template>
          <!-- This part turns off button borders/on mouseover animations -->
        <ControlTemplate TargetType="Button">
            <ContentPresenter Content="{TemplateBinding Content}"/>
        </ControlTemplate>
    </Button.Template>
    <Grid>
        <Ellipse HorizontalAlignment="Center"  VerticalAlignment="Center"
                 Height="60"                   Width="155"
                 Stroke="Black"                StrokeThickness="2"
                 Fill="{Binding RouterStatus.StatusColor,
                       Converter={local:Level2Color_Converter}}">
            <Ellipse.Style>
                <Style TargetType="{x:Type Ellipse}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Name="Router_storyboard">
                                    <BeginStoryboard.Storyboard>
                                        <Storyboard>
                                            <ColorAnimation To="{StaticResource StatusBlinked}"
                                                            Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
                                                            AutoReverse="True"
                                                            RepeatBehavior="Forever"
                                                            Duration="0:0:0.5"/>
                                        </Storyboard>
                                    </BeginStoryboard.Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="False">
                            <DataTrigger.EnterActions>
                                <StopStoryboard BeginStoryboardName="Router_storyboard"/>
                            </DataTrigger.EnterActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>
        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
                   FontFamily="Arial"         FontSize="13"
                   Foreground="White"         Text="ROUTER">
            <TextBlock.Effect>
                <DropShadowEffect/>
            </TextBlock.Effect>
        </TextBlock>
    </Grid>
</Button>

Проблема в том, что если объект уже мигает и вы меняете цвет (скажем, с желтого на красный), он отключает анимацию раскадровки (даже если bool IsFlashing все еще верен).

Обратите внимание, что нет фиксированного порядка в получении статусов детей (мы получаем их в том порядке, в котором они отправляются из производственного приложения). Также обратите внимание, что это многопоточное приложение (поток чтения ставит сообщение в очередь к потоку обработчика сообщений, который обновляет ViewModel, а графический интерфейс обновляется через INotifyPropertyChanged).

Есть ли способ исправить это такая, что раскадровка, анимация продолжается, если IsFlashing является истинным, независимо от того, есть ли способ исправить это такая, что раскадровка, анимация продолжается, если IsFlashing является истинным, независимо от заполнения изменение цвета? (или не выйти из синхронизации по изменению цвета?)

Что я уже пробовал:

In manual testing (I had a test window with buttons to force color/flashing status changes), I solved this in the following way:
<pre lang="c#">            if (mockup_IsBlinking)
            {
                  // Toggle is blinking to keep it blinking
                mockup_IsBlinking = false;
                mockup_IsBlinking = true;
            }

То есть, если он мигал, выключите мигание, а затем снова включите, чтобы перезапустить анимацию.

Для реального тестирования (получения обновлений состояния из приложения, которое я отслеживаю) я изменил ColorStatus, чтобы имитировать тот же метод, изменив сеттер:
public class ColorStatus: INotifyPropertyChanged
{
    private eStatusColor _StatusColor;
    public  eStatusColor  StatusColor {   get
                                          {
                                              return _StatusColor;
                                          }
                                          set
                                          {
                                              _StatusColor = value;
                                              if (IsFlashing)
                                              {
                                                  IsFlashing = false;
                                                  IsFlashing = true;
                                              }
                                          }
                                      }
    public bool         IsFlashing    {get; set;}

      // ...
}

Но, когда я подключаюсь к фактическому применению Я контроль, я поверил сплетне детей статус обновления приходят так быстро, что анимация выходит из синхронизма (мы получаем 135 детей, добавляют, как обычно, который вызывает верхнего уровня формы кнопки от неактивных в нормальных с IsFlashing=false, то фактическое ребенок статус обновления приходят в 3 обычный для предупреждения с IsFlashing=true и 27 нормальных до критических с IsFlashing=истина). Когда я запускаю монитор, ~ 1/5 времени верхний уровень мигает правильно (мигает), 4/5 времени он неверен (IsFlashing истинен, но анимация раскадровки не работает).

Gerry Schmitz

Я не вижу никакого кода "OnPropertyChanged", связанного с IsFlashing; любая привязка будет видеть его только при запуске.

Member 12606650

Я сократил эти элементы в классе ColorStatus с помощью "// ..." (а также метода ctor), но у него есть стандартные элементы INotifyPropertyChanged (иначе он не компилировался бы). Вот недостающие строки кода INotifyPropertyChanged:

публичное событие PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
protected PropertyChangedEventHandler PropertyChangedField;
общественного недействительными on свойство changed(строки наименование)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}

Gerry Schmitz

Вы должны вызвать обработчик, когда хотите распознать изменение; он не срабатывает в вакууме. Вы реализовали только минимум, который ничего не дает ... кроме компиляции. "Интерфейсы" ничего не говорят о выполнении; только "крючки".

Тебе нужно почитать.

Member 12606650

В прошлом меня упрекали за слишком много кода в моих постах, поэтому я, возможно, сократил слишком много в этом посте, но код на месте.
У меня есть тестовое окно, в котором есть кнопка для поворота цвета и кнопка для переключения IsFlashing bool. Это работает в 100% случаев. Но когда я подключаюсь к системе, которую отслеживаю, она взрывает начальное состояние системы, и мы получаем ~165 обновлений за ~ 1 секунду, и это работает только ~1/5 времени. Под работой я подразумеваю, что конечный результат моего элемента верхнего уровня должен мигать, но он не мигает (хотя IsFlashing-это правда). Ручной тест работает, потому что мой щелчок не является достаточно быстрым, чтобы вывести пользовательский интерфейс из синхронизации.

1 Ответов

Рейтинг:
0

Member 12606650

Я решил эту проблему, изменив раскадровку с ColorAnimation на Storyboard.TargetProperty (Path.Fill) к DoubleAnimationUsingKeyFrames на раскадровке.TargetProperty (Путь.Непрозрачность), а затем поместить сплошную черную фигуру (эллипс в приведенном выше примере) под анимирующую фигуру. Теперь, поскольку привязываются 2 разных элемента (Fill on StatusColor enum и Opacity on IsFlashing), пользовательский интерфейс не выходит из синхронизации.

Так что новая черная фигура находится на панели.ZIndex="0", форма с цветом и раскадровкой находится на панели.ZIndex="1" (и текст на панели.ZIndex="2").

Я также немного изменил раскадровку (от того, чтобы дать больше времени цвету, прежде чем стать черным. Вот xaml для этого объекта

<!-- Router Oval -->
<Button Grid.Column="1"             Margin="0,3"
        VerticalAlignment="Center"  HorizontalAlignment="Center"
        Focusable="False"           ToolTip="Open Router Window"
        Command="{Binding OpenRouterWindow_Command}" >
    <Button.Template>
          <!-- This part turns off button borders/on mouseover animations -->
        <ControlTemplate TargetType="Button">
            <ContentPresenter Content="{TemplateBinding Content}"/>
        </ControlTemplate>
    </Button.Template>
    <Grid>
        <Ellipse Panel.ZIndex="0"
                 HorizontalAlignment="Center"  VerticalAlignment="Center"
                 Height="60"                   Width="155"
                 Stroke="Black"                StrokeThickness="2"
                 Fill="{StaticResource StatusBlinkedBrush}"/>
        <Ellipse Panel.ZIndex="1"
                 HorizontalAlignment="Center"  VerticalAlignment="Center"
                 Height="60"                   Width="155"
                 Stroke="Black"                StrokeThickness="2"
                 Fill="{Binding RouterStatus.StatusColor,
                       Converter={local:Level2Color_Converter}}">
            <Ellipse.Style>
                <Style TargetType="{x:Type Ellipse}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Name="Router_storyboard">
                                    <BeginStoryboard.Storyboard>
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames 
                                                    Storyboard.TargetProperty="(Path.Opacity)" 
                                                    AutoReverse="True" RepeatBehavior="Forever"
                                                    Duration="0:0:0.7">
                                                <LinearDoubleKeyFrame Value="1"    KeyTime="0:0:0"   />
                                                <LinearDoubleKeyFrame Value="1"    KeyTime="0:0:0.2" />
                                                <LinearDoubleKeyFrame Value="0.75" KeyTime="0:0:0.35"/>
                                                <LinearDoubleKeyFrame Value="0.5"  KeyTime="0:0:0.6" />
                                                <LinearDoubleKeyFrame Value="0"    KeyTime="0:0:0.7" />
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </BeginStoryboard.Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="False">
                            <DataTrigger.EnterActions>
                                <StopStoryboard BeginStoryboardName="Router_storyboard"/>
                            </DataTrigger.EnterActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>
        <TextBlock Panel.ZIndex="2" 
                   VerticalAlignment="Center" HorizontalAlignment="Center"
                   FontFamily="Arial"         FontSize="13"
                   Foreground="White"         Text="ROUTER">
            <TextBlock.Effect>
                <DropShadowEffect/>
            </TextBlock.Effect>
        </TextBlock>
    </Grid>
</Button>