MiniMapControl.xaml.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / NetFx40 / Tools / System.Activities.Presentation / System / Activities / Presentation / MiniMap / MiniMapControl.xaml.cs / 1305376 / MiniMapControl.xaml.cs

                            //---------------------------------------------------------------- 
// Copyright (c) Microsoft Corporation.  All rights reserved.
//---------------------------------------------------------------
#if DEBUG
//#define MINIMAP_DEBUG 
#endif
 
namespace System.Activities.Presentation 
{
    using System; 
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media; 
    using System.Windows.Shapes;
    using System.Diagnostics; 
    using System.Windows.Threading; 
    using System.Globalization;
 
    // This class is a control displaying minimap of the attached scrollableview control
    // this class's functionality is limited to delegating events to minimap view controller

    partial class MiniMapControl : UserControl 
    {
        public static readonly DependencyProperty MapSourceProperty = 
                DependencyProperty.Register("MapSource", 
                typeof(ScrollViewer),
                typeof(MiniMapControl), 
                new FrameworkPropertyMetadata(null,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback(OnMapSourceChanged)));
 
        MiniMapViewController lookupWindowManager;
        bool isMouseDown = false; 
 
        public MiniMapControl()
        { 
            InitializeComponent();
            this.lookupWindowManager = new MiniMapViewController(this.lookupCanvas, this.lookupWindow, this.contentGrid);
        }
 
        public ScrollViewer MapSource
        { 
            get { return GetValue(MapSourceProperty) as ScrollViewer; } 
            set { SetValue(MapSourceProperty, value); }
        } 

        static void OnMapSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            MiniMapControl mapControl = (MiniMapControl)sender; 
            mapControl.lookupWindowManager.MapSource = mapControl.MapSource;
        } 
 
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        { 
            base.OnMouseLeftButtonDown(e);
            if (this.lookupWindowManager.StartMapLookupDrag(e))
            {
                this.CaptureMouse(); 
                this.isMouseDown = true;
            } 
        } 

