Intersoft ClientUI Documentation
How-to: Enable Load on Demand for UXBreadCrumb

Description

This example demonstrates how to configure UXBreadCrumb to enable load-on-demand feature.

Prerequisite

This example uses project files from example How-to: Use UXBreadCrumb for Page Navigation.

Tutorial

Enabling Load on Demand

1. Modify Sitemap.xml to match the following:

Example Title
Copy Code
<Sitemap>
  <SitemapData>
    <DisplayName>Home</DisplayName>
    <Path>/</Path>
    <Children>
      <SitemapData>
        <DisplayName>ClientUI</DisplayName>
        <Path>/ClientUI</Path>
        <Children>
          <SitemapData>
            <DisplayName>Scheduling Controls</DisplayName>
            <Path>/ClientUI/Scheduling Controls</Path>
            <Children>
              <SitemapData>
                <DisplayName>Schedule View</DisplayName>
                <Path>/ClientUI/Scheduling Controls/Schedule View</Path>
              </SitemapData>
            </Children>
          </SitemapData>
          <SitemapData>
            <DisplayName>Ribbon Controls</DisplayName>
            <Path>/ClientUI/Ribbon Controls</Path>
            <Children>
              <SitemapData>
                <DisplayName>Ribbon</DisplayName>
                <Path>/ClientUI/Ribbon Controls/Ribbon</Path>
              </SitemapData>
            </Children>
          </SitemapData>
        </Children>
      </SitemapData>
      <SitemapData>
        <DisplayName>ASP.NET</DisplayName>
        <Path>/ASP.NET</Path>
      </SitemapData>
    </Children>
  </SitemapData>
</Sitemap>
        
Sitemap.xml must be modified as such that the root URI must be equal to the PathSeparator, by default PathSeparator is set to '/'. UXBreadCrumb uses the URI information and separates them using PathSeparator to generate the items, therefore it is crucial that the sitemap structure must be well-formed.

2. Modify the ViewModel to match the following:

BreadCrumbViewModel.cs
Copy Code
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Resources;
using System.Xml.Linq;
using Intersoft.Client.Framework;
using Intersoft.Client.UI.Navigation;

namespace BreadCrumbLoadOnDemand.ViewModels
{
    public class BreadCrumbViewModel : ViewModelBase
    {
        #region Fields

        private object _expandedItem;
        private string _greetingText;
        private object _processedItem;
        private object _queryResult;
        private string _queryText;
        private Uri _resolvedUri;

        #endregion

        #region Constructor

        public BreadCrumbViewModel()
        {
            //Get the StreamResourceInfo for the XML file
            StreamResourceInfo resourceStream = Application.GetResourceStream(new Uri("/BreadCrumbLoadOnDemand;Component/XML/Sitemap.xml", UriKind.Relative));
            if (resourceStream != null)
            {
                XDocument xdoc = XDocument.Load(resourceStream.Stream, LoadOptions.SetBaseUri);
                //Use the utility method to generate sitemap from XML
                Sitemap = Intersoft.Client.UI.Navigation.UXBreadCrumb.GenerateSitemap(xdoc, "DisplayName", "Path", "Children", null);
            }
        }

        #endregion

        #region BackgroundWorker

        public BackgroundWorker BackgroundWorker { get; set; }

        void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            if (worker != null)
            {
                SitemapData obj = e.Argument as SitemapData;
                if (obj != null)
                {
                    var dataThread = new Thread(() =>
                    {
                        //This is to simulate request, do not use in production code
                        Thread.Sleep(500);

                        CrossPlatform.BeginInvoke(new Action(() =>
                        {
                            if (!worker.CancellationPending)
                            {
                                this.SearchSitemap(obj);

                                if (this.SitemapData != null && this.SitemapData.Children != null && this.SitemapData.Children.Any())
                                    obj.Children = this.SitemapData.Children;
                                else
                                    obj.Children = null;

                                this.ProcessedItem = obj;
                            }
                        }));
                    });
                    dataThread.Start();

                }
            }
        }

        #endregion

        #region Properties

