Windows Communication Foundation (WCF) reminder

1969 days ago

under construction

What a WCF is?

WCF is a framework for building service-oriented applications.

WCF’s ABС

In order to access some WCF service you have to define endpoint which contains such key parameters as: Address, Binding and Contract

Address can be defined as IP or host name with port and special path. Different kinds of protocols can be used: HTTP, TCP, NamedPipe, Peer2Peer, MSMQ.
http://host:8080
net.tcp://host:987/service
net.pipe//pipe/service

Binding specifies service access mode.

  • Protocols determines the security, reliability, and context flow settings to use for messages that are sent to the endpoint;
  • Encoding determines the wire encoding to use for messages that are sent to the endpoint, for example, text/XML, binary, or Message Transmission Optimization Mechanism (MTOM);
  • Transport determines the underlying transport protocol to use when sending messages to the endpoint: HTTP, TCP, NamedPipe, Peer2Peer, MSMQ.

Supported bindings and its characteristics are shown here

Contract is a way to describe what service does.

  • Srvice contract describes an operations that service can perform.
  • Data Contract defines which data types are passed to and from the service.
  • Fault contracts defines errors raised by the service.
  • Message contracts contains Data like a DatContract, however, it allows to control the structure of SOAP message, e.g. what data should be in header and what in body, or element names. It also makes some restrictions on operation syntax. MessageContract
  • Collection data contract allows to control xml representation of collections precisely.

        [ServiceContract]
        public interface ISomeService
        {
            [OperationContract]
            SomeData SomeOperation(int arg1, string arg2, SomeData arg3);

            [OperationContract]
            SomeMessage SomeOperation(SomeMessage);
        }

        [DataContract]
        public class SomeData
        {
            [DataMember]
            public int SomeId { get; set; }
            [DataMember]
            public List<string> SomeData { get; set; }
        }

        [MessageContract]
        public class SomeMessage
        {
           [MessageHeader(ProtectionLevel=None)] public int arg1;
           [MessageHeader(ProtectionLevel=Sign)] public int arg2;
           [MessageBodyMember(ProtectionLevel=EncryptAndSign)] public string SomeData;
        }

        [CollectionDataContract(
            Name = "CountriesOrRegionsWithCapitals", 
            ItemName = "entry", 
            KeyName = "countryorregion", 
            ValueName = "capital")]
        public class CountriesOrRegionsWithCapitals2 : Dictionary<string, string> { }

Binding

Standart bindings support transport protocols: HTTP, TCP, P2P, Named Pipes, MSMQ.
Purpose of using of named pipes is when communication is required between different WCF applications on a single computer, and you want to prevent any communication from another machine.

Encoding could be binary, text or MTOM.

Security could be on transport or message level or without any.

Some binding support sessions and some of them are not.

Some binding support transactions and some of them are not.

The diagram that helps choosing corect binding

Fault contract

Fault contract allows making standartized fault messages.

        [ServiceContract]
        public interface ISomeService
        {
            [OperationContract]
            [FaultContract(typeof(MathFault))]
            SomeData SomeOperation(int arg1, string arg2, SomeData arg3);
        }

        [DataContract]
        public class SomeFault
        {
            [DataMember]
            public string Cause { get; set; }
            [DataMember]
            public string Resolution { get; set; }
        }

Now the SomeOperation body can throw exception this way:
throw new FaultException(
    new SomeFault { Cause = "Becuse exception ocured", Resolution = "Write better code)."} );

And the fault can be handled on the client side using:
catch (FaultException<SomeFault> e).

Message Patterns in WCF

Request-Reply Pattern is a syncronous way of comunication. The client thread sends a request to the service and thread hangs while waiting the serice responce.

One-Way Message Exchange Pattern is the of comunication "shoot and forget". Client sends a request without waiting response and doesn't have the result of operation or even the information concerning the exceptions that may occur.

Duplex or Callback Message Exchange Pattern is an asyncronous way of comunication. Client sends a request without waiting response but having the result of operation as callback from the server side. IOW it is a Request-Reply Pattern without hanging the client thread.

One-Way pattern can be easily implemented by setting IsOneWay property in your operation contract.

[OperationContract(IsOneWay = true)]
void LogAction(string action);

