The official Fatica Labs Blog! RSS 2.0
# Monday, 14 March 2011

This is the list of the posts related to Workflow4 Re-Hosting

Post 1 First drop, showing we can host the designer view in pure markup.
Post 2 Adding the activities bar into the game.
Post 3 Removing the blue gear, and adding the proper activity image.
Post 4 Showing the property editor grid.
Post 5 Default WorkflowView command routing.

 

Sample Project has a place on Codeplex.

Monday, 14 March 2011 20:16:50 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

There is an entire suite of command defined by the designer view:

      • DesignerView.CopyAsImageCommand;
      • DesignerView.CopyCommand;
      • DesignerView.CreateArgumentCommand;
      • DesignerView.CreateVariableCommand;
      • DesignerView.CreateWorkflowElementCommand;
      • DesignerView.CutCommand;
      • DesignerView.CycleThroughDesignerCommand;
      • DesignerView.DeleteBreakpointCommand;
      • DesignerView.DisableBreakpointCommand;
      • DesignerView.EnableBreakpointCommand;
      • DesignerView.ExpandAllCommand;
      • DesignerView.ExpandCommand;
      • DesignerView.ExpandInPlaceCommand;
      • DesignerView.FitToScreenCommand;
      • DesignerView.GoToParentCommand;
      • DesignerView.InsertBreakpointCommand;
      • DesignerView.PasteCommand;
      • DesignerView.RedoCommand;
      • DesignerView.ResetZoomCommand;
      • DesignerView.SaveAsImageCommand;
      • DesignerView.SelectAllCommand;
      • DesignerView.ToggleArgumentDesignerCommand;
      • DesignerView.ToggleImportsDesignerCommand;
      • DesignerView.ToggleMiniMapCommand;
      • DesignerView.ToggleSelectionCommand;
      • DesignerView.ToggleVariableDesignerCommand;
      • DesignerView.UndoCommand;
      • DesignerView.ZoomInCommand;
      • DesignerView.ZoomOutCommand;

But since the view is internal in the designer it is not automatic binding commands form the UI to the underlying target. So our hosting solution propose an attached property, to route the command to the view in a way suitable in pure markup too. The property we create is called DesignerCommandTarget, and is used as below:

   1:  <Button Style="{StaticResource toolButton}" ToolTip="Zoom In"   Command="wfview:DesignerView.ZoomInCommand"
   2:   host:HostingHelper.DesignerCommandTarget="{StaticResource wfHost}" 
   3:  >
   4:                      <Image Source="Assets/zoom_in.png"></Image>
   5:  </Button>
   6:                 

 

So we basically bind the command to the button by using Command, as usual, but we set the DesignerCommandTarget to our hosting instance, that is defined in the resource. In order to have the commands exposed by the designer in XAML, we added the wfview namespace reference as underlined below:

   1:  <Window x:Class="WF4Host.MainWindow"
   2:          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          xmlns:toolbox="clr-namespace:System.Activities.Presentation.Toolbox;assembly=System.Activities.Presentation"
   5:          xmlns:host="clr-namespace:WF4Host"
   6:          xmlns:custom="clr-namespace:MyActivities;assembly=MyActivities"
   7:          xmlns:wf="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
   8:          xmlns:wfview="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
   9:          xmlns:activities="clr-namespace:System.Activities.Statements;assembly=System.Activities"
  10:          Title="MainWindow" Height="650" Width="825"
  11:          Icon="Assets/App.ico"
  12:          TextOptions.TextFormattingMode="Display"
  13:         
  14:          >

Command routing is very simple, and it is shown below:

 

   1:   protected static void OnCommandTargetChanged(DependencyObject depo, DependencyPropertyChangedEventArgs depa)
   2:          {
   3:              var src = depo as ICommandSource;
   4:              if (null != src && depa.NewValue != null )
   5:              {
   6:                  var ctargetdescr = DependencyPropertyDescriptor.FromName("CommandTarget"
   7:                                                , depo.GetType()
   8:                                                , depo.GetType()
   9:                                                ,true);
  10:                  ctargetdescr.SetValue(depo
  11:  , (depa.NewValue as HostingHelper).Designer.Context.Services.GetService<DesignerView>());
  12:              }
  13:          }

