File: System\Windows\Navigation\JournalEntry.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
//
// Description:
// implements the Avalon Journal Entry Object
//
//
//
//
 
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.IO.Packaging;
using System.Globalization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security ;
using System.Windows.Markup;
 
using MS.Internal.AppModel;
using MS.Internal;
using MS.Internal.Utility;
 
using System.Windows.Controls.Primitives;
 
//In order to avoid generating warnings about unknown message numbers and
//unknown pragmas when compiling your C# source code with the actual C# compiler,
//you need to disable warnings 1634 and 1691. (Presharp Documentation)
#pragma warning disable 1634, 1691
 
namespace System.Windows.Navigation
{
    /// <summary>
    /// Derived classes encapsulate content/view state associated with custom journal entries.
    /// Object instances are passed to AddBackEntry() and also requested by the framework via the
    /// IProvideCustomContentState interface.
    /// </summary>
    [Serializable]
    public abstract class CustomContentState
    {
        /// <summary>
        /// Provides the display name for the journal entry created for this custom content state.
        /// If the returned string is null or empty, the name will be obtained, in order of
        /// precedence, from either: the JournalEntry.Name or Page.Title attached property set on
        /// the content root; the Window's title; or the current source URI.
        /// </summary>
        public virtual string JournalEntryName
        {
            get { return null; }
        }
 
        /// <summary>
        /// Called by the framework when navigation to a custom journal entry occurs. The method
        /// re-applies the encapsulated content/view state to the base content (element tree).
        /// </summary>
        public abstract void Replay(NavigationService navigationService, NavigationMode mode);
    };
 
    /// <summary>
    /// Interface to be implemented by a navigator's Content object (root element). Provides
    /// snapshots of the content state for custom journal entries.
    /// </summary>
    /// <remarks> As an alternative to implementing this interface, an application can handle
    /// the Navigating event and set the NavigatingCancelEventArgs.ContentStateToSave property.
    /// However, this method does not preserve the state of child frames.
    /// </remarks>
    public interface IProvideCustomContentState
    {
        /// <summary>
        /// Override this method to capture the custom content state or the content's view state.
        /// </summary>
        /// <returns></returns>
        CustomContentState GetContentState();
    };
 
    /// <summary>
    /// The base class for all journal entries.
    /// </summary>
    /// <remarks>
    /// Custom serialization implementation is needed (ISerializable) because the DependencyObject base
    /// is not [Serializable]. The Name DP is saved explicitly. (Even though it is registered as
    /// attached, it is also set on JE. KeepAlive is not saved, because KA entries are pruned before
    /// TravelLog serialization.)
    /// </remarks>
    [Serializable]
    public class JournalEntry : DependencyObject, ISerializable
    {
        // Ctors
        /////////////////////////////////////////////////////////////////////
 
        #region Ctors
 
        internal JournalEntry(JournalEntryGroupState jeGroupState, Uri uri)
        {
            _jeGroupState = jeGroupState;
            if (jeGroupState != null)
            {
                // Seems convenient to always do this here.
                jeGroupState.GroupExitEntry = this;
            }
            Source = uri;
        }
 
        /// <summary>
        /// Serialization constructor. Marked Protected so derived classes can be deserialized correctly
        /// </summary>
        protected JournalEntry(SerializationInfo info, StreamingContext context)
        {
            _id = info.GetInt32("_id");
            _source = (Uri)info.GetValue("_source", typeof(Uri));
            _entryType = (JournalEntryType)info.GetValue("_entryType", typeof(JournalEntryType));
            _jeGroupState = (JournalEntryGroupState)info.GetValue("_jeGroupState", typeof(JournalEntryGroupState));
            _customContentState = (CustomContentState)info.GetValue("_customContentState", typeof(CustomContentState));
            _rootViewerState = (CustomJournalStateInternal)info.GetValue("_rootViewerState", typeof(CustomJournalStateInternal));
            this.Name = info.GetString("Name");
        }
 
        /// <summary>
        /// ISerializable implementation
        /// </summary>
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            ArgumentNullException.ThrowIfNull(info);
 
            info.AddValue("_id", _id);
            info.AddValue("_source", _source);
            info.AddValue("_entryType", _entryType);
            // All these have to be written even if they are null. Othewise info.GetValue() would throw
            // in the serialization ctor.
            info.AddValue("_jeGroupState", _jeGroupState);
            info.AddValue("_customContentState", _customContentState);
            info.AddValue("_rootViewerState", _rootViewerState);
            info.AddValue("Name", this.Name); // see Remarks on class declaration
        }
        #endregion
 
        // Public methods
        /////////////////////////////////////////////////////////////////////
 
        #region Public Methods
 