Duplex pattern is a bit harder. You have to have two interfaces, the service interface and the callback interface. Both operations have to marked as OneWay and a service must know the callback contract interface.


        [ServiceContract(CallbackContract=typeof(ILoggingServiceCallback))]
        public interface ILoggingService
        {
            [OperationContract(IsOneWay = true)]
            void LogAction(string action);
        }

        public interface ILoggingServiceCallback
        {
            [OperationContract(IsOneWay = true)]
            void LoggingSuccessful(bool someResult);
        }

Looks fine, but who will call LoggingSuccessful? From the service operation body (implementation of LogAction method) will be called the LoggingSuccessfull method manually (OperationContext.Current.GetCallbackChannel().LoggingSuccessful(true);).

On the client side you have to implement ILoggingServiceCallback in order to have an ability to execute some logic when service calls back.

Sessions

WCF service session used only to identify the client, an can't store some additional data because service must be stateless by definition.

- Not all the bindings support sessions, so the one that supports has to chosen.

- The InstanceContextMode behavior must be set as "PerSession".

- The service contract's property SessionMode has to be "Allowed" or "Required". If it equals "NotAllowed" the connections that require session will be ignored. In the case of "Required" the session-less connections will be ignored.

In the case of NetTCPBinding and NetNamedPipeBinding, WCF identifies the client by the underlying transport channel. However, the WSHttp uses connection-less http protocol, so in order to identify the client session it adds session id into header.

Operation can be marked as an operation that breaks session. For this purpose OperationContract has the "IsTerminating" property. If it set as True it breaks the session.

Service Instantiation

You can control service instance creation mode using InstanceContextMode behavior. The service instance can be one per all requests "Single", in that case the requests will be placed in queue and processed consequently. The instance can be also maiden one per request "PerCall" or one per session "PerSession"

Service instantiation can be managed by OperationContract property IsInitiating. The client can't call operations where IsInitiating = False before the operation that has IsInitiating = True has been executed.
The parameter IsTerminating = True terminates session, so after that the operation with IsInitiating = True can be called only.

SessionMode must be "Required"!

        [OperationContract(IsInitiating = True)]
        bool SignIn()   

        [OperationContract(IsInitiating = false)]
        List<string> GetData()

        [OperationContract(IsInitiating = false)]
        void SetData(string data)

        [OperationContract(IsInitiating = false, IsTerminating = True)]
        bool SignOut()

Transactions

Not all the standart binding support transactions.

Transaction support provided through DTC (Distributed Transaction Manager).

The transaction necesity can be configured by OperationContract.TransactionFlow property, it could be Allowed, NotAllowed, Mandatory.

Definition "[OperationBehavior(TransactionScopeRequired=true)]" makes metod as one transction even if the metod haven't been put in the transaction scope.
If the method will be finished without exceptions, the transaction will be commited.
This behavior could be changed with parameter TransactionAutoComplete.
If the autocompleation switched of
([OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=false )]),
the transaction must be commited automatically by calling "OperationContext.Current.SetTransactionComplete();"

Transaction can be also controlled from the client side, using transaction scope (using(TransactionScope scope = new TransactionScope())). The "scope" variable in that case musn't be sent as operation parameter, it will be picked up automatically.

More

Behaviors

Streaming

Creating REST service in WCF.

REST service is a service that uses URI and operation name (POST, GET, PUT, DELETE) for getting operation result. It can use any transport protocol, but preferable for this purposes using HTTP becauses it realises CRUD operation on the protocol level and can be acces from any browser.

To make it work, add additional attribute (WebInvoke) to your operation.
WebInvoke can be configured using various properties, the more interesting ones:
- UriTemplate - that defines URI wich be used to acces operation and parameters, like: "Customer/{id}";
- Method - defines CRUD operation, like "POST";
- ResponseFormat - can be defined output format as an XML or JSON. Response format can be chosen automaticly using WebHttpBehavior.AutomaticFormatSelectionEnabled.

REST operations will not appear in WSDL specification.

Alexander Molodih

,

XAML Path Markup test

2362 days ago

This Path Markup tester for XAML may be useful for developers and those who wants to practice creating different shapes with path.

You can read path markup language description here.

Get Microsoft Silverlight
Alexander Molodih

,

Templated custom controls in XAML (Windows 8)

2654 days ago

Example for .NET UserGroup

Making templated XAML control under Windows 8 METRO are the same as for WPF or Silverlight. So the theory you can read from my previouse article.
Of course there are some differences present. Differences that I found described in current article.

