2010-12-31 9 views
27

Mam aplikację WPF 4 opartą na MVVM, która korzysta z ProgressBar, aby pokazać procentowe zakończenie długotrwałej operacji.Jak zatrzymać pulsowanie/animację WPF ProgressBar po osiągnięciu 100%?

<ProgressBar Name="ProgressBar" 
    IsIndeterminate="False" 
    Minimum="0" 
    Maximum="100" 
    Value="{Binding Path=ProgressPercentageComplete, Mode=OneWay}" 
    Visibility="Visible"/> 

Jestem szczęśliwy dla „pulsujące” animacja podczas uruchamiania pasek postępu jest w ruchu, ale gdy osiąga ona 100% Chciałbym go zatrzymać animację i po prostu pozostać statyczny w 100%.

Próbowałem ustawienie IsIndeterminate="False" ale to nie pomaga i po przeczytaniu dokumentacji MSDN widzę dlaczego:

Gdy ta właściwość ma wartość true, ProgressBar ożywia kilka taktów ruchomych poprzek ProgressBar w sposób ciągły i ignoruje właściwość Value.

Czy można zatrzymać tę animację? Całkowicie lub w 100%.

+1

Zabijanie pulsującego obrazu jest złym pomysłem, ponieważ rozumowanie interfejsu użytkownika zapewnia ciągłą informację zwrotną od użytkownika; aby uniknąć przekonania użytkownika, że ​​coś jest zablokowane i przestało odpowiadać. –

+4

Aplikacje blokują się za pomocą ciągle wyświetlanych animacji interfejsu użytkownika. Potrzeba więcej, aby przekonać użytkownika, że ​​aplikacja wciąż żyje. –

+0

@RobertRossney, celowo zamknąłem swój wątek UI i * puf *, animacja zatrzymała się, dając natychmiastową informację zwrotną. aaron-mciver ma rację, że pulsacja ma cel UX. –

Odpowiedz

7

Możesz to zrobić, kopiując cały kod ControlTemplate dla ProgressBar, a następnie dodaj Trigger dla warunku, w którym ProgressBar.Value=100. XAML as sprawi, że ProgressBar zachowa się tak jak teraz. Usuń komentarz Tagi na dole i animacja zostanie zatrzymana, gdy właściwość Wartość ProgressBar osiągnie 100. Jedyną słabością jest to, że po zmianie Maksymalnej właściwości ProgressBar musisz również zmienić Triger. Czy ktoś wie, jak powiązać wyzwalacz z rzeczywistą wartością właściwości maksymalnej?