Every time we attach the property we check if the target is a CommandSource, so basically the strategy works for any object implementing this interface. If this pre condition is satisfied, we simply bind the WorkflowDesigner view as a CommandTarget for that source. In this way commands from our UI behave as they are defined inside the workflow, and we don’t need to write any code behind to custom wire them.

Our example application now looks like this:

 

image

Code for this post is hosted here.
Monday, 14 March 2011 11:13:09 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

# Monday, 07 March 2011

Showing the property grid is achieved using  the same strategy we used to show the main editor, since it is a View internally exposed by the WorkflowDesigner. So we can decide where to put the property grid in XAML only in order to be blend friendly. Let’s have a look at the XAML:

   1:              <toolbox:ToolboxControl Grid.Column="0" Grid.Row="1" host:ToolboxItemSource.CategorySource="{StaticResource GeneralTools}"/>
   2:              <GridSplitter ResizeDirection="Columns" Grid.RowSpan="3" Grid.Column="1" Grid.Row="1" Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
   3:              <host:MainViewPresenter HostingHelper="{StaticResource wfHost}" Grid.RowSpan="3" Grid.Column="2" Grid.Row="1"/>
   4:              <GridSplitter ResizeDirection="Rows" Grid.Column="0" Grid.Row="2" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
   5:              <host:PropertyViewPresenter HostingHelper="{StaticResource wfHost}" Grid.Column="0" Grid.Row="3"/>

Here we have all the graphical widget composing the designer as XAML tags. At line 5 we actually present the property editor. As a noise there is also some splitter we add in order to have a better usable interface. Code for the presenter is quite the same as the one we use to present the main view. So similar that could be refactor in some base class, but it is not done at the moment.

What we have right now is this:

image

A little problem appear to be the cursor over the splitter. For some weird reason the WF controls seems to stole the mouse cursor management and there is no way out to change it.

A poor man workaround is to make the splitter turn to bold when the mouse hover on it:

       <Style TargetType="GridSplitter">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="true">
                    <Setter Property="Background" Value="Black"/>
                 </Trigger>
 
            </Style.Triggers>
        </Style>

 

As usual the code so far can be found here.

Monday, 07 March 2011 20:41:30 (GMT Standard Time, UTC+00:00)  #    Comments [2] - Trackback
Programmin | Recipes | ReHosting | WF4

# Sunday, 06 March 2011

When we host the activities palette, we notice a blue gear without meaning on each of our activity. This should be easy to solve, but it is not so easy for the built in activities. So what we have till now is:

image

We want to have something better, even for the standard activities. In WF4 is not said that the ToolboxBitmapAttribute, the one used to bind the image to show in the palette is attached to all activities, and we can say it is not for sure for the built in activities. Fortunately the WF4 architecture provides a way to attach attributes to objects runtime, this is achieved via the AttributeTableBuilder class, that provide a way to register and apply attributes runtime to objects via another helper class: MetadataStore. So if we have somewhere these icons, we can attach the proper attribute runtime, and all is done, but where to find it ?  Do we have proper licensing to distribute it ? A reply to this question could be found here, and in particular in the Brannon King’s comment. Brannon show how to grab the icons from the redistributable System.Activities.Presentation dll, and more, it post the whole code to provide the attributes creation and binding, and graphics retrieval too. So we grab this great work and we add it in our helper class ToolboxItemSource, so we can ensure designer has the proper graphic information for both built in and custom activities we add as a source. Let’s have a look at the code, that is just a little modified:

   1:                  var builder = new AttributeTableBuilder();
   2:                  foreach (var item in query)
   3:                  {
   4:                      AddIconAttributes(item, builder);
   5:                  }
   6:                  MetadataStore.AddAttributeTable(builder.CreateTable());

