Spirograph’s  are cool.  See here and here.

I put together three ways to generate points for a Spirograph, first using a Brute Force straight generate the points, second using a Parallel.For and third using LINQ.

Two important classes first.  First is a static DoubleExtension class to help determine when two doubles are about equal:

using System;

namespace DC.SpiroGraph.Core
{
    public static class DoubleExtension
    {
        public static bool AboutEqual(double x, double y)
        {
            double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;

            var variance = x > y ? x - y : y - x;
            return Math.Abs(variance)
         }
    }
}

Second, a simple Point class. Yeah, I probably could have used the Drawing Point class.

using System;
using System.Diagnostics;

namespace DC.SpiroGraph.Core
{
    [DebuggerDisplay("X = {X}, Y = {Y}")]
    public class Point
    {
        public double X { get; set; }
        public double Y { get; set; }

        public bool NotAboutEqualTo(Point point)
        {
            return !DoubleExtension.AboutEqual(X, point.X) && !DoubleExtension.AboutEqual(Y, point.Y);
        }
    }
}

Note the use of DebuggerDisplay to help with the debugging.
The SpiroGraphGenerator:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DC.SpiroGraph.Core
{
    public class SpiroGraphGenerator
    {
        /// <summary>
        /// Radius, R, of Circle (equator) centered at the origin
        /// </summary>
        public double Radius1 { get; set; }

        /// <summary>
        /// Radius, r, of circle (bicyle wheel) cetered at (R + r, 0)
        /// </summary>
        public double Radius2 { get; set; }

        /// <summary>
        /// Distance of Point (reflector) from the center of Circle 2 (the circle of Radius2)
        /// </summary>
        public double Position { get; set; }

        /// <summary>
        /// Controls how precise the SpiroGraph is drawn.
        /// Controls t of f(t).
        /// Resolution = 360, t = 0, 1, 2, 3, ...,360
        /// Resolution = 180, t = 0, 2, 4, 6, ... 360
        ///
        /// </summary>
        public double Resolution { get; set; }

        public IEnumerable<Point>  GetSpiroGraphPoints()
        {

            var t = 0d;
            var sumOfRadius = Radius1 + Radius2;

            var firstPoint = new Point { X = Xt(sumOfRadius, Radius2, Position, t), Y = Yt(sumOfRadius, Radius2, Position, t) };
            var currentPoint = new Point { X = firstPoint.X, Y = firstPoint.Y };

            // Convert to Radians
            var increment = (360 / Resolution) * Math.PI / 180;

            do
            {
                for (var i = 0; i < Resolution; i++)
                {
                    yield return currentPoint;
                    t += increment;
                    currentPoint = new Point { X = Xt(sumOfRadius, Radius2, Position, t), Y = Yt(sumOfRadius, Radius2, Position, t) };
                }
            } while (currentPoint.NotAboutEqualTo(firstPoint));

            yield return currentPoint;
        }

        public IEnumerable<Point> GetSpiroGraphPoints2()
        {
            var endPoints = FindAllEndPoints();
            var numberOfEndPonits = endPoints.Count();
            var numberOfPoints = Convert.ToInt32((numberOfEndPonits - 1)*Resolution + 1);
            var points = new Point[numberOfPoints];
            var resolution = Convert.ToInt32(Resolution);

            var sumOfRadius = Radius1 + Radius2;

            // Convert to Radians
            var increment = (360/Resolution)*Math.PI/180;
            points[0] = GetPoint(sumOfRadius, 0);

            Parallel.For(1, numberOfEndPonits, i =>
			   {
				   for (var j = 0; j < resolution; j++)
				   {
					   var pointIndex = (i - 1)*resolution + j + 1;
					   var t = pointIndex*increment;
					   points[pointIndex] = GetPoint(sumOfRadius, t);
				   }
			   });
            return points;
        }

        public IEnumerable<Point> GetSpiroGraphPoints3()
        {
            var endPoints = FindAllEndPoints();
            var numberOfEndPonits = endPoints.Count();
            var numberOfPoints = Convert.ToInt32((numberOfEndPonits - 1)*Resolution + 1);

            var sumOfRadius = Radius1 + Radius2;

            // Convert to Radians
            var increment = (360/Resolution)*Math.PI/180;

            var points2 = Enumerable.Range(0, numberOfPoints)
                .Select(i => new {Index = i, t = i*increment})
                .Select(it => new {it.Index, point = GetPoint(sumOfRadius, it.t)})
                .OrderBy(ip => ip.Index)
                .Select(ip => ip.point);

            return points2;
        }

        public IEnumerable<Point> FindAllEndPoints()
        {
            var t = 0d;
            var sumOfRadius = CalculateSumOfRadius();

            var firstPoint = GetPoint(sumOfRadius, t);
            var currentPoint = new Point { X = firstPoint.X, Y = firstPoint.Y };
            // Convert to Radians
            var increment = CalculateIncrement();

            do
            {
                yield return currentPoint;
                t += (increment*Resolution);
                currentPoint = GetPoint(sumOfRadius, t);
            } while (currentPoint.NotAboutEqualTo(firstPoint));

            yield return currentPoint;
        }

        private double CalculateIncrement()
        {
            return (360/Resolution)*Math.PI/180;
        }

        private double CalculateSumOfRadius()
        {
            return Radius1 + Radius2;
        }

        private static double Xt(double sumOfRadius, double radius2, double position , double t)
        {
            return sumOfRadius * Math.Cos(t) + position * Math.Cos(sumOfRadius * t / radius2);
        }

        private static double Yt(double sumOfRadius, double radius2, double position, double t)
        {
            return sumOfRadius * Math.Sin(t) + position * Math.Sin(sumOfRadius * t / radius2);
        }

        private Point GetPoint(double sumOfRadius, double t)
        {
            return new Point { X = Xt(sumOfRadius, Radius2, Position, t), Y = Yt(sumOfRadius, Radius2, Position, t) };
        }
    }
}

