[How To] Save WF4 Workflow Definition to Image Using Code

[How To] Save WF4 Workflow Definition to Image Using Code

Rate This
  • Comments 2

One of the benefits of using WF4 to create workflow is that it provides a graphical workflow designer. This means in most of the cases, developers don’t need to write code to define their workflows. Instead, they can use the graphical workflow designer we provided. This can not only dramatically improve the productivity but also bring down the barrier of authoring workflows so that even non-developers can do so. Another advantage of this kind of graphical design is that it’s intuitive and easy to understand. This is why it’s been used so far in almost all kinds of workflow solutions.

In some scenarios, you may want to save your workflow definitions to images so that you can use them later. For example:

  1. Attach them in some document
  2. Show them in some web pages to illustrate the definitions of the workflows
  3. Etc.

You may have already noticed that you can save the workflow definition to an image by right click on the designer surface and like Copy as Image or Save as Image. However, this is a manual approach. What if I want to write some code to save the images? You may ask.

Good news is, you can do it. Bad news is, you need to write some code, maybe a little bit more than you expected. (If you don’t want read the entire blog, you can find the sample I used here)

“What the hack?” You may wonder. Shouldn’t there be a public API for me to use? Even WF 3.x has that API. Well, it appears to be it should till you get to know why we didn’t provide one. But hanging there. It’s not hard to implement it by yourself. I promise. It will also help you to have a better understanding about how workflow designer works.

Why there is no public API for this?

WF4 workflow designer is written in WPF while WF 3.x workflow designer is written in WinForm. This is a very significant difference. WPF doesn’t support generating an image of the UI when the UI itself has been drawn yet. So in order to save a workflow definition to an image, you need to create and actually make WPF draw the workflow definition in a workflow designer first.

This is the main reason why we didn’t provide any public API for saving as image. Because even if we did, you will only be able to use it after the workflow designer is opened, which is confusing. And it’s hard to imagine Microsoft will release an API in .NET Framework saying “You have to call it after you open the workflow designer UI”. That’s just bad API.

Basic idea of the solution

OK, knowing why we didn’t provide a public API for this. Let’s look at how we are going to achieve the goal by ourselves. At the high level, we need to

  1. Create a workflow designer
  2. Load the workflow definition which you want to save to an image
  3. Make sure the workflow designer is drawn by WPF (basically putting it into a visible UI control)
  4. Capture an image of the workflow definition
  5. Save the image to a file
  6. Close the workflow designer

For the sake of convenience, I won’t show you how to create a rehost workflow designer. Instead, I’ll jump in directly to the “save image” part. You can find a sample of creating rehost workflow designer here.

Let’s try some code

OK, let’s write some code. Following is a code snippet showing the basic idea of a rehost workflow designer.

namespace Microsoft.Samples.DesignerRehosting
{
    public partial class RehostingWfDesigner : Window
    {
        private WorkflowDesigner workflowDesigner;

        public RehostingWfDesigner()
        {
            InitializeComponent();
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);

            // register metadata
            (new DesignerMetadata()).Register();
            // create a big workflow for saving image
            Activity workflow = new Sequence
            {
                Activities = { 
                    new Sequence(), new Sequence(), new Sequence(),
                    new Sequence(), new Sequence(), new Sequence(),
                    new Sequence(), new Sequence(), new Sequence()
                }
            };
            // create the workflow designer and load the workflow
            workflowDesigner = new WorkflowDesigner();
            workflowDesigner.Load(workflow);
            DesignerBorder.Child = workflowDesigner.View;
        }
    }
}

Now let’s look at how to generate an image of the workflow definition using the standard mechanism of WPF. After all, workflow designer canvas is a WPF control.

BitmapFrame CreateWorkflowDefinitionImage()
{
    const double DPI = 96.0;
    // this is the designer area we want to save
    Visual areaToSave = ((DesignerView)VisualTreeHelper.GetChild(
        this.workflowDesigner.View, 0)).RootDesigner;
    // get the size of the targeting area
    Rect size = VisualTreeHelper.GetDescendantBounds(areaToSave);
    RenderTargetBitmap bitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height,
       DPI, DPI, PixelFormats.Pbgra32);
    bitmap.Render(areaToSave);
    return BitmapFrame.Create(bitmap);
}

The above C# method is very straightforward. Just get the workflow diagram part of the workflow designer and create an in-memory image of it using some WPF API. The next thing is simple: create a file and save the image.