        /// <summary>
        /// Reads the attached property JournalEntry.Name from the given element.
        /// Setting it at the root element of a navigator will update NavWin’s dropdown menu when navigating inside of that navigator.
        /// </summary>
        /// <param name="dependencyObject">The element from which to read the attached property.</param>
        /// <returns>The property's value.</returns>
        public static string GetName(DependencyObject dependencyObject)
        {
            // not verifying Context on static method
 
            return dependencyObject != null ? (string)dependencyObject.GetValue(NameProperty) : null;
        }
 
        /// <summary>
        /// Writes the attached property JournalEntry.Name to the given element.
        /// </summary>
        /// <param name="dependencyObject">The element to which to write the attached property.</param>
        /// <param name="name">The property value to set</param>
        public static void SetName(DependencyObject dependencyObject, string name)
        {
            // not verifying Context on static method
 
            ArgumentNullException.ThrowIfNull(dependencyObject);
 
            dependencyObject.SetValue(NameProperty, name);
        }
 
        /// <summary>
        /// Reads the attached property KeepAlive from the given element. This is not a property of the
        /// journal, but rather a flag to indicate that this tree should be keep alived
        /// as part of journaling. If KeepAlive == true, then the entire tree will be saved.
        /// </summary>
        /// <param name="dependencyObject">The element from which to read the attached property.</param>
        /// <returns>The property's value.</returns>
        /// <seealso cref="JournalEntry.KeepAliveProperty" />
        public static bool GetKeepAlive(DependencyObject dependencyObject)
        {
            // not verifying Context on static method
            ArgumentNullException.ThrowIfNull(dependencyObject);
 
            return (bool)dependencyObject.GetValue(KeepAliveProperty);
        }
 
        /// <summary>
        /// Writes the attached property KeepAlive to the given element. See above.
        /// </summary>
        /// <param name="dependencyObject">The element to which to write the attached property.</param>
        /// <param name="keepAlive">The property value to set</param>
        /// <seealso cref="JournalEntry.KeepAliveProperty" />
        public static void SetKeepAlive(DependencyObject dependencyObject, bool keepAlive)
        {
            // not verifying Context on static method
 
            ArgumentNullException.ThrowIfNull(dependencyObject);
 
            dependencyObject.SetValue(KeepAliveProperty, keepAlive);
        }
        #endregion
 
        // Public properties
        /////////////////////////////////////////////////////////////////////
 
        #region Public Properties
        /// <summary>
        /// The Uri of the stored Page
        /// </summary>
        public Uri Source
        {
            get { return _source; }
            set { _source = BindUriHelper.GetUriRelativeToPackAppBase(value); }
        }
 
        /// <summary> The custom content state or content view state associated with the entry </summary>
        public CustomContentState CustomContentState
        {
            get { return _customContentState; }
            internal set { _customContentState = value; }
        }
 
