Display “My Pictures” in Silverlight application at design time

by Andrej Tozon 7. January 2010 01:54

Following up on The ultimate hack for Silverlight in Blend post from Josh Smith, I tried to make Blend display pictures from the My Pictures folder right in my Silverlight application. Needless to say, it worked as advertised :)

Blend

The ViewModel is set through d:DataContext:

public class MainPageViewModel
{
    public MainPageViewModel()
    {
        SetLocalPictures();
    }

    private void SetLocalPictures()
    {
        string folder = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
        IEnumerable<string> pictures = Directory.EnumerateFiles(folder);
        Pictures = (
            from p in pictures 
            select new Uri(string.Format("file://{0}", p.Replace('\\', '/')), UriKind.Absolute)
            ).ToArray();
    }

    public Uri[] Pictures { get; set; }
}

The thing is that this code wouldn’t work with Silverlight application running in a non-trust mode – it would throw a security exception. However, setting this ViewModel as the run-time DataContext and running with elevated permissions, the pictures would get displayed as well.

And of course this works in Visual Studio 2010 designer too:

VS2010

A nice alternative for Blend’s sample data…



Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Development | Expression | Layout | MVVM | Silverlight

Halloween Live Gallery

by Andrej Tozon 26. October 2009 14:39

With Halloween around the corner, it’s time for some scary photos… In this post I’ll explain how I built my demo application I showed at my Windows 7 Launch talk.

Expression Blend 3 shipped with a few interesting samples, available for immediate use. One of them is called Wall3D (Silverlight 3) and is a great example of one of the new Silverlight 3D features, called perspective transform.

image

This sample (perhaps a starter kit is a better description for it) features a 3D photo gallery, which sort of puts the viewer in the center of an oval “room”, full of photos, with an option to rotate the view to the left or right (flick / mouse click and pan), zoom in or out (mouse wheel), etc.. The kind of what people used to do in Flash; now it’s possible in Silverlight too. The sample serves also as a demonstration of yet another new Blend 3 feature, called Sample data – the photos on the above image were generated through this feature. Let’s explore the sample and see how we can push it further, shall we?

After opening Blend 3, switch to the Samples page and choose Wall3D (Silverlight 3) and the default project will be generated for you. image

You can test the application by hitting F5. You navigate through the gallery by flicking the view left or right and zoom in or out with your middle mouse button.

Examining the code that’s been created in the project will reveal the following:

  • the photos come from the sample data source (XML + photos, which are also included in the project – look into the SampleData folder)
  • “The wall” is driven by the CircularPanel3D control (a custom panel)

Knowing this, we’re ready to create our own 3D gallery. We’ll only need a single thing from this sample project, and that’s the CircularPanel3D.cs file, found in the root of the project. Copy and save that in some other folder for later reference, then start a new Silverlight 3 Application project in Blend.

I used an ItemsControl instead of ListBox to arrange and display the photos. Of course both will accept CircularPanel3D as their ItemsPanel, I just didn’t need item selection in this case. Here’s how it all looked in Blend designer:

Now what’s wrong with this picture? It’s hard to design something when you’ve got no visual feedback to help you with the layout and arrangements. I have already modified the ItemTemplate, but without the actual photos, it’s hard to do proper design work.  And that’s where the sample data comes in. To create a sample data source, open the Data panel and click on the second icon from the right image (Add sample data source). Choose Define New Sample Data, name the data source and click OK. You can shape the sample data source however you like – flat, hierarchical, with entities having simple or complex type properties, etc. For this gallery, I created the following source:

image

Bind this data source to the ItemsControl as its items source – voila!

Cool, much better. Modifying the ItemTemplate has just become a lot easier. Let’s add another element for displaying a bit more details about the photo (like the title and location where it was taken).

Photo view Details view

So we’ve got two views for the ItemTemplate, each view being represented with a separate element – one’s with the photo and the second one with details. We have to provide a way for user to switch between the two; how would we do this? Write code? Call the developer? Not necessarily. If we’re in luck, the appropriate piece of code may have already been written, waiting to be taken it used in Blend for declaratively specifying whatever we wanted to do to those elements and how. That piece of code is called a behavior. There are several places on the web to look for behaviors (as well as more info on what they are and how to produce them), but my first bets would be the Expression Gallery and Expression Blend Samples library on Codeplex; the letter containing some very handy behaviors.

I added a couple of behaviors to add interaction to gallery elements - FlipAction would switch the photo and details part through flip motion, triggered by DoubleClick trigger; and ScaleBehavior would enlarge the photo on mouse over. A couple of clicks for very cool-looking effect.

Let’s take a look at the data source... I decided to use Flickr to retrieve the photos, because it’s API is easy to use and can (amongst other things) return geo-location of the place where photo was taken (when available). This geo-location is then passed to the Bing Maps geocoding web serviceas an input parameter to retrieve the real-world address; these addresses can be seen on the bottom of the flipped photo.

image

Playing with geo-location was fun so I decided to take it a step further. I put together a special ShowMapAction, which is currently triggered by a mouse wheel over the photo (or details). The action simply shows the location on the interactive Bing Map (I used Bing Maps Silverlight Map Control for this).