Well this portion is in the member function AddTools, already present in our helper, we basically creates an AttributeTableBuilder, then we add the resources attributes, and then we realize, at line 6, the association. Let’s have a look at the Brannon’s code, that we embed in the AddIconAttributes:

   1:        protected static  bool AddIconAttributes(Type type, AttributeTableBuilder builder)
   2:          {
   3:              var secondary = false;
   4:              var tbaType = typeof(ToolboxBitmapAttribute);
   5:              var imageType = typeof(System.Drawing.Image);
   6:              var constructor = tbaType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { imageType, imageType }, null);
   7:              string resourceKey = type.IsGenericType ? type.GetGenericTypeDefinition().Name : type.Name;
   8:              int index = resourceKey.IndexOf('`');
   9:              if (index > 0)
  10:              {
  11:                  resourceKey = resourceKey.Remove(index);
  12:              }
  13:              if (resourceKey == "Flowchart")
  14:              {
  15:                  resourceKey = "FlowChart"; // it appears that themes/icons.xaml has a typo here
  16:              }
  17:              resourceKey += "Icon";
  18:              Bitmap small, large;
  19:              object resource = resources[resourceKey];
  20:              if (!(resource is DrawingBrush))
  21:              {
  22:                  resource = resources["GenericLeafActivityIcon"];
  23:                  secondary = true;
  24:              }
  25:              var dv = new DrawingVisual();
  26:              using (var context = dv.RenderOpen())
  27:              {
  28:                  context.DrawRectangle(((DrawingBrush)resource), null, new Rect(0, 0, 32, 32));
  29:                  context.DrawRectangle(((DrawingBrush)resource), null, new Rect(32, 32, 16, 16));
  30:              }
  31:              var rtb = new RenderTargetBitmap(32, 32, 96, 96, PixelFormats.Pbgra32);
  32:              rtb.Render(dv);
  33:              using (var outStream = new MemoryStream())
  34:              {
  35:                  BitmapEncoder enc = new PngBitmapEncoder();
  36:                  enc.Frames.Add(BitmapFrame.Create(rtb));
  37:                  enc.Save(outStream);
  38:                  outStream.Position = 0;
  39:                  large = new Bitmap(outStream);
  40:              }
  41:              rtb = new RenderTargetBitmap(16, 16, 96, 96, PixelFormats.Pbgra32);
  42:              dv.Offset = new Vector(-32, -32);
  43:              rtb.Render(dv);
  44:              using (var outStream = new MemoryStream())
  45:              {
  46:                  BitmapEncoder enc = new PngBitmapEncoder();
  47:                  enc.Frames.Add(BitmapFrame.Create(rtb));
  48:                  enc.Save(outStream);
  49:                  outStream.Position = 0;
  50:                  small = new Bitmap(outStream);
  51:              }
  52:   
  53:              var tba = constructor.Invoke(new object[] { small, large }) as ToolboxBitmapAttribute;
  54:              builder.AddCustomAttributes(type, tba);
  55:              return secondary;
  56:          }

 

I’ve just stored the resources needed in a local static dictionary, by this call:

 

   1:  static ResourceDictionary resources = new ResourceDictionary
   2:   { Source = new Uri("pack://application:,,,/System.Activities.Presentation;component/themes/icons.xaml") };

 

Basically Brannon’s code use the assumption the icon is the name of the activity suffixed by “Icon”, except for a typo on FlowChart solved at lines 13-16. Then he create a bitmap and it save it on a memory stream. That stream is then use as an argument for ToolboxBitmapAttribute. So we put all together and we obtain this new presentation:

image

So we got rid of the blue gear. We will need the property editor next, always a XAML only strategy to present it has to be provided, probably with the same strategy we used for the main editor itself.

Have a look at the code here.

Sunday, 06 March 2011 22:10:10 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

# Saturday, 05 March 2011

In the previous post we had a look on how to host WF4 designer in pure markup, we continue now to host, always in markup, the toolbar for dropping activities into the designer surface. The ToolboxControl is fortunately a standard WPF control, so it is not a problem to use it in markup, but it is a little difficult to add a list of categorized activities, because we have to add as a content of the control a list of category, each with a list of category. We want to use some sort of activity list source instead. So let’s have a look at the markup that add a ToolBoxControl to our designer:

   1:   <host:MainViewPresenter HostingHelper="{StaticResource wfHost}"  Grid.Column="1" Grid.Row="1"/>
   2:   <toolbox:ToolboxControl Grid.Column="0" Grid.Row="1" host:ToolboxItemSource.CategorySource="{StaticResource GeneralTools}" >

So the line 2 is the markup tag for adding the ToolboxControl, but the interesting part is the:

host:ToolboxItemSource.CategorySource="{StaticResource GeneralTools}"