I created a simple WinForm app using DevExpress controls, this is how I populated the graph with points:

using System;
using System.Linq;
using System.Windows.Forms;

namespace DC.SpiroGraph.WinForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void CreateData(bool useParallel)
        {
            try
            {
                Cursor.Current = Cursors.WaitCursor;

                var sg = new Core.SpiroGraphGenerator
                {
                    Radius1 = Convert.ToDouble(radius1TextEdit.Text),
                    Radius2 = Convert.ToDouble(radius2TextEdit.Text),
                    Position = Convert.ToDouble(positionTextEdit.Text),
                    Resolution = Convert.ToDouble(resolutionTextEdit.Text)
                };

                var endPoints = sg.FindAllEndPoints();
                endPointCountLabel.Text = string.Format("# of End Points: {0}", endPoints.Count());

                var graphPoints = useParallel?  sg.GetSpiroGraphPoints2() : sg.GetSpiroGraphPoints3();
                pointCountLabel.Text = string.Format("# of Points: {0}", graphPoints.Count());

                chartControl1.Series[0].DataSource = graphPoints;

            }
            catch (Exception)
            {

                // Eat It
            }
            finally
            {
                Cursor.Current = Cursors.Default;
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var series = chartControl1.Series[0];
            series.Label.Visible = false;
            series.ArgumentDataMember = "X";
            series.ValueDataMembers.AddRange(new string[] {"Y"});

            radius1TextEdit.Text = "60";
            radius2TextEdit.Text = "60";
            positionTextEdit.Text = "60";
            resolutionTextEdit.Text = "270";

            CreateData(true);
        }

        private void simpleButton1_Click(object sender, EventArgs e)
        {
            CreateData(true);
        }

        private void simpleButton2_Click(object sender, EventArgs e)
        {
            CreateData(false);
        }
    }
}

Future changes will be to make a series for each rotation about the main circle, this way we can start adding different colors to the graph.

Share
 

While working with the FileSystemWatcher I found that if too many files were created the built in buffer will overflowed and files will be skipped.  After much research I found out about the Producer-Consumer Problem.  Then I found that .Net 4 has the BlockingCollection which helps solve the issue.  But how to use it with the FileSystemWatcher?

On StackOverflow I found Making PLINQ and BlockingCollection work together.  I’m not so interested in the PLINQ issue but this is a great example of using The BlockingCollection with FileSystemWatcher.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

namespace ConsoleApplication4
{
    public class Program
    {
        private const string Folder = "C:\\Temp\\InputData";

        static void Main(string[] args) {

            var cts = new CancellationTokenSource();
            foreach (var obj in Input(cts.Token))
                Console.WriteLine(obj);
        }

        public static IEnumerable<object> Input(CancellationToken cancellationToken) {
            var fileList = new BlockingCollection<string>();

            var watcher = new FileSystemWatcher(Folder);
            watcher.Created += (source, e) => {
                if (cancellationToken.IsCancellationRequested)
                    watcher.EnableRaisingEvents = false;
                else if (Path.GetFileName(e.FullPath) == "STOP") {
                    watcher.EnableRaisingEvents = false;
                    fileList.CompleteAdding();
                    File.Delete(e.FullPath);
                } else
                    fileList.Add(e.FullPath);
            };
            watcher.EnableRaisingEvents = true;

            return from file in
                       fileList
                            .GetConsumingEnumerable(cancellationToken)
                            .AsParallel()
                            .WithMergeOptions(ParallelMergeOptions.NotBuffered)
                            .WithCancellation(cancellationToken)
                            .WithDegreeOfParallelism(5)
                   let obj = CreateMyObject(file)
                   select obj;
        }

        private static object CreateMyObject(string file) {
            return file;
        }
    }
}
Share
 

Excel does not have a running total feature or function built in.  All the examples I found on the web to do running totals included VBA code. Not that I have anything against VBA but I thought there should be a way to do running totals with built in worksheet functions.

Enter our one of our favorite functions: OFFSET().  But first, what is a running total?

Running Total Example

A running total is when you have a list of Values and you want to total of the current Value with the Previous values.  Wikipedia states that a running total is “summation of a sequence of numbers which is updated each time a new number is added to the sequence, simply by adding the value of the new number to the running total.”

The key to getting the SUM() correct is getting the Range correct.  For a given Range of Values, start with the First number and SUM() until you get to the current row.  You can do this by using the OFFSET() function and taking advantage of Excel’s table features to get the column range.

 OFFSET ( cell reference, rows, columns, [ height ], [ width ] ) 

In the above case the Running Total column’s formula becomes:

 =SUM( OFFSET( [Values], 0, 0, ROW() - ROW([Values]) + 1, 1 ) ) 

[Values] is the Column we want the running total for.

rows = 0 and columns = 0 because we want to start at the very first cell of [Values]

[width] = 1 because we want only the [Values] column

[height] = ROW() – ROW([Values]) + 1, this is the magic line.

To get the height we have to figure out our current Row number, subtract off the starting Row of [Values] then add 1.  ROW([Values]) gives us the starting row of the column and ROW() gives us the current row.  For example, if the Table starts on row 3 (headers are on row 3) then the column [Values] starts on row 4.  The height of the very first cell in [Values] is:

 ROW() - ROW([Values]) + 1 = 4 - 4 + 1 = 1 

The height of the 3rd cell in the [Values] column is:

ROW() - ROW([Values]) + 1 = 6 - 4 + 1 = 3 

Offset Function in Excel

Running Total

Share
 