image

Just a word of caution: Geo-encoded photos are cool, but be careful what you’re publishing on the net. The information may not always be used for good purposes.

The final result can be seen here (live). Right in time for keeping track with what’s going on on Halloween.

Finally I have to say I’m spending a lot more time in Blend since version 3 got out. It’s evolving into a great tool and behaviors are going to take a significant part in designer’s/integrator’s role in the future.

http://tozon.info/gallery/



Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Design | Development | Expression | Layout | Silverlight | Behaviors | Photo / Travel

Windows 7 Launch talk: Building cool applications with MS Expression tools

by Andrej Tozon 23. October 2009 13:02

Windows 7 launch day was fun. I gave a talk on Expression tools (and related) – here’s the PowerPoint slide deck for those who asked for it:

I used Expression Blend 3 to build a photo viewer application, which I’ll blog about later; key points here were designer-developer workflow + using sample data and behaviors.

I blogged about the Microsoft ICE last year and I used that exact sample I’ve used in that blog post, but with one difference: did you know that by installing ICE on your desktop will, you can stitch panoramic (or large composite) photos right from your Windows Live Photo Gallery? It’s as easy as selecting the photos and click on the Extras menu item:

Create Image Composite...

Yet another option for stitching a bunch of photos together will give you the Deep Zoom Composer(free and not listed as an Expression tool, but sure looks and feels like it), which has similar features and much more compositing power (different zoom levels, layers, etc.)

And for closing I briefly showcased Expression Encoder 3 and IIS Smooth Streaming.

Yup, fun.



Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Expression | Silverlight | Software | Tech | User Experience | WPF | Design | Layout

Dynamic Data Forms for Silverlight, revisited

by Andrej Tozon 7. April 2009 17:12

A couple of weeks ago, I wrote a post about handling dynamic forms in Silverlight. With this continuation post, I’m going to make a few changes to the original project:

1. Implement a new, custom, field type and provide a template for it (I’ll write about it later), and
2. Make a few changes to the FormFieldTemplateSelector to make it more generic. So instead of writing templates like this:

A new TemplateSelectorDataTemplate…

<local:FormFieldTemplateSelector.StringTemplate>
    <DataTemplate>
        <TextBox Text="{Binding Value, Mode=TwoWay}" Width="100"/>
    </DataTemplate>
</local:FormFieldTemplateSelector.StringTemplate>
<local:FormFieldTemplateSelector.DateTimeTemplate>
    <DataTemplate>
        <basics:DatePicker SelectedDate="{Binding Value, Mode=TwoWay}" Width="100" />
    </DataTemplate>
</local:FormFieldTemplateSelector.DateTimeTemplate>

You could simply write:

<local:TemplateSelectorDataTemplate DataType="System.String">
    <TextBox Text="{Binding Value, Mode=TwoWay}" Width="100"/>
</local:TemplateSelectorDataTemplate>
<local:TemplateSelectorDataTemplate DataType="System.DateTime">
    <basics:DatePicker SelectedDate="{Binding Value, Mode=TwoWay}" Width="100" />
</local:TemplateSelectorDataTemplate>

You can see the DataType property on the TemplateSelectorDataTemplate used instead of using concrete templates like FormFieldTemplateSelecot.StringTemplate. This way, the FormFieldTemplateSelector’s DataType property can be matched with TemplateSelectorDataTemplate’s DataType property [long names confusion, I know] and you don’t have to factor a new template for each new field type you come with, making it feel a bit more like the DataTemplate implementation in WPF. Strings are used here instead of types. One reason for this is that Silverlight doesn’t support x:Type markup, and the second is the fact you can use any string to identify your field type: for example, you can use “#Signature#” string as a custom field type to insert a signature control into your form.

A written signature form field…

To demonstrate a support for custom field types, I created a special control, which will allow the user to sign herself on a special panel and the signature will be submitted to the server together with the other field values.

The control, which will allow us to write on it with the mouse, is of course InkPresenter. I’ve encapsulated it in another control, called SignaturePanel, which adds required mouse event handler which enables the user to write on the InkPresenter with a mouse. Here’s this control, contained within a new TemplateSelectorDataTemplate:

<local:TemplateSelectorDataTemplate DataType="#Signature#"> <local:SignaturePanel Width="100" Height="50"
Strokes="{Binding Value, Mode=TwoWay, Converter={StaticResource strokesConverter}}" /> </local:TemplateSelectorDataTemplate>

The template looks nothing special, except that strokesConverter, specified with the binding. What that does is serialize the Strokes collection from the InkPresenter when being written to the underlying ViewModel, where we need it when sending all submitted data over to the server side. This means that signature actually gets sent over the wire in a form of strokes collection, which you can store in a database. Alternatively, you could convert that strokes into the bitmap on the server and store the signature as an image [And I’ve yet to check how Silverlight 3 WritableBitmap can help doing this on the client]. Anyway, I’ve found StrokeCollection (de)serialization code in Julia Lerman’s Create Web Apps You Can Draw On with Silverlight 2 MSDN article and is included in the code sample attached to this post.

Here’s how the current form looks like:

image

