Wednesday, July 20, 2011

Silverlight and WPF Measurement Pain

One of the more irritating things I've been dealing with in Silverlight and WPF is that sizing and automatic content placement is a pain.  Don't get me wrong -- I love the new rendering engine and how it works to update the layout, but it can be a pain to "think in terms of WPF or Silverlight."  Especially when the two don't exactly have the same methods of working.

I made a decision fairly early on to build a static Utilities class, which holds all of my ugly code where I have to use the very, very, very fun

#if SILVERLIGHT            

#else

#endif

constructs, which gets me to the point of this post.

It's difficult to determine the size of a control -- especially the size it wants to be.  Ideally, yes, the DesiredSize will have that exact size.  Unfortunately, the DesiredSize is somewhat misleading -- it's the DesiredSize of the space it's confined to.  In addition, it works totally different between Silverlight and WPF.

I was primarily concerned with sizing things before they are rendered (they have no parent).  This is drastically different from figuring out a size once they are in the layout tree and the control itself has a parent.

In any case, I'm only going to cover the basics.  The most simple way to determine the size something wants to take up is to do artificially measure the element and then check the desired size.  Assuming I have a TextBlock named Frank, the following code would determine the size Frank really wants:

Frank.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Frank.Height = Frank.DesiredSize.Height;
Frank.Width = Frank.DesiredSize.Width;

