Welcome to MSDN Blogs Sign in | Join | Help

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.

Source code for my TechEd RIA talk

The latest iteration of the FantasyBaseball demo... the end result looks virtually identical, but the underlying code has been cleaned up in a couple different ways.  I simplified the solution/project set up significantly, cutting it down from four projects to two.  And I cleaned up the chart control, got it down to under 400 lines and fixed a few bugs in the process.  I could probably trim a few more lines out of the chart, I spent a few extra lines trying hard to reuse existing elements rather than creating new ones, but when all was said and done that "optimization" didn't actually speed anything up...  Just goes to show when you're doing performance work why it's so important to measure the impact of your changes, not everything that sounds fast actually is.

 

Slides & video for my MIX talk

I had several requests for the slides, I've posted them as an attachment.

The video for the talk (I haven't had a chance to watch it myself) is:
  • Building Silverlight Applications using .NET (Part 1)
  • Building Silverlight Applications using .NET (Part 2) 
  • Source code for my MIX demo

    It's been a fun conference, getting to show off all the things we've been working on. And as usual, its also been a lot of work (both by me and the rest of the Silverlight team).  So before I get completely out of conference mode, I wanted to post Jamie + my fantasy baseball demo + chart. As usual, its a demo so we didn't get every detail perfect; something I'd like to clean up is the chart control.  I went back and forth on whether I wanted a general chart engine or one specific to the app, so its not entirely consistent. But still useful...
    Posted by nkramer | 19 Comments

    Attachment(s): FantasyBaseball.zip

    Nick on Silverlight & WPF

    Yup, it's official, WPF/E is now Silverlight. Once upon a time, I used to get annoyed at the marketing department for these changes. "Why can't these guys just pick a name and stick with it? And why is it such a big secret until we officially release it? It's just a name, how important could it be?" But, over time, I've learned to appreciate why marketing does this (even if it does cause me occasional grief).

    A lot of marketing is about finding a way to explain the essence of the product to people really, really fast. There's a lot of products in the world, far too many for any one person to pay attention to all of them. You don't even have enough time to spend 30 seconds on each of them -- you want to know whether that product is relevant, in three words or less. And that's where the marketing department shines, finding names where your gut reaction is the correct one. Quick, what operating system does Windows Presentation Foundation run on?

    Another thing I've come to accept is that like anything else, it takes time and multiple iterations to get the marketing right. What we now call .Net 3.0 was initially WinFX, until we learned how much it was confusing people who were using .Net.  And while you can do a lot of planning up front, there's no substitution for testing a name in the real world.

    The final mystery to me was why the secrecy.  Are we really worried about someone taking the name Windows Presentation Foundation before we get it?  Sure, we want to get the domain name & trademark before someone else squats on them. But mostly it's about creating the most "buzz", by which I mean free advertising. All that news coverage of a product announcement, that's worth millions of dollars of advertising in the amount of customer awareness it generates.

    So while I'm not thrilled about updating the name of my blog yet again, I sleep better knowing that we have smart people making sure we get the most out of our product names.

    Posted by nkramer | 1 Comments

    Getting mouse positions

    John points out that WPF/E's mouse event.X/Y are in the coordinate space of the browser plug-in, which can be pretty awkward to work with if your element is rotated, scaled, or otherwise transformed.  In February CTP, unfortunately there's no way of transforming the coordinates into the space of your choice, short of writing the matrix multiplication math yourself.  I ran into this as well while writing the slider control, it's definitely ugly, we plan to provide an API for this in a future CTP.

    Posted by nkramer | 1 Comments

    Zip file for the mini-framework

    I zipped up all the different files I use for my WPF/E mini-framework.  I also threw in a couple other controls I've been working on -- slider, hyperlinks, and "animated button" (really just a test case to prove that the framework handles storyboards inside control definitions).  Keep in mind you need to run this over http, not over a file: URL...

    I should also confess before someone calls me on it that not everything I did here is best practice:

    • I should have used a TextBlock instead of Glyphs, that way I wouldn't have had to include various font files in the zip
    • I should have referred to the official aghiost.js instead of having my own copy

       Stay tuned for the service pack <g>

      Posted by nkramer | 0 Comments

      Attachment(s): commoncontrols.zip
      More Posts Next page »
       
      Page view tracker