ailon's DevBlog: Development related stuff in my life

Writing WPF/Silverlight compatible code. Part 5: Adding PropertyChangedCallback

11/30/2009 5:13:27 PM

Note for future readers: These series discuss WPF and Silverlight versions that are current stable versions at the time of this writing – WPF 3.5 and Silverlight 3.

These are ongoing series of posts on the subject of WPF/Silverlight compatibility. New posts will be added to the Table of Contents post as they are written so bookmark that post or just subscribe to my RSS feed.

No OverrideMetadata() Take 2

One thing I didn’t mention in the “No OverrideMetadata()” section of “Part 2: Dependency Properties” is how to add a handler to be called when value of inherited dependency property changes. In WPF you can use OverrideMetadata() method to do it like this:

   1: static MyControl()
   2: {
   3: #if !SILVERLIGHT
   4:     FrameworkPropertyMetadata newMetadata = new FrameworkPropertyMetadata();
   5:     newMetadata.PropertyChangedCallback += MyControl.VisibilityProperty_Changed;
   6:     Control.VisibilityProperty.OverrideMetadata(typeof(MyControl), newMetadata);
   7: #endif
   8: }

Then you do what you wanted to do in your handler which is called every time Visibility changes:

   1: private static void VisibilityProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
   2: {
   3:     // do something here
   4: }

As you know there’s no OverrideMetadata() in Silverlight. So what can we do? Unfortunately, to my knowledge, there’s no elegant way to do this. There are however a couple of not so pretty hacks/workarounds. One is described in an answer to a similar question on StackOverflow.

I’ve used another method which involves an extra DependencyProperty declaration and binding of the original dependency property to this one. In the above mentioned case a Silverlight portion for achieving the same result looks like this:

   1: #if SILVERLIGHT
   2:         // declare an extra DependencyProperty for the workaround
   3:         // It uses the same PropertyChangedCallback as WPF code for Visibility
   4:         private static readonly DependencyProperty VisibilityChangedWorkaroundProperty = DependencyProperty.Register(
   5:             "VisibilityChangedWorkaround", typeof(Visibility), typeof(MyControl),
   6:             new PropertyMetadata(MyControl.VisibilityProperty_Changed));
   7: #endif
   8:  
   9: public MyControl()
  10: {
  11: #if SILVERLIGHT
  12:     // visibility changed event workaround
  13:     Binding visibilityBnd = new Binding("Visibility");
  14:     visibilityBnd.Source = this;
  15:     visibilityBnd.Mode = BindingMode.TwoWay;
  16:     this.SetBinding(MyControl.VisibilityChangedWorkaroundProperty, visibilityBnd);
  17: #endif
  18: }

As you can see above, we’ve declared and extra dependency property called VisibilityChangedWorkaroundProperty and we’ve attached the same handler to it’s property changed event as the one used in the WPF code above. We don’t need a CLR property wrapper for it and we’ve declared it as private so it doesn’t show up anywhere outside of our class. Then in the constructor we bind our new workaround property to the real inherited Visibility property. So, when inherited property changes, our local property changes too, and the callback is called.

kick it on DotNetKicks.com Shout it

Tags: , ,

Writing WPF/Silverlight compatible code. Part 4: Geometries

11/23/2009 7:26:43 PM

Note for future readers: These series discuss WPF and Silverlight versions that are current stable versions at the time of this writing – WPF 3.5 and Silverlight 3.

These are ongoing series of posts on the subject of WPF/Silverlight compatibility. New posts will be added to the Table of Contents post as they are written so bookmark that post or just subscribe to my RSS feed.

Default Contstructors Only

The first thing you notice when you start working with geometries in Silverlight after you’ve dealt with them in WPF is absence of constructor overloads. So, if you’ve used to write constructs like this in WPF:

RectangleGeometry rectangle = new RectangleGeometry(new Rect(10, 10, 50, 40));