Ah, a crafty trick (and it works in both Silverlight and WPF without a hitch; if Frank didn't have a parent, the code would be different between the two platforms).  We make the control believe it has unlimited space, and then we can use DesiredSize.  Play with it a little bit -- set a breakpoint on DesiredSize before and after this call and see the difference.

For more information about how the Layout system works (which is an excellent read for understanding how WPF/Silverlight work), check out this MSDN article: http://msdn.microsoft.com/en-us/library/ms745058.aspx

Thursday, July 14, 2011

The XAML Message From Hell: "Error HRESULT E_FAIL has been returned from a call to a COM component."

The most irritating thing in XAML-based projects (Silverlight, WPF) is the infamous "Error HRESULT E_FAIL has been returned from a call to a COM component."

Why Microsoft didn't decide to catch it and give a more appropriate exception escapes me.  However, some things I've learned about this:

Your XAML is failing to load!


Now, your XAML may actually be correct, is the funny thing -- but maybe your project is missing the references.  For example, I'm using the Silverlight Toolkit and setting styles at runtime on the fly, allowing for potential theming.  I kept getting this error, even though my other projects which use the same code are building fine -- so why is my latest project failing to build?

I missed a reference that was used in the other project.  When it came to load the XAML style, it was likely getting hung up on the missing reference and saying, "OK, I see you want to use System.Windows.Controls.Layout.Toolkit...  but what in the world is that?"

To rectify this issue, it was necessary to add references all over again.

So, my troubleshooting advice is:

  1. Make sure the XAML is correct.  When writing cross-platform (i.e. Silverlight and WPF), some of the controls/targets behave differently or have different properties.  Visual Studio generally points this out as a build error.
  2. Make sure that you are referencing all dependent assemblies.  Due to Silverlight's dependencies, if I write a library on top of Silverlight Toolkit, which then gets used in an application, that application needs to have the references to Silverlight Toolkit.  
Good luck and when in doubt -- get an extra set of eyes!

Wednesday, July 13, 2011

Embedding (Silverlight) Content in SharePoint Webparts

I'm going to start this off by saying that I am, by no means, a SharePoint expert.  Developing for SharePoint is painful, but also rewarding.  When it works, you feel an immense amount of pride that your solution worked after all of your labor.  However, there's the immeasurable pain and headaches that come from attempting to write it.

That being said, I did find a very clever way of embedding a piece of content (in my case, a Silverlight xap -- but this could be anything, a JPEG, PNG, MP3, etc) into a SharePoint webpart.  Given my background of making everything work on multiple platforms, I wanted to have the simplest possible way of doing this, without having to continually go back and forth with new code.  Given I'm also trying to write a platform of tools and controls, this enables me to make a standardized package where we're not going to have conflicts with different versions of a chunk of content in the layouts folder with different assemblies/features installed.

The jist of this is that we're going to have ASP.NET share the content through a compiled DLL.  Seems complicated?  Not as much as you'd think.  It's a relatively simple process:
  1. Add content to your project.
  2. Add information about the resource to your assembly.
  3. Utilize the resource.
First things first -- make sure you have a SharePoint project setup in Visual Studio 2010.  I imagine this will probably work in prior versions of Visual Studio, but I haven't tested it.

1. Add content to your project.

Originally, I wanted to do things the "right" way for embedding my content -- I wanted to add each file into the mapped Layouts folder that Visual Studio 2010 gracefully provides.  However, a minor issue arose from this -- my content is changing dynamically, and I needed to add it as a linked item (like the way I've done with Silverlight and WPF controls).  Since the file is linked and not actually in the folder, Visual Studio doesn't have it in the directory and thus, it is not built.  You could potentially make a script to copy it back/forth after the content is built (in my case, it's a Silverlight xap), but then you have issues with checking it out, etc.

Instead, we want to link it, since that's much, much, much easier.  It prevents a lot of issues that could also result with conflicting and different features, in the event of any major issues with deployment.

So, I created a folder in my project called "Resources" where I would link this in.  In this example, I have a Silverlight application called "HelloWorld.xap" and will be linking in the existing xap from the Release directory.  Make sure that this is included as an "Embedded Resource" from the "Build Actions."

Gotta love our Hello World applications!

Now, that we have that done, we can get to the slightly uglier part.

2. Add information about the resource to your assembly.

In the SharePoint project, we need to open up AssemblyInfo.cs under Properties and edit it very minorly.  We want to add in the new resource we have.  I had to add in a reference to System.Web, and you likely will too.

At the bottom of the file, you will need to add one entry per resource, as follows:


[assembly: System.Web.UI.WebResource("EmbeddedSPContent.Resources.HelloWorld.xap""application/x-silverlight-2")]

The first parameter of System.Web.UI.WebResource is the name of the file and its location in the project.  Notice that to notate paths/directories, we need to use periods instead of slashes.

The second parameter of System.Web.UI.WebResource is the mime-type.  What do you want the DLL to tell the browser that the file is?  In my case, I want it to be recognized as a Silverlight application.  This could potentially be a PNG, MP3, and so forth.

Still hanging on there?  Cool, because we're almost done.


3. Utilize the resource.

Ah, yes -- the most important part.  We have to actually use it now.  But that's simple.  I'm going to build a pretty standard WebPart and override RenderContents.


I added the following code to render it (a very quick/dirty Silverlight HTML mockup):


            string resourceUrl = Page.ClientScript.GetWebResourceUrl(GetType(), "EmbeddedSPContent.Resources.HelloWorld.xap");
 
            string control = @"
            <div id=""silverlightControlHost"">
                <object data=""data:application/x-silverlight-2,"" type=""application/x-silverlight-2"" width=""400px"" height=""400px"">
                    <param name=""source"" value=""{MY_URL}"" />
                    <param name=""minRuntimeVersion"" value=""4.0.50826"" />
 
                    <a href=""http://go.microsoft.com/fwlink/?LinkID=149156&amp;v=4.0.50826.0"" style=""text-decoration:none"">
                        <img src=""http://go.microsoft.com/fwlink/?LinkId=108181"" alt=""Get Microsoft Silverlight"" style=""border-style:none"" />
                    </a>
                </object>
            </div>";
 
            control = control.Replace("{MY_URL}", resourceUrl);
 
            writer.Write(control);


and voila, it works!

I know what you're thinking -- that's a sexy web part.

I'm including a sample on MSDN for others to use, so this will hopefully make it easier to follow what was done:

http://code.msdn.microsoft.com/Embedding-Resources-for-d61b251d

Please note that in order to get this working, I had to deploy it as a Farm Solution, rather than a Sandboxed Solution.

Tuesday, July 12, 2011

SplineSeries for the Silverlight Toolkit

While the Silverlight Toolkit has made some amazing strides in improving, it still lacks some functionality compared to other toolkit offerings.  In particular, Silverlight Toolkit's charts lack splines, or smoothed lines.  The idea here is that we don't want the graph to appears as though a line is moving from point to point directly -- that it moves parabolically, or softer.

A typical line series.

Now, this is all fun and games, but can we get something where transitions are more smooth?  Yes, we can.

A  spline series.

Notice, the spline series changes are more gradual and curved.  Are they an entirely accurate representation of the in-between?  No, not always -- but they're not meant to be (necessarily).

The Spline Series is literally a copy/paste of the LineSeries, with a few changes.  Note that this is located under the Charting/Series folder of the DataVisualization.Toolkit project.  Essentially, what we want to do is change the Polyline to a Path.  The idea here is that a Polyline doesn't draw Beziers.  Below is my (large) code,

SplineSeries.cs:

// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
 
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows.Media;
using System.Windows.Shapes;
 
#if !DEFINITION_SERIES_COMPATIBILITY_MODE
 
namespace System.Windows.Controls.DataVisualization.Charting
{
    /// <summary>
    /// Represents a control that contains a data series to be rendered in X/Y 
    /// line format.
    /// </summary>
    /// <QualityBand>Preview</QualityBand>
    [StyleTypedProperty(Property = DataPointStyleName, StyleTargetType = typeof(LineDataPoint))]
    [StyleTypedProperty(Property = "LegendItemStyle", StyleTargetType = typeof(LegendItem))]
    [StyleTypedProperty(Property = "PathStyle", StyleTargetType = typeof(Path))]
    [TemplatePart(Name = DataPointSeries.PlotAreaName, Type = typeof(Canvas))]
    [SuppressMessage("Microsoft.Maintainability""CA1501:AvoidExcessiveInheritance", Justification = "Depth of hierarchy is necessary to avoid code duplication.")]
    public partial class SplineSeries : LineAreaBaseSeries<LineDataPoint>
    {
        #region public PointCollection Points
        /// <summary>
        /// Gets the collection of points that make up the spline.
        /// </summary>
        public PointCollection Points
        {
            get { return GetValue(PointsProperty) as PointCollection; }
            private set { SetValue(PointsProperty, value); }
        }
 
        /// <summary>
        /// Identifies the Points dependency property.
        /// </summary>
        public static readonly DependencyProperty PointsProperty =
            DependencyProperty.Register(
                "Points",
                typeof(PointCollection),
                typeof(SplineSeries),
                null);
        #endregion public PointCollection Points
 
        #region public PathGeometry SplinePoints
        /// <summary>
        /// Gets the collection of points that make up the line.
        /// </summary>
        public PathGeometry SplinePoints
        {
            get { return GetValue(SplinePointsProperty) as PathGeometry; }
            private set { SetValue(SplinePointsProperty, value); }
        }
 
        /// <summary>
        /// Identifies the SplinePoints dependency property.
        /// </summary>
        public static readonly DependencyProperty SplinePointsProperty =
            DependencyProperty.Register(
                "SplinePoints",
                typeof(PathGeometry),
                typeof(SplineSeries),
                null);
        #endregion public PathGeometry SplinePoints
 
        #region public double SplineTension
 
        /// <summary>
        /// Gets or sets the tension in the beziers that make up the spline.
        /// </summary>
        /// <remarks>
        /// The greater the tension, the more straight/linear the spline will look.
        /// Less tension creates a more curvy spline.
        /// </remarks>
        public double SplineTension
        {
            get { return (double) GetValue(SplineTensionProperty); }
            set { SetValue(SplineTensionProperty, value); }
        }
 
        /// <summary>
        /// Identifies the SplineTension dependency property.
        /// </summary>
        public static readonly DependencyProperty SplineTensionProperty =
            DependencyProperty.Register(
                "SplineTension",
                typeof(double),
                typeof(SplineSeries),
                new PropertyMetadata(2.5));
        #endregion public double SplineTension
 
        #region public Style PathStyle
        /// <summary>
        /// Gets or sets the style of the Path object that follows the data 
        /// points.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming""CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Path", Justification = "Matches System.Windows.Shapes.Path.")]
        public Style PathStyle
        {
            get { return GetValue(PathStyleProperty) as Style; }
            set { SetValue(PathStyleProperty, value); }
        }
 
        /// <summary>
        /// Identifies the PathStyle dependency property.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming""CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Path", Justification = "Matches System.Windows.Shapes.Path.")]
        public static readonly DependencyProperty PathStyleProperty =
            DependencyProperty.Register(
                "PathStyle",
                typeof(Style),
                typeof(SplineSeries),
                null);
        #endregion public Style PathStyle
 
#if !SILVERLIGHT
        /// <summary>
        /// Initializes the static members of the LineSeries class.
        /// </summary>
        [SuppressMessage("Microsoft.Performance""CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Dependency properties are initialized in-line.")]
        static SplineSeries()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SplineSeries), new FrameworkPropertyMetadata(typeof(SplineSeries)));
        }
 
#endif
        /// <summary>
        /// Initializes a new instance of the LineSeries class.
        /// </summary>
        public SplineSeries()
        {
#if SILVERLIGHT
            this.DefaultStyleKey = typeof(SplineSeries);
#endif
        }
 
        /// <summary>
        /// Acquire a horizontal linear axis and a vertical linear axis.
        /// </summary>
        /// <param name="firstDataPoint">The first data point.</param>
        protected override void GetAxes(DataPoint firstDataPoint)
        {
            GetAxes(
                firstDataPoint,
                (axis) => axis.Orientation == AxisOrientation.X,
                () =>
                {
                    IAxis axis = CreateRangeAxisFromData(firstDataPoint.IndependentValue);
                    if (axis == null)
                    {
                        axis = new CategoryAxis();
                    }
                    axis.Orientation = AxisOrientation.X;
                    return axis;
                },
                (axis) => axis.Orientation == AxisOrientation.Y && axis is IRangeAxis,
                () =>
                {
                    DisplayAxis axis = (DisplayAxis)CreateRangeAxisFromData(firstDataPoint.DependentValue);
                    if (axis == null)
                    {
                        throw new InvalidOperationException(Properties.Resources.DataPointSeriesWithAxes_NoSuitableAxisAvailableForPlottingDependentValue);
                    }
                    axis.ShowGridLines = true;
                    axis.Orientation = AxisOrientation.Y;
                    return axis;
                });
        }
 
        /// <summary>
        /// Updates the Series shape object from a collection of Points.
        /// </summary>
        /// <param name="points">Collection of Points.</param>
        protected override void UpdateShapeFromPoints(IEnumerable<Point> points)
        {
            if (points.Any())
            {
                PointCollection pointCollection = new PointCollection();
                foreach (Point point in points)
                {
                    pointCollection.Add(point);
                }
 
                //At least two points are necessary to generate a proper spline
                if (pointCollection.Count >= 2)
                {
                    PathGeometry geometry = new PathGeometry();
                    PathFigure figure = new PathFigure();
 
                    PointCollection bezierPoints = GetBezierPoints(pointCollection);
 
                    figure.StartPoint = bezierPoints[0];
                    for (int i = 1; i < bezierPoints.Count; i += 3)
                    {
                        figure.Segments.Add(new BezierSegment()
                            {
                                Point1 = bezierPoints[i],
                                Point2 = bezierPoints[i + 1],
                                Point3 = bezierPoints[i + 2]
                            });
                    }
 
                    geometry.Figures.Add(figure);
                    SplinePoints = geometry;
                }
                else
                {
                    SplinePoints = null;
                }
 
                Points = pointCollection;
            }
            else
            {
                Points = null;
                SplinePoints = null;
            }
        }
 
        #region Bezier Curve Building
 
        /*
         * Formulas and code pulled from Kerem Kat's MapBezier example:
         * http://www.codeproject.com/KB/silverlight/MapBezier.aspx
         */ 
 
        private PointCollection GetBezierPoints(PointCollection pts)
        {
            PointCollection ret = new PointCollection();
 
            for (int i = 0; i < pts.Count; i++)
            {
                // for first point append as is.
                if (i == 0)
                {
                    ret.Add(pts[0]);
                    continue;
                }
 
                // for each point except first and last get B1, B2. next point. 
                // Last point do not have a next point.
                ret.Add(GetB1(pts, i - 1, SplineTension));
                ret.Add(GetB2(pts, i - 1, SplineTension));
                ret.Add(pts[i]);
            }
 
            return ret;
        }
 
        private Point GetB1(PointCollection pts, int i, double a)
        {
            Point  derivedPoint = GetDerivative(pts, i, a);
            return new Point(pts[i].X + derivedPoint.X / 3, pts[i].Y + derivedPoint.Y / 3);
        }
 
        private Point GetB2(PointCollection pts, int i, double a)
        {
            Point derivedPoint = GetDerivative(pts, i+1, a);
            return new Point(pts[i + 1].X - derivedPoint.X / 3, pts[i + 1].Y - derivedPoint.Y / 3);
        }
 
        private Point GetDerivative(PointCollection pts, int i, double a)
        {
            if (pts.Count < 2)
                throw new ArgumentOutOfRangeException("pts""Data must contain at least two points.");
  
            if (i == 0)
            {
                // First point.
                return new Point((pts[1].X - pts[0].X) / a, (pts[1].Y - pts[0].Y) / a);
            }
            if (i == pts.Count - 1)
            {
                // Last point.
                return new Point((pts[i].X - pts[i - 1].X) / a, (pts[i].Y - pts[i - 1].Y) / a);
            }
  
            return new Point((pts[i + 1].X - pts[i - 1].X) / a, (pts[i + 1].Y - pts[i - 1].Y) / a);
        }
        #endregion
    }
}
 