I'll move control described in the previouse article from WPF/Silverlight to Windows 8 METRO application.

First stage the same:

        public sealed class PacmanControl : Control
        {
            public static readonly DependencyProperty SizeProperty =
                DependencyProperty.Register("Size", typeof (double), 
                    typeof (PacmanControl), new PropertyMetadata(default(double)));

            public double Size
            {
                get { return (double) GetValue(SizeProperty); }
                set { SetValue(SizeProperty, value); }
            }

            public static readonly DependencyProperty MouthAngleProperty =
                DependencyProperty.Register("MouthAngle", typeof (double), 
                    typeof (PacmanControl), new PropertyMetadata(default(double)));

            public double MouthAngle
            {
                get { return (double) GetValue(MouthAngleProperty); }
                set { SetValue(MouthAngleProperty, value); }
            }

            public PacmanControl()
            {
                DefaultStyleKey = typeof(PacmanControl);
            }
        }

Second stage the same too:

        <Style TargetType="Pacman:PacmanControl">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Pacman:PacmanControl">
                        <Grid>
                            <!--  upper chew  -->
                            <Path
                                    x:Name="TopChew"
                                    VerticalAlignment="Top"
                                    Data="M0,0 A0.5,0.5,0,1,1,1,0 L0.5,0"
                                    Fill="Yellow"
                                    Stretch="Uniform"
                                    Stroke="Gray"
                                    StrokeEndLineCap="Round"
                                    StrokeStartLineCap="Round"
                                    StrokeThickness="1">
                            </Path>
                            <!--  down chew  -->
                            <Path
                                    x:Name="BotChew"
                                    VerticalAlignment="Bottom"
                                    Data="M0,0 A0.5,0.5,0,1,0,1,0 L0.5,0"
                                    Fill="Yellow"
                                    Stretch="Uniform"
                                    Stroke="Gray"
                                    StrokeEndLineCap="Round"
                                    StrokeStartLineCap="Round"
                                    StrokeThickness="1">
                            </Path>
                            <!--  Eye  -->
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="0.45*" />
                                    <ColumnDefinition Width="0.1*" />
                                    <ColumnDefinition Width="0.45*" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="0.25*" />
                                    <RowDefinition Height="0.1*" />
                                    <RowDefinition Height="0.65*" />
                                </Grid.RowDefinitions>
                                <Ellipse
                                    Grid.Row="1"
                                    Grid.Column="1"
                                    Fill="Black" />
                            </Grid>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Template bindings are working too:

                    <ControlTemplate TargetType="Pacman:PacmanControl">
                        <Grid                                
                            Width="{TemplateBinding Size}"
                            Height="{TemplateBinding Size}">
                               ...

Some transformations in Windows 8 was changed (LayoutTransform changed to RenderTransform). But Rotate transform still present:

                                <Path.RenderTransform>
                                    <RotateTransform />
                                </Path.RenderTransform>

But in Windows 8 we can't change RotateTransform using direct access to the class. IOW: when you get topRotator instance using GetTemplateChild and change its properties it doesn't make any effect on figure. We have to access parent container

Next step should look like it:

    [TemplatePart(Name = TopChew, Type = typeof(Path))]
    [TemplatePart(Name = BotChew, Type = typeof(Path))]
    public class PacmanControl : Control
    {
        private const string BotChew = "BotChew";
        private const string TopChew = "TopChew";

        private Path _botChew;
        private Path _topChew;

        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _topChew = GetTemplateChild(TopChew) as Path;
            _botChew = GetTemplateChild(BotChew) as Path;
        }
    }