you should forget about them when you start caring about WPF/Silverlight compatibility. Use something like this instead:

   1: RectangleGeometry rectanble = new RectangleGeometry()
   2: {
   3:     Rect = new Rect(10, 10, 50, 40)
   4: };

This works in both and isn’t that much longer.

No PathSegment.IsStroked

IsStroked let’s you specify that a segment of some path shouldn’t be stroked. So, in WPF you can create a path like this

image

with XAML like this:

   1: <Path Stroke="Black" StrokeThickness="3">
   2:     <Path.Data>
   3:         <PathGeometry>
   4:             <PathFigure StartPoint="0,20">
   5:                 <LineSegment Point="20,0" />
   6:                 <LineSegment Point="40,20" />
   7:                 <LineSegment Point="60,20" IsStroked="False" />
   8:                 <LineSegment Point="80,0" />
   9:                 <LineSegment Point="100,20" />
  10:             </PathFigure>
  11:         </PathGeometry>
  12:     </Path.Data>
  13: </Path>

As you have probably guessed, it’s not going to work in Silverlight 3 (and as far as I’ve checked in Siverlight 4 beta either). For simple scenarios like the one above, you can overcome this limitation by simply dividing PathFigure into several figures (one for each continuos segment) like this

   1: <Path Stroke="Black" StrokeThickness="3">
   2:     <Path.Data>
   3:         <PathGeometry>
   4:             <PathFigure StartPoint="0,20">
   5:                 <LineSegment Point="20,0" />
   6:                 <LineSegment Point="40,20" />
   7:             </PathFigure>
   8:             <PathFigure StartPoint="60,20">
   9:                 <LineSegment Point="80,0" />
  10:                 <LineSegment Point="100,20" />
  11:             </PathFigure>
  12:         </PathGeometry>
  13:     </Path.Data>
  14: </Path>

I’ve created a simple helper extension method to do the same trick automatically in code depending on whether it’s being called in WPF or Silverlight:

   1: public static void AddLineSegment(this PathGeometry pathGeometry, Point point, bool isStroked)
   2: {
   3:     PathFigure figure = pathGeometry.Figures[pathGeometry.Figures.Count - 1];
   4:  
   5: #if SILVERLIGHT
   6:     if (isStroked)
   7:     {
   8:         figure.Segments.Add(new LineSegment() { Point = point });
   9:     }
  10:     else
  11:     {
  12:         // silverlight has no IsStroked properto on LineSegment
  13:         // so we create a new PathFigure to imitate non-stroked line segment
  14:         figure = new PathFigure();
  15:         figure.StartPoint = point;
  16:         pathGeometry.Figures.Add(figure);
  17:     }
  18: #else
  19:     figure.Segments.Add(new LineSegment(point, isStroked));
  20: #endif
  21: }

You can use it like this when constructing paths in your code:

myPathGeometry.AddLineSegment(myPoint, true);

The method can be generalized to support other types of PathSegments, but I needed it for lines so there you have lines-only version. You can generalize/adapt it yourself.

This saves us in cases like the one above. But here’s another simple case that above technique fails to solve:

image

In WPF a figure like this can be created with this XAML:

   1: <Path HorizontalAlignment="Center" VerticalAlignment="Center"
   2:     Stroke="Black" Fill="LightGreen" StrokeThickness="3">
   3:     <Path.Data>
   4:         <PathGeometry>
   5:             <PathFigure StartPoint="0,40">
   6:                 <LineSegment Point="0,20" IsStroked="False" />
   7:                 <LineSegment Point="20,0" />
   8:                 <LineSegment Point="40,20" />
   9:                 <LineSegment Point="60,20" IsStroked="False" />
  10:                 <LineSegment Point="80,0" />
  11:                 <LineSegment Point="100,20" />
  12:                 <LineSegment Point="100,40" IsStroked="False" />
  13:             </PathFigure>
  14:         </PathGeometry>
  15:     </Path.Data>
  16: </Path>

