In my previous post, I began the process of creating a sample implementation of a user control that would allow the user to view and interact with the constituent steps inside a running workflow. I created the wrapper classes for both the sequence and step, created the business activities (AddViewToRegion and ClearViewsFromRegion) and created two workflow activities (OrderedActivity and UnorderedActivity) Now I will create the user control that will bind to the data entities defined in my workflow activities, as well as wire everything up.
WorkflowViewerView.xaml
<ComboBox x:Name="ActivityDropDown" Grid.Row="0" DockPanel.Dock="Top" ItemsSource="{Binding ActivityCV}"> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <DockPanel Grid.Row="1" > <Button x:Name="Complete" prism:Click.Command="{Binding CompleteCommand}" DockPanel.Dock="Bottom"> <TextBlock>Complete</TextBlock> </Button> <ListBox x:Name="ActivityItemListBox" SelectionMode="Single" DockPanel.Dock="Top" HorizontalContentAlignment="Stretch" ItemsSource="{Binding BookmarkCV}">
The xaml for the control will consist of a dropdown control with the selectable activities (in this case, OrderedActivity and UnorderedActivity), a “Complete” button for manual termination of the current activity, and a ListBox to contain and display the collection of UserActivitySteps. Pretty basic WPF binding operations here. Prism provides an interface to tie UI control actions back to the associated view model for this view. I use this for the Click event on the Complete button, tying it to the CompleteCommand on WorkflowViewerViewModel.
<DataTemplate.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Value="True"> <Setter TargetName="dataBorderShadow" Property="Effect"> <Setter.Value> <DropShadowEffect ShadowDepth="0" Color="LimeGreen" BlurRadius="10"/> </Setter.Value> </Setter> </DataTrigger> <DataTrigger Binding="{Binding Status, Mode=OneWay}" Value="{x:Static Activities:UserStepStatus.Completed}"> <Setter TargetName="dataBorder" Property="BorderBrush" Value="Green"/> <Setter TargetName="bookmarkValue" Property="Text" Value="Completed"/> </DataTrigger> </DataTemplate.Triggers>
I also defined some data triggers on the items in the ListBox, so that the selected item displays a blur/shadow effect and also so that completed steps will show up as green.
The rest of the functionality comes in the view model implementation.
WorkflowViewerViewModel
private readonly List<ActivityDescriptor> _model; private ObservableCollection<UserStepActivity> _userSteps; public ICollectionView ActivityCV { get; private set; } public ICollectionView BookmarkCV { get; private set; } public DelegateCommand CompleteCommand { get; private set; } public WorkflowViewerViewModel() { _model = Helper.ActivityList; ActivityCV = new ListCollectionView(_model); ActivityCV.CurrentChanged += new EventHandler(SelectedItemChanged); _userSteps = new ObservableCollection<UserStepActivity>(); BookmarkCV = new ListCollectionView(this._userSteps); BookmarkCV.CurrentChanged += new EventHandler(SelectedBookmarkChanged); CompleteCommand = new DelegateCommand(OnComplete); }
The Helper class returns the pre-configured list of two activities for the dropdown to bind against. I initialize the user steps collection, prior to any activity having been selected.
private void SelectedItemChanged(object sender, EventArgs e) { // Publish the DataItemSelected event when the current selection changes. ActivityDescriptor item = ActivityCV.CurrentItem as ActivityDescriptor; if (item != null && item.Xaml != null) { Helper.TryLoadAndValidateActivity(item.Xaml, out _currentActivity); _userSteps.Clear(); UserStepSequence rootActivitySequence = default(UserStepSequence); Helper.InspectActivity(_currentActivity, _userSteps, ref rootActivitySequence); rootActivitySequence.UserStepSequenceChanged += UserStepSequenceChanged; _activeActivityBookmark = rootActivitySequence.BookmarkName.Expression.ToString(); _currentWorkflowApplication = new WorkflowApplication(_currentActivity); _currentWorkflowApplication.Run(); } }
When the user selects an activity, I load the System.Activities.Activity from the associated xaml file. I then interrogate the activity and extract a reference to the UserStepSequence. This reference allows me to access the constituent step activities and associate them with the user steps collection bound to the ListBox. I can also wire up additional event handlers here for events published by the UserStepSequence (or UserStepActivity for that matter.) I’ll re-use the BookmarkName value to resume the UserStepSequence when the user selects an individual step within the workflow. Finally, I start the workflow application.
private void SelectedBookmarkChanged(object sender, EventArgs e) { ActivityDescriptor item = ActivityCV.CurrentItem as ActivityDescriptor; if (item != null && item.Xaml != null) { UserStepActivity userStep = BookmarkCV.CurrentItem as UserStepActivity; if (userStep != null) { _currentWorkflowApplication.ResumeBookmark(_activeActivityBookmark, userStep.BookmarkNameValue); } } }
After confirming I have an active activity, I read the ListBox item selected by the user an use the BookmarkNameValue for that step as an argument into the ResumeBookmark call on the workflow. Now for some screenshots.
Conclusion
And…that’s pretty much it! The sample at the end of the post has additional functionality, or at least, a first pass at some additional functionality around messaging events up from the sequence and sequence steps as well as managing status transitions for the steps themselves. But the core of the sample, which I’ve reviewed above, demonstrates the key functionality needed to accomplish my original goals. I hope that this is enough to get you started on your own custom (and more robust) implementations that will accomplish your own goals for visualizing workflow applications!
Please find the sample source code here