woensdag 23 maart 2011

Alternative to saving a file in Silverlight

In Silverlight the most common way of saving a file is done by opening a SaveFileDialog and then using the provided stream to write the file to the filesystem.
Although this is a preferred method it does provide some problems in the current release of Silverlight:
  • ·        First of all, there is no download added in the download history of your browser.  So once the user has saved the file, he or she needs to remember where the file was saved and under what name it was saved.
  • ·        Another issue is that there is no support for entering a default filename in the current release of Silverlight (SL4 at the time of writing).  Without a doubt that Microsoft will fix this in the next release, but as the deadline of current project approaches, we want to have this functionality now.
A possible solution to this problem is navigating to a download page in your Silverlight application.  The download page will send a response to the user.  In this response we will fill the content header with the requested file.  In the provided example, I temporarly buffer the file.  The reason I do this is so I can delete the file from the filesystem.  One scenario where you wish to do this, is when preparing downloads and moving them to a temporary location.  Enough talk, here is the code:
In the Silverlight application we navigate to the download location, we add a parameter to tell the page which file and where it is:
HtmlPage.Window.Navigate(new Uri(String.Format("download.aspx?File={0}", someFileName)), "_blank");

Then in the codebehind of the download page we can add following code:
protected void Page_Load(object sender, System.EventArgs e)
{
    // Get the filepaths
    string filePath = ConfigurationManager.AppSettings(DownloadRootPathSetting);
    string fileLocation = Request.QueryString("File");
    System.IO.FileInfo TargetFile = new System.IO.FileInfo(Path.Combine(filePath, fileLocation));

    // Buffer the file
    byte[] buffer = new byte[TargetFile.Length + 1];
    FileStream sr = TargetFile.OpenRead();
    using ((sr))
    {
        sr.Read(buffer, 0, buffer.Length);
    }

    // Assemble the response
    Response.Clear();
    Response.AddHeader("Content-Disposition", "attachment; filename=" + TargetFile.Name);
    Response.AddHeader("Content-Length", TargetFile.Length.ToString());
    Response.ContentType = "application/octet-stream";
    Response.BinaryWrite(buffer);

    // Delete the file
    File.Delete(Path.Combine(filePath, fileLocation));
    Directory.Delete(Path.GetDirectoryName(Path.Combine(filePath, fileLocation)));

    // Send the response back
    Response.End();
}

Enjoy!

woensdag 16 maart 2011

Build your own silverlight in browser toast

Build your own in browser toast
Toast can be very handy tools that allow you to notify the user of an event that happened without blocking him.  However it’s a shame that toast aren’t available when running your application in browser.  Off course it is understandable that toast can’t provide the full use within the user’s browser, although in some cases this could prove useful.   Image the user downloading a file from your server and you wish to notify him when the download is finished.  Or some operation is finished or an event is raised in another page.  Then toasts can be very useful.
In this example I will be using a navigation application.  The reason why I do this is because then we can define our ‘toast’ in the mainpage and it will be a level higher in the hierarchy than the pages.  We aren’t really going to be using a toast but a popup that will behave like a toast.  Here is the code:
In our MainPage.xaml we define the popup and a corresponding storyboard that will make the popup show up and down:
    <UserControl.Resources>
        <ViewModels:MainViewModel x:Key="vm"/>
        <Storyboard x:Name="PopupStoryBoard">
               <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.VerticalOffset)" Storyboard.TargetName="popup">
                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="-60"/>
                <EasingDoubleKeyFrame KeyTime="0:0:3.2" Value="-60"/>
                <EasingDoubleKeyFrame KeyTime="0:0:4" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Style="{StaticResource LayoutRootGridStyle}" Background="{Binding}" DataContext="{StaticResource vm}">

Some other code can come here…  

    <Popup x:Name="popup" Width="auto" Height="auto" Grid.ColumnSpan="3" HorizontalAlignment="Center" VerticalAlignment="Bottom" VerticalOffset="0"
               DataContext="{Binding PopupViewModel}" IsOpen="True">
               <!--Visibility="{Binding RelativeSource={RelativeSource self}, Path=DataContext.Show, Converter={StaticResource VisibilityConverter}}">-->
            <Border Name="popupBoder" Width="auto" BorderThickness="1"
                    BorderBrush="{StaticResource BaseBrush7}"
                    Background="White">
                <TextBlock Text="{Binding Text}" FontSize="12" Margin="10"/>
            </Border>
        </Popup>
    </Grid>