Threw together a quick parallel stopwatch test. Not sure if the times prove anything.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Scratch.ParallelProcessing
{
    class Program
    {
        static void Main(string[] args)
        {
            const int count = 10000000;
            var source1 = Enumerable.Range(0, count).ToArray();
            var source2 = Enumerable.Range(0, count).ToArray();
            var source3 = Enumerable.Range(0, count).ToArray();

            Stopwatch stopwatch = new Stopwatch();

            var parallelElapsedTimes = new List<TimeSpan>();
            var linearElapsedTimes = new List<TimeSpan>();
            var linqSelectElapsedTimes = new List<TimeSpan>();

            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                var parallelResults = Parallel.ForEach(source1, s => s %= 2);
                parallelElapsedTimes.Add(stopwatch.Elapsed);
                stopwatch.Reset();

                stopwatch.Start();
                LinearAction(source2, s => s %= 2);
                linearElapsedTimes.Add(stopwatch.Elapsed);
                stopwatch.Reset();

                stopwatch.Reset();
                stopwatch.Start();
                Array.ForEach(source3, s=>s = s%2);
                linqSelectElapsedTimes.Add(stopwatch.Elapsed);
                stopwatch.Reset();

            }

            Console.WriteLine("Elapsed Time\t\tMin\t\tMax\t\t\tAvg");
            Console.WriteLine("============\t\t===\t\t===\t\t\t===");
            Console.WriteLine("{0}\t\t{1}\t\t{2}\t\t\t{3}", "Parallel", parallelElapsedTimes.Min(t => t.Milliseconds), parallelElapsedTimes.Max(t => t.Milliseconds), parallelElapsedTimes.Average(t => t.Milliseconds));
            Console.WriteLine("{0}\t\t\t{1}\t\t{2}\t\t\t{3}", "Linear", linearElapsedTimes.Min(t => t.Milliseconds), linearElapsedTimes.Max(t => t.Milliseconds), linearElapsedTimes.Average(t => t.Milliseconds));
            Console.WriteLine("{0}\t\t\t{1}\t\t{2}\t\t\t{3}", "Linq", linqSelectElapsedTimes.Min(t => t.Milliseconds), linqSelectElapsedTimes.Max(t => t.Milliseconds), linqSelectElapsedTimes.Average(t => t.Milliseconds));

        }

        public static void LinearAction<T>(IEnumerable<T> source, Action<T> action)
        {
            foreach (var s in source) action(s);
        }
    }
}

Results of the timer:

Elapsed Time Min Max Avg
============ === === ===
Parallel 63 191 79.5
Linear 138 143 140.3
Linq 54 56 54.5
Press any key to continue . . .

I’m running 64 bit Vista on a Intel Core2 Duo with 4GB RAM. The Parallel seems to be inconsistent, and depends a lot on whether or not it grabs that second CPU.

Share
 

Open file, read in lines, return individual words, get length of each word, Order by the length of the words, count each word of specific length.


static void Main()
{

	var counts = OpenFileReturnWords(@"LoremIpsumDolor.txt")
		.AsParallel().Select(w=>w.Length)
		.AsParallel().ToLookup(k => k)
		.Select(c => new { Number = c.Key, CountOfNumber = c.Count() })
		.OrderBy(c=>c.Number);

	foreach (var count in counts)
		Console.WriteLine("Count of {0:0000}: {1}", count.Number, count.CountOfNumber);

	Console.WriteLine("Total Count: {0}", counts.Sum(c=>c.CountOfNumber));
}

public static IEnumerable<string> OpenFileReturnWords(string fileName)
{
	using (var reader = new StreamReader(fileName))
	{
		string line;
		while ((line = reader.ReadLine()) != null)
		{
			var wordsInLine = line.Split(new[] {' ', '.'})
				.Where(word => !string.IsNullOrEmpty(word));

			foreach (var word in wordsInLine)
				yield return word;
		}
	}
	yield break;
}
Share
 

I’ve seen a lot of code to generate SQL statements. Invariable the programmer has an array of strings that they loop through (for example to put into an IN clause) and they always have a check to see of the current item is the first or last in the list. The typical usage is to have a StringBuilder and an if statement which determines if an extra comma (or plus sign or whatever) is added or left out.

I say: Stop Doing That!

Use the string.Join().


var strings = new[] {  "Darren", "Dawn", "Thomas", "Zoey" };

var results = string.Format("Replace \"{0}\" with {1} Question Marks: ({2})",
	string.Join(",", strings), strings.Length,
	string.Join(",", Enumerable.Repeat("?", strings.Length))
	);

Console.WriteLine(results);

The resulting output is:

Replace "Darren,Dawn,Thomas,Zoey" with 4 Question Marks: (?,?,?,?)

Share
 

With all the brew ha ha going on about how Delicious is going to be dumped I made a backup of my Delicious bookmarks. While I was at it I created a quick utility to parse the export file so I could play with the links and tags.