#endif


Note that I borrowed my Spline/Bezier code from Kerem Kat's example: http://www.codeproject.com/KB/silverlight/MapBezier.aspx

Essentially what it does, is takes prior points to adjust such that they curve into the next.  The UpdateShapeFromPoints method was changed from the standard LineSeries and uses all of the helper methods that were added to calculate a Bezier curve.

We're pretty much almost done, except for one more minor touch.  We need to have the default style included, such that it complies with how Silverlight Toolkit handles it's control templating.  We add the following chunk of code to Themes\generic.xaml of the same project:


    <!--  charting:SplineSeries  -->
    <Style TargetType="charting:SplineSeries">
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="PathStyle">
            <Setter.Value>
                <Style TargetType="Path">
                    <Setter Property="StrokeThickness" Value="2" />
                    <Setter Property="StrokeMiterLimit" Value="1" />
                </Style>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="charting:SplineSeries">
                    <Canvas x:Name="PlotArea">
                        <Path Data="{TemplateBinding SplinePoints}" Stroke="{TemplateBinding Background}" Style="{TemplateBinding PathStyle}"/>
                        <Polyline Points="{TemplateBinding Points}" />
                    </Canvas>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Now, just recompile the Toolkit and you should be good to go!

I've included a "patch" with these files and their updates -- they should be capable of being pasted over their old versions (the Themes\generic.xaml just has the SplineSeries.xaml pasted into it -- I would strongly recommend you doing this with your own source):

https://docs.google.com/viewer?a=v&pid=explorer&chrome=true&srcid=0B9hGvs6yJgfJNzA4YmE1OTctNzNmYy00MzMzLWJhNDQtNTc0M2FlNTA3NzFm&hl=en_US