</UserControl>

We can then define a viewmodel for the Popup.  For simplicities sake, we will make this a singleton.
Imports ePostbus.ViewModels.Base

Namespace ViewModels

    Public Class PopupViewModel
Inherits ViewModelBase

        Private _show As Boolean
        Private _text As String
        Private Shared _instance As PopupViewModel

        Public Shared ReadOnly Property Instance As PopupViewModel
            Get
                If _instance Is Nothing Then
                    _instance = New PopupViewModel
                End If
                Return _instance
            End Get
        End Property

        Public Sub Show()
            If ShowAction IsNot Nothing Then
                ShowAction.Invoke()
            End If
        End Sub

        Public Shared Property ShowAction As Action

        Public Property Text As String
            Get
                Return _text
            End Get
            Set(value As String)
                _text = value
                RaisePropertyChanged(Function() Me.Text)
            End Set
        End Property

        Protected Overloads Sub RaisePropertyChanged(Of propertyToInvoke)(ByVal expression As Expression(Of Func(Of propertyToInvoke)))
            Dim body = TryCast(expression.Body, MemberExpression)
            If body Is Nothing Then
                Throw New ArgumentException("'expression' should be a member expression")
            End If

            Dim propertyName As String = body.Member.Name

            MyBase.RaisePropertyChanged(propertyName)
        End Sub

    End Class
End Namespace

Al we than need is to initialize our ShowAction so we can play the storyboard when needed:
        PopupViewModel.ShowAction = Sub()
                                        Dim storyboard = TryCast(Me.Resources("PopupStoryBoard"), Storyboard)
                                        storyboard.Begin()
                                    End Sub

I putted this blogpost in VB.NET, so I don’t discriminate anyone ;)
Have fun…

dinsdag 8 maart 2011

How to bind on itemssource of other control

Imagine we want to be able to enable/disable a button depending on whether the itemssource of a listbox contains items.  
1.      What we can do is bind the IsEnabled property of the button to the count of the itemssource.  To be able to do that, we need a collection that will warn us when an item has been added or removed to the collection, we would need an observablecollection.
2.      Then when we bind to the count of the itemssource, we need to be able to convert it to a boolean property.  For this we can write a converter.

3.      Then in the code behind we can write some add and remove operations to test our functionality.

So our XAML with the converter would look like this:

<navigation:Page x:Class="SilverlightApplication1.Home"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
    Title="Home"
    Style="{StaticResource PageStyle}" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:SilverlightApplication1="clr-namespace:SilverlightApplication1" xmlns:layoutToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit">
    <UserControl.Resources>
        <SilverlightApplication1:CountGreaterThanZeroConverter x:Key="CountGreaterThanZeroConverter"/>
    </UserControl.Resources>
    <StackPanel x:Name="LayoutRoot" Background="White" Width="150">
        <TextBlock Text="{Binding ElementName=MyAutoCompleteBox, Path=SelectedItem, TargetNullValue='No item selected', StringFormat='Selected Item: {0}'}" />
        <ListBox x:Name="ListBox" BorderThickness="0" SelectionMode="Multiple" ItemsSource="{Binding Items}" />

        <Button x:Name="AddButton" Click="AddButton_Click" Content="AddButton" />
        <Button x:Name="RemoveButton" Click="RemoveButton_Click" Content="RemoveButton" />
        <Button x:Name="DisabledButton" Click="RemoveButton_Click" Content="Disabled Button" IsEnabled="{Binding ItemsSource.Count, ElementName=ListBox,Converter={StaticResource CountGreaterThanZeroConverter}}"  />
    </StackPanel>


</navigation:Page>


The converter itself:
    public class CountGreaterThanZeroConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            return (int)value > 0;
        }

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

        #endregion
    }

And last but not lease the code behind with our items property to bind to:

    public partial class Home : Page
    {
        public Home()
        {
            InitializeComponent();
            Items = new ObservableCollection<string>();


            DataContext = this;

        }

        public ObservableCollection<string> Items
        {
            get;
            private set;
        }


        // Executes when the user navigates to this page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }


        private void AddButton_Click(object sender, RoutedEventArgs e)
        {
            Items.Add(Guid.NewGuid().ToString());
  
        }

        private void RemoveButton_Click(object sender, RoutedEventArgs e)
        {
            Items.RemoveAt(0);
        }
    }