void SaveImageToFile(string fileName, BitmapFrame image)
{
    using (FileStream fs = new FileStream(fileName, FileMode.Create))
    {
        BitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(image));
        encoder.Save(fs);
        fs.Close();
    }
}

At last, let’s try to invoke the above 2 methods in the OnInitialized() method to hook it up and then close the application.

protected override void OnInitialized(EventArgs e)
{
    // ...
    this.SaveImageToFile("test.jpg", this.CreateWorkflowDefinitionImage());
    Application.Current.Shutdown();
}

Exception? What the hell?

Run the code to have a try. Wait a sec, exception thrown. What?

After some debugging work, it’s not hard to find that the root cause of this exception is because the size of the UI control we are trying to capture is negative.

Why? This is because although we invoked the above 2 methods to save the image after creating the workflow designer, the workflow designer hasn’t been fully created by WPF yet.

You may know that every WPF UI control has a LayoutUpdated event, meaning the layout has been completed. This sounds like a plan. However, only this is not enough, ‘cause before workflow designer draws the workflow definition completely, the layout will be changed multiple times, which will trigger multiple LayoutUpdated events. In order to solve this issue, we need a Timer.

private Timer timer;
private delegate void TimerDelegate();

protected override void OnInitialized(EventArgs e)
{
    this.timer = new System.Timers.Timer(1000);
    this.timer.AutoReset = false;
    this.timer.Elapsed += new ElapsedEventHandler(CaptureTimer_Elapsed);
    this.timer.Start();
    workflowDesigner.View.LayoutUpdated += new EventHandler(View_LayoutUpdated);
}

void CaptureTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    this.timer.Stop();
    this.timer = null;
    this.workflowDesigner.View.Dispatcher.BeginInvoke(
        System.Windows.Threading.DispatcherPriority.ApplicationIdle,
        (TimerDelegate)delegate()
        {
            this.SaveImageToFile("test.jpg", this.CreateWorkflowDefinitionImage());
            Application.Current.Shutdown();
        });
}

void View_LayoutUpdated(object sender, EventArgs e)
{
    if (this.timer != null)
    {
        this.timer.Stop();
        this.timer.Start();
    }
}

Now let’s try it again. Good! No exceptions any more! Check out the bin\Debug folder. Bam, there it is, test.jpg.

No exception, good. But wait, why I get a uncompleted image?

Now you may think it’s done, which unfortunately is not the case. Open the image and take a look. You will find that the there will be several Sequence activities at the bottom shown as blank box. Why did this happen? (The image should be vertical. I rotated the image here in order to save some space.)

In order to understand this, we need to discuss a advanced mechanism of workflow designer: Virtualization. Imagine you have a huge workflow. When you open it in workflow designer, since the viewport is a fixed size (based on the size of your workflow designer window), it’s a big waste to draw all parts of the workflow since only a small portion will be shown at once. So what virtualization does is delay the drawing of the workflow diagram till when you really need it, which means when you move the viewport to the specific region. This can improve the performance of workflow designer dramatically.

However, it also causes some trouble when you are trying to save an image. Because due to virtualization when you invoke the methods to save the image, the workflow diagram hasn’t been drawn completely yet.

Filling the last piece

So how can we solve this problem? Well, you may notice that there is a Fit to Screen button in workflow designer. The only we need to do is to use this button to show the entire workflow before capturing the image. See the following code.

private bool IsVirtualized = false;

void View_LayoutUpdated(object sender, EventArgs e)
{
    if (!IsVirtualized)
    {
        ((RoutedCommand)DesignerView.FitToScreenCommand).Execute(null,
            this.workflowDesigner.Context.Services.GetService<DesignerView>());
        IsVirtualized = true;
    }
    if (this.timer != null)
    {
        this.timer.Stop();
        this.timer.Start();
    }
}

Now let’s run the project for one last time. Bingo! All the activities have been captured successfully!

Conclusion

I hope this is not too overwhelming to you. Although the code I showed above is not the most intuitive one, but I hope you can understand why we need to write it in this way. Again, we would love to provide a kick-ass API but there are constraints.

You can find the full sample code here. Hope this would help and let me know if you have any questions/feedbacks.

  • Is it possible to create image of workflow using dll function .

    first of all i am using wf 4.0

    i am creating an project file for  activity  creation using code not using designer.

    then need to create a function inside that  activity project which will create image based on activity.

    and this image need to be shown using asp.net web application ..

    Is it possible

    Thanks in advance

  • My question is it possible to create image with out loading to WPF Framework..

Page 1 of 1 (2 items)
Leave a Comment
  • Please add 3 and 4 and type the answer here:
  • Post