Source code is now available. Please note that this is a Silverlight 3 project. It doesn’t contain any SL3 code, it’s just the project files were converted to SL3 when opened in Visual Studio.

All comments welcome. Enjoy.

Currently rated 3.0 by 4 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

MVVM | Silverlight | User Experience | Development | Layout

Dynamic Data Forms for Silverlight with a Data Template Selector Control

by Andrej Tozon 15. March 2009 16:04

The basic idea behind this post is to show a simple way for items/list controls to display each item data with a different template. The ideal candidate for such exercise are data forms, where user can enter different kinds of information - text, numbers, check marks, etc. Imagine a scenario, where each data form field would be presented as a separate ListBox item… no matter of what data type that field is.

The data model for this solution is going to be very simple and generic. We’ll need a generic class, which will represent a single field in a form. Each field will have an id, a caption, a value (entered by the user) and a value type (what’s the type of the value – text, integer, date, etc.):

public class AutoFormField
{
    public Guid Id { get; private set; }
    public string Caption { get; set; }
    public string Value { get; set; }
    public string Type { get; set; }

    public AutoFormField()
    {
        Id = Guid.NewGuid();
    }
}

When the user requests a form, the associated web service will return a collection of AutoFormFields, representing all fields in the form that user should fill out. I’m not using any database for this example, so I’ve hardcoded the fields, returning to the client, in the service itself:

public class AutoFormService : IAutoFormService
{
    public Collection<AutoFormField> GetForm()
    {
        Collection<AutoFormField> form = new Collection<AutoFormField>();
        form.Add(new AutoFormField()
        {
            Caption = "Name: ",
            Type = typeof(string).FullName
        });
        form.Add(new AutoFormField()
        {
            Caption = "Last name: ",
            Type = typeof(string).FullName
        });
        form.Add(new AutoFormField()
        {
            Caption = "Birth date: ",
            Type = typeof(DateTime).FullName
        });
        form.Add(new AutoFormField()
        {
            Caption = "Is employed: ",
            Type = typeof(bool).FullName,
            Value = false.ToString()
        });
        return form;
    }
}
 
The above code shows that the form consists of two text fields, a date and a yes/no field. We would naturally want to display TextBoxes for text entry, DatePicker for a date, and a CheckBox for a yes/no field entry. The Type property contains an identifier, which will tell the client what kind of entry field to display; field data type names are used here for convenience.
Let’s move to the client and implement this.
 
I’ll use the ItemsControl to display the items, because I don’t need the notion of a selected item (yet). Let’s first get the basics out of the way; this is how the ItemsControl looks like, displaying all the form’s captions:
 
<ItemsControl ItemsSource="{Binding Fields, Source={StaticResource viewModel}}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Caption}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
Its ItemsSource property is bound to a ViewModel’s Fields property, which gets populated when a call to the GetForm() web service method returns. The ItemTemplate is set up so that TextBlock displays the fields’ captions.

What we need to do next is display the actual entry fields the user will fill in. I already mentioned the Type property; this will be the key in deciding what kind of control to display.

If we would be coding this in WPF, we would use a DataTemplateSelector to help us out with selecting the right template to load for a specific type. Unfortunately, this feature is not implemented in Silverlight, so we’re on our own here. As it turns out, there isn’t much code required to come up with a solution. This is something that I call…

… a data template selector control…

For our example, we need templates for: a TextBox, a DatePicker and a CheckBox. The following user control will accept all these templates as properties and contain logic to choose between them based on a certain property, which will be bound to AutoFormField’s Type property:

public class FormFieldTemplateSelector : UserControl { public DataTemplate StringTemplate { get; set; } public DataTemplate DateTimeTemplate { get; set; } public DataTemplate BooleanTemplate { get; set; } public DataTemplate SignatureTemplate { get; set; } public static readonly DependencyProperty FieldTypeProperty = DependencyProperty.Register("FieldType", typeof(string), typeof(FormFieldTemplateSelector), new PropertyMetadata(string.Empty));

 

public string FieldType { get { return (string)GetValue(FieldTypeProperty); } set { SetValue(FieldTypeProperty, value); } } public FormFieldTemplateSelector() { Loaded += new RoutedEventHandler(OnLoaded); } private void OnLoaded(object sender, RoutedEventArgs e) { string fieldType = FieldType; if (fieldType == typeof(string).FullName) { Content = StringTemplate.LoadContent() as UIElement; } else if (fieldType == typeof(DateTime).FullName) { Content = DateTimeTemplate.LoadContent() as UIElement; } else if (fieldType == typeof(bool).FullName) { Content = BooleanTemplate.LoadContent() as UIElement; } else { Content = null; } } }

