Welcome to MSDN Blogs Sign in | Join | Help

Silverlight 3 API listing in one big text file

One of the things we do to keep API quality high in Silverlight is to periodically dump the complete public API set to a text file & run a diff against previous versions to see what's changed. Figured these "txref" files might be of use to someone else as well.  Two caveats -- the printout for generic typenames is a little funky, and it doesn't list any APIs from mscorlib.

Since this blog server only seems to support one file per post, I'll post twice, once for Silverlight 3, and again for Silverlight 4 beta.   Let me know if you find it useful.

xml syntax highlighting in Silverlight

Several years back I posted a XML tokenizer for syntax highlighting. At the time I didn't post a complete app, since it was part of a larger project; if someone recently asked me for a little more context for the tokenizer so I put together a quickie Silverlight project showing how to use the tokenizer for syntax highlighting:

<UserControl x:Class="XmlNotepad.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"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
  <Grid x:Name="LayoutRoot" Background="AliceBlue">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
            <TextBox x:Name="box" AcceptsReturn="True" Grid.Row="0"/>
        <TextBlock x:Name="block" Grid.Row="1"/>
    </Grid>
</UserControl>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace XmlNotepad {
    public partial class MainPage : UserControl {
        public MainPage() {
            InitializeComponent();
            box.TextChanged += new TextChangedEventHandler(box_TextChanged);
        }

        // for xaml
        private static Color ColorForToken(XmlToken token, string tokenText) {
            Color color = Colors.Black;
            switch (token.Kind) {
                case XmlTokenKind.Open:
                case XmlTokenKind.OpenClose:
                case XmlTokenKind.Close:
                case XmlTokenKind.SelfClose:
                case XmlTokenKind.CommentBegin:
                case XmlTokenKind.CommentEnd:
                case XmlTokenKind.CDataBegin:
                case XmlTokenKind.CDataEnd:
                case XmlTokenKind.Equals:
                case XmlTokenKind.OpenProcessingInstruction:
                case XmlTokenKind.CloseProcessingInstruction:
                case XmlTokenKind.AttributeValue:
                    color = Colors.Blue;
                    break;
                case XmlTokenKind.ElementName:
                    color = Colors.Brown;
                    break;
                case XmlTokenKind.TextContent:
                    color = Colors.Black;
                    break;
                case XmlTokenKind.AttributeName:
                case XmlTokenKind.Entity:
                    color = Colors.Red;
                    break;
                case XmlTokenKind.CommentText:
                    color = Colors.Green;
                    break;
            }

            return color;
        }

        void box_TextChanged(object sender, TextChangedEventArgs e) {
            block.Inlines.Clear();
            XmlTokenizer tokenizer = new XmlTokenizer();
            XmlTokenizerMode mode = XmlTokenizerMode.OutsideElement;
            string xml = box.Text;

            List<XmlToken> tokens = tokenizer.Tokenize(xml, ref mode);
            List<string> tokenTexts = new List<string>(tokens.Count);
            List<Color> colors = new List<Color>(tokens.Count);
            int position = 0;
            foreach (XmlToken token in tokens) {
                string tokenText = xml.Substring(position, token.Length);
                tokenTexts.Add(tokenText);
                Color color = ColorForToken(token, tokenText);
                colors.Add(color);
                position += token.Length;
            }
            for (int i = 0; i < tokens.Count; i++) {
                Run run = new Run();
                run.Foreground = new SolidColorBrush(colors[i]);
                run.Text = tokenTexts[i];
                block.Inlines.Add(run);
            }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace XmlNotepad
{
    /*
     * this file implements a mostly correct XML tokenizer.  The token boundaries
     * have been chosen to match Visual Studio syntax highlighting, so a few of
     * the boundaries are little weird.  (Especially comments) known issues:
     *
     * Doesn't handle DTD's
     * mediocre handling of processing instructions <? ?> -- it won't crash,
     *      but the token boundaries are wrong
     * Doesn't enforce correct XML
     * there's a few cases where it will die if given invalid XML
     *
     *
     * This tokenizer has been designed to be restartable, so you can tokenize
     * one line of XML at a time.
     */
   
    //enum TokenColors
    //{
    //    Punctuation, StringLiteral, ElementName, AttributeName, Comment, Normal
    //}

    enum XmlTokenKind : short
    {
        Open, // <
        Close,//>
        SelfClose,// />
        OpenClose,// </
        ElementName,
        ElementWhitespace,//whitespace between attributes
        AttributeName,
        Equals, // inside attribute
        AttributeValue, // attribute value
        CommentBegin, // <!--
        CommentText,
        CommentEnd, // -->
        Entity, // &gt;
        OpenProcessingInstruction, // <?
        CloseProcessingInstruction, // ?>
        CDataBegin, // <![CDATA[
        CDataEnd,// ]]>
        TextContent,
        //WhitespaceContent, // text content that's whitespace.  Space is embedded inside
        EOF, // end of file
    }

    // Used so you can restart the tokenizer for the next line of XML
    enum XmlTokenizerMode
    {
        InsideComment,
        InsideProcessingInstruction,
        AfterOpen,
        AfterAttributeName,
        AfterAttributeEquals,
        InsideElement, // after element name, before attribute or />
        OutsideElement,
        InsideCData,
    }

    struct XmlToken
    {
        public XmlTokenKind Kind;
        public short Length;
        public XmlToken(XmlTokenKind kind, int length)
        {
            Kind = kind;
            Length = (short)length;
        }
    }

    // XML tokenizer, tokens are designed to match Visual Studio syntax highlighting
    class XmlTokenizer
    {
        string input;
        int position = 0;
        XmlTokenizerMode mode = XmlTokenizerMode.OutsideElement;

        public static List<XmlToken> Tokenize(string input)
        {
            XmlTokenizerMode mode = XmlTokenizerMode.OutsideElement;
            XmlTokenizer tokenizer = new XmlTokenizer();
            return tokenizer.Tokenize(input, ref mode);
        }

        public List<XmlToken> Tokenize(string input, ref XmlTokenizerMode _mode)
        {
            this.input = input;
            this.mode = _mode;
            this.position = 0;
            List<XmlToken> result = Tokenize();
            _mode = this.mode;
            return result;
        }

        private List<XmlToken> Tokenize()
        {
            List<XmlToken> list = new List<XmlToken>();
            XmlToken token;
            do {
                int previousPosition = position;
                token = NextToken();
                string tokenText = input.Substring(previousPosition, token.Length);
                list.Add(token);
            } while (token.Kind != XmlTokenKind.EOF);

            List<string> strings = TokensToStrings(list, input);

            return list;
        }

        private List<string> TokensToStrings(List<XmlToken> list, string input)
        {
            List<string> output = new List<string>();
            int position = 0;
            foreach (XmlToken token in list) {
                output.Add(input.Substring(position, token.Length));
                position += token.Length;
            }
            return output;
        }

        // debugging function
        public string RemainingText
        {
            get { return input.Substring(position); }
        }

        private XmlToken NextToken()
        {
            if (position >= input.Length)
                return new XmlToken(XmlTokenKind.EOF, 0);

            XmlToken token;
            switch (mode) {
                case XmlTokenizerMode.AfterAttributeEquals:
                    token = TokenizeAttributeValue();
                    break;
                case XmlTokenizerMode.AfterAttributeName:
                    token = TokenizeSimple("=", XmlTokenKind.Equals, XmlTokenizerMode.AfterAttributeEquals);
                    break;
                case XmlTokenizerMode.AfterOpen:
                    token = TokenizeName(XmlTokenKind.ElementName, XmlTokenizerMode.InsideElement);
                    break;
                case XmlTokenizerMode.InsideCData:
                    token = TokenizeInsideCData();
                    break;
                case XmlTokenizerMode.InsideComment:
                    token = TokenizeInsideComment();
                    break;
                case XmlTokenizerMode.InsideElement:
                    token = TokenizeInsideElement();
                    break;
                case XmlTokenizerMode.InsideProcessingInstruction:
                    token = TokenizeInsideProcessingInstruction();
                    break;
                case XmlTokenizerMode.OutsideElement:
                    token = TokenizeOutsideElement();
                    break;
                default:
                    token = new XmlToken(XmlTokenKind.EOF, 0);
                    throw new Exception ("missing case");
                    break;
            }
            return token;
        }

        private bool IsNameCharacter(char character)
        {
            // XML rule: Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender
            bool result = char.IsLetterOrDigit(character)
            || character == '.' | character == '-' | character == '_' | character == ':';
            return result;
        }

        private XmlToken TokenizeAttributeValue()
        {
            Debug.Assert(mode == XmlTokenizerMode.AfterAttributeEquals);
            int closePosition = input.IndexOf(input[position], position + 1);
            XmlToken token = new XmlToken(XmlTokenKind.AttributeValue, closePosition + 1 - position);
            position = closePosition + 1;
            mode = XmlTokenizerMode.InsideElement;
            return token;
        }

        private XmlToken TokenizeName(XmlTokenKind kind, XmlTokenizerMode nextMode)
        {
            Debug.Assert(mode == XmlTokenizerMode.AfterOpen || mode == XmlTokenizerMode.InsideElement);
            int i;
            for (i = position; i < input.Length; i++) {
                if (!IsNameCharacter(input[i])) {
                    break;
                }
            }
            XmlToken token = new XmlToken(kind, i - position);
            mode = nextMode;
            position = i;
            return token;
        }

        private XmlToken TokenizeElementWhitespace()
        {
            int i;
            for (i = position; i < input.Length; i++) {
                if (!char.IsWhiteSpace(input[i])) {
                    break;
                }
            }
            XmlToken token = new XmlToken(XmlTokenKind.ElementWhitespace, i - position);
            position = i;
            return token;
        }

        private bool StartsWith(string text)
        {
            if (position + text.Length > input.Length)
                return false;
            else
                return input.Substring(position, text.Length) == text;
        }

        private XmlToken TokenizeInsideElement()
        {
            if (char.IsWhiteSpace(input[position]))
                return TokenizeElementWhitespace();
            else if (StartsWith("/>"))
                return TokenizeSimple("/>", XmlTokenKind.SelfClose, XmlTokenizerMode.OutsideElement);
            else if (StartsWith(">"))
                return TokenizeSimple(">", XmlTokenKind.Close, XmlTokenizerMode.OutsideElement);
            else {
                return TokenizeName(XmlTokenKind.AttributeName, XmlTokenizerMode.AfterAttributeName);
            }
        }


        //// We break on newlines because that makes it easier for us
        //// to ignore the space after comments
        //private Token TokenizeWhitespaceContent()
        //{
        //    Debug.Assert(char.IsWhiteSpace(input[position]));
        //    bool sawNewline = false;
        //    int i;
        //    for (i = position; i < input.Length; i++) {
        //        if (!char.IsWhiteSpace(input[i])) {
        //            break;
        //        } else if (input[i] == '\n' || input[i] == '\r') {
        //            sawNewline = true;
        //        } else if (sawNewline) {
        //            break;
        //        }
        //    }
        //    Token token = new Token(TokenKind.WhitespaceContent, i - position);
        //    position = i;
        //    return token;
        //}


        private XmlToken TokenizeText()
        {
            Debug.Assert(input[position] != '<');
            Debug.Assert(input[position] != '&');
            Debug.Assert(mode == XmlTokenizerMode.OutsideElement);
            int i;
            for (i = position; i < input.Length; i++) {
                if (input[i] == '<' || input[i] == '&') {
                    break;
                }
            }
            XmlToken token = new XmlToken(XmlTokenKind.TextContent, i - position);
            position = i;
            return token;
        }

        private XmlToken TokenizeOutsideElement()
        {
            Debug.Assert(mode == XmlTokenizerMode.OutsideElement);
            if (position >= input.Length)
                return new XmlToken(XmlTokenKind.EOF, 0);

            switch (input[position]) {
                case '<':
                    return TokenizeOpen();
                case '&':
                    return TokenizeEntity();
                default:
                    return TokenizeText();
            }
        }

        private XmlToken TokenizeSimple(string text, XmlTokenKind kind, XmlTokenizerMode nextMode)
        {
            XmlToken token = new XmlToken(kind, text.Length);
            position += text.Length;
            mode = nextMode;
            return token;
        }

        private XmlToken TokenizeOpen()
        {
            Debug.Assert(input[position] == '<');
            if (StartsWith("<!--")) {
                return TokenizeSimple("<!--", XmlTokenKind.CommentBegin, XmlTokenizerMode.InsideComment);
            } else if (StartsWith("<![CDATA[")) {
                return TokenizeSimple("<![CDATA[", XmlTokenKind.CDataBegin, XmlTokenizerMode.InsideCData);
            } else if (StartsWith("<?")) {
                return TokenizeSimple("<?", XmlTokenKind.OpenProcessingInstruction, XmlTokenizerMode.InsideProcessingInstruction);
            } else if (StartsWith("</")) {
                return TokenizeSimple("</", XmlTokenKind.OpenClose, XmlTokenizerMode.AfterOpen);
            } else {
                return TokenizeSimple("<", XmlTokenKind.Open, XmlTokenizerMode.AfterOpen);
            }
        }

        private XmlToken TokenizeEntity()
        {
            Debug.Assert(mode == XmlTokenizerMode.OutsideElement);
            Debug.Assert(input[position] == '&');
            XmlToken token = new XmlToken(XmlTokenKind.Entity, input.IndexOf(';', position) - position);
            position += token.Length;
            return token;
        }

        private XmlToken TokenizeInsideProcessingInstruction()
        {
            Debug.Assert(mode == XmlTokenizerMode.InsideProcessingInstruction);
            int tokenend = input.IndexOf("?>", position);
            if (position == tokenend) {
                position += "?>".Length;
                mode = XmlTokenizerMode.OutsideElement;
                return new XmlToken(XmlTokenKind.CloseProcessingInstruction, "?>".Length);
            } else {
                XmlToken token = new XmlToken(XmlTokenKind.TextContent, tokenend - position);
                position = tokenend;
                return token;
            }
        }

        private XmlToken TokenizeInsideCData()
        {
            Debug.Assert(mode == XmlTokenizerMode.InsideCData);
            int tokenend = input.IndexOf("]]>", position);
            if (position == tokenend) {
                position += "]]>".Length;
                mode = XmlTokenizerMode.OutsideElement;
                return new XmlToken(XmlTokenKind.CDataEnd, "]]>".Length);
            } else {
                XmlToken token = new XmlToken(XmlTokenKind.TextContent, tokenend - position);
                position = tokenend;
                return token;
            }
        }


        private XmlToken TokenizeInsideComment()
        {
            Debug.Assert(mode == XmlTokenizerMode.InsideComment);
            int tokenend = input.IndexOf("-->", position);
            if (position == tokenend) {
                position += "-->".Length;
                mode = XmlTokenizerMode.OutsideElement;
                return new XmlToken(XmlTokenKind.CommentEnd, "-->".Length);
            } else {
                XmlToken token = new XmlToken(XmlTokenKind.CommentText, tokenend - position);
                position = tokenend;
                return token;
            }
        }
    }
}

If you're doing an interactive editor, you'll want to fix a few straightforward bugs in the tokenizer for handling invalid XML. Enjoy!

Posted by nkramer | 0 Comments

Subclassing Shape (or more accurately, Path)

One of the more subtle improvements in Silverlight 4 is that we unsealed the Path class. That means you can write a class that behaves like a shape without re-implementing properties like Fill, Stroke, etc. -- just subclass, set Path.Data to render what you want, and provide Measure/Arrange appropriate functionality. (Existing Shapes don't all use the same measure/arrange logic)  Here's a sample Triangle class:

    public class Triangle : Path
    {
        public Triangle()
        {
            CreatePathData(0,0);
        }

        private double lastWidth = 0;
        private double lastHeight = 0;
        private PathFigure figure;

        private void AddPoint(double x, double y)
        {
            LineSegment segment = new LineSegment();
            segment.Point = new Point(x + 0.5 * StrokeThickness,
                y + 0.5 * StrokeThickness);
            figure.Segments.Add(segment);
        }

        private void CreatePathData(double width, double height)
        {
            // in order to fit in our layout slot we need to reduce the size of the stroke
            height -= this.StrokeThickness;
            width -= this.StrokeThickness;

            // needed to avoid a layout loop
            if (lastWidth == width && lastHeight == height) return;
            lastWidth = width;
            lastHeight = height;

            var geometry = new PathGeometry();
            figure = new PathFigure();
            figure.StartPoint = new Point(0 + 0.5 * StrokeThickness, height + 0.5 * StrokeThickness);
            AddPoint(width, height);
            AddPoint(width / 2, 0);
            AddPoint(0, height);
            figure.IsClosed = true;
            geometry.Figures.Add(figure);
            this.Data = geometry;
        }
       
        protected override Size MeasureOverride(Size availableSize)
        {
            return availableSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            CreatePathData(finalSize.Width, finalSize.Height);
            return finalSize;
        }
    }

Enjoy!

Posted by nkramer | 1 Comments

Silverlight 4 Security Overview White Paper

Wanted to let folks know about a white paper we're making available (attached below). We plan to incorporate this into the main Silverlight documentation by the time we ship the Silverlight 4, but in the meantime I didn't want to keep this content to all ourselves.  J We have a fair amount of documentation on Silverlight security already, but there's a couple holes that we hope to address with this paper. The more obvious is that it's, well, an overview -- sometimes you don't need all the gory details, you need a basic lay of the land so you can orient yourself and figure out what details are relevant to you. The second thing we're trying to address is to give an introduction to our security thinking, for example why it's safe for Silverlight to allow sandboxed apps to open files (OpenFileDialog & isolated storage). We don't get into every detail of every security decision we've made, but it will give you a lot of insight into how we choose what to enable in the sandbox.

As usual, we appreciate your feedback, both about Silverlight and about this paper, either via blog comments or by emailing me directly at nkramer@microsoft.com. Thanks.

Update 11/23 -- Thanks for your feedback, I made some edits based on your suggestions:

  • reorganized the networking section
  • added WebBrowser.NavigateToString() as a way to create XSS holes in Silverlight
  • improved wording -- "keep the user safe" -> "help keep the user safe", got rid of the "provably correct" line, etc.
  • provided pointer to more info on mark of the web
  • fixed a couple typos
  • used .doc instead of .docx

Thanks again, I'm looking forward to the next round of feedback!

 

Bulk check-in for SharePoint

This has absolutely nothing to do with Silverlight or WPF, but thought people might find it useful anyway. If you've ever tried to check in a large number of files into SharePoint, you'll notice that there isn't a way to check them in bulk -- you have to check them in one at a time, which gets pretty tedious when you have several hundred files. You can buy programs which add this feature (e.g. http://www.sharepointboost.com/batch-check-in.html) -- but you can also write it yourself, using Sharepoint's web services. I won't claim it's the simplest or most elegant thing I've ever written, but it got the job done for me.

Here's how to do it:

1) Create a new project in Visual Studio. Pretty much anything except Silverlight will work -- Silverlight requires a cross domain file that most SharePoint servers don't & shouldn't have.

2) Add some Web References.  (I recommend the old-school Web Reference over Service Reference since most of the sample code out there uses the former)  You'll want:

I called mine ListsWebService and SiteDataWebService, respectively

3) Grab the following code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.Linq;
using System.IO;
using System.Xml;

namespace SharePointCheckin
{
    public class SharepointHelper
    {
        // useful link to know about:
        // http://weblogs.asp.net/jimjackson/archive/2008/02/11/get-a-sharepoint-list-guid-from-the-browser.aspx

       
        private string urlToSubweb;

        public SharepointHelper(string urlToSubweb)
        {
            this.urlToSubweb = urlToSubweb;
        }

        public string[] GetFolderItems(string absoluteUrl)
        {
            var service = new SiteDataWebService.SiteData();
            Initialize(service, "sitedata.asmx");
            SiteDataWebService._sFPUrl[] urls = null;
            var r = service.EnumerateFolder(absoluteUrl, out urls);
            string[] result = (from doc in urls
                             select urlToSubweb + "/" + doc.Url).ToArray();
            return result;
        }


        public bool CheckIn(string absoluteUrl, string comment)
        {
            ListsWebService.Lists service = new ListsWebService.Lists();
            Initialize(service, "lists.asmx");
            bool result = service.CheckInFile(absoluteUrl,
                comment,
                "1");
            // "1" is the code for major check-in rather than minor or replace
            return result; //  true for success, false for failure or if file is already checked in
        }

        public bool CheckOut(string absoluteUrl)
        {
            ListsWebService.Lists service = new ListsWebService.Lists();
            Initialize(service, "lists.asmx");
            bool result = false;
            try {
                result = service.CheckOutFile(absoluteUrl,
                    "false", // checked out for offline editing
                    null); // lastmodified
            } catch (System.Web.Services.Protocols.SoapException e) {
                if (e.Detail.InnerText.Contains("is checked out or locked for editing by"))
                    return false;
                else
                    throw;
            }
            return result; //  true for success, false for failure or if file is already checked out
        }

        public bool RevertCheckOut(string absoluteUrl)
        {
            ListsWebService.Lists service = new ListsWebService.Lists();
            Initialize(service, "lists.asmx");
            bool result = service.UndoCheckOut(absoluteUrl);
            return result; //  true for success, false for failure or if file is already checked in
        }

        // based off http://www.sharepoint-tips.com/2007/02/how-to-use-getlistitems-web-service.html
        public string GetWebID()
        {
            SiteDataWebService.SiteData service = new SiteDataWebService.SiteData();
            Initialize(service, "sitedata.asmx");

            SiteDataWebService._sWebMetadata webMetaData;
            SiteDataWebService._sWebWithTime[] arrWebWithTime;
            SiteDataWebService._sListWithTime[] arrListWithTime;
            SiteDataWebService._sFPUrl[] arrUrls;
            string roles; string[] roleUsers; string[] roleGroups;
            uint i = service.GetWeb(out webMetaData, out arrWebWithTime, out arrListWithTime, out arrUrls, out roles, out roleUsers, out roleGroups);
            return webMetaData.WebID;
        }

        // based off http://www.sharepoint-tips.com/2007/02/how-to-use-getlistitems-web-service.html
        public string[] GetListItems(string listName)
        {
            ListsWebService.Lists service = new ListsWebService.Lists();
            Initialize(service, "lists.asmx");
            XmlDocument doc = new XmlDocument();
            // if you want to do server-side filtering, here's a good place to do it
            doc.LoadXml("<Document><Query /><ViewFields /><QueryOptions /></Document>");
            XmlNode listQuery = doc.SelectSingleNode("//Query");
            XmlNode listViewFields = doc.SelectSingleNode("//ViewFields");
            XmlNode listQueryOptions = doc.SelectSingleNode("//QueryOptions");

            string webID = GetWebID();
            XmlNode items = service.GetListItems(listName, string.Empty, listQuery, listViewFields, string.Empty, listQueryOptions, webID);
            List<string> results = new List<string>();
            foreach (var row in items.ChildNodes[1].ChildNodes) {
                if (row is XmlElement) {
                    string name = (row as XmlElement).GetAttribute("ows_EncodedAbsUrl");
                    results.Add(name);
                }
            }
            return results.ToArray();
        }

        public string[] GetListItemsFromGuid(string listGuid)
        {
            SiteDataWebService.SiteData service = new SiteDataWebService.SiteData();
            Initialize(service, "sitedata.asmx");
            //string webID = GetWebID();
            string items = service.GetListItems(listGuid, "", "", 0);
            var document = new XmlDocument();
            document.LoadXml(items);
            List<string> results = new List<string>();
            foreach (var row in document.DocumentElement.GetElementsByTagName("z:row")) {
                if (row is XmlElement) {
                    string name = (row as XmlElement).GetAttribute("ows_EncodedAbsUrl");
                    results.Add(name);
                }
            }
            return results.ToArray();
        }

        private void Initialize(System.Web.Services.Protocols.SoapHttpClientProtocol service,
            string fooDotAsmx)
        {
            service.Url = urlToSubweb + "/_vti_bin/" + fooDotAsmx;
            service.UseDefaultCredentials = true;
        }
    }

}

4) Write some logic to get a list of files, & enumerate over them to check them in or out. For example, mine looked like:

                var helper = new SharepointHelper(http://sitename);
                var guid = "{1234aaaa-1a2a-11a1-aaa1-11111a1a1111}";
                var files =
                    from file in helper.GetListItemsFromGuid(guid)
                    where file.EndsWith(".docx")
                    select file;
                foreach (string file in files) {
                    //helper.CheckOut(file);
                    helper.CheckIn(file, "bulk check in");
                    //helper.RevertCheckOut(file);
                }
 

Note that SharePoint handles folders differently from lists (e.g. "shared documents"), you'll need to enumerate those differently. For folders (which live inside lists), you just need the absolute URL of the folder, then call SharepointHelper.GetFolderItems(). Enumerating a list is a little trickier, you'll need the GUID for the list, then call SharepointHelper.GetListItemsFromGuid(). There's probably a way to figure out the guid programmatically if you're willing to call enough web services, but I did it manually using the steps on http://weblogs.asp.net/jimjackson/archive/2008/02/11/get-a-sharepoint-list-guid-from-the-browser.aspx

Happy hacking!

Posted by nkramer | 2 Comments

VB item template for Silverlight Resource Dictionary

Here's the VB version of the resource dictionary item template.  Copy the attachment into "C:\Users\<your name>\Documents\Visual Studio 2008\Templates\ItemTemplates\Visual Basic" .

VS item templates for Silverlight Resource Dictionaries

To make it easier to create merged resource dictionaries in Silverlight 3 beta, I put together a little Visual Studio item template so you can do "Add | New item... | Silverlight Resource Dictionary".

For C# users, take the attachment & copy it into "C:\Users\<your name>\Documents\Visual Studio 2008\Templates\ItemTemplates\Visual C#" .  Look for the template at the bottom of the dialog in the "My Templates" section.

Enjoy!

ChangeableObject and ValueConverter: databinding conveniences

A couple more bits of code that others might find useful...  Whenever I do databinding-intensive apps (both Silverlight & WPF), I find myself writing a fair amount of boilerplate code.  If you want change notifications on your class, you need to inherit from INotifyPropertyChanged, define a PropertyChanged event, and write a couple lines of code to fire it:

    if (this.PropertyChanged!= null) {
        var args = new PropertyChangedEventArgs("SomeProperty");
        PropertyChanged(this, args);
     }

Not rocket science, but it gets old after the 10th time.  How about a ChangeableObject base class?:

    public class ChangeableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null) {
                var args = new PropertyChangedEventArgs(info);
                PropertyChanged(this, args);
            }
        }
    }