<Window x:Class="ProgressBarSpike.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="350" Width="525" 
     Loaded="Window_Loaded"> 
    <Window.Resources> 
     <LinearGradientBrush x:Key="ProgressBarBackground" EndPoint="1,0" StartPoint="0,0"> 
      <GradientStop Color="#BABABA" Offset="0"/> 
      <GradientStop Color="#C7C7C7" Offset="0.5"/> 
      <GradientStop Color="#BABABA" Offset="1"/> 
     </LinearGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarBorderBrush" EndPoint="0,1" StartPoint="0,0"> 
      <GradientStop Color="#B2B2B2" Offset="0"/> 
      <GradientStop Color="#8C8C8C" Offset="1"/> 
     </LinearGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarGlassyHighlight" EndPoint="0,1" StartPoint="0,0"> 
      <GradientStop Color="#50FFFFFF" Offset="0.5385"/> 
      <GradientStop Color="#00FFFFFF" Offset="0.5385"/> 
     </LinearGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarTopHighlight" EndPoint="0,1" StartPoint="0,0"> 
      <GradientStop Color="#80FFFFFF" Offset="0.05"/> 
      <GradientStop Color="#00FFFFFF" Offset="0.25"/> 
     </LinearGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" EndPoint="1,0" StartPoint="0,0"> 
      <GradientStop Color="#00FFFFFF" Offset="0"/> 
      <GradientStop Color="#60FFFFFF" Offset="0.4"/> 
      <GradientStop Color="#60FFFFFF" Offset="0.6"/> 
      <GradientStop Color="#00FFFFFF" Offset="1"/> 
     </LinearGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarIndicatorDarkEdgeLeft" EndPoint="1,0" StartPoint="0,0"> 
      <GradientStop Color="#0C000000" Offset="0"/> 
      <GradientStop Color="#20000000" Offset="0.3"/> 
      <GradientStop Color="#00000000" Offset="1"/> 
     </LinearGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarIndicatorDarkEdgeRight" EndPoint="1,0" StartPoint="0,0"> 
      <GradientStop Color="#00000000" Offset="0"/> 
      <GradientStop Color="#20000000" Offset="0.7"/> 
      <GradientStop Color="#0C000000" Offset="1"/> 
     </LinearGradientBrush> 
     <RadialGradientBrush x:Key="ProgressBarIndicatorLightingEffectLeft" RadiusY="1" RadiusX="1" RelativeTransform="1,0,0,1,0.5,0.5"> 
      <GradientStop Color="#60FFFFC4" Offset="0"/> 
      <GradientStop Color="#00FFFFC4" Offset="1"/> 
     </RadialGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarIndicatorLightingEffect" EndPoint="0,0" StartPoint="0,1"> 
      <GradientStop Color="#60FFFFC4" Offset="0"/> 
      <GradientStop Color="#00FFFFC4" Offset="1"/> 
     </LinearGradientBrush> 
     <RadialGradientBrush x:Key="ProgressBarIndicatorLightingEffectRight" RadiusY="1" RadiusX="1" RelativeTransform="1,0,0,1,-0.5,0.5"> 
      <GradientStop Color="#60FFFFC4" Offset="0"/> 
      <GradientStop Color="#00FFFFC4" Offset="1"/> 
     </RadialGradientBrush> 
     <LinearGradientBrush x:Key="ProgressBarIndicatorGlassyHighlight" EndPoint="0,1" StartPoint="0,0"> 
      <GradientStop Color="#90FFFFFF" Offset="0.5385"/> 
      <GradientStop Color="#00FFFFFF" Offset="0.5385"/> 
     </LinearGradientBrush> 
     <Style x:Key="ProgressBarStyleStopAnimation" TargetType="{x:Type ProgressBar}"> 
      <Setter Property="Foreground" Value="#01D328"/> 
      <Setter Property="Background" Value="{StaticResource ProgressBarBackground}"/> 
      <Setter Property="BorderBrush" Value="{StaticResource ProgressBarBorderBrush}"/> 
      <Setter Property="BorderThickness" Value="1"/> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type ProgressBar}"> 
         <Grid x:Name="TemplateRoot" SnapsToDevicePixels="true"> 
          <Rectangle Fill="{TemplateBinding Background}" RadiusY="2" RadiusX="2"/> 
          <Border Background="{StaticResource ProgressBarGlassyHighlight}" CornerRadius="2" Margin="1"/> 
          <Border BorderBrush="#80FFFFFF" BorderThickness="1,0,1,1" Background="{StaticResource ProgressBarTopHighlight}" Margin="1"/> 
          <Rectangle x:Name="PART_Track" Margin="1"/> 
          <Decorator x:Name="PART_Indicator" HorizontalAlignment="Left" Margin="1"> 
           <Grid x:Name="Foreground"> 
            <Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}"/> 
            <Grid x:Name="Animation" ClipToBounds="true"> 
             <Rectangle x:Name="PART_GlowRect" Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" HorizontalAlignment="Left" Margin="-100,0,0,0" Width="100"/> 
            </Grid> 
            <Grid x:Name="Overlay"> 
             <Grid.ColumnDefinitions> 
              <ColumnDefinition MaxWidth="15"/> 
              <ColumnDefinition Width="0.1*"/> 
              <ColumnDefinition MaxWidth="15"/> 
             </Grid.ColumnDefinitions> 
             <Grid.RowDefinitions> 
              <RowDefinition/> 
              <RowDefinition/> 
             </Grid.RowDefinitions> 
             <Rectangle x:Name="LeftDark" Fill="{StaticResource ProgressBarIndicatorDarkEdgeLeft}" Margin="1,1,0,1" RadiusY="1" RadiusX="1" Grid.RowSpan="2"/> 
             <Rectangle x:Name="RightDark" Grid.Column="2" Fill="{StaticResource ProgressBarIndicatorDarkEdgeRight}" Margin="0,1,1,1" RadiusY="1" RadiusX="1" Grid.RowSpan="2"/> 
             <Rectangle x:Name="LeftLight" Grid.Column="0" Fill="{StaticResource ProgressBarIndicatorLightingEffectLeft}" Grid.Row="2"/> 
             <Rectangle x:Name="CenterLight" Grid.Column="1" Fill="{StaticResource ProgressBarIndicatorLightingEffect}" Grid.Row="2"/> 
             <Rectangle x:Name="RightLight" Grid.Column="2" Fill="{StaticResource ProgressBarIndicatorLightingEffectRight}" Grid.Row="2"/> 
             <Border x:Name="Highlight1" Background="{StaticResource ProgressBarIndicatorGlassyHighlight}" Grid.ColumnSpan="3" Grid.RowSpan="2"/> 
             <Border x:Name="Highlight2" Background="{StaticResource ProgressBarTopHighlight}" Grid.ColumnSpan="3" Grid.RowSpan="2"/> 
            </Grid> 
           </Grid> 
          </Decorator> 
          <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/> 
         </Grid> 
         <ControlTemplate.Triggers> 
          <Trigger Property="Orientation" Value="Vertical"> 
           <Setter Property="LayoutTransform" TargetName="TemplateRoot"> 
            <Setter.Value> 
             <RotateTransform Angle="-90"/> 
            </Setter.Value> 
           </Setter> 
          </Trigger> 
          <Trigger Property="IsIndeterminate" Value="true"> 
           <Setter Property="Visibility" TargetName="LeftDark" Value="Collapsed"/> 
           <Setter Property="Visibility" TargetName="RightDark" Value="Collapsed"/> 
           <Setter Property="Visibility" TargetName="LeftLight" Value="Collapsed"/> 
           <Setter Property="Visibility" TargetName="CenterLight" Value="Collapsed"/> 
           <Setter Property="Visibility" TargetName="RightLight" Value="Collapsed"/> 
           <Setter Property="Visibility" TargetName="Indicator" Value="Collapsed"/> 
          </Trigger> 
          <Trigger Property="IsIndeterminate" Value="false"> 
           <Setter Property="Background" TargetName="Animation" Value="#80B5FFA9"/> 
          </Trigger> 
          <!-- 
          <Trigger Property="Value" Value="100"> 
           <Setter Property="Visibility" TargetName="Animation" Value="Collapsed"/> 
          </Trigger> 
          --> 

         </ControlTemplate.Triggers> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
    </Window.Resources> 
    <StackPanel> 
     <ProgressBar Name="Progress" Height="50" Style="{DynamicResource ProgressBarStyleStopAnimation}"/> 
    </StackPanel> 