Unfortunately, I don’t know a way to achieve this in Silverlight other than overlaying 2 paths – one for the fill and one for the stroke like this:

   1: <Path Fill="LightGreen" StrokeThickness="3">
   2:     <Path.Data>
   3:         <PathGeometry>
   4:             <PathFigure StartPoint="0,40">
   5:                 <LineSegment Point="0,20"/>
   6:                 <LineSegment Point="20,0" />
   7:                 <LineSegment Point="40,20" />
   8:                 <LineSegment Point="60,20"/>
   9:                 <LineSegment Point="80,0" />
  10:                 <LineSegment Point="100,20" />
  11:                 <LineSegment Point="100,40" />
  12:             </PathFigure>
  13:         </PathGeometry>
  14:     </Path.Data>
  15: </Path>
  16:  
  17: <Path Stroke="Black" StrokeThickness="3">
  18:     <Path.Data>
  19:         <PathGeometry>
  20:             <PathFigure StartPoint="0,20">
  21:                 <LineSegment Point="20,0" />
  22:                 <LineSegment Point="40,20" />
  23:             </PathFigure>
  24:             <PathFigure StartPoint="60,20">
  25:                 <LineSegment Point="80,0" />
  26:                 <LineSegment Point="100,20" />
  27:             </PathFigure>
  28:         </PathGeometry>
  29:     </Path.Data>
  30: </Path>

And since we are talking about WPF/Silverlight reusable code here, this is the way we should do it to work in both. Let me know if you happen to know a better way of doing something like this without having to rely on 2 objects.

kick it on DotNetKicks.com Shout it

Tags: , ,

amCharts Stock Chart for Silverlight Released

11/19/2009 4:21:40 PM

amCharts has just released a beta version of amCharts Stock Chart for Silverlight!

Stock Chart for Silverlight - click for live demo

This version includes all the cool advanced features of our WPF version and enables you to have spectacular advanced stock (and basically any date/time based data) charts in your Silverlight 3 applications.

Take a look at the demos, download your copy, add an advanced control to your Silverlight application and, please, help us spread the word.

kick it on DotNetKicks.com Shout it

Tags: ,

Setting Pens on Shapes in WPF

11/18/2009 2:42:36 PM

Sometimes you need to draw various shapes, drawings and objects using the same elaborate pen. Like the rectangle and the star in this picture.

pentostroke

Problem is that some WPF elements like GeometryDrawing (represented by star in this picture) take Pen object for their pen settings and classes derived from Shape (like Rectangle, Ellipse, etc.) expose all the pen settings directly in the form of StrokeXxx properties. So when you want to draw several GeometryDrawings and several shapes using the same pen settings you end up defining 2 sets of these settings in your Resources: one in the form of a Pen object and 1 for StrokeXxx properties.

In this article we will rectify this problem with the help of one simple extension method and an attached property.

ApplyPen() Method

First we create ApplyPen() extension method for the Shape class. All it does is assigns Pen properties to the appropriate StrokeXxx properties of the Shape. Here's the code:

   1: public static void ApplyPen(this Shape shape, Pen pen)
   2: {
   3:     if (pen != null)
   4:     {
   5:         shape.Stroke = pen.Brush;
   6:         shape.StrokeThickness = pen.Thickness;
   7:         shape.StrokeDashCap = pen.DashCap;
   8:         if (pen.DashStyle != null)
   9:         {
  10:             shape.StrokeDashArray = pen.DashStyle.Dashes;
  11:             shape.StrokeDashOffset = pen.DashStyle.Offset;
  12:         }
  13:         shape.StrokeStartLineCap = pen.StartLineCap;
  14:         shape.StrokeEndLineCap = pen.EndLineCap;
  15:         shape.StrokeLineJoin = pen.LineJoin;
  16:         shape.StrokeMiterLimit = pen.MiterLimit;
  17:     }
  18: }

Attached Property – Pen