        public object ExpandedItem
        {
            get { return this._expandedItem; }
            set
            {
                //Destroy current BackgroundWorker to cancel
                if (this.BackgroundWorker != null)
                {
                    this.BackgroundWorker.DoWork -= BackgroundWorker_DoWork;
                    this.BackgroundWorker.CancelAsync();
                    this.BackgroundWorker = null;
                }

                if (this._expandedItem != value)
                {
                    this._expandedItem = value;
                    SitemapData obj = value as SitemapData;
                    if (obj != null)
                    {
                        //Wrap the thread to a BackgroundWorker to support cancellation.
                        this.BackgroundWorker = new BackgroundWorker { WorkerSupportsCancellation = true };
                        this.BackgroundWorker.DoWork += BackgroundWorker_DoWork;
                        this.BackgroundWorker.RunWorkerAsync(obj);
                    }

                    OnPropertyChanged("ExpandedItem");
                }
            }
        }

        public object ProcessedItem
        {
            get { return this._processedItem; }
            set
            {
                if (this._processedItem != value)
                {
                    this._processedItem = value;
                    OnPropertyChanged("ProcessedItem");
                }
            }
        }

        public object QueryResult
        {
            get { return this._queryResult; }
            set
            {
                if (this._queryResult != value)
                {
                    this._queryResult = value;
                    OnPropertyChanged("QueryResult");
                }
            }
        }

        public string QueryText
        {
            get { return this._queryText; }
            set
            {
                if (this._queryText != value)
                {
                    this._queryText = value;
                    var dataThread = new Thread(() =>
                    {
                        //This is to simulate request, do not use in production code
                        Thread.Sleep(500);

                        CrossPlatform.BeginInvoke(new Action(() =>
                        {
                            this.QueryResult = this.SearchQueryTextFromSitemap(this._queryText);
                        }));
                    });
                    dataThread.Start();
                }
                else
                {
                    this._queryText = value;
                }
                OnPropertyChanged("QueryText");
            }
        }

        public SitemapData ResultSitemapData { get; set; }

        public IEnumerable<SitemapData> Sitemap { get; set; }

        public string GreetingText
        {
            get { return this._greetingText; }
            set
            {
                if (this._greetingText != value)
                {
                    this._greetingText = value;
                    OnPropertyChanged("GreetingText");
                }
            }
        }

        public Uri ResolvedUri
        {
            get { return this._resolvedUri; }
            set
            {
                if (this._resolvedUri != value)
                {
                    this._resolvedUri = value;
                    OnPropertyChanged("ResolvedUri");
                    //After ResolvedUri has been set, find the appropriate display name to be displayed to GreetingText
                    this.FindDisplayNameRecursive(this.Sitemap);
                }
            }
        }

        public SitemapData SitemapData { get; set; }

        #endregion

        #region Methods

        private void FindDisplayNameRecursive(IEnumerable sitemap)
        {
            if (sitemap != null)
            {
                foreach (SitemapData sitemapData in sitemap)
                {
                    if (sitemapData.NavigateUri == this.ResolvedUri)
                    {
                        this.GreetingText = "You're now at: " + sitemapData.DisplayName;
                        break;
                    }

                    if (sitemapData.Children != null)
                        this.FindDisplayNameRecursive(sitemapData.Children);
                }
            }
        }

        private void SearchSitemap(SitemapData sitemapData)
        {
            this.RecursiveSitemapSearch(sitemapData, this.Sitemap);
        }

        private void RecursiveSitemapSearch(SitemapData sitemapData, IEnumerable<SitemapData> collection)
        {
            foreach (SitemapData data in collection)
            {
                if (sitemapData.NavigateUri == data.NavigateUri)
                    this.SitemapData = data;

                if (data.Children != null)
                    this.RecursiveSitemapSearch(sitemapData, data.Children);
            }
        }

        private object SearchQueryTextFromSitemap(string queryText)
        {
            ObservableCollection<SitemapData> collection = new ObservableCollection<SitemapData>();
            this.SearchQueryTextRecursive(queryText, this.Sitemap, collection);
            return collection;
        }