CategorySource is an attached property of type ToolboxItemSource, an helper class we use to make easier binding to a list of activities. In this particular case we have defined an instance on the resources, called GeneralTools, as below:

   1:   <host:ToolboxItemSource x:Key="GeneralTools">
   2:              <host:ToolboxSource TargetCategory="General" AllSiblingsOf="{x:Type activities:Delay}"  />
   3:              <host:ToolboxSource TargetCategory="Custom" AllSiblingsOf="{x:Type custom:Activity1}"  />
   4:   </host:ToolboxItemSource>

As we can see, we can add to the ItemSource a list of source of type ToolboxSource, an helper class too. That class has the only pourpose of collecting the target category name, and a type of one activity to pick an assembly and contextually add all activities contained in. Most of the job is done by ToolboxItemSource, when the property is actually attached, so the code below:

   1:          protected static void OnCategorySourceChanged(DependencyObject depobj,DependencyPropertyChangedEventArgs dpcea)
   2:          {
   3:              if (null != dpcea.NewValue && dpcea.NewValue is ToolboxItemSource)
   4:              {
   5:                  var tbsrc = dpcea.NewValue as ToolboxItemSource;
   6:                  foreach (var source in tbsrc.Sources)
   7:                  {
   8:                      AddTools(depobj as ToolboxControl, source);
   9:                  }
  10:              }
  11:          }
  12:   
  13:          private static void AddTools(ToolboxControl toolboxControl, ToolboxSource source)
  14:          {
  15:              if (null != source.AllSiblingsOf)
  16:              {
  17:                  var cat = toolboxControl.Categories.Where(q => q.CategoryName.Equals(source.TargetCategory)).FirstOrDefault();
  18:                  if (null == cat)
  19:                  {
  20:                      cat = new ToolboxCategory(source.TargetCategory);
  21:                      toolboxControl.Categories.Add(cat);
  22:                  }
  23:                  var query = from type in source.AllSiblingsOf.Assembly.GetTypes()
  24:                              where type.IsPublic &&
  25:                              !type.IsNested &&
  26:                              !type.IsAbstract &&
  27:                              !type.ContainsGenericParameters &&
  28:                              (typeof(Activity).IsAssignableFrom(type) ||
  29:                              typeof(IActivityTemplateFactory).IsAssignableFrom(type))
  30:                              orderby type.Name
  31:                              select new ToolboxItemWrapper(type);
  32:                  foreach (var item in query)
  33:                      cat.Add(item);
  34:              }

35: }

 

Very easy, when we attach the item source, we pump on the control all the required activities, and it’s done. So this is another step on a XAML only re-hosting, let’s see the result:

image

Well you will probably note the very fancy gear icon that is the “mean nothing” icon that we usually have when we forgot something, and actually we forgot something, but differently from the old workflow is not so easy: a look here could help, and we probably work on that direction in further steps.

All are welcome to grab or cooperate on the source code for this sample here.

Saturday, 05 March 2011 22:37:05 (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
Programmin | Recipes | ReHosting | WF4

This post is based on this great post from The Problem Solver, but here we try to use less code behind and more markup language, to let the “host” decide where to put the designer components in his application. Basically there is three main chunk involved in hosting the WF:

  1. The main designer surface
  2. The property view
  3. The toolbox.

Components 1) and 2) are exposed as property of the WorkflowDesigner class, that itself is not a UIElement, but a simple class derived from object. The two property of interest are View and PropertyInspectorView, and both are UIElements. The problem is: how to let’s the host application writer to decide where to place these in XAML ? Lets start creating a class that act as an helper, and provide inside it some initialization code:

namespace WF4Host
{
    public class HostingHelper:DependencyObject
    {
        public WorkflowDesigner Designer { get { return workflowDesigner;} }
        WorkflowDesigner workflowDesigner;
        public HostingHelper()
        {
            workflowDesigner = new WorkflowDesigner();
            new DesignerMetadata().Register();
            //Creates an empty workflow
            workflowDesigner.Load(new Sequence());
        }
    }
    
}