Now you can inherit the PropertyChanged event, and simplify firing it to a single line, e.g.:

    public class Elevator : ChangeableObject
    {
        private double position = 0;
        public double Position
        {
            get { return position; }
            set { position = value; NotifyPropertyChanged("Position"); }
        }
    }

Similarly, it never ceases to annoy me that whenever I write a value converter, I'm forced to define a a ConvertBack method even though I almost never use it.  Plus, IntelliSense for implementing interfaces isn't as strong as overriding virtual methods...  So here's a trivial base class:

    public abstract class ValueConverter : IValueConverter
    {
        public abstract object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);

        public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new Exception("unsupported");
        }
    }

Which can be used like this:

    public class BoolToVisibilityConverter : ValueConverter {
        public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var v = (bool)value;
            var result = (v) ? Visibility.Visible : Visibility.Collapsed;
            return result;
        }
    }

Posted by nkramer | 9 Comments

Why is the Silverlight Key enum missing common characters like comma and period?

Short version is, the keys we left out are harder than you might think to support, and they weren't necessary for the scenarios we focused on (tabbing around forms, writing common controls, accelerated keys, etc.).
 
The longer version requires a little background on how keyboards differ across various languages, especially on Mac. Different languages have different keyboards -- eg, in English, the comma and period are on separate keys, but some European keyboards put them on the same key.  Windows has an abstraction called the virtual key code (eg, OemPeriod), which is what the KeyDown event returns.  Unfortunately Mac doesn't have a concept of virtual key -- they have keyboard-dependent scan codes and they have Unicode, but they don't have the thing that sits in between on Windows called virtual keys.  For alphanumeric and a couple other common keys, Mac uses the same scan code for all languages, but for the rest of the keys they make no promises.  So if Silverlight wanted to support some of these keys on Mac, we would need to create our own mapping layer -- eg. for Russian, scan code 1373 => OemPeriod, for Japanese scan code 528 => OemPeriod...  It could be done, but didn't seem like the best use of resources, so we handled the most common cases and give you PlatformKeyCode for the rest.
Posted by nkramer | 5 Comments