        /// <summary>
        /// The name of the journal entry to be displayed in the drop-down list
        /// on the Back/Forward buttons
        /// </summary>
        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }
 
        /// <summary>
        /// Attached DependencyProperty for Name property.
        /// Setting it at the root element of a navigator will update NavWin’s dropdown menu when navigating inside of that navigator.
        /// </summary>
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.RegisterAttached("Name", typeof(string), typeof(JournalEntry), new PropertyMetadata(String.Empty));
 
        /// <summary>
        /// DependencyProperty for KeepAlive property.
        /// </summary>
        public static readonly DependencyProperty KeepAliveProperty =
            DependencyProperty.RegisterAttached("KeepAlive", typeof(bool), typeof(JournalEntry), new PropertyMetadata(false));
 
        #endregion
 
        // Internal methods
        /////////////////////////////////////////////////////////////////////
 
        #region Internal Methods
 
        /// <summary>
        /// Is this journal entry journaling a page function?
        /// </summary>
        internal virtual bool IsPageFunction()
        {
            return false;
        }
 
        /// <summary>
        /// Is this journal entry holding on to a tree?
        /// </summary>
        /// <returns></returns>
        internal virtual bool IsAlive()
        {
            return false;
        }
 
        internal virtual void SaveState(object contentObject)
        {
            ArgumentNullException.ThrowIfNull(contentObject);
            if (!IsAlive())
            {
                if (_jeGroupState.JournalDataStreams != null)
                {
                    Debug.Assert(!_jeGroupState.JournalDataStreams.HasAnyData,
                        "JournalDataSreams should have been emptied by the last RestoreState() call.");
                }
                else
                {
                    _jeGroupState.JournalDataStreams = new DataStreams();
                }
                _jeGroupState.JournalDataStreams.Save(contentObject);
            }
        }
 
        internal virtual void RestoreState(object contentObject)
        {
            ArgumentNullException.ThrowIfNull(contentObject);
            if (IsAlive())
            {
                Debug.Assert(_jeGroupState.JournalDataStreams == null);
            }
            else
            {
                DataStreams jds = _jeGroupState.JournalDataStreams;
                if (jds != null)
                {
                    jds.Load(contentObject);
                    // DataStreams not needed anymore. Clear for fresh saving when the next navigation
                    // occurs.
                    jds.Clear();
                }
            }
        }
 
        internal virtual bool Navigate(INavigator navigator, NavigationMode navMode)
        {
            Debug.Assert(navMode == NavigationMode.Back || navMode == NavigationMode.Forward);
 
            // This is fallback functionality if somebody creates a JournalEntry and gives it a URI.
            if (this.Source != null)
            {
                return navigator.Navigate(Source, new NavigateInfo(Source, navMode, this));
            }
            else
            {
                Invariant.Assert(false, "Cannot navigate to a journal entry that does not have a Source.");
                return false;
            }
        }
 
        /// <summary>
        /// Returns a string that contains the URI for display in a JournalEntry.
        /// It removes pack://application:,,,/ and any component, and replaces
        /// pack://siteoforigin:,,,/ with the actual site of origin URI.
        /// </summary>
        /// <param name="uri">The URI to display</param>
        /// <param name="siteOfOrigin">The site of origin URI</param>
        /// <returns>display name</returns>
        internal static string GetDisplayName(Uri uri, Uri siteOfOrigin)
        {
            // In case this is a fragment navigation in a tree that was not navigated to by URI...
            if (!uri.IsAbsoluteUri)
            {
                return uri.ToString();
            }
 
            string displayName;
            if (string.Equals(uri.Scheme, PackUriHelper.UriSchemePack, StringComparison.OrdinalIgnoreCase))
            {
                Uri relative = BaseUriHelper.MakeRelativeToSiteOfOriginIfPossible(uri);
                if (!relative.IsAbsoluteUri)
                {
                    displayName = (new Uri(siteOfOrigin, relative)).ToString();
                }
                else
                {
                    string relativeUri = uri.AbsolutePath + uri.Query + uri.Fragment;
                    string part, assy, assyVers, assyKey;
                    BaseUriHelper.GetAssemblyNameAndPart(new Uri(relativeUri, UriKind.Relative), out part, out assy, out assyVers, out assyKey);
                    if (!string.IsNullOrEmpty(assy))
                    {
                        displayName = part;
                    }
                    else
                    {
                        displayName = relativeUri;
                    }
                }
            }
            else
            {
                displayName = uri.ToString();
            }
 
            if (!string.IsNullOrEmpty(displayName) && displayName[0] == '/')
            {
                displayName = displayName.Substring(1);
            }
 
            return displayName;
        }
        #endregion
 
        // Internal properties
        /////////////////////////////////////////////////////////////////////
 
        #region Internal properties
 
        internal JournalEntryGroupState JEGroupState
        {
#if DEBUG
            [DebuggerStepThrough]
#endif
            get { return _jeGroupState; }
            set { _jeGroupState = value; }
        }
 
        /// <summary>
        /// We will now store the entire entry data in the travellog so Journaling will
        /// work even if you renavigate back to the app from html pages as long as it is
        /// linear history navigations.
        /// </summary>
        internal int Id
        {
            get { return _id; }
            set { _id = value; }
        }
 
        /// <summary>
        /// The NavigationService(toplevel || frame) where this entry was last displayed.
        /// </summary>
        internal Guid NavigationServiceId
        {
            get { return _jeGroupState.NavigationServiceId; }
        }
 
        /// <summary>
        /// Journal entry type
        /// </summary>
        internal JournalEntryType EntryType
        {
#if DEBUG
            [DebuggerStepThrough]
#endif
            get { return _entryType; }
            set { _entryType = value; }
        }
 
        /// <summary>
        /// Can this be navigated to?
        /// </summary>
        internal bool IsNavigable()
        {
            return _entryType == JournalEntryType.Navigable;
        }
 
        internal uint ContentId
        {
            get { return _jeGroupState.ContentId; }
        }
 
        internal CustomJournalStateInternal RootViewerState
        {
            get { return _rootViewerState; }
            set { _rootViewerState = value; }
        }
 
        #endregion
 
        // Private fields
        //
        // If you add any fields here, check if it needs to be serialized.
        // If yes, then add it to the ISerializable implementation and make
        // corresponding changes in the Serialization constructor.
        //
        /////////////////////////////////////////////////////////////////////
 
        #region Private fields
        private int _id;
        private JournalEntryGroupState _jeGroupState;
        /// <summary>
        /// Converted relative to pack://application, if possible
        /// </summary>
        private Uri _source;
        private JournalEntryType _entryType = JournalEntryType.Navigable;
        private CustomContentState _customContentState;
        private CustomJournalStateInternal _rootViewerState;
        #endregion
    }
 
    //**
    //** All derived classes are in MS.Internal.AppModel.
}