File: Core\CSharp\System\Windows\Media\HostVisual.cs
Project: wpf\src\PresentationCore.csproj (PresentationCore)
//------------------------------------------------------------------------------
//
// <copyright file="HostVisual.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:
//     Host visual.
//
//------------------------------------------------------------------------------
 
namespace System.Windows.Media
{
    using System;
    using System.Windows.Threading;
    using System.Windows.Media;
    using System.Windows.Media.Composition;
    using System.Diagnostics;
    using System.Collections;
    using System.Collections.Generic;
    using MS.Internal;
    using System.Resources;
    using System.Runtime.InteropServices;
    using MS.Win32;
    using System.Threading;
 
    using SR=MS.Internal.PresentationCore.SR;
    using SRID=MS.Internal.PresentationCore.SRID;
 
    /// <summary>
    /// Host visual.
    /// </summary>
    public class HostVisual : ContainerVisual
    {
        //----------------------------------------------------------------------
        //
        //  Constructors
        //
        //----------------------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        ///
        /// </summary>
        public HostVisual()
        {
 
        }
 
        #endregion Constructors
 
        //----------------------------------------------------------------------
        //
        //  Protected Methods
        //
        //----------------------------------------------------------------------
 
        #region Protected Methods
 
        /// <summary>
        /// HitTestCore
        /// </summary>
        protected override HitTestResult HitTestCore(
            PointHitTestParameters hitTestParameters)
        {
            //
            // HostVisual never reports itself as being hit. To change this
            // behavior clients should derive from HostVisual and override
            // HitTestCore methods.
            //
            return null;
        }
 
        /// <summary>
        /// HitTestCore
        /// </summary>
        protected override GeometryHitTestResult HitTestCore(
            GeometryHitTestParameters hitTestParameters)
        {
            //
            // HostVisual never reports itself as being hit. To change this
            // behavior clients should derive from HostVisual and override
            // HitTestCore methods.
            //
            return null;
        }
 
        #endregion Protected Methods
 
        //----------------------------------------------------------------------
        //
        //  Internal Methods
        //
        //----------------------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        ///
        /// </summary>
        internal override Rect GetContentBounds()
        {
            return Rect.Empty;
        }
 
        /// <summary>
        ///
        /// </summary>
        internal override void RenderContent(RenderContext ctx, bool isOnChannel)
        {
            //
            // Make sure that the visual target is properly hosted.
            //
 
            EnsureHostedVisualConnected(ctx.Channel);
        }
 
        /// <summary>
        ///
        /// </summary>
        internal override void FreeContent(DUCE.Channel channel)
        {
            //
            // Disconnect hosted visual from this channel.
            //
 
            using (CompositionEngineLock.Acquire())
            {
                DisconnectHostedVisual(
                    channel,
                    /* removeChannelFromCollection */ true);
            }
 
            base.FreeContent(channel);
        }
 
        /// <summary>
        ///
        /// </summary>
        internal void BeginHosting(VisualTarget target)
        {
            //
            // This method is executed on the visual target thread.
            //
 
            Debug.Assert(target != null);
            Debug.Assert(target.Dispatcher.Thread == Thread.CurrentThread);
 
            using (CompositionEngineLock.Acquire())
            {
                //
                // Check if another target is already hosted by this
                // visual and throw exception if this is the case.
                //
                if (_target != null)
                {
                    throw new InvalidOperationException(
                        SR.Get(SRID.VisualTarget_AnotherTargetAlreadyConnected)
                        );
                }
 
                _target = target;
 
                //
                // If HostVisual and VisualTarget on same thread, then call Invalidate
                // directly. Otherwise post invalidate message to the host visual thread
                // indicating that content update is required.
                //
                if (this.CheckAccess())
                {
                    Invalidate();
                }
                else
                {
                    Dispatcher.BeginInvoke(
                        DispatcherPriority.Normal,
                        (DispatcherOperationCallback)delegate(object args)
                        {
                            Invalidate();
                            return null;
                        },
                        null
                        );
                }
            }
        }
 
        /// <summary>
        ///
        /// </summary>
        internal void EndHosting()
        {
            //
            // This method is executed on the visual target thread.
            //
 
            using (CompositionEngineLock.Acquire())
            {
                Debug.Assert(_target != null);
                Debug.Assert(_target.Dispatcher.Thread == Thread.CurrentThread);
 
                DisconnectHostedVisualOnAllChannels();
 
                _target = null;
            }
        }
 
        /// <summary>
        /// Should be called from the VisualTarget thread
        /// when it is safe to access the composition node
        /// and out of band channel from the VisualTarget thread
        /// to allow for the handle duplication/channel commit
        /// </summary>
        internal object DoHandleDuplication(object channel)
        {
            DUCE.ResourceHandle targetsHandle = DUCE.ResourceHandle.Null;
 
            using (CompositionEngineLock.Acquire())
            {
                targetsHandle = _target._contentRoot.DuplicateHandle(_target.OutOfBandChannel, (DUCE.Channel)channel);
 
                Debug.Assert(!targetsHandle.IsNull);
 
                _target.OutOfBandChannel.CloseBatch();
                _target.OutOfBandChannel.Commit();
            }
            
            return targetsHandle;
        }
 