Command helper classes for Silverlight & WPF

Here's a couple classes I've found helpful when writing WPF & Silverlight applications, which I've named simply Command and CommandHelper.  Sometimes, you just want to do simple commanding stuff, and you don't need the overhead of RoutedCommand.  (Also, Silverlight doesn't have RoutedCommand)  The Command class below gives you a quick & easy way to define commands and ways to invoke them: keyboard shortcuts, context menus, toolbars, etc.  Sample usage:

            command = new Command();
            command.Text = "Zoom";
            command.Key = Key.Z;
            command.ModifierKeys = ModifierKeys.Control;
            command.Button = zoomButton;
            command.Execute += delegate() {
                imageDisplay.Zoom = !imageDisplay.Zoom;
            };
            commands.AddCommand(command); // call CommandHelper.AddCommand


As a bonus, by avoiding WPF KeyBinding, you can tie your commands to keystrokes that WPF's KeyBinding wouldn't let you, such as "A" w/o ctrl or alt modifiers.

Here's the complete code:

    public class Command : ICommand
    {
        public event SimpleDelegate Execute;
        //public event CancelEventHandler CanExecute;

        void ICommand.Execute(object parameter)
        {
            if (Execute != null)
                Execute();
        }

        bool ICommand.CanExecute(object parameter)
        {
            // not necessary for this application, and CancelEventArgs doesn't exist on Silverlight
            //CancelEventArgs args = new CancelEventArgs(false);
            //if (CanExecute != null)
            //    CanExecute(this, args);
            //return !args.Cancel;
            return true;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add { }
            remove { }
        }

        public Key Key = Key.None;
        public string DisplayKey;
        public ModifierKeys ModifierKeys = ModifierKeys.None;
        public string Text = "";
        public bool HasMenuItem = true;
        public Button Button = null; // hooks up the command to the button
    }

    public class CommandHelper
    {
        private UIElement owner;
        private List<Command> commands = new List<Command>();

        public CommandHelper(UIElement owner)
        {
            this.owner = owner;
            owner.KeyDown += new KeyEventHandler(keyDown);
        }

        private void keyDown(object sender, KeyEventArgs e)
        {
            foreach (Command command in commands) {
                // Intentionally ignore modifier keys
                bool shiftKeyMatches = (command.ModifierKeys & ModifierKeys.Shift) == (Keyboard.Modifiers & ModifierKeys.Shift);
                if (command.Key == e.Key && shiftKeyMatches) {
                    (command as ICommand).Execute(null);
                }
            }
        }

 

#if WPF
        public void AddBinding(Command command, RoutedCommand applicationCommand)
        {
            CommandBinding binding = new CommandBinding(applicationCommand);
            binding.Executed += delegate(object sender, ExecutedRoutedEventArgs e)
            {
                ((ICommand)command).Execute(null);
            };
            owner.CommandBindings.Add(binding);
        }

        public ContextMenu contextmenu;
#endif

        public void AddMenuSeparator()
        {
#if WPF
            var item = new Separator();
            contextmenu.Items.Add(item);
#endif
        }

        public void AddCommand(Command command)
        {
            commands.Add(command);

            // KeyBinding insists that ModifierKeys != 0 for alphabetic keys,
            // so we have to roll our own
            //this.CommandBindings.Add(new CommandBinding(command));
            //KeyGesture gesture = new KeyGesture(command.Key, command.ModifierKeys);
            //this.InputBindings.Add(new KeyBinding(command, gesture));

#if WPF
            if (command.HasMenuItem) {
                MenuItem item = new MenuItem();
                string text = command.Text + ShortcutText(command);
                item.Header = text;
                item.Command = command;
                contextmenu.Items.Add(item);
            }
#endif

            if (command.Button != null) {
                string text = command.Text + ShortcutText(command);
                ToolTip tooltip = new ToolTip();
                tooltip.Content = text;
                tooltip.Background = (Brush)Application.Current.Resources["menuBackground"];
                tooltip.Foreground = (Brush)Application.Current.Resources["menuForeground"];
                tooltip.BorderBrush = (Brush)Application.Current.Resources["shotclockBrush"];
                command.Button.Click += (object sender, RoutedEventArgs e) => {
                    (command as ICommand).Execute(null);
                };
#if WPF
                command.Button.ToolTip = tooltip;
                //command.Button.Command = command;
#endif
            }
        }

        private static string ShortcutText(Command command)
        {
            string text = "";
            string keyText = null;
            if (command.DisplayKey != null)
                keyText = command.DisplayKey;
            else if (command.Key != Key.None) {
                keyText = command.Key.ToString();
                if ((command.ModifierKeys & ModifierKeys.Shift) != 0)
                    keyText = "shift+" + keyText;
            }

            if (keyText != null)
                text += " (" + keyText + ")";
            return text;
        }
    }

Some assembly required:

  • Inside AddCommand when creating tool tips for toolbar buttons, I put some app-specific styling logic...
  • I punted on supporting CanExecute.  It's trivial to get working in WPF, just uncomment the code above.  Silverlight is nontrivial, hard part is deciding when to call it for the toolbar case -- WPF essentially polls on a timer with some heuristics to minimize perf cost, you'll need to figure out what heuristics work for your Silverlight app.

Enjoy!

Posted by nkramer | 6 Comments

BeginAnimation for Silverlight 2

Here's a version of the BeginAnimation extension method, updated for Silverlight 2:

    static class SilverlightHelpers
    {
        public static void BeginAnimation(this FrameworkElement obj, DependencyProperty property, DoubleAnimation animation)
        {
            var storyboard = new Storyboard();
            storyboard.Children.Add(animation);
            Storyboard.SetTarget(storyboard, obj);
            Storyboard.SetTargetProperty(storyboard, new PropertyPath(property));
            storyboard.Begin();
        }
    }

Cut-and-paste that baby into your project, and through the magic of C# extension methods, you now have a WPF-style BeginAnimation method on FrameworkElement:

            var a = new DoubleAnimation();
            a.To = 200;
            button.BeginAnimation(Canvas.LeftProperty, a);

Like the earlier version, it's not completely the same as WPF in that calling BeginAnimation more than once on the timeline doesn't work, but it's still a handy shortcut for programmatic animations.

Posted by nkramer | 1 Comments

BeginAnimation() method for Silverlight

Yeah, it's been awhile since I've blogged anything, so I thought a nice way to get back in the game was to post a BeginAnimation method.  Sometimes you just want to start a simple animation in code, and the Silverlight way of doing it through storyboards just seems like more code that you feel like typing, which is where the WPF-inspired BeginAnimation comes in.  Turns out you can do a pretty good BeginAnimation API yourself, using C# extension methods -- just drop the following code into your project:

    public static class Helper
    {
        public static void BeginAnimation(this FrameworkElement e, string prop, Timeline t)
        {
            var sb = new Storyboard();
            e.Resources.Add(sb);
            sb.Children.Add(t);
            Storyboard.SetTarget(sb, e);
            Storyboard.SetTargetProperty(sb, prop);
            sb.Begin();
        }
    }

Which can be used:
                var tb = (TextBox)sender;
                var an = new DoubleAnimation();
                an.From = 100;
                an.To = 200;
                tb.BeginAnimation("Height", an);
 
(Okay, it doesn't handle all the corner cases quite like WPF, in particular calling BeginAnimation more than once on the timeline doesn't work, and the string parameter will need to change to DependencyProperty in SL beta 2 once we fix the Silverlight Storyboard.TargetProperty signature to match WPF, but the extension method does handle the most common case...)

Posted by nkramer | 3 Comments

Last conference for awhile...

I got a chance to speak about Silverlight at the TechReady conference this week (same talk I gave at MIX), I love to talk about Silverlight and it's always great to talk with people who are in the trenches using or thinking about using your stuff. TechReady is an interesting conference, it's like TechEd except with all-Microsoft attendees and a few talks thrown in on subjects like "Linux compete" and "selling SQL Server". It's mostly intended for "the field" -- i.e., people not in product development, such as support, consulting, evangelism, etc. Fairly impressive scale, too, there were several thousand attendees and we had the entire Seattle Convention Center.

The conference also marked a personal milestone for me, I've now spoken at every major Microsoft conference (or at least, every conference that's major to my line of work) -- PDC, TechEd, MIX, WinHEC, even gave a talk once at Driver DevCon. I'm not a real outgoing person, but I enjoy giving these talks, I like honing the presentation skills and putting together a good presentation forces you to see things from another perspective. I also get a lot more out of conferences when I'm a speaker, since more people come up to me to talk.