Using ApplyPen() method we can apply any pen to any shape from code but we want to be able to do this from XAML too. So we define an attached property called Pen which when set calls our extension method to apply the pen. Here's how it's implemented:

   1: public static readonly DependencyProperty PenProperty = DependencyProperty.RegisterAttached(
   2:     "Pen", typeof(Pen), typeof(ShapeExtender),
   3:     new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ShapeExtender.PenProperty_Changed))
   4:     );
   5:  
   6: public static void SetPen(Shape shape, Pen pen)
   7: {
   8:     shape.SetValue(PenProperty, pen);
   9: }
  10:  
  11: public static Pen GetPen(Shape shape)
  12: {
  13:     return (Pen)shape.GetValue(PenProperty);
  14: }
  15:  
  16: private static void PenProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  17: {
  18:     Shape shape = d as Shape;
  19:     if (shape != null)
  20:     {
  21:         shape.ApplyPen(e.NewValue as Pen);
  22:     }
  23: }

Now we can define a single Pen in our resources and use it for both Rectangle and star (GeometryDrawing) in our sample.

Pen definition:

   1: <Window.Resources>
   2:     <Pen x:Key="MyPen" Thickness="10" LineJoin="Bevel">
   3:         <Pen.Brush>
   4:             <LinearGradientBrush>
   5:                 <GradientStop Color="#FF5555" Offset="0" />
   6:                 <GradientStop Color="#FFFF55" Offset="0.2" />
   7:                 <GradientStop Color="#55FFFF" Offset="0.4" />
   8:                 <GradientStop Color="#FF55FF" Offset="0.6" />
   9:                 <GradientStop Color="#5555FF" Offset="0.8" />
  10:                 <GradientStop Color="#55FF55" Offset="1" />
  11:             </LinearGradientBrush>
  12:         </Pen.Brush>
  13:         <Pen.DashStyle>
  14:             <DashStyle Dashes="0.2 2 2 0.2" />
  15:         </Pen.DashStyle>
  16:     </Pen>
  17: </Window.Resources>

Pen applied to both GeometryDrawing and a Rectangle:

   1: <Rectangle Name="Rectangle1" Canvas.Left="20" Canvas.Top="20" Width="200" Height="100"
   2:            local:ShapeExtender.Pen="{StaticResource MyPen}"
   3:            >
   4: </Rectangle>
   5:  
   6: <Image Canvas.Left="250" Canvas.Top="20" Height="100" Width="100">
   7:     <Image.Source>
   8:         <DrawingImage>
   9:             <DrawingImage.Drawing>
  10:                 <GeometryDrawing Pen="{StaticResource MyPen}">
  11:                     <GeometryDrawing.Geometry>
  12:                         <PathGeometry>
  13:                             <PathFigure StartPoint="20,100" IsClosed="True">
  14:                                 <LineSegment Point="100,40" />
  15:                                 <LineSegment Point="0,40" />
  16:                                 <LineSegment Point="80,100" />
  17:                                 <LineSegment Point="50,0" />
  18:                             </PathFigure>
  19:                         </PathGeometry>
  20:                     </GeometryDrawing.Geometry>
  21:                 </GeometryDrawing>
  22:             </DrawingImage.Drawing>
  23:         </DrawingImage>
  24:     </Image.Source>
  25: </Image>

Bonus: GetPenFromStroke() Method

Using ApplyPen() method and Pen attached property we can apply a Pen to a Shape. But what if we need to go the other way around? For example if you draw something from code using DrawingContext.DrawXxx() methods you need a Pen. And sometimes you want this pen to match settings of some Shape. So you need the opposite of ApplyPen() method – a method that takes a Shape and returns a Pen based on it's StrokeXxx settings. Here is a super-simple but nevertheless useful GetPenFromStroke() extension method:

   1: public static Pen GetPenFromStroke(this Shape shape)
   2: {
   3:     return new Pen()
   4:     {
   5:         Brush = shape.Stroke,
   6:         Thickness = shape.StrokeThickness,
   7:         DashCap = shape.StrokeDashCap,
   8:         DashStyle = new DashStyle()
   9:         {
  10:             Dashes = shape.StrokeDashArray,
  11:             Offset = shape.StrokeDashOffset
  12:         },
  13:         StartLineCap = shape.StrokeStartLineCap,
  14:         EndLineCap = shape.StrokeEndLineCap,
  15:         LineJoin = shape.StrokeLineJoin,
  16:         MiterLimit = shape.StrokeMiterLimit
  17:     };
  18: }

