Tuesday, 1 March 2011

Using external styles with static resources in Silverlight

I've recently had the need to apply an external style (i.e. one thats outside the .xap file) which includes static resources and at runtime.

... Anyone starting to see a problem here? Maybe not, its actually a little subtle. Basically, the problem is surrounding the use of static resources, and in particular trying to change a static resource at runtime.

The short and curlys are that it's not an easy thing to do. As the resource is static, its loaded once at the start (during the InitializeComponent() call in the App.xaml.cs contructor) and if you want to change it there after, you have to find the particual resource from the application resource dictionaries (see Application.Current.Resources) and changes its value manually. I've found that even clearing the resource dictionary and re-loading didn't work either... go figure!

So my solution was to not even bother to load the generic style (containing static resources) if there was an external one we wanted to use instead. A bit like the code below:

public partial class App : Application
    {
        private static IDisplayPage m_currentPage = null;
        private IDictionary m_initParams = null;

        public App()
        {
            this.Startup += this.Application_Startup;
            this.Exit += this.Application_Exit;
            this.UnhandledException += this.Application_UnhandledException;     
        }

        /// 
        /// Occurs when the application is started/// 
        /// 
        /// 
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // Set the init params and intialise the application
            m_initParams = e.InitParams;
            Initialise();
        }

        private void Initialise()
        {
            // We need to load the new theme before InitaliseComponent so that the right static resources are loaded first time!
            // Otherwise the defaults will be loaded and the newer ones are ignored.
            string themeLocation = ConfigurationManager.Settings["ThemeLocation"];
            if (string.IsNullOrEmpty(themeLocation) == false)
            {
                // We have a theme location present, so try to load it! The application will be 
                // loaded after the theme is downloaded and applyed to the application resources.
                LoadTheme(themeLocation);
            }
            else
            {
                // Skip loading the theme as we haven't supplied one. Go straight for the good stuff!
                LoadApplication();
            }
        }

        /// 
        /// Loads the given theme into the application resources
        /// 
        /// 
        private void LoadTheme(string themeLocation)
        {
            // Create the client to load the file
            WebClient client = new WebClient();

            // When the download is complete, apply it!
            client.DownloadStringCompleted += ThemeDownloaded;

            // Start the download of the theme off. The location should be local as the screen will be blank while downloading!
            client.DownloadStringAsync(new Uri(themeLocation, UriKind.RelativeOrAbsolute));
        }

        /// 
        /// Occurs when an external theme has finished downloading
        /// 
        /// 
        /// 
        private void ThemeDownloaded(object sender, DownloadStringCompletedEventArgs e)
        {
            string themeLocation = ConfigurationManager.Settings["ThemeLocation"];

            if (!e.Cancelled && (e.Error == null))
            {
                try
                {
                    // Read in the external style and store it as a resource dictionary
                    ResourceDictionary dictionary = XamlReader.Load(e.Result) as ResourceDictionary;

                    if (dictionary != null)
                    {
                        // If the reading was successful, store it as our current dictionary
                        Application.Current.Resources.MergedDictionaries.Add(dictionary);
                    }
                }
                catch (XamlParseException ex)
                {
                    ServiceClient webService = new ServiceClient();
                    webService.LogErrorAsync("Problem parsing theme (" + themeLocation + "): " + ex.Message, ex.StackTrace, SLUtilities.GetClientVersion());
                }
            }
            else
            {
                if (e.Error != null)
                {
                    ServiceClient webService = new ServiceClient();
                    webService.LogErrorAsync("Problem loading theme (" + themeLocation + "): " + e.Error.Message, e.Error.StackTrace, SLUtilities.GetClientVersion());
                }
            }

            // After our attempt at loading an external theme, load the rest of the application
            LoadApplication();
        }

        /// 
        /// Loads the rest of the application, initialising the components if an external theme hasn't already been loaded.
        /// 
        private void LoadApplication()
        {
            // If we haven't been able to reach an external theme for the app, use the internal default
            if (Application.Current.Resources.MergedDictionaries.Count == 0)
            {
                // Initialise the main app.xaml page, including resources and styles
                InitializeComponent();
            }

           // Do the rest of our application specific setup
        }
}