</Window> 
+0

Czy istnieje sposób, aby nie określać "Value =" 100 "' jawnie? Ponieważ takie podejście nie jest uniwersalne. To sprawia, że ​​ustawiam 'Maximum' dowolnego' ProgressBar' jako "100". Próbowałem użyć 'Binding' ale to nie działa :( – monstr

+0

Należy poważnie rozważyć odpowiedź Dave'a Clemmer'a (patrz poniżej) jako alternatywę, ponieważ zastąpienie szablonu kontrolnego ma również pewne wady, jak zauważa Dave. – user2261015

0

Myślę, że jedynym sposobem jest stworzenie niestandardowego stylu dla ProgressBar.
Opis MSDN dla IsIndeterminate odnosi się do innego zachowania i domyślnie jest to wartość false, więc ustawienie niczego nie zmienia.

+0

Popraw poprawność tej odpowiedzi, informując dlaczego wartość -1. –

1

Można zamienić konwerter używany przez PART_Indicator który domyślnie jest ProgressBarBrushConverter czyli tam, gdzie animacja pochodzi z ...

... 

TranslateTransform transform = new TranslateTransform(); 
double num11 = num8 * 100; 
DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames(); 
animation.Duration = new Duration(TimeSpan.FromMilliseconds(num11)); 
animation.RepeatBehavior = RepeatBehavior.Forever; 
for (int i = 1; i <= num8; i++) 
{ 
    double num13 = i * num7; 
    animation.KeyFrames.Add(new DiscreteDoubleKeyFrame(num13, KeyTime.Uniform)); 
} 
transform.BeginAnimation(TranslateTransform.XProperty, animation); 