With property changed:

    [TemplatePart(Name = TopChew, Type = typeof(Path))]
    [TemplatePart(Name = BotChew, Type = typeof(Path))]
    public class PacmanControl : Control
    {
        private const string BotChew = "BotChew";
        private const string TopChew = "TopChew";

        public static readonly DependencyProperty MouseAngleProperty =
            DependencyProperty.Register("MouthAngle", typeof(double), typeof(PacmanControl),
                new PropertyMetadata(default(double),
                    (o, args) => ((PacmanControl)o).PropertyChangedCallback()));

        public static readonly DependencyProperty SizeProperty =
            DependencyProperty.Register("Size", typeof(double), typeof(PacmanControl),
                new PropertyMetadata(default(double),
                    (o, args) => ((PacmanControl)o).PropertyChangedCallback()));

        private Path _botChew;
        private Path _topChew;

        public double MouthAngle
        {
            get { return (double)GetValue(MouseAngleProperty); }
            set { SetValue(MouseAngleProperty, value); }
        }

        public double Size
        {
            get { return (double)GetValue(SizeProperty); }
            set { SetValue(SizeProperty, value); }
        }

        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _topChew = GetTemplateChild(TopChew) as Path;
            _botChew = GetTemplateChild(BotChew) as Path;
            PropertyChangedCallback();
        }

        private void PropertyChangedCallback()
        {
            RotateTransform topRotator = null;
            RotateTransform botRotator = null;
            if (_topChew != null)
                topRotator = _topChew.RenderTransform as RotateTransform;
            if (_botChew != null)
                botRotator = _botChew.RenderTransform as RotateTransform;

            if (topRotator != null)
            {
                topRotator.CenterX = Size / 2;
                topRotator.CenterY = Size / 2;
                topRotator.Angle = -MouthAngle;
            }
            if (botRotator != null)
            {
                botRotator.CenterX = Size / 2;
                botRotator.Angle = MouthAngle;
            }
        }
    }

Changing visual states still the same:

        public PacmanControl()
        {
            DefaultStyleKey = typeof(PacmanControl);
            Loaded += (sender, args) => VisualStateManager.GoToState(this, "Normal", true);
            PointerEntered += (sender, args) => VisualStateManager.GoToState(this, "MouseOver", true);
            PointerExited += (sender, args) => VisualStateManager.GoToState(this, "Normal", true);
            PointerPressed += (sender, args) => VisualStateManager.GoToState(this, "Pressed", true);
            PointerReleased += (sender, args) => VisualStateManager.GoToState(this, "Normal", true);
        }
    [TemplateVisualState(Name = "Normal")]
    [TemplateVisualState(Name = "MouseOver")]
    [TemplateVisualState(Name = "Pressed")]

We cant access to topRotator from animation too. You have to access parent container "BotChew" with "(Path.RenderTransform).(RotateTransform.Angle)".

                    <ControlTemplate TargetType="Pacman:PacmanControl">
                        <Grid Width="{TemplateBinding Size}" Height="{TemplateBinding Size}">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal">
                                        <Storyboard>
                                            <DoubleAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)" />
                                            <DoubleAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)" />
                                            <ColorAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.Fill).(SolidColorBrush.Color)" />
                                            <ColorAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.Fill).(SolidColorBrush.Color)" />
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver">
                                        <Storyboard>
                                            <DoubleAnimation
                                                AutoReverse="True"
                                                Duration="0:0:0.2"
                                                From="40"
                                                RepeatBehavior="Forever"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                            <DoubleAnimation
                                                AutoReverse="True"
                                                Duration="0:0:0.2"
                                                From="-40"
                                                RepeatBehavior="Forever"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <DoubleAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                            <DoubleAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                            <ColorAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
                                                To="Red" />
                                            <ColorAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
                                                To="Red" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                         ...

Source code you can find here Pacman control for windows 8

Alexander Molodih

,

Templated custom controls in XAML (WPF, Silverlight)

2667 days ago

If you need a custom control you can make it in an easyest way using other controls you have and make connection between them in code behind. But here are probably few disadvantages like hard connection between logic and view and imposibility for customization control's view in the future. That's why if you want to use you control more than once it is better to make templated control.

I show templated custom control creation using packman as example.

Get Microsoft Silverlight

Firstable we have to define control properties. Packman has parameters: size and mouth angle. So we have to create two properties: Size and MouthAngle. In the future we propably want to bind to this properties so it's better if we make this properties as dependecy properties.

Dependency Property can be registred only in the class inherited from DependencyObject. DependencyObject contains control's properties dictionary, this dictionary uses DependencyProperty as key and propery value as value. Using methods GetValue and SetValue we can access to this dictionary. Should notice that when you register Dependency Property, current value at the dictionary doesn't initializes, so we can have a lot of propertyes in the control but it still have small size in the memory.