        #endregion Internal Methods
 
 
        //----------------------------------------------------------------------
        //
        //  Private Methods
        //
        //----------------------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// Connects the hosted visual on a channel if necessary.
        /// </summary>
        private void EnsureHostedVisualConnected(DUCE.Channel channel)
        {
            //
            // Conditions for connecting VisualTarget to Host Visual:-
            // 1. The channel on which we are rendering should not be synchronous. This
            //    scenario is not supported currently.
            // 2. VisualTarget should not be null.
            // 3. They should not be already connected.
            //
            if (!(channel.IsSynchronous)
                && _target != null
                && !_connectedChannels.Contains(channel))
            {
                Debug.Assert(IsOnChannel(channel));
 
                DUCE.ResourceHandle targetsHandle = DUCE.ResourceHandle.Null;
 
                bool doDuplication = true;
 
                //
                // If HostVisual and VisualTarget are on same thread, then we just addref
                // VisualTarget. Otherwise, if on different threads, then we duplicate
                // VisualTarget onto Hostvisual's channel.
                //
                if (_target.CheckAccess())
                {
                    Debug.Assert(_target._contentRoot.IsOnChannel(channel));
                    Debug.Assert(_target.OutOfBandChannel == MediaContext.CurrentMediaContext.OutOfBandChannel);
                    bool created = _target._contentRoot.CreateOrAddRefOnChannel(this, channel, VisualTarget.s_contentRootType);
                    Debug.Assert(!created);
                    targetsHandle = _target._contentRoot.GetHandle(channel);
                }
                else
                {
                    //
                    // Duplicate the target's handle onto our channel.
                    //
                    // We must wait synchronously for the _targets Dispatcher to call
                    // back and do handle duplication. We can't do handle duplication
                    // on this thread because access to the _target CompositionNode
                    // is not synchronized. If we duplicated here, we could potentially
                    // corrupt the _target OutOfBandChannel or the CompositionNode
                    // MultiChannelResource. We have to wait synchronously because
                    // we need the resulting duplicated handle to hook up as a child
                    // to this HostVisual.
                    //
 
                    object returnValue = _target.Dispatcher.Invoke(
                        DispatcherPriority.Normal,
                        TimeSpan.FromMilliseconds(1000),
                        new DispatcherOperationCallback(DoHandleDuplication),
                        channel
                        );
 
                    //
                    // Duplication and flush is complete, we can resume processing
                    // Only if the Invoke succeeded will we have a handle returned.
                    //
                    if (returnValue != null)
                    {
                        targetsHandle = (DUCE.ResourceHandle)returnValue;
                    }
                    else
                    {
                        // The Invoke didn't complete
                        doDuplication = false;
                    }
                }
 
                if (doDuplication)
                {
                    if (!targetsHandle.IsNull)
                    {
                        using (CompositionEngineLock.Acquire())
                        {
                            DUCE.CompositionNode.InsertChildAt(
                                _proxy.GetHandle(channel),
                                targetsHandle,
                                0,
                                channel);
                        }
 
                        _connectedChannels.Add(channel);
 
                        //
                        // Indicate that that content composition root has been
                        // connected, this needs to be taken into account to
                        // properly manage children of this visual.
                        //
 
                        SetFlags(channel, true, VisualProxyFlags.IsContentNodeConnected);
                    }
                }
                else
                {
                    //
                    // We didn't get a handle, because _target belongs to a
                    // different thread, and the Invoke operation failed. We can't do
                    // anything except try again in the next render pass. We can't
                    // call Invalidate during the render pass because it pushes up
                    // flags that are being modified within the render pass, so get
                    // the local Dispatcher to do it for us later.
                    //
                    Dispatcher.BeginInvoke(
                        DispatcherPriority.Normal,
                        (DispatcherOperationCallback)delegate(object args)
                        {
                            Invalidate();
                            return null;
                        },
                        null
                        );
                }
            }
        }
 
 
        /// <summary>
        /// Disconnects the hosted visual on all channels we have
        /// connected it to.
        /// </summary>
        private void DisconnectHostedVisualOnAllChannels()
        {
            foreach (DUCE.Channel channel in _connectedChannels)
            {
                DisconnectHostedVisual(
                    channel,
                    /* removeChannelFromCollection */ false);
            }
 
            _connectedChannels.Clear();
        }
 
 
        /// <summary>
        /// Disconnects the hosted visual on a channel.
        /// </summary>
        private void DisconnectHostedVisual(
            DUCE.Channel channel,
            bool removeChannelFromCollection)
        {
            if (_target != null && _connectedChannels.Contains(channel))
            {
                DUCE.CompositionNode.RemoveChild(
                    _proxy.GetHandle(channel),
                    _target._contentRoot.GetHandle(channel),
                    channel
                    );
 
                //
                // Release the targets handle. If we had duplicated the handle,
                // then this removes the duplicated handle, otherwise just decrease
                // the ref count for VisualTarget.
                //
 
                _target._contentRoot.ReleaseOnChannel(channel);
 
                SetFlags(channel, false, VisualProxyFlags.IsContentNodeConnected);
 
                if (removeChannelFromCollection)
                {
                    _connectedChannels.Remove(channel);
                }
            }
        }
 
 
        /// <summary>
        /// Invalidate this visual.
        /// </summary>
        private void Invalidate()
        {
            SetFlagsOnAllChannels(true, VisualProxyFlags.IsContentDirty);
 
            PropagateChangedFlags();
        }
 
        #endregion Private Methods
 
 
        //----------------------------------------------------------------------
        //
        //  Private Fields
        //
        //----------------------------------------------------------------------
 
        #region Private Fields
 
        /// <summary>
        /// The hosted visual target.
        /// </summary>
        /// <remarks>
        /// This field is free-threaded and should be accessed from under a lock.
        /// </remarks>
        private VisualTarget _target;
 
        /// <summary>
        /// The channels we have marshalled the visual target composition root.
        /// </summary>
        /// <remarks>
        /// This field is free-threaded and should be accessed from under a lock.
        /// </remarks>
        private List<DUCE.Channel> _connectedChannels = new List<DUCE.Channel>();
 
        #endregion Private Fields
    }
}