... 

default logic dla ProgressBarBrushConverter można następnie modyfikować w zależności od potrzeb .

Być może będziesz musiał przekazać parametry do konwertera, aby mógł sprawdzić wartość i dostarczyć odpowiednią animację lub jej brak w zależności od stanu ProgressBar.

8

Odpowiedź Dabblernla jest solidna. Oto hack (ponieważ opiera się na wewnętrznej nazwy elementu, dokłada blask, który jest szczegółów wdrażania i mogą ulec zmianie w kolejnej wersji):

void SetGlowVisibility(ProgressBar progressBar, Visibility visibility) { 
    var anim = progressBar.Template.FindName("Animation", progressBar) as FrameworkElement; 
    if (anim != null) 
     anim.Visibility = visibility; 
} 

Jeśli jak ProgressBar jest realizowany zmian ten hack może przestać działać.

Z drugiej strony, rozwiązanie, które całkowicie zastępuje XAML i style, może blokować i poprawiać kolory, ramki itp. I wyłączać zachowania, które mogą być dodane do nowszej wersji ProgressBar w przyszłości ...

Edytuj: Szczegóły dotyczące implementacji uległy zmianie. Zmieniono "PART_GlowRect" na "Animation" - ten pierwszy jest używany tylko w aero.normalcolor.xaml, podczas gdy drugi używany jest również w nowszych wersjach: aero2.normalcolor.xaml i aerolite.normalcolor.xaml.

9

pisałem uogólniony rozwiązanie tego problemu za pomocą dołączonego właściwość, pozwalając mi przełączyć zachowanie na każdej ProgressBar po prostu za pośrednictwem nieruchomości bezpośrednio lub stylu seter, tak:

<ProgressBar helpers:ProgressBarHelper.StopAnimationOnCompletion="True" /> 

Kod:

public static class ProgressBarHelper { 
    public static readonly DependencyProperty StopAnimationOnCompletionProperty = 
     DependencyProperty.RegisterAttached("StopAnimationOnCompletion", typeof(bool), typeof(ProgressBarHelper), 
              new PropertyMetadata(OnStopAnimationOnCompletionChanged)); 

    public static bool GetStopAnimationOnCompletion(ProgressBar progressBar) { 
     return (bool)progressBar.GetValue(StopAnimationOnCompletionProperty); 
    } 

    public static void SetStopAnimationOnCompletion(ProgressBar progressBar, bool value) { 
     progressBar.SetValue(StopAnimationOnCompletionProperty, value); 
    } 

    private static void OnStopAnimationOnCompletionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { 
     var progressBar = obj as ProgressBar; 
     if (progressBar == null) return; 

     var stopAnimationOnCompletion = (bool)e.NewValue; 

     if (stopAnimationOnCompletion) { 
      progressBar.Loaded += StopAnimationOnCompletion_Loaded; 
      progressBar.ValueChanged += StopAnimationOnCompletion_ValueChanged; 
     } else { 
      progressBar.Loaded -= StopAnimationOnCompletion_Loaded; 
      progressBar.ValueChanged -= StopAnimationOnCompletion_ValueChanged; 
     } 

     if (progressBar.IsLoaded) { 
      ReevaluateAnimationVisibility(progressBar); 
     } 
    } 

    private static void StopAnimationOnCompletion_Loaded(object sender, RoutedEventArgs e) { 
     ReevaluateAnimationVisibility((ProgressBar)sender); 
    } 