Key to parsing the file was the HTML Agility Pack. The Bookmark class:

public class Bookmark
{
	public string Title { get; set; }
	public string Href { get; set; }
	public DateTime AddDate { get; set; }
	public string AddDateEpoch { get; set; }
	public List<string> Tags { get; set; }
	public bool IsPrivate { get; set; }
	public Bookmark()
	{
		Tags = new List<string>();
	}

	public static Bookmark New(HtmlNode node)
	{
		if (node == null) throw new ArgumentNullException("node");

		var bookmark = new Bookmark
						   {
							   Title = node.InnerText ?? string.Empty,
							   Href = node.Attributes["href"].Value ?? string.Empty,
							   AddDate = FromUnixTime(Convert.ToDouble(node.Attributes["ADD_DATE"].Value ?? "0")),
							   IsPrivate = (node.Attributes["ADD_DATE"].Value ?? "0").Equals("1")
						   };

		bookmark.Tags.AddRange(GetTags(node.Attributes["tags"].Value ?? string.Empty));

		return bookmark;
	}

	protected static DateTime FromUnixTime(double unixTime)
	{
		DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
		return epoch.AddSeconds(unixTime);
	}

	protected static string[] GetTags(string tagList)
	{
		if(string.IsNullOrEmpty(tagList)) return new string[]{};

		return tagList.Trim().Split(',');
	}

}

The tricky part was really just handling the Epoch time for the AddDate field.

Using the class:

var doc = new HtmlDocument();
doc.Load(@"Data\delicious.htm");

var bookmarks = doc.DocumentNode.SelectNodes("//a[@href]").Select(Bookmark.New);

var tags = bookmarks.SelectMany(b => b.Tags).Distinct().OrderBy(t => t);

var output = new StringBuilder();

foreach (var tag in tags)
{
	output.AppendLine(tag);
	string localTag = tag;
	var taggedBookmarks = bookmarks.Where(b => b.Tags.Contains(localTag)).OrderBy(b => b.AddDate);
	foreach (var taggedBookmark in taggedBookmarks)
	{
		output.AppendFormat("\t{0}", taggedBookmark.Title).AppendLine();
	}
}

File.WriteAllText("TaggedBookmarks.txt", output.ToString());
Console.WriteLine(output.ToString());
Share
 

Damon Payne has a post AddRange for ObservableCollection in Silverlight 3. It is pretty short and sweet way to improve performance when batch adding data to an ObservableCollection.

He created a SmartCollection:

    public class SmartCollection<T> : ObservableCollection<T>
    {
        public SmartCollection()
        {
            _suspendCollectionChangeNotification = false;
        }

        bool _suspendCollectionChangeNotification;

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (!_suspendCollectionChangeNotification)
            {
                base.OnCollectionChanged(e);
            }
        }

        public void SuspendCollectionChangeNotification()
        {
            _suspendCollectionChangeNotification = true;
        }

        public void ResumeCollectionChangeNotification()
        {
            _suspendCollectionChangeNotification = false;
        }

        public void AddRange(IEnumerable<T> items)
        {
            this.SuspendCollectionChangeNotification();
            try
            {
                foreach (var i in items)
                    base.InsertItem(Count, i);
            }
            finally
            {
                this.ResumeCollectionChangeNotification();

                var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
                this.OnCollectionChanged(arg);
            }
        }
    }

I created a scratch / test app to see if it works:

<UserControl
    x:Class="SilverlightApplication10.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Orientation="Horizontal">
            <StackPanel Margin="5">
                <StackPanel Orientation="Horizontal" Margin="5">
                    <TextBlock x:Name="SlowText" Height="50"  Width="100"/>
                    <Button x:Name="SlowButton" Content="Slow" Click="SlowButtonClick"/>
                </StackPanel>
                <my:DataGrid x:Name="SlowDataView"
                             Height="300"
                             AutoGenerateColumns="True"
                             RowBackground="Cornsilk"
                             AlternatingRowBackground="LightGray"/>
            </StackPanel>

            <StackPanel Margin="5">
                <StackPanel Orientation="Horizontal" Margin="5">
                    <TextBlock x:Name="FastText" Height="50"  Width="100"/>
                    <Button x:Name="FastButton" Content="Fast" Click="FastButtonClick"/>
                </StackPanel>
                <my:DataGrid x:Name="FastDataView"
                             Height="300"
                             AutoGenerateColumns="True"
                             RowBackground="Cornsilk"
                             AlternatingRowBackground="LightGray"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            _slowCollection = new ObservableCollection<MyClass>();
            SlowDataView.ItemsSource = _slowCollection;

            _fastCollection = new SmartCollection<MyClass>();
            FastDataView.ItemsSource = _fastCollection;
        }

        #region Slow

        private readonly ObservableCollection<MyClass> _slowCollection;
        private void SlowButtonClick(object sender, RoutedEventArgs e)
        {
            var timeSpan = Time(() => { for (int i = 0; i < 25000; i++) _slowCollection.Add(MyClass.BuildRandom()); });
            Dispatcher.BeginInvoke(() => this.SlowText.Text = timeSpan.ToString());
        }

        #endregion

        #region Fast

        private readonly SmartCollection<MyClass> _fastCollection;
        private void FastButtonClick(object sender, RoutedEventArgs e)
        {

            var timeSpan = Time(() =>
                                    {
                                        var list = new List<MyClass>();
                                        for (int i = 0; i < 25000; i++) list.Add(MyClass.BuildRandom());
                                        _fastCollection.AddRange(list);
                                    });

            Dispatcher.BeginInvoke(() => this.FastText.Text = timeSpan.ToString());
        }

        #endregion

        private static TimeSpan Time(Action a)
        {
            var start = DateTime.Now;
            a();
            var end = DateTime.Now;
            return end - start;
        }
    }
    internal class MyClass : INotifyPropertyChanged
    {
        private static Random _random;

        #region Id

        private string _id;

        public string Id
        {
            get { return _id; }
            set
            {
                if (value != _id)
                {
                    _id = value;
                    NotifyPropertyChanged("Id");
                }
            }
        }

        #endregion

        #region SomeRandomNumber

        private int _someRandomNumber;

        public int SomeRandomNumber
        {
            get { return _someRandomNumber; }
            set
            {
                if (value != _someRandomNumber)
                {
                    _someRandomNumber = value;
                    NotifyPropertyChanged("SomeRandomNumber");
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propertyName)
        {
            var propertyChanged = PropertyChanged;
            if (propertyChanged != null)
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        public static MyClass BuildRandom()
        {
            if(_random==null)
                _random = new Random((int) DateTime.Now.Ticks);

            var myclass = new MyClass
                              {
                                  Id = Guid.NewGuid().ToString("N"),
                                  SomeRandomNumber = _random.Next(1, 100)
                              };

            return myclass;
        }
    }

Running the code shows that indeed the AddRange works well to speed up inserts. I was getting about 15 to 25 seconds on the Slow Data and from 3 to 0.09 seconds on the Fast Data. It was interesting to note that if the Slow button was pressed a second time it actually took longer than the first click, but the Fast button had the opposite affect.

Share
 

Last week, Gartner predicted that mobile would be a trillion-dollar industry by 2014. It\’s a market Microsoft doesn\’t want to be shut out of.

via Why is Microsoft suddenly so hot for HTML5? | Betanews.

This and my earlier post reinforce my belief that it is time to break the chains of Microsoft.  Meaning it is time for me to stop being a Mort.

Share
 

“Silverlight is our development platform for Windows Phone,” he said. Silverlight also has some “sweet spots” in media and line-of-business applications, he said.

But when it comes to touting Silverlight as Microsoft’s vehicle for delivering a cross-platform runtime, “our strategy has shifted,” Muglia told me.

via Microsoft: Our strategy with Silverlight has shifted | ZDNet.

I think it is time to move away from the Microsoft platform.  I’m getting tired of having to learn a new framework every year just to keep up with their changes.

I’ve been using Silverlight at work and I like it but if there is not going to be any support or they totally re-target the platform for just phones, whats the point?

Time for python – html5 - JavaScript stack.

Share
© 2012 Tech Shorts Suffusion theme by Sayontan Sinha