kick it on DotNetKicks.com Shout it

Tags:

Writing WPF/Silverlight compatible code. Part 3: Brushes, Strokes and Dashes

11/12/2009 7:14:43 PM

Note for future readers: These series discuss WPF and Silverlight versions that are current stable versions at the time of this writing – WPF 3.5 and Silverlight 3.

These are ongoing series of posts on the subject of WPF/Silverlight compatibility. New posts will be added to the Table of Contents post as they are written so bookmark that post or just subscribe to my RSS feed.

In this part I’m going to outline my findings about the differences in one of the fundamental visual aspects of WPF/Silverlight user interface visual appearance – Brush and related issues.

No Brushes in Silverlight

That title sounds scary. It’s not that bad as it may look at the first glance though. I’m talking about Brushes class here (not Brush). Brushes class contains a set of predefined solid color brushes which are very handy shortcuts for specifying solid most common brushes in your code.

In pure WPF you are most probably accustomed to using something like this:

   1: border.Background = Brushes.White;

Well, I have bad news for you. You should forget about this, if you are going to write code that works in both WPF and Silverlight. Fortunately both WPF and Silverlight have Colors class which is almost the same as Brushes except rather than predefined brushes it contains predefined colors. So the above code could be universally rewritten this way:

   1: border.Background = new SolidColorBrush(Colors.White);

A little longer but still better than forking your code for such a small issue.

There’s no difference in setting named brushes in XAML though:

   1: <Border Name="border" Background="White">
   2: </Border>

This works in both.

No Clone() in Silverlight

Well, this is not directly related to brushes, but it’s were I got hit by the absence of object cloning in Silverlight. I’ve cloned my stroke brush in WPF, made it semi-transparent and used that brush as my fill brush. This worked with any type of brush with minimal effort on my side. Unfortunately that happened to be quite a difficult issue in Silverlight. Fortunately Justin Angel has posted a universal Clone() method to Silverlight forums. So, the day is saved once again.

Stroke vs. Pen

WPF has Pen class, Silverlight doesn’t. Pen is a nice class encapsulating all the aspects of a stroke in one reusable object. It’s used in WPF in GeometryDrawing (which is not in Silverlight, too, but I’ll leave it for another installment) and other places. Annoying thing is that it’s dropped in most places (like all the shapes) in favor of separate StrokeXxx properties. I even wrote an extension method and an attached property for the Shape derived classes to use Pen. I’ll share it in a separate post since it’s irrelevant in Silverlight anyway.

In Silverlight you don’t have Pen. Most likely it’s for a simple reason – you don’t have any of the classes that use it anyway. So, if you use Pen somewhere in your WPF code you can be sure that you’ll have to rewrite that code in some greater scope than just changing Pen to StrokeXxx.

No DashStyle in Silverlight

Since there’s no Pen, it’s only logical that there’s no DashStyle, too. You just use StrokeDashArray, StrokeDashCap and StrokeDashOffset instead.

kick it on DotNetKicks.com Shout it

Tags: , ,

Weekend Project: Open Source Silverlight Tic-Tac-Toe

11/9/2009 5:24:11 PM

I’m a Visual Studio guy. I feel at home coding XAML by hand and knowing that no one will mess with it. I’ve tried running Expression Blend several times but closed it everytime without doing something substantial. This time I decided that I need to do something simple (but real) and utilize as many Microsoft Expression Studio tools as possible. So, I did this

Tic-Tac-Toe Silverlight 3

a simple Tic-Tac-Toe game (requires Silverlight 3).

Another reason and inspiration for doing  this was a dialog I recently had with my daughter Daniela:

Daniela: Daddy, are you a programmer?
Me: Well, yeah..
Daniela: Can you programm a game?
Me: Yes, a simple one
Daniela: Ok, so do one for me

Daddy said – daddy did.

I’ve posted sources of this project to the Downloads page of this blog under Ms-PL license. You wont find any programming gems in there, but if you want to see a couple of simple custom templated controls, a modified button template, some animation and other small stuff – give it a look.

I think I’ll make a resolution to do some simplistic non-work related project like this every boring weekend.

Tags:

Writing WPF/Silverlight compatible code. Part 2: Dependency Properties

11/5/2009 7:33:43 PM

Note for future readers: These series discuss WPF and Silverlight versions that are current stable versions at the time of this writing – WPF 3.5 and Silverlight 3.

These are ongoing series of posts on the subject of WPF/Silverlight compatibility. New posts will be added to the Table of Contents post as they are written so bookmark that post or just subscribe to my RSS feed.

DependencyProperty.Register

Dependency properties are one of the core concepts of both WPF and Silverlight. To register a dependency property you use DependencyProperty.Register() method. WPF 3.5 has 3 overloads for that method while Silverlight has only one.

   1: public static DependencyProperty Register(
   2:     string name,
   3:     Type propertyType,
   4:     Type ownerType,
   5:     PropertyMetadata typeMetadata
   6: )

Obviously if you want to create cross-platform code you should use that one method all the time in WPF.

No FrameworkPropertyMetadata

For most WPF framework-level application development purposes, FrameworkPropertyMetadata is the type used for dependency property metadata, rather than the base metadata types PropertyMetadata or UIPropertyMetadata.

from MSDN.

Unfortunately there’s no FrameworkPropertyMetadata class in Silverlight.

One of the most used improvements of FrameworkPropertyMetadata over PropertyMetadata are Boolean settings that control invalidation of various layout aspects of the object such as AffectsMeasure, AffectsArrange, etc. If some of these settings are set to true, corresponding aspect is invalidated when property value changes.

Fortunately it’s not too difficult to mimic this behavior using PropertyMetadata and PropertyChangedCallback. So if you would write something like this for WPF-only application:

   1: public static readonly DependencyProperty SomethingProperty = DependencyProperty.Register(
   2:     "Something", typeof(string), typeof(Window1),
   3:     new FrameworkPropertyMetadata(string.Empty,
   4:         FrameworkPropertyMetadataOptions.AffectsMeasure)
   5:         );

you can rewrite it this way to work in both WPF and Silverlight:

   1: public static readonly DependencyProperty SomethingProperty = DependencyProperty.Register(
   2:     "Something", typeof(string), typeof(Window1),
   3:     new PropertyMetadata(string.Empty,
   4:         new PropertyChangedCallback(Window1.SomethingProperty_Changed))
   5:         );
   6:  
   7: private static void SomethingProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
   8: {
   9:     ((FrameworkElement)d).InvalidateMeasure();
  10: }

To make life easier you can create a helper class with methods for invalidating various aspects and combinations and use methods from that class for properties that do nothing else on change.

No Value Coercion

WPF includes a powerful mechanism for value coercion.

Coerce value callbacks do pass the specific DependencyObject instance for properties, as do PropertyChangedCallback implementations that are invoked by the property system whenever the value of a dependency property changes. Using these two callbacks in combination, you can create a series of properties on elements where changes in one property will force a coercion or reevaluation of another property.

A typical scenario for using a linkage of dependency properties is when you have a user interface driven property where the element holds one property each for the minimum and maximum value, and a third property for the actual or current value. Here, if the maximum was adjusted in such a way that the current value exceeded the new maximum, you would want to coerce the current value to be no greater than the new maximum, and a similar relationship for minimum to current.

from MSDN.