The way to set it up in the item template is simple too. Here’s the complete ItemTemplate:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition MinWidth="100" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <TextBlock Text="{Binding Caption}" VerticalAlignment="Center" />
    <local:FormFieldTemplateSelector Grid.Column="1" FieldType="{Binding Type}" Margin="0,2,0,2">
        <local:FormFieldTemplateSelector.StringTemplate>
            <DataTemplate>
                <TextBox Text="{Binding Value, Mode=TwoWay}" Width="100"/>
            </DataTemplate>
        </local:FormFieldTemplateSelector.StringTemplate>
        <local:FormFieldTemplateSelector.DateTimeTemplate>
            <DataTemplate>
                <basics:DatePicker SelectedDate="{Binding Value, Mode=TwoWay}" Width="100" />
            </DataTemplate>
        </local:FormFieldTemplateSelector.DateTimeTemplate>
        <local:FormFieldTemplateSelector.BooleanTemplate>
            <DataTemplate>
                <CheckBox IsChecked="{Binding Value, Mode=TwoWay}" />
            </DataTemplate>
        </local:FormFieldTemplateSelector.BooleanTemplate>
    </local:FormFieldTemplateSelector>
</Grid>

Of course, this is just one way to put it together. You're free to style each field completely to your liking. This is what we have so far:

To wrap this up, you just have to add a button to submit the form back to the server. I’m not discussing this here since it’s pretty straightforward.

I will, however, continue to build up on this example in the next couple of posts. Some topics I’ll dig into is styling, positioning and more (interesting) templates. I’ll also post the full code in the final post of this series.

Currently rated 4.6 by 5 people

  • Currently 4.6/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Development | MVVM | Silverlight | User Experience | Layout

Disabling items in a Silverlight ListBox

by Andrej Tozon 10. March 2009 14:27

This is a quick tip on making the ListBox items behave as disabled.

You know the semaphore, right? STOP on red, GO on green? OK. I’m drilling this with my two-year old when driving her to kindergarten every morning so lately it’s probably stuck in my mind a bit more than it should be.

OK. This is a ListBox:

image

Yes, it is:

<ListBox ItemsSource="{Binding Lights}" BorderThickness="0">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Ellipse Fill="{Binding Brush}" Width="50" Height="50" Stroke="Black" Cursor="Hand" />
        </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem" >
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">
                        <ContentPresenter x:Name="contentPresenter" Margin="4" />
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

The data context of this ListBox is a ViewModel with the Lights property. Lights is a collection of five Light objects.

One property that the Light object exposes is the Brush property; it will simply point to a solid color brush of a red, yellow or green color, respectively. The Ellipse’s Fill property is bound to the Brush property as can observed in the above code.

Semaphores usually change colors in an order. The ones in this sample will blink randomly, changing their colors in 2 second intervals:

public Light()
{
    this.SetColor();
    DispatcherTimer timer = new DispatcherTimer()
    {
        Interval = TimeSpan.FromSeconds(2.0)
    };
    
    timer.Tick += (s, e) => { this.SetColor(); };
    timer.Start();
}

private void SetColor()
{
    Color[] colorArray = new Color[] { Colors.Red, Colors.Yellow, Colors.Green };
    Color color = colorArray[randomizer.Next(0, 3)];
    this.Brush = new SolidColorBrush(color);
    this.IsEnabled = color == Colors.Green;
}

Now, we want to allow the user click only on green light, OK? The last line in the above SetColor method sets the IsEnabled property on the Light object only when the current color is set to Green.

With the IsEnabled property up and about, it gets really easy. To prevent the user from clicking on the ListBox item, simply set bind its IsHitTestVisible property to IsEnabled property on the Light class (bold underlined part added):

<ControlTemplate TargetType="ListBoxItem">
    <ContentPresenter x:Name="contentPresenter" Margin="4" 
        IsHitTestVisible="{Binding IsEnabled}" Cursor="Hand" /> </ControlTemplate>

[Cursor property is there for instant visual feedback on when the item is enabled]

That’s it, really. You can test it by adding i.e. a Click event handler to the contentPresenter and display a message box or something (included in the sample below). You would also want to build a better templates for the enabled/disabled items. If built around the bound IsEnabled property, it should work fine.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Development | MVVM | Layout | Silverlight

Silverlight TreeView: MVVM and editing [5 – Add]

by Andrej Tozon 28. January 2009 16:59

In the previous post, we enabled editing for our TreeView. The last operation we need to implement is adding a new item to the hierarchy. With our tree item editor in place, there’s not so much left to do…

The only change we need to make with the View is add an Add button, right next to the Edit button. We’ll wire it to the Add command, which already exists.

The next stop is the ViewModel. We need to change the existing OnAddTopicExecuted handler to add a new child to the HelpTopic, passed into the method through CommandParameter:

private void OnAddTopicExecuted(object sender, ExecutedEventArgs e)
{
    HelpTopic topic = new HelpTopic();
    HelpTopic parent = e.Parameter as HelpTopic;
    if (parent == null)
    {
        HelpTopics.Add(topic);
    }
    else
    {
        parent.SubTopics.Add(topic);
    }
    topic.BeginEdit();
}

Note: when CommandParameter is not set to a valid HelpTopic (when it’s null), we’re adding a child to the root.

There is something wrong with the code above. I’m assuming that the SubTopics collection was already initialized when I created a new HelpTopic class instance. I this case, I assumed wrong and the above code would throw a nasty error.

I need to assure that SubTopics property is never null as this puts the HelpTopic in an invalid state. I’ll do that by inserting the collection initialization code in its constructor:

public HelpTopic()
{
    SubTopics = new ObservableCollection<HelpTopic>();
}

Better. But we’re not done yet. There is no way for user to cancel the newly added item. Sure, she can press the Cancel button when editing, but the node will not be removed from the tree.

This is the time to provide the HelpTopic class with an unique identificator field. When working with the database, the type of this field would align with whatever your database table primary key type is. For simplicity, I’ll use Guids here, just because their generation results in more unique results than a number randomization would.

The logic behind is this: when a new HelpTopic is being added, its Id is set to null. If the item is confirmed, the new Id is set, otherwise the item is removed from the hierarchy. Here’s how modified OnEndEditTopic and OnCancelEditTopic now look like:

private void OnEndEditTopic(object sender, ExecutedEventArgs e)
{
    HelpTopic topic = e.Parameter as HelpTopic;
    if (!topic.Id.HasValue)
    {
        topic.Id = Guid.NewGuid();
    }
    topic.EndEdit();
}

private void OnCancelEditTopic(object sender, ExecutedEventArgs e)
{
    HelpTopic topic = e.Parameter as HelpTopic;
    if (!topic.Id.HasValue)
    {
        topic.RemoveFromStructure(HelpTopics, item => { return item.SubTopics; });
    }
    topic.CancelEdit();
}

Wait! Before we end this, let me just put another button in a TreeView to enable user to add a new HelpTopic to the root of the tree. This is the modified template with a new button in place (added lines in bold italic):

<ControlTemplate TargetType="slt:TreeView">
    <Grid x:Name="Root" >
        <Grid.Resources>
            <SolidColorBrush x:Key="BorderBrush" Color="#FF000000" />
        </Grid.Resources>
        <Border x:Name="Border" BorderBrush="{StaticResource BorderBrush}" 
            BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"> <Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" Margin="1"> <ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Auto"
                    VerticalScrollBarVisibility="Auto" Background="{x:Null}" BorderBrush="Transparent"
                    BorderThickness="0" IsTabStop="False" TabNavigation="Once"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Button HorizontalAlignment="Left" ToolTipService.ToolTip="Add Topic" input:CommandService.Command="AddTopic"> <Image Source="/EditableTreeView;component/Resources/add.png" Stretch="None" /> </Button> <ItemsPresenter x:Name="TreeItems" Grid.Row="1" Margin="5" /> </Grid> </ScrollViewer> </Border> </Border> </Grid> </ControlTemplate>

Editable TreeView

OK, we’ve got our TreeView to allow basic inline editing now, but not in very realistic scenario and it lacks usability. I’m going to end this series with this post and spin off to more general topics posts, covering more MVVM, LOB scenarios and Silverlight in general. A taste of things to come: improving, extending and expanding on the current state of this sample, going over the wire, testing and user experience.

The source code for this sample is available:

Currently rated 5.0 by 6 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Development | Layout | MVVM | Silverlight | Software | User Experience

Silverlight TreeView: MVVM and editing [4 – Edit]

by Andrej Tozon 26. January 2009 16:09

In the fourth post in this series we’ll get to actually edit an item in the TreeView.

First, we have to make our HelpTopic class editable. Well, yes, it is editable now, but we’ll make it even more editable :) by

a] implementing the IEditableObject interface. IEditableObject is defined as:

public interface IEditableObject
{
    void BeginEdit();
    void CancelEdit();
    void EndEdit();
}

The method names in the interface are so straightforward that I shouldn’t need to explain them here. However, one method which we benefit most from when our business object implements this interface, is CancelEdit. Why? Well, let’s say you have your TextBox twoway-bound to some object’s property. When you change the value in this TextBox and tab out, the changes are committed to your object and you have no options to undo this change. If you wanted to provide the OK/Cancel button pair for user to either confirm or undo her changes, you have a slight problem.

The IEditableObject interface is here to help. The CancelEdit method is responsible for reverting your object to the state it was before BeginEdit was called.

One of the Silverlight controls that supports this interface is DataGrid, so making your objects implement this interface will make them work consistently with DataGrid scenarios too.

Let’s see how our HelpTopic would implement the interface:

public class HelpTopic : IEditableObject
{
    private string nameCopy;

    public string Name { get; set; }
    public ObservableCollection<HelpTopic> SubTopics { get; set; }

    public HelpTopic()
    {
    }

    public void BeginEdit()
    {
        nameCopy = this.Name;
    }

    public void CancelEdit()
    {
        Name = nameCopy;
    }

    public void EndEdit()
    {
    }
}

I’ve introduced a new private variable to store the value of the name property in case the CancelEdit is called. When dealing with complex objects, you would want to store a deep clone of your object instead of storing each and every (sub)property in a separate variable.

b] creating a new property, which would indicate that the object is in edit mode. We would set this property within these three new methods. It is important that changes to this property are propagated to the UI, so we also need:

c] implementing the INotifyPropertyChanged interface. This interface is not new, our PageViewModel already implements it. When implementing this interface, we can also change the Name property implementation to support notifications.

The full HelpTopic class now looks like:

public class HelpTopic : IEditableObject, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private bool isEditing;
    private string name;
    private string nameCopy;

    public ObservableCollection<HelpTopic> SubTopics { get; set; }

    public string Name
    {
        get { return name; }
        set
        {
            if (name == value)
            {
                return;
            }

            name = value;
            OnPropertyChanged("Name");
        }
    }

    public bool IsEditing
    {
        get { return isEditing; }
        private set
        {
            if (isEditing == value)
            {
                return;
            }

            isEditing = value;
            OnPropertyChanged("IsEditing");
        }
    }

    public HelpTopic()
    {
    }

    public void BeginEdit()
    {
        nameCopy = this.Name;
        IsEditing = true;
    }

    public void CancelEdit()
    {
        Name = nameCopy;
        IsEditing = false;
    }

    public void EndEdit()
    {
        IsEditing = false;
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

[Edit: changed IsEditing setter above to private; the property should be set from within the class only]

Moving upwards to the ViewModel, we need another three commands for invoking the new methods – BeginEditTopic, EndEditTopic and CancelEditTopic commands. Their ViewModel implementation is, again, pretty simple and straightforward:

private void OnBeginEditTopic(object sender, ExecutedEventArgs e)
{
    HelpTopic topic = e.Parameter as HelpTopic;
    topic.BeginEdit();
}

private void OnEndEditTopic(object sender, ExecutedEventArgs e)
{
    HelpTopic topic = e.Parameter as HelpTopic;
    topic.EndEdit();
}

private void OnCancelEditTopic(object sender, ExecutedEventArgs e)
{
    HelpTopic topic = e.Parameter as HelpTopic;
    topic.CancelEdit();
}

All three methods just call their corresponding method on the HelpTopic item, passed into the method as a command parameter.

Now for the UI part… I guess I *could* do this with Xaml only, but I decided on building a new content control to pose as an View/Edit item switcher. The main reason for doing this is that I didn’t want end up with every item in the tree carry its own editor within its template. Instead, this new control will be responsible for loading the editor when one is needed.

In short, things are set up like this: I have this new control called EditableItemControl, pushed to somewhere near the top of the TreeViewItem’s visual tree with its contents set to what original item would look like. EditableItemControl has a property called EditorTemplate, which should be set to what the tree item should look like in edit mode. This is the EditorTemplate I used:

<Grid >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <TextBox Text="{Binding Name, Mode=TwoWay}" 
        input:CommandService.Command="EndEditTopic"
        input:CommandService.CommandParameter="{Binding}" 
    />
    <Button Name="saveButton" ToolTipService.ToolTip="OK" Grid.Column="1"
        input:CommandService.Command="EndEditTopic"
        input:CommandService.CommandParameter="{Binding}">
        <Image Source="/EditableTreeView;component/Resources/ok.png" Stretch="None" />
    </Button>
    <Button Name="closeButton" ToolTipService.ToolTip="Cancel" Grid.Column="2"
        input:CommandService.Command="CancelEditTopic"
        input:CommandService.CommandParameter="{Binding}">
        <Image Source="/EditableTreeView;component/Resources/cancel.png" Stretch="None" />
    </Button>
</Grid>

When editing is initialized, control’s original content is preserved and replaced with the above editor template, just to be switched back when editing is over. There are two ways to commit the changes – one is to press the saveButton, and the other is to press the Return key focused on the TextBox. Note that the TextBox is also wired to the EndEditTopic command, and TextBox control being one of the actionable elements in the SLExtensions framework, this simply works.

The key to make the switch between view and edit states is EditableItem’s IsEditing dependency property – the control goes to edit state when this property is set to true, and returns to its normal state when its value is set back to false. Ok, but how is this property set? Remember that our HelpTopic class also implements a property with the same name? They fit perfectly together:

<local:EditableTreeViewItem Grid.ColumnSpan="3" 
       IsEditing="{Binding IsEditing}" 
       HorizontalContentAlignment="Stretch">
    ...
</local:EditableTreeViewItem>

To tie all the ends together – when the Edit button is pressed, BeginEdit command is executed. ViewModel handles that command by calling the BeginEdit method on the HelpTopic item, for which the command was executed. IsEditing property gets set for this item and at the same time picked up by the EditableItemControl, which does the switch and display the editor for it. It’s like playing The Incredible Machine!

OK, we made the TreeView control editable. However, there are a few visual issues – one of them is that a mouse-over in a subitem shows the edit/delete commands for its ancestors all the way up to the root. I don’t know whether this is a default behavior for the TreeView or a bug of some sort, but until I find out, I’m leaving it as it is. I did use a TreeViewItem’s MouseOver state which wasn’t in the original template, so it may be that this state is not (properly) supported. Anyway, I’ll definitely get back to this, but first we have some adding to do.

About to edit an item

Editing an item

Next in the series: adding an item

The source code for this sample is available:

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Development | Development | MVVM | MVVM | Layout | Layout | Silverlight | Silverlight | User Experience | User Experience

Silverlight TreeView: MVVM and editing [3 – Delete]

by Andrej Tozon 23. January 2009 17:06

After the first two introductory posts, we're ready to move into the TreeView control itself. We’ll begin by implementing the easiest of the three update commands (insert/update/delete) – to delete an item in a TreeView.

This one actually looks very simple, all we need is to redefine TreeView’s ItemTemplate by adding a delete button we’ve created in the previous post:

<slt:TreeView.ItemTemplate>
    <slt:HierarchicalDataTemplate ItemsSource="{Binding SubTopics}">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
            <Button ToolTipService.ToolTip="Delete Topic" Margin="2,0,0,0" 
                    input:CommandService.Command="DeleteTopic" 
                    input:CommandService.CommandParameter="{Binding}">
                <Image Source="/EditableTreeView;component/Resources/delete.png" Stretch="None" />
            </Button>
        </StackPanel>
    </slt:HierarchicalDataTemplate>
</slt:TreeView.ItemTemplate>

As we’ve seen in the previous post, associating the delete Button with the DeleteTopic command will invoke the same command when the button is pressed. What’s new is that we’re now passing the current item as a CommandParameter. By ‘current item’ I don’t mean the currently selected item, but the item, which button was clicked.

In order to get this working, we have to change the ViewModel as well:

private void OnDeleteTopicExecuted(object sender, ExecutedEventArgs e)
{
    HelpTopic topic = e.Parameter as HelpTopic;

    bool isRemoved = topic.RemoveFromStructure(HelpTopics, item => { return item.SubTopics; });
    if (!isRemoved)
    {
        throw new InvalidOperationException(string.Format("Topic '{0}' couldn't be removed.", topic.Name));
    }
}

OnDeleteTopicExecuted now deletes the element, passed as the invoked command’s parameter. To delete an element from a hierarchical collection structure, we somehow need to find its parent. Because the HelpTopic class doesn’t know about its parent, I used a simple hierarchical query to help me out. The generic RemoveFromStructure() extension method finds an item in the passed structure and deletes the item, if found.

TreeView

Now we’re able to delete individual items in the tree, but all those distractive buttons scattered around the screen introduced a lot of noise to the view. We should be somehow able to hide them.

There’s a couple of ways to do this. For now, we’ll just modify the TreeViewItem’s style a bit and set it as TreeView’s ItemContainerStyle. TreeViewItem’s style also contains the TreeViewItem’s template, defining the overall look of the items in the TreeView – how the items looks selected, when mouse-over etc. Don’t confuse it with the ItemTemplate – this one basically defines TreeViewItem’s content. To not complicate this further, here’s our TreeViewItem style modification [only modified parts shown here, the whole style is defined in project’s App.Xaml]:

<StackPanel Orientation="Horizontal"> <ContentPresenter x:Name="content" Cursor="{TemplateBinding Cursor}" Content="{TemplateBinding Content}"
  ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Left"
        Margin="{TemplateBinding Padding}" /> <Button x:Name="commands" ToolTipService.ToolTip="Delete Topic" Margin="4,0,0,0" Opacity="0" input:CommandService.Command="DeleteTopic" input:CommandService.CommandParameter="{Binding}"> <Image Source="/EditableTreeView;component/Resources/delete.png" Stretch="None" /> </Button> </StackPanel>

 
and this is the added animation for the pressed state, showing the button when mouse is over the item:
 
<DoubleAnimation Storyboard.TargetName="commands" Storyboard.TargetProperty="Opacity" Duration="0" To="1" />

The current TreeView overall styling and chosen mouse-over behavior might not be the best suiting for any type of application. We’ll worry about making it look pretty after we get it working right.

The TreeView definition now looks like:

<slt:TreeView VerticalAlignment="Stretch" Grid.Row="1" ItemsSource="{Binding HelpTopics}"
    ItemContainerStyle="{StaticResource TreeVireItemContainerStyle}">
    <slt:TreeView.ItemTemplate>
        <slt:HierarchicalDataTemplate ItemsSource="{Binding SubTopics}">
            <TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
        </slt:HierarchicalDataTemplate>
    </slt:TreeView.ItemTemplate>
</slt:TreeView>

The delete button is now visible only when mouse hovers over each TreeView item. Pressing it deletes the same node, which is what we wanted to finish this post with anyway. And we still didn’t write any serious code… We’ll get more into the code in the next part of the series.

Next: item editing

The source code for this sample is available:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Development | Layout | MVVM | Silverlight | User Experience

Silverlight TreeView: MVVM and editing [1]

by Andrej Tozon 20. January 2009 16:13

This is the first post in a series with a simple, yet specific goal: to add editing capabilities to Silverlight Toolkit’s TreeView control and build a MVVM (Model-View-ViewModel) pattern application with it.

This introductory post will deal with really basic stuff. Nothing new and fancy, we’re just going to set up the grounds for a simple application that displays a hierarchical collection of some help topics in a TreeView.

We’ll begin with finding the suitable hierarchical structure to put in the TreeView. For the purpose of this sample, I copied some content from Silverlight SDK documentation:

Silverlight Help Topics

First, we’ll need a data generator that will create the data structure. In a real world scenario, you would call a web service to get such data, but we’ll get to that a bit later. For now, this dummy data generator will have to do:

public static class DataGenerator
{
public ObservableCollection<HelpTopic> GetHelpTopics()
{
return new ObservableCollection<HelpTopic>()
{
new HelpTopic() { Name = "Debugging, Error Handling, and Exceptions", SubTopics =
                new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Debugging Overview", SubTopics =
                    new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Walkthrough: Setting Up Remote Debugging on the Macintosh"}
}},
new HelpTopic() {Name = "Error Handling"},
new HelpTopic() {Name = "Exceptions", SubTopics=new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Exception Class and Properties"},
new HelpTopic() {Name = "Exception Hierarchy"},
new HelpTopic() {Name = "Exception Handling"},
new HelpTopic() {Name = "Best Practices for Handling Exceptions"}
}}
}},
new HelpTopic() { Name = "Deployment and Localization", SubTopics =
                new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Creating Globally Aware Applications"},
new HelpTopic() {Name = "Localizing Silverlight Applications"}
}},
new HelpTopic() { Name = "Performance", SubTopics = new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Performance Tips"},
new HelpTopic() {Name = "How To: Use a Background Worker"},
new HelpTopic() {Name = "Threading", SubTopics = new ObservableCollection<HelpTopic>()
{
new HelpTopic() {Name = "Managed Threading Overview"},
new HelpTopic() {Name = "Best Practices for Managed Threading"},
new HelpTopic() {Name = "Threading Objects and Features"}
}},
}}
};
}
}