DefaultStyleKey defines style type that must be aplied as default

        public sealed class PacmanControl : Control
        {
            public static readonly DependencyProperty SizeProperty =
                DependencyProperty.Register("Size", typeof (double), 
                    typeof (PacmanControl), new PropertyMetadata(default(double)));

            public double Size
            {
                get { return (double) GetValue(SizeProperty); }
                set { SetValue(SizeProperty, value); }
            }

            public static readonly DependencyProperty MouthAngleProperty =
                DependencyProperty.Register("MouthAngle", typeof (double), 
                    typeof (PacmanControl), new PropertyMetadata(default(double)));

            public double MouthAngle
            {
                get { return (double) GetValue(MouthAngleProperty); }
                set { SetValue(MouthAngleProperty, value); }
            }

            public PacmanControl()
            {
                DefaultStyleKey = typeof(PacmanControl);
            }
        }

Note: Make all properties in your custom control as dependency properties. In 99% you might want to bin them in the future.
Binding wouldn't work if your control doesn't inherit from the FrameworkElement class. Control class inherits from FrameworkElement that's why bindings must work.

Note: if you won't define DefaultStyleKey it will be equaled to typeof(Control) (This assignment appears in the Control class constructor.) And default style will be style with type Control insted of PacmanControl.

Now we have PacmanControl that we can use in the XAML. Only thing is it won't show anything.

        <Pacman:PacmanControl
            x:Name="pacman"
            MouthAngle="40"
            Size="100" />

Next we have to create template for this control. Template it's view of control. For template attaching we have to define "Template" property. It can be done with styles.
As you might know higher priority in the defining Template property directly, next - from style.

You must be curious about how standart controls are having good look even if you not define template in your application? That's because XAML engine looks at the assembly where control was defined and searching resource with name "Generic.xaml". When it founds the XAML engine applyes styles from it.

Pacman's face consists of two semicirles and eye:

        <Style TargetType="Pacman:PacmanControl">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Pacman:PacmanControl">
                        <Grid>
                            <!--  upper chew  -->
                            <Path
                                    x:Name="TopChew"
                                    VerticalAlignment="Top"
                                    Data="M0,0 A0.5,0.5,0,1,1,1,0 L0.5,0"
                                    Fill="Yellow"
                                    Stretch="Uniform"
                                    Stroke="Gray"
                                    StrokeEndLineCap="Round"
                                    StrokeStartLineCap="Round"
                                    StrokeThickness="1">
                            </Path>
                            <!--  down chew  -->
                            <Path
                                    x:Name="BotChew"
                                    VerticalAlignment="Bottom"
                                    Data="M0,0 A0.5,0.5,0,1,0,1,0 L0.5,0"
                                    Fill="Yellow"
                                    Stretch="Uniform"
                                    Stroke="Gray"
                                    StrokeEndLineCap="Round"
                                    StrokeStartLineCap="Round"
                                    StrokeThickness="1">
                            </Path>
                            <!--  Eye  -->
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="0.45*" />
                                    <ColumnDefinition Width="0.1*" />
                                    <ColumnDefinition Width="0.45*" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="0.25*" />
                                    <RowDefinition Height="0.1*" />
                                    <RowDefinition Height="0.65*" />
                                </Grid.RowDefinitions>
                                <Ellipse
                                    Grid.Row="1"
                                    Grid.Column="1"
                                    Fill="Black" />
                            </Grid>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Now we have pacman sceleton, but properties Size and MouthAngle doesn't do anything.

Get Microsoft Silverlight

After that we have to bound control properties in the template.
With properties With and Height of the parent Grid we can limit pacman's size. Only thin we need is to make: Grid.Width=PacmanControl.Size and Grid.Height=PacmanControl.Size.
We can solve this issue using bindings, actually TemplateBinding extension. TemplateBinding is the only way to bind to control properties from the template.

                    <ControlTemplate TargetType="Pacman:PacmanControl">
                        <Grid                                
                            Width="{TemplateBinding Size}"
                            Height="{TemplateBinding Size}">
                               ...

If you run current example you'll see pacman. And can resize it using Size property.

Get Microsoft Silverlight