Unfortunately there’s no such mechanism in Silverlight. You’ll have to deal with scenarios like the one above by just using a combination of PropertyChangedCallbacks and regular methods. In some complicated but not critical scenarios you might find it reasonable to use value coercion in WPF and just leave uncoerced values in Silverlight. You can do this by forking your code as described in part 1 of these series.

No OverrideMetadata()

WPF let’s you override property metadata. You can do this using OverrideMetadata method of DependencyProperty. In the code below we override the default width of our control inherited from Control class.

   1: static MyControl()
   2: {
   3:     FrameworkPropertyMetadata newMetadata = new FrameworkPropertyMetadata();
   4:     newMetadata.DefaultValue = 180.0;
   5:     Control.WidthProperty.OverrideMetadata(typeof(MyControl), newMetadata);
   6: }

Again, there’s no OverrideMetadata in Silverlight. When you just want to change the default value of some property you can simply set the value in your default public constructor.

   1: public MyControl()
   2: {
   3:     this.Width = 180;
   4: }

However I'm not sure if or how you can set new PropertyChangedCallback on inherited property in Silverlight. Let me know in the comments if you do.

No read-only dependency properties

One of the really nice features in WPF are read-only dependency properties. These are very useful when you need to have a property reflecting some internally determined state of your object (like IsMouseOver property) and don’t want other developers to be able to change the value from the outside. You can easily do this with regular .NET properties but what if you want a read-only property while still enjoying all the perks of dependency property mechanism?

Unfortunately I have no workaround for Silverlight. Sure, you can create a regular CLR property wrapper for dependency property having only the get accessor, but this wont stop anyone from setting the value using SetValue() method.

So you’ll have to make a choice here (unless you find some really working solution): either forget about read-only dependency properties altogether or use separate code (either using pre-processor directives or partial classes) for WPF and Silverlight. Just remember that you’ll have to use different code for not only your dependency property declarations, but SetValue calls, too (you use SetValue() on DependencyPropertyKey with read-only properties). So things can really get messy with constant #if SILVERLIGHT forking.

kick it on DotNetKicks.com Shout it

Tags: , ,

Writing WPF/Silverlight compatible code. Table of Contents

11/5/2009 5:25:00 PM

Note for future readers: These series discuss WPF and Silverlight versions that are current stable versions at the time of this writing – WPF 3.5 and Silverlight 3.

Table of Contents

These are ongoing series of posts on the subject of WPF/Silverlight compatibility. New posts will be added to this ToC as they are written so bookmark this post or just subscribe to my RSS feed.

  1. The Big Picture
  2. Dependency Properties
  3. Brushes, Strokes and Dashes
  4. Geometries
  5. Adding PropertyChangedCallback
  6. Adding XAML files as links
  7. Ghost Lines in Silverlight
  8. … more to come…

Tags: , ,

Writing WPF/Silverlight compatible code. Part 1: The Big Picture

11/2/2009 11:53:59 AM

Note for future readers: These series discuss WPF and Silverlight versions that are current stable versions at the time of this writing – WPF 3.5 and Silverlight 3.

These are ongoing series of posts on the subject of WPF/Silverlight compatibility. New posts will be added to the Table of Contents post as they are written so bookmark that post or just subsribe to my RSS feed.

Introduction

Silverlight is a subset of WPF. This is the short official version. In reality this might be close to true featurewise, but as developers of cross-platform (here and later in these series "platform" should mean WPF or Silverlight) solutions we care not only about what benefits some feature provides to the end user, but about it's API for us (developers) and quite often even about it's internal implementation.

This is how it looks to me at this point (please excuse my artistic “skills”):

wpf_silverlight_theory

At the time of this writing I'm in the middle of converting of one of our WPF charting controls to WPF/Silverlight  compatible code. It took me more than a week of intense work just to get it to compile and display something under Silverlight and I have yet a lot to do before everything works as expected. During this journey I've been writing down all the differences I encounter and I'm going to share my findings, solutions and workarounds in these series.

There's great high-level conceptual information on the differences in Silverlight documentation and Wintillect’s whitepaper, but it's often the details that cost (or save) you time and cause headaches.