As much as I like the conferences, though, I'm glad I don't have any on my calendar for a while. They take a lot of time to prepare, and every now and then I'd like to work on Silverlight as well as talk about it. <g> It's also been pretty busy year for me, between business & pleasure I've been on the road roughly once a month since December. I like to travel but it takes a toll, I'm really looking forward to sleeping in the same bed for the next couple weeks.

Posted by nkramer | 1 Comments

Drag drop with feedback

I put together a little Silverlight app demonstrating how to drag and drop between different areas of your Silverlight app.  (This isn't drag and drop in the OLE sense, for security reasons Silverlight doesn't support dragging between processes)  I actually did two versions, one in JavaScript and one in C# ((Silverlight 1.0 and Silverlight 1.1 alpha, respectively), although the approach is the same for both.

The experience I wanted to build was that some areas of the application used auto layout, so that when you dropped an item in that area, the item would snap into place.  And I wanted some kind of visual feedback to show where the item would be put.  So I created an element which I called the shadow element, which is that feedback -- shadow element goes where the item will when you release the mouse button.  So when the mouse moves during a drag, we update the location of the shadow element:

 function handleMouseMove (sender, args) {
        var item = sender;
        if (isMouseCaptured) {
            ...
            moveShadow(overPanel);
        }
 }

        function moveShadow(overPanel)
        {
            hideShadow();

            if (overPanel != null && !overPanel.Equals(workspace2)) {
                overPanel.Children.Add(shadowElement);
                arrangePanel(overPanel);
            }
        }