Dificalties in the control's properties bounding.
Next thing we may want to do is to add mouth angle changing. In the other words we want to rotate semicircles. We can make it with transformations (RenderTransform and class RotateTransform). Let's add RotateTrnsform classes TopRotator and BotRotator to semicircles:

        <Style TargetType="Pacman:PacmanControl">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Pacman:PacmanControl">
                        <Grid                                
                            Width="{TemplateBinding Size}"
                            Height="{TemplateBinding Size}">
                            <!--  upper chew  -->
                            <Path
                                    x:Name="TopChew"
                                    VerticalAlignment="Top"
                                    Data="M0,0 A0.5,0.5,0,1,1,1,0 L0.5,0"
                                    Fill="Yellow"
                                    Stretch="Uniform"
                                    Stroke="Gray"
                                    StrokeEndLineCap="Round"
                                    StrokeStartLineCap="Round"
                                    StrokeThickness="1">
                                <Path.RenderTransform>
                                    <RotateTransform x:Name="TopRotator" />
                                </Path.RenderTransform>
                            </Path>
                            <!--  down chew  -->
                            <Path
                                    x:Name="BotChew"
                                    VerticalAlignment="Bottom"
                                    Data="M0,0 A0.5,0.5,0,1,0,1,0 L0.5,0"
                                    Fill="Yellow"
                                    Stretch="Uniform"
                                    Stroke="Gray"
                                    StrokeEndLineCap="Round"
                                    StrokeStartLineCap="Round"
                                    StrokeThickness="1">
                                <Path.RenderTransform>
                                    <RotateTransform x:Name="BotRotator" />
                                </Path.RenderTransform>
                            </Path>
                            <!--  Eye  -->
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="0.45*" />
                                    <ColumnDefinition Width="0.1*" />
                                    <ColumnDefinition Width="0.45*" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="0.25*" />
                                    <RowDefinition Height="0.1*" />
                                    <RowDefinition Height="0.65*" />
                                </Grid.RowDefinitions>
                                <Ellipse
                                    Grid.Row="1"
                                    Grid.Column="1"
                                    Fill="Black" />
                            </Grid>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

You may think to bind RotateTransform.Angle property to MouthAngle, but it won't work. RotateTransform doesn't inherits from FrameworkElement class so bindings won't work.
For this reason we'll make this assignments in the control class. Ofcourse it's some kind of connection between control logic and view but we'll make it tiny, and it still be the templated control because we can redefine template. There is only one disadvantage, in the new template we will have to have RotateTransform classes TopRotator and BotRotator.

When new template is attaching virtual metod OnApplyTemplate() is executing. At this moment you can look for TopRotator and BotRotator classes and save it in the memory:

        private const string TopRotator = "TopRotator";
        private const string BotRotator = "BotRotator";

        private RotateTransform _topRotator;
        private RotateTransform _botRotator;

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _topRotator = GetTemplateChild(TopRotator) as RotateTransform;
            _botRotator = GetTemplateChild(BotRotator) as RotateTransform;
        }

After that we can define rotation properties from PacmanControl.

        [TemplatePart(Name = TopRotator, Type = typeof(RotateTransform))]
        [TemplatePart(Name = BotRotator, Type = typeof(RotateTransform))]
        public class PacmanControl : Control
        {
            private const string TopRotator = "TopRotator";
            private const string BotRotator = "BotRotator";

            public static readonly DependencyProperty SizeProperty =
                DependencyProperty.Register("Size", typeof (double), typeof (PacmanControl),
                     new PropertyMetadata(default(double),
                          (o, args) => ((PacmanControl) o).PropertyChangedCallback()));

            public static readonly DependencyProperty MouthAngleProperty =
                DependencyProperty.Register("MouthAngle", typeof (double), typeof (PacmanControl),
                     new PropertyMetadata(default(double),
                          (o, args) => ((PacmanControl) o).PropertyChangedCallback()));

            public double MouthAngle
            {
                get { return (double) GetValue(MouthAngleProperty); }
                set { SetValue(MouthAngleProperty, value); }
            }

            private RotateTransform _topRotator;
            private RotateTransform _botRotator;

            public double Size
            {
                get { return (double) GetValue(SizeProperty); }
                set { SetValue(SizeProperty, value); }
            }
        
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
                _topRotator = GetTemplateChild(TopRotator) as RotateTransform;
                _botRotator = GetTemplateChild(BotRotator) as RotateTransform;
                PropertyChangedCallback();
            }

            private void PropertyChangedCallback()
            {
                if (_topRotator != null)
                {
                    _topRotator.CenterX = Size/2;
                    _topRotator.CenterY = Size/2;
                    _topRotator.Angle = -MouthAngle;
                }
                if (_botRotator != null)
                {
                    _botRotator.CenterX = Size/2;
                    _botRotator.Angle = MouthAngle;
                }
            }
        }