        private void SearchQueryTextRecursive(string queryText, IEnumerable<SitemapData> collection, ObservableCollection<SitemapData> returnCollection)
        {
            foreach (SitemapData data in collection)
            {
                if (data.NavigateUri != null && data.NavigateUri.OriginalString.StartsWith(queryText))
                    returnCollection.Add(data);

                if (data.Children != null)
                    this.SearchQueryTextRecursive(queryText, data.Children, returnCollection);
            }
        }

        #endregion
    }
}

The load on demand feature mechanism works as follows:

In view mode, when user clicks the dropdown of a UXBreadCrumbItem, the ExpandedItem property is set by the user in the view model, and is returned to the UXBreadCrumb control using the ProcessedItem property.

In editing mode, when user types a query text, it is bound to QueryText property. When QueryText raises the OnPropertyChanged event, the view model searches the sitemap for the matching navigate URI, and returns the result to UXBreadCrumb control via QueryResult property, which will be displayed on suggestion dropdown list.

Consider a scenario when the drop down is opened and user hover to several items in a rapid manner, therefore it is recommended to wrap the request in a BackgroundWorker as shown above to elegantly cancel the previous requests and only process the last item request.

3. Open Customers.xaml in folder Views, and modify to the following code:

Customers.xaml
Copy Code
<Intersoft:UXPage 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" mc:Ignorable="d"
        xmlns:Intersoft="http://intersoft.clientui.com/schemas"
        xmlns:ViewModels="clr-namespace:BreadCrumbLoadOnDemand.ViewModels" x:Class="BreadCrumbLoadOnDemand.Customers"
        Title="Customers Page" d:DesignWidth="800" d:DesignHeight="600" Style="{StaticResource CommonPageStyle}">

    <Intersoft:UXPage.DataContext>
        <ViewModels:BreadCrumbViewModel />
    </Intersoft:UXPage.DataContext>

    <Grid x:Name="LayoutRoot">
        <Grid.Background>
            <ImageBrush AlignmentY="Bottom" AlignmentX="Right" Stretch="None" Opacity="0.5"
                    ImageSource="../Assets/Images/CustomersFolderLarge.png">
                <ImageBrush.Transform>
                    <TranslateTransform X="40" Y="40" />
                </ImageBrush.Transform>
            </ImageBrush>
        </Grid.Background>
        <Intersoft:DockPanel Margin="10" FillChildMode="Custom">
            <Intersoft:StylishLabel Content="Customers Page" Intersoft:DockPanel.Dock="Top"
                    Style="{StaticResource PageHeaderStyle}" />
            <StackPanel Intersoft:DockPanel.IsFillElement="True">
                <Intersoft:UXBreadCrumb Sitemap="{Binding Sitemap}" DisplayMemberPath="DisplayName"
                        NavigateUriMemberPath="NavigateUri" CollectionMemberPath="Children"
                        TargetFrame="MainContentFrame" IsLoadOnDemand="True"
                        ExpandedItem="{Binding ExpandedItem, Mode=TwoWay}"
                        ProcessedItem="{Binding ProcessedItem, Mode=TwoWay}"
                        QueryText="{Binding QueryText, Mode=TwoWay}" QueryResult="{Binding QueryResult, Mode=TwoWay}" RootName="Home" RootUri="/"  />
                <Intersoft:UXFrame x:Name="MainContentFrame" JournalOwnership="OwnsJournal"
                        Source="/ClientUI/Scheduling Controls/Schedule View" >
                    <Intersoft:UXFrame.UriMapper>
                        <Intersoft:UriMapper>
                            <Intersoft:UriMapping Uri="/{page}" MappedUri="/Views/TestPage.xaml?uri={page}" />
                        </Intersoft:UriMapper>
                    </Intersoft:UXFrame.UriMapper>
                </Intersoft:UXFrame>
            </StackPanel>
        </Intersoft:DockPanel>
    </Grid>
</Intersoft:UXPage>

Note that ExpandedItemProcessedItemQueryTextQueryResultRootName, and RootUri is set to support the load on demand scenario. The UXFrame Source and UriMapper is also updated accordingly.

For more information on UXFrame, see UXFrame page.

4. Run the project and navigate to Customers page.

Result

See Also