So as you can see, the application when first loaded, checks a settings file in the .xap and a location to an external theme. If it can find it, the theme is downloaded and instead of calling InitializeComponents(), we just load the resources in the theme instead. If there is no location for an external theme, we just load up the generic resources and styles using InitializeComponents() as normal.

I have to say, not the most satisfying work around, but it does work!

Hope this helps someone!

Friday, 18 February 2011

Image Collection Browsing Kiosk

So recently at work, I've been lucky enough to be working on quite a unique product. Its primarily a kiosk application for museums and enables users to easily browse large image collections (tens of thousands).

While the user is browsing their way through, our algorithms look at the meta-data in the selected images and start to adapt the new mix of images to the users interests. This means that each different user of the kiosk will have a completely different experience.

The application also support video and audio assets, providing the user with a very immersive experience.

I personally have worked on all parts of this application, from the UI and web-service to the tool-chain and adaptive algorithms. My favourite part is undoubtedly the UI work!

The video below shows a development build which includes an image tray, allowing users to print off their favourite images to take home.



Hopefully I'll be able to post about some useful tit-bits of knowledge I've gained while developing the system!

New Blog Engine

I've finally got fed up with wordpress (due to the lack of template editing options) and I'm moving over to google's Blogger. So far I'm actually really impressed! Hopefully this'll boost the amount of posts I make too! :)

Thursday, 10 February 2011

Finally, a good carpark timer app!

Ok… I’ll come clean, its mine! :)

I’ve finally finished work on my first commercial android app, which is designed to keep track of your parking tickets, and so far its helped me out immensely, so as the theory goes, hopefully it’ll help some of you out too!


If anyone is interested you can find it on the android market.

Any problems at all, please feel free to feedback, your comments will help shape future versions!

Sunday, 16 January 2011

Lookless Carousel Control in Silverlight

One of the tasks I had when designing the kiosk application for DeepVisuals, was the need for a background which changes every so often.

Ok, so this would be very easy to brute force; a couple of storyboard and hard-coded images and we're done. But I wanted to take this as an opportunity to develop my understanding about parts and states and look-less controls. Basically our end idea for the kiosk application is to make it skin-able so a look-less control which is styled separately is the best way forward.

So I started off by reading a few blogs and post, got stuck in. This was what I came up with:

[TemplatePart(Name = "PART_CarouselGrid", Type = typeof(Grid))]
    public class Carousel : Control
    {
        protected Queue<UIElement> m_elements = new Queue<UIElement>();
        protected DispatcherTimer m_cycleCarouselTimer = new DispatcherTimer();

        protected Storyboard m_fadeStoryboard;

        protected DoubleAnimation m_fadeOutAnimation;
        protected DoubleAnimation m_fadeInAnimation;

        protected Grid m_carouselGrid;

        public Carousel()
        {
            DefaultStyleKey = typeof(Carousel);
        }

        /// 
        /// Dependency property for the duration of a transiton.
        /// 
        public static DependencyProperty TransitionDurationProperty = DependencyProperty.Register(
            "TransitionDuration",
            typeof(double),
            typeof(Carousel),
            new PropertyMetadata(5d));

        /// 
        /// Dependency property for the duration each carousel item is displayed for.
        /// 
        public static DependencyProperty DisplayDurationProperty = DependencyProperty.Register(
            "DisplayDuration",
            typeof(double),
            typeof(Carousel),
            new PropertyMetadata(30d));

        /// 
        /// The duration of the transition between items in the carousel.
        /// 
        public double TransitionDuration
        {
            get { return (double)GetValue(TransitionDurationProperty); }
            set { SetValue(TransitionDurationProperty, value); }
        }

        /// 
        /// The duration each carousel item is displayed for.
        /// 
        public double DisplayDuration
        {
            get { return (double)GetValue(DisplayDurationProperty); }
            set { SetValue(DisplayDurationProperty, value); }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Get the grid used for storing backgrounds
            m_carouselGrid = (Grid)GetTemplateChild("PART_CarouselGrid");

            LoadCarousel();
        }

        private void LoadCarousel()
        {
            // Get the images in the background holder
            foreach (UIElement element in m_carouselGrid.Children)
            {
                element.Opacity = 0;
                m_elements.Enqueue(element);
            }

            if (m_elements.Count == 0)
            {
                // If there are no images, we can't really do much
                throw new ArgumentException("Carousel must contain at least 1 UIElement");
            }
            else if (m_elements.Count > 1)
            {
                // Set up the storyboards 
                SetupAnimations();

                // If we have enough images to fade between, then lets start the timer
                m_cycleCarouselTimer.Interval = TimeSpan.FromSeconds(DisplayDuration);
                m_cycleCarouselTimer.Tick += new EventHandler(CycleCarouselTimer_Tick);
                m_cycleCarouselTimer.Start();
            }

            // Set the first image to be visible for starters
            m_elements.Peek().Opacity = 1;
        }

        /// 
        /// Fades out the current image in the carousel
        /// 
        public virtual void Start()
        {
            m_cycleCarouselTimer.Start();
        }

        /// 
        /// Fades in the current image in the carousel
        /// 
        public virtual void Stop()
        {
            m_cycleCarouselTimer.Stop();
        }

        private void SetupAnimations()
        {
            m_fadeStoryboard = new Storyboard();

            m_fadeInAnimation = new DoubleAnimation();
            m_fadeInAnimation.To = 1d;
            m_fadeInAnimation.Duration = TimeSpan.FromSeconds(TransitionDuration);
            Storyboard.SetTargetProperty(m_fadeInAnimation, new PropertyPath("(UIElement.Opacity)"));

            m_fadeOutAnimation = new DoubleAnimation();
            m_fadeOutAnimation.To = 0d;
            m_fadeOutAnimation.Duration = TimeSpan.FromSeconds(TransitionDuration);
            Storyboard.SetTargetProperty(m_fadeOutAnimation, new PropertyPath("(UIElement.Opacity)"));

            m_fadeStoryboard.Children.Add(m_fadeInAnimation);
            m_fadeStoryboard.Children.Add(m_fadeOutAnimation);

            m_fadeStoryboard.Completed += new EventHandler(ElementFade_Completed);
        }

        private void ElementFade_Completed(object sender, EventArgs e)
        {
            // Set the final values before stopping the storyboard so we can re-use.
            m_elements.First().Opacity = 1d;
            m_elements.Last().Opacity = 0d;
            m_fadeStoryboard.Stop();

            // Call the virtual method to alert anyone overriding
            OnCycleCompleted();
        }

        private void CycleCarouselTimer_Tick(object sender, EventArgs e)
        {
            OnCycleCarousel();
        }

        protected virtual void OnCycleCompleted()
        {

        }

        protected virtual void OnCycleCarousel()
        {
            UIElement fadeOutImage = m_elements.Dequeue();
            UIElement fadeInImage = m_elements.Peek();

            Storyboard.SetTarget(m_fadeOutAnimation, fadeOutImage);
            Storyboard.SetTarget(m_fadeInAnimation, fadeInImage);

            m_fadeStoryboard.Begin();
            m_elements.Enqueue(fadeOutImage);
        }
    }

So the beauty of this control is that the items (or images) that it scrolls through periodically are actually defined in the style for the control. That's what the line below is for:

[TemplatePart(Name = "PART_CarouselGrid", Type = typeof(Grid))]