Note: You have to make properties assignment on reqire properties changing and in OnApplyTemplate() method execution.

Note: Attribute TemplatePart needs for showing designer/programmer/tool(Blend) that this control is expecting such classes with such names in the template. This attributes doesn't imply on the control behaviour.

Congratulations: At this moment we have fully working pacman. And we can change it's size and mouth angle dynamicly.

Get Microsoft Silverlight

Reaction on user action
There is only one thing we haven't done, it's some reaction on hovering and mouse pressing.
We can provide it with visual states.

VisualStates are containing in the VisualStateGroup class that connects to controls with "VisualStateManager.VisualStateGroups" attached property. VisualStateGroup property is some kind of dictionary where key is a string and value is an animation. Using this key you can access associated animation and start it, method VisualStateManager.GoToState is doing it. If dictionary doesn't contain requested key it won't throw any exception. Let's assign "Normal", "MouseOver", "Pressed" visual states according to some user actions:

Add following to constructor:

            Loaded += (sender, args) => VisualStateManager.GoToState(this, "Normal", true);
            MouseEnter += (sender, args) => VisualStateManager.GoToState(this, "MouseOver", true);
            MouseLeave += (sender, args) => VisualStateManager.GoToState(this, "Normal", true);
            MouseLeftButtonDown += (sender, args) => VisualStateManager.GoToState(this, "Pressed", true);
            MouseLeftButtonUp += (sender, args) => VisualStateManager.GoToState(this, "Normal", true);

And add requirements for template:

    [TemplateVisualState(Name = "Normal")]
    [TemplateVisualState(Name = "MouseOver")]
    [TemplateVisualState(Name = "Pressed")]

After that we can associate visual states with the animations. About animation and storyboard you can read in my previous post: Animation in Silverlight 5

                    <ControlTemplate TargetType="Pacman:PacmanControl">
                        <Grid Width="{TemplateBinding Size}" Height="{TemplateBinding Size}">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal">
                                        <Storyboard>
                                            <DoubleAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)" />
                                            <DoubleAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)" />
                                            <ColorAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.Fill).(SolidColorBrush.Color)" />
                                            <ColorAnimation
                                                Duration="0:0:0.2"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.Fill).(SolidColorBrush.Color)" />
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver">
                                        <Storyboard>
                                            <DoubleAnimation
                                                AutoReverse="True"
                                                Duration="0:0:0.2"
                                                From="40"
                                                RepeatBehavior="Forever"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                            <DoubleAnimation
                                                AutoReverse="True"
                                                Duration="0:0:0.2"
                                                From="-40"
                                                RepeatBehavior="Forever"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <DoubleAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                            <DoubleAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty=
                                                    "(Path.RenderTransform).(RotateTransform.Angle)"
                                                To="0" />
                                            <ColorAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="BotChew"
                                                Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
                                                To="Red" />
                                            <ColorAnimation
                                                Duration="0:0:0.5"
                                                Storyboard.TargetName="TopChew"
                                                Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
                                                To="Red" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                         ...
Get Microsoft Silverlight

Current state can be assigned only one per group. When visual state is changing, current animation (from previous visual state) stops and new animation starts. If you doesn't define From values in your animations animation will start from current point and it looks like continuation.

Note: Animation mustn't crossing. IOW one property can't be animated more than ones in the moment, if it be it'll thow exception.

Source code you can find here pacman_silverlight

Alexander Molodih

,

Comment

Animation in Silverlight 5, WPF, Windows 8 METRO

2691 days ago

Storyboard

Storyboard is a controller that manages animation.

Storyboard containns timer that starts ticking after "Storyboard.BeginTime" time, during "Storyboard.Duration" time.
If you define "AutoReverse" property, animation duration will be doubled automatically.
So if you want to calculate animation duraction, it would be "BeginTime + Duration / SpeedRatio" if "AutoReverse" property is false and "BeginTime + Duration / SpeedRatio * 2" in the other case.

Storyboard also provides some method that allows managing animation (play, stop, seek, get current time and so on)

The "Children" property contains an list of animation describers.

Animation describers

Animation describers it's some amount of data that describes how object properties must change their values during animation time.

There are two types of animation in Silverlight:

  • From/To/By animations - this animation types are describing first and last object state for current animation cycle;
  • Key-frame animations - this type of animation are describing object states by each moment of time.

