• mwinkle.blog

    Swiss Cheese and WF4, or, An Introduction to ActivityAction

    • 8 Comments

    swiss cheese

    One common scenario that was often requested by customers of WF 3 was the ability to have templated or “grey box” or “activities with holes” in them (hence the Swiss cheese photo above).  In WF4 we’ve done this in a way that way we call ActivityAction

    Motivation

    First I’d like to do a little bit more to motivate the scenario. 

    Consider an activity that you have created for your ERP system called CheckInventory.  You’ve gone ahead and encapsulated all of the logic of your inventory system, maybe you have some different paths of logic, maybe you have interactions with some third party systems, but you want your customers to use this activity in their workflows when they need to get the level of inventory for an item. 

    Consider more generally an activity where you have a bunch of work you want to get done, but at various, and specific, points throughout that work, you want to allow the consumer of that activity to receive a callback and provide their own logic to handle that.  The mental model here is one of delegates.

    Finally, consider providing the ability for a user to specify the work that they want to have happen, but also make sure that you can strongly type the data that is passed to it.  In the first case above, you want to make sure that the Item in question is passed to the action that the consumer supplies. 

    In wf3, we had a lot of folks want to be able to do something like this. It’s a very natural extension to wanting to model things as activities and composing into higher level activities.  We like being able to string together 10 items as a black box for reuse, but we really want the user to specify exactly what should happen between steps 7 and 8. 

    A slide that I showed at PDC showed it this way (the Approval and Payment boxes represent the places I want a consumer to supply additional logic):

    image 

     

     

    Introducing ActivityAction

    Very early on the release, we knew this was one of the problems that we needed to tackle. The mental model that we are most aligned with is that of a delegate/ callback in C#.  If you think about a delegate, what are you doing, you are giving an object the implementation of some bit of logic that the object will subsequently call.  That’s the same thing that’s going on with an ActivityAction.  there are three important parts to an ActivityAction

    • The Handler (this is the logic of the ActivityAction)
    • The Shape (this determines the data that will be passed to the handler)
    • The way that we invoke it from our activity

    Let’s start with some simple code (this is from a demo that I showed in my PDC talk).  This is a timer activity which allows us to time the execution for the contained activity and then uses an activity action to act on the result.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Activities;
    using System.Diagnostics;
     
    namespace CustomActivities.ActivityTypes
    {
     
        public sealed class Timer : NativeActivity<TimeSpan>
        {
            public Activity Body { get; set; }
            public Variable<Stopwatch> Stopwatch { get; set; }
            public ActivityAction<TimeSpan> OnCompletion { get; set; }
     
            public Timer()
            {
                Stopwatch = new Variable<Stopwatch>();
            }
     
            protected override void CacheMetadata(NativeActivityMetadata metadata)
            {
                metadata.AddImplementationVariable(Stopwatch);
                metadata.AddChild(Body);
                metadata.AddDelegate(OnCompletion);
            }
     
            protected override void Execute(NativeActivityContext context)
            {
                Stopwatch sw = new Stopwatch();
                Stopwatch.Set(context, sw);
                sw.Start();
                // schedule body and completion callback
                context.ScheduleActivity(Body, Completed);
     
            }
     
            private void Completed(NativeActivityContext context, ActivityInstance instance)
            {
                if (!context.IsCancellationRequested)
                {
                    Stopwatch sw = Stopwatch.Get(context);
                    sw.Stop();
                    Result.Set(context, sw.Elapsed);
                    if (OnCompletion != null)
                    {
                        context.ScheduleAction<TimeSpan>(OnCompletion, Result.Get(context));
                    }
                }
            }
     
            protected override void Cancel(NativeActivityContext context)
            {
                context.CancelChildren();
                if (OnCompletion != null)
                {
                    context.ScheduleAction<TimeSpan>(OnCompletion, TimeSpan.MinValue);
                }
            }
        }
    }

    A few things to note about this code sample:

    • The declaration of an ActivityAction<TimeSpan> as a member of the activity.  You’ll note we use the OnXXX convention often for activity actions.
    • The usage of the ActivityAction<T> with on type argument.  The way to read this, or any of the 15 other types is that the T is the type of the data that will be passed to the activity action’s handler.
      • Think about this like an Activity<Foo> corresponding to a void DoSomething(Foo argument1) method
    • The call to NativeActivityMetadata.AddDelegate() which lets the runtime know that it needs to worry about the delegate
    • The code in the Completed( ) method which checks to see if OnCompletion is set and then schedules it using ScheduleAction.  I want to call out that line of code.
    if (OnCompletion != null)
    {
        context.ScheduleAction<TimeSpan>(OnCompletion, Result.Get(context));
    }

    It is important to note that I use the second parameter (and the third through 16th if that version is provided) in order to provide the data.  This way, the activity determines what data will be passed to the handler, allowing the activity to determine what data is visible where.  This is a much better way than allowing an invoked child to access any and all data from the parent.  This lets us be very specific about what data goes to the ActivityAction.   Also, you could make it so that OnCompletion must be provided, that is, the only way to use the activity is to supply an implementation.  If you have something like “ProcessPayment” you likely want that to be a required thing.  You can use the CacheMetadata method in order to check and validate this.

    Now, let’s look at the code required to consume this time activity:

    DelegateInArgument<TimeSpan> time = new DelegateInArgument<TimeSpan>();
    a = new Timer
    {
        Body = new HttpGet { Url = "http://www.microsoft.com" },
        OnCompletion = new ActivityAction<TimeSpan>
        {
            Argument = time,
            Handler = new WriteLine { 
                Text = new InArgument<string>(
                    ctx => 
                        "Time input from timer " + time.Get(ctx).TotalMilliseconds)
            } 
    
        }
    };

    There are a couple of interesting things here:

    • Creation of DelegateInArgument<TimeSpan> : This is used to represent the data passed by the ActivityAction to the handler
    • Creation of the ActivityAction to pass in.  You’ll note that the Argument property is set to the DelegateInArgument, which we can then use in the handler
    • The Handler is the “implementation” that we want to invoke.  here’s it’s pretty simple, it’s a WriteLine and when we construct the argument we construct if from a lambda that uses the passed in context to resolve the DelegateInArgument when that executes.

    At runtime, when we get to the point in the execution of the Timer activity, the WriteLine that the hosting app provided will be scheduled when the ScheduleAction is called.  This means we will output the timing information that the Timer observed.  A different implementation could have an IfThen activity and use that to determine if an SLA was enforced or not, and if not, send a nasty email to the WF author.  The possibilities are endless, and they open up scenarios for you to provide specific extension points for your activities.

    That wraps up a very brief tour of ActivityAction.  ActivityAction provides a easy way to create an empty place in activity that the consumer can use to supply the logic that they want executed.  In the second part of this post, we’ll dive into how to create a designer for one of these, how to represent this in XAML, and a few other interesting topics.

    It’s that time of year that I’ll be taking a little bit of time off for the holidays, so I will see y’all in 2010!

    Photo Credit

  • mwinkle.blog

    Inspection, Default Services and Items (WF4 EditingContext Intro Part 6)

    • 0 Comments

    This part 6 of my 6  part series on the EditingContext.

  • Introduction
  • Sharing Functionality between Designers 
  • Host provided capabilities  
  • Providing callbacks for the host 
  • Subscription/Notification engine
  • Inspection, Default Services and Items (you are here)

    I want to wrap up this series of posts by posting some code for an activity designer that functions more as a diagnostic tool, and will display all of the Items and services of the EditingContext within the designer.  This will be useful from an investigation perspective, and hopefully as a diagnostic tool.  We will use this to help us understand what are the services that are available out of the box in VS, as well as in a rehosted application. 

    We first need to create an empty activity to attach a designer to.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Activities;
    using System.ComponentModel;
    
    namespace blogEditingContext
    {
        [Designer(typeof(DiagnosticDesigner))]
        public sealed class Diagnosticator : CodeActivity
        {
            // Define an activity input argument of type string
            public InArgument<string> Text { get; set; }
    
            // If your activity returns a value, derive from CodeActivity<TResult>
            // and return the value from the Execute method.
            protected override void Execute(CodeActivityContext context)
            {
                // Obtain the runtime value of the Text input argument
                string text = context.GetValue(this.Text);
            }
        }
    }

    Now, let’s create our designer.  We could do fancy treeviews or object browser style UI’s, but as this is a blog post, I want to provide you with the basics, and then let you figure out how that is most useful to you.  So, we will just create a designer that writes out to debug output the relevant information. 

    <sap:ActivityDesigner x:Class="blogEditingContext.DiagnosticDesigner"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
        xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
      <Grid>
            <Button Click="Button_Click">Debug.WriteLine Context Data</Button>
        </Grid>
    </sap:ActivityDesigner>

    And now the code

    using System.Diagnostics;
    using System.Linq;
    using System.Windows;
    
    namespace blogEditingContext
    {
        // Interaction logic for DiagnosticDesigner.xaml
        public partial class DiagnosticDesigner
        {
            public DiagnosticDesigner()
            {
                InitializeComponent();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                // the goal here is to output meaningful and useful information about 
                // the contents of the editing context here. 
                int level = Debug.IndentLevel;
                Debug.WriteLine("Items in the EditingContext");
                Debug.IndentLevel++;
                foreach (var item in Context.Items.OrderBy(x => x.ItemType.ToString()))
                {
                    Debug.WriteLine(item.ItemType);
                }
    
                Debug.IndentLevel = level;
                Debug.WriteLine("Services in the EditingContext");
                foreach (var service in Context.Services.OrderBy(x => x.ToString()))
                {
                    Debug.WriteLine(service);
                }
            }
        }
    }

    Let’s break this down.  The work here happens in the button click where we simply order by types’ string representations and output them to the debug writer (a more robust implementation might use a trace writer that could be configured in the app, but for this purpose, this will be sufficient.

    So, what output do we get?

    VS Standard Services and Items

    We determine this by using the activity in a freshly opened WF project

    Items

     

    System.Activities.Presentation.Hosting.AssemblyContextControlItem
    System.Activities.Presentation.Hosting.ReadOnlyState
    System.Activities.Presentation.Hosting.WorkflowCommandExtensionItem
    System.Activities.Presentation.View.Selection
    System.Activities.Presentation.WorkflowFileItem

    Services

    System.Activities.Presentation.Debug.IDesignerDebugView
    System.Activities.Presentation.DesignerPerfEventProvider
    System.Activities.Presentation.FeatureManager
    System.Activities.Presentation.Hosting.ICommandService
    System.Activities.Presentation.Hosting.IMultiTargetingSupportService
    System.Activities.Presentation.Hosting.WindowHelperService
    System.Activities.Presentation.IActivityToolboxService
    System.Activities.Presentation.IIntegratedHelpService
    System.Activities.Presentation.IWorkflowDesignerStorageService
    System.Activities.Presentation.IXamlLoadErrorService
    System.Activities.Presentation.Model.AttachedPropertiesService
    System.Activities.Presentation.Model.ModelTreeManager
    System.Activities.Presentation.Services.ModelService
    System.Activities.Presentation.Services.ViewService
    System.Activities.Presentation.UndoEngine
    System.Activities.Presentation.Validation.IValidationErrorService
    System.Activities.Presentation.Validation.ValidationService
    System.Activities.Presentation.View.ActivityTypeDesigner+DisplayNameUpdater
    System.Activities.Presentation.View.DesignerView
    System.Activities.Presentation.View.IExpressionEditorService
    System.Activities.Presentation.View.ViewStateService
    System.Activities.Presentation.View.VirtualizedContainerService

     

    Basic Rehosted Application Standard Services and Items

    Items

    System.Activities.Presentation.Hosting.ReadOnlyState
    System.Activities.Presentation.Hosting.WorkflowCommandExtensionItem
    System.Activities.Presentation.View.Selection

    Services

    System.Activities.Presentation.DesignerPerfEventProvider
    System.Activities.Presentation.FeatureManager
    System.Activities.Presentation.Hosting.WindowHelperService
    System.Activities.Presentation.Model.AttachedPropertiesService
    System.Activities.Presentation.Model.ModelTreeManager
    System.Activities.Presentation.Services.ModelService
    System.Activities.Presentation.Services.ViewService
    System.Activities.Presentation.UndoEngine
    System.Activities.Presentation.Validation.ValidationService
    System.Activities.Presentation.View.DesignerView
    System.Activities.Presentation.View.ViewStateService
    System.Activities.Presentation.View.VirtualizedContainerService

    Comparison Table View

    Items VS Rehosted

    System.Activities.Presentation.Hosting.AssemblyContextControlItem 

    Yes

    No

    System.Activities.Presentation.Hosting.ReadOnlyState 

    Yes

    Yes

    System.Activities.Presentation.Hosting.WorkflowCommandExtensionItem 

    Yes

    Yes

    System.Activities.Presentation.View.Selection 

    Yes

    Yes

    System.Activities.Presentation.WorkflowFileItem 

    Yes

    No

    Services VS Rehosted 

    System.Activities.Presentation.Debug.IDesignerDebugView 

    Yes

    No

    System.Activities.Presentation.DesignerPerfEventProvider 

    Yes

    Yes

    System.Activities.Presentation.FeatureManager 

    Yes

    Yes

    System.Activities.Presentation.Hosting.ICommandService 

    Yes

    No

    System.Activities.Presentation.Hosting.IMultiTargetingSupportService 

    Yes

    No

    System.Activities.Presentation.Hosting.WindowHelperService 

    Yes

    Yes

    System.Activities.Presentation.IActivityToolboxService 

    Yes

    No

    System.Activities.Presentation.IIntegratedHelpService 

    Yes

    No

    System.Activities.Presentation.IWorkflowDesignerStorageService 

    Yes

    No

    System.Activities.Presentation.IXamlLoadErrorService 

    Yes

    No

    System.Activities.Presentation.Model.AttachedPropertiesService 

    Yes

    Yes

    System.Activities.Presentation.Model.ModelTreeManager 

    Yes

    Yes

    System.Activities.Presentation.Services.ModelService 

    Yes

    Yes

    System.Activities.Presentation.Services.ViewService 

    Yes

    Yes

    System.Activities.Presentation.UndoEngine 

    Yes

    Yes

    System.Activities.Presentation.Validation.IValidationErrorService 

    Yes

    No

    System.Activities.Presentation.Validation.ValidationService 

    Yes

    Yes

    System.Activities.Presentation.View.ActivityTypeDesigner+DisplayNameUpdater 

    Yes

    No

    System.Activities.Presentation.View.DesignerView 

    Yes

    Yes

    System.Activities.Presentation.View.IExpressionEditorService 

    Yes

    No

    System.Activities.Presentation.View.ViewStateService 

    Yes

    Yes

    System.Activities.Presentation.View.VirtualizedContainerService 

    Yes

    Yes

     

     

     

     

    Conclusion

    This wraps up our series on the editing context.  We’ve gone through the basics of why we need it, what we can do with it, and then we moved how to use it, from both the very simple to the very complex.  We’ve finished with a diagnostic tool to help understand what all items I can bind to.

    What’s Next From Here?

    A few ideas for the readers who have read all of these:

    • Wire up a few attached properties to reflect back through to some interesting data (like if it is selected).  These attached properties could then be used directly by your UI (via the binding in XAML) to let your designers display and react to changes in the data
    • Think about ideas for services you might want to add in VS without depending on an activity to inject it (and send me mail, I am trying to compile a list of interesting things)
    • Are there service/item implementations you want to override in VS?
    • Is there a service/item you expect to see that is not there?

    Thanks for now!

     

     

     

  • mwinkle.blog

    Subscription / Notification Engine (WF4 EditingContext Intro Part 5)

    • 2 Comments

    This part 5 of my 6  part series on the EditingContext.

    In this post, we’re going to tie together a few of the things we’ve seen in the last few posts and show how we can wire up parts of the designer (or the hosting application) to changes made to the Items collection of the EditingContext to do some interesting things.

    You will note that both the ServiceManager and ContextItemManager have Subscribe methods, and I’ve talked in previous posts about how the publish mechanism is a little different.  I want to dive a little deeper into how these work, the different overloads, and what you can expect have happen on the subscribe side of things.

    Services

    On service, there are two different publish methods.  I will list all four, and then talk about how there are really only two :-)

    Method

    Description

    Publish(Type, PublishServiceCallback) Publishes the specified service type, but does not declare an instance. When the service is requested, the Publish service callback will be invoked to create the instance. The callback is invoked only once. After that, the instance it returned is cached.
    Publish(Type, Object) Publishes the given service. After it is published, the service instance remains in the service manager until the editing context is disposed of.
    Publish<(Of <(TServiceType>)>)(TServiceType) Publishes the given service. After it is published, the service instance remains in the service manager until the editing context is disposed of.
    Publish<(Of <(TServiceType>)>)(PublishServiceCallback<(Of <(TServiceType>)>)) Publishes the given service type, but does not declare an instance yet. When the service is requested, the PublishServiceCallback will be invoked to create the instance. The callback is invoked only once. After that, the instance it returned is cached.

    There are really only two methods here, and some generic sugar for the other two.  They are Publish(Type, PublishServiceCallback), and Publish(Type, Object).  If you were to look at the implementation of the generic versions, they simply turn around and call the un-generic form.

    The difference between the basic one (Publish(Type, Object)) and the version with the callback is that the callback enables us to be a little more lazy and wait to actually create the instance of the object until it is first requested.  Let’s look at how PublishServiceCallback is defined:

    public delegate Object PublishServiceCallback(
        Type serviceType
    )

    What this lets us do is that the first time someone calls GetService, this method will be called with the responsibility of returning an instance of the service type.  Subsequent calls to GetService will simply return the instance returned by the method provided fro the PublishServiceCallback.  It is important to note that on Publish, any subscribers will be notified.  As the Subscribe callback takes an instance, we will internally call GetService on the notification, which will in turn call the PublishServiceCallback to instantiate an object.  If we have subscribers, our publish is less lazy (but that’s by design, we have consumers who are ready and waiting to consume).

    Let’s now look at the subscribe methods.  Again, here there are two methods (generic and non-generic), but they both do the same thing:

    Method

    Description

    Subscribe Invokes the provided callback when someone has published the requested service. If the service was already available, this method invokes the callback immediately.
    Subscribe<(Of <(TServiceType>)>) Invokes the provided callback when someone has published the requested service. If the service was already available, this method invokes the callback immediately.

    Both of these use a SubscribeServiceCallback defined as the following

    public delegate void SubscribeServiceCallback(
        Type serviceType,
        Object serviceInstance
    )

    This allows any consumer to be notified when a service is initially published.  This is an important distinction we will call out versus items which provide a more advanced subscription method (namely, to changes).

    Why is this Useful?

    Generally we find this useful for a few reasons:

    • Services may not be available at designer initialization, or the order in which they are created may not be fixed (it is ultimately up to the host to determine this). 
    • Services may be provided by the host.  It is possible your activity designer may be used in a host that does not provide that service.  You may want to be flexible in handling that
    • Services can be injected at any time.  A publish – subscribe model lets us have a little more flexibility to react to new services as they are added.  You could imagine a spell checking service that a host only provides on the first time someone hits “spell check.”  When this service comes online, then our designers can decide to consume this.
    • Flipping things around, you may want to use a service to callout to a host, and you would like the host to subscribe for when an certain activity designer will publish a service.

    Now let’s talk about Items:

    Items

    Items do not have a publish method, per se, but they have the SetValue method which basically publishes an instance to the context.  The semantics of SetValue are that it will first attempt to store the new value.  Provided that succeeds, we then call OnItemChanged on the ContextItem itself.  This is basically notifies the object itself (giving it a chance to react, clean up, or throw if something is really wrong).  If this throws, the old value is preserved.  If this succeeds, we then notify anyone who has subscribed to the changes. 

    GetValue allows me to retrieve the ContextItem.  There are two GetValue’s, one generic, the other non-generic, but with a type as its parameter.  It is important to note the point that is also present in the docs.  If there is not an item present when this is called, the default value will be instantiated and returned. 

    Provided items are written using SetValue, all of the subscribers will be subsequently notified.  If I just do an arbitrary GetValue and then make a few changes without calling SetValue, by default nothing interesting is going to happen (that is, no subscribers will be notified, subsequent calls to GetValue will get the updated object however).  Subscribe (and it’s generic counterpart) allow me to provide a SubscribeContextCallback which will be invoked whenever SetValue is called.   This functions basically in the same way that it does for Services.

    An interesting pattern for Items that we use in a few places throughout the designer is to create an AttachedProperty on the modelItem (similar to this post) which in the implementation of the Getter and Setter will call out to the editing context to get or set the value from a ContextItem.  This gives me a WPF friendly binding surface (foo.Bar binding syntax) that we can wire up to be change aware.  We do this for a number of our triggers within our style implementation for things like is selected, etc.  Future post note for me is that I should go through all of the attached properties present on a ModelItem that you could use to bind to :-)

     

    This wraps up a tour of the Subscription / Notification engine present within the EditingContext. 

  • mwinkle.blog

    Providing Callbacks for the Host (WF4 EditingContext Intro Part 4)

    • 0 Comments

    This part 4 of my 6  part series on the EditingContext.

     

    In addition to having a host provide an instance of a type to be used within the designer, it can also be used to pass an instance that will route callbacks to the host.  I covered this briefly in a previous post (Displaying Validation Errors in a Rehosted WF4 Designer).  In that case, we provide an implementation of IValidationErrorService, which the designer infrastructure will call, if available, towards the end of a completion of a validation episode.  In the sample application in that post, we use that instance to route, and display the validation errors in the system.

    Rather than duplicate the code, I will simply encourage you to check out that post and think about the way you could publish a service that your activity designers know about, and use it as a mechanism for calling methods within the hosting application. 

  • mwinkle.blog

    Host Provided Capabilities (WF4 EditingContext Intro Part 3)

    • 0 Comments

    This part 3 of my 6  part series on the EditingContext.

  • Introduction
  • Sharing Functionality between Designers 
  • Host provided capabilities  (you are here)
  • Providing callbacks for the host
  • Subscription/Notification engine
  • Inspection, Default Services and Items

    EditingContext is used by our primary hosting application, Visual Studio, to provide concrete implementations of certain services.  The example that we will talk about here is the IExpressionEditorService.  Now, one thing that we would have really liked to have done in vs2010 is to provide a way to use the intellisense enabled VB editor that you see within VS inside a rehosted app.  For various reasons, we were not able to ship with that dependency in the .NET framework.  However, we needed a mechanism for our primary host to have the intellisense experience (and similarly, you could build an experience, or maybe we’ll ship one in the future for rehosted apps). 

    Let’s look at the design of IExpressionEditorService:

     

    Name Description
    CloseExpressionEditors Closes all the active expression editors.
    CreateExpressionEditor Overloaded. Creates a new expression editor.
    UpdateContext Updates the context for the editing session.

    Inside the ExpressionTextBox control, when the control needs to render the editor, it has code that looks like the following (note, if it can’t find an instance, it skips and just uses a plain old text box):

    if (this.Context.Services.GetService<IExpressionEditorService>() != null)
    {
          return this.Context.Services.GetService<IExpressionEditorService>().CreateExpressionEditor(...)
    }

    Using the following overload of CreateExpressionEditor:

    IExpressionEditorInstance CreateExpressionEditor(
        AssemblyContextControlItem assemblies,
        ImportedNamespaceContextItem importedNamespaces,
        List<ModelItem> variables,
        string text
    )

    Now, what happens is that inside the code that we ship in the Microsoft.VisualStudio.Activities.Addin.dll, there is both a concrete implementation of this type, as well as the code which will publish an instance of this to the editing context.  Remember, this is the same thing that you can do in your app for a these host provided services.  In a subsequent post, I will get into more details of what are the ones that the designer has built in (like IExpressionEditorService). 

  • mwinkle.blog

    Sharing Functionality Between Designers (WF4 EditingContext Intro Part 2)

    • 10 Comments

    This part 2 of my 6 part series on the EditingContext.

     

    Setup

    We will need a custom activity, EmptyOne and designer called InteractWithServiceDesigner. 

    using System.Activities;
    using System.ComponentModel;
    
    namespace blogEditingContext
    {
        [Designer(typeof(InteractWithServicesDesigner))]
        public sealed class EmptyOne : CodeActivity
        {
            // Define an activity input argument of type string
            public InArgument<string> Text { get; set; }
    
            // If your activity returns a value, derive from CodeActivity<TResult>
            // and return the value from the Execute method.
            protected override void Execute(CodeActivityContext context)
            {
                // Obtain the runtime value of the Text input argument
                string text = context.GetValue(this.Text);
            }
        }
    }

    What We Will See

    The designers for Foo will leverage a new service in order to display a list of database tables.  We will also need to publish this service to the editing context, and handle the fact that we don’t know who might publish it (or when it might be published).  Note that in VS, there is no way to inject services except by having an activity designer do it.   In a rehosted app, the hosting application could publish additional services (see part 4) that the activities can consume.  In this case though, we will use the activity designer as our hook.

    Publishing a Service

    Let’s look at the designer for Foo (as Foo is our generic, and relatively boring activity).

    <sap:ActivityDesigner x:Class="blogEditingContext.InteractWithServicesDesigner"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
        xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
        <StackPanel>
            <ListBox Height="100" Name="listBox1" Width="120" />
            <Button Name="button1" Click="Button_Click">Publish Service</Button>
        </StackPanel>
    </sap:ActivityDesigner>

    Not much to this, except a drop down list that is currently unbound (but a name is provided).  Also note that there is a button that says to “publish the service”.  Let’s first look at the code for the button click

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (!this.Context.Services.Contains<ISampleService>())
        {
            this.Context.Services.Publish<ISampleService>(new SampleServiceImpl());
        }
    }

    What are we doing here?  We first check if this service is already published using the Contains method.  We can do this because ServiceManager implements IEnumerable<Type>.

    [update, finishing this sentence.]  One could also consume the service using  GetService<TResult>.  You may also note that there is a GetRequiredService<T>.  This is a call that we know won’t return null, as the services we are requesting must be there for the designer to work.  Rather than returning null, this will throw an exception. Within the designer, we generally think of one service as required:

    Let’s look at the definition of the service.  Here you can see that we are using both an interface and then providing an implementation of that interface.  You could just as easily use an abstract class, or even a concrete class, there is no constraint on the service type.

    using System.Collections.Generic;
    
    namespace blogEditingContext
    {
        public interface ISampleService
        {
            IEnumerable<string> GetDropdownValues(string DisplayName);
        }
    
        public class SampleServiceImpl : ISampleService
        {
            public IEnumerable<string> GetDropdownValues(string DisplayName)
            {
                return new string[]  { 
                    DisplayName + " Foo", 
                    DisplayName + " Bar",
                    "Baz " + DisplayName
                } ;
            }
        }
    
    }

    If there is not a service present, we will publish an instance of one.  This becomes the singleton instance for any other designer that may request it.  Right now, we have a designer that can safely publish a service.  Let’s look at consuming one

    Consuming a Service

    Let’s look at some code to consume the service.  There are two parts to this.  One is simply consuming it, which we already saw above in discussing GetService and GetRequiredService .  The second is hooking into the notification system to let us know when a service is made available.  In this case, it’s a little contrived, as the service isn’t published until the button click, but it’s good practice to use the subscription mechanism as we make no guarantees on ordering, or timing of service availability.

    Subscribing to Service

    Here, using the Subscribe<TServiceType> method, we wait for the service to be available.  The documentation summarizes this method nicely:

    Invokes the provided callback when someone has published the requested service. If the service was already available, this method invokes the callback immediately.

    In the OnModelItemChanged method, we will subscribe and hook a callback.  The callback’s signature is as follows:

    public delegate void SubscribeServiceCallback<TServiceType>(
        TServiceType serviceInstance
    )

    As you can see, in this callback, the service instance is provided, so we can query it directly. You may ask, “why not in Intialize?”  well, there are no guarentees that the editing context will be available at that point. We could either subscribe to context being made available, or just use ModelItemChanged:

    protected override void OnModelItemChanged(object newItem)
    {
        if (!subscribed)
        {
            this.Context.Services.Subscribe<ISampleService>(
                servInstance =>
                {
                    listBox1.ItemsSource = servInstance.GetDropdownValues(this.ModelItem.Properties["DisplayName"].ComputedValue.ToString());
                    button1.IsEnabled = false;
                }
                );
            subscribed = true; 
        }
    }

    This wraps a basic introduction to the ServiceManager type and how to leverage it effectively to share functionality in designers.

    Let’s look at a before and after shot in the designer:

    Before & After

    before after

     

     

    What about Items?

    Items follow generally the same Get, Subscribe, and publish pattern, but rather than publish, there is a SetValue method.  If you have “just data” that you would like to share between designers (or between the host and the designer) an Item is the way to go about that.  The most commonly used item we’ve seen customers use is the Selection item in order to be able to get or set the currently selected model item.

    That’s our tour of basic publish and subscribe with Services and Items.

     

     

    [updated 12/22/2009 @ 10:23 am to finish an unclear sentence about GetService<>]

    [updated 12/22/2009 @ 8:50pm : Link to download sample code is here]

    Attachment(s): blogEditingContext.zip

  • mwinkle.blog

    Introduction to the WF4 Designer Editing Context (Part 1)

    • 2 Comments

    I want to briefly touch on the editing context and give a little introduction to its capabilities.  This is part 1 of a 6 part series

    The way to think about the editing context is that it is the point of contact between the hosting application, and the designer (and elements on the designer).  In my PDC talk, I had the following slide which I think captures the way to think about how these elements are layered together. 

    image

    Motivation

    The editing context represents the a common boundary between the hosting application and the designer, and the mechanism to handle interaction with the designer (outside of the most common methods that have been promoted on WorkflowDesigner).  If you were to look at the implementation of some of the more common methods on WorkflowDesigner, you would see that almost all of these use the editing context in order to get anything done.  For instance, the Flush method (and Save which calls Flush) will acquire an IDocumentPersistenceService from the Services collection, and then use that in order to properly serialize the document. 

    The EditingContext type has two important properties

    Items

    The Items collection is for data that is shared between the host and the designer, or data that is available to all designers.  These need to derive from ContextItem which will provide the mechanism to hook into subscription and change notification. There are a couple of interesting methods on the ContextItemManager type  :

    Services

    Services represent functionality that is either provided by the host for the designer to use, or is used by the designer to make functionality available to all designers within the editor.  Generally, these are likely defined as interfaces, but can also be a type.  It is then required for an implementation or an instance to be provided.  This instance will be shared as a singleton.  There are  a few interesting methods on the ServiceManager type:

    We’ll start walking through these over the next few posts.

  • mwinkle.blog

    Emitting the mc:Ignorable Instruction In Your WF4 XAML

    • 1 Comments

    Frequent forum guest Notre posed this question to the forums the other day noting that the XAML being produced from ActivityXamlServices.CreateBuilderWriter() was slightly different than the XAML being output from WorkflowDesigner.Save().  The reason for this stems from the fact that WorkflowDesigner leverages an additional internal type (which derives from XamlXmlWriter) in order to attach the mc:Ignorable attribute. 

    Why use mc:Ignorable?

    From the source at MSDN:

    The mc XML namespace prefix is the recommended prefix convention to use when mapping the XAML compatibility namespace http://schemas.openxmlformats.org/markup-compatibility/2006.

    Elements or attributes where the prefix portion of the element name are identified as mc:Ignorable will not raise errors when processed by a XAML processor. If that attribute could not be resolved to an underlying type or programming construct, then that element is ignored. Note however that ignored elements might still generate additional parsing errors for additional element requirements that are side effects of that element not being processed. For instance, a particular element content model might require exactly one child element, but if the specified child element was in an mc:Ignorable prefix, and the specified child element could not be resolved to a type, then the XAML processor might raise an error.

    Basically, this lets a XAML reader gracefully ignore any content marked from that namespace if it cannot be resolved.  So, imagine a runtime scenario where we don’t want to load System.Activities.Presentation every time we read a WF XAML file that may contain viewstate.  As a result, we use mc:Ignorable, which means the reader will skip that content when it does not have that assembly referenced at runtime. 

    This is what the output from the designer usually contains:

    <Sequence 
         mc:Ignorable="sap" 
         mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces"
         xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" 
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" 
         xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" 
         xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" 
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <sap:WorkflowViewStateService.ViewState>
        <scg:Dictionary x:TypeArguments="x:String, x:Object">
          <x:Boolean x:Key="IsExpanded">True</x:Boolean>
        </scg:Dictionary>
      </sap:WorkflowViewStateService.ViewState>
      <Persist sap:VirtualizedContainerService.HintSize="211,22" />
      <Persist sap:VirtualizedContainerService.HintSize="211,22" />
      <WriteLine sap:VirtualizedContainerService.HintSize="211,61" />
    </Sequence>

    mc:Ignorable will cause the ViewState and HintSize to be ignored.

     

    Why do I have to worry about this?

    If you use WorkflowDesigner.Save(), you don’t.  If you want to be able to serialize the ActivityBuilder and have XAML which is what the designer produces, you need will need to add a XamlXmlWriter into the XamlWriter stack in order to get the right output. You may also worry about this if you are implementing your own storage and plan on writing some additional XAML readers or writers for additional extensibility and flexibility.

    How Do I Get the Same Behavior?

    The code below describes the same approach you would need to take to implement an XamlXmlWriter that does the same thing our internal type does.  While I can’t copy and paste code, this does the same thing.  We do two things:

    • Override WriteNamespace() to collect all of the namespaces being emitted.  We do this to specifically check for ones that we should ignore, and to also gather all of the prefixes to make sure we don’t have a collision
    • Override WriteStartObject() to generate and write out the Ignorable attribute within the start (first) member for any namespaces we should ignore.

    What namespaces do we ignore in the designer?  Just one: http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation

    using System.Collections.Generic;
    using System.IO;
    using System.Xaml;
    using System.Xml;
     
    namespace IgnorableXamlWriter
    {
        class IgnorableXamlXmlWriter : XamlXmlWriter
        {
     
            HashSet<NamespaceDeclaration> ignorableNamespaces = new HashSet<NamespaceDeclaration>();
            HashSet<NamespaceDeclaration> allNamespaces = new HashSet<NamespaceDeclaration>();
            bool objectWritten;
            bool hasDesignNamespace;
            string designNamespacePrefix;
     
            public IgnorableXamlXmlWriter(TextWriter tw, XamlSchemaContext context)
                : base(XmlWriter.Create(tw,
                                        new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }),
                                        context,
                                        new XamlXmlWriterSettings { AssumeValidInput = true })
            {
     
            }
     
            public override void WriteNamespace(NamespaceDeclaration namespaceDeclaration)
            {
                if (!objectWritten)
                {
                    allNamespaces.Add(namespaceDeclaration);
                    // if we find one, add that to ignorable namespaces
                    // the goal here is to collect all of them that might point to this
                    // if you had a broader set of things to ignore, you would collect 
                    // those here.
                    if (namespaceDeclaration.Namespace == "http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation")
                    {
                        hasDesignNamespace = true;
                        designNamespacePrefix = namespaceDeclaration.Prefix;
                    }
                }
                base.WriteNamespace(namespaceDeclaration);
            }
     
            public override void WriteStartObject(XamlType type)
            {
                if (!objectWritten)
                {
                    // we should check if we should ignore 
                    if (hasDesignNamespace)
                    {
                        // note this is not robust as mc could naturally occur
                        string mcAlias = "mc";
                        this.WriteNamespace(
                            new NamespaceDeclaration(
                                "http://schemas.openxmlformats.org/markup-compatibility/2006",
                                mcAlias)
                                );
     
                    }
                }
                base.WriteStartObject(type);
                if (!objectWritten)
                {
                    if (hasDesignNamespace)
                    {
                        XamlDirective ig = new XamlDirective(
                            "http://schemas.openxmlformats.org/markup-compatibility/2006",
                            "Ignorable");
                        WriteStartMember(ig);
                        WriteValue(designNamespacePrefix);
                        WriteEndMember();
                        objectWritten = true;
                    }
                }
            }
     
        }
    }

    One note on the code above, it is noted that the generation of the namespace prefix “mc” is not robust.  In the product code we will check to see if mc1, mc2, … are available up to mc1000.  In that case we would then append a GUID for the ugliest XML namespace known to mankind.  The chance of collision up to 1000 would be a highly extreme edge case.

    How would I use this? The following code shows feeding this into a CreateBuilderWriter that is passed to XamlServices.Save()

    StringBuilder sb = new StringBuilder();
    XamlSchemaContext xsc = new XamlSchemaContext();
    var bw = ActivityXamlServices.CreateBuilderWriter(
        new IgnorableXamlXmlWriter(new StringWriter(sb), xsc));
    
    XamlServices.Save(bw,
                      wd.Context.Services.GetService<ModelTreeManager>().Root.GetCurrentValue());
  • mwinkle.blog

    AttachedProperty Part 2, Putting it Together

    • 0 Comments

    On my last post, Jason jumped right to the punchline in his comment here. He asks “if there is an easy way to have the properties value serialized out to the xaml.”

    First, let’s look at what we need to do from the XAML side.

    First, create a helper type with a getter and setter for the property that you want to attach.  Here we’re going to attach comments:

    public class Comment
    {
        static AttachableMemberIdentifier CommentTextName = new AttachableMemberIdentifier(typeof(Comment), "CommentText");
    
        public static object GetCommentText(object instance)
        {
            object viewState;
            AttachablePropertyServices.TryGetProperty(instance, CommentTextName, out viewState);
            return viewState;
        }
    
        public static void SetCommentText(object instance, object value)
        {
            AttachablePropertyServices.SetProperty(instance, CommentTextName, value);
        }
    
    }

    Next, let’s use the AttachableMemberIdentifier and the AttachablePropertyServices to do something interesting with this:

    AttachableMemberIdentifier ami = new AttachableMemberIdentifier(typeof(Comment), "CommentText");
    Dog newDog = new Dog { Age = 12, Name = "Sherlock", Noise = "Hooowl" };
    AttachablePropertyServices.SetProperty(newDog, ami, "A very good dog");
    string s = XamlServices.Save(newDog);
    Console.WriteLine("XAML");
    Console.WriteLine(s);
    Dog aSecondNewDog = XamlServices.Load(new StringReader(s)) as Dog;
    string outter;
    AttachablePropertyServices.TryGetProperty(aSecondNewDog, ami, out outter);
    Console.WriteLine("read out: {0}", outter);

    Let’s see the output from this:

    XAML
    <Dog
         Age="12"
         Comment.CommentText="A very good dog"
         Name="Sherlock"
         Noise="Hooowl" 
         xmlns="clr-namespace:AttachedPropertiesBlogPosting;assembly=AttachedPropertiesBlogPosting" />
    read out: A very good dog

    You’ll note that the value is contained in the XAML under Comment.CommentText.

    Pulling it all Together

    Let’s take what we did in the last post and combine it with the above stuff in order to have an attached property that writes through and is stored inside the XAML.

    AttachedProperty<string> Comment = new AttachedProperty<string>
    {
        IsBrowsable = true,
        Name = "Comment",
        Getter = (mi =>
            { string temp;
              AttachablePropertyServices.TryGetProperty<string>(mi.GetCurrentValue(), ami, out temp);
              return temp;
            }),
        Setter = ( (mi,val) => AttachablePropertyServices.SetProperty(mi.GetCurrentValue(), ami, val) )
    
    };
    aps.AddProperty(Comment);
    dogMi.Properties["Comment"].SetValue("I think I like that dog");
    string xaml = XamlServices.Save(dogMi.GetCurrentValue());
    Console.WriteLine("XAML");
    Console.WriteLine(xaml);

    What are we doing here, well, we basically just use the Getter and Setter to write through to the underlying instance.  You’ll note that usually, we never want to go and use GetCurrentValue(), as any changes made there are not made via the ModelItem tree which means we might miss a change notification.  However, given that the only place where we can store the XAML attached property is on the instance itself, this gives us a good way to write through.  The XAML output below shows that this works as expected:

    <Dog 
       Age="5"
       Comment.CommentText="I think I like that dog"
       Name="Sasha" 
       Noise="Snort" 
       xmlns="clr-namespace:AttachedPropertiesBlogPosting;assembly=AttachedPropertiesBlogPosting" />
  • mwinkle.blog

    WF4 Design Time AttachedPropertiesService and Attached Properties

    • 3 Comments

    I’ve been meaning to throw together some thoughts on attached properties and how they can be used within the designer.  Basically, you can think about attached properties as injecting some additional “stuff” onto an instance that you can use elsewhere in your code.

    Motivation

    In the designer, we want to be able to have behavior and view tied to interesting aspects of the data.  For instance, we would like to have a view updated when an item becomes selected.  In WPF, we bind the style based on the “isSelectionProperty.”  Now, our data model doesn’t have any idea of selection, it’s something we’d like the view level to “inject” that idea on any model item so that a subsequent view could take advantage of.  You can kind of view Attached Properties as a nice syntactic sugar to not have to keep a bunch of lookup lists around.  As things like WPF bind to the object very well, and not so much a lookup list, this ends up being an interesting model.

    To be clear, you could write a number of value converters that take the item being bound, look up in a lookup list somewhere, and return the result that will be used.  The problem we found is that we were doing this in a bunch of places, and we really wanted to have clean binding statements inside our WPF XAML, rather than hiding a bunch of logic in the converters.

    How Does it Work

    First, some types.

    Name Description
    AttachedPropertiesService Service in editing context for managing AttachedProperties
    AttachedProperty Base attached property type (abstract)
    AttachedProperty<T> Strongly typed attached property with interesting getter/setter programmability

    in diagram form:

    image

    One thing that might look a little funny to some folks who have used attached properties in other contexts (WF3, WPF, XAML), is the “IsBrowsable” property.  The documentation is a little sparse right now, but what this will do is determine how discoverable the property is.  If this is set to true, the attached property will show up in the Properties collection of the ModelItem to which the AP is attached.  What this means is that it can show up in the Property grid, you can bind WPF statements directly to it, as if it were a real property of the object.  Attached properties by themselves have no actual storage representation, so these exist as design time only constructs.

    Getter/ Setter?

    One other thing that you see on the AttachedProperty<T> is the Getter and Setter properties.  These are of type Func<ModelItem,T> and Action<ModelItem,T> respectively.  What these allow you to do is perform some type of computation whenever the get or set is called against the AttachedProperty.  Why is this interesting?  Well, let’s say that you’d like to have a computed value retrieved, such as “IsPrimarySelection” checking with the Selection context item to see if an item is selected.  Or, customizing the setter to either store the value somewhere more durable, or updating a few different values.  The other thing that happens is that since all of these updates go through the ModelItem tree, any changes will be propagated to other listeners throughout the designer.

    Looking at Some Code

    Here is a very small console based app that shows how you can program against the attached properties.  An interesting exercise for the reader would be to take this data structure, put it in a WPF app and experiment with some of the data binding.

    First, two types:

    public class Dog
    {
        public string Name { get; set; }
        public string Noise { get; set; }
        public int Age { get; set; }
       
    }
    
    public class Cat
    {
        public string Name { get; set; }
        public string Noise { get; set; }
        public int Age { get; set; }
    }

    Ignore no common base type, that actually makes this a little more interesting, as we will see.

    Now, let’s write some code.  First, let’s initialize and EditingContext and ModelTreeManager

       1:       static void Main(string[] args)
       2:          {
       3:              EditingContext ec = new EditingContext();
       4:              ModelTreeManager mtm = new ModelTreeManager(ec);
       5:              mtm.Load(new object[] { new Dog { Name = "Sasha", Noise = "Snort", Age = 5 },
       6:                                      new Cat { Name="higgs", Noise="boom", Age=1 } });
       7:              dynamic root = mtm.Root;
       8:              dynamic dog = root[0];
       9:              dynamic cat = root[1];
      10:              ModelItem dogMi = root[0] as ModelItem;
      11:              ModelItem catMi = root[1] as ModelItem;

    Note, lines 7-9 will not work in Beta2 (preview of coming attractions).  To get lines 10-11 working in beta2, cast root to ModelItemCollection and then use the indexers to extract the values

    Now, let’s build an attached property, and we will assign it only to type “dog”

       1:  // Add an attached Property
       2:  AttachedProperty<bool> ap = new AttachedProperty<bool>
       3:  {
       4:      IsBrowsable = true,
       5:      Name = "IsAnInterestingDog",
       6:      Getter = (mi => mi.Properties["Name"].ComputedValue.ToString() == "Sasha"),
       7:      OwnerType = typeof(Dog)
       8:  };
       9:  ec.Services.Publish<AttachedPropertiesService>(new AttachedPropertiesService());
      10:  AttachedPropertiesService aps = ec.Services.GetService<AttachedPropertiesService>();
      11:  aps.AddProperty(ap);
      12:   
      13:  Console.WriteLine("---- Enumerate properties on dog (note new property)----");
      14:  dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
      15:   
      16:  Console.WriteLine("---- Enumerate properties on cat (note  no new property) ----");
      17:  catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));

    Let’s break down what happened here.

    • Line2-8, create an AttachedProperty<bool>
      • We set IsBrowsable to true, we want to see it in the output
      • Name, that’s what it will be projected as
      • OwnerType, we only want this to apply to Dog’s, not Cat’s or Objects or whatever.
      • Finally, Getter, and look what we do here, we operate on the model item to do some computation and return a bool (in this case, we look to see if the name property equals “Sasha”
    • Line 9-11 create an AttachedPropertiesService and add it to the editing context.
    • Lines 13-17 output the properties, and let’s see what that looks like:
    ---- Enumerate properties on dog (note new property)----
     Property : Name
     Property : Noise
     Property : Age
     Property : IsAnInterestingDog
    ---- Enumerate properties on cat (note  no new property) ----
     Property : Name
     Property : Noise
     Property : Age

    Ok, so that’s interesting, we’ve injected a new property, only on the dog type.  If I got dogMI.Properties[“IsAnInterestingDog”], I would have a value that I could manipulate (albeit returned via the getter).

    Let’s try something a little different:

       1:  AttachedProperty<bool> isYoungAnimal = new AttachedProperty<bool>
       2:  {
       3:      IsBrowsable = false,
       4:      Name = "IsYoungAnimal",
       5:      Getter = (mi => int.Parse(mi.Properties["Age"].ComputedValue.ToString()) < 2)
       6:  };
       7:   
       8:  aps.AddProperty(isYoungAnimal);
       9:   
      10:  // expect to not see isYoungAnimal show up
      11:  Console.WriteLine("---- Enumerate properties on dog  (note isYoungAnimal doesn't appear )----");
      12:  dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
      13:  Console.WriteLine("---- Enumerate properties on cat (note isYoungAnimal doesn't appear )----");
      14:  catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
      15:   
      16:  Console.WriteLine("---- get attached property via GetValue ----");
      17:  Console.WriteLine("getting non browsable attached property on dog {0}", isYoungAnimal.GetValue(dogMi));
      18:  Console.WriteLine("getting non browsable attached property on cat {0}", isYoungAnimal.GetValue(catMi));

    Let’s break this down:

    • Lines 1-6 create a new attached property
      • IsBrowsable is false
      • No OwnerType being set
      • The Getter does some computation to return true or false
    • Lines 10-14 write out the properties (as above)
    • Lines 17-18 extract the value with AttachedPropertyInstance.GetValue(ModelItem)

    Let’s see the output there:

    ---- Enumerate properties on dog  (note isYoungAnimal doesn't appear )----
     Property : Name
     Property : Noise
     Property : Age
     Property : IsAnInterestingDog
    ---- Enumerate properties on cat (note isYoungAnimal doesn't appear )----
     Property : Name
     Property : Noise
     Property : Age
    ---- get attached property via GetValue ----
    getting non browsable attached property on dog False
    getting non browsable attached property on cat True

    As we can see, we’ve now injected this behavior, and we can extract the value. 

    Let’s get a little more advanced and do something with the setter.  Here, if isYoungAnimal is set to true, we will change the age (it’s a bit contrived, but shows the dataflow on simple objects, we’ll see in a minute a more interesting case).

       1:  // now, let's do something clever with the setter. 
       2:  Console.WriteLine("---- let's use the setter to have some side effect ----");
       3:  isYoungAnimal.Setter = ((mi, val) => { if (val) { mi.Properties["Age"].SetValue(10); } });
       4:  isYoungAnimal.SetValue(cat, true);
       5:  Console.WriteLine("cat's age now {0}", cat.Age);

    Pay attention to what the Setter does now.  We create the method through which subsequent SetValue’s will be pushed.  Here’s that output:

    ---- let's use the setter to have some side effect ----
    cat's age now 10

    Finally, let’s show an example of how this can really function as some nice sugar to eliminate the need for a lot of value converters in WPF by using this capability as a way to store the relationship somewhere (rather than just using at a nice proxy to change a value):

       1:  // now, let's have a browesable one with a setter.
       2:  // this plus dynamics are a mini "macro language" against the model items
       3:   
       4:  List<Object> FavoriteAnimals = new List<object>();
       5:   
       6:  // we maintain state in FavoriteAnimals, and use the getter/setter func
       7:  // in order to query or edit that collection.  Thus changes to an "instance"
       8:  // are tracked elsewhere.
       9:  AttachedProperty<bool> isFavoriteAnimal = new AttachedProperty<bool>
      10:  {
      11:      IsBrowsable = false,
      12:      Name = "IsFavoriteAnimal",
      13:      Getter = (mi => FavoriteAnimals.Contains(mi)),
      14:      Setter = ((mi, val) => 
      15:          {
      16:              if (val)
      17:                  FavoriteAnimals.Add(mi);
      18:              else
      19:              {
      20:                  FavoriteAnimals.Remove(mi);
      21:              }
      22:          })
      23:  };
      24:   
      25:   
      26:  aps.AddProperty(isFavoriteAnimal);
      27:   
      28:  dog.IsFavoriteAnimal = true;
      29:  // remove that cat that isn't there
      30:  cat.IsFavoriteAnimal = false;
      31:  cat.IsFavoriteAnimal = true;
      32:  cat.IsFavoriteAnimal = false;
      33:   
      34:  Console.WriteLine("Who are my favorite animal?");
      35:  FavoriteAnimals.ForEach(o => Console.WriteLine((o as ModelItem).Properties["Name"].ComputedValue.ToString()));

    Little bit of code, let’s break it down one last time:

    • Line 14 – Create a setter that acts upon the FavoriteAnimals collection to either add or remove the element
    • Line 28-32 – do a few different sets on this attached property
      • NOTE: you can’t do that in beta2 as the dynamic support hasn’t been turned on.  Rather you would have to do isFavoriteAnimal.SetValue(dogMi, true).
    • Line 35 then prints the output to the console, and as expected we only see the dog there:
    -- Who are my favorite animals?
    Sasha

    I will attach the whole code file at the bottom of this post, but this shows you how you can use the following:

    • Attached properties to create “computed values” on top of existing types
    • Attached properties to inject a new (and discoverable) property entry on top of the designer data model (in the form of a new property)
    • Using the Setter capability to both propagate real changes to the type, providing a nice way to give a cleaner interface, as well as use it as a mechanism to store data about the object outside of the object, but in a way that gives me access to it such that it seems like the object. 
      • This is some really nice syntactic sugar that we sprinkle on top of things

    What do I do now?

    Hopefully this post gave you some ideas about how the attached property mechanisms work within the WF4 designer.  These give you a nice way to complement the data model and create nice bindable targets that your WPF Views can layer right on top of.

    A few ideas for these things:

    • Use the Setters to clean up a “messy” activity API into a single property type that you then build a custom editor for in the property grid. 
    • Use the Getters (and the integration into the ModelProperty collection) in order to create computational properties that are used for displaying interesting information on the designer surface.
    • Figure out how to bridge the gap to take advantage of the XAML attached property storage mechanism, especially if you author runtime types that look for attached properties at runtime. 
    • Use these, with a combination of custom activity designers to extract and display interesting runtime data from a tracking store

     

    Full Code Posting

    using System;
    using System.Activities.Presentation;
    using System.Activities.Presentation.Model;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace AttachedPropertiesBlogPosting
    {
        class Program
        {
            static void Main(string[] args)
            {
                EditingContext ec = new EditingContext();
                ModelTreeManager mtm = new ModelTreeManager(ec);
                mtm.Load(new object[] { new Dog { Name = "Sasha", Noise = "Snort", Age = 5 },
                                        new Cat { Name="higgs", Noise="boom", Age=1 } });
                dynamic root = mtm.Root;
                dynamic dog = root[0];
                dynamic cat = root[1];
                ModelItem dogMi = root[0] as ModelItem;
                ModelItem catMi = root[1] as ModelItem;
              
                // Add an attached Property
                AttachedProperty<bool> ap = new AttachedProperty<bool>
                {
                    IsBrowsable = true,
                    Name = "IsAnInterestingDog",
                    Getter = (mi => mi.Properties["Name"].ComputedValue.ToString() == "Sasha"),
                    OwnerType = typeof(Dog)
                };
                ec.Services.Publish<AttachedPropertiesService>(new AttachedPropertiesService());
                AttachedPropertiesService aps = ec.Services.GetService<AttachedPropertiesService>();
                aps.AddProperty(ap);
    
                Console.WriteLine("---- Enumerate properties on dog (note new property)----");
                dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
    
                Console.WriteLine("---- Enumerate properties on cat (note  no new property) ----");
                catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
    
    
                
                AttachedProperty<bool> isYoungAnimal = new AttachedProperty<bool>
                {
                    IsBrowsable = false,
                    Name = "IsYoungAnimal",
                    Getter = (mi => int.Parse(mi.Properties["Age"].ComputedValue.ToString()) < 2)
                };
    
                aps.AddProperty(isYoungAnimal);
    
                // expect to not see isYoungAnimal show up
                Console.WriteLine("---- Enumerate properties on dog  (note isYoungAnimal doesn't appear )----");
                dogMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
                Console.WriteLine("---- Enumerate properties on cat (note isYoungAnimal doesn't appear )----");
                catMi.Properties.ToList().ForEach(mp => Console.WriteLine(" Property : {0}", mp.Name));
    
                Console.WriteLine("---- get attached property via GetValue ----");
                Console.WriteLine("getting non browsable attached property on dog {0}", isYoungAnimal.GetValue(dogMi));
                Console.WriteLine("getting non browsable attached property on cat {0}", isYoungAnimal.GetValue(catMi));
                
                
                // now, let's do something clever with the setter. 
                Console.WriteLine("---- let's use the setter to have some side effect ----");
                isYoungAnimal.Setter = ((mi, val) => { if (val) { mi.Properties["Age"].SetValue(10); } });
                isYoungAnimal.SetValue(cat, true);
                Console.WriteLine("cat's age now {0}", cat.Age);
    
                // now, let's have a browesable one with a setter.
                // this plus dynamics are a mini "macro language" against the model items
    
                List<Object> FavoriteAnimals = new List<object>();
    
                // we maintain state in FavoriteAnimals, and use the getter/setter func
                // in order to query or edit that collection.  Thus changes to an "instance"
                // are tracked elsewhere.
                AttachedProperty<bool> isFavoriteAnimal = new AttachedProperty<bool>
                {
                    IsBrowsable = false,
                    Name = "IsFavoriteAnimal",
                    Getter = (mi => FavoriteAnimals.Contains(mi)),
                    Setter = ((mi, val) => 
                        {
                            if (val)
                                FavoriteAnimals.Add(mi);
                            else
                            {
                                FavoriteAnimals.Remove(mi);
                            }
                        })
                };
                aps.AddProperty(isFavoriteAnimal);
                dog.IsFavoriteAnimal = true;
                // remove that cat that isn't there
                cat.IsFavoriteAnimal = false;
                cat.IsFavoriteAnimal = true;
                cat.IsFavoriteAnimal = false;
                Console.WriteLine("Who are my favorite animals?");
                FavoriteAnimals.ForEach(o => Console.WriteLine((o as ModelItem).Properties["Name"].ComputedValue.ToString()));
                Console.ReadLine();
            }
        }
    
        public class Dog
        {
            public string Name { get; set; }
            public string Noise { get; set; }
            public int Age { get; set; }
        }
    
        public class Cat
        {
            public string Name { get; set; }
            public string Noise { get; set; }
            public int Age { get; set; }
        }
    }
  • mwinkle.blog

    WF4 ViewStateService

    • 2 Comments

    A comment posted by Notre asked for some more details about view state and attached property services, so I thought I would dive into those next.  I will follow-up in a subsequent post on the AttachedPropertyService, as there is a little bit more going on there.

    Motivation

    Why do I care about viewstate?  Well, usually it is because we want to write something down and store it for later that is not required for runtime.  A common example of viewstate is the position of nodes within a flowchart.  While not required to execute the flowchart, they are required to effectively view the flowchart. 

    Where to write them down?

    This was a question that caused a fair amount of debate on the team.  There are basically two places to write down things like view state in a file-based world. 

    1. In the source document itself
    2. In a document that stays close to the source (usually referred to as a sidecar file)

    We had customers asking for both.  The motivation for the first is that for things like flowchart, where I may always care about the visualization representation, I want to keep that metadata around and only deal with one element.  For the second, it is motivated by the reason that we want a clean source document that only describes the minimal artifact to run.  Now, there are certainly many stops along the spectrum (for instance, we might always want to keep annotations or source comments in the source document, and put positioning elsewhere).  For VS2010, we landed with a unified API to use, and we write in the source document.  This is something that is likely to change in future releases, as it does make things like textual diffs rather painful.

    So, that’s why we want to use it.

    How do we use it?

    We are going to create a simple activity designer that lets me write down a comment.

    A few simple steps:

    1. Create a new VS Project, let’s create an Activity Library
    2. Add a Designer to that activity library
    3. Add an attribute to the activity pointing to the designer
    4. Add a new WorkflowConsoleApp Project
    5. Build

    image

    Now, let’s go and make our activity designer a little interesting.

    Let’s add a text box and a button.  We’ll make the text of the button something obvious like “commit comment” The XAML for the activity designer looks like this:

    <sap:ActivityDesigner x:Class="simpleActivity.CommentingDesigner"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
        xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
      <Grid>
            <StackPanel Name="stackPanel1" VerticalAlignment="Top" >
                <TextBox  Name="commentBlock"   />
                <Button Content="Load View State" Name="loadViewState" Click="loadViewState_Click" />
                <Button Content="Commit View State"  Name="button1" Click="button1_Click" />
            </StackPanel>
        </Grid>
    </sap:ActivityDesigner>

    Now, let’s add some code to the button (and to the initialization of the form)

    ViewStateService has a few useful methods on it.  I want to call out a subtle difference.  You will see StoreViewState and StoreViewStateWithUndo.  The primary distinction as the name implies is that one will simply write the view state down and will bypass the undo/redo stack.  This is for view state like an expanded/collapsed view.  You don’t really want ctl-z to simply flip expanded versus collapsed for you.  But for something like flowchart, where changing some of the viewstate, like position, might be such a thing that you want support for undoing the action.  That’s the primary difference.

    So, our code for the button looks like this:

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        ViewStateService vss = this.Context.Services.GetService<ViewStateService>();
        vss.StoreViewStateWithUndo(this.ModelItem, "comment", commentBlock.Text);
    }

    Now, on load, we want to be able to populate the value, so we will use the RetrieveViewState method in order to extract this.

    private void loadViewState_Click(object sender, RoutedEventArgs e)
    {
        ViewStateService vss = this.Context.Services.GetService<ViewStateService>();
        commentBlock.Text = vss.RetrieveViewState(this.ModelItem, "comment") as string;
    }

    Now, let’s go back to our workflow project and put an instance of this activity on the surface:

    image

    Let’s add some viewstate information and commit it.  Now let’s look at the XAML:

    <s4:NotRealInterestingActivity Text="{x:Null}" sap:VirtualizedContainerService.HintSize="200,99">
      <sap:WorkflowViewStateService.ViewState>
        <scg3:Dictionary x:TypeArguments="x:String, x:Object">
          <x:String x:Key="comment">basic comment</x:String>
        </scg3:Dictionary>
      </sap:WorkflowViewStateService.ViewState>
    </s4:NotRealInterestingActivity>

    Ok, now, to show that we can pull this in, let’s change the text in the xaml and then reload our designer.

    image 

    You can muck around with ctl-z to see that this does get handled correctly via the undo. 

    The other important thing to note is that this takes an object, so your viewstate is not limited to strings, you can have more full featured objects if you’d like. Finally, the ViewStateService also has a ViewStateChanged you can subscribe to in order to handle, dispatch, and react to view state changes in the designer.

  • mwinkle.blog

    Where is System.Activities.Design in WF4 Beta2 and Beyond?

    • 0 Comments

    I got an email over the weekend asking about this, and I realized that it’s somewhat buried in this post here.  Anyway, in Beta1, you often saw System.Activities.Design.  For beta2 (and RTM), one important change

    System.Activities.Design  => System.Activities.Presentation

    The primary reason for this change is that the *.Design suffix is generally reserved for VS design extensibility.  As our designer ships in the framework, *.Design was not the correct suffix.  *.Presentation is where we landed. 

    Hoping that putting this in the title lands this high up in search queries so that this post might be useful for a few people.

  • Page 1 of 1 (12 items)