The Approach

I assume that anyone reading this plans to develop a cross-platform application or control or at least wants to be prepared for the future. Such project could be approached in two ways:

  1. Develop cross-platform project right from the start
  2. Develop for one platform and adopt for the other later.

Develop cross-platform project right from the start
This is the way to go if your only concern is the quality of the end result. Unfortunately in real life quality is often offset by the other very important metric – time to market. It's hard to argue that one will see some real tangible results faster when concentrating all attention on one platform.

Develop for one platform and adopt for the other later
As was said above you will most likely go this route. Here you have another 2 options: start from WPF or Silverlight. There might be some business, religious or other reasons to start from one or the other, but we will set all of these aside and look only on technical aspects.

From my point of view life would be easier if you start with Silverlight and ease into WPF later. With that said it’s not necessary that easier equals better. You will most likely settle for lowest common denominator which could be minor problem in most cases but could mean a lot in some. I’ll post a separate part on one of the differences were taking a special route for WPF version could make a difference.

If you start with WPF, however, you will most likely end-up doing quite some rewriting when time comes to step into Silverlight, but you wont have to break what’s working just to get a better performing WPF version (considering there’s a reason to care about performance in your project).

The Toolset

There are several methods to facilitate code reuse between Silverlight and WPF versions of your poject while handling the differences at the same time. Personally I use linked code files for common parts and two methods to tame the differences – preprocessor directives (where just a small portion of code differs) and partial classes (for serious differences).

Linked code files in Visual Studio

To add a linked code file to your WPF or Silverlight project in Visual Studio you just go to Project –> “Add Existing Item…” navigate to your other (Silverlight or WPF) project directory, locate the file and use “Add As Link” item in the “Add” dropdown button.

vs_add_as_link

Preprocessor (compiler) directives

When you have a small amount of code differing between the versions you can use preprocessor directives to include one or the other chunk of code depending on the type of project being compiled. For your convinience Silverlight project template predefines “SILVERLIGHT” constant. You can define something shorter and more mistyping proof but I find it’s safer to go with this default (you never know when and how you are goint to reuse this code). Here’s how you go about this:

   1: private TextBox _dataTextBox;
   2: public override void OnApplyTemplate()
   3: {
   4: #if SILVERLIGHT
   5:     _dataTextBox = this.GetTemplateChild("PART_DataTextBox") as TextBox;
   6: #else
   7:     _dataTextBox = this.Template.FindName("PART_DataTextBox", this) as TextBox;
   8: #endif
   9: }

Partial classes

While preprocessor directives are ok for ducttaping small differences, the code becomes quite difficult to digest pretty quickly. Enter partial classes. You define common code in one partial class file and link that file from one project to the other. Then you create a separate partical class files for methods that differ in each project.

Here’s how a simplistic Visual Studio solution implementing this approach looks like:

vs_partial_classes

MyControlWPF is a WPF project. It contains MyControl.cs file which contains a partial MyControl class. That file contains code that is identical for both WPF and Silverlight. It is added to MyControlSL (our Silverlight project) as link. So there’s only one copy of this file to maintain and it’s used in both projects. MyControl.WPF.cs and MyControl.SL.cs on the other hand contain members of MyControl that differ in implementation between WPF and Silverlight.

What about XAML?

Unfortunatelly there’s no standard way to use preprocessor directives or something like that to isolate the differences. There are some custom hacks however. While this is definitely cool from enthusiasts point of view or when you have really large XAML files to maintain, I think it’s easier to simply handle 2 files where differences are unavoidable. So you either make your XAML work for both WPF and Silverlight and link that XAML file from one project or you maintain 2 different files.

That’s it for part 1. Starting with the the next part I’ll show the differences I’ve stumbled upon and a ways to solve or workaround them. Stay tuned…

kick it on DotNetKicks.com Shout it

Tags: , ,

Copyright © 2003 - 2017 Alan Mendelevich
Powered by BlogEngine.NET 2.5.0.6