Animation is going acording to the some control property. These properties can be a numeric values, color or point.

Storyboard provides easy way to animate color and motion fluently. It is animation for color, numeric and pont values.

As you might guess Silverlight doesn't provide animation for any other property types because it isn't avident how other propertie types should describe intermediate values.

Describing "From/To/By" based animation

There are some classes which are describing animation parameters.

  • Color animation describes - ColorAnimation
  • Numeric values animation - DoubleAnimation
  • Point values (X,Y) - PointAnimation

To make animation of this type, you have to define such animation parameters:
"From" - defines starting property value (this propertie equals to current value if you not define it);
"To" - final value (equals to value thet was defined in control);
"Duration" - the animation duration (length).

Difference in default values between "From" and "To": Default value for "From" is the value on which last animation was stoped and default value for "To" is the value that was assigned to control.
For example: if we had defined for control value 40px and last animation has changed it to 20px, our properties for animation would have such default values: From=20px, To=40px. It helps making fluent transition between different animations.

Note: In Windows 8 METRO application the From and To default values are defining only on storyboard loading so sometimes it leads to incorrect animation working (when you are chenging properties in runtime). In Silverlight and WPF it works fine.

There are some other additional properties that allows you to delay your animation start, define incrementation for current value, make animation reversable and the other stuf you can read about in the documentation.

The record below describes how to animate "Width" propery of "MyAnimatedShape". It animates this property from 100 to 300 with duration in 1 second.

            <DoubleAnimation 
                Storyboard.TargetName="MyAnimatedShape" 
                Storyboard.TargetProperty="Width"
                From="100"
                To="300" 
                Duration="0:0:1" />

Color animation are making the same way. Property "To" in this case describes color. But how do we get animation for "Fill" property of circle object if it isn't a color? Fill property is a Brush.
It resolves very easy. We can define property from property in animation TargetProperty (actually we can make property chain with any level of imlicity).
Property in chains splitted by dots, every property in this chain describes with such structure (Class.Property). I.e. to describe "Fill" property of Ellipse type you have to use this (Ellipse.Fill), to describe property "Color" of SolidColorBrush you have to use it (SolidColorBrush.Color) and if you want to make properties chain you have to use such string "(Ellipse.Fill).(SolidColorBrush.Color)".
You can also use array accessors in this string

Color animation for solid color brush:

            <ColorAnimation 
                Storyboard.TargetName="MyAnimatedCircle"
                Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)"
                To="Orange"
                Duration="0:0:1" />

Color animation for linear gradient brush:

            <ColorAnimation 
                Storyboard.TargetName="MyAnimatedCircle"
                Storyboard.TargetProperty="(Ellipse.Fill).(LinearGradientBrush.GradientStops)[0].(GradientStop.Color)"
                To="Orange"
                Duration="0:0:1" />       

Describing frame based animations.

Sometimes you have to make difficult animation, even something like cartoons. In this case you have to use animation based on key-frames.
Key-frame describes value/state of the object property at the one moment of time. E.g. "Width" property must be set to 100 at 0 second, 300 at 1 second and come back to 100 at 2 second.

This animation is describing same as "From/To/By animations" with only one exception, insted of "From" and "To" values it uses colection of the values and time when this value must be showed, e.g:

            <DoubleAnimationUsingKeyFrames
                Storyboard.TargetName="MyAnimatedCircle" 
                Storyboard.TargetProperty="Width">
                <DiscreteDoubleKeyFrame KeyTime="0" Value="100" />
                <LinearDoubleKeyFrame KeyTime="0:0:1" Value="300" />
                <LinearDoubleKeyFrame KeyTime="0:0:2" Value="100" />                
            </DoubleAnimationUsingKeyFrames>

This animation doing the same as en animation example for DoubleAnimation described above except returning width value 100 at 2 sec. time.

There are four classes for describing key based animation:

  • Color animation describes - ColorAnimationUsingKeyFrames
  • Numeric values animation - DoubleAnimationUsingKeyFrames
  • Point values (X,Y) - PointAnimationUsingKeyFrames
  • Any object values - ObjectAnimationUsingKeyFrames
All animation describers except "ObjectAnimationUsingKeyFrames" support fluent animation between frames.

Alexander Molodih

,

Molodih Alexander
Categories
Recent articles
Advertisement
Alex's family travels