Avatar

Blog (pg. 14)

  • Published on
    When you want to page a collection in Silverlight all the online documentation points you to the 'PagedCollectionView' class, which offers a paging wrapper around an IEnumerable. This needs the full list of IEnumerable data to function in the first place, which is fine for small datasets, but for the most part you want to page a database resultset from many thousands of rows, down to maybe 10 at a time. Your Silverlight application probably gets its data from a WCF service, either directly or by using RIA Services link, so you want to pass your paging request from Silverlight to the service layer, where it can be transformed through your architecture to more than likely a SQL query, so that one page of data is transported at a time. There are hints on Google to implement an IPagedCollectionView in order to achieve this. Below shows my version of an IPagedCollectionView which can then be used for 'virtual' paging of data in Silverlight:
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.Collections.Specialized;
    using System.Collections;
    
    /// <summary>
    /// A class which can be used in Silverlight to enable server side (virtual) paging code to store it's view state
    /// </summary>
    public class PagedVirtualCollectionView : IPagedCollectionView, IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged
    {
        /// <summary>
        /// Constructor takes initial page of data as its source
        /// </summary>
        /// <param name="source">A page of source data</param>
        public PagedVirtualCollectionView(IEnumerable source)
        {
            _sourceCollection = source;
        }
    
        private IEnumerable _sourceCollection;
        /// <summary>
        /// The underlying page of source data
        /// </summary>
        public IEnumerable SourceCollection
        {
            get { return _sourceCollection; }
            set { 
                _sourceCollection = value; 
                OnPropertyChanged("SourceCollection"); 
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
            }
        }
    
        private int _VirtualItemCount;
        /// <summary>
        /// Get or set this value, which is the total number of records in the database
        /// </summary>
        public int VirtualItemCount
        {
            get { return _VirtualItemCount; }
            set { 
                _VirtualItemCount = value; 
                OnPropertyChanged("VirtualItemCount"); 
                OnPropertyChanged("ItemCount"); 
                OnPropertyChanged("TotalItemCount"); 
            }
        }
    
        public int VirtualPageCount
        {
            get { return (int)Math.Ceiling((double)VirtualItemCount / (double)PageSize); }
        }
    
        private int _pageIndex = 0;
        private int _pageSize;
    
        #region "IEnumerable"
        /// <summary>
        /// For IEnumerable interface, passes the call to the underlying SourceCollection
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            return _sourceCollection.GetEnumerator();
        }
        #endregion
    
        #region "IPagedCollectionView"
        public bool CanChangePage
        {
            get { return !_isPageChanging; }
        }
    
        private bool _isPageChanging = false;
        /// <summary>
        /// Return true between states of page changing and page changed, otherwise false
        /// </summary>
        public bool IsPageChanging
        {
            get { return _isPageChanging; }
            set { _isPageChanging = value; OnPropertyChanged("IsPageChanging"); }
        }
    
        /// <summary>
        /// This will be the same as the virtual item count, for use in data paging controls
        /// </summary>
        public int ItemCount
        {
            get { return VirtualItemCount; }
        }
    
        public bool MoveToFirstPage()
        {
            return MoveToPage(0);
        }
    
        public bool MoveToLastPage()
        {
            return MoveToPage(VirtualPageCount - 1);
        }
    
        public bool MoveToNextPage()
        {
            return MoveToPage(PageIndex + 1);
        }
    
        public bool MoveToPage(int pageIndex)
        {
            if (pageIndex >= 0 && pageIndex <= (VirtualPageCount - 1))
            {
                    
    
                //fire the page changing event so the call can be made to load the next page of data
                PageChangingEventArgs pcea = new PageChangingEventArgs(pageIndex);
                OnPageChanging(pcea);
    
                if (!pcea.Cancel)
                {
                    //let outside world know we are changing pages
                    IsPageChanging = true;
    
                    //event handlers have run, we should now be on the new page
                    _pageIndex = pageIndex;
    
                    //let the outside world know we are no longer changing pages
                    IsPageChanging = false;
    
                    OnPropertyChanged("PageIndex");
    
                    //raise an event to signal the completed change of page
                    OnPageChanged();
                    return true;
                }
                else
                {
                    //event handler cancelled the page change
                    return false;
                }
                    
            }
            else
            {
                //page index out of bounds, or was busy changing pages
                return false;
            }
        }
    
        public bool MoveToPreviousPage()
        {
            return MoveToPage(PageIndex - 1);
        }
    
        public event EventHandler<EventArgs> PageChanged;
        protected void OnPageChanged()
        {
            if (PageChanged != null)
                PageChanged(this, EventArgs.Empty);
        }
    
        public event EventHandler<PageChangingEventArgs> PageChanging;
        protected void OnPageChanging(PageChangingEventArgs e)
        {
            if (PageChanging != null)
                PageChanging(this, e);
        }
    
        /// <summary>
        /// Read-only version of page index, use the helper functions to move through the pages
        /// </summary>
        public int PageIndex
        {
            get { return _pageIndex; }
        }
    
        /// <summary>
        /// Get/set the page size which will can then be used by the external paging calls
        /// </summary>
        public int PageSize
        {
            get
            {
                return _pageSize;
            }
            set
            {
                _pageSize = value;
            }
        }
    
        public int TotalItemCount
        {
            //this will always be the same as virtual item count
            get { return VirtualItemCount; }
        }
        #endregion
    
        #region "INotifyPropertyChanged"
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    
        #region"INotifyCollectionChanged"
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
                CollectionChanged(this, e);
        }
        #endregion
    }
    
    Basically it works by allowing you to specify a 'VirtualItemCount' aswel as your 'SourceCollection' so that you can dictate how many items there are in total (before paging has been applied). Whether you are using MVVM or traditional DataContext, you should bind your grid and pager to the instance of the PagedVirtualCollectionView and handle the 'PageChanging' event. You can then replace the 'SourceCollection' with the current page of data and set the 'VirtualItemCount' to the total count of records. E.g.
    
    private PagedVirtualCollectionView _SearchResults = new PagedVirtualCollectionView(new List<SearchResult>());
    public PagedVirtualCollectionView SearchResults
    {
        get { return _SearchResults; }
    }
    
    public MyViewModel()
    {
        _SearchResults.PageChanging += new EventHandler<PageChangingEventArgs>(SearchResultsPageChangingHandler);
    }
    
    protected void SearchResultsPageChangingHandler(object sender, PageChangingEventArgs e)
    {
        UpdateSearchResults(e.NewPageIndex);
    }
    
    public void UpdateSearchResults(int pageIndex)
    {
        //CALL YOUR SERVICE PASSING pageIndex and SearchResults.PageSize
    }
    
    void UpdateSearchResultsCompleted(object sender, SearchCompletedEventArgs e)
    {          
        if (e.Error == null)
        {
            SearchResults.SourceCollection = e.Result;
            SearchResults.VirtualItemCount = e.totalRecords; //totalRecords is defined as an 'out' parameter to the WCF service
        }
    }
    
  • Published on
    Recently I had a problem with a URL rewriting regular expression not picking up URL requests, resulting in 404 not found errors. The expression was designed to pickup anything that consists of a string containing no dots (extensionless files). This is because I know these files exist only in a database and have a handler. It worked fine until one day it just didn't. After enabling logging in Helicon rewriter, I could see the original URL was not actually being passed through to the Helicon ISAPI filter and infact had already been modified to include '/eurl.axd/{hash}' on the end. Hence why the regex was not making a positive match. A quick Google revealed a change in ASP.NET 4 that seems to handle extensionless file requests slightly differently than before. It looks like .NET 4 is getting in early (ASP.NET never used to fire for non-mapped files, right?) and picking up the extensionless file and (maybe because its IIS 6) is rewriting the URL, as above, with the '/eurl.axd/{hash}' part suffixed. This was now causing a 404. To fix this I simply added a 2nd regular expression that would pick up these requests for extensionless files with aforementioned 'eurl.axd' suffix so that this will also pass to the database file handler.
  • Published on
    After the Silverlight runtime update to 4.0.60531.0, opening a XAML file in design view was crashing visual studio. There was also a problem in opening the Toolbox, or any designer that loads the toolbox, with Visual Studio crashing when "Loading toolbox content from package 'Microsoft.VisualStudio.IDE.ToolboxControlsInstaller.ToolboxInstallerPackage' {2C298B35-07DA-45F1-96A3-BE55D91C8D7A}" When I found the solution, it appears this probably only affects systems where 'System.Windows.dll' has been previously registered in the GAC. The solution, is to remove System.Windows from your GAC using the following command from the Visual Studio Command Prompt: gacutil -u System.Windows
  • Published on
    Recently I needed to traverse the visual tree of a Silverlight dependancy object (UI element) in order to find a child object with the type I was looking for. As I didn't know how far nested the child would be, I wrote a recursive helper function which will scan all child elements looking for the first instance it finds. I then extended this functionality with a overload allowing to specify the name of the object I was looking for. I thought it might be useful elsewhere, so here it is:
    
    private T FindControlByType<T>(DependencyObject container) where T : DependencyObject
    {
        return FindControlByType<T>(container, null);
    }
    
    private T FindControlByType<T>(DependencyObject container, string name) where T : DependencyObject
    {
        T foundControl = null;
    
        //for each child object in the container
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
        {
            //is the object of the type we are looking for?
            if (VisualTreeHelper.GetChild(container, i) is T && (VisualTreeHelper.GetChild(container, i).GetValue(FrameworkElement.NameProperty).Equals(name) || name == null))
            {
                foundControl = (T)VisualTreeHelper.GetChild(container, i);
                break;
            }
            //if not, does it have children?
            else if (VisualTreeHelper.GetChildrenCount(VisualTreeHelper.GetChild(container, i)) > 0)
            {
                //recursively look at its children
                foundControl = FindControlByType<T>(VisualTreeHelper.GetChild(container, i), name);
                if (foundControl != null)
                    break;
            }
        }
    
        return foundControl;
    }
    
    You can tweak the code to accept more parameters if you need more comparisons to match your object.
  • Published on
    These errors seem to be becoming more and more frequent and I'm still looking for a workaround. I have managed to raise emails and audible warnings before this happens, but there seems to be latency issues in getting any response. Sometimes I have to deal with this manually :(