I wanted some of the panels to have auto-layout, a stack panel-like effect, so I wrote a method to do that:

        function arrangeInRow(canvas)
        {
            var next = 10;
            var i;
            for (i = 0; i <canvas.Children.count; i ++) {
                var element = canvas.children.GetItem(i);
                if (element.toString() == "Ellipse") {
                    var child = element;
                    child["Canvas.Left"] = next;
                    child["Canvas.Top"] = 10;
                    next += child.Width;
                    next += 10;
                }
            }
        }

(The if statement in there is a bit of a hack, I was lazy when I wrote the xaml and so my container had some elements I wanted to be laid out -- the ellipses -- and some elements I didn't want layout to move...)  Dave Relyea has a series of layout samples on http://blogs.msdn.com/devdave/archive/2007/05/17/silverlight-1-1-alpha-layout-system-and-controls-framework.aspx.

As the mouse moves around, you need to figure out what it's over.  Scott Barnes describes a technique for doing this in http://blogs.msdn.com/msmossyblog/archive/2007/06/16/performing-a-hittest-with-silverlight.aspx, my code is slightly different since I wrote the first iteration of couple months ago, but it's the same technique.

As you drag an item between containers, you want the item to be on top of all the other containers.  If you don't do anything special, the item you're moving around will remain part of its original container, and the item will be part of its container's z-order -- so your item will appear on top of some containers but underneath others.  Simplest solution is to change the item's parent, remove it from its container and add it to the root element.  When you do that, you'll need to adjust the Canvas.Left and Canvas.Top properties, which is what this function does:

        function changeParentKeepPosition(item, newParent, e)
        {
            var parent = item.getParent();
            item["Canvas.Top"] = translateY(item["Canvas.Top"], parent, newParent, e);
            item["Canvas.Left"] = translateX(item["Canvas.Left"], parent, newParent, e);
            item.getParent().Children.Remove(item);
            newParent.Children.Add(item);
        }

I wrote the C# version of first, in that version there's a single Translate method that takes a Point.  Unfortunately, creating points in JavaScript is fairly awkward (and createFromXaml doesn't support <Point>), so easiest solution for me was to write separate translateX and translateY functions.  This ended up being the single biggest difference between the JavaScript and C# versions...

        function translateX(point, from, to, e)
        {
            var fromPoint = e.GetPosition(from);
            var toPoint = e.GetPosition(to);
            var delta = fromPoint.X - toPoint.X;
            var result = point - delta;
            return result;
        }

        function translateY(point, from, to, e)
        {
            var toPoint = e.GetPosition(to);
            var fromPoint = e.GetPosition(from);
            var delta = fromPoint.Y - toPoint.Y;
            var result = point - delta;
            return result;
        }

where e is any mouse eventargs...  It's an interesting little trick, although GetPosition was designed for getting the mouse position, with a couple extra lines you can turn it into a general-purpose coordinate transformation routine like the above.

More Posts Next page »
 
Page view tracker