Basically it says that when you style this control, it must have a control in, which is a Grid and it must be called 'PART_CarouselGrid'. This makes it possible for the look-less control to get a reference to the Grid in OnApplyTemplate. From here we can then go through all the elements in the grid (specified in the style) and use them as the elements we'll rotate through.

A sample style you could use with the background would be similar to below, which basically says each item will be shown for 30 seconds, the transition between items will take 1 second and the items to switch between will simply be a black background and a white background (not interesting I know, but hey, its an example!). :)



And in the application, the background is added using:

<my:Carousel Style="blah" />

Simples!

Friday, 17 December 2010

Reflecting on Silverlight's RichTextBox and it's XAML Export Problems

During developement of our Image Relationship software, we came across the need to use the Silverlight RichTextBox with images, and most critical of all, for the RichTextBox to persist images when saving the output to the database.

Unfortunately Silverlight has let us down again. For one reason or another, the control doesn't export any images in the RichTextBox, or any other UIElement for that matter. It just returns an empty Run element instead.

Very frustrating!

Rather than shell out hundreds of pounds for third-party rich text box or use a unsupported free one such as the Liquid tools, we decided to write our out support manually.

To do this, we needed to manually export all of the RichTextBox contents, along with any InlineUIElements. Thankfully, there is a property on the RichTextBox which allows you to gain access to all the blocks within the control. From this you can iterate through them and with the help of reflection, write them all to a string builder. Fantastic!

The export function would like like this:

public string Export(RichTextBox rtb)
        {
                StringBuilder sb = new StringBuilder();
                sb.Append("
"); foreach (Block b in rtb.Blocks) { // Look at each block. They should either be a section or paragraph. if (b is Paragraph) { Paragraph p = b as Paragraph; writeElement(sb, p, false); // Go through each inline within the paragraph foreach (Inline i in p.Inlines) { if (i is InlineUIContainer) { // We need to look at these seperately as they have different requriements InlineUIContainer inlineContainer = (InlineUIContainer)i; parseInlineContainer(sb, inlineContainer); } else { // If its not an InlineUIContainer, our write element method will help! writeElement(sb, i); } } sb.Append(""); } } sb.Append("
"); return sb.ToString(); }

Parsing the InlineUIContainer may look like this:

private void parseInlineContainer(StringBuilder sb, InlineUIContainer inlineContainer)
        {
            if (inlineContainer.Child is Image)
            {
                sb.Append("");

                // If you want to be able to export other UIElements, this is where you handle them!
                Image image = inlineContainer.Child as Image;
                if (image.Source is BitmapImage)
                {
                    BitmapImage bm = image.Source as BitmapImage;

                    string uri;
                    if (bm.UriSource.IsAbsoluteUri)
                    {
                        uri = bm.UriSource.AbsoluteUri;
                    }
                    else
                    {
                        uri = bm.UriSource.ToString();
                    }

                    sb.Append("");        
                }

                sb.Append("");
            }
        }


And fleshing out the writeElement method may look something like this:

private void writeElement(StringBuilder sb, TextElement i, bool withClosingTag)
        {
            // Incase the control has any inlines (i.e. hyperlink)
            InlineCollection inlines = null;

            Type type = i.GetType();
            sb.Append("<" + type.Name + " ");

            foreach (PropertyInfo pi in type.GetProperties())
            {
                // Check if the property name is one of the ones we want
                if (m_acceptableAttributes.Contains(pi.Name))
                {
                    // Check if its readable and isn't null
                    if (pi.CanRead && pi.GetValue(i, null) != null)
                    {
                        // Get the value object and asset its something we're expecting
                        object valueObj = pi.GetValue(i, null);

                        if (valueObj is InlineCollection)
                        {
                            // Make sure we're not grabing inlines from a paragraph
                            if (i is Paragraph == false)
                            {
                                // If we have an inline collection, it means we have children, congrats!
                                inlines = valueObj as InlineCollection;
                            }
                        }
                        else
                        {
                            // Check if this element has inlines (hyperlink), if so we need to add them 
                            string value = parsePropertyValue(i, valueObj);

                            // Append the property to the string builder
                            sb.Append(pi.Name + "=\"" + value + "\" ");
                        }
                    }
                }
            }

            // Check if we have child inlines
            if (inlines != null)
            {
                // Close of the control ready for children
                sb.Append(" >");

                foreach (Inline inline in inlines)
                {
                    writeElement(sb, inline);
                }

                // Close the control
                sb.Append("");
            }
            else
            {
                if (withClosingTag)
                {
                    // If we need a closing tag, add one
                    sb.Append(" />");
                }
                else
                {
                    // Otherwise just close the element
                    sb.Append(" >");
                }
            }
        }


And finally the parsePropertyValue method could look like this:

private string parsePropertyValue(TextElement i, object valueObj)
        {
            string value;

            if (valueObj is SolidColorBrush)
            {
                // If the value is a colour brush, use color value
                SolidColorBrush brush = valueObj as SolidColorBrush;
                value = brush.Color.ToString();
            }
            else if (valueObj is TextDecorationCollection)
            {
                // There is only every one text decoration for silverlight, underline.
                // See remarks here: http://msdn.microsoft.com/en-us/library/ms603219(v=VS.95).aspx
                value = "Underline";
            }
            else
            {
                value = valueObj.ToString();
            }

            return value;
        }


So now we have the valid XAML, surely the RichTextBox should allow us to import the InlineUIElements within the XAML, using the XAML property... well, no.

It seems the control doesn't like importing InlineUIContainers as much as it likes exporting them. So we have to manually spoon feed the control again.

To do this, we used the XamlReader object, provided by the silverlight libraries, to load the XAML we manually exported into their respective instances. From this, we use the Blocks property on the control again to add each block one by one. Not so tricky

public void Import(RichTextBox rtb, string xaml)
        {
            // Load up the XAML using the XamlReader
            Object o = XamlReader.Load(xaml);

            if (o is Section)
            {
                // Make sure its a section and clear out the old stuff in the rtb
                Section s = o as Section;
                rtb.Blocks.Clear();

                // Remove the blocks from the section first as adding them straight away
                // to the rtb will throw an exception because they are a child of two controls.
                List<Block> tempBlocks = new List<Block>();
                foreach (Block block in s.Blocks)
                {
                    tempBlocks.Add(block);
                }
                s.Blocks.Clear();

                // Add them block by block to the RTB
                foreach (Block block in tempBlocks)
                {
                    rtb.Blocks.Add(block);
                }
            }
        }


Eh voila! We have persisted a UIElement from the RichTextBox and loaded it back in again.

Of course, a solution like this would seem a bit nasty if we didn't have a nice neat export/import class which we could use, instead of the lack-luster XAML property, so thats what we've done!

You can download it below:

Source (49KB)
Binaries (8KB)

Feel free to use it any way you like!

Just to note, currently this library only exports InlineUIContainers which contain Images, but this could be easily extendible.

Also, the library isn't extensively tested, so please modify and use as you feel fit!

Monday, 8 November 2010

Developing Apps on the Toshiba Folio 100

On my travels, I have recently been required to develop an application for a 10″ Android tablet. So we targeted the Toshiba Folio 100, thinking that as it has a ‘Market Place’ (all-be-it toshiba) you’d be able to develop and debug on the device its self.

Unfortunately not out the box! There are no drivers for the tablet at all, so the best you can do with the usb cable (not supplied) attached to your PC is use it as a mass storage device.

To enable eclipse to install development apps on the device, you need to follow these life saver steps: http://forums.computers.toshiba-europe.com/forums/message.jspa?messageID=217132.

Basically you need to edit the driver that comes with the SDK to include the name of this new device. Then I un-installed the driver that windows installed for it (Composite device) and re-attached the USB cable. This time I pointed it to my newly edited driver, and thankfully it worked! :)

Thanks a lot to the people on the Toshiba forums!