Looks ugly, but there is really just one entity class involved – here is the HelpTopics class:

public class HelpTopic
{
public string Name { get; set; }
public ObservableCollection<HelpTopic> SubTopics { get; set; }

public HelpTopic()
{
}
}

The name property will hold the text to be displayed in a tree, and SubTopics is a collection of child items. ObservableCollection plays a significant role in MVVM applications because of its ability to inform potential listeners that the collection has changed in some way (item was added, changed, or list was cleared, etc.). With MVVM, one potential listener is the User Interface (visual elements in your application), which can react to any changes, made in data which it’s bound to.
The HelpTopic class currently doesn’t hold any unique Id field. I intentionally left that out for the moment; it will be added when needed.

OK, we have our Model. What we need next is the ViewModel. A ViewModel is a bridge between the View and the Model and acts like a Model to the View. Hence the name :)

Since it’s going to serve the main page, we’ll call it – the PageViewModel:

public class PageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private ObservableCollection<HelpTopic> helpTopics;

public ObservableCollection<HelpTopic> HelpTopics
{
get { return helpTopics; }
private set
{
if (helpTopics == value)
{
return;
}
helpTopics = value;
OnPropertyChanged("HelpTopics");
}
}

public PageViewModel()
{
HelpTopics = DataGenerator.GetHelpTopics();
}

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{

handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

The ViewModel only has one property [HelpTopics] because that’s currently the only one we need. Notice its private setter – we’re allowing setting this property from within the ViewModel only. Also, until we implement a better method to retrieve our data, we’re initializing the HelpTopics data collection directly in ViewModel’s constructor.

The ViewModel implements the INotifyPropertyChanged interface to notify its listeners when one of its properties has changed. Similar to what ObservableCollection does, but this one’s for a single class.

To finish this, all we have left is to compose a View. We’ll populate it with a TreeView control, set it to up to display our topics and create an instance of our PageViewModel for the DataContext:

<Grid x:Name="LayoutRoot">
    <Grid.DataContext>
        <local:PageViewModel />
    </Grid.DataContext>
    <slt:TreeView VerticalAlignment="Stretch" Margin="20" Width="300" ItemsSource="{Binding HelpTopics}">
        <slt:TreeView.ItemTemplate>
            <slt:HierarchicalDataTemplate ItemsSource="{Binding SubTopics}">
                <TextBlock Text="{Binding Name}" />
            </slt:HierarchicalDataTemplate>
        </slt:TreeView.ItemTemplate>
    </slt:TreeView>
</Grid>

The PageViewModel instance is set as a DataContext of the topmost page element (Grid), which means it will serve all page’s elements. One of its consumers is the TreeView control, which takes whatever is in its HelpTopics property for its items source. The last thing we had to do is set up a HierarchicalDataTemplate, specifying the property holding the subitems, and the visual of the tree item – a simple TextBlock with its Text property bound to the HelpTopic class’ Name property will suffice.

Help Topics TreeViewThis is what our TreeView control looks now. Far from being editable or anything yet, but except coding the dummy data generator and a class with one property, we hardly did any coding; And that’s the power of the MVVM pattern.

Next in the series: modifying the data structure.

The source code for this sample is available:

Currently rated 5.0 by 6 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Development | Development | Layout | Layout | Silverlight | Silverlight | User Experience | User Experience | MVVM | MVVM

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen



Andrej Tozon
Andrej Tozon's on Twitter View Andrej Tozon's profile on LinkedIn Subscribe to me on FriendFeed Andrej Tozon's Facebook profile

MVP - Client Application Developer

Microsoft Certified Solution Developer

MSN Alerts

Get help from Andrej Tozon!

 

Currently reading


Microsoft Silverlight 4 Data and Services Cookbook

Stay tuned for the review... In the mean time, you can read an excerpt from the book.

RecentComments

Comment RSS