basically we creates an instance of the WorkflowDesigner itself, load a first Sequence activity, and start the designer runtime. In xaml we can declare such an object as a StaticResource:

   1:  <Window x:Class="WF4Host.MainWindow"
   2:          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          xmlns:host="clr-namespace:WF4Host"
   5:          xmlns:wf="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
   6:          Title="MainWindow" Height="650" Width="825"
   7:          >
   8:      <Window.Resources>
   9:          <host:HostingHelper x:Key="wfHost"/>
  10:          <LinearGradientBrush x:Key="backBrush" StartPoint="0,0" EndPoint="0,1" >
  11:              <GradientStop Offset="0.1" Color="DarkSlateBlue"></GradientStop>
  12:              <GradientStop Offset="0.2" Color="DarkSlateGray"/>
  13:              <GradientStop Offset="0.9" Color="BlanchedAlmond"/>
  14:          </LinearGradientBrush>
  15:      </Window.Resources>

 

Easy: at line 4 we add the namespace for our project. At line 9 we declare an instance of our HostingHelper and we name it wfHost. Remaining resources are just some beautiful ( ehm ) arts.

Then we need to present the graphical components. Lets declare in our code a control deriving from grid:

   1:  namespace WF4Host
   2:  {
   3:      public class MainViewPresenter : Grid
   4:      {
   5:          public HostingHelper HostingHelper
   6:          {
   7:              get { return (HostingHelper)GetValue(HostingHelperProperty); }
   8:              set { SetValue(HostingHelperProperty, value); }
   9:          }
  10:   
  11:          public static readonly DependencyProperty HostingHelperProperty =
  12:              DependencyProperty.Register("HostingHelper", typeof(HostingHelper)
  13:              , typeof(MainViewPresenter)
  14:              , new UIPropertyMetadata(null, new PropertyChangedCallback(OnHostingHelperChanged)));
  15:   
  16:          protected static void OnHostingHelperChanged(DependencyObject dobj
  17:              , DependencyPropertyChangedEventArgs dpcea)
  18:          {
  19:              if (dobj is MainViewPresenter)
  20:              {
  21:                  var mvp = dobj as MainViewPresenter;
  22:                  mvp.Children.Clear();
  23:                  if (dpcea.NewValue != null)
  24:                  {
  25:                      var host = dpcea.NewValue as HostingHelper;
  26:                      host.Designer.View.SetValue(Panel.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
  27:                      host.Designer.View.SetValue(Panel.VerticalAlignmentProperty, VerticalAlignment.Stretch);
  28:                      mvp.Children.Add(host.Designer.View);
  29:                  }
  30:              }
  31:          }
  32:      }
  33:  }

 

So this class is the MainView presenter. We call the Main View the one in which the WF is actually drawn. This control has a dependency property called HostingHelper, so we can bind it to the current HostingHelper that orchestrates the Workflow design. Let see how to do it in xaml:

   1:  <Grid Name="grid">
   2:              <Grid.RowDefinitions>
   3:                  <RowDefinition Height="32"/>
   4:                  <RowDefinition Height="*"/>
   5:              </Grid.RowDefinitions>
   6:                  <Grid.ColumnDefinitions>
   7:              <ColumnDefinition Width="100"></ColumnDefinition>
   8:              <ColumnDefinition></ColumnDefinition>
   9:              <ColumnDefinition Width="100"></ColumnDefinition>
  10:          </Grid.ColumnDefinitions>
  11:               
  12:              <host:MainViewPresenter
  13:               HostingHelper="{StaticResource wfHost}"  Grid.Column="1" Grid.Row="1"/>
  14:              
  15:          </Grid>

So at line 12 we declare our presenter, and we bind his hosting helper property to the hosting helper instance wfHost.

This is the current result:

image

So we achieved less code behind and more “blendability” by creating a little tool library. We should improve it with others components to complete the designer environment. I decided to publish this example as a project on codeplex, so you can join to use and improve it.

Saturday, 05 March 2011 10:49:00 (GMT Standard Time, UTC+00:00)  #    Comments [2] - Trackback
Programmin | Recipes | ReHosting | WF4

My Stack Overflow
Contacts

Send mail to the author(s) E-mail

Tags
profile for Felice Pollano at Stack Overflow, Q&A for professional and enthusiast programmers
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2017
Felice Pollano
Sign In
Statistics
Total Posts: 157
This Year: 0
This Month: 0
This Week: 0
Comments: 124
This blog visits
All Content © 2017, Felice Pollano
DasBlog theme 'Business' created by Christoph De Baene (delarou) and modified by Felice Pollano
Nike Winkels Nederland Outlet Nike Nederland Store Outlet Nike Nederland 2015 Outlet Nike Outlet Online Nike Sneakers Outlet