    private static void StopAnimationOnCompletion_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { 
     var progressBar = (ProgressBar)sender; 

     if (e.NewValue == progressBar.Maximum || e.OldValue == progressBar.Maximum) { 
      ReevaluateAnimationVisibility(progressBar); 
     } 
    } 

    private static void ReevaluateAnimationVisibility(ProgressBar progressBar) { 
     if (GetStopAnimationOnCompletion(progressBar)) { 
      var animationElement = GetAnimationElement(progressBar); 
      if (animationElement != null) { 
       if (progressBar.Value == progressBar.Maximum) { 
        animationElement.SetCurrentValue(UIElement.VisibilityProperty, Visibility.Collapsed); 
       } else { 
        animationElement.InvalidateProperty(UIElement.VisibilityProperty); 
       } 
      } 
     } 
    } 

    private static DependencyObject GetAnimationElement(ProgressBar progressBar) { 
     var template = progressBar.Template; 
     if (template == null) return null; 

     return template.FindName("PART_GlowRect", progressBar) as DependencyObject; 
    } 
} 

Zasadniczo dodaje obsługę ValueChanged, która dostosowuje widoczność animowanego elementu.

Kilka uwag:

  • Używam "PART_GlowRect" aby znaleźć element animowany, choć ktoś nazwał to hack. Nie zgadzam się: nazwa tego elementu oficjalnie jest dokumentowana przez TemplatePartAttribute, co można zobaczyć w ProgressBar's declaration. O ile prawdą jest, że to niekoniecznie gwarantuje, że wymieniony element istnieje, jedynym powodem, dla którego powinno go zabraknąć, jest to, że funkcja animacji w ogóle nie jest obsługiwana. Jeśli jest obsługiwany, ale używa innej nazwy elementu niż ta, którą udokumentowano, uznałbym to za błąd, a nie za szczegół implementacji.

  • Ponieważ wyciągam element z szablonu, konieczne jest również obsłużenie zdarzenia Loaded (które powstaje po zastosowaniu szablonu) w celu oczekiwania na udostępnienie szablonu przed próbą ustawienia początkowej widoczności, iw razie potrzeby ustaw go ponownie, gdy szablon zostanie zastąpiony w locie zmianą motywu.

  • Zamiast jawnie przełączanie między Collapsed i VisibilityVisible używam SetCurrentValue ustawić do Collapsed i InvalidateProperty go zresetować. SetCurrentValue stosuje wartość, która nie ma pierwszeństwa przed innymi źródłami wartości, a InvalidateProperty ponownie ocenia właściwość bez uwzględnienia ustawienia SetCurrentValue. Zapewnia to, że jeśli istnieją style lub wyzwalacze, które mogłyby wpłynąć na widoczność w normalnych warunkach (tj. Gdy jest to , a nie na 100%), to zresetowałoby się do tego zachowania, jeśli pasek postępu zostanie ponownie użyty (przejście z 100% z powrotem do 0%) zamiast być zakodowanym na stałe do Visible.

+0

Działa dobrze, ale jak ustawić pędzel koloru jednolitego paska postępu, jak w poprzedniej odpowiedzi? – monstr

+0

@monstr Nie można tego zrobić w sposób niezawodny bez zastąpienia całego szablonu kontrolnego. Teoretycznie można zastąpić [OnApplyTemplate] (http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.onapplytemplate (v = vs.110) .aspx), aby znaleźć i zmodyfikować elementy, które tworzą wygląd 3D ("Animacja" i "Nakładka"), tak jak w przypadku "PART_GlowRect". Ale nie gwarantują one części szablonu, mogą się zmieniać w zależności od aktualnego motywu systemu Windows. – nmclean

+1

Zrobiłem to poprzez dodanie kodu w metodzie "ReevaluateAnimationVisibility". Użyłem części szablonu "Animation" i zweryfikowałem ją na wartości null, więc nawet nie ma części "Animacja" w aktualnym motywie systemu Windows - nie spowoduje to wyjątków. – monstr

Powiązane problemy