WPF 데이터 트리거 및 스토리 보드
-
09-06-2019 - |
문제
ViewModel/프레젠테이션 모델이 사용 중일 때 진행 애니메이션을 트리거하려고 합니다.IsBusy 속성이 있고 ViewModel이 UserControl의 DataContext로 설정되어 있습니다.IsBusy 속성이 true인 경우 "progressAnimation" 스토리보드를 트리거하는 가장 좋은 방법은 무엇입니까?Blend에서는 UserControl 수준에 이벤트 트리거만 추가할 수 있으며 데이터 템플릿에서만 속성 트리거를 생성할 수 있습니다.
"progressAnimation"은 사용자 컨트롤의 리소스로 정의됩니다.
DataTrigger를 UserControl의 스타일로 추가하려고 시도했지만 StoryBoard를 시작하려고 하면 다음 오류가 발생합니다.
'System.Windows.Style' value cannot be assigned to property 'Style'
of object'Colorful.Control.SearchPanel'. A Storyboard tree in a Style
cannot specify a TargetName. Remove TargetName 'progressWheel'.
ProgressWheel은 애니메이션을 적용하려는 개체의 이름이므로 대상 이름을 제거하는 것은 분명히 내가 원하는 것이 아닙니다.
저는 코드를 통해 이벤트를 노출하고 애니메이션을 시작/중지하는 대신 데이터 바인딩 기술을 사용하여 XAML에서 이 문제를 해결하고 싶었습니다.
해결책
ProgressWheel 자체에 애니메이션을 선언하면 원하는 것이 가능합니다.XAML:
<UserControl x:Class="TriggerSpike.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<UserControl.Resources>
<DoubleAnimation x:Key="SearchAnimation" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:4"/>
<DoubleAnimation x:Key="StopSearchAnimation" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:4"/>
</UserControl.Resources>
<StackPanel>
<TextBlock Name="progressWheel" TextAlignment="Center" Opacity="0">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsBusy}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<StaticResource ResourceKey="SearchAnimation"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<StaticResource ResourceKey="StopSearchAnimation"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
Searching
</TextBlock>
<Label Content="Here your search query"/>
<TextBox Text="{Binding SearchClause}"/>
<Button Click="Button_Click">Search!</Button>
<TextBlock Text="{Binding Result}"/>
</StackPanel>
코드 숨김:
using System.Windows;
using System.Windows.Controls;
namespace TriggerSpike
{
public partial class UserControl1 : UserControl
{
private MyViewModel myModel;
public UserControl1()
{
myModel=new MyViewModel();
DataContext = myModel;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
myModel.Search(myModel.SearchClause);
}
}
}
뷰모델:
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace TriggerSpike
{
class MyViewModel:DependencyObject
{
public string SearchClause{ get;set;}
public bool IsBusy
{
get { return (bool)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
public static readonly DependencyProperty IsBusyProperty =
DependencyProperty.Register("IsBusy", typeof(bool), typeof(MyViewModel), new UIPropertyMetadata(false));
public string Result
{
get { return (string)GetValue(ResultProperty); }
set { SetValue(ResultProperty, value); }
}
public static readonly DependencyProperty ResultProperty =
DependencyProperty.Register("Result", typeof(string), typeof(MyViewModel), new UIPropertyMetadata(string.Empty));
public void Search(string search_clause)
{
Result = string.Empty;
SearchClause = search_clause;
var worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
IsBusy = true;
worker.RunWorkerAsync();
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IsBusy=false;
Result = "Sorry, no results found for: " + SearchClause;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
}
}
}
도움이 되었기를 바랍니다!
다른 팁
애니메이션을 적용할 요소에 직접 애니메이션을 첨부하는 방법을 제안하는 답변이 간단한 경우에는 이 문제를 해결하지만, 여러 요소를 대상으로 해야 하는 복잡한 애니메이션이 있는 경우 실제로는 실행 가능하지 않습니다.(물론 각 요소에 애니메이션을 첨부할 수 있지만 관리하기가 매우 어렵습니다.)
따라서 이 문제를 해결할 수 있는 다른 방법이 있습니다. DataTrigger
명명된 요소를 대상으로 하는 애니메이션을 실행합니다.
WPF에는 트리거를 연결할 수 있는 세 가지 위치가 있습니다.요소, 스타일, 템플릿.그러나 처음 두 옵션은 여기서 작동하지 않습니다.첫 번째는 WPF가 사용을 지원하지 않기 때문에 제외됩니다. DataTrigger
요소에 직접.(제가 아는 한 특별한 이유는 없습니다.제가 기억하는 한, 몇 년 전 WPF 팀 사람들에게 이에 대해 물었을 때 그들은 지원하고 싶었지만 이를 작동시킬 시간이 없었다고 말했습니다.) 그리고 스타일은 사라졌습니다. 보고하신 오류 메시지에는 스타일과 연결된 애니메이션에서 명명된 요소를 대상으로 지정할 수 없다고 나와 있습니다.
그러면 템플릿이 남습니다.이를 위해 컨트롤 또는 데이터 템플릿을 사용할 수 있습니다.
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<ControlTemplate.Resources>
<Storyboard x:Key="myAnimation">
<!-- Your animation goes here... -->
</Storyboard>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<DataTrigger
Binding="{Binding MyProperty}"
Value="DesiredValue">
<DataTrigger.EnterActions>
<BeginStoryboard
x:Name="beginAnimation"
Storyboard="{StaticResource myAnimation}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard
BeginStoryboardName="beginAnimation" />
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
<!-- Content to be animated goes here -->
</ControlTemplate>
</ContentControl.Template>
<ContentControl>
이 구성을 통해 WPF에서는 애니메이션이 템플릿 내부의 명명된 요소를 참조할 수 있게 되었습니다.(여기서는 애니메이션과 템플릿 콘텐츠를 모두 비워 두었습니다. 당연히 실제 애니메이션 콘텐츠로 채울 것입니다.)
이것이 템플릿에서는 작동하지만 스타일에서는 작동하지 않는 이유는 템플릿을 적용할 때 정의된 명명된 요소가 항상 존재하므로 해당 템플릿 범위 내에 정의된 애니메이션이 해당 요소를 참조하는 것이 안전하기 때문입니다.일반적으로 스타일의 경우는 그렇지 않습니다. 왜냐하면 스타일은 서로 다른 여러 요소에 적용될 수 있으며 각 요소는 매우 다른 시각적 트리를 가질 수 있기 때문입니다.(필요한 요소가 있을 것이라고 확신할 수 있는 시나리오에서도 이 작업을 수행할 수 없다는 점은 조금 실망스럽습니다. 하지만 애니메이션이 오른쪽에 명명된 요소에 바인딩되는 것을 매우 어렵게 만드는 것이 있을 수 있습니다. 시간.스타일 요소를 효율적으로 재사용할 수 있도록 WPF에 최적화가 많이 있다는 것을 알고 있으므로 아마도 그 중 하나가 이 기능을 지원하기 어렵게 만드는 것 같습니다.)
IsBusy 속성 대신 RoutedEvent를 사용하는 것이 좋습니다.OnBusyStarted 및 OnBusyStopped 이벤트를 실행하고 적절한 요소에 이벤트 트리거를 사용하세요.
DataObject 클래스의 PropertyChanged 이벤트를 구독하고 Usercontrol 수준에서 RoutedEvent가 실행되도록 할 수 있습니다.
RoutedEvent가 작동하려면 종속성 개체(DependancyObject)에서 파생된 클래스가 있어야 합니다.
속성이 변경되면 Trigger.EnterAction을 사용하여 애니메이션을 시작할 수 있습니다.
<Trigger Property="IsBusy" Value="true">
<Trigger.EnterActions>
<BeginStoryboard x:Name="BeginBusy" Storyboard="{StaticResource MyStoryboard}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="BeginBusy" />
</Trigger.ExitActions>
</Trigger>