I finally got around to uploading the SimpleViewer-SL source code to CodePlex. Minor changes were required to get the code to compile on Silverlight 2.
The SimpleViewer-SL project on CodePlex can be found here: http://www.codeplex.com/simpleviewerSL. Please feel free to report all bugs/issues/enhancements on this site.
Showing posts with label SimpleViewer. Show all posts
Showing posts with label SimpleViewer. Show all posts
Saturday, October 18, 2008
Monday, June 30, 2008
SimpleViewer-SL ported to Silverlight 2 Beta 2
SimpleViewer-SL now works on Beta 2 as well. I updated the inline samples in the previous posts so folks with Beta 2 should be able to view all the sample galleries that I created using SimpleViewer-SL.
If you recollect from my earlier post, I did not use the ScrollViewer control since I wanted to keep the .xap package as small as possible. This is no longer the case since ScrollViewer is now part of the Silverlight framework itself. I still haven't updated the code to take advantage of this but might do so at a later date.
If you recollect from my earlier post, I did not use the ScrollViewer control since I wanted to keep the .xap package as small as possible. This is no longer the case since ScrollViewer is now part of the Silverlight framework itself. I still haven't updated the code to take advantage of this but might do so at a later date.
Monday, May 12, 2008
Scrolling, without using ScrollViewer!
The main objective when building SimpleViewer-SL was to keep the executable as small as possible and hence I didn't have the option of using ScrollViewer. I still needed scrolling functionality though and hence had to take some alternate approach. This post is a result of my findings. There is possibly no reason for anyone to take this approach, unless there is a compelling reason to not use ScrollViewer, but it might prove useful!
So how did I go about implementing scrolling? The trick is to contain the visible area and use TranslateTransform.
Let's look at the Xaml first
What we are trying to do here is display the thumbnails in a grid, but, as you can see from the above Xaml, I have had to create 3 grids to implement scrolling.
Most of the code is self explanatory but I would like to elaborate on a few key points which is highlighted in the code:
So how did I go about implementing scrolling? The trick is to contain the visible area and use TranslateTransform.
Let's look at the Xaml first
<UserControl x:Class="SimpleViewer_SL.Thumbnails"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid x:Name="LayoutThumbnailsViewport" HorizontalAlignment="Left">
<Grid x:Name="LayoutThumbnails" HorizontalAlignment="Left">
<Grid.RenderTransform>
<TranslateTransform x:Name="GridTranslate" />
</Grid.RenderTransform>
<Grid.Resources>
<Storyboard x:Name="GridScrollStoryboard">
<DoubleAnimation x:Name="GridScrollAnimation" Storyboard.TargetName="GridTranslate" Storyboard.TargetProperty="X" Duration="00:00:00.5"/>
</Storyboard>
</Grid.Resources>
</Grid>
</Grid>
<Canvas x:Name="RightNav" Height="25" Width="30" Background="Transparent" HorizontalAlignment="Right">
<Path Height="25" Width="30" Stretch="Fill"
Data="F1 M 0,50L 130,50L 80,0L 130,0L 200,70L 130,140L 80,140L 130,90L 0,90L 0,50 Z">
</Path>
</Canvas>
<Canvas x:Name="LeftNav" Height="25" Width="30" Background="Transparent" HorizontalAlignment="Left">
<Path Height="25" Width="30" Stretch="Fill"
Data="F1 M 200,90L 70,90L 120,140L 70,140L 0,70L 70,0L 120,0L 70,50L 200,50L 200,90 Z">
</Path>
</Canvas>
</Grid>
</UserControl>
What we are trying to do here is display the thumbnails in a grid, but, as you can see from the above Xaml, I have had to create 3 grids to implement scrolling.
- LayoutThumbnails - This is the grid which will host the thumbnails
- LayoutThumbnailsViewport - This is the grid which will host the translated thumbnails
- LayoutRoot - This is the grid which will host the clipped thumbnails
- GridTranslate - This exposes the translate transform of LayoutThumbnails
- GridScrollStoryboard - This is the storyboard that animates GridTranslate to simulate (horizontal) scrolling
public partial class Thumbnails : UserControl
{
Page _parent = null;
int _borderWidth = 2;
int _selectedThumbnailIndex = -1;
int _rowCount = 0;
int _colCount = 0;
int _totalColCount = 0;
int _numPages = 0;
int _currPage = 0;
Size _thumbSize = new Size { Width = 65, Height = 65 };
int _borderPlusPaddingWidth = 0;
public double ControlWidth { get { return (_thumbSize.Width + (_borderPlusPaddingWidth * 2)) * _colCount; }}
public Thumbnails(Page parent)
{
_parent = parent;
_rowCount = _parent.Settings.ThumbnailRows;
_colCount = _parent.Settings.ThumbnailColumns;
_totalColCount = (int)(Math.Ceiling((double)_parent.Settings.ImagesSettings.Count / _rowCount));
_numPages = (int)(Math.Ceiling((double)_totalColCount / _colCount));
_borderPlusPaddingWidth = _parent.Settings.ThumbPadding + _borderWidth;
InitializeComponent();
LayoutRoot.Width = ControlWidth; // ****1****
LayoutThumbnailsViewport.Width = ControlWidth * _numPages; // ****2****
CreateLayout();
DrawThumbnails();
}
int CreateLayout() {
// Thumbnails
LayoutRoot.RowDefinitions.Add(new RowDefinition());
LayoutThumbnailsViewport.SetValue(Grid.RowProperty, 0);
if (_numPages > 0) {
// Navigation Arrows
LayoutRoot.RowDefinitions.Add(new RowDefinition { Height = new GridLength(30) });
RightNav.SetValue(Grid.RowProperty, 1);
LeftNav.SetValue(Grid.RowProperty, 1);
}
}
void DrawThumbnails()
{
for (int row = 0; row < _rowCount; row++) {
LayoutThumbnails.RowDefinitions.Add(new RowDefinition { Height = new GridLength(_thumbSize.Height + (_borderPlusPaddingWidth * 2)) });
}
for (int col = 0; col < _colCount * _numPages; col++) {
LayoutThumbnails.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(_thumbSize.Width + (_borderPlusPaddingWidth * 2)) });
}
int imageIndex = 0;
int startCol = 0;
for (int page = 0; page < _numPages; page++) {
for (int row = 0; row < _rowCount; row++) {
for (int col = 0; col < _colCount && imageIndex < _parent.Settings.ImagesSettings.Count; col++) {
CreateImage(row, col + startCol, imageIndex);
imageIndex++;
}
}
startCol += _colCount;
}
LeftNav.MouseLeftButtonUp += (sender, e) => {
ScrollLeft();
};
RightNav.MouseLeftButtonUp += (sender, e) => {
ScrollRight();
};
}
public void CreateImage(int row, int col, int imageIndex)
{
Image thumb = new Image();
thumb.Source = new BitmapImage(new Uri(_parent.Settings.ThumbPath + _parent.Settings.ImagesSettings[imageIndex].Name, UriKind.Relative));
Border border = new Border {
Width = _thumbSize.Width + (_borderWidth*2),
Height = _thumbSize.Height + (_borderWidth*2),
BorderThickness = new Thickness(_borderWidth),
BorderBrush = new SolidColorBrush(_parent.Settings.FrameColor),
Margin = new Thickness(_parent.Settings.ThumbPadding),
Opacity = 0.6
};
border.SetValue(Grid.RowProperty, row);
border.SetValue(Grid.ColumnProperty, col);
border.SetValue(NameProperty, "Image" + imageIndex.ToString());
border.Tag = imageIndex.ToString(); // Tag can only handle Strings as of SL2 B1 release
border.Child = thumb;
LayoutThumbnails.Children.Add(border);
border.MouseLeftButtonUp += (sender, e) => {
Rect LayoutRootRect = new Rect(0, 0, LayoutRoot.RenderSize.Width, LayoutRoot.RenderSize.Height);
if (LayoutRootRect.Contains(e.GetPosition(LayoutRoot))) { // ****3****
Border currentBorder = (Border)sender;
SelectedThumbnailIndex = Int32.Parse((string)currentBorder.Tag);
}
};
}
bool CanScrollRight { get { return ((_currPage + 1) < _numPages); }}
bool CanScrollLeft { get { return (_currPage > 0); }}
void ScrollRight()
{
if (CanScrollRight) {
_currPage++;
GridScrollAnimation.To = -(_thumbSize.Width + (_borderPlusPaddingWidth * 2)) * _colCount * _currPage;
GridScrollStoryboard.Begin();
}
}
void ScrollLeft()
{
if (CanScrollLeft) {
_currPage--;
GridScrollAnimation.To = -(_thumbSize.Width + (_borderPlusPaddingWidth * 2)) * _colCount * _currPage;
GridScrollStoryboard.Begin();
}
}
}
Most of the code is self explanatory but I would like to elaborate on a few key points which is highlighted in the code:
- The width of LayoutThumbnailsViewport should be equal to or greater than the width of the entire grid. If the width is not explicitly set or is less than the width of the entire grid, translatetransform will clip the contents that lie outside the area of the grid
- The width of LayoutRoot should be set to the viewing area. This should be less than the width of LayoutThumbnailsViewport if scrolling is desired
- One undesired effect of creating this layout is that the underlying grid (LayoutThumbnails) and hence the thumbnails receive mouse events even for the contents that is outside the visible area (LayoutRoot). To circumvent this I had to put this check - if (LayoutRootRect.Contains(e.GetPosition(LayoutRoot))) - in the border.MouseLeftButtonUp event handler. (Note that border is synonymous with thumbnail)
Sunday, May 4, 2008
SimpleViewer-SL Beta 1
Update 5/8/08: Fixed blank screen bug when navigating between thumbnails, and improved browser resizing support...
The Silverlight version of the flash based photo album viewer - SimpleViewer, is here! I decided to call it SimpleViewer-SL for now.
Here are a few examples that were created using SimpleViewer-SL (the keyboard arrow keys are supported as well)
SimpleViewer-SL is highly customizable. It is compatible with SimpleViewer and hence all options exposed by SimpleViewer are applicable to SimpleViewer-SL as well. The options are listed here. The only XML option that is not supported at this time is enableRightClickOpen. Note that the text fields like title and Caption should be plain text (no HTML) for now since I haven't implemented the HTML text control as yet. As for the HTML options, the only supported option is xmlDataPath. The HTML option should be passed to the SimpleViewer-SL Silverlight object using the param tag - <param name="initParams" value="xmlDataPath=gallery1.xml" />
The album can be created using any of the methods described here . After creating the albums, a few modifications to the generated file(s) is required to make it work on SimpleViewer-SL.
The xap can be found here.
The directory structure for the samples is as follows:
Between the above links, the sample html and xml files, it should be fairly straightforward for anyone to figure out how to use SimpleViewer-SL. Overall it is fairly feature complete but there is always room for improvement.It is a little unstable, in that sometime navigating between thumbnails results in a blank screen, but I think it is more to do with Silverlight being a Beta release (looks like it was more to do with my code!).
This exercise took me a little over a week of part time work so I have to say that I am pretty impressed with Silverlight. Most of my time was spent in figuring out a way to work around Silverlight 2 Beta 1 bugs. I suspect that I can simplify the code a bit after the final release of Siverlight 2. Nevertheless, the current size of SimpleViewer-SL is a mere 17kb, the same as SimpleViewer, with lots of opportunity for improvement.
For those looking for the code, I am planning to codeplex it when I get some time. Feel free to leave comments to pressure me into doing it sooner rather than later!
The Silverlight version of the flash based photo album viewer - SimpleViewer, is here! I decided to call it SimpleViewer-SL for now.
Here are a few examples that were created using SimpleViewer-SL (the keyboard arrow keys are supported as well)
- Thumbnails on the left, 3x3 grid
- Thumbnails on the right, 5x4 grid
- Thumbnails on bottom, 2x4 grid
- Thumbnails on the left, with background image
SimpleViewer-SL is highly customizable. It is compatible with SimpleViewer and hence all options exposed by SimpleViewer are applicable to SimpleViewer-SL as well. The options are listed here. The only XML option that is not supported at this time is enableRightClickOpen. Note that the text fields like title and Caption should be plain text (no HTML) for now since I haven't implemented the HTML text control as yet. As for the HTML options, the only supported option is xmlDataPath. The HTML option should be passed to the SimpleViewer-SL Silverlight object using the param tag - <param name="initParams" value="xmlDataPath=gallery1.xml" />
The album can be created using any of the methods described here . After creating the albums, a few modifications to the generated file(s) is required to make it work on SimpleViewer-SL.
The xap can be found here.
The directory structure for the samples is as follows:
gallery1/
SimpleViewer-SL.xap
gallery1.html
gallery1.xml
gallery2.html
gallery2.xml
gallery3.html
gallery3.xml
gallery4.html
gallery4.xml
thumbs/
65x65 thumbnail images
images/
the corresponding big images
Between the above links, the sample html and xml files, it should be fairly straightforward for anyone to figure out how to use SimpleViewer-SL. Overall it is fairly feature complete but there is always room for improvement.
This exercise took me a little over a week of part time work so I have to say that I am pretty impressed with Silverlight. Most of my time was spent in figuring out a way to work around Silverlight 2 Beta 1 bugs. I suspect that I can simplify the code a bit after the final release of Siverlight 2. Nevertheless, the current size of SimpleViewer-SL is a mere 17kb, the same as SimpleViewer, with lots of opportunity for improvement.
For those looking for the code, I am planning to codeplex it when I get some time. Feel free to leave comments to pressure me into doing it sooner rather than later!
Friday, April 25, 2008
Next Project - Port SimpleViewer to Silverlight
Having run out of ideas on how further to exploit Deep Zoom, I am going to now focus my energy on porting the closed-source flash based SimpleViewer to Silverlight. Again, my objective is to learn Silverlight, so I will try to push some boundaries (if required). As part of this exercise, I will try to stick to the original goal of SimpleViewer, that is keep the download as small as possible. The current version (1.8.5) of SimpleViewer is only 17k and that is what I am aiming for. This, of course, means no using the extended controls, but only what is available in the core Silverlight 2 SDK!
So, why SimpleViewer? I actually use it in my personal blog and have always wanted more control over it. I also want to know what it takes to create the same experience in Silverlight. I am expecting this to be a trivial exercise, but let's see!
My first roadblock!
I wanted to keep everything as dynamic as possible. This means no explicitly specifying the height and width of any control. I was happy to see that the image control provides this capability and is able to work well within the constraints of MaxWidth and MaxHeight. This was all wonderful and perfect for my needs but now I wanted to draw a simple border around the image. Pretty easy...all I need is the dimensions of the image (control), but this is where it got a little complicated. The image control does give access to the dimensions but a little too late. I won't go into the details here but I did post it on the Silverlight.net forum is anyone's interested. By the way this is a great forum and one of the best places to get answers to all your Silverlight questions or issues.
What I discovered is that the image loaded event is not a reliable place to extract the dimensions from the image control. I tried a kludgey workaround by creating a timer and accessing the dimensions from the image control a little later - around 100 milli seconds - but this is definitely not reliable. But at least I had a solution!
While I was playing with it some more, I discovered the SizeChanged event. This also doesn't help if the image dimensions are extracted from the image control itself, but I discovered that the event itself has the size, and that is when I had my solution!
There might be some issues with this so please leave a comment if you find something. I am assuming this is a bug with the image loaded event so, hopefully, it will be fixed in the next release of Silverlight. But, for now, it looks like I can make some "not so kludgey" progress!
So, why SimpleViewer? I actually use it in my personal blog and have always wanted more control over it. I also want to know what it takes to create the same experience in Silverlight. I am expecting this to be a trivial exercise, but let's see!
My first roadblock!
I wanted to keep everything as dynamic as possible. This means no explicitly specifying the height and width of any control. I was happy to see that the image control provides this capability and is able to work well within the constraints of MaxWidth and MaxHeight. This was all wonderful and perfect for my needs but now I wanted to draw a simple border around the image. Pretty easy...all I need is the dimensions of the image (control), but this is where it got a little complicated. The image control does give access to the dimensions but a little too late. I won't go into the details here but I did post it on the Silverlight.net forum is anyone's interested. By the way this is a great forum and one of the best places to get answers to all your Silverlight questions or issues.
What I discovered is that the image loaded event is not a reliable place to extract the dimensions from the image control. I tried a kludgey workaround by creating a timer and accessing the dimensions from the image control a little later - around 100 milli seconds - but this is definitely not reliable. But at least I had a solution!
While I was playing with it some more, I discovered the SizeChanged event. This also doesn't help if the image dimensions are extracted from the image control itself, but I discovered that the event itself has the size, and that is when I had my solution!
There might be some issues with this so please leave a comment if you find something. I am assuming this is a bug with the image loaded event so, hopefully, it will be fixed in the next release of Silverlight. But, for now, it looks like I can make some "not so kludgey" progress!
MyImage.SizeChanged += (sender, e) => {
if (e.PreviousSize.Width == 0 && e.PreviousSize.Height == 0 && e.NewSize != e.PreviousSize) {
// e.NewSize contains the dimension of the image
// MyImage.Width and MyImage.ActualWidth is unreliable so use e.NewSize instead
DrawBorderAroundImage(e.NewSize);
}
};
Subscribe to:
Posts (Atom)