        protected override void OnMouseMove(MouseEventArgs e) 
        {
            base.OnMouseMove(e);
            if (this.isMouseDown)
            { 
                this.lookupWindowManager.DoMapLookupDrag(e);
            } 
        } 

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) 
        {
            base.OnMouseLeftButtonUp(e);
            if (this.isMouseDown)
            { 
                Mouse.Capture(null);
                this.isMouseDown = false; 
                this.lookupWindowManager.StopMapLookupDrag(); 
            }
        } 

        protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
        {
            this.lookupWindowManager.CenterView(e); 
            e.Handled = true;
            base.OnMouseDoubleClick(e); 
        } 

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) 
        {
            base.OnRenderSizeChanged(sizeInfo);
            this.lookupWindowManager.MapViewSizeChanged(sizeInfo);
        } 

        // This class wraps positioning and calculating logic of the map view lookup window 
        // It is also responsible for handling mouse movements 

        internal class LookupWindow 
        {
            Point mousePosition;
            Rectangle lookupWindowRectangle;
            MiniMapViewController parent; 

 
            public LookupWindow(MiniMapViewController parent, Rectangle lookupWindowRectangle) 
            {
                this.mousePosition = new Point(); 
                this.parent = parent;
                this.lookupWindowRectangle = lookupWindowRectangle;
            }
 
            public double Left
            { 
                get { return Canvas.GetLeft(this.lookupWindowRectangle); } 
                set
                { 
                    //check if left corner is within minimap's range - clip if necessary
                    double left = Math.Max(value - this.mousePosition.X, 0.0);
                    //check if right corner is within minimap's range - clip if necessary
                    left = (left + Width > this.parent.MapWidth ? this.parent.MapWidth - Width : left); 
                    //update canvas
                    Canvas.SetLeft(this.lookupWindowRectangle, left); 
                } 
            }
 
            public double Top
            {
                get { return Canvas.GetTop(this.lookupWindowRectangle); }
                set 
                {
                    //check if top corner is within minimap's range - clip if necessary 
                    double top = Math.Max(value - this.mousePosition.Y, 0.0); 
                    //check if bottom corner is within minimap's range - clip if necessary
                    top = (top + Height > this.parent.MapHeight ? this.parent.MapHeight - Height : top); 
                    //update canvas
                    Canvas.SetTop(this.lookupWindowRectangle, top);
                }
            } 

            public double Width 
            { 
                get { return this.lookupWindowRectangle.Width; }
                set { this.lookupWindowRectangle.Width = value; } 
            }

            public double Height
            { 
                get { return this.lookupWindowRectangle.Height; }
                set { this.lookupWindowRectangle.Height = value; } 
            } 

            public double MapCenterXPoint 
            {
                get { return this.Left + (this.Width / 2.0); }
            }
 
            public double MapCenterYPoint
            { 
                get { return this.Top + (this.Height / 2.0); } 
            }
 
            public double MousePositionX
            {
                get { return this.mousePosition.X; }
            } 

            public double MousePositionY 
            { 
                get { return this.mousePosition.Y; }
            } 

            public bool IsSelected
            {
                get; 
                private set;
            } 
 
            public void SetPosition(double left, double top)
            { 
                Left = left;
                Top = top;
            }
 
            public void SetSize(double width, double height)
            { 
                Width = width; 
                Height = height;
            } 

            //whenever user clicks on the minimap, i check if clicked object is
            //a lookup window - if yes - i store mouse offset within the window
            //and mark it as selected 
            public bool Select(object clickedItem, Point clickedPosition)
            { 
                if (clickedItem == this.lookupWindowRectangle) 
                {
                    this.mousePosition = clickedPosition; 
                    this.IsSelected = true;
                }
                else
                { 
                    Unselect();
                } 
                return this.IsSelected; 
            }
 
            public void Unselect()
            {
                this.mousePosition.X = 0;
                this.mousePosition.Y = 0; 
                this.IsSelected = false;
            } 
 
            public void Center(double x, double y)
            { 
                Left = x - (Width / 2.0);
                Top = y - (Height / 2.0);
            }
 
            public void Refresh(bool unselect)
            { 
                if (unselect) 
                {
                    Unselect(); 
                }
                SetPosition(Left, Top);
            }
        } 

        // This class is responsible for calculating size of the minimap's view area, as well as 
        // maintaining the bi directional link between minimap and control beeing visualized. 
        // Whenever minimap's view window position is updated, the control's content is scrolled
        // to calculated position 
        // Whenever control's content is resized or scrolled, minimap reflects that change in
        // recalculating view's window size and/or position

        internal class MiniMapViewController 
        {
            Canvas lookupCanvas; 
            Grid contentGrid; 
            ScrollViewer mapSource;
            LookupWindow lookupWindow; 

            public MiniMapViewController(Canvas lookupCanvas, Rectangle lookupWindowRectangle, Grid  contentGrid)
            {
                this.lookupWindow = new LookupWindow(this, lookupWindowRectangle); 
                this.lookupCanvas = lookupCanvas;
                this.contentGrid = contentGrid; 
            } 

            public ScrollViewer MapSource 
            {
                get { return this.mapSource; }
                set
                { 
                    this.mapSource = value;
                    //calculate view's size and set initial position 
                    this.lookupWindow.Unselect(); 
                    this.CalculateLookupWindowSize();
                    this.lookupWindow.SetPosition(0.0, 0.0); 
                    CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top);
                    this.UpdateContentGrid();

                    if (null != this.mapSource && null != this.mapSource.Content && this.mapSource.Content is Viewbox) 
                    {
                        Viewbox content = (Viewbox)this.mapSource.Content; 
                        //hook up for all content size changes - handle them in OnContentSizeChanged method 
                        content.SizeChanged += (s, e) =>
                            { 
                                this.contentGrid.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
                                    new Action(() => { OnContentSizeChanged(s, e); }));
                            };
 
                        //in case of scroll viewer - there are two different events to handle in one notification:
                        this.mapSource.ScrollChanged += (s, e) => 
                        { 
                            this.contentGrid.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
                                new Action(() => 
                                    {
                                        //when user changes scroll position - delegate it to OnMapSourceScrollChange
                                        if (0.0 != e.HorizontalChange || 0.0 != e.VerticalChange)
                                        { 
                                            OnMapSourceScrollChanged(s, e);
                                        } 
                                        //when size of the scroll changes delegate it to OnContentSizeChanged 
                                        if (0.0 != e.ViewportWidthChange || 0.0 != e.ViewportHeightChange)
                                        { 
                                            OnContentSizeChanged(s, e);
                                        }
                                    }));
                        }; 
                        this.OnMapSourceScrollChanged(this, null);
                        this.OnContentSizeChanged(this, null); 
                    } 
                }
            } 

            //bunch of helper getters - used to increase algorithm readability and provide default
            //values, always valid values, so no additional divide-by-zero checks are neccessary
 
            public double MapWidth
            { 
                get { return this.contentGrid.ActualWidth - 2 * (this.contentGrid.ColumnDefinitions[0].MinWidth); } 
            }
 
            public double MapHeight
            {
                get { return this.contentGrid.ActualHeight - 2 * (this.contentGrid.RowDefinitions[0].MinHeight); }
            } 

            internal LookupWindow LookupWindow 
            { 
                get { return this.lookupWindow; }
            } 

            double VisibleSourceWidth
            {
                get { return (null == MapSource || 0.0 == MapSource.ViewportWidth ? 1.0 : MapSource.ViewportWidth); } 
            }
 
            double VisibleSourceHeight 
            {
                get { return (null == MapSource || 0.0 == MapSource.ViewportHeight ? 1.0 : MapSource.ViewportHeight); } 
            }


            public void CenterView(MouseEventArgs args) 
            {
                Point pt = args.GetPosition(this.lookupCanvas); 
                this.lookupWindow.Unselect(); 
                this.lookupWindow.Center(pt.X, pt.Y);
                CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top); 
            }

            public void MapViewSizeChanged(SizeChangedInfo sizeInfo)
            { 
                this.OnContentSizeChanged(this, EventArgs.Empty);
                this.lookupWindow.Unselect(); 
                this.CalculateLookupWindowSize(); 
                if (sizeInfo.WidthChanged && 0.0 != sizeInfo.PreviousSize.Width)
                { 
                    this.lookupWindow.Left =
                        this.lookupWindow.Left * (sizeInfo.NewSize.Width / sizeInfo.PreviousSize.Width);
                }
                if (sizeInfo.HeightChanged && 0.0 != sizeInfo.PreviousSize.Height) 
                {
                    this.lookupWindow.Top = 
                        this.lookupWindow.Top * (sizeInfo.NewSize.Height / sizeInfo.PreviousSize.Height); 
                }
            } 

            public bool StartMapLookupDrag(MouseEventArgs args)
            {
                bool result = false; 
                HitTestResult hitTest =
                    VisualTreeHelper.HitTest(this.lookupCanvas, args.GetPosition(this.lookupCanvas)); 
 
                if (null != hitTest && null != hitTest.VisualHit)
                { 
                    Point clickedPosition = args.GetPosition(hitTest.VisualHit as IInputElement);
                    result = this.lookupWindow.Select(hitTest.VisualHit, clickedPosition);
                }
                return result; 
            }
 
            public void StopMapLookupDrag() 
            {
                this.lookupWindow.Unselect(); 
            }

            public void DoMapLookupDrag(MouseEventArgs args)
            { 
                if (args.LeftButton == MouseButtonState.Released && this.lookupWindow.IsSelected)
                { 
                    this.lookupWindow.Unselect(); 
                }
                if (this.lookupWindow.IsSelected) 
                {
                    Point to = args.GetPosition(this.lookupCanvas);
                    this.lookupWindow.SetPosition(to.X, to.Y);
                    CalculateMapPosition( 
                        to.X - this.lookupWindow.MousePositionX,
                        to.Y - this.lookupWindow.MousePositionY); 
                } 
            }
 
            void CalculateMapPosition(double left, double top)
            {
                if (null != MapSource && 0 != this.lookupWindow.Width && 0 != this.lookupWindow.Height)
                { 
                    MapSource.ScrollToHorizontalOffset((left / this.lookupWindow.Width) * VisibleSourceWidth);
                    MapSource.ScrollToVerticalOffset((top / this.lookupWindow.Height) * VisibleSourceHeight); 
                } 
            }
 
            //this method calculates position of the lookup window on the minimap - it should be triggered when:
            // - user modifies a scroll position by draggin a scroll bar
            // - scroll sizes are updated by change of the srcollviewer size
            // - user drags minimap view - however, in this case no lookup update takes place 
            void OnMapSourceScrollChanged(object sender, ScrollChangedEventArgs e)
            { 
                if (!this.lookupWindow.IsSelected && null != MapSource) 
                {
                    this.lookupWindow.Unselect(); 
                    this.lookupWindow.Left =
                        this.lookupWindow.Width * (MapSource.HorizontalOffset / VisibleSourceWidth);

                    this.lookupWindow.Top = 
                        this.lookupWindow.Height * (MapSource.VerticalOffset / VisibleSourceHeight);
                } 
                DumpData("OnMapSourceScrollChange"); 
            }
 
            //this method calculates size and position of the minimap view - it should be triggered when:
            // - zoom changes
            // - visible size of the scrollviewer (which is map source) changes
            // - visible size of the minimap control changes 
            void OnContentSizeChanged(object sender, EventArgs e)
            { 
                //get old center point coordinates 
                double centerX = this.lookupWindow.MapCenterXPoint;
                double centeryY = this.lookupWindow.MapCenterYPoint; 
                //update the minimap itself
                this.UpdateContentGrid();
                //calculate new size
                this.CalculateLookupWindowSize(); 
                //try to center around old center points (window may be moved if doesn't fit)
                this.lookupWindow.Center(centerX, centeryY); 
                //update scrollbars 
                CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top);
                DumpData("OnContentSizeChanged"); 
            }

            //this method calculates size of the lookup rectangle, based on the visible size of the object,
            //including current map width 
            void CalculateLookupWindowSize()
            { 
                double width = this.MapWidth; 
                double height = this.MapHeight;
 
                if (this.MapSource.ScrollableWidth != 0 && this.MapSource.ExtentWidth != 0)
                {
                    width = (this.MapSource.ViewportWidth / this.MapSource.ExtentWidth) * this.MapWidth;
                } 
                else
                { 
                    //width = 
                }
                if (this.MapSource.ScrollableHeight != 0 && this.MapSource.ExtentHeight != 0) 
                {
                    height = (this.MapSource.ViewportHeight / this.MapSource.ExtentHeight) * this.MapHeight;
                }
                this.lookupWindow.SetSize(width, height); 
            }
 
            //this method updates content grid of the minimap - most likely, minimap view will be scaled to fit 
            //the window - so there will be some extra space visible on the left and right sides or above and below actual
            //mini map view - we don't want lookup rectangle to navigate within that area, since it is not representing 
            //actual view - we increase margins of the minimap to disallow this
            void UpdateContentGrid()
            {
                bool resetToDefault = true; 
                if (this.MapSource.ExtentWidth != 0 && this.MapSource.ExtentHeight != 0)
                { 
                    //get width to height ratio from map source - we want to display our minimap in the same ratio 
                    double widthToHeightRatio = this.MapSource.ExtentWidth / this.MapSource.ExtentHeight;
 
                    //calculate current width to height ratio on the minimap
                    double height = this.contentGrid.ActualHeight;
                    double width = this.contentGrid.ActualWidth;
                    //ideally - it should be 1 - whole view perfectly fits minimap 
                    double minimapWidthToHeightRatio = (height * widthToHeightRatio)/(width > 1.0 ? width : 1.0);
 
                    //if value is greater than one - we have to reduce height 
                    if (minimapWidthToHeightRatio > 1.0)
                    { 
                        double margin = (height - (height / minimapWidthToHeightRatio))/2.0;

                        this.contentGrid.ColumnDefinitions[0].MinWidth = 0.0;
                        this.contentGrid.ColumnDefinitions[2].MinWidth = 0.0; 
                        this.contentGrid.RowDefinitions[0].MinHeight = margin;
                        this.contentGrid.RowDefinitions[2].MinHeight = margin; 
                        resetToDefault = false; 
                    }
                    //if value is less than one - we have to reduce width 
                    else if (minimapWidthToHeightRatio < 1.0)
                    {
                        double margin = (width - (width * minimapWidthToHeightRatio)) / 2.0;
                        this.contentGrid.ColumnDefinitions[0].MinWidth = margin; 
                        this.contentGrid.ColumnDefinitions[2].MinWidth = margin;
                        this.contentGrid.RowDefinitions[0].MinHeight = 0.0; 
                        this.contentGrid.RowDefinitions[2].MinHeight = 0.0; 
                        resetToDefault = false;
                    } 
                }
                //perfect match or nothing to display - no need to setup margins
                if (resetToDefault)
                { 
                    this.contentGrid.ColumnDefinitions[0].MinWidth = 0.0;
                    this.contentGrid.ColumnDefinitions[2].MinWidth = 0.0; 
                    this.contentGrid.RowDefinitions[0].MinHeight = 0.0; 
                    this.contentGrid.RowDefinitions[2].MinHeight = 0.0;
                } 
            }

            [Conditional("MINIMAP_DEBUG")]
            void DumpData(string prefix) 
            {
                System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} ScrollViewer: EWidth {1}, EHeight {2}, AWidth {3}, AHeight {4}, ViewPortW {5} ViewPortH {6}", prefix, mapSource.ExtentWidth, mapSource.ExtentHeight, mapSource.ActualWidth, mapSource.ActualHeight, mapSource.ViewportWidth, mapSource.ViewportHeight)); 